Viewing is open to everyone. Downloading and starring need a GitHub sign-in.
Sign in with GitHub
train_locator_ranker.py
v0.1.0-build.85
Latest
Commit ede38f5
Sign in with GitHub to download. Each download is recorded with your GitHub username.
| 1 | #!/usr/bin/env python3 |
| 2 | """Generate the tiny on-device CKey border-component ranker. |
| 3 | |
| 4 | This is deliberately a candidate ranker, not a decoder. It is trained from |
| 5 | deterministic synthetic component measurements, while the native geometry |
| 6 | fit and RS/CRC checks remain the authority for accepting a CKey. Replace or |
| 7 | augment the synthetic distributions with labelled camera captures as they are |
| 8 | collected; the output stays a dependency-free Swift source file. |
| 9 | """ |
| 10 | import argparse |
| 11 | from pathlib import Path |
| 12 | import numpy as np |
| 13 | from sklearn.linear_model import LogisticRegression |
| 14 | |
| 15 | parser = argparse.ArgumentParser(description="Train the on-device CKey candidate ranker.") |
| 16 | parser.add_argument("--features", type=Path, help="CSV emitted by train_synthetic_locator.py") |
| 17 | args = parser.parse_args() |
| 18 | |
| 19 | if args.: |
| 20 | rows = np.loadtxt(args., delimiter=",", skiprows=1) |
| 21 | if rows.!= 2 or rows.shape[1] != 4: |
| 22 | raise SystemExit("feature CSV must contain label,aspect_error,fill_ratio,relative_side") |
| 23 | labels, features = rows[:, 0], rows[:, 1:] |
| 24 | else: |
| 25 | # Kept only as a dependency-light smoke path. Production updates should |
| 26 | # use the encoded/rasterized corpus generated by train_synthetic_locator. |
| 27 | rng = np..default_rng(0xC1E7) |
| 28 | count = 12_000 |
| 29 | positive = np.column_stack(( |
| 30 | np.abs(rng.normal(.035, .035, count)), |
| 31 | np.clip(rng.normal(.09, .045, count), .005, .28), |
| 32 | np.clip(rng.normal(.32, .16, count), .06, .72), |
| 33 | )) |
| 34 | negative = np.column_stack(( |
| 35 | np.abs(rng.normal(.42, .32, count)), |
| 36 | np.clip(rng.beta(2, 3, count) * .8, .01, .95), |
| 37 | rng.uniform(.01, .98, count), |
| 38 | )) |
| 39 | features = np.vstack((positive, negative)) |
| 40 | labels = np.r_[np.ones(count), np.zeros(count)] |
| 41 | # Keep the generated Swift feature contract identical to CameraScanner: the |
| 42 | # source CSV carries a relative side, while the on-device ranker converts it |
| 43 | # to distance from the useful 0.32 frame fraction. |
| 44 | features = features.copy() |
| 45 | features[:, 2] = np.abs(features[:, 2] - .32) |
| 46 | model = LogisticRegression(C=3, max_iter=2000, random_state=0).fit(features, labels) |
| 47 | weights = model.coef_[0] |
| 48 | bias = model.intercept_[0] |
| 49 | target = Path(__file__).parents[1] / "ios/CKeyReader/LocatorRanker.swift" |
| 50 | target.write_text(f'''// Generated by tools/train_locator_ranker.py. Do not hand-tune.\n\nimport Foundation\n\nenum LocatorRanker {{\n private static let weights = [{weights[0]:.8f}, {weights[1]:.8f}, {weights[2]:.8f}]\n private static let bias = {bias:.8f}\n\n static func score(aspectError: Double, fillRatio: Double, relativeSide: Double) -> Double {{\n let sideError = abs(relativeSide - 0.32)\n let z = bias + weights[0] * aspectError + weights[1] * fillRatio + weights[2] * sideError\n return 1.0 / (1.0 + exp(-z))\n }}\n}}\n''') |
| 51 | print(f"wrote {target}; training accuracy={model.score(features, labels):.3f}; examples={len(labels)}") |