islkit
Indian Sign Language (ISL) recognition from MediaPipe Holistic landmarks.
islkit turns camera frames into glosses. It covers every step: a landmark feature encoder, a dual-branch temporal convolutional network (TCN), a loader for the INCLUDE dataset, live inference with a confidence-gated decline, and a small HTTP/SSE recognition service. It runs on CPU and is built to work on small ARM boards as well as laptops.
camera → MediaPipe Holistic → RawFrame → encode_clip (T×352) → TCN → gloss | None
Install
pip install islkit # inference and the recognition service
pip install "islkit[train]" # + pandas, pyarrow, xgboost for INCLUDE and the baseline
Requires Python 3.12. mediapipe is pinned to 0.10.18, which also pins
numpy<2. That is the last MediaPipe release that keeps mp.solutions and
still runs on ARMv8.0-A (Cortex-A53) boards.
On macOS, XGBoost needs brew install libomp. Don't import torch and
xgboost in the same process: each bundles its own libomp, and the process
aborts with OMP: Error #15. import islkit keeps torch lazy for this reason.
Design
Features, not raw landmarks. Holistic's flattened output has 1,662 values, and 85% of them are face mesh. The encoder reduces each frame to 183 floats (352 with velocity):
| Block | Dims |
|---|---|
| Hand-local shapes (2 × 21 × 3) | 126 |
| Wrist positions in body frame | 6 |
| Upper-body pose (11 landmarks) | 33 |
| Non-manual scalars from face | 4 |
| Validity mask | 14 |
The encoder uses three coordinate frames rather than one normalisation. Body frame: origin at the shoulder midpoint, scaled by shoulder width. Hand-local frame: origin at the wrist, scaled by the wrist-to-middle-MCP distance. Wrist position is recovered separately in the body frame. This keeps handshape separate from location.
Principles the code enforces:
- Hand slots are geometric. Slot 0 is the hand nearer the dominant-side shoulder. MediaPipe's handedness label flips under occlusion and is never read.
- Mask, never zero-fill. A missing hand is not a hand at the origin. A validity bit is carried per part and multiplied through the network.
- The label map is frozen.
LabelMapis saved beside the weights. Class order rebuilt from a directory listing silently shifts indices. - Checkpoints are tied to the encoder. A checkpoint records an encoder
fingerprint, and
SignRecogniserrefuses to serve it through a different encoder. - Store raw landmarks. Recordings hold raw MediaPipe output, so they can be re-encoded when the normalisation changes.
- The model can say "I don't know". Below the confidence threshold a
prediction is
None. For an accessibility device, silence is better than a confident wrong answer.
Usage
Encode a clip
from islkit import encode_clip
from islkit.infer import HolisticExtractor
frames = []
with HolisticExtractor() as extractor:
for frame_bgr in video_frames: # BGR numpy arrays, e.g. from cv2
_, raw, _ = extractor.process(frame_bgr)
frames.append(raw)
clip = encode_clip(frames, T=48) # (48, 352) float32
Recognise a sign
from islkit import SignRecogniser
recogniser = SignRecogniser("classifier.pt", threshold=0.6) # labels_*.json beside it
prediction = recogniser.classify(frames)
print(prediction.gloss, prediction.confidence, prediction.top3)
prediction.gloss is None when the model declines.
Train
from islkit import build_model, load_include
from islkit.model import fit
data = load_include("path/to/isl-mediapipe-holistic-landmarks") # data.X: (N, 48, 352)
model = build_model(n_classes=len(data.label_map))
fit(model, data.X, data.y)
load_include reads the Kaggle
indian-sign-language-mediapipe-holistic-landmarks
dump of INCLUDE and caches the encoded arrays. That dump records no signer or
session, so a class-stratified k-fold over whole clips is the only honest split
available. It still leaks signers, so report numbers from it as optimistic.
For your own recordings, use leave-one-session-out: build_dataset returns a
sessions array for this.
replace_head and freeze_backbone support fine-tuning a pretrained backbone
on a smaller vocabulary. Always report per-class F1 (per_class_f1), not just
aggregate accuracy.
Run the recognition service
import threading
from islkit.infer import ClipStore, HolisticExtractor, SignRecogniser
from islkit.pipeline import CameraSource, RecognitionPipeline
from islkit.server import EventHub, make_server
recogniser = SignRecogniser("classifier.pt")
hub = EventHub()
pipeline = RecognitionPipeline(
recogniser=recogniser,
source_factory=lambda: CameraSource(0, "1280x720"),
on_event=hub.publish,
extractor_factory=HolisticExtractor,
store=ClipStore(),
on_pause=hub.clear_take,
)
threading.Thread(target=pipeline.run, daemon=True).start()
make_server(pipeline, hub).serve_forever() # 127.0.0.1:9978
| Route | Does |
|---|---|
GET /results |
Server-sent events: tracking, takes, predictions |
POST /capture |
Start or pause capture |
GET /health |
Pipeline and subscriber status |
islkit.view.make_view_server serves an optional annotated MJPEG debug view
on a separate port.
Pretrained weights are not shipped with the package.
Guides and experiments
docs/: how the features, dataset, baseline, pretraining and fine-tuning work, each argued from committed results.experiments/: the scripts that produce those results, runnable from a clone of this repository.
Development
uv sync --extra train
uv run pytest
uv run ruff check .
The tests cover the properties that would silently break recognition: encoder invariances, mask gating, label-map and checkpoint round-trips, and the saved-clip layout.
License
MIT. See LICENSE.
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 islkit-0.1.0.tar.gz.
File metadata
- Download URL: islkit-0.1.0.tar.gz
- Upload date:
- Size: 118.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04ee6e652ab5125845af97ff821c4be4a4a9fabde8a437b6f8fe858444806f07
|
|
| MD5 |
cb7fe876b10536bb38545802f6a9eb6a
|
|
| BLAKE2b-256 |
d9573ab921c421a7ac91d3f88ddeb62dcc55ca5d9b392a603a45d4fbfc7c8e19
|
Provenance
The following attestation bundles were made for islkit-0.1.0.tar.gz:
Publisher:
publish.yml on jbrathwa/islkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
islkit-0.1.0.tar.gz -
Subject digest:
04ee6e652ab5125845af97ff821c4be4a4a9fabde8a437b6f8fe858444806f07 - Sigstore transparency entry: 2822382490
- Sigstore integration time:
-
Permalink:
jbrathwa/islkit@6acf88922ff3708aeb6c1fbeadfdc44cb52d788f -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/jbrathwa
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6acf88922ff3708aeb6c1fbeadfdc44cb52d788f -
Trigger Event:
push
-
Statement type:
File details
Details for the file islkit-0.1.0-py3-none-any.whl.
File metadata
- Download URL: islkit-0.1.0-py3-none-any.whl
- Upload date:
- Size: 81.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5d57852660eeb4258372be4c97712e69f4d084f4cf46ab93fdfb1f93edda0481
|
|
| MD5 |
44b6518fa2c8fa6a4318370b21a05eca
|
|
| BLAKE2b-256 |
aba8a1143c656a67ec978556d1b3bd357f2f4c9084ac8b927edd7647197382d8
|
Provenance
The following attestation bundles were made for islkit-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on jbrathwa/islkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
islkit-0.1.0-py3-none-any.whl -
Subject digest:
5d57852660eeb4258372be4c97712e69f4d084f4cf46ab93fdfb1f93edda0481 - Sigstore transparency entry: 2822382510
- Sigstore integration time:
-
Permalink:
jbrathwa/islkit@6acf88922ff3708aeb6c1fbeadfdc44cb52d788f -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/jbrathwa
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6acf88922ff3708aeb6c1fbeadfdc44cb52d788f -
Trigger Event:
push
-
Statement type: