On-device emotion inference from biosignals (heart rate and RR intervals) for Python applications.
- Privacy-first: All processing happens on-device
- Real-time: <5ms inference latency
- Three emotion states: Amused, Calm, Stressed
- Sliding window: 60s window with 5s step (configurable)
- Python 3.8+: Modern Python with type hints
- Thread-safe: Concurrent data ingestion supported
- Zero dependencies: Core functionality requires only NumPy and Pandas
pip install synheart-emotiongit clone https://github.com/synheart-ai/synheart-emotion-python.git
cd synheart-emotion-python
pip install -e .For advanced model loading (scikit-learn, XGBoost):
pip install synheart-emotion[ml]pip install synheart-emotion[dev]# Quick verification
python -c "from synheart_emotion import EmotionEngine, EmotionConfig; print('✓ Installation successful')"
# Run tests
pytest tests/
# Run examples
python examples/basic_usage.py
python examples/cli_demo.py --samples 15# Install build tools
pip install build twine
# Build package
python -m build
# This creates:
# - dist/synheart_emotion-0.0.1.tar.gz (source distribution)
# - dist/synheart_emotion-0.0.1-py3-none-any.whl (wheel)Import Error: Make sure the package is installed with pip list | grep synheart-emotion
Version Conflicts: Upgrade dependencies with pip install --upgrade numpy pandas
Missing Dependencies: Install all requirements with pip install -r requirements.txt
from datetime import datetime
from synheart_emotion import EmotionConfig, EmotionEngine
# Create engine with default configuration
config = EmotionConfig()
engine = EmotionEngine.from_pretrained(config)
# Push data from wearable
engine.push(
hr=72.0,
rr_intervals_ms=[850.0, 820.0, 830.0, 845.0, 825.0],
timestamp=datetime.now()
)
# Get inference result when ready
results = engine.consume_ready()
for result in results:
print(f"Emotion: {result.emotion}")
print(f"Confidence: {result.confidence:.1%}")
print(f"Probabilities: {result.probabilities}")from datetime import datetime
from synheart_emotion import EmotionConfig, EmotionEngine
# Initialize engine
config = EmotionConfig()
engine = EmotionEngine.from_pretrained(config)
# Simulate wearable data stream
hr_data = [72.0, 73.5, 71.8, 74.2, 72.5]
rr_data = [
[850.0, 820.0, 830.0, 845.0, 825.0],
[855.0, 815.0, 835.0, 840.0, 830.0],
# ... more data
]
# Push data
for hr, rr_intervals in zip(hr_data, rr_data):
engine.push(
hr=hr,
rr_intervals_ms=rr_intervals,
timestamp=datetime.now()
)
# Consume results
results = engine.consume_ready()
if results:
result = results[0]
print(f"Emotion: {result.emotion} ({result.confidence:.1%})")See the examples/ directory for more comprehensive examples:
basic_usage.py- Simple emotion inferencecustom_config.py- Custom configuration and loggingstreaming_data.py- Continuous data stream simulation
config = EmotionConfig(
window_seconds=60.0, # 60 second window
step_seconds=5.0, # 5 second step
min_rr_count=30, # Minimum RR intervals
hr_baseline=65.0 # Personal HR baseline
)def custom_logger(level, message, context):
print(f"[{level}] {message}")
engine = EmotionEngine.from_pretrained(
config=config,
on_log=custom_logger
)# Get buffer statistics
stats = engine.get_buffer_stats()
print(f"Data points: {stats['count']}")
print(f"Duration: {stats['duration_ms']}ms")
print(f"HR range: {stats['hr_range']}")
print(f"RR count: {stats['rr_count']}")
# Clear buffer
engine.clear()Configuration for the emotion inference engine.
@dataclass
class EmotionConfig:
model_id: str = "svm_linear_wrist_sdnn_v1_0"
window_seconds: float = 60.0
step_seconds: float = 5.0
min_rr_count: int = 30
return_all_probas: bool = True
hr_baseline: Optional[float] = None
priors: Optional[Dict[str, float]] = NoneAttributes:
model_id- Model identifierwindow_seconds- Rolling window size (default: 60s)step_seconds- Emission cadence (default: 5s)min_rr_count- Minimum RR intervals required (default: 30)return_all_probas- Return all label probabilities (default: True)hr_baseline- Optional HR baseline for personalizationpriors- Optional label priors for calibration
Main emotion inference engine.
Class Methods:
@classmethod
def from_pretrained(
config: EmotionConfig,
model: Optional[LinearSvmModel] = None,
on_log: Optional[Callable] = None
) -> EmotionEngineCreate engine from pretrained model.
Instance Methods:
def push(
hr: float,
rr_intervals_ms: List[float],
timestamp: datetime,
motion: Optional[Dict[str, float]] = None
) -> NonePush new data point into the engine.
def consume_ready() -> List[EmotionResult]Consume ready results (throttled by step interval).
def get_buffer_stats() -> Dict[str, Any]Get current buffer statistics.
def clear() -> NoneClear all buffered data.
Result of emotion inference.
@dataclass
class EmotionResult:
timestamp: datetime
emotion: str
confidence: float
probabilities: Dict[str, float]
features: Dict[str, float]
model: Dict[str, Any]Attributes:
timestamp- Timestamp when inference was performedemotion- Predicted emotion label (top-1)confidence- Confidence score (0.0-1.0)probabilities- All label probabilitiesfeatures- Extracted features (hr_mean, sdnn, rmssd)model- Model metadata
Methods:
@classmethod
def from_inference(
timestamp: datetime,
probabilities: Dict[str, float],
features: Dict[str, float],
model: Dict[str, Any]
) -> EmotionResultCreate from raw inference data.
def to_dict() -> Dict[str, Any]Convert to dictionary for JSON serialization.
Static utility class for feature extraction.
class FeatureExtractor:
@staticmethod
def extract_hr_mean(hr_values: List[float]) -> float
@staticmethod
def extract_sdnn(rr_intervals_ms: List[float]) -> float
@staticmethod
def extract_rmssd(rr_intervals_ms: List[float]) -> float
@staticmethod
def extract_features(
hr_values: List[float],
rr_intervals_ms: List[float],
motion: Optional[Dict[str, float]] = None
) -> Dict[str, float]Base exception class with subclasses:
TooFewRRError- Too few RR intervalsBadInputError- Invalid input dataModelIncompatibleError- Model incompatible with featuresFeatureExtractionError- Feature extraction failed
# Basic usage
python examples/basic_usage.py
# Custom configuration
python examples/custom_config.py
# Streaming data simulation
python examples/streaming_data.py- Python 3.8+
- NumPy >= 1.21.0
- Pandas >= 1.3.0
Optional (for ML model loading):
- scikit-learn >= 1.0.0
- joblib >= 1.1.0
- xgboost >= 1.5.0
The package follows a modular architecture:
synheart_emotion/
├── __init__.py # Package exports
├── config.py # Configuration dataclass
├── engine.py # Main inference engine
├── error.py # Error classes
├── features.py # Feature extraction
├── models.py # Model classes
└── result.py # Result dataclass
- Push - Biosignal data (HR, RR intervals) pushed to engine
- Buffer - Data stored in sliding window ring buffer
- Extract - Features extracted when window is full
- Infer - Model predicts emotion probabilities
- Emit - Results emitted at configured intervals
The engine uses threading.RLock() for thread-safe operations:
- Multiple threads can push data concurrently
- Buffer operations are protected
- Results can be consumed from any thread
IMPORTANT: This library uses demo placeholder model weights that are NOT trained on real biosignal data. For production use, you must provide your own trained model weights.
All processing happens on-device. No data is sent to external servers.
pytest tests/black src/ examples/ tests/
isort src/ examples/ tests/mypy src/See LICENSE file for details.
Contributions are welcome! See our Contributing Guidelines for details.
- Main Repository: synheart-emotion (Source of Truth)
- Documentation: RFC E1.1
- Model Card: Model Card
- Examples: Examples
- Models: Pre-trained Models
- Tools: Development Tools
- Synheart AI: synheart.ai
- Issues: GitHub Issues
If you use this package in your research, please cite:
@software{synheart_emotion,
title = {Synheart Emotion: On-device emotion inference from biosignals},
author = {Goytom, Israel},
year = {2025},
version = {0.0.1},
url = {https://github.com/synheart-ai/synheart-emotion}
}