ERML — Emotion Recognition ML
A pip-installable Python SDK for facial emotion recognition. Drop it into your own project, pass it a frame, get back structured emotion data — no camera or display logic included.
from erml import EmotionDetector, FacePrediction, format_results
detector = EmotionDetector()
results = detector.analyze("photo.jpg")
# Object access — full IDE autocomplete
print(results[0].emotion, results[0].confidence)
# Or export to plain dict
print(format_results(results))
Face 1 [x=85 y=67 w=259 h=259]
Emotion : Neutral
Confidence : 42.9%
All scores :
neutral 42.9% ████████
sad 18.2% ███
fear 15.4% ███
angry 10.2% ██
happy 7.5% █
surprise 5.2% █
disgust 0.1%
Install
pip install erml
Or from source:
git clone https://github.com/sid-lakhani/erml
cd erml
uv venv --python 3.12
source .venv/bin/activate.fish # or: source .venv/bin/activate (bash/zsh)
uv pip install -r requirements.txt -r requirements-dev.txt
uv pip install -e .
Requires Python 3.8–3.12. TensorFlow does not yet support Python 3.13+. To train, additionally install:
uv pip install -r requirements-train.txt
Usage
Basic
from erml import EmotionDetector
detector = EmotionDetector()
results = detector.analyze("photo.jpg")
Input types
import cv2
import numpy as np
from PIL import Image
# File path
results = detector.analyze("photo.jpg")
# OpenCV BGR array
frame = cv2.imread("photo.jpg")
results = detector.analyze(frame)
# PIL Image
pil_img = Image.open("photo.jpg")
results = detector.analyze(pil_img)
Output
analyze() returns a list of dicts — one per detected face:
[
FacePrediction(
emotion="happy", # top predicted emotion
confidence=0.87, # float 0–1
all={ # scores for all 7 emotions
"angry": 0.01,
"disgust": 0.00,
"fear": 0.02,
"happy": 0.87,
"sad": 0.03,
"surprise": 0.05,
"neutral": 0.02
},
bbox=BoundingBox(x=120, y=80, w=64, h=64)
)
]
Access fields directly with full IDE autocomplete:
result = results[0]
print(result.emotion) # "happy"
print(result.bbox.x) # 120
Or export to a plain dictionary (e.g. for JSON logging):
raw = results[0].model_dump()
raw_list = [r.model_dump() for r in results]
Returns [] if no face is detected. Never raises on empty input.
Human-readable output
from erml import EmotionDetector, format_results
detector = EmotionDetector()
print(format_results(detector.analyze("photo.jpg")))
Emotions
angry · disgust · fear · happy · sad · surprise · neutral
Trained on FER-2013.
Training
To retrain from scratch, download FER-2013 and place it at dataset/ organized by emotion subfolder, then:
python training/train.py
Trains for up to 20 epochs with early stopping, saves best weights to erml/assets/erml_v1.h5.
Quick inference script
python examples/test_inference.py photo.jpg
Webcam demo
python examples/webcam_demo.py
python examples/webcam_demo.py --camera 1 # alternate camera index
Press Q to quit. Draws bounding boxes and emotion labels in real time.
Tests
pytest tests/ -v
All tests run fully headless — no camera, no display, no trained model required.
Project structure
erml/
├── erml/
│ ├── __init__.py # public API: EmotionDetector, FacePrediction, format_results
│ ├── constants.py # EMOTION_LABELS and shared constants
│ ├── detector.py # EmotionDetector class (ONNX & YuNet runtime)
│ ├── download.py # atomic auto-downloader for ONNX weights
│ ├── preprocess.py # face ROI preprocessing
│ ├── schemas.py # Pydantic output models (FacePrediction, BoundingBox)
│ ├── utils.py # format_results helper
│ └── assets/ # ONNX weights cached here automatically
├── training/
│ ├── model.py # TF/Keras CNN architecture definition
│ └── train.py # training script (requires: pip install erml[train])
├── tests/
├── scripts/
│ └── export_onnx.py # utility to export trained Keras models to ONNX
├── examples/
└── pyproject.toml
Versioning
| Version | Status | Notes |
|---|---|---|
| v0.1.0 | current | FER-2013, basic CNN, ~52% val accuracy |
| v0.2.0 | planned | Improved architecture, confidence calibration |
| v1.0.0 | planned | Stable public API, full docs |
License
MIT
Metadata
Release files for erml 1.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| erml-1.0.1.tar.gz | 31.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| erml-1.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 46.2 kB
Release files / erml-1.0.1.tar.gz
| Download URL | erml-1.0.1.tar.gz |
|---|---|
| Size | 31.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3873be9d178113dd17cb0117cbeee6e4860b99648b9cb27fd114e3598440a3d4
|
|
BLAKE2b-256 checksum How to use checksums |
af7d31370926ede38fed689700646d8d8fa38913db05d675ec5564b6aa20b306
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logRelease files / erml-1.0.1-py3-none-any.whl
| Download URL | erml-1.0.1-py3-none-any.whl |
|---|---|
| Size | 14.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e6f9bfddc49a53d3d64e19834a24074f29ef10b8d9cf4ceac51780f06c77e797
|
|
BLAKE2b-256 checksum How to use checksums |
e504e4b8d7f0337a811c3bc0e0515ab7d3d66e3009b2867f4858451c6a79c28b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency log