Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions BENCHMARKS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# ZPE-Ink Benchmarks

This file is a scaffold for Phase 2.
Real public-dataset rows land in Phase 3 only.
Do not backfill synthetic or proxy data here.

## Reproducible Methodology

1. Record the repo commit, host, OS, Python version, Swift version, Rust toolchain, and browser/runtime version.
2. Record the exact dataset URL or local path, the command used, the sample count, and whether the source is public or proxy.
3. Run `encode -> decode -> verify` on the same sample set.
4. Capture raw bytes, encoded bytes, ratio, fidelity metric, and wall-clock timing.
5. Keep proxy/demo rows separate from public dataset rows.

## Phase 2 Scaffold

| dataset | source | baseline | zpe | ratio | fidelity | notes |
|---|---|---|---|---|---|---|
| Synthetic proxy | repo fixtures | raw float32 | n/a | n/a | roundtrip only | Proxy/demo surface only |
| Public datasets | reserved for Phase 3 | raw float32 | n/a | n/a | n/a | IAM, CASIA, and other real rows land in Phase 3 |

## Phase 3 Reservation

- Use only freely available public datasets.
- Replace proxy rows with measured rows from runnable scripts.
- Cite the dataset URL, the exact command, and the fidelity metric for each row.
3 changes: 3 additions & 0 deletions code/bindings/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ wasm-bindgen = "0.2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
crc32fast = "1"

[package.metadata.wasm-pack.profile.release]
wasm-opt = false
8 changes: 8 additions & 0 deletions code/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,17 @@ classifiers = [
dependencies = []

[project.optional-dependencies]
test = [
"pytest>=8.0",
]
docs = [
"mkdocs>=1.6",
]
dev = [
"build>=1.2.2",
"pytest>=8.0",
"setuptools>=68",
"wheel",
]
telemetry = [
"comet-ml>=3.49",
Expand Down
64 changes: 64 additions & 0 deletions code/tests/test_clean_install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from __future__ import annotations

import subprocess
import sys
import venv
from pathlib import Path


def _run(command: list[str], *, cwd: Path | None = None) -> subprocess.CompletedProcess[str]:
result = subprocess.run(command, cwd=cwd, capture_output=True, text=True, check=False)
if result.returncode != 0:
raise AssertionError(
"command failed",
{
"command": command,
"cwd": str(cwd) if cwd is not None else None,
"returncode": result.returncode,
"stdout": result.stdout,
"stderr": result.stderr,
},
)
return result


def test_clean_install_from_built_wheel(tmp_path: Path) -> None:
repo_root = Path(__file__).resolve().parents[2]
dist_dir = tmp_path / "dist"
dist_dir.mkdir(parents=True, exist_ok=True)

_run(
[
sys.executable,
"-m",
"build",
"--wheel",
"--no-isolation",
"--outdir",
str(dist_dir),
str(repo_root / "code"),
],
cwd=repo_root,
)

wheels = sorted(dist_dir.glob("zpe_ink-*.whl"))
assert len(wheels) == 1

clean_env_dir = tmp_path / "clean-venv"
venv.EnvBuilder(with_pip=True, clear=True).create(clean_env_dir)
python_bin = clean_env_dir / ("Scripts" if sys.platform == "win32" else "bin") / "python"

_run(
[
str(python_bin),
"-m",
"pip",
"install",
"--no-index",
"--no-deps",
str(wheels[0]),
]
)

smoke = _run([str(python_bin), "-m", "zpe_ink", "verify-roundtrip"])
assert smoke.stdout.strip() == "roundtrip_ok"
111 changes: 111 additions & 0 deletions code/tests/test_examples_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from __future__ import annotations

import json
import os
import shutil
import socket
import subprocess
import sys
import time
import urllib.request
from pathlib import Path

import pytest

REPO_ROOT = Path(__file__).resolve().parents[2]


def _run(command: list[str], *, cwd: Path | None = None, timeout: int = 120) -> subprocess.CompletedProcess[str]:
return subprocess.run(command, cwd=cwd, capture_output=True, text=True, check=False, timeout=timeout)


def test_python_iam_example_proxy_mode_runs() -> None:
result = _run([sys.executable, "examples/python_load_iam.py", "--proxy-demo"], cwd=REPO_ROOT)
assert result.returncode == 0, result.stderr
payload = json.loads(result.stdout)
assert payload["mode"] == "proxy-demo"
assert payload["roundtrip_pass"] is True
assert payload["sample_count"] == 1


def test_swift_pencilkit_example_runs_with_repo_binding(tmp_path: Path) -> None:
swiftc = shutil.which("swiftc")
if not swiftc:
pytest.skip("swiftc is required for the Swift example")

binary_path = tmp_path / "zpe_ink_swift_demo"
build = _run(
[
swiftc,
str(REPO_ROOT / "code" / "bindings" / "swift" / "ZPEInk.swift"),
str(REPO_ROOT / "examples" / "swift_pencilkit.swift"),
"-o",
str(binary_path),
],
cwd=REPO_ROOT,
)
assert build.returncode == 0, build.stderr

run = _run([str(binary_path)], cwd=REPO_ROOT)
assert run.returncode == 0, run.stderr
payload = json.loads(run.stdout)
assert payload["status"] == "PASS"
assert payload["stroke_count"] == 2


def test_wasm_web_demo_build_runs(tmp_path: Path) -> None:
wasm_pack = shutil.which("wasm-pack")
cargo = shutil.which("cargo")
if not wasm_pack or not cargo:
pytest.skip("wasm-pack and cargo are required for the wasm web demo")

demo_dir = REPO_ROOT / "examples" / "wasm_web_demo"
pkg_dir = demo_dir / "pkg"
if pkg_dir.exists():
shutil.rmtree(pkg_dir)

build = _run(["bash", "build.sh"], cwd=demo_dir, timeout=240)
assert build.returncode == 0, build.stderr
assert (pkg_dir / "zpe_ink_wasm.js").exists()
assert (pkg_dir / "zpe_ink_wasm_bg.wasm").exists()

with socket.socket() as sock:
sock.bind(("127.0.0.1", 0))
port = str(sock.getsockname()[1])
env = os.environ.copy()
env["PORT"] = port
server = subprocess.Popen(
["bash", "serve.sh"],
cwd=demo_dir,
env=env,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
try:
deadline = time.time() + 10.0
while time.time() < deadline:
try:
with urllib.request.urlopen(f"http://127.0.0.1:{port}/index.html", timeout=1.0) as response:
html = response.read().decode("utf-8")
assert "ZPE-Ink WASM Demo" in html
break
except Exception:
if server.poll() is not None:
raise AssertionError("serve.sh exited before serving index.html")
time.sleep(0.2)
else:
server.terminate()
try:
server.wait(timeout=5)
except subprocess.TimeoutExpired:
server.kill()
server.wait(timeout=5)
raise AssertionError("serve.sh did not start cleanly within 10 seconds")
finally:
if server.poll() is None:
server.terminate()
try:
server.wait(timeout=5)
except subprocess.TimeoutExpired:
server.kill()
server.wait(timeout=5)
19 changes: 19 additions & 0 deletions code/tests/test_high_frequency_strokes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from __future__ import annotations

import random

from zpe_ink.codec import decode_zpink, encode_zpink
from zpe_ink.fixtures import generate_high_velocity_stroke


def test_high_frequency_strokes_roundtrip_lossless() -> None:
rng = random.Random(20260408)
strokes = [generate_high_velocity_stroke(rng, points=240) for _ in range(6)]

encoded = encode_zpink(strokes, mode="lossless", seed=20260408)
decoded = decode_zpink(encoded)

assert decoded["mode"] == "lossless"
assert decoded["seed"] == 20260408
assert len(decoded["strokes"]) == len(strokes)
assert decoded["strokes"] == strokes
52 changes: 52 additions & 0 deletions code/tests/test_swift_binding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from __future__ import annotations

import shutil
import subprocess
from pathlib import Path

import pytest

from zpe_ink.codec import canonical_json, decode_zpink, encode_zpink
from zpe_ink.fixtures import generate_synthetic_lossless

REPO_ROOT = Path(__file__).resolve().parents[2]
CODE_ROOT = REPO_ROOT / "code"


def _run(command: list[str], *, timeout: int = 45) -> subprocess.CompletedProcess[str]:
return subprocess.run(command, capture_output=True, text=True, check=False, timeout=timeout)


def _build_fixture(tmp_path: Path) -> tuple[Path, Path, str]:
strokes = generate_synthetic_lossless(seed=20260406)[:16]
payload = encode_zpink(strokes, mode="lossless", seed=20260406)
expected = canonical_json(decode_zpink(payload))
payload_path = tmp_path / "swift_fixture.zpink"
expected_path = tmp_path / "swift_expected.json"
payload_path.write_bytes(payload)
expected_path.write_text(expected, encoding="utf-8")
return payload_path, expected_path, expected


def test_swift_binding_canonical_json_roundtrip(tmp_path: Path) -> None:
swiftc = shutil.which("swiftc")
if not swiftc:
pytest.skip("swiftc is required for Swift binding verification")

payload_path, expected_path, expected = _build_fixture(tmp_path)

swift_bin = tmp_path / "swift_decode_bin"
swift_build = _run(
[
swiftc,
str(CODE_ROOT / "bindings" / "swift" / "ZPEInk.swift"),
str(CODE_ROOT / "bindings" / "swift" / "Tests" / "ZPEInkParity.swift"),
"-o",
str(swift_bin),
]
)
assert swift_build.returncode == 0, swift_build.stderr

swift_run = _run([str(swift_bin), str(payload_path), str(expected_path)])
assert swift_run.returncode == 0, swift_run.stderr
assert swift_run.stdout == expected
37 changes: 37 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Examples

Runnable demos only. Proxy mode is labeled where synthetic data stands in for gated corpora.

## Swift pencilkit demo

Run from the repo root:

```bash
swiftc code/bindings/swift/ZPEInk.swift examples/swift_pencilkit.swift -o /tmp/zpe-ink-swift-demo
/tmp/zpe-ink-swift-demo
```

The script uses a deterministic PencilKit-style capture on this Mac, asks the repo Python package to encode `.zpink`, then decodes and verifies the payload through the repo Swift binding.

## IAM-style loader

```bash
python3 examples/python_load_iam.py --proxy-demo
python3 examples/python_load_iam.py --input /path/to/sample.inkml
```

Proxy mode uses generated IAM-shaped strokes. Real-path mode reads InkML or UNIPEN-like input and round-trips it through the repo codec.

## WASM web demo

```bash
cd examples/wasm_web_demo
bash build.sh
PORT=8123 bash serve.sh
```

Open `http://127.0.0.1:8123`. The page decodes a synthetic `.zpink` sample with the repo wasm binding and also accepts file uploads.

## Benchmarks

`../BENCHMARKS.md` holds the scaffold. Real public-dataset rows land in Phase 3.
Loading
Loading