- README install section now reflects reality (git URL, not PyPI) - Add a Source link at the top and a Development section at the bottom - Update Running-the-bundled-example to clone-then-uv-sync flow since examples/ is sdist-only - pyproject.toml: replace github.com/your-org placeholder with the actual git.vercanti.com URL Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
154 lines
4.9 KiB
Markdown
154 lines
4.9 KiB
Markdown
# human_mouse
|
|
|
|
Replay real human mouse trajectories from the [SapiMouse](https://www.ms.sapientia.ro/~manyi/sapimouse/sapimouse.html)
|
|
dataset onto a Playwright page.
|
|
|
|
Useful when you need mouse movement that is statistically indistinguishable
|
|
from a real user — for ML-based bot-detection research, behavioral biometrics
|
|
prototyping, or replay-based test fixtures.
|
|
|
|
- Source: <https://git.vercanti.com/yellowdog/human_mouse>
|
|
- License: MIT
|
|
|
|
## Install
|
|
|
|
The package is not on PyPI yet. Install directly from the git remote:
|
|
|
|
```bash
|
|
# core only
|
|
uv add "human_mouse @ git+https://git.vercanti.com/yellowdog/human_mouse.git"
|
|
|
|
# with the bundled CloakBrowser example
|
|
uv add "human_mouse[demo] @ git+https://git.vercanti.com/yellowdog/human_mouse.git"
|
|
```
|
|
|
|
Or pin to a specific commit / tag:
|
|
|
|
```bash
|
|
uv add "human_mouse @ git+https://git.vercanti.com/yellowdog/human_mouse.git@v0.1.0"
|
|
```
|
|
|
|
`pip install` works with the same `git+https://...` URL.
|
|
|
|
## One-time dataset download
|
|
|
|
```python
|
|
import human_mouse as hm
|
|
hm.download_sapimouse("./data") # ~8 MB, no registration
|
|
```
|
|
|
|
This unzips SapiMouse to `./data/sapimouse/userN/...`.
|
|
|
|
## Quick start
|
|
|
|
```python
|
|
import human_mouse as hm
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
with sync_playwright() as pw:
|
|
browser = pw.chromium.launch(headless=False)
|
|
page = browser.new_page()
|
|
page.goto("https://example.com")
|
|
|
|
# one-shot: pick a real human trajectory matching the distance and replay it
|
|
seg = hm.replay_random(
|
|
page,
|
|
start=(100, 100),
|
|
end=(900, 500),
|
|
data_root="./data/sapimouse",
|
|
density=4, # 4x spatial upsample for smoother visuals
|
|
seed=42,
|
|
)
|
|
print(f"replayed {seg.user}/{seg.session}, {len(seg.points)} source points")
|
|
```
|
|
|
|
## Step-by-step API
|
|
|
|
For full control over which segment is picked and how it's processed:
|
|
|
|
```python
|
|
import human_mouse as hm
|
|
|
|
segs = hm.load_all_segments("./data/sapimouse")
|
|
|
|
seg = hm.pick_segments(
|
|
segs,
|
|
n=1,
|
|
target_distance=1000, # only match segments ~1000 px long
|
|
distance_tolerance=0.3, # ±30 %
|
|
max_path_ratio=2.0, # skip erratic meanderers
|
|
seed=42,
|
|
)[0]
|
|
|
|
points = hm.affine_warp(seg, (100, 100), (900, 500))
|
|
points = hm.upsample(points, factor=4)
|
|
hm.replay(page, points, speed=1.0)
|
|
```
|
|
|
|
## Public API
|
|
|
|
| Symbol | Purpose |
|
|
|---|---|
|
|
| `Segment` | dataclass with `.points`, `.start`, `.end`, `.straight_distance`, `.path_length`, `.path_ratio`, `.duration_ms` |
|
|
| `load_all_segments(data_root)` | scan SapiMouse, return every continuous Move-only segment ≥ 50 pts |
|
|
| `pick_segments(segments, n, ...)` | filter by distance / path-ratio / distinct-session, randomly choose `n` |
|
|
| `affine_warp(seg, start, end)` | translate + rotate + uniform-scale onto new endpoints (shape preserved) |
|
|
| `upsample(points, factor)` | linearly interpolate `factor-1` sub-points between every adjacent pair |
|
|
| `replay(page, points, speed=1.0)` | drive `page.mouse.move(...)` honoring the recorded `dt` |
|
|
| `replay_random(page, start, end, data_root, ...)` | one-shot: pick + warp + upsample + replay |
|
|
| `download_sapimouse(dest)` | download and unzip the dataset |
|
|
|
|
## Running the bundled example
|
|
|
|
The `examples/` folder ships a Playwright canvas page (`demo.html`) plus a
|
|
runner script that uses [CloakBrowser](https://github.com/CloakHQ/CloakBrowser).
|
|
You need a local checkout for this since `examples/` ships only in the sdist:
|
|
|
|
```bash
|
|
git clone https://git.vercanti.com/yellowdog/human_mouse.git
|
|
cd human_mouse
|
|
uv sync --extra demo
|
|
python -m human_mouse download ./sapimouse_data # one-time, ~8 MB
|
|
uv run python examples/cloakbrowser_demo.py sapi # one trajectory
|
|
uv run python examples/cloakbrowser_demo.py multi --n 10 # 10 overlaid
|
|
```
|
|
|
|
Outputs land in `./outputs/`:
|
|
|
|
- `sapi.png` + `sapi.json` — single trajectory screenshot and per-event coords
|
|
- `multi/overlay.png` + per-run JSONs + `summary.json`
|
|
|
|
## Development
|
|
|
|
```bash
|
|
git clone https://git.vercanti.com/yellowdog/human_mouse.git
|
|
cd human_mouse
|
|
uv sync --all-extras # core + [demo] + [dev]
|
|
uv run pytest # 23 tests, ~40s with SapiMouse present
|
|
uv build # produces wheel + sdist in dist/
|
|
```
|
|
|
|
Integration tests auto-skip when `sapimouse_data/sapimouse/` is missing, so
|
|
the core 10 unit tests run on a fresh clone without the dataset.
|
|
|
|
## What `replay` is and isn't
|
|
|
|
**Is**: a deterministic replay of one real human's mouse path, warped to your
|
|
endpoints. The shape (curvature, hesitation, end-point fumbling) and the
|
|
timing (`dt` distribution) come straight from a real recording.
|
|
|
|
**Isn't**: a generator. It samples from a fixed dataset of ~4 000 segments.
|
|
For per-call novelty, randomize the seed; for true synthesis, look at
|
|
trajectory generative models (e.g. SapiAgent, DMTG).
|
|
|
|
## Dataset attribution
|
|
|
|
If you use SapiMouse data in published work, cite:
|
|
|
|
> Antal, M. et al. *SapiMouse: Mouse Dynamics-based User Authentication Using
|
|
> Deep Feature Learning*. IEEE SACI 2021.
|
|
|
|
## License
|
|
|
|
MIT.
|