A BPE tokenizer for digital ink.
Project description
Tokink
Tokink is a Byte-Pair Encoding (BPE) tokenizer designed specifically for digital ink (online handwriting). It enables a compressed and discrete representation of digital ink, improving compatibility with the transformer architecture.
Installation
pip install tokink
Quick Start
from tokink import Ink, Tokinkizer
from tokink.processor import scale, to_int
# Load or create digital ink
ink = Ink.example() # or Ink.from_json("path/to/ink.json")
# Preprocess: scale down for better compression
ink = to_int(scale(ink, 1/16))
# Initialize tokenizer
tokenizer = Tokinkizer.from_pretrained(vocab_size=32_000)
# Encode ink to tokens
tokens = tokenizer.encode(ink)
# Decode tokens back to ink
reconstructed_ink = tokenizer.decode(tokens)
# Visualize
reconstructed_ink.plot()
💡 Try it interactively: Check out
examples/quickstart.ipynbfor a hands-on notebook walkthrough.
Background & Motivation
Digital ink is crucial for digital note-taking applications. Two important ML tasks involve digital ink: Handwritten Text Recognition (HTR) and Handwritten Text Generation (HTG).
Traditional Representation
The natural representation of digital ink as list[list[tuple[float, float]]] (strokes containing points) is awkward for modern ML models. Consider drawing an equal sign:
[
[(0.0, 0.0), (2.0, 0.0)], # First horizontal line
[(0.0, 1.0), (2.0, 1.0)] # Second horizontal line
]
A common solution is the Point-3 format: list[(Δx, Δy, p)], where p is a binary pen state (pen down/up):
[(2.0, 0.0, 1), (-2.0, 1.0, 0), (2.0, 0.0, 1)]
However, this representation has issues:
- No compression: Every coordinate change increases sequence length by one
- Incompatible with transformers: Transformers more naturally model distribution of discrete sequences
Naive Token Approach
One might try rounding coordinates to integers and treating each xy-coordinate as a token:
["[0, 0]", "[2, 0]", "[UP]", "[0, 1]", "[2, 1]"]
This has critical problems:
- Massive vocabulary: A 1000×1000 canvas requires 1 million tokens
- Extreme sparsity: Low compression with BPE
- Out-of-vocabulary (OOV): When training on a 1000×1000 canvas, "[1001, 1000]" token is not in model's vocabulary
The Tokink Solution
Tokink uses a novel approach inspired by Bresenham's line algorithm, which rasterizes lines on pixelated displays. We break all pen movements into 8 directional arrows: ↑, ↓, ←, →, ↖, ↗, ↙, ↘.
For example, rendering a line from (0, 0) to (10, 4):
Combined with special [UP] and [DOWN] tokens for pen state, we can express any digital ink using just 10 base tokens. The equal sign becomes:
["[DOWN]", "→", "→", "[UP]", "←", "↙", "[DOWN]", "→", "→", "[UP]"]
This has several benefits:
- Tiny vocabulary: Only 10 base tokens before BPE
- High compression: Reduced sparsity enables effective BPE merging
- No OOV: Every digital ink can be tokenized by the 10 base tokens.
Example tokenization of "hello":
Usage Examples
Handwritten Text Recognition (HTR)
Complete pipeline for recognizing handwritten text:
from tokink import Ink, Tokinkizer
from tokink.processor import jitter, rotate, scale, to_int
SCALE_FACTOR = 1 / 16
VOCAB_SIZE = 32_000
def preprocess_ink(ink: Ink) -> Ink:
"""Scale down coordinates for better tokenization compression."""
return scale(ink, SCALE_FACTOR)
def augment_ink(ink: Ink) -> Ink:
"""Apply rotation and jittering for data augmentation."""
ink = rotate(ink, angle_degrees=5)
ink = jitter(ink, sigma=0.5)
return ink
# Load dataset
dataset = [
(Ink.example(), "By Trevor Williams. A move"),
# Add more (ink, label) pairs...
]
# Preprocess and augment
processed_data = []
for ink, label in dataset:
# Original (preprocessed)
processed_data.append((to_int(preprocess_ink(ink)), label))
# Augmented (preprocess then augment)
processed_data.append((to_int(augment_ink(preprocess_ink(ink))), label))
# Tokenize
tokenizer = Tokinkizer.from_pretrained(vocab_size=VOCAB_SIZE)
tokenized_data = [(tokenizer.encode(ink), label) for ink, label in processed_data]
# Train your model with tokenized data
# model.train(tokenized_data)
See examples/htr.py for the complete example.
Handwritten Text Generation (HTG)
Generate handwriting from text prompts:
from tokink import Ink, Tokinkizer
from tokink.processor import resample, scale, smooth, to_int
SCALE_FACTOR = 1 / 16
VOCAB_SIZE = 32_000
def postprocess_generated(ink: Ink) -> Ink:
"""
Post-process generated ink for smooth, natural appearance.
Steps:
1. Scale back to original coordinate space
2. Resample to increase point density
3. Apply Savitzky-Golay smoothing to reduce tokenization artifacts
"""
ink = scale(ink, 1 / SCALE_FACTOR)
ink = resample(ink, sample_every=2)
ink = smooth(ink)
return ink
# Initialize tokenizer
tokenizer = Tokinkizer.from_pretrained(vocab_size=VOCAB_SIZE)
# Generate tokens from your model
# generated_tokens = model.generate("Hello world")
# Decode and post-process
raw_ink = tokenizer.decode(generated_tokens)
smooth_ink = postprocess_generated(raw_ink)
smooth_ink.plot()
See examples/htg.py for the complete example.
API Reference
Core Classes
-
Ink: Represents digital ink as strokesInk.example(): Load example inkInk.from_json(path): Load from JSON fileplot(): Visualize the ink
-
Tokinkizer: BPE tokenizer for digital inkfrom_pretrained(vocab_size): Load pretrained tokenizerencode(ink): Convert ink to token IDsdecode(tokens): Convert token IDs back to ink
Preprocessing Functions
All available in tokink.processor:
scale(ink, factor): Scale coordinatesrotate(ink, angle_degrees): Rotate inkjitter(ink, sigma): Add Gaussian noise for augmentationresample(ink, sample_every): Resample points for density controlsmooth(ink): Apply Savitzky-Golay smoothingto_int(ink): Convert float coordinates to integers
License
MIT License - see LICENSE for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tokink-0.1.1.tar.gz.
File metadata
- Download URL: tokink-0.1.1.tar.gz
- Upload date:
- Size: 1.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d651aae2b11857390eb51b42373d125ea10fd8b457989bc1643b124d0a5d36ec
|
|
| MD5 |
594534c2eeec56068a1941794ae6ceb6
|
|
| BLAKE2b-256 |
e08ba90a253cf74d498f817a5a4d767e5bfa07a083002b93fc40cce854fbb770
|
Provenance
The following attestation bundles were made for tokink-0.1.1.tar.gz:
Publisher:
python-publish.yml on douglasswng/tokink
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tokink-0.1.1.tar.gz -
Subject digest:
d651aae2b11857390eb51b42373d125ea10fd8b457989bc1643b124d0a5d36ec - Sigstore transparency entry: 821004741
- Sigstore integration time:
-
Permalink:
douglasswng/tokink@141a7f70cca796368815268421969751fb9a009c -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/douglasswng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@141a7f70cca796368815268421969751fb9a009c -
Trigger Event:
release
-
Statement type:
File details
Details for the file tokink-0.1.1-py3-none-any.whl.
File metadata
- Download URL: tokink-0.1.1-py3-none-any.whl
- Upload date:
- Size: 1.7 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4bb9b8cb8ebd267702720cd74ffc51f50189f9a0ea42cdfaae99bd5be2450aa3
|
|
| MD5 |
7c5d9bc364c67ef6c6ff9e2967e31b98
|
|
| BLAKE2b-256 |
0b5712c718eb68fb10bec7b23830ec01fd1180ef2e4c99a15789d338d6b8e716
|
Provenance
The following attestation bundles were made for tokink-0.1.1-py3-none-any.whl:
Publisher:
python-publish.yml on douglasswng/tokink
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tokink-0.1.1-py3-none-any.whl -
Subject digest:
4bb9b8cb8ebd267702720cd74ffc51f50189f9a0ea42cdfaae99bd5be2450aa3 - Sigstore transparency entry: 821004745
- Sigstore integration time:
-
Permalink:
douglasswng/tokink@141a7f70cca796368815268421969751fb9a009c -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/douglasswng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@141a7f70cca796368815268421969751fb9a009c -
Trigger Event:
release
-
Statement type: