Spaces:
Sleeping
Sleeping
Commit History
-
fix(analyzer): static overlay boxes end when the ball departs
balls_init is a photograph of the first frame, not a standing claim: on shot_05 the dashed box for the 8 sat on its empty starting spot for 217 of 243 frames with the ball's own tail leading away. It now stops the frame the entry's track leaves the seed by more than BALL_DEDUP_M -- shot_05 217 -> 1, shot_02's ball 5 235 -> 28 (last drawn frame 35, t 1.167). Entries with no track are untouched.58e7bd2 -
fix(analyzer): clamp cue position across long track gaps
A line drawn between the ends of a 0.9 s hole is not a measurement: on shot_07 it swept within CUE_EXCL_M of six still balls in turn (the 2 at 4.1 mm), switching their detections off. Each half of a hole over LOST_MAX_S now answers with its own end; shorter holes still interpolate. The two detections that stole ball 8's track sit 3.4 mm from the end and stay excluded. 271 passed / 27 skipped.ff7db2d -
fix(analyzer): exclude cue-coincident detections from ball tracks
The detector classifies the cue as a Solid now and then, and an object track whose ball has just gone into a pocket adopts it: on shot_02 that ran ball 5 backwards out of the far corner pocket to t=1.233 and cost a real pot flag. Candidates in EITHER band within CUE_EXCL_M 0.06 of the interpolated cue position are struck off; track_cue is untouched. Measured: ball 5 now ends t=1.200 at (1.244, 2.458), 0.086 m from the (1.27, 2.54) pocket at 1.01 m/s heading +9 deg -- pocketed_guess True. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>20b78c7 -
fix(analyzer): persistent overlay boxes for static balls_init entries
Ball 5 of shot_02 reads 0.208 against an operating point of 0.2993, so it never entered frames_dets and had no box on any frame, while balls_init (the 1280 first-frame pass) reads it at 0.688 and the metric table draws it. A balls_init entry with no high detection within BALL_DEDUP_M now draws itself: a thin DASHED square carrying the number balls_init already has, BallID not run again. Measured on the probe clip: ball 5 boxed on 235 of 243 frames, no ball drawn twice. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>fbf1204 -
fix(analyzer): draw only on-table detections in the overlay
The overlay drew every recorded box while analyze_clip has always dropped the ones projecting off the cloth, so the imgsz 960 pass put rectangles on the wall behind the far rail -- 2172 of 3476 ball boxes on shot_02, reaching table y 4.48 and x -0.42. _on_table_dets applies the same class filter and the same IN_TOL_M gate, in the same height-compensated frame, and none survives into the drawing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>b202b21 -
fix(analyzer): anchor rack_guess to the rack spots
The first demo regeneration fired the flag on shot_07, the tail of a break: eight balls strung along the foot rail, five inside RACK_R_M and the cue 0.75 m away, so every other condition held. A rack is built ON THE SPOT, so require the cluster centre within RACK_SPOT_R 0.25 m of one. Measured on that bundle: shot_09 0.067 m, shot_10 0.083 m, shot_07 0.570 m. 5 tests on the real layouts; pytest 2x green on both venvs.a40be3b -
chore(analyzer): archive the far-zone demo regeneration driver
The run happened and its gate came back RED, so the bundle was not shipped and step 6 has no data commit -- but the driver and the thresholds it measured belong in git either way. Also drops two stray blank lines above rack_cluster. Red: rack_guess also fires on shot_07, pocketed_guess gains a tenth flag on shot_10, shot_09 v0 moves -41.7%. Numbers in the HANDOFF.2e493d6 -
fix(analyzer): overlay BallID honours ball-set profile
The overlay called identify_balls with the standard palette while balls_init used the job's profile, so a Dynaspheres set (5 purple, not orange) would be numbered one way in the video and another on the metric table -- one statement to the viewer, two answers. Read the profile once per job and hand it to both. Gate: 2 tests, both layers pass the same table; pytest 2x green, 2 venvs.5b4ac58 -
feat(analyzer): rack_guess display flag for occluded break racks
Down the table the rack is a diamond seen edge on, so balls hide behind balls and 9 of 9 is unreachable -- at 1280 the shot_09 cluster even returns ten boxes for nine balls. Flag it instead of inventing points: 3+ balls inside 0.15 m of one centre with the cue 0.5 m away. Gate: on for shot_09 (4 of 9 seen) and shot_10 (5 of 9), off for shot_02/03/05; 9 unit tests; pytest 2x green on both venvs.2bb6493 -
feat(analyzer): track-gated low-conf recovery for live tracks
Detect at DET_CONF_FLOOR 0.05 and read the answer in two bands: above op_conf nothing changes anywhere, below it a detection may only extend a track already alive, inside that track's gate, when no high candidate is in it. IN_TOL_M gates the low band too -- the probe found boxes past the far cushion projecting 1.7 m off the cloth. Gate: 13 unit tests over both trackers; pytest 2x green on both venvs.562bec5 -
feat(analyzer): hybrid detect imgsz 960 with 1280 first-frame pass
Balls at the far rail are ~22 px wide and imgsz 640 shrinks them to 7, which is why shot_02 never had ball 5. 960 for every frame costs 2.1x, and the one frame balls_init is built from is read at 1280, where that ball reads 0.688 instead of 0.208. Segmentation stays at 640. Gate: shot_02 balls_init has a ball 0.0158 m from (1.145, 2.468) at conf 0.688 >= OP_CONF 0.2993; pytest 2x green on both venvs.9694f55 -
feat(analyzer): load ball-set colour profile for BallID
The tournament uses Dynaspheres Titan (the 5 is purple, not orange), so a correctly detected ball still gets the wrong number. load_profile reads a JSON of palette / white_range / the ball-8 thresholds; the method's own constants stay unreachable from it. With POOLCOACH_BALLID_PROFILE unset nothing moves: no key in the job JSON, the default palette, and the 14 demo shots still serialise without one. pytest green 3x in both venvs.d5faecf
-
fix(cv): flag pocket-bound balls that vanish mid-flight
The detector loses a ball still flying at the far corners -- 23.7 cm short on shot_03 -- so the near-pocket rule never fired and the ball stalled mid-cloth on the replay. Guess the pocket from the end chord as well (early end, still moving, aimed, close) and name it in pocket_xy. Gate: pytest 208 app-env / 222 cv-env; each of the four conditions proven load-bearing by relaxing its own constant alone.e940fef -
fix(cv): cap the association gate and close lost ball tracks
The gate widened past the 2.84 m table diagonal after 0.253 s out of sight, so 6/19 demo tracks ended within 3 cm of ANOTHER ball's start and a pocketed ball's trail dragged across the cloth. Cap the gate at GATE_CAP_M 0.5 and close a track missing over LOST_MAX_S 0.5 s. Object-ball tracks only; track_cue untouched. Gate: pytest 197 passed / 14 skipped (app), 211 passed (cv), twice.bff9861 -
feat(cv): draw object-ball trails in the shot overlay
Only the cue left a tail, so the video showed the target balls teleporting between boxes. Each ball_track now draws its own tail in the same TRAIL_N window, coloured through init_idx -> balls_init -> number, under the cue tail so the cue stays the main character. Gate: pytest 188/202 green, 5 new cases on the pure half.2bcd966 -
feat(cv): flag likely pocketed balls in ball tracks
The replay needs to know which balls to fade out. Two conditions, both required, so a ball simply lost behind a player is not read as potted: the track ends within POT_NEAR_M of a pocket AND stops more than POT_GONE_S before the clip ends. Display only -- no metric reads it. Gate: pytest 183/197 green, 11 new cases.0d6965c -
feat(cv): track object balls across frames in analyze_clip
The non-cue detections were already collected and thrown away after the still-ball clustering; keeping them per frame gives every object ball a track for free, with no extra detection pass. Deterministic greedy NN with a gate that widens over a miss, seeded from balls_init and suppressed below MOVE_MIN_M. Gate: pytest 169/183 green, 2 runs of the tracker on one input byte-identical.bfd4175
-
chore(repo): normalize line endings with text=auto
The tree mixed CRLF and LF with only *.bat declared, so an English-only edit produced a 3704-line diff and every commit needed its EOL restored by hand. Content is untouched: git diff --ignore-cr-at-eol shows .gitattributes alone. pytest 160+7skip (app) / 167 (cv), 3 runs each.2cfc662
-
refactor(analyzer): drop unused training code from shotnet.py
Nothing in this repository trains ShotNet; the loader, batcher, collate, loss, gate metrics and the two augmentations were dead weight that read as a live training path. The poolcoach-rl history keeps them for a retrain. Gate: shotnet.py 862 -> 449 lines; checkpoint shotnet_20260812_c4b still loads and the worker boots in 7.3s; shots 6 and 10 re-analysed on CPU give EVERY field identical to before the trim; pytest 160 passed, 7 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>5c88cde -
refactor(analyzer): translate server-generated strings to English
The app is going one language; Vietnamese error and warning text in an otherwise English code base made every reader switch twice. Gate: pytest 68 passed; AST identical after blanking every string on all 7 modules, cv_worker only by the _CONF_LABEL_VI -> _CONF_LABEL rename (the VI suffix stops meaning anything once the labels are English). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>fe8d0e2 -
refactor(analyzer): translate identifiers and comments to English
Comments, docstrings, identifiers and test names are now English, including the Dockerfile and .dockerignore reasoning. Strings the user reads stay Vietnamese, because the product's UI language is Vietnamese: HTTP error details, per-shot reasons, warnings and the CSV headers. Semantics are unchanged -- an AST comparison with string constants blanked is identical for every module except three renamed locals; pytest 68 passed.876aff5 -
feat(analyzer): port analyzer pipeline from poolcoach-rl
Traced the import graph from the Analyzer routes: app store/transport/ embedded worker, src/poolcoach_cv end to end, cv_worker, the multi-shot contract test, the packaged demo video and the Hugging Face deploy set. The suggestion engine, drills, QR/TV and the image-scan job stay behind, so their routes, schemas, tables and front-end sections are gone with them; /api/table now reads pooltool directly (src/poolcoach_sim/table.py).da25347 -
chore(repo): scaffold poolcoach-app with layered requirements
Fresh repository for the Analyzer, split off from poolcoach-rl. Git LFS tracks *.pt/*.mp4/*.png from this first commit because Hugging Face (Xet) rejects any binary that never went through LFS, even in an old commit -- the previous repository had to push an orphan snapshot branch instead. Requirements are rebuilt from the import graph of the code being ported, not copied: core / app / cv / cv-cpu / dev.27b06b1