Spaces:
Runtime error
Runtime error
Commit History
-
Bake metrics narrative into submission notebook (§2.4–§2.6, §3.3, §4.1, §11.2) (#29)
theDocWho +2: theDocWho, Claude Opus 4.7
Unverified signatureReviewers now see the full identifier / damage_seg / parts_seg / Variant-D story statically without running anything: - §2.4 — markdown explaining why 0.40 (VMMRdb) vs 0.77 (Stanford) is a preprocessing mismatch and class-granularity effect, not a model regression. Six-factor breakdown + an honest comparison table for Options 1 / 2 / 3. - §2.5 — train/val loss + top-1 accuracy curves per epoch with stage boundary annotated. Stanford curve always plotted; VMMRdb plotted when metrics.json is pulled from the Colab run. - §2.6 — three confusion / F1 views from the cached Stanford F1 run (data/eval/identifier_stanford_f1.json): top-20 by support heatmap, bottom-20 by F1 bar chart, and make-level aggregated heatmap (49 makes, 0.854 accuracy — the metric Variant D actually depends on). - §3.3 / §4.1 — damage_seg + parts_seg YOLOv8 per-epoch curves (box + mask loss, mAP50, mAP50-95, P/R) loaded from each run's ultralytics/results.csv. - §11.2 — Variant D end-to-end MAE on a ha…4176ea4
-
Add crash-resume + GDrive sync to identifier-continue training
The Kaggle interactive runner pauses when the tab loses focus, and a 9/12 epoch crash used to mean redoing all prior epochs. This adds: - `ContinueConfig.resume_from` / `resume_run_dir` + `--resume` / `--resume-run-dir` CLI flags. Loads the last `epoch_NNN.pt`, restores model state + epoch + stage + best_val, re-applies stage-2 unfreeze when needed, and writes new epochs into the same run dir so `best.pt` / `metrics.json` keep accumulating. - Kaggle notebook: switch guidance to "Save & Run All (Commit)" so tab focus no longer matters, plus rclone-backed GDrive sync cells. Restores prior run on launch, mirrors checkpoints to GDrive every 60s, training cell auto-builds the right `--resume` flags when a prior run is detected. - New section 9 documents the recovery loop end-to-end.266d37e
-
Update ROOT path for vmmrdb dataset location
theDocWhoUnverified signature6b92f4d -
Phase 6: VMMRdb identifier extension (196 → up to ~9,170 make/model/year)
Wires the CC0 Kaggle mirror of VMMRdb into the continued-training path so the make/model/year identifier can recognise far more cars — which flows straight into Variant D / multi-car costing (more cars named → better make/segment cost). - data/vmmrdb.py: loader mirroring the stanford_cars API. Auto-detects the class-folder layout, parses make/model/year from folder names, and supports a `top_n` cap (default 2000) to keep training tractable vs the 9,170-class tail. ROOT resolved at call time so it's reconfigurable. - train/continue_identifier.py + data/compcars.py: re-landed from the Phase-6 branch (warm-start the 196-class ResNet-50, swap head to the new label space, two-stage fine-tune, Stanford make-level forgetting anchor). - ml_identifier: read class_names from the checkpoint so a continue-trained model self-describes its (now ~2000) classes at inference. - CLI: `ccdp train identifier-continue --dataset vmmrdb --top-n 2000`. - download_datasets.sh: opt-in `VMMRDB=1` (C…2ad33c7
-
Multi-car mode: detect every vehicle, identify each, group damage per car
New "Multi-car (group damage per car)" mode in the demo. Reuses existing pieces: - CarGate.detect_all(): returns every vehicle (box + mask), with a tiny-box filter and containment-aware NMS so one car isn't counted several times and a "whole-scene" super-box / sub-boxes are suppressed. - infer/multi_car.py: assign each damage to the best-overlapping vehicle (mask when available, else bbox), then per-car identify (crop -> identifier) + price via the catalog estimator; total = sum; non-overlapping damage -> unassigned. - viz.annotate_multicar(): one coloured box + label ("Car N: make/model $cost") per car, with that car's damage boxes in the same colour. Verified end-to-end on a 2-car composite (real models): the flat-tire car and the hood-scratch car are detected, grouped, and priced separately; background car shows $0. 8 new tests (grouping by mask/bbox, detect_all, containment NMS). Suite 142 -> 143; lint clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>fbfbcff
-
Fix demo error on 'no car' (and 'no image'): return 6 values to match outputs
The Gradio handler is wired to 6 outputs (annotated, id, variant_a, variant_b, variant_d, json), and the main return produces 6 — but the early "no image" and "no car" exits still returned 5 (pre-Variant-D arity). Gradio can't map 5 values to 6 outputs, so a no-car upload errored in the UI even though the gate correctly reported "no car" in the logs/JSON. Both early returns now return 6 values. Adds tests asserting every _estimate exit (no-image, no-car) returns 6 values. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>bcc0baa -
Merge pull request #16 from theDocWho/checkpoint-21-variant-d-graceful
theDocWhoUnverified signatureHotfix: Variant D fails gracefully when seg weights are absent7bb228c -
Variant D: fail gracefully when seg weights are absent (HF Space hotfix)
On the Space the yoloseg/parts weights aren't present yet, so production_target() returns None. SegModel is lazy, so VariantDPipeline constructed fine and then blew up inside predict() with an uncaught FileNotFoundError ("Segmentation weights not found: None"). Now the pipeline validates + raises at construction, which the demo's _get_pipelines try/except catches → logs "Variant D unavailable" once at startup and disables the variant, instead of erroring on every request. Injected models (tests) bypass the check. Suite green; lint clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>0663d2a -
XGBoost(C): mask-area cost feature from the YOLOv8-seg model
Adds the mask-area path to the cost regressor — true damaged-area % per damage type (vs Variant B's bbox area): - extract_seg_features.py: yoloseg masks -> bbox-compatible feature parquet (GT polygon-area path + real-model path); joins XGBoost like the bbox features. - train_xgb: variant 'c' joins seg features (mirrors 'b'). - CLI: `ccdp train extract-seg-features [--gt]` + `ccdp train xgb --variant c`. Trained + measured locally on CarDD (test split): - GT polygon area (upper bound): R² 0.939, MAPE 10.8% - real yoloseg model masks: R² 0.709, MAPE 25.6% - (for reference) XGBoost(B) bbox: R² 0.736, MAPE 26.0% Honest read: true mask area is a near-perfect predictor of the (synthetic) cost target — but the current yoloseg mask quality (mask mAP50-95 ~0.53) caps the realised feature, landing on par with bbox-based Variant B. The GT↔model gap is headroom a stronger segmenter would unlock. 3 new tests; suite 132 -> 135 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.co…beee3d4 -
Variant D: parts-aware costing (YOLOv8-seg damage ∩ parts → catalog)
Turns the two Colab-trained YOLOv8-seg models into a better cost number, no retraining: - seg_inference.py: SegModel wrapper → per-instance masks resized to the original image so damage and part masks share a pixel grid. - parts_map.py: 23 carparts-seg classes → 15 canonical catalog parts; assign_damage_to_parts() overlaps each damage mask with the part masks (real "dent on front_bumper"), with a bbox-centre heuristic fallback so an unmatched damage still contributes a cost instead of dropping to $0. - variant_d.py: VariantDPipeline → damage∩parts → fallback_estimate (existing three-tier catalog) → calibrated INR/USD cost. - demo: "Variant D (parts-aware seg)" option with damage→part breakdown + overlay. - app.py: best-effort fetch of yoloseg.pt + parts.pt (no prod break if absent). Replaces the heuristic infer_part_from_damage with a learned part assignment for parts-keyed costing. Verified end-to-end on real CarDD images with the trained weights (tire_flat→wheel, scratch→…29bdc89
-
Add make/model confidence floor (0.30) to the deployable Phase 5 stack
Deployable-now branch: car-presence gate + ML identifier wiring (Phase 5) plus the confidence floor — all running on the existing v0.2.0 weights, no Phase 6/7 training apparatus and no Variant C. Below the 0.30 floor an out-of-distribution car's make/model guess is dropped to 'unknown' so cost falls back to body-type/segment pricing instead of mislabelling. Exposed as a demo slider. Suite 123 -> 125 green; deployable on v0.2.0 weights as-is. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>a2fbdab -
Phase 5: activate make/model identification + car-presence gate
Wires up the make/model identifier that was trained in Phase 1.5 but never called, and adds a "is there a car, and where?" gate in front of it — both on the ResNet-50 family (the gate is a COCO Mask R-CNN; masks ignored, boxes used). New modules (ccdp.identification): - car_gate.py — CarGate (COCO maskrcnn_resnet50_fpn) + pure decide()/pad_box; returns the highest-scoring car/truck/bus box or has_car=False. - ml_identifier.py — MLIdentifier inference wrapper around the production identifier checkpoint; maps class idx -> make/model/year via a bundled Stanford Cars class-name resource (no dataset needed). - auto_identify.py — orchestrates gate -> crop -> identifier into one call, returning an IdentificationResult the cost pipelines accept. - data/stanford_cars_classes.json — bundled 196-class name map. Integrations: - car_identifier.identify() gains an opt-in ML stage (use_ml=, injectable), ordered af…c9f7f1e
-
Fix production_target() to fall back to file-on-disk for HF deploy
Latent bug since v0.1.0: registry.json is gitignored and not shipped to the HuggingFace Space, so production_target() returned None for every variant. That meant: * VariantBPipeline raised FileNotFoundError ('No detector weights available') — the loud failure mode the user is currently seeing. * VariantAPipeline silently fell back to an *untrained* ImageNet ResNet50 with a random 6-output head. Every 'damage prediction' on the Space since v0.1.0 was noise from random weights, not the trained model. This is why the v0.2.0 classifier deploy felt like a no-op — the new weights downloaded fine, but production_target still returned None so they were never loaded. Fix: when registry.json has no entry for a variant, check whether checkpoints/production/{variant}.pt exists on disk and resolve to that. This matches the layout app.py creates from the GitHub Release. Full local dev continues to use the registry path when populated, so promote() flows are unchanged. …8469c29 -
Add Stanford Cars as 'no damage' negatives for the classifier
Root cause of the false-positive failure on undamaged cars: CarDD contains only damaged-car photos, so the classifier never sees a 'no damage' example and has no way to predict the empty label set. This commit teaches the trainer to mix Stanford Cars images (which are by-and-large undamaged) into the dataset with all-zero damage labels — Option A from the data-strategy discussion. What's new - iter_negatives() in data/loaders.py: scans Stanford Cars image directory, yields Records with damage_types=[] and dataset tag 'stanford_cars_negative'. Returns nothing (no error) if the directory is missing, so existing CarDD-only flows are unaffected. - mix_negatives() in damage_dataset.py: deterministic, ratio-controlled mixer. Caps at available negatives rather than oversampling with replacement (duplicating identical photos would just teach the model to memorise them). Doesn't mutate inputs. - TrainConfig.negative_ratio + --negative-ratio CLI flag. Default 0.0 preserves legacy …5da26fe -
Honor classifier threshold + show explicit no-detection feedback
Three real issues, surfaced when testing on real-world Indian street photos of a Hyundai Verna: 1. VariantAPipeline.predict(threshold=...) declared the parameter but _forward hardcoded >= 0.5, silently ignoring the override. Now _forward takes threshold as an arg and the parameter actually does what its name promises. New regression tests in tests/test_variant_a_threshold.py pin the behavior. 2. When the YOLOv8 detector found zero boxes, the demo silently returned the un-annotated image — visually indistinguishable from a draw failure. annotate_no_detections now stamps a translucent banner so the user can tell the detector ran and produced no result. 3. The Estimate tab gained a Sensitivity accordion with two sliders (classifier threshold default 0.6 raised from 0.5, detector conf default 0.20 lowered from 0.25) and the Variant B output now reports the box count. The About tab spells out the four real limitations: no 'undamaged' class in CarDD, domai…f2dc9af -
Add annotated image output + 7 beginner notebooks
ccdp.viz.overlay renders YOLO detector boxes onto a PIL image (per-class color palette, Pillow-only, 8 unit tests). Wired into: - Gradio demo: new Image component shows boxes alongside the JSON panel - FastAPI: new /estimate/annotated endpoint returns PNG bytes Beginner notebook series under notebooks/ (00-06) walks through every model from first principles: convolution math, residual connections, two-stage transfer learning, IoU/NMS/mAP, gradient boosting, the catalog calibration trick, end-to-end inference. Each notebook has a Colab bootstrap, demo-scale runnable training cells, and an opt-in full-training cell. Generated via notebooks/_build_notebooks.py (committed so future tweaks edit Python, not ipynb JSON). Tests: 84/84 passing (76 existing + 8 new). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>5b27429
-
checkpoint-3: comparison report, FastAPI, Gradio, HF Space deploy
Phase 3 brings the system to humans. Three deliverables, all reading from the existing v0.1.0 production registry — no new training, no model changes. ## New modules ccdp/preprocess (Stage A: deterministic downscale + quality report). - `normalize_for_inference` LANCZOS-resizes long edges to 1600 px max. - `quality_report` returns sharpness (Laplacian variance), brightness, contrast, low-resolution / low-light / blurry booleans. - Stage B (super-resolution) explicitly deferred to a later checkpoint. ccdp/eval — Variant A vs B head-to-head evaluator. - `metrics.py`: pure per-class P/R/F1 + regression metrics (no sklearn). - `comparison.py`: `build_comparison()` runs both pipelines on the seed=42 test split, returns a `Comparison` dataclass. - `report.py`: Jinja2 -> HTML always; WeasyPrint -> PDF when installed. - `reports/templates/report.html.j2`: A4-formatted report with headline table, per-variant detail, tier/latency, top failures, disclosures. ccdp/api…323fee6 -
Refactor: extract utils + base pipeline, expand docs
Code changes (KISS + single-responsibility): - src/ccdp/utils/{device,transforms}.py: deduplicate `pick_device`, `seed_everything`, IMAGENET stats, train/eval transforms (~120 lines previously copy/pasted across 5 files). - src/ccdp/infer/base.py: new `BaseVariantPipeline` holding the shared downstream — XGBoost loading, calibration, FX conversion, provenance construction. `VariantAPipeline` and `VariantBPipeline` now subclass it and only implement their vision-specific forward pass. - Wire trainers (`train_car_identifier`, `train_damage_classifier`) and the classifier feature extractor to import from `ccdp.utils` instead of holding private copies of the same helpers. - Fix latent bugs surfaced by the smoke test: * `_can_use_xgb` now also checks that the bundle actually loaded. * `BaseVariantPipeline` resolves the production symlink before deriving the bundle dir; previously it pointed at `checkpoints/production/` where `bundle.json` does not live. Do…76955d6 -
Initial commit: end-to-end car damage + repair-cost predictor
Variants A and B trained end-to-end on CarDD + comprehensive + iaai + Stanford Cars datasets, glued together by a versioned parts-cost catalog and a three-tier (exact / nearest-class / catalog-only) cost estimator with full provenance on every prediction. Production metrics (v0.1.0): - Identifier (ResNet50 + RandAugment + MixUp + CutMix): val acc 77.0% - Damage classifier (ResNet50, 6 damage types): val macro-F1 0.834 - YOLOv8n detector: mAP50 0.687, mAP50-95 0.540 - XGBoost(A) (image features only): val R² 0.630, test R² 0.642 - XGBoost(B) (+ bbox features): val R² 0.716, test R² 0.736 64 unit tests pass. Honest disclosures of synthetic-cost limitation in PLAN.md and progress/*.md.4ce7a7e