chore: scaffold Merlin Phase A repo
This commit is contained in:
commit
072ee3f36c
9 changed files with 88 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.env
|
||||||
|
.merlin/
|
||||||
|
models/*.pkl
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
|
node_modules/
|
||||||
|
app/dist/
|
||||||
0
merlin/__init__.py
Normal file
0
merlin/__init__.py
Normal file
0
merlin/actions/__init__.py
Normal file
0
merlin/actions/__init__.py
Normal file
56
merlin/config.py
Normal file
56
merlin/config.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
"""
|
||||||
|
MerlinConfig — load and save user gesture-to-action mappings.
|
||||||
|
|
||||||
|
Config file: ~/.merlin/config.yaml
|
||||||
|
If the file does not exist, defaults are used and written on first save.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field, asdict
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
CONFIG_PATH = Path.home() / ".merlin" / "config.yaml"
|
||||||
|
|
||||||
|
DEFAULT_MAPPINGS: dict[str, str] = {
|
||||||
|
"left_blink": "left_click",
|
||||||
|
"right_blink": "right_click",
|
||||||
|
"both_blink": "double_click",
|
||||||
|
"head_nod": "key_enter",
|
||||||
|
"head_shake": "key_escape",
|
||||||
|
"head_tilt_left": "scroll_up",
|
||||||
|
"head_tilt_right": "scroll_down",
|
||||||
|
"open_palm": "key_space",
|
||||||
|
"pinch": "drag_start",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MerlinConfig:
|
||||||
|
mappings: dict[str, str] = field(default_factory=lambda: dict(DEFAULT_MAPPINGS))
|
||||||
|
confidence_threshold: float = 0.80
|
||||||
|
camera_device: int = 0
|
||||||
|
dwell_ms: int = 800
|
||||||
|
blink_threshold: float = 0.20
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls, path: Path = CONFIG_PATH) -> MerlinConfig:
|
||||||
|
if not path.exists():
|
||||||
|
return cls()
|
||||||
|
with path.open() as f:
|
||||||
|
data = yaml.safe_load(f) or {}
|
||||||
|
return cls(
|
||||||
|
mappings=data.get("mappings", DEFAULT_MAPPINGS),
|
||||||
|
confidence_threshold=float(data.get("confidence_threshold", 0.80)),
|
||||||
|
camera_device=int(data.get("camera_device", 0)),
|
||||||
|
dwell_ms=int(data.get("dwell_ms", 800)),
|
||||||
|
blink_threshold=float(data.get("blink_threshold", 0.20)),
|
||||||
|
)
|
||||||
|
|
||||||
|
def save(self, path: Path = CONFIG_PATH) -> None:
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with path.open("w") as f:
|
||||||
|
yaml.safe_dump(asdict(self), f, default_flow_style=False)
|
||||||
0
merlin/features/__init__.py
Normal file
0
merlin/features/__init__.py
Normal file
22
pyproject.toml
Normal file
22
pyproject.toml
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=68"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "merlin"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Merlin — BCI and alternative input training harness (BSL 1.1)"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = [
|
||||||
|
"circuitforge-core[gestures-mediapipe]",
|
||||||
|
"fastapi>=0.110",
|
||||||
|
"uvicorn[standard]>=0.29",
|
||||||
|
"scikit-learn>=1.4",
|
||||||
|
"joblib>=1.3",
|
||||||
|
"pyyaml>=6.0",
|
||||||
|
"pyautogui>=0.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
include = ["merlin*"]
|
||||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
0
tests/test_actions/__init__.py
Normal file
0
tests/test_actions/__init__.py
Normal file
0
tests/test_features/__init__.py
Normal file
0
tests/test_features/__init__.py
Normal file
Loading…
Reference in a new issue