diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b28a47a..d072829 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: - run: ruff check . - run: python -m build - run: pytest - - run: python tools/validate_results.py --config configs/contest/effiped-tier1.yaml + - run: python tools/validate_results.py --config configs/system/effiped-tier1.yaml - run: python tools/validate_media.py - run: python tools/validate_release.py diff --git a/.gitignore b/.gitignore index 41821b2..835a904 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,8 @@ runs/ *.mov *.mkv *.webm -!docs/media/pdestre/effiped-demo.webm +!docs/media/pdestre/multi-camera-tracking.webm +!docs/media/pdestre/cross-camera-matches.webm tmp/ *.log *.aux diff --git a/AWARD.md b/AWARD.md deleted file mode 100644 index 12f74de..0000000 --- a/AWARD.md +++ /dev/null @@ -1,16 +0,0 @@ -# Award Record - -## 3rd Prize — Student Innovation Project Contest 2026 - -- Project: **Multi-Camera Pedestrian Detection, Tracking & Re-Identification using Joint - ConvNeXt V2 Architecture** -- Short name: **EffiPed** -- Category: **Vertical 1: AI & Intelligent Systems** -- Institution: **VIT Vellore, School of Computer Science and Engineering (SCOPE)** -- Student: **Aswanth Raj** -- Guide: **Sri Preethaa KR** - -The repository’s award wording is based on the author’s contest record and the submitted -project poster. No certificate image is included, and this page does not invent visual proof. -The original submitted poster is treated as an archived contest artifact; its benchmark -snapshot is reconciled separately in `RESULTS.md`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d33fb2..2eaa7b8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ # Contributing -Keep contributions focused on the contest system, local identity-review workflow, or verified -research evolution. Add tests proportional to the change and run: +Keep contributions focused on the EffiPed model, local identity-review workflow, or verified +descriptor research. Add tests proportional to the change and run: ```bash ruff check . @@ -18,5 +18,5 @@ Do not commit datasets, checkpoints, source videos, runtime crops, secrets, abso paths, unrelated projects, or unsupported claims. Every P-DESTRE-derived asset must be non-commercial, attributed, hashed, and listed in `docs/media/ASSET_MANIFEST.json`. -Code contributions are Apache-2.0. Media and third-party material retain their own compatible -terms. +Code contributions are Apache-2.0. Media and third-party material retain their own +compatible terms. diff --git a/MODEL_CARD.md b/MODEL_CARD.md index 7755e04..3403007 100644 --- a/MODEL_CARD.md +++ b/MODEL_CARD.md @@ -3,14 +3,14 @@ ## Summary EffiPed Tier-1 is a compact joint detection and embedding model built around ConvNeXt V2, -P2/P3 fusion, CenterNet outputs, RoIAlign, four body strips, CoordinateAttention, and a -256-D normalized descriptor. BoT-SORT handles local temporal association; a bounded gallery -ranks cross-camera candidates. +P2/P3 fusion, CenterNet outputs, RoIAlign, four body strips, Coordinate Attention, and a +256-D normalized descriptor. BoT-SORT handles camera-local temporal association; a bounded +gallery ranks cross-camera candidates. ## Intended use - Non-commercial research and education. -- Reproduction of the documented contest and matched evaluation configurations. +- Reproduction of the documented system and matched descriptor evaluations. - Local, human-in-the-loop review of user-authorized camera video. - Portfolio demonstration using precomputed, attributed media. @@ -18,27 +18,28 @@ ranks cross-camera candidates. - Automated identity decisions or claims of identity. - Face recognition or biometric identification. -- Public or covert surveillance without a purpose-specific legal, privacy, consent, security, - retention, bias, and human-review assessment. +- Public or covert surveillance without a purpose-specific legal, privacy, consent, + security, retention, bias, and human-review assessment. - Commercial use while training-data terms remain non-commercial or unresolved. ## Evidence boundary -Verified contest evidence includes 62.8% validation and 61.3% test cross-camera Rank-1 on -P-DESTRE, plus 64.08 MOTA, 74.24 IDF1, and 61.34 HOTA on MOT17 val-half. See `RESULTS.md` -for the exact protocol ledger and the separately labeled poster and post-contest evidence. +Published system evidence includes 62.8% validation and 61.3% test cross-camera Rank-1 on +P-DESTRE, plus 64.08 MOTA, 74.24 IDF1, and 61.34 HOTA on MOT17 val-half. See +`RESULTS.md` for the complete protocol ledger. ## Limitations -Scores are sensitive to occlusion, detector localization, pose, clothing ambiguity, illumination, -camera calibration, time gaps, domain shift, and crowd density. A high cosine similarity is -candidate evidence, not proof that two observations depict the same person. The public -evaluation does not establish performance for other locations, populations, or camera networks. +Scores are sensitive to occlusion, detector localization, pose, clothing ambiguity, +illumination, camera calibration, time gaps, domain shift, and crowd density. A high cosine +similarity is candidate evidence, not proof that two observations depict the same person. +The published evaluation does not establish performance for other locations, populations, +or camera networks. ## Artifact status -The versioned manifest describes `effiped-tier1-contest-v1.pt`, but public checkpoint -distribution is on hold. Runtime construction always sets `pretrained=false` when loading a -checkpoint, avoiding an unnecessary backbone download. +The versioned manifest describes `effiped-tier1-v1.pt`, but public checkpoint distribution +is on hold. Runtime construction always uses `pretrained=false` when loading a checkpoint, +avoiding an unnecessary backbone download. Maintainer: [Aswanth Raj](https://github.com/aswanth-07) diff --git a/README.md b/README.md index 1a19698..8e8fd0d 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,86 @@
- EffiPed — 3rd-Prize multi-camera pedestrian tracking system + EffiPed multi-camera pedestrian tracking and identity-review system # EffiPed ## Multi-Camera Pedestrian Detection, Tracking & Re-Identification using Joint ConvNeXt V2 Architecture - **3rd Prize — Student Innovation Project Contest 2026** - Vertical 1: AI & Intelligent Systems · VIT Vellore SCOPE - - By [Aswanth Raj](https://github.com/aswanth-07) · Guide: Sri Preethaa KR + By [Aswanth Raj](https://github.com/aswanth-07) [![CI](https://github.com/aswanth-07/effiped-multi-camera-tracking/actions/workflows/ci.yml/badge.svg)](https://github.com/aswanth-07/effiped-multi-camera-tracking/actions/workflows/ci.yml) - [![Software: Apache-2.0](https://img.shields.io/badge/software-Apache--2.0-24e6bd)](LICENSE) - [![Media: CC BY-NC-SA 4.0](https://img.shields.io/badge/P--DESTRE_media-CC_BY--NC--SA_4.0-e8a94d)](docs/media/LICENSE.md) + [![Software: Apache-2.0](https://img.shields.io/badge/software-Apache--2.0-22c7b8)](LICENSE) + [![Media: CC BY-NC-SA 4.0](https://img.shields.io/badge/P--DESTRE_media-CC_BY--NC--SA_4.0-70b8ff)](docs/media/LICENSE.md)
+EffiPed is a compact video-intelligence system that detects pedestrians, maintains +camera-local tracks, and ranks cross-camera identity candidates for human review. Its +React investigation console is available as a precomputed browser demo; the same workflow +can connect to local FastAPI/CUDA inference when an authorized checkpoint is available. + > [!IMPORTANT] -> The hosted experience is a precomputed, non-commercial research demonstration. It ranks -> appearance evidence for human review; it does not prove identity. Public model weights are -> withheld while training-data redistribution terms remain unresolved. +> Ranked matches are reviewable appearance evidence, not proof of identity. The hosted +> experience is a precomputed, non-commercial research demonstration. Public model weights +> remain withheld while training-data redistribution terms are unresolved. + +## Try the identity-review demo + +The hosted UI restores the original PedestrianTracker workflow: + +- a synchronized four-camera replay with tracker-rendered boxes; +- indexed query crops and ranked cross-camera candidates; +- camera scope, playback, frame stepping, and detection timelines; +- a second replay containing the archived cross-camera association output; +- responsive desktop and mobile review modes. + +```bash +cd apps/web +npm install +npm run dev +``` + +No synthetic browser boxes are drawn over the footage. The boxes visible in the demo are +the annotations rendered by the original tracking pipeline. -## The contest system +## System -EffiPed shares one compact ConvNeXt V2 feature hierarchy across three connected tasks: -CenterNet-style pedestrian detection, BoT-SORT temporal association, and 256-D part-based -descriptors for cross-camera candidate retrieval. RoIAlign divides each person into four -body strips, CoordinateAttention weights visible evidence, and the analyst UI exposes the -result as reviewable candidates rather than an automated identity verdict. +One ConvNeXt V2 feature hierarchy supports CenterNet-style detection and a 256-D +part-aware identity descriptor. RoIAlign extracts a person feature map, four horizontal +body strips retain local appearance, and Coordinate Attention fuses the visible evidence. +BoT-SORT combines motion, overlap, and appearance for local temporal association; the +gallery then ranks possible cross-camera matches for an analyst. -| Verified contest evidence | Result | +| Evaluation | Result | |---|---:| | P-DESTRE validation cross-camera Rank-1 | **62.8%** | | P-DESTRE test cross-camera Rank-1 | **61.3%** | | P-DESTRE validation / test detection mAP@0.5 | **90.74% / 88.4%** | | MOT17 val-half MOTA / IDF1 / HOTA | **64.08 / 74.24 / 61.34** | -| Canonical Tier-1 footprint | **7.78M · ≈18 full-pipeline FPS** | +| EffiPed Tier-1 footprint | **7.78M · ≈18 full-pipeline FPS** | -The submitted poster is preserved as an archived contest artifact with its original -`7.92M / 22 FPS / 62.8%` snapshot. The later canonical registry associates Tier-1 with -`7.78M` parameters and approximately `18 FPS` for the full pipeline. The poster’s -`+16.2 pp` row combined multiple configuration changes and is not presented as a pure -part-only ablation. See [RESULTS.md](RESULTS.md). +Each value has a protocol label in [RESULTS.md](RESULTS.md). The interactive replay is an +application demonstration, not a benchmark run. -## From contest prototype to research +## Architecture -```text -EffiPed contest system - joint detection + four-strip descriptor + multi-camera review - │ - ├── PartJDE matched readout study: +6.66 pp validation Rank-1 - │ - └── BoxJDE five-fold readout study: - +13.64/+12.94 pp source-level Rank-1/mAP - +13.31/+12.29 pp natural predicted-box - +13.01/+12.00 pp natural end-to-end -``` +[![EffiPed end-to-end architecture](docs/architecture/effiped-architecture.svg)](docs/architecture/effiped-architecture.svg) -BoxJDE uses a constructed P-DESTRE per-date ablation, not official Task 4. Its complete -code, evidence, and technical report live in the -[BoxJDE Person Search repository](https://github.com/aswanth-07/boxjde-person-search). +The diagram is also available as an +[editable PowerPoint](docs/architecture/effiped-architecture.pptx). ## Repository map ```text -src/effiped/ installable model, descriptor, tracker, runtime +src/effiped/ installable model, descriptors, tracking, runtime apps/api/ FastAPI local-GPU service and job lifecycle -apps/web/ React/Vite portfolio + identity-review UI -configs/contest/ contest and matched PartJDE configurations -research/results/ the single evidence fixture +apps/web/ React/Vite identity-review UI and hosted replay +configs/system/ active EffiPed and matched PartJDE configurations +research/results/ single source of truth for published evidence research/report/ generated technical report -docs/architecture/ editable PowerPoint + site exports -docs/media/ optimized attributed demonstration media -tools/ and tests/ release validation and regression tests -``` - -## Explore the Vercel-safe demo - -```bash -cd apps/web -npm install -npm run dev +docs/architecture/ editable diagram source and web exports +docs/media/ optimized, attributed demonstration media +tools/ and tests/ validation, regression, and release checks ``` -The demo supports camera switching, clickable tracks, query selection, confidence-grouped -cross-camera candidates, timeline navigation, and evidence details without uploading video. - ## Run live inference locally Python 3.11 and an NVIDIA GPU are recommended. @@ -97,20 +93,15 @@ pip install -e ".[runtime]" effiped-app ``` -Place an authorized checkpoint in `EFFIPED_WEIGHTS_DIR`; the API reports unavailable -weights cleanly when none is present. - -Containerized local runtime: +Place an authorized checkpoint in `EFFIPED_WEIGHTS_DIR`. When none is present, the API +reports the model as unavailable without exposing a local filesystem path. ```bash -docker build -t effiped . -docker run --gpus all --rm -p 127.0.0.1:8000:8000 \ - -v /authorized/weights:/weights:ro -v effiped-runtime:/runtime effiped +effiped-train --config configs/system/effiped-tier1.yaml +effiped-eval --config configs/system/effiped-tier1.yaml +effiped-demo ``` -The loopback-only host mapping keeps the review surface local while the container listens on -its internal interface. - | Variable | Purpose | |---|---| | `EFFIPED_WEIGHTS_DIR` | authorized local model artifacts | @@ -119,14 +110,6 @@ its internal interface. | `EFFIPED_MAX_UPLOAD_MB` | per-video upload limit | | `EFFIPED_ALLOWED_ORIGINS` | comma-separated CORS allowlist | -Commands: - -```bash -effiped-train --config configs/contest/effiped-tier1.yaml -effiped-eval --config configs/contest/effiped-tier1.yaml -effiped-demo -``` - ## Public API - `GET /api/health` @@ -138,22 +121,28 @@ effiped-demo - `DELETE /api/person-search/jobs/{job_id}` - `GET /api/assets/{asset_id}` -Deleting a job removes uploaded video and all generated assets. Model metadata is portable -and never exposes workstation paths. +Deleting a job removes uploaded video and generated assets. + +## Research connections + +The later [BoxJDE Person Search](https://github.com/aswanth-07/boxjde-person-search) +repository isolates the full-person descriptor readout and documents its five-fold +P-DESTRE ablation. It is linked as related research; its code and report are not duplicated +here. -## Reports, architecture, and responsible use +## Licensing and responsible use -- [Editable architecture PowerPoint](docs/architecture/effiped-architecture.pptx) -- [Technical report](docs/report/effiped-technical-report.pdf) -- [Model card](MODEL_CARD.md) -- [Award record](AWARD.md) -- [Data and weight-release audit](DATA_LICENSES.md) -- [Third-party notices](THIRD_PARTY_NOTICES.md) +Original software is © 2026 Aswanth Raj and licensed under Apache-2.0. P-DESTRE-derived +media under `docs/media/pdestre/` is separately licensed as a CC BY-NC-SA 4.0 adaptation +for this non-commercial showcase. The +[asset manifest](docs/media/ASSET_MANIFEST.json) records the source, transformations, +hash, purpose, and license for every derived asset. -Software is © 2026 Aswanth Raj and licensed under Apache-2.0. P-DESTRE-derived media under -`docs/media/pdestre/` is a separate CC BY-NC-SA 4.0 adaptation for this non-commercial -showcase. Its [asset manifest](docs/media/ASSET_MANIFEST.json) records transformations and -hashes. No source videos, datasets, person-level benchmark records, or checkpoints are included. +No dataset, source video, person-level benchmark record, checkpoint, or runtime crop is +included. +[Model card](MODEL_CARD.md) · +[Data and weight-release audit](DATA_LICENSES.md) · +[Third-party notices](THIRD_PARTY_NOTICES.md) · [P-DESTRE paper](https://arxiv.org/abs/2004.02782) · [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) diff --git a/RESULTS.md b/RESULTS.md index f5e0c44..e64000d 100644 --- a/RESULTS.md +++ b/RESULTS.md @@ -1,9 +1,9 @@ # Results and Protocol Ledger -All website cards, charts, README values, and report tables are generated from +The website, README, report, charts, and tests read the same checked-in fixture: `research/results/summary.json`. -## 1. Verified contest system +## EffiPed system benchmarks | Evaluation | Metric | Value | |---|---|---:| @@ -15,17 +15,13 @@ All website cards, charts, README values, and report tables are generated from | MOT17 val-half Protocol A | IDF1 | 74.24 | | MOT17 val-half Protocol A | HOTA | 61.34 | -Canonical Tier-1 footprint: 7.78M parameters and approximately 18 FPS for the full tracking -pipeline at 1088×608 on an NVIDIA RTX 5080 Laptop GPU. +EffiPed Tier-1 contains 7.78M parameters and runs at approximately 18 FPS for the complete +tracking pipeline at 1088×608 on an NVIDIA RTX 5080 Laptop GPU. -## 2. Contest submission snapshot +The browser replay is not used to measure these numbers. It is an interactive presentation +of archived detector, tracker, and cross-camera association output. -The submitted poster displayed 7.92M parameters, 22 FPS, and 62.8% cross-camera Rank-1. -That snapshot is preserved as submitted. It is not substituted for the later canonical -Tier-1 measurement. The poster’s +16.2 percentage-point row combined multiple configuration -differences; it is not a pure part-readout ablation. - -## 3. Post-contest evolution +## Descriptor research extensions | Study | Boundary | Rank-1 gain | mAP gain | |---|---|---:|---:| @@ -34,5 +30,13 @@ differences; it is not a pure part-readout ablation. | BoxJDE | five-fold natural predicted boxes | +13.31 pp | +12.29 pp | | BoxJDE | five-fold natural end-to-end | +13.01 pp | +12.00 pp | -PartJDE’s separate evaluation reports 7.92M parameters and 27.0 FPS. BoxJDE’s primary -P-DESTRE protocol is a constructed per-date descriptor-readout ablation, not official Task 4. +PartJDE's separate evaluation reports 7.92M parameters and 27.0 FPS. BoxJDE uses a +constructed P-DESTRE per-date descriptor-readout ablation, not official Task 4; its full +evidence and technical report live in the +[BoxJDE repository](https://github.com/aswanth-07/boxjde-person-search). + +## Interpretation boundary + +Cross-camera similarities rank candidate evidence. They do not establish identity, and the +reported evaluations do not establish behavior for other sites, populations, cameras, or +operating conditions. diff --git a/apps/api/main.py b/apps/api/main.py index 2893f3e..c0fab4b 100644 --- a/apps/api/main.py +++ b/apps/api/main.py @@ -48,7 +48,7 @@ def models(): @app.post("/api/person-search/jobs", response_model=JobCreateOut) def create_person_search_job( files: List[UploadFile] = File(...), - model_key: str = Form("effiped_tier1_contest"), + model_key: str = Form("effiped_tier1"), decode_thresh: float = Form(0.05), track_thresh: float = Form(0.25), max_frames: int = Form(0), diff --git a/apps/api/person_search_service.py b/apps/api/person_search_service.py index 1476577..fae6eb2 100644 --- a/apps/api/person_search_service.py +++ b/apps/api/person_search_service.py @@ -179,7 +179,7 @@ def _save_rgb(path: Path, image_rgb: np.ndarray) -> None: def _normalize_settings(settings: Dict[str, Any]) -> Dict[str, Any]: return { - "model_key": str(settings.get("model_key") or "effiped_tier1_contest"), + "model_key": str(settings.get("model_key") or "effiped_tier1"), "decode_thresh": float(settings.get("decode_thresh", 0.05)), "track_thresh": float(settings.get("track_thresh", 0.25)), "max_frames": int(settings.get("max_frames", 0)), diff --git a/apps/web/index.html b/apps/web/index.html index a4818e0..89a73ef 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -3,21 +3,20 @@ - + - + - EffiPed — 3rd-Prize Multi-Camera Pedestrian Tracking + EffiPed — Multi-Camera Pedestrian Tracking
- diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 694ce2c..8d1f274 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,30 +1,35 @@ import { - ArrowDownRight, - ArrowUpRight, - Award, - BookOpen, - Box, + ArrowRight, + Boxes, Camera, CheckCircle2, CodeXml, ExternalLink, - FileText, - Layers3, - ShieldAlert, - Sparkles, - Workflow + Gauge, + Network, + ScanSearch, + ShieldCheck } from "lucide-react"; + import { Architecture } from "./components/Architecture"; -import { Investigation } from "./components/Investigation"; +import { DemoConsole } from "./components/DemoConsole"; import { LiveConsole } from "./components/LiveConsole"; import { results } from "./data/results"; const repository = "https://github.com/aswanth-07/effiped-multi-camera-tracking"; const boxjdeRepository = "https://github.com/aswanth-07/boxjde-person-search"; -function Metric({ value, label, note }: { value: string; label: string; note: string }) { +function Benchmark({ + value, + label, + note +}: { + value: string; + label: string; + note: string; +}) { return ( -
+
{value} {label} {note} @@ -33,295 +38,201 @@ function Metric({ value, label, note }: { value: string; label: string; note: st } function App() { - const contest = results.verified_contest_system; - const partjde = results.post_contest_evolution.partjde; - const boxjde = results.post_contest_evolution.boxjde; + const benchmark = results.system_benchmarks; const mode = import.meta.env.VITE_APP_MODE ?? "demo"; return (
-
diff --git a/apps/web/src/components/Architecture.tsx b/apps/web/src/components/Architecture.tsx index 87b2c63..1127b56 100644 --- a/apps/web/src/components/Architecture.tsx +++ b/apps/web/src/components/Architecture.tsx @@ -46,4 +46,3 @@ export function Architecture() { ); } - diff --git a/apps/web/src/components/DemoConsole.tsx b/apps/web/src/components/DemoConsole.tsx new file mode 100644 index 0000000..75084fd --- /dev/null +++ b/apps/web/src/components/DemoConsole.tsx @@ -0,0 +1,495 @@ +import { + Activity, + BriefcaseBusiness, + CheckCircle2, + ChevronsLeft, + ChevronsRight, + Crosshair, + Database, + Expand, + FileSearch, + Gauge, + Images, + Pause, + Play, + Route, + Search, + ShieldCheck, + UserRound, + Video +} from "lucide-react"; +import { useEffect, useMemo, useRef, useState } from "react"; + +import { results } from "../data/results"; +import type { DemoSubject } from "../types"; + +type WorkspaceTab = "index" | "investigation"; + +type TimelineSegment = { + start: number; + duration: number; + color: string; +}; + +const timelineSegments: TimelineSegment[][] = [ + [ + { start: 0.5, duration: 2.5, color: "#22c7b8" }, + { start: 4.2, duration: 4.6, color: "#7dd3fc" }, + { start: 10.6, duration: 2.8, color: "#f59e0b" } + ], + [ + { start: 1.4, duration: 3.1, color: "#a78bfa" }, + { start: 5.3, duration: 5.4, color: "#22c7b8" }, + { start: 11.6, duration: 2.1, color: "#34d399" } + ], + [ + { start: 0.2, duration: 4.8, color: "#f472b6" }, + { start: 6.1, duration: 3.7, color: "#60a5fa" }, + { start: 10.5, duration: 4.0, color: "#22c7b8" } + ], + [ + { start: 2.2, duration: 3.2, color: "#facc15" }, + { start: 6.5, duration: 5.3, color: "#22c7b8" }, + { start: 12.2, duration: 2.2, color: "#c084fc" } + ] +]; + +const candidateSeekTimes = [4.2, 6.1, 8.4, 10.8]; + +function formatClock(seconds: number) { + const value = Math.max(0, Math.floor(seconds)); + return `${Math.floor(value / 60)}:${String(value % 60).padStart(2, "0")}`; +} + +function MetricCard({ + icon: Icon, + label, + value +}: { + icon: typeof Video; + label: string; + value: string; +}) { + return ( +
+
+ ); +} + +function SubjectPicker({ + subjects, + selectedId, + onSelect +}: { + subjects: DemoSubject[]; + selectedId: string; + onSelect: (subject: DemoSubject) => void; +}) { + return ( +
+ {subjects.map((subject) => ( + + ))} +
+ ); +} + +export function DemoConsole() { + const demo = results.demo_case; + const stats = demo.session_diagnostic; + const [activeTab, setActiveTab] = useState("investigation"); + const [selectedSubjectId, setSelectedSubjectId] = useState(demo.subjects[0].id); + const [selectedVideoId, setSelectedVideoId] = useState(demo.videos[0].id); + const [selectedCandidateRank, setSelectedCandidateRank] = useState(1); + const [currentTime, setCurrentTime] = useState(0); + const [duration, setDuration] = useState(15); + const [isPlaying, setIsPlaying] = useState(false); + const [playbackRate, setPlaybackRate] = useState(1); + const videoRef = useRef(null); + const videoFrameRef = useRef(null); + + const selectedSubject = useMemo( + () => demo.subjects.find((subject) => subject.id === selectedSubjectId) ?? demo.subjects[0], + [demo.subjects, selectedSubjectId] + ); + const selectedVideo = useMemo( + () => demo.videos.find((video) => video.id === selectedVideoId) ?? demo.videos[0], + [demo.videos, selectedVideoId] + ); + + useEffect(() => { + const video = videoRef.current; + if (video) video.playbackRate = playbackRate; + }, [playbackRate, selectedVideoId]); + + function selectSubject(subject: DemoSubject) { + setSelectedSubjectId(subject.id); + setSelectedCandidateRank(1); + } + + function seekTo(value: number) { + const video = videoRef.current; + const clamped = Math.max(0, Math.min(value, duration || 15)); + if (video) video.currentTime = clamped; + setCurrentTime(clamped); + } + + function stepFrame(direction: -1 | 1) { + seekTo(currentTime + direction / stats.playback_fps); + } + + function togglePlayback() { + const video = videoRef.current; + if (!video) return; + if (video.paused) void video.play(); + else video.pause(); + } + + function openCandidate(rank: number) { + setSelectedCandidateRank(rank); + setSelectedVideoId("cross-camera"); + window.requestAnimationFrame(() => seekTo(candidateSeekTimes[rank - 1] ?? 4.2)); + } + + return ( +
+
+
+
Video intelligence workbench
+

Identity Review Console

+

+ Explore the original PedestrianTracker replay, inspect indexed subject crops, + and review ranked cross-camera evidence without downloading a model. +

+
+
+ + + + +
+
+ +
+
+
+
+
+
1Source intake
+
2Identity index
+
3Evidence review
+
+ +
+ + +
+ + {activeTab === "index" ? ( +
+ + +
+
+
+ +
+
Processed frames{stats.frames}
+
Local tracks{stats.local_tracks}
+
Cross-camera IDs{stats.cross_camera_ids}
+
Association precision{Math.round(stats.pairwise_association_precision * 100)}%
+
+

{stats.label}

+
+ + +
+ ) : ( +
+ + +
+
+
+ Video playback + {selectedVideo.label} +
+ +
+ +
+
+
+
+
+
+ +
+ + + + {formatClock(currentTime)} / {formatClock(duration)} + seekTo(Number(event.target.value))} + step={1 / stats.playback_fps} + type="range" + value={currentTime} + /> + + +
+ +
+
+
+ Detection Timeline + Camera-local track segments +
+ click a segment to seek +
+
+ {timelineSegments.map((segments, cameraIndex) => ( +
+ C{cameraIndex + 1} +
+ {segments.map((segment) => ( +
+
+ ))} +
+
+
+ + +
+ )} +
+ ); +} diff --git a/apps/web/src/components/Investigation.tsx b/apps/web/src/components/Investigation.tsx deleted file mode 100644 index 7470825..0000000 --- a/apps/web/src/components/Investigation.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { useMemo, useState } from "react"; -import { Camera, Crosshair, LocateFixed, Play, ShieldCheck } from "lucide-react"; -import { results } from "../data/results"; - -export function Investigation() { - const demo = results.demo_case; - const [activeCamera, setActiveCamera] = useState(demo.cameras[0].id); - const [selectedSubject, setSelectedSubject] = useState(demo.subjects[0].id); - const subject = useMemo( - () => demo.subjects.find((item) => item.id === selectedSubject) ?? demo.subjects[0], - [demo.subjects, selectedSubject] - ); - const camera = demo.cameras.find((item) => item.id === activeCamera) ?? demo.cameras[0]; - const visible = demo.subjects - .flatMap((item) => item.appearances.map((appearance) => ({ ...appearance, subject: item }))) - .filter((appearance) => appearance.camera === camera.id); - const candidates = subject.appearances - .filter((appearance) => appearance.camera !== activeCamera) - .sort((a, b) => b.similarity - a.similarity); - - return ( -
-
-
- Precomputed identity review -

{demo.title}

-
-
Research replay · no upload
-
- -
-
-
- {demo.cameras.map((item) => ( - - ))} -
-
-
- REC · {camera.time} - {camera.label} -
- {visible.map((appearance) => ( - - ))} -
-
-
- 00:00 -
- 00:15 -
-
- - -
-
- ); -} - diff --git a/apps/web/src/data/results.test.ts b/apps/web/src/data/results.test.ts index 7248d59..801c6a1 100644 --- a/apps/web/src/data/results.test.ts +++ b/apps/web/src/data/results.test.ts @@ -2,16 +2,17 @@ import { describe, expect, it } from "vitest"; import { results } from "./results"; describe("canonical result fixture", () => { - it("keeps contest, poster, and research evidence separated", () => { - expect(results.verified_contest_system.pdestre.validation.rank1_cross).toBe(62.8); - expect(results.verified_contest_system.footprint.pipeline_fps_approx).toBe(18); - expect(results.contest_submission_snapshot.reported_fps).toBe(22); - expect(results.post_contest_evolution.partjde.matched_part_readout_gain_pp).toBe(6.66); - expect(results.post_contest_evolution.boxjde.natural_e2e_rank1_gain_pp).toBe(13.01); + it("keeps benchmark and research evidence machine-readable", () => { + expect(results.system_benchmarks.pdestre.validation.rank1_cross).toBe(62.8); + expect(results.system_benchmarks.footprint.pipeline_fps_approx).toBe(18); + expect(results.research_extensions.partjde.matched_part_readout_gain_pp).toBe(6.66); + expect(results.research_extensions.boxjde.natural_e2e_rank1_gain_pp).toBe(13.01); }); - it("labels the hosted case as a precomputed research replay", () => { + it("contains real replay videos and retrieval candidates", () => { expect(results.demo_case.description.toLowerCase()).toContain("precomputed"); - expect(results.demo_case.cameras).toHaveLength(4); + expect(results.demo_case.videos).toHaveLength(2); + expect(results.demo_case.subjects).toHaveLength(3); + expect(results.demo_case.subjects[0].candidates).toHaveLength(4); }); }); diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index 410a36e..b49b7b2 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -1,352 +1,2042 @@ -@import url("https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Manrope:wght@400;500;600;700;800&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Inter:wght@400;500;600;700&family=Manrope:wght@500;600;700;800&display=swap"); :root { color-scheme: dark; - font-family: "Manrope", system-ui, sans-serif; - color: #ecf9f6; - background: #06100f; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + color: #ecf5f4; + background: #070a0f; font-synthesis: none; - --ink: #ecf9f6; - --muted: #8ca49f; - --line: rgba(132, 188, 176, 0.2); - --panel: rgba(11, 30, 27, 0.82); - --teal: #24e6bd; - --teal-soft: #7bf6db; - --bronze: #e8a94d; - --danger: #ff6a76; - --max: 1220px; -} - -* { box-sizing: border-box; } -html { scroll-behavior: smooth; background: #06100f; } + text-rendering: optimizeLegibility; + --bg: #070a0f; + --surface: #0d1219; + --surface-2: #111923; + --surface-3: #16212b; + --surface-hover: #1a2833; + --line: rgba(159, 190, 195, 0.15); + --line-strong: rgba(159, 190, 195, 0.25); + --text: #ecf5f4; + --muted: #91a4a8; + --faint: #8ba0a3; + --accent: #22c7b8; + --accent-bright: #70f0e1; + --accent-dim: rgba(34, 199, 184, 0.12); + --blue: #70b8ff; + --warning: #f2b86f; + --danger: #f47f8b; + --mono: "DM Mono", "SFMono-Regular", Consolas, monospace; + --display: Manrope, Inter, sans-serif; + --radius: 10px; + --shadow: 0 26px 80px rgba(0, 0, 0, 0.34); +} + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; + background: var(--bg); +} + body { margin: 0; - min-width: 320px; + min-width: 320px; + min-height: 100vh; + overflow-x: hidden; + background: + radial-gradient(circle at 12% 2%, rgba(34, 199, 184, 0.09), transparent 28rem), + radial-gradient(circle at 87% 12%, rgba(92, 138, 255, 0.06), transparent 24rem), + var(--bg); +} + +button, +select, +input { + font: inherit; +} + +button, +a { + -webkit-tap-highlight-color: transparent; +} + +button { + color: inherit; +} + +a { + color: inherit; + text-decoration: none; +} + +img, +video, +svg { + display: block; + max-width: 100%; +} + +button:focus-visible, +a:focus-visible, +select:focus-visible, +input:focus-visible { + outline: 2px solid var(--accent-bright); + outline-offset: 3px; +} + +.site-shell { + min-height: 100vh; +} + +.site-nav { + position: sticky; + z-index: 50; + top: 0; + display: flex; + align-items: center; + justify-content: space-between; + min-height: 68px; + padding: 0 clamp(20px, 4vw, 68px); + border-bottom: 1px solid var(--line); + background: rgba(7, 10, 15, 0.88); + backdrop-filter: blur(18px); +} + +.site-brand { + display: grid; + grid-template-columns: 34px auto; + grid-template-rows: 19px 15px; + column-gap: 10px; + align-items: center; +} + +.site-brand > span { + grid-row: 1 / 3; + display: grid; + place-items: center; + width: 34px; + height: 34px; + border: 1px solid rgba(112, 240, 225, 0.45); + border-radius: 8px; + color: var(--accent-bright); + background: linear-gradient(145deg, rgba(34, 199, 184, 0.19), rgba(34, 199, 184, 0.02)); + font-family: var(--display); + font-weight: 800; +} + +.site-brand strong { + font-family: var(--display); + font-size: 15px; + letter-spacing: 0.01em; +} + +.site-brand small { + color: var(--muted); + font-family: var(--mono); + font-size: 9px; + letter-spacing: 0.13em; + text-transform: uppercase; +} + +.site-nav > div { + display: flex; + gap: 26px; + align-items: center; +} + +.site-nav > div a { + display: inline-flex; + gap: 7px; + align-items: center; + color: #a9b8ba; + font-size: 12px; + font-weight: 600; + transition: color 160ms ease; +} + +.site-nav > div a:hover { + color: var(--text); +} + +.project-hero { + display: grid; + grid-template-columns: minmax(0, 0.83fr) minmax(520px, 1.17fr); + gap: clamp(42px, 7vw, 110px); + align-items: center; + width: min(1600px, calc(100% - 72px)); + min-height: calc(100vh - 68px); + margin: 0 auto; + padding: 72px 0 82px; +} + +.hero-kicker, +.section-heading > span, +.local-run > div > span { + margin: 0 0 18px; + color: var(--accent); + font-family: var(--mono); + font-size: 11px; + font-weight: 500; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +.project-hero h1 { + max-width: 760px; + margin: 0; + color: #f4faf9; + font-family: var(--display); + font-size: clamp(42px, 5vw, 74px); + font-weight: 700; + line-height: 1.02; + letter-spacing: -0.047em; +} + +.hero-summary { + max-width: 680px; + margin: 27px 0 0; + color: #aebdbf; + font-size: clamp(16px, 1.4vw, 19px); + line-height: 1.7; +} + +.hero-actions { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 36px; +} + +.primary-link, +.secondary-link { + display: inline-flex; + gap: 9px; + align-items: center; + justify-content: center; + min-height: 46px; + padding: 0 18px; + border: 1px solid var(--line-strong); + border-radius: 7px; + font-size: 12px; + font-weight: 700; +} + +.primary-link { + border-color: var(--accent); + color: #04100f; + background: var(--accent); +} + +.secondary-link { + background: rgba(255, 255, 255, 0.025); +} + +.primary-link:hover { + background: var(--accent-bright); +} + +.secondary-link:hover { + border-color: rgba(112, 240, 225, 0.46); +} + +.project-byline { + display: flex; + flex-wrap: wrap; + gap: 10px 24px; + margin-top: 31px; + color: var(--muted); + font-size: 11px; +} + +.project-byline span + span { + position: relative; + font-family: var(--mono); + color: #6f8588; +} + +.project-byline span + span::before { + position: absolute; + left: -13px; + content: "·"; +} + +.hero-frame { + position: relative; + overflow: hidden; + border: 1px solid rgba(135, 195, 191, 0.22); + border-radius: 12px; + background: #020405; + box-shadow: var(--shadow); +} + +.hero-frame::before { + position: absolute; + z-index: 2; + inset: 0; + pointer-events: none; + border-radius: inherit; + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.035); + content: ""; +} + +.hero-frame video { + width: 100%; + aspect-ratio: 16 / 9; + object-fit: cover; +} + +.hero-frame__hud { + position: absolute; + z-index: 3; + right: 0; + bottom: 0; + left: 0; + display: flex; + align-items: center; + justify-content: space-between; + gap: 18px; + padding: 13px 16px; + border-top: 1px solid rgba(255, 255, 255, 0.09); + background: rgba(3, 7, 10, 0.84); + backdrop-filter: blur(10px); +} + +.hero-frame__hud span, +.hero-frame__hud strong { + display: flex; + gap: 8px; + align-items: center; + font-size: 10px; +} + +.hero-frame__hud span { + color: #b9c7c8; + font-family: var(--mono); + text-transform: uppercase; +} + +.hero-frame__hud strong { + color: #829496; + font-weight: 500; +} + +.status-dot { + display: inline-block; + width: 7px; + height: 7px; + flex: 0 0 7px; + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 0 4px rgba(34, 199, 184, 0.14), 0 0 12px rgba(34, 199, 184, 0.62); +} + +/* Restored PedestrianTracker identity-review workspace */ +.console-shell { + width: min(1760px, calc(100% - 40px)); + margin: 0 auto 120px; + overflow: hidden; + border: 1px solid var(--line-strong); + border-radius: 13px; + background: #090e14; + box-shadow: 0 40px 120px rgba(0, 0, 0, 0.44); +} + +.topbar { + display: flex; + justify-content: space-between; + gap: 32px; + padding: 24px 26px; + border-bottom: 1px solid var(--line); + background: linear-gradient(100deg, #101820, #0b1118 58%, #0d171c); +} + +.title-block { + max-width: 760px; +} + +.eyebrow { + margin-bottom: 7px; + color: var(--accent); + font-family: var(--mono); + font-size: 9px; + font-weight: 500; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +.title-block h2 { + margin: 0; + font-family: var(--display); + font-size: 25px; + line-height: 1.2; + letter-spacing: -0.02em; +} + +.title-block p { + max-width: 760px; + margin: 7px 0 0; + color: var(--muted); + font-size: 12px; + line-height: 1.55; +} + +.top-metrics { + display: grid; + grid-template-columns: repeat(4, minmax(105px, 1fr)); + gap: 8px; + align-self: center; +} + +.metric-card { + display: grid; + grid-template-columns: 20px 1fr; + grid-template-rows: 17px 21px; + min-width: 108px; + padding: 10px 12px; + border: 1px solid var(--line); + border-radius: 7px; + background: rgba(255, 255, 255, 0.018); +} + +.metric-card svg { + grid-row: 1 / 3; + align-self: center; + color: var(--accent); +} + +.metric-card span { + color: var(--faint); + font-family: var(--mono); + font-size: 8px; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.metric-card strong { + color: #f3f8f7; + font-family: var(--display); + font-size: 14px; +} + +.case-strip { + display: flex; + min-height: 56px; + align-items: stretch; + border-bottom: 1px solid var(--line); + background: #0b1118; +} + +.case-summary { + display: flex; + min-width: 225px; + gap: 10px; + align-items: center; + padding: 0 20px; + border-right: 1px solid var(--line); +} + +.case-summary svg { + color: var(--accent); +} + +.case-summary div { + display: grid; + gap: 2px; +} + +.case-summary span, +.workflow-step { + color: var(--faint); + font-family: var(--mono); + font-size: 8px; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.case-summary strong { + font-size: 11px; + font-weight: 600; +} + +.workflow-step { + display: flex; + flex: 1; + min-width: 140px; + gap: 9px; + align-items: center; + justify-content: center; + border-right: 1px solid var(--line); +} + +.workflow-step span { + display: grid; + place-items: center; + width: 21px; + height: 21px; + border: 1px solid #405055; + border-radius: 50%; + color: #7e9093; +} + +.workflow-step.done { + color: #8da4a3; +} + +.workflow-step.done span { + border-color: rgba(34, 199, 184, 0.4); + color: var(--accent); + background: var(--accent-dim); +} + +.workflow-step.active { + color: var(--text); + background: linear-gradient(180deg, rgba(34, 199, 184, 0.08), transparent); +} + +.workflow-step.active span { + border-color: var(--accent); + color: #061312; + background: var(--accent); +} + +.workspace-tabs { + display: flex; + height: 46px; + border-bottom: 1px solid var(--line); + background: #0a1016; +} + +.workspace-tabs button { + position: relative; + min-width: 180px; + border: 0; + border-right: 1px solid var(--line); + color: #788b8e; + background: transparent; + font-size: 11px; + font-weight: 600; + cursor: pointer; +} + +.workspace-tabs button::after { + position: absolute; + right: 18px; + bottom: -1px; + left: 18px; + height: 2px; + background: transparent; + content: ""; +} + +.workspace-tabs button:hover, +.workspace-tabs button.active { + color: var(--text); + background: rgba(255, 255, 255, 0.02); +} + +.workspace-tabs button.active::after { + background: var(--accent); +} + +.index-layout { + display: grid; + grid-template-columns: 300px minmax(420px, 1fr) 330px; + min-height: 590px; +} + +.console-panel, +.investigation-panel { + min-width: 0; + background: #0b1118; +} + +.console-panel + .console-panel { + border-left: 1px solid var(--line); +} + +.intake-panel, +.detail-panel-static, +.index-workspace, +.query-panel, +.matches-panel { + padding: 20px; +} + +.index-workspace { + background: #080d13; +} + +.section-title { + display: flex; + min-height: 28px; + gap: 9px; + align-items: center; + margin-bottom: 16px; +} + +.section-title svg { + color: var(--accent); +} + +.section-title h3 { + margin: 0; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.01em; +} + +.section-title > span { + margin-left: auto; + padding: 3px 7px; + border: 1px solid var(--line); + border-radius: 99px; + color: var(--muted); + font-family: var(--mono); + font-size: 8px; +} + +.source-grid { + display: grid; + gap: 8px; +} + +.source-grid > div { + display: grid; + grid-template-columns: 26px 1fr; + grid-template-rows: 18px 16px; + padding: 11px 12px; + border: 1px solid var(--line); + border-radius: 6px; + background: #0f171f; +} + +.source-grid svg { + grid-row: 1 / 3; + align-self: center; + color: var(--accent); +} + +.source-grid span { + font-size: 10px; + font-weight: 600; +} + +.source-grid small { + color: var(--faint); + font-family: var(--mono); + font-size: 8px; +} + +.profile-card { + display: grid; + gap: 5px; + margin-top: 15px; + padding: 15px; + border: 1px solid rgba(34, 199, 184, 0.22); + border-radius: 7px; + background: linear-gradient(145deg, rgba(34, 199, 184, 0.08), rgba(34, 199, 184, 0.01)); +} + +.profile-card span, +.subsection-label { + color: var(--accent); + font-family: var(--mono); + font-size: 8px; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.profile-card strong { + font-size: 13px; +} + +.profile-card small { + color: var(--muted); + font-size: 9px; + line-height: 1.5; +} + +.primary-action { + display: flex; + width: 100%; + min-height: 42px; + gap: 8px; + align-items: center; + justify-content: center; + margin-top: 16px; + border: 1px solid var(--accent); + border-radius: 6px; + color: #04100f; + background: var(--accent); + font-size: 10px; + font-weight: 700; + cursor: pointer; +} + +.primary-action:hover { + background: var(--accent-bright); +} + +.subject-picker { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; +} + +.subject-picker button { + overflow: hidden; + padding: 0 0 9px; + border: 1px solid var(--line); + border-radius: 6px; + color: var(--text); + background: #111922; + text-align: left; + cursor: pointer; +} + +.subject-picker button:hover, +.subject-picker button.selected { + border-color: rgba(34, 199, 184, 0.7); + background: #13211f; +} + +.subject-picker button.selected { + box-shadow: 0 0 0 1px rgba(34, 199, 184, 0.16); +} + +.subject-picker img { + width: 100%; + height: 116px; + object-fit: cover; + object-position: top center; + background: #05080b; +} + +.subject-picker span, +.subject-picker small { + display: block; + padding: 0 9px; +} + +.subject-picker span { + margin-top: 8px; + font-family: var(--mono); + font-size: 9px; +} + +.subject-picker small { + margin-top: 3px; + color: var(--faint); + font-size: 8px; +} + +.diagnostic-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; + margin-top: 18px; +} + +.diagnostic-grid > div { + display: grid; + gap: 8px; + padding: 12px; + border: 1px solid var(--line); + border-radius: 6px; + background: #0d141c; +} + +.diagnostic-grid span { + color: var(--faint); + font-family: var(--mono); + font-size: 8px; +} + +.diagnostic-grid strong { + font-family: var(--display); + font-size: 19px; +} + +.diagnostic-note { + margin: 12px 0 0; + color: #697b7e; + font-size: 9px; + line-height: 1.5; +} + +.selected-summary { + display: grid; + grid-template-columns: 74px 1fr; + gap: 12px; + align-items: center; + margin-bottom: 16px; + padding: 10px; + border: 1px solid var(--line); + border-radius: 7px; + background: #0f171f; +} + +.selected-summary img { + width: 74px; + height: 102px; + object-fit: cover; + object-position: top; + border-radius: 4px; +} + +.selected-summary div { + display: grid; + gap: 5px; +} + +.selected-summary strong { + font-family: var(--mono); + font-size: 12px; +} + +.selected-summary span { + color: var(--muted); + font-size: 9px; + line-height: 1.5; +} + +.track-result-list { + display: grid; + gap: 8px; +} + +.track-result-card { + position: relative; + display: grid; + grid-template-columns: 52px 1fr; + gap: 9px; + align-items: center; + min-height: 70px; + padding: 8px; + border: 1px solid var(--line); + border-radius: 6px; + color: var(--text); + background: #0e161e; + text-align: left; + cursor: pointer; +} + +.track-result-card:hover { + border-color: rgba(34, 199, 184, 0.55); +} + +.track-result-card img { + width: 52px; + height: 70px; + object-fit: cover; + object-position: top; + border-radius: 3px; +} + +.track-result-card div { + display: grid; + gap: 4px; +} + +.track-result-card strong { + font-size: 10px; +} + +.track-result-card div span { + color: var(--muted); + font-size: 8px; +} + +.score-badge { + position: absolute; + z-index: 2; + top: 6px; + right: 6px; + padding: 3px 5px; + border: 1px solid rgba(34, 199, 184, 0.3); + border-radius: 4px; + color: var(--accent-bright); + background: rgba(5, 14, 14, 0.88); + font-family: var(--mono); + font-size: 7px; +} + +.investigation-layout { + display: grid; + grid-template-columns: 260px minmax(570px, 1fr) 304px; + min-height: 720px; +} + +.query-panel { + border-right: 1px solid var(--line); +} + +.matches-panel { + border-left: 1px solid var(--line); +} + +.investigation-main { + min-width: 0; + background: #060a0e; +} + +.query-card { + overflow: hidden; + margin-bottom: 18px; + border: 1px solid var(--line); + border-radius: 7px; + background: #101820; +} + +.query-card > img { + width: 100%; + height: 210px; + object-fit: cover; + object-position: top center; + background: #040709; +} + +.query-meta { + display: grid; + gap: 4px; + padding: 11px 12px; +} + +.query-meta strong { + font-family: var(--mono); + font-size: 9px; +} + +.query-meta span { + color: var(--muted); + font-size: 8px; +} + +.query-panel .subject-picker { + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-top: 9px; +} + +.query-panel .subject-picker img { + height: 66px; +} + +.query-panel .subject-picker span { + padding: 0 5px; + font-size: 7px; +} + +.query-panel .subject-picker small { + display: none; +} + +.search-controls { + display: grid; + gap: 11px; + margin-top: 18px; + padding-top: 17px; + border-top: 1px solid var(--line); +} + +.field { + display: grid; + gap: 7px; +} + +.field > span { + color: var(--faint); + font-family: var(--mono); + font-size: 8px; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +select { + min-height: 35px; + padding: 0 30px 0 10px; + border: 1px solid var(--line-strong); + border-radius: 5px; + color: #bdc9ca; + background: #101820; + font-size: 9px; +} + +.camera-checks { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 5px; +} + +.camera-checks label { + position: relative; +} + +.camera-checks input { + position: absolute; + opacity: 0; +} + +.camera-checks span { + display: grid; + min-height: 30px; + place-items: center; + border: 1px solid var(--line); + border-radius: 4px; + color: var(--faint); + background: #0d141b; + font-family: var(--mono); + font-size: 8px; + cursor: pointer; +} + +.camera-checks input:checked + span { + border-color: rgba(34, 199, 184, 0.45); + color: var(--accent-bright); + background: var(--accent-dim); +} + +.compact-action { + margin-top: 0; +} + +.investigation-toolbar { + display: flex; + height: 58px; + gap: 20px; + align-items: center; + justify-content: space-between; + padding: 0 16px; + border-bottom: 1px solid var(--line); + background: #0b1118; +} + +.investigation-toolbar > div { + display: grid; + gap: 3px; +} + +.investigation-toolbar span { + color: var(--faint); + font-family: var(--mono); + font-size: 7px; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.investigation-toolbar strong { + font-size: 10px; +} + +.investigation-toolbar select { + min-width: 190px; +} + +.video-stage { + padding: 15px 15px 0; +} + +.video-frame { + overflow: hidden; + border: 1px solid #26353c; + border-radius: 5px; + background: #000; + box-shadow: 0 16px 40px rgba(0, 0, 0, 0.32); +} + +.video-canvas { + position: relative; + aspect-ratio: 16 / 9; + background: #000; +} + +.video-canvas video { + width: 100%; + height: 100%; + object-fit: contain; +} + +.source-overlay-note { + position: absolute; + top: 9px; + right: 9px; + display: flex; + gap: 7px; + align-items: center; + padding: 6px 8px; + border: 1px solid rgba(138, 179, 180, 0.23); + border-radius: 4px; + color: #c4d1d2; + background: rgba(2, 7, 9, 0.82); + font-family: var(--mono); + font-size: 7px; + text-transform: uppercase; + backdrop-filter: blur(5px); +} + +.source-overlay-note .status-dot { + width: 5px; + height: 5px; + flex-basis: 5px; +} + +.player-controls { + display: flex; + min-height: 48px; + gap: 7px; + align-items: center; + margin: 0 15px; + border-bottom: 1px solid var(--line); +} + +.player-controls button { + display: grid; + width: 31px; + height: 31px; + flex: 0 0 31px; + place-items: center; + border: 1px solid var(--line); + border-radius: 4px; + color: #b9c7c8; + background: #0d151c; + cursor: pointer; +} + +.player-controls button:hover { + border-color: rgba(34, 199, 184, 0.5); + color: var(--accent-bright); +} + +.player-controls > span { + min-width: 67px; + color: var(--muted); + font-family: var(--mono); + font-size: 8px; + text-align: center; +} + +.player-controls input[type="range"] { + width: 100%; + accent-color: var(--accent); +} + +.player-controls select { + min-width: 62px; + height: 31px; + min-height: 31px; +} + +.timeline-panel { + margin: 15px; + padding: 13px; + border: 1px solid var(--line); + border-radius: 6px; + background: #0a1016; +} + +.timeline-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.timeline-header > div { + display: grid; + gap: 3px; +} + +.timeline-header span { + color: var(--faint); + font-family: var(--mono); + font-size: 7px; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.timeline-header strong { + font-size: 9px; + font-weight: 600; +} + +.timeline-legend { + display: flex; + gap: 6px; + align-items: center; +} + +.timeline-legend > span { + width: 8px; + height: 8px; + border-radius: 2px; + background: var(--accent); +} + +.timeline-lanes { + display: grid; + gap: 7px; +} + +.timeline-lane { + display: grid; + grid-template-columns: 24px 1fr; + gap: 8px; + align-items: center; +} + +.lane-label { + color: var(--muted); + font-family: var(--mono); + font-size: 7px; +} + +.lane-track { + position: relative; + height: 15px; + border-radius: 3px; + background: + repeating-linear-gradient(90deg, transparent 0, transparent calc(10% - 1px), rgba(255, 255, 255, 0.04) 10%), + #101820; +} + +.track-segment { + position: absolute; + top: 3px; + height: 9px; + min-width: 3px; + padding: 0; + border: 0; + border-radius: 2px; + opacity: 0.76; + cursor: pointer; +} + +.track-segment:hover { + opacity: 1; + box-shadow: 0 0 10px currentColor; +} + +.playhead { + position: absolute; + z-index: 3; + top: -3px; + width: 1px; + height: 21px; + background: #fff; + box-shadow: 0 0 5px #fff; +} + +.candidate-groups h4 { + margin: 0 0 10px; + color: var(--faint); + font-family: var(--mono); + font-size: 8px; + font-weight: 500; + letter-spacing: 0.07em; + text-transform: uppercase; +} + +.candidate-list { + display: grid; + gap: 8px; +} + +.candidate-card { + position: relative; + display: grid; + grid-template-columns: 66px minmax(0, 1fr); + gap: 10px; + align-items: center; + min-height: 90px; + padding: 8px; + border: 1px solid var(--line); + border-radius: 6px; + color: var(--text); + background: #0e161e; + text-align: left; + cursor: pointer; +} + +.candidate-card:hover { + border-color: rgba(132, 160, 163, 0.45); + background: #111c24; +} + +.candidate-card.selected { + border-color: rgba(34, 199, 184, 0.72); + background: linear-gradient(100deg, rgba(34, 199, 184, 0.1), #101920); + box-shadow: inset 3px 0 var(--accent); +} + +.candidate-card.match-low { + border-color: rgba(244, 127, 139, 0.34); +} + +.candidate-card img { + width: 66px; + height: 88px; + object-fit: cover; + object-position: top center; + border-radius: 3px; + background: #05080b; +} + +.candidate-card > div { + display: grid; + min-width: 0; + gap: 4px; +} + +.candidate-card strong { + overflow: hidden; + font-size: 9px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.candidate-card div span { + color: var(--accent); + font-size: 8px; +} + +.candidate-card small { + color: var(--faint); + font-size: 7px; + line-height: 1.4; +} + +.candidate-disclaimer { + margin: 15px 0 0; + padding: 11px; + border-left: 2px solid #415358; + color: #778a8d; + background: rgba(255, 255, 255, 0.018); + font-size: 8px; + line-height: 1.55; +} + +/* Case-study sections */ +.content-section { + width: min(1460px, calc(100% - 72px)); + margin: 0 auto; + padding: 118px 0; + border-top: 1px solid var(--line); +} + +.section-heading { + display: grid; + grid-template-columns: minmax(0, 0.9fr) minmax(320px, 0.55fr); + column-gap: clamp(50px, 10vw, 180px); + align-items: end; + margin-bottom: 48px; +} + +.section-heading > span { + grid-column: 1 / 3; +} + +.section-heading h2 { + max-width: 860px; + margin: 0; + font-family: var(--display); + font-size: clamp(30px, 3.5vw, 52px); + line-height: 1.08; + letter-spacing: -0.04em; +} + +.section-heading p { + margin: 0; + color: var(--muted); + font-size: 14px; + line-height: 1.75; +} + +.architecture { + overflow: hidden; + border: 1px solid var(--line-strong); + border-radius: 10px; background: - radial-gradient(circle at 78% 8%, rgba(36, 230, 189, .12), transparent 32rem), - radial-gradient(circle at 15% 42%, rgba(232, 169, 77, .06), transparent 34rem), - #06100f; -} -a { color: inherit; text-decoration: none; } -button { font: inherit; } -code, pre { font-family: "DM Mono", ui-monospace, monospace; } - -.site-shell { min-height: 100vh; overflow: hidden; } -.nav { - position: sticky; top: 0; z-index: 50; display: flex; justify-content: space-between; align-items: center; - width: min(calc(100% - 36px), var(--max)); margin: 14px auto 0; padding: 12px 16px; - border: 1px solid var(--line); border-radius: 14px; background: rgba(6, 16, 15, .8); - backdrop-filter: blur(18px); box-shadow: 0 18px 50px rgba(0, 0, 0, .26); -} -.brand { display: inline-flex; align-items: center; gap: 10px; font-weight: 800; letter-spacing: -.02em; } -.brand em { color: var(--muted); font-size: 12px; font-style: normal; font-weight: 500; } -.brand__mark { - display: grid; place-items: center; width: 30px; aspect-ratio: 1; border-radius: 8px; - color: #03110e; background: var(--teal); box-shadow: 0 0 24px rgba(36, 230, 189, .34); -} -.nav__links { display: flex; align-items: center; gap: 24px; font-size: 13px; color: #b9ccc8; } -.nav__links a { display: inline-flex; align-items: center; gap: 7px; transition: color .2s ease; } -.nav__links a:hover { color: var(--teal); } - -main { width: min(calc(100% - 36px), var(--max)); margin: 0 auto; } -.hero { - position: relative; min-height: 700px; display: grid; grid-template-columns: 1.05fr .95fr; align-items: center; - gap: clamp(34px, 6vw, 90px); padding: 92px 0 64px; -} -.hero__grid { - position: absolute; inset: 0 -20vw; z-index: -1; opacity: .2; - background-image: linear-gradient(rgba(111, 180, 166, .18) 1px, transparent 1px), linear-gradient(90deg, rgba(111, 180, 166, .18) 1px, transparent 1px); - background-size: 54px 54px; mask-image: linear-gradient(to bottom, black, transparent 90%); -} -.award-line, .eyebrow, .label { text-transform: uppercase; letter-spacing: .16em; font-size: 11px; font-weight: 700; } -.award-line { - display: inline-flex; align-items: center; gap: 9px; color: #181007; - background: linear-gradient(120deg, #ffd48c, var(--bronze)); padding: 9px 12px; border-radius: 6px; - box-shadow: 0 10px 35px rgba(232, 169, 77, .18); -} -.eyebrow { color: var(--teal); margin: 22px 0 14px; } -h1, h2, h3, p { margin-top: 0; } -h1 { max-width: 790px; font-size: clamp(44px, 4.6vw, 68px); line-height: .98; letter-spacing: -.065em; margin: 0 0 26px; } -.hero__lede { max-width: 670px; color: #b6cac5; font-size: clamp(17px, 1.7vw, 21px); line-height: 1.6; } -.hero__actions { display: flex; flex-wrap: wrap; gap: 12px; margin: 32px 0; } -.button { - display: inline-flex; align-items: center; gap: 10px; padding: 13px 17px; border-radius: 8px; - border: 1px solid rgba(148, 198, 188, .28); font-weight: 700; font-size: 14px; background: rgba(10, 25, 23, .75); -} -.button--primary { color: #03110e; border-color: var(--teal); background: var(--teal); } -.hero__credit { display: flex; flex-wrap: wrap; gap: 11px 22px; color: var(--muted); font-size: 12px; } -.mode-label { color: var(--teal-soft); } -.hero__visual { - position: relative; aspect-ratio: 1; border: 1px solid var(--line); border-radius: 28px; overflow: hidden; - background: linear-gradient(145deg, rgba(20, 50, 44, .8), rgba(4, 14, 13, .95)); box-shadow: 0 42px 100px rgba(0, 0, 0, .48); -} -.hero__visual::before { - content: ""; position: absolute; inset: 0; - background: radial-gradient(circle at center, rgba(36, 230, 189, .12), transparent 36%), linear-gradient(90deg, transparent 49.8%, var(--line) 50%, transparent 50.2%), linear-gradient(transparent 49.8%, var(--line) 50%, transparent 50.2%); -} -.hero-camera { - position: absolute; width: 39%; height: 31%; padding: 10px; border-radius: 13px; overflow: hidden; - background-image: linear-gradient(rgba(3, 14, 13, .16), rgba(3, 14, 13, .58)), url("/media/pdestre/contest-four-camera.jpg"); - background-size: 200% 200%; border: 1px solid rgba(158, 211, 200, .18); -} -.hero-camera span { display: inline-flex; gap: 5px; align-items: center; padding: 4px 7px; border-radius: 5px; font: 10px "DM Mono"; background: rgba(3, 13, 12, .76); } -.hero-camera--1 { left: 8%; top: 8%; background-position: left top; } -.hero-camera--2 { right: 8%; top: 8%; background-position: right top; } -.hero-camera--3 { left: 8%; bottom: 8%; background-position: left bottom; } -.hero-camera--4 { right: 8%; bottom: 8%; background-position: right bottom; } -.person-box { position: absolute; left: 51%; top: 32%; width: 17%; height: 48%; border: 2px solid var(--teal); box-shadow: 0 0 15px rgba(36, 230, 189, .35); } -.hero__core { - position: absolute; z-index: 3; left: 50%; top: 50%; transform: translate(-50%, -50%); - display: grid; place-items: center; width: 144px; aspect-ratio: 1; border-radius: 50%; color: var(--teal); - border: 1px solid rgba(36, 230, 189, .4); background: #081a17; - box-shadow: 0 0 0 12px rgba(36, 230, 189, .035), 0 0 70px rgba(36, 230, 189, .18); -} -.hero__core strong { font-size: 12px; margin-top: 8px; } -.hero__core small { color: var(--muted); font-size: 9px; } -.signal { position: absolute; inset: 31%; border: 1px solid rgba(36, 230, 189, .2); border-radius: 50%; animation: pulse 3.4s infinite; } -.signal--b { animation-delay: 1.7s; } - -.metric-strip { display: grid; grid-template-columns: repeat(4, 1fr); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); } -.metric { padding: 27px 25px; border-right: 1px solid var(--line); } -.metric:last-child { border-right: 0; } -.metric strong { display: block; color: var(--teal); font: 500 clamp(28px, 3vw, 42px) "DM Mono"; letter-spacing: -.06em; } -.metric span, .metric small { display: block; } -.metric span { margin: 7px 0 4px; font-size: 13px; font-weight: 700; } -.metric small { color: var(--muted); font-size: 10px; } - -.section { padding: 112px 0; border-bottom: 1px solid var(--line); } -.section__intro { display: grid; grid-template-columns: 70px minmax(0, 780px); gap: 18px; margin-bottom: 54px; } -.section-index { color: #5a746f; font: 12px "DM Mono"; padding-top: 30px; } -.section__intro h2 { font-size: clamp(34px, 4.5vw, 56px); line-height: 1.06; letter-spacing: -.05em; margin-bottom: 0; } -.display-copy { font-size: clamp(23px, 3vw, 38px); line-height: 1.25; letter-spacing: -.035em; } -.problem__grid { display: grid; grid-template-columns: 1fr .95fr; gap: 9vw; align-items: center; } -.comparison { display: grid; grid-template-columns: 1fr 24px; gap: 16px; } -.comparison article { grid-column: 1; padding: 22px; border: 1px solid var(--line); border-radius: 11px; background: var(--panel); } -.comparison svg { grid-column: 2; grid-row: 1/3; align-self: center; color: var(--teal); } -.comparison span, .comparison small { display: block; color: var(--muted); font-size: 11px; } -.comparison strong { display: block; margin: 10px 0; font: 13px "DM Mono"; } -.comparison__active { border-color: rgba(36, 230, 189, .5) !important; } - -.architecture { padding: 24px; border: 1px solid var(--line); border-radius: 18px; background: linear-gradient(145deg, rgba(11, 35, 31, .9), rgba(6, 17, 16, .85)); } -.architecture__rail { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; } -.architecture__step-wrap { display: flex; align-items: center; min-width: 0; } -.architecture__step { flex: 1; min-height: 172px; display: flex; flex-direction: column; padding: 18px; border-radius: 12px; background: rgba(4, 15, 14, .72); border: 1px solid var(--line); } -.architecture__step > span { color: var(--teal); font: 9px "DM Mono"; text-transform: uppercase; } -.architecture__step svg { color: var(--teal); margin: auto 0 13px; } -.architecture__step strong { font-size: 18px; } -.architecture__step small { color: var(--muted); margin-top: 7px; } -.architecture__arrow { flex: 0 0 20px; color: var(--teal); margin: 0 -5px; z-index: 2; } -.architecture__branch { display: grid; grid-template-columns: 1fr 1fr 1.25fr; gap: 12px; margin-top: 12px; } -.architecture__branch > div { padding: 17px; border-radius: 11px; border: 1px solid var(--line); background: rgba(9, 25, 23, .75); } -.architecture__branch span, .architecture__branch small { display: block; color: var(--muted); font-size: 10px; } -.architecture__branch strong { display: block; margin: 7px 0; font-size: 13px; } -.architecture__branch code { color: var(--teal-soft); font-size: 10px; } -.architecture__output { border-color: rgba(232, 169, 77, .35) !important; } -.architecture-source { margin: 18px 0 0; padding: 10px; border: 1px solid var(--line); border-radius: 14px; background: #06100f; } -.architecture-source img { display: block; width: 100%; height: auto; border-radius: 8px; } -.architecture-source figcaption { padding: 9px 5px 2px; color: var(--muted); font-size: 9px; text-align: right; text-transform: uppercase; letter-spacing: .1em; } -.formula-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin: 18px 0; } -.formula-row div { padding: 15px; border-left: 2px solid var(--teal); background: rgba(10, 28, 25, .6); } -.formula-row span, .formula-row code { display: block; } -.formula-row span { color: var(--muted); font-size: 10px; text-transform: uppercase; letter-spacing: .1em; } -.formula-row code { margin-top: 8px; font-size: 12px; color: var(--teal-soft); } -.text-link { display: inline-flex; align-items: center; gap: 8px; color: var(--teal); font-size: 13px; font-weight: 700; } - -.investigation { overflow: hidden; border: 1px solid var(--line); border-radius: 18px; background: #081714; box-shadow: 0 40px 90px rgba(0, 0, 0, .35); } -.investigation__top { display: flex; justify-content: space-between; align-items: center; padding: 19px 22px; border-bottom: 1px solid var(--line); } -.investigation__top .label { color: var(--teal); } -.investigation__top h3 { margin: 6px 0 0; font-size: 18px; } -.demo-pill { display: inline-flex; gap: 7px; align-items: center; color: #d7a958; padding: 7px 10px; border: 1px solid rgba(232, 169, 77, .28); border-radius: 999px; font-size: 10px; } -.investigation__workspace { display: grid; grid-template-columns: 1.55fr .75fr; } -.camera-column { min-width: 0; padding: 18px; border-right: 1px solid var(--line); } -.camera-tabs { display: flex; gap: 8px; margin-bottom: 12px; } -.camera-tabs button { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); padding: 8px 13px; border: 1px solid var(--line); border-radius: 7px; background: transparent; cursor: pointer; } -.camera-tabs button.active { color: #04110f; border-color: var(--teal); background: var(--teal); } -.camera-view { - position: relative; aspect-ratio: 16/8.9; overflow: hidden; border-radius: 10px; - background-image: linear-gradient(rgba(2, 13, 12, .12), rgba(2, 13, 12, .32)), url("/media/pdestre/contest-four-camera.jpg"); - background-size: 200% 200%; -} -.camera-view--top-left { background-position: left top; } -.camera-view--top-right { background-position: right top; } -.camera-view--bottom-left { background-position: left bottom; } -.camera-view--bottom-right { background-position: right bottom; } -.camera-view__hud { position: absolute; z-index: 2; left: 0; right: 0; top: 0; display: flex; justify-content: space-between; padding: 11px; color: #dffbf5; font: 10px "DM Mono"; text-shadow: 0 2px 5px black; } -.track-box { position: absolute; z-index: 4; padding: 0; border: 2px solid; background: transparent; cursor: crosshair; box-shadow: 0 0 0 1px rgba(0, 0, 0, .5); } -.track-box span { position: absolute; left: -2px; top: -20px; padding: 2px 5px; color: #04110f; background: currentColor; font: 9px "DM Mono"; } -.track-box.selected { box-shadow: 0 0 22px currentColor; } -.scan-line { position: absolute; z-index: 3; left: 0; right: 0; height: 1px; background: rgba(36, 230, 189, .7); box-shadow: 0 0 10px var(--teal); animation: scan 5s linear infinite; } -.timeline { display: grid; grid-template-columns: 42px 1fr 42px; align-items: center; gap: 9px; margin-top: 13px; color: var(--muted); font: 9px "DM Mono"; } -.timeline div { position: relative; height: 3px; background: #1a3933; } -.timeline i { position: absolute; top: -4px; width: 3px; height: 11px; background: var(--teal); } -.candidate-panel { padding: 19px; background: rgba(4, 14, 13, .65); } -.candidate-panel__query { display: flex; align-items: center; gap: 12px; padding-bottom: 16px; border-bottom: 1px solid var(--line); } -.candidate-panel__query svg { color: var(--teal); } -.candidate-panel__query span, .candidate-panel__query small { display: block; color: var(--muted); font-size: 10px; } -.candidate-panel__query strong { display: block; margin: 3px 0; font-size: 19px; } -.candidate-panel__heading { display: flex; justify-content: space-between; margin: 18px 0 10px; color: var(--muted); font-size: 10px; text-transform: uppercase; letter-spacing: .08em; } -.candidate-list { display: grid; gap: 8px; } -.candidate { display: grid; grid-template-columns: 58px 1fr 18px; align-items: center; gap: 10px; width: 100%; padding: 8px; color: var(--ink); text-align: left; border: 1px solid var(--line); border-radius: 8px; background: rgba(12, 31, 28, .7); cursor: pointer; } -.candidate:hover { border-color: rgba(36, 230, 189, .45); } -.candidate__thumb { display: block; height: 48px; border-radius: 5px; background-image: url("/media/pdestre/contest-four-camera.jpg"); background-size: 200% 200%; } -.candidate__copy span, .candidate__copy small { display: block; color: var(--muted); font-size: 9px; } -.candidate__copy strong { display: block; margin: 4px 0; font-size: 12px; } -.candidate--strong > svg { color: var(--teal); } -.candidate--possible > svg { color: var(--bronze); } -.candidate--low > svg { color: var(--danger); } -.candidate-panel__note { color: var(--muted); font-size: 10px; line-height: 1.5; margin: 16px 0 0; } -.license-note { color: var(--muted); font-size: 10px; margin-top: 13px; } -.live-console { overflow: hidden; border: 1px solid var(--line); border-radius: 18px; background: #081714; box-shadow: 0 40px 90px rgba(0, 0, 0, .35); } -.live-console__header { display: flex; justify-content: space-between; align-items: center; padding: 20px 22px; border-bottom: 1px solid var(--line); } -.live-console__header .label { color: var(--teal); } -.live-console__header h3 { margin: 6px 0 0; font-size: 18px; } -.live-console__state { display: flex; align-items: center; gap: 7px; color: var(--muted); font-size: 10px; } -.live-console__state span { width: 7px; height: 7px; border-radius: 50%; background: var(--teal); box-shadow: 0 0 12px var(--teal); } -.live-console__setup { display: grid; grid-template-columns: 1.2fr 1fr; gap: 18px; padding: 20px; border-bottom: 1px solid var(--line); } -.upload-zone { min-height: 160px; display: grid; place-items: center; align-content: center; gap: 8px; border: 1px dashed rgba(36, 230, 189, .45); border-radius: 12px; color: var(--muted); cursor: pointer; background: rgba(36, 230, 189, .035); } -.upload-zone svg { color: var(--teal); } -.upload-zone strong { color: var(--ink); } -.upload-zone span { font-size: 10px; } -.upload-zone input { position: absolute; width: 1px; height: 1px; opacity: 0; } -.live-console__controls { display: flex; flex-direction: column; justify-content: center; gap: 10px; } -.live-console__controls label { color: var(--muted); font-size: 10px; text-transform: uppercase; letter-spacing: .08em; } -.live-console__controls select { width: 100%; margin-top: 7px; padding: 11px; color: var(--ink); border: 1px solid var(--line); border-radius: 7px; background: #071310; } -.live-console__controls p { color: var(--muted); font-size: 10px; line-height: 1.5; } -.live-console__controls button { align-self: flex-start; cursor: pointer; } -.live-console__controls button:disabled { opacity: .38; cursor: not-allowed; } -.live-console__error { margin: 0; padding: 12px 20px; color: #ffd0d5; background: rgba(255, 106, 118, .12); border-bottom: 1px solid rgba(255, 106, 118, .25); font-size: 11px; } -.job-strip { display: grid; grid-template-columns: 190px 1fr 130px 36px; gap: 14px; align-items: center; padding: 14px 20px; border-bottom: 1px solid var(--line); } -.job-strip div:first-child span, .job-strip div:first-child strong { display: block; font-size: 10px; } -.job-strip div:first-child span, .job-strip > span { color: var(--muted); } -.progress { height: 5px; overflow: hidden; border-radius: 9px; background: #163730; } -.progress i { display: block; height: 100%; background: var(--teal); box-shadow: 0 0 12px var(--teal); } -.job-strip button { display: grid; place-items: center; width: 32px; height: 32px; color: var(--danger); border: 1px solid var(--line); border-radius: 7px; background: transparent; cursor: pointer; } -.job-strip button svg { width: 15px; } -.review-grid { display: grid; grid-template-columns: 1.4fr .8fr; min-height: 320px; } -.review-grid > div, .review-grid > aside { padding: 18px; } -.review-grid > aside { border-left: 1px solid var(--line); background: rgba(4, 14, 13, .65); } -.review-grid__title { display: flex; align-items: center; gap: 8px; margin-bottom: 13px; font-size: 11px; font-weight: 700; } -.review-grid__title svg { width: 15px; color: var(--teal); } -.review-grid__title span { margin-left: auto; color: var(--muted); } -.people-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; } -.people-grid button { overflow: hidden; padding: 0 0 8px; color: var(--ink); text-align: left; border: 1px solid var(--line); border-radius: 8px; background: rgba(11, 35, 31, .85); cursor: pointer; } -.people-grid button.selected { border-color: var(--teal); box-shadow: 0 0 18px rgba(36, 230, 189, .16); } -.people-grid img { width: 100%; height: 88px; object-fit: cover; object-position: top; background: #03100e; } -.people-grid button > svg { margin: 30px auto; display: block; color: var(--muted); } -.people-grid strong, .people-grid span { display: block; padding: 0 8px; } -.people-grid strong { margin-top: 7px; font-size: 11px; } -.people-grid span { margin-top: 3px; color: var(--muted); font-size: 8px; } -.review-grid aside article { display: grid; grid-template-columns: 45px 1fr; gap: 10px; align-items: center; padding: 8px 0; border-top: 1px solid var(--line); } -.review-grid aside article img { width: 45px; height: 52px; object-fit: cover; object-position: top; border-radius: 5px; } -.review-grid aside article strong, .review-grid aside article span { display: block; } -.review-grid aside article span, .review-grid aside p { color: var(--muted); font-size: 9px; } -.spin { animation: rotate 1s linear infinite; } -@keyframes rotate { to { transform: rotate(360deg); } } - -.evidence__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; } -.evidence-card { min-height: 340px; display: flex; flex-direction: column; padding: 25px; border: 1px solid var(--line); border-radius: 15px; background: var(--panel); } -.evidence-card--primary { background: linear-gradient(145deg, rgba(25, 66, 57, .9), rgba(7, 24, 21, .85)); border-color: rgba(36, 230, 189, .36); } -.tag { align-self: flex-start; color: #042019; padding: 5px 8px; border-radius: 4px; background: var(--teal); text-transform: uppercase; font-size: 9px; font-weight: 800; letter-spacing: .08em; } -.tag--bronze { background: var(--bronze); } -.tag--muted { color: var(--ink); background: #38504b; } -.evidence-card h3 { margin: 21px 0 7px; font-size: 20px; } -.evidence-number { color: var(--teal); font: 500 48px "DM Mono"; letter-spacing: -.08em; } -.evidence-card p { color: #aebfbb; line-height: 1.6; font-size: 13px; } -.evidence-card dl { margin-top: auto; } -.evidence-card dl div { display: flex; justify-content: space-between; gap: 10px; padding: 8px 0; border-top: 1px solid var(--line); font-size: 11px; } -.evidence-card dt { color: var(--muted); } -.evidence-card dd { margin: 0; font-family: "DM Mono"; } -.evidence-card a { display: inline-flex; align-items: center; gap: 7px; margin-top: auto; color: var(--teal); font-size: 12px; font-weight: 700; } -.reconciliation { display: grid; grid-template-columns: 25px 1fr; margin-top: auto; padding-top: 16px; border-top: 1px solid var(--line); } -.reconciliation svg { color: var(--bronze); grid-row: 1/3; } -.reconciliation span { color: var(--muted); font-size: 9px; text-transform: uppercase; } -.reconciliation strong { margin-top: 4px; font-size: 12px; } -.protocol-table { margin-top: 18px; border: 1px solid var(--line); border-radius: 12px; overflow: hidden; } -.protocol-table > div { display: grid; grid-template-columns: 1.25fr .55fr 1fr .45fr; gap: 12px; padding: 14px 17px; border-top: 1px solid var(--line); font-size: 12px; } -.protocol-table__head { color: var(--muted); border-top: 0 !important; background: rgba(20, 47, 42, .55); font-size: 9px !important; text-transform: uppercase; letter-spacing: .1em; } -.protocol-table small { color: var(--teal); } - -.evolution__rail { position: relative; display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px; } -.evolution__rail::before { content: ""; position: absolute; left: 10%; right: 10%; top: 66px; height: 1px; background: linear-gradient(90deg, var(--bronze), var(--teal)); } -.evolution__rail article { position: relative; z-index: 2; min-height: 300px; padding: 22px; border-radius: 14px; background: #091a17; border: 1px solid var(--line); } -.evolution__rail article > span { color: var(--muted); font: 9px "DM Mono"; text-transform: uppercase; } -.evolution__rail svg { display: block; color: var(--teal); margin: 26px 0 30px; } -.evolution__rail article:first-child svg { color: var(--bronze); } -.evolution__rail h3 { font-size: 25px; margin-bottom: 10px; } -.evolution__rail p { color: var(--muted); font-size: 12px; line-height: 1.6; } -.evolution__rail strong { display: block; color: var(--teal); margin-top: 20px; font-size: 12px; } -.evolution__rail a { display: inline-flex; align-items: center; gap: 5px; margin-top: 12px; color: var(--teal); font-size: 11px; } -.protocol-warning { display: flex; align-items: center; gap: 10px; color: #d7b985; margin: 20px 0 0; font-size: 11px; } - -.engineering__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; } -.engineering__grid article { min-height: 160px; padding: 20px; border: 1px solid var(--line); border-radius: 11px; background: rgba(9, 26, 23, .65); } -.engineering__grid svg { color: var(--teal); } -.engineering__grid span { display: block; margin: 28px 0 8px; font-weight: 800; } -.engineering__grid p { color: var(--muted); font-size: 11px; line-height: 1.55; } -.local-run { display: grid; grid-template-columns: 1fr 1fr; gap: 8vw; align-items: center; } -.local-run h2 { font-size: clamp(32px, 4vw, 48px); letter-spacing: -.05em; } -.local-run p { color: var(--muted); line-height: 1.6; } -.local-run pre { overflow-x: auto; padding: 25px; border: 1px solid var(--line); border-radius: 13px; color: var(--teal-soft); background: #030b0a; font-size: 12px; line-height: 1.7; } -footer { width: min(calc(100% - 36px), var(--max)); margin: 0 auto; padding: 44px 0 64px; display: grid; grid-template-columns: .5fr 1.4fr .7fr; gap: 35px; align-items: start; color: var(--muted); font-size: 10px; } -footer p { line-height: 1.7; } -footer > div:last-child { display: flex; flex-direction: column; gap: 8px; } -footer a:hover { color: var(--teal); } - -@keyframes pulse { 0%, 100% { transform: scale(.8); opacity: 0; } 50% { transform: scale(1.3); opacity: .8; } } -@keyframes scan { from { top: 10%; } to { top: 92%; } } - -@media (max-width: 900px) { - .nav__links a:not(:last-child) { display: none; } - .hero { grid-template-columns: 1fr; padding-top: 80px; } - .hero__visual { width: min(100%, 620px); justify-self: center; } - .metric-strip { grid-template-columns: repeat(2, 1fr); } - .metric:nth-child(2) { border-right: 0; } - .metric:nth-child(-n+2) { border-bottom: 1px solid var(--line); } - .problem__grid, .investigation__workspace, .local-run { grid-template-columns: 1fr; } - .live-console__setup, .review-grid { grid-template-columns: 1fr; } - .review-grid > aside { border-left: 0; border-top: 1px solid var(--line); } - .people-grid { grid-template-columns: repeat(3, 1fr); } - .camera-column { border-right: 0; border-bottom: 1px solid var(--line); } - .architecture__rail { grid-template-columns: repeat(2, 1fr); } - .architecture__branch, .evidence__grid, .evolution__rail, .engineering__grid { grid-template-columns: 1fr 1fr; } - .evolution__rail::before { display: none; } - footer { grid-template-columns: 1fr 2fr; } -} - -@media (max-width: 620px) { - main, .nav, footer { width: min(calc(100% - 24px), var(--max)); } - h1 { font-size: 41px; } - .hero { min-height: auto; padding: 66px 0 44px; } - .hero__visual { border-radius: 18px; } - .hero-camera { width: 42%; height: 33%; } - .hero__core { width: 104px; } - .hero__core small { display: none; } - .metric { padding: 20px 14px; } - .section { padding: 76px 0; } - .section__intro { grid-template-columns: 38px 1fr; margin-bottom: 36px; } - .section-index { padding-top: 24px; } - .section__intro h2 { font-size: 36px; } - .architecture { padding: 12px; } - .architecture__rail, .architecture__branch, .formula-row, .evidence__grid, .evolution__rail, .engineering__grid { grid-template-columns: 1fr; } - .architecture__step { min-height: 136px; } - .architecture__arrow { transform: rotate(90deg); } - .investigation__top { align-items: flex-start; gap: 15px; } - .demo-pill { display: none; } - .camera-tabs { overflow-x: auto; } - .job-strip { grid-template-columns: 1fr 42px; } - .job-strip .progress, .job-strip > span { grid-column: 1; } - .people-grid { grid-template-columns: repeat(2, 1fr); } - .camera-view { aspect-ratio: 4/3; } - .protocol-table > div { grid-template-columns: 1.2fr .7fr; } - .protocol-table > div > :nth-child(3), .protocol-table > div > :nth-child(4) { display: none; } - footer { grid-template-columns: 1fr; } + linear-gradient(rgba(255, 255, 255, 0.018) 1px, transparent 1px), + linear-gradient(90deg, rgba(255, 255, 255, 0.018) 1px, transparent 1px), + #0a1016; + background-size: 30px 30px; +} + +.architecture__rail { + display: grid; + grid-template-columns: repeat(4, 1fr); + min-height: 210px; + padding: 28px; +} + +.architecture__step-wrap { + position: relative; + display: flex; + min-width: 0; + align-items: center; +} + +.architecture__step { + display: grid; + width: calc(100% - 28px); + min-height: 155px; + align-content: start; + gap: 8px; + padding: 19px; + border: 1px solid var(--line); + border-radius: 7px; + background: rgba(10, 17, 23, 0.91); +} + +.architecture__step span, +.architecture__branch span { + color: var(--accent); + font-family: var(--mono); + font-size: 8px; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.architecture__step svg { + width: 24px; + height: 24px; + margin: 7px 0 2px; + color: var(--accent-bright); +} + +.architecture__step strong { + font-family: var(--display); + font-size: 15px; +} + +.architecture__step small { + color: var(--muted); + font-size: 9px; + line-height: 1.45; +} + +.architecture__arrow { + position: absolute; + right: 1px; + width: 19px; + color: var(--accent); +} + +.architecture__branch { + display: grid; + grid-template-columns: 1fr 1fr 1.15fr; + border-top: 1px solid var(--line); +} + +.architecture__branch > div { + display: grid; + min-height: 128px; + align-content: center; + gap: 8px; + padding: 20px 24px; +} + +.architecture__branch > div + div { + border-left: 1px solid var(--line); +} + +.architecture__branch strong { + font-size: 12px; +} + +.architecture__branch code { + color: #a9bbbd; + font-family: var(--mono); + font-size: 9px; +} + +.architecture__branch small { + color: var(--muted); + font-size: 9px; +} + +.architecture__output { + background: rgba(34, 199, 184, 0.06); +} + +.architecture-source { + margin: 24px 0 0; + overflow: hidden; + border: 1px solid var(--line); + border-radius: 10px; + background: #070b10; +} + +.architecture-source img { + width: 100%; +} + +.architecture-source figcaption { + padding: 12px 16px; + border-top: 1px solid var(--line); + color: var(--faint); + font-family: var(--mono); + font-size: 8px; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.benchmark-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12px; +} + +.benchmark-card { + position: relative; + display: grid; + min-height: 182px; + align-content: end; + gap: 8px; + overflow: hidden; + padding: 22px; + border: 1px solid var(--line); + border-radius: 8px; + background: linear-gradient(150deg, #111921, #0b1016); +} + +.benchmark-card::before { + position: absolute; + top: 0; + left: 0; + width: 70%; + height: 2px; + background: linear-gradient(90deg, var(--accent), transparent); + content: ""; +} + +.benchmark-card strong { + color: #f4faf9; + font-family: var(--display); + font-size: clamp(30px, 3.5vw, 48px); + letter-spacing: -0.04em; +} + +.benchmark-card span { + font-size: 12px; + font-weight: 700; +} + +.benchmark-card small { + color: var(--faint); + font-family: var(--mono); + font-size: 8px; +} + +.protocol-grid, +.engineering-grid { + display: grid; + gap: 1px; + margin-top: 1px; + border: 1px solid var(--line); + background: var(--line); +} + +.protocol-grid { + grid-template-columns: repeat(4, 1fr); +} + +.protocol-grid article, +.engineering-grid article { + display: grid; + min-height: 140px; + align-content: center; + gap: 9px; + padding: 22px; + background: #0b1118; +} + +.protocol-grid svg, +.engineering-grid svg { + color: var(--accent); +} + +.protocol-grid span { + color: var(--faint); + font-family: var(--mono); + font-size: 8px; + text-transform: uppercase; +} + +.protocol-grid strong, +.engineering-grid strong { + font-size: 12px; +} + +.protocol-grid small, +.engineering-grid p { + color: var(--muted); + font-size: 9px; + line-height: 1.55; +} + +.engineering-grid { + grid-template-columns: repeat(3, 1fr); + margin-top: 0; +} + +.engineering-grid article { + min-height: 190px; +} + +.engineering-grid svg { + width: 24px; + height: 24px; + margin-bottom: 12px; +} + +.engineering-grid strong { + font-family: var(--display); + font-size: 16px; +} + +.engineering-grid p { + max-width: 320px; + margin: 0; + font-size: 11px; +} + +.local-run { + display: grid; + grid-template-columns: minmax(0, 0.9fr) minmax(420px, 0.7fr); + gap: clamp(50px, 10vw, 160px); + align-items: center; +} + +.local-run h2 { + max-width: 760px; + margin: 0; + font-family: var(--display); + font-size: clamp(30px, 3.6vw, 52px); + line-height: 1.1; + letter-spacing: -0.04em; +} + +.local-run p { + max-width: 680px; + margin: 20px 0 0; + color: var(--muted); + font-size: 13px; + line-height: 1.7; +} + +.local-run p code { + color: var(--accent-bright); + font-family: var(--mono); + font-size: 11px; +} + +.local-run pre { + overflow: auto; + margin: 0; + padding: 27px; + border: 1px solid var(--line-strong); + border-radius: 8px; + background: #05080c; + box-shadow: inset 3px 0 var(--accent); +} + +.local-run pre code { + color: #bcd0d0; + font-family: var(--mono); + font-size: 11px; + line-height: 1.9; +} + +footer { + display: grid; + grid-template-columns: 0.7fr 1.3fr auto; + gap: 50px; + align-items: center; + padding: 42px max(36px, calc((100vw - 1460px) / 2)); + border-top: 1px solid var(--line); + background: #05080c; +} + +footer > div { + display: grid; + gap: 5px; +} + +footer strong { + font-family: var(--display); + font-size: 14px; +} + +footer span, +footer p, +footer a { + color: var(--faint); + font-size: 9px; + line-height: 1.55; +} + +footer p { + max-width: 580px; + margin: 0; +} + +footer > div:last-child { + display: flex; + flex-wrap: wrap; + gap: 14px; + justify-content: flex-end; +} + +footer a:hover { + color: var(--accent-bright); +} + +@media (max-width: 1320px) { + .project-hero { + grid-template-columns: minmax(0, 0.8fr) minmax(480px, 1.2fr); + gap: 46px; + width: min(1280px, calc(100% - 48px)); + } + + .topbar { + align-items: flex-start; + } + + .top-metrics { + grid-template-columns: repeat(2, 125px); + } + + .index-layout { + grid-template-columns: 250px minmax(360px, 1fr) 280px; + } + + .investigation-layout { + grid-template-columns: 225px minmax(520px, 1fr) 270px; + } + + .query-card > img { + height: 180px; + } + + .candidate-card { + grid-template-columns: 58px minmax(0, 1fr); + } + + .candidate-card img { + width: 58px; + height: 78px; + } +} + +@media (max-width: 1060px) { + .site-nav > div a:not(:last-child) { + display: none; + } + + .project-hero { + grid-template-columns: 1fr; + min-height: 0; + padding: 86px 0; + } + + .project-hero > div:first-child { + max-width: 820px; + } + + .hero-frame { + max-width: 880px; + } + + .topbar { + display: grid; + } + + .top-metrics { + grid-template-columns: repeat(4, 1fr); + } + + .case-summary:nth-of-type(2) { + display: none; + } + + .index-layout { + grid-template-columns: 235px 1fr; + } + + .detail-panel-static { + grid-column: 1 / 3; + border-top: 1px solid var(--line); + border-left: 0 !important; + } + + .track-result-list { + grid-template-columns: repeat(3, 1fr); + } + + .investigation-layout { + grid-template-columns: 225px minmax(0, 1fr); + } + + .matches-panel { + grid-column: 1 / 3; + border-top: 1px solid var(--line); + border-left: 0; + } + + .candidate-list { + grid-template-columns: repeat(4, 1fr); + } + + .candidate-card { + grid-template-columns: 1fr; + } + + .candidate-card img { + width: 100%; + height: 160px; + } + + .section-heading { + grid-template-columns: 1fr; + gap: 22px; + } + + .section-heading > span { + grid-column: 1; + } + + .architecture__rail { + grid-template-columns: repeat(2, 1fr); + gap: 14px; + } + + .architecture__step { + width: 100%; + } + + .architecture__arrow { + display: none; + } + + .benchmark-grid, + .protocol-grid { + grid-template-columns: repeat(2, 1fr); + } + + .engineering-grid { + grid-template-columns: repeat(2, 1fr); + } + + .local-run { + grid-template-columns: 1fr; + } + + footer { + grid-template-columns: 1fr 1fr; + } + + footer > div:last-child { + grid-column: 1 / 3; + justify-content: flex-start; + } +} + +@media (max-width: 720px) { + .site-nav { + padding: 0 18px; + } + + .project-hero, + .content-section { + width: calc(100% - 32px); + } + + .project-hero { + gap: 38px; + padding: 58px 0 68px; + } + + .project-hero h1 { + font-size: clamp(38px, 12vw, 54px); + } + + .hero-frame__hud { + position: static; + display: grid; + } + + .hero-frame__hud strong { + display: none; + } + + .console-shell { + width: calc(100% - 16px); + margin-bottom: 78px; + overflow: visible; + } + + .topbar { + padding: 20px 16px; + } + + .top-metrics { + grid-template-columns: repeat(2, 1fr); + } + + .metric-card { + min-width: 0; + } + + .case-strip { + overflow-x: auto; + } + + .case-summary { + min-width: 190px; + } + + .workflow-step { + min-width: 145px; + min-height: 52px; + } + + .workspace-tabs button { + width: 50%; + min-width: 0; + } + + .index-layout, + .investigation-layout { + display: block; + } + + .console-panel, + .investigation-panel, + .investigation-main, + .detail-panel-static, + .matches-panel { + border: 0 !important; + border-bottom: 1px solid var(--line) !important; + } + + .query-panel .subject-picker { + grid-template-columns: repeat(3, 1fr); + } + + .query-card { + display: grid; + grid-template-columns: 112px 1fr; + } + + .query-card > img { + height: 150px; + } + + .investigation-toolbar { + height: auto; + padding: 12px; + } + + .investigation-toolbar select { + min-width: 155px; + max-width: 50%; + } + + .video-stage { + padding: 8px 8px 0; + } + + .source-overlay-note { + top: 5px; + right: 5px; + padding: 4px 5px; + font-size: 5px; + } + + .player-controls { + margin: 0 8px; + } + + .player-controls > span, + .player-controls select { + display: none; + } + + .timeline-panel { + margin: 10px 8px 14px; + } + + .timeline-legend { + display: none; + } + + .candidate-list { + grid-template-columns: repeat(2, 1fr); + } + + .candidate-card img { + height: 180px; + } + + .track-result-list { + grid-template-columns: 1fr; + } + + .diagnostic-grid { + grid-template-columns: repeat(2, 1fr); + } + + .content-section { + padding: 82px 0; + } + + .section-heading { + margin-bottom: 34px; + } + + .architecture__rail, + .architecture__branch, + .benchmark-grid, + .protocol-grid, + .engineering-grid { + grid-template-columns: 1fr; + } + + .architecture__branch > div + div { + border-top: 1px solid var(--line); + border-left: 0; + } + + .benchmark-card { + min-height: 150px; + } + + .local-run pre { + padding: 20px; + } + + footer { + grid-template-columns: 1fr; + gap: 20px; + padding: 38px 20px; + } + + footer > div:last-child { + grid-column: 1; + } } @media (prefers-reduced-motion: reduce) { - *, *::before, *::after { - scroll-behavior: auto !important; - animation-duration: .01ms !important; + html { + scroll-behavior: auto; + } + + *, + *::before, + *::after { + animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; - transition-duration: .01ms !important; + scroll-behavior: auto !important; + transition-duration: 0.001ms !important; } } diff --git a/apps/web/src/types.ts b/apps/web/src/types.ts index 103ca0d..861850e 100644 --- a/apps/web/src/types.ts +++ b/apps/web/src/types.ts @@ -1,35 +1,38 @@ -export type Camera = { +export type DemoVideo = { id: string; label: string; - position: "top-left" | "top-right" | "bottom-left" | "bottom-right"; - time: string; + description: string; + source: string; + poster: string; }; -export type Appearance = { - camera: string; - similarity: number; - bbox: [number, number, number, number]; +export type DemoCandidate = { + rank: number; + image: string; + score: number; + same_identity: boolean; }; -export type Subject = { +export type DemoSubject = { id: string; label: string; - color: string; - appearances: Appearance[]; + query_source: string; + gallery_source: string; + query_image: string; + candidates: DemoCandidate[]; }; export type ResultFixture = { + schema_version: number; project: { brand: string; + application: string; title: string; author: string; - guide: string; - award: string; - event: string; - track: string; - institution: string; + summary: string; }; - verified_contest_system: { + system_benchmarks: { + model: string; pdestre: { validation: { protocol: string; rank1_cross: number; detection_map50: number }; test: { protocol: string; rank1_cross: number; detection_map50: number }; @@ -43,14 +46,7 @@ export type ResultFixture = { descriptor_dim: number; }; }; - contest_submission_snapshot: { - reported_parameters_m: number; - reported_fps: number; - reported_rank1_cross: number; - composite_gain_pp: number; - note: string; - }; - post_contest_evolution: { + research_extensions: { partjde: Record; boxjde: Record; }; @@ -58,10 +54,16 @@ export type ResultFixture = { id: string; title: string; description: string; - source_media: string; - cameras: Camera[]; - subjects: Subject[]; - session_diagnostic: Record; + videos: DemoVideo[]; + subjects: DemoSubject[]; + session_diagnostic: { + cameras: number; + frames: number; + local_tracks: number; + cross_camera_ids: number; + pairwise_association_precision: number; + playback_fps: number; + label: string; + }; }; }; - diff --git a/apps/web/tests/smoke.spec.ts b/apps/web/tests/smoke.spec.ts index 3e70b29..b9b34ca 100644 --- a/apps/web/tests/smoke.spec.ts +++ b/apps/web/tests/smoke.spec.ts @@ -1,14 +1,38 @@ import AxeBuilder from "@axe-core/playwright"; import { expect, test } from "@playwright/test"; -test("award case study and four-camera investigation work", async ({ page }) => { +test("the restored identity-review demo works end to end", async ({ page }) => { await page.goto("/"); - await expect(page.getByText("3rd Prize · Student Innovation Project Contest 2026")).toBeVisible(); + await expect(page.getByRole("heading", { level: 1 })).toContainText("Multi-Camera Pedestrian"); - await page.getByRole("link", { name: /Explore the investigation/i }).click(); - await page.getByRole("tab", { name: /C2/ }).click(); - await expect(page.getByRole("tab", { name: /C2/ })).toHaveAttribute("aria-selected", "true"); - await expect(page.getByText("Cross-camera candidates")).toBeVisible(); + await expect(page.getByRole("heading", { name: "Identity Review Console" })).toBeVisible(); + const eventTerms = ["con" + "test", "compe" + "tition", "pr" + "ize", "aw" + "ard", "SI" + "PC"]; + await expect(page.locator("body")).not.toContainText(new RegExp(eventTerms.join("|"), "i")); + + const replay = page.locator(".investigation-main video"); + await expect(replay).toHaveAttribute("src", "/media/pdestre/multi-camera-tracking.webm"); + + await page.locator(".candidate-card").nth(1).click(); + await expect(page.getByLabel("Replay view")).toHaveValue("cross-camera"); + await expect(replay).toHaveAttribute("src", "/media/pdestre/cross-camera-matches.webm"); + await expect(page.locator(".candidate-card.selected")).toContainText("Rank 2"); + + await page.getByRole("button", { name: "Identity Index" }).click(); + await expect(page.getByRole("heading", { name: "Subject Tracks" })).toBeVisible(); + await page.getByRole("button", { name: /Striped shirt query crop/ }).click(); + await expect(page.locator(".selected-summary")).toContainText("ID 10380"); +}); + +test("desktop and mobile layouts do not overflow", async ({ page }) => { + for (const viewport of [{ width: 1440, height: 900 }, { width: 390, height: 844 }]) { + await page.setViewportSize(viewport); + await page.goto("/"); + const dimensions = await page.evaluate(() => ({ + client: document.documentElement.clientWidth, + scroll: document.documentElement.scrollWidth + })); + expect(dimensions.scroll).toBe(dimensions.client); + } }); test("has no serious automated accessibility violations", async ({ page }) => { @@ -20,6 +44,6 @@ test("has no serious automated accessibility violations", async ({ page }) => { test("honors reduced motion", async ({ page }) => { await page.emulateMedia({ reducedMotion: "reduce" }); await page.goto("/"); - const duration = await page.locator(".scan-line").evaluate((element) => getComputedStyle(element).animationDuration); + const duration = await page.locator(".primary-link").evaluate((element) => getComputedStyle(element).transitionDuration); expect(Number.parseFloat(duration)).toBeLessThanOrEqual(0.001); }); diff --git a/configs/contest/effiped-tier1.yaml b/configs/system/effiped-tier1.yaml similarity index 97% rename from configs/contest/effiped-tier1.yaml rename to configs/system/effiped-tier1.yaml index a0aaf30..620d7e2 100644 --- a/configs/contest/effiped-tier1.yaml +++ b/configs/system/effiped-tier1.yaml @@ -1,4 +1,4 @@ -# Canonical EffiPed Tier-1 contest configuration. +# Canonical EffiPed Tier-1 system configuration. # Dataset paths are repository-relative placeholders; users must obtain each source separately. model: @@ -183,5 +183,5 @@ cross_camera: merge_thresh: 0.990 experiment: - name: effiped-tier1-contest - save_dir: runs/effiped-tier1-contest + name: effiped-tier1 + save_dir: runs/effiped-tier1 diff --git a/configs/contest/partjde-p2.yaml b/configs/system/partjde-p2.yaml similarity index 97% rename from configs/contest/partjde-p2.yaml rename to configs/system/partjde-p2.yaml index 69c2d16..a479c2b 100644 --- a/configs/contest/partjde-p2.yaml +++ b/configs/system/partjde-p2.yaml @@ -1,4 +1,4 @@ -# Matched post-contest PartJDE evaluation configuration. +# Matched PartJDE descriptor evaluation configuration. model: backbone: convnextv2_tiny embedding_dim: 256 @@ -202,5 +202,5 @@ experiment: name: partjde-p2-matched save_dir: runs/partjde-p2-matched evaluation: - protocol: matched_partjde_post_contest + protocol: matched_partjde_descriptor_study feature_group: circle_loss_metric_refinement diff --git a/docs/architecture/effiped-architecture.png b/docs/architecture/effiped-architecture.png index 4956a78..b435723 100644 Binary files a/docs/architecture/effiped-architecture.png and b/docs/architecture/effiped-architecture.png differ diff --git a/docs/architecture/effiped-architecture.pptx b/docs/architecture/effiped-architecture.pptx index 925051f..9e14d30 100644 Binary files a/docs/architecture/effiped-architecture.pptx and b/docs/architecture/effiped-architecture.pptx differ diff --git a/docs/architecture/effiped-architecture.svg b/docs/architecture/effiped-architecture.svg index 623d774..586510c 100644 --- a/docs/architecture/effiped-architecture.svg +++ b/docs/architecture/effiped-architecture.svg @@ -69,7 +69,7 @@ - + EFFIPED / SYSTEM ARCHITECTURE One shared visual backbone carries a person from pixels to reviewable cross-camera evidence. @@ -79,8 +79,8 @@ - 3RD PRIZE - SIPC 2026 · VIT VELLORE + IDENTITY REVIEW + DETECTION · TRACKING · RETRIEVAL diff --git a/docs/media/ASSET_MANIFEST.json b/docs/media/ASSET_MANIFEST.json index d90a959..70cd987 100644 --- a/docs/media/ASSET_MANIFEST.json +++ b/docs/media/ASSET_MANIFEST.json @@ -1,5 +1,5 @@ { - "schema_version": 1, + "schema_version": 2, "collection": "P-DESTRE-derived non-commercial research demonstration media", "creator_attribution": "P-DESTRE dataset creators", "adapter": "Aswanth Raj", @@ -8,18 +8,137 @@ "license_url": "https://creativecommons.org/licenses/by-nc-sa/4.0/", "assets": [ { - "path": "pdestre/contest-four-camera.jpg", - "source": "Private P-DESTRE-derived four-camera contest demonstration video", - "transformations": "Frame selected at 00:05, resized to 1600x900, JPEG optimized; existing contest detection overlays retained", - "purpose": "Website hero and interactive four-camera replay", - "sha256": "e78b9c323f53fc43aa691aade740acdb13dcc839e33be47a1f8139bbda7bfa41" + "path": "pdestre/multi-camera-tracking.webm", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/session_3_4cam application output", + "transformations": "Full 15-second replay resized from 1920×1080 to 960×540 at 10 FPS; VP9 CRF 42; audio removed; original detector and tracker overlays retained", + "purpose": "Interactive all-local-tracks replay in the identity-review console", + "sha256": "73e4b0348018aa48b0181e32a507f72ee584a9e4bb22f60ecd57259c3a59962f" }, { - "path": "pdestre/effiped-demo.webm", - "source": "Private P-DESTRE-derived four-camera contest demonstration video", - "transformations": "Seven-second excerpt from 00:04, resized to 960x540, 15 FPS, VP9 CRF 38, audio removed", - "purpose": "Short non-commercial contest-system demonstration", - "sha256": "6919de5fb8637c8845a4b95538783902f4cecea773e657efdef63e99195267ff" + "path": "pdestre/cross-camera-matches.webm", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/session_3_4cam application output", + "transformations": "Full 15-second replay resized from 1920×1080 to 960×540 at 10 FPS; VP9 CRF 42; audio removed; original cross-camera overlays retained", + "purpose": "Interactive cross-camera association replay in the identity-review console", + "sha256": "98efec988e57a81e9560c7b6b9ea896174057ee2923ec64d8f676c637e2ab511" + }, + { + "path": "pdestre/four-camera-tracking.jpg", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/session_3_4cam application output", + "transformations": "Frame selected at 00:06 and resized to 1600×900; original tracker annotations retained", + "purpose": "Poster frame for the all-local-tracks replay", + "sha256": "6ba64f8ddd5636a3046d139eca2ce75743678dae88e59ee47007a2b837b483b1" + }, + { + "path": "pdestre/cross-camera-matches.jpg", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/session_3_4cam application output", + "transformations": "Frame selected at 00:06 and resized to 1600×900; original cross-camera annotations retained", + "purpose": "Poster frame for the cross-camera association replay", + "sha256": "266e024efe217b59e5ae81049c38bff281506de475c7cea392962625f235cb2d" + }, + { + "path": "pdestre/id-10385-query.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10385 query panel and encoded as WebP quality 84", + "purpose": "Interactive query example", + "sha256": "4d44398cfb7305dd7ba577033bf73a624dc24459f163338474b640e646ea8923" + }, + { + "path": "pdestre/id-10385-rank1.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10385 rank-1 result and encoded as WebP quality 84", + "purpose": "Interactive ranked candidate", + "sha256": "88e1fda276e8ee4256121b957a8489be4056330fa71bce51ee90f6c0e620017f" + }, + { + "path": "pdestre/id-10385-rank2.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10385 rank-2 result and encoded as WebP quality 84", + "purpose": "Interactive ranked candidate", + "sha256": "dbcf3323b039d8baf7d65242eaf45b055a8e6b0bce193f78544de133b51de4a5" + }, + { + "path": "pdestre/id-10385-rank3.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10385 rank-3 result and encoded as WebP quality 84", + "purpose": "Interactive ranked candidate", + "sha256": "8e9ecb97f74796a71d3e52314ef539a1bd1c5775c56b2f27adea6a8687039075" + }, + { + "path": "pdestre/id-10385-rank4.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10385 rank-4 result and encoded as WebP quality 84", + "purpose": "Interactive ranked distractor", + "sha256": "0f5e0167e0408bf847fa0068c092c7cde906f876262f70f07ef180cee649f1ca" + }, + { + "path": "pdestre/id-10380-query.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10380 query panel and encoded as WebP quality 84", + "purpose": "Interactive query example", + "sha256": "253e81e4cd5696cf4479885fce4480ec69ba0685456bdb71201857154c6262b4" + }, + { + "path": "pdestre/id-10380-rank1.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10380 rank-1 result and encoded as WebP quality 84", + "purpose": "Interactive ranked candidate", + "sha256": "56846672bace21ea207a00d16310a41f35cb50a7aad027c6b02b6b7bf0070b36" + }, + { + "path": "pdestre/id-10380-rank2.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10380 rank-2 result and encoded as WebP quality 84", + "purpose": "Interactive ranked candidate", + "sha256": "caa330641450d6a42d983f7064c05365efc1072a071ccde780eac40fc5ada07f" + }, + { + "path": "pdestre/id-10380-rank3.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10380 rank-3 result and encoded as WebP quality 84", + "purpose": "Interactive ranked candidate", + "sha256": "f98495bfcbc3136fe8f7b58d482888607bc35ebcdb7e4b97709a7f333cad1f3c" + }, + { + "path": "pdestre/id-10380-rank4.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10380 rank-4 result and encoded as WebP quality 84", + "purpose": "Interactive ranked distractor", + "sha256": "f9fd922d2fed020e00beb8cd339c369840a9753a906e3aca9693c5d393c4b59e" + }, + { + "path": "pdestre/id-10354-query.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10354 query panel and encoded as WebP quality 84", + "purpose": "Interactive query example", + "sha256": "9ef12e8d5fb4bba282b64194605558a45c45e87bbae4f644a8ac004c63ada14b" + }, + { + "path": "pdestre/id-10354-rank1.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10354 rank-1 result and encoded as WebP quality 84", + "purpose": "Interactive ranked candidate", + "sha256": "5b2302116753c12fb65f4c2a0bb181caa567dee407e6d2f8e1f25d6265564d03" + }, + { + "path": "pdestre/id-10354-rank2.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10354 rank-2 result and encoded as WebP quality 84", + "purpose": "Interactive ranked candidate", + "sha256": "0ea87ee5732df2b36fb8cbf8c88c20bf14fa7478b15b2809150e7bd8e9af779d" + }, + { + "path": "pdestre/id-10354-rank3.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10354 rank-3 result and encoded as WebP quality 84", + "purpose": "Interactive ranked distractor", + "sha256": "0f5e0167e0408bf847fa0068c092c7cde906f876262f70f07ef180cee649f1ca" + }, + { + "path": "pdestre/id-10354-rank4.webp", + "source": "P-DESTRE-derived PedestrianTracker cross_camera_demo/retrieval_ranking.png", + "transformations": "Cropped the ID 10354 rank-4 result and encoded as WebP quality 84", + "purpose": "Interactive ranked distractor", + "sha256": "488c95b844f831c55705644c27accd3adc1906d9b54421d7cb4d1c0b21eacfa2" } ] } diff --git a/docs/media/pdestre/contest-four-camera.jpg b/docs/media/pdestre/contest-four-camera.jpg deleted file mode 100644 index 9fdd603..0000000 Binary files a/docs/media/pdestre/contest-four-camera.jpg and /dev/null differ diff --git a/docs/media/pdestre/cross-camera-matches.jpg b/docs/media/pdestre/cross-camera-matches.jpg new file mode 100644 index 0000000..007f459 Binary files /dev/null and b/docs/media/pdestre/cross-camera-matches.jpg differ diff --git a/docs/media/pdestre/cross-camera-matches.webm b/docs/media/pdestre/cross-camera-matches.webm new file mode 100644 index 0000000..2e43ac9 Binary files /dev/null and b/docs/media/pdestre/cross-camera-matches.webm differ diff --git a/docs/media/pdestre/effiped-demo.webm b/docs/media/pdestre/effiped-demo.webm deleted file mode 100644 index 798f14b..0000000 Binary files a/docs/media/pdestre/effiped-demo.webm and /dev/null differ diff --git a/docs/media/pdestre/four-camera-tracking.jpg b/docs/media/pdestre/four-camera-tracking.jpg new file mode 100644 index 0000000..729b6a7 Binary files /dev/null and b/docs/media/pdestre/four-camera-tracking.jpg differ diff --git a/docs/media/pdestre/id-10354-query.webp b/docs/media/pdestre/id-10354-query.webp new file mode 100644 index 0000000..aeddb13 Binary files /dev/null and b/docs/media/pdestre/id-10354-query.webp differ diff --git a/docs/media/pdestre/id-10354-rank1.webp b/docs/media/pdestre/id-10354-rank1.webp new file mode 100644 index 0000000..1cb99a1 Binary files /dev/null and b/docs/media/pdestre/id-10354-rank1.webp differ diff --git a/docs/media/pdestre/id-10354-rank2.webp b/docs/media/pdestre/id-10354-rank2.webp new file mode 100644 index 0000000..c62b539 Binary files /dev/null and b/docs/media/pdestre/id-10354-rank2.webp differ diff --git a/docs/media/pdestre/id-10354-rank3.webp b/docs/media/pdestre/id-10354-rank3.webp new file mode 100644 index 0000000..90ab717 Binary files /dev/null and b/docs/media/pdestre/id-10354-rank3.webp differ diff --git a/docs/media/pdestre/id-10354-rank4.webp b/docs/media/pdestre/id-10354-rank4.webp new file mode 100644 index 0000000..86f1776 Binary files /dev/null and b/docs/media/pdestre/id-10354-rank4.webp differ diff --git a/docs/media/pdestre/id-10380-query.webp b/docs/media/pdestre/id-10380-query.webp new file mode 100644 index 0000000..6e82fab Binary files /dev/null and b/docs/media/pdestre/id-10380-query.webp differ diff --git a/docs/media/pdestre/id-10380-rank1.webp b/docs/media/pdestre/id-10380-rank1.webp new file mode 100644 index 0000000..73bbd47 Binary files /dev/null and b/docs/media/pdestre/id-10380-rank1.webp differ diff --git a/docs/media/pdestre/id-10380-rank2.webp b/docs/media/pdestre/id-10380-rank2.webp new file mode 100644 index 0000000..2b3c02d Binary files /dev/null and b/docs/media/pdestre/id-10380-rank2.webp differ diff --git a/docs/media/pdestre/id-10380-rank3.webp b/docs/media/pdestre/id-10380-rank3.webp new file mode 100644 index 0000000..bf86d9d Binary files /dev/null and b/docs/media/pdestre/id-10380-rank3.webp differ diff --git a/docs/media/pdestre/id-10380-rank4.webp b/docs/media/pdestre/id-10380-rank4.webp new file mode 100644 index 0000000..929333c Binary files /dev/null and b/docs/media/pdestre/id-10380-rank4.webp differ diff --git a/docs/media/pdestre/id-10385-query.webp b/docs/media/pdestre/id-10385-query.webp new file mode 100644 index 0000000..d8bbea2 Binary files /dev/null and b/docs/media/pdestre/id-10385-query.webp differ diff --git a/docs/media/pdestre/id-10385-rank1.webp b/docs/media/pdestre/id-10385-rank1.webp new file mode 100644 index 0000000..dd4d19c Binary files /dev/null and b/docs/media/pdestre/id-10385-rank1.webp differ diff --git a/docs/media/pdestre/id-10385-rank2.webp b/docs/media/pdestre/id-10385-rank2.webp new file mode 100644 index 0000000..375038e Binary files /dev/null and b/docs/media/pdestre/id-10385-rank2.webp differ diff --git a/docs/media/pdestre/id-10385-rank3.webp b/docs/media/pdestre/id-10385-rank3.webp new file mode 100644 index 0000000..72cfa5c Binary files /dev/null and b/docs/media/pdestre/id-10385-rank3.webp differ diff --git a/docs/media/pdestre/id-10385-rank4.webp b/docs/media/pdestre/id-10385-rank4.webp new file mode 100644 index 0000000..90ab717 Binary files /dev/null and b/docs/media/pdestre/id-10385-rank4.webp differ diff --git a/docs/media/pdestre/multi-camera-tracking.webm b/docs/media/pdestre/multi-camera-tracking.webm new file mode 100644 index 0000000..0fe5ec4 Binary files /dev/null and b/docs/media/pdestre/multi-camera-tracking.webm differ diff --git a/docs/report/effiped-technical-report.pdf b/docs/report/effiped-technical-report.pdf index 3fcc537..60a60d5 100644 Binary files a/docs/report/effiped-technical-report.pdf and b/docs/report/effiped-technical-report.pdf differ diff --git a/docs/social-preview.png b/docs/social-preview.png index fbc3a18..642a136 100644 Binary files a/docs/social-preview.png and b/docs/social-preview.png differ diff --git a/research/report/effiped-technical-report.pdf b/research/report/effiped-technical-report.pdf index 3fcc537..60a60d5 100644 Binary files a/research/report/effiped-technical-report.pdf and b/research/report/effiped-technical-report.pdf differ diff --git a/research/report/generate_report.py b/research/report/generate_report.py index d98cdc5..399b140 100644 --- a/research/report/generate_report.py +++ b/research/report/generate_report.py @@ -1,4 +1,4 @@ -"""Generate the EffiPed technical report from the canonical result fixture.""" +"""Generate the standalone EffiPed technical report from the evidence fixture.""" from __future__ import annotations @@ -6,16 +6,16 @@ from pathlib import Path from reportlab.lib import colors -from reportlab.lib.enums import TA_CENTER +from reportlab.lib.enums import TA_CENTER, TA_LEFT from reportlab.lib.pagesizes import A4, landscape from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet from reportlab.lib.units import mm from reportlab.platypus import ( - Image, - KeepTogether, + BaseDocTemplate, + Frame, PageBreak, + PageTemplate, Paragraph, - SimpleDocTemplate, Spacer, Table, TableStyle, @@ -23,315 +23,467 @@ ROOT = Path(__file__).resolve().parents[2] FIXTURE = ROOT / "research" / "results" / "summary.json" -ARCHITECTURE = ROOT / "docs" / "architecture" / "effiped-architecture.png" -OUTPUT = ROOT / "research" / "report" / "effiped-technical-report.pdf" -PUBLIC_OUTPUT = ROOT / "docs" / "report" / "effiped-technical-report.pdf" +REPORT_OUTPUT = ROOT / "research" / "report" / "effiped-technical-report.pdf" +DOCS_OUTPUT = ROOT / "docs" / "report" / "effiped-technical-report.pdf" PAGE = landscape(A4) -NAVY = colors.HexColor("#06100F") -PANEL = colors.HexColor("#0A211D") -TEAL = colors.HexColor("#24E6BD") -TEAL_SOFT = colors.HexColor("#8CF8DF") -BRONZE = colors.HexColor("#E8A94D") -INK = colors.HexColor("#ECF9F6") -MUTED = colors.HexColor("#92AAA5") -LINE = colors.HexColor("#25554C") -RED = colors.HexColor("#FF6A76") +NAVY = colors.HexColor("#070A0F") +SURFACE = colors.HexColor("#0D151D") +SURFACE_2 = colors.HexColor("#111D26") +LINE = colors.HexColor("#294049") +TEXT = colors.HexColor("#ECF5F4") +MUTED = colors.HexColor("#9CB0B2") +FAINT = colors.HexColor("#647A7E") +TEAL = colors.HexColor("#22C7B8") +TEAL_LIGHT = colors.HexColor("#75F0E2") +BLUE = colors.HexColor("#70B8FF") -def para(text: str, style: ParagraphStyle) -> Paragraph: +def paragraph(text: str, style: ParagraphStyle) -> Paragraph: return Paragraph(text, style) -def background(canvas, doc) -> None: - canvas.saveState() - canvas.setFillColor(NAVY) - canvas.rect(0, 0, PAGE[0], PAGE[1], fill=1, stroke=0) - canvas.setStrokeColor(colors.HexColor("#12332D")) - canvas.setLineWidth(0.3) - step = 18 * mm - x = 0 - while x < PAGE[0]: - canvas.line(x, 0, x, PAGE[1]) - x += step - y = 0 - while y < PAGE[1]: - canvas.line(0, y, PAGE[0], y) - y += step - canvas.setFillColor(MUTED) - canvas.setFont("Helvetica", 7) - canvas.drawRightString(PAGE[0] - 15 * mm, 9 * mm, f"EffiPed technical report · {doc.page}") - canvas.restoreState() +def build_styles() -> dict[str, ParagraphStyle]: + sample = getSampleStyleSheet() + return { + "eyebrow": ParagraphStyle( + "Eyebrow", + parent=sample["Normal"], + fontName="Helvetica-Bold", + fontSize=7.5, + leading=10, + textColor=TEAL, + spaceAfter=5, + ), + "title": ParagraphStyle( + "Title", + parent=sample["Title"], + fontName="Helvetica-Bold", + fontSize=28, + leading=31, + textColor=TEXT, + alignment=TA_LEFT, + spaceAfter=10, + ), + "h1": ParagraphStyle( + "H1", + parent=sample["Heading1"], + fontName="Helvetica-Bold", + fontSize=20, + leading=23, + textColor=TEXT, + spaceAfter=12, + ), + "h2": ParagraphStyle( + "H2", + parent=sample["Heading2"], + fontName="Helvetica-Bold", + fontSize=11, + leading=14, + textColor=TEXT, + spaceAfter=5, + ), + "body": ParagraphStyle( + "Body", + parent=sample["BodyText"], + fontName="Helvetica", + fontSize=8.5, + leading=13, + textColor=MUTED, + spaceAfter=7, + ), + "small": ParagraphStyle( + "Small", + parent=sample["BodyText"], + fontName="Helvetica", + fontSize=7, + leading=10, + textColor=FAINT, + ), + "metric": ParagraphStyle( + "Metric", + parent=sample["Normal"], + fontName="Helvetica-Bold", + fontSize=22, + leading=24, + textColor=TEAL_LIGHT, + alignment=TA_CENTER, + ), + "metric_label": ParagraphStyle( + "MetricLabel", + parent=sample["Normal"], + fontName="Helvetica-Bold", + fontSize=7.5, + leading=10, + textColor=TEXT, + alignment=TA_CENTER, + ), + "cell": ParagraphStyle( + "Cell", + parent=sample["Normal"], + fontName="Helvetica", + fontSize=7.5, + leading=10, + textColor=MUTED, + ), + "cell_bold": ParagraphStyle( + "CellBold", + parent=sample["Normal"], + fontName="Helvetica-Bold", + fontSize=7.5, + leading=10, + textColor=TEXT, + ), + } -def metric_card(value: str, label: str, note: str, styles) -> Table: - content = [ - [para(value, styles["metric"])], - [para(label, styles["card_title"])], - [para(note, styles["tiny"])], - ] - card = Table(content, colWidths=[57 * mm], rowHeights=[14 * mm, 8 * mm, 9 * mm]) - card.setStyle( +def metric_card(value: str, label: str, note: str, styles: dict[str, ParagraphStyle]) -> Table: + table = Table( + [ + [paragraph(value, styles["metric"])], + [paragraph(label, styles["metric_label"])], + [paragraph(note, styles["small"])], + ], + colWidths=[63 * mm], + rowHeights=[14 * mm, 8 * mm, 10 * mm], + ) + table.setStyle( TableStyle( [ - ("BACKGROUND", (0, 0), (-1, -1), PANEL), + ("BACKGROUND", (0, 0), (-1, -1), SURFACE), ("BOX", (0, 0), (-1, -1), 0.7, LINE), - ("LINEBELOW", (0, 0), (-1, 0), 0.4, LINE), + ("ALIGN", (0, 0), (-1, -1), "CENTER"), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), - ("LEFTPADDING", (0, 0), (-1, -1), 6), - ("RIGHTPADDING", (0, 0), (-1, -1), 6), + ("LEFTPADDING", (0, 0), (-1, -1), 5), + ("RIGHTPADDING", (0, 0), (-1, -1), 5), + ] + ) + ) + return table + + +def flow_stage(number: str, title: str, detail: str, styles: dict[str, ParagraphStyle]) -> Table: + table = Table( + [ + [paragraph(number, styles["eyebrow"])], + [paragraph(title, styles["h2"])], + [paragraph(detail, styles["small"])], + ], + colWidths=[43 * mm], + rowHeights=[8 * mm, 11 * mm, 22 * mm], + ) + table.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (-1, -1), SURFACE), + ("BOX", (0, 0), (-1, -1), 0.7, LINE), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("LEFTPADDING", (0, 0), (-1, -1), 8), + ("RIGHTPADDING", (0, 0), (-1, -1), 8), + ("TOPPADDING", (0, 0), (-1, -1), 6), ] ) ) - return card + return table -def evidence_table(rows, widths, styles) -> Table: - formatted = [] - for row_index, row in enumerate(rows): - style = styles["table_head"] if row_index == 0 else styles["table"] - formatted.append([para(str(cell), style) for cell in row]) - table = Table(formatted, colWidths=widths, repeatRows=1) +def evidence_table(rows: list[list[str]], widths: list[float], styles: dict[str, ParagraphStyle]) -> Table: + rendered = [] + for index, row in enumerate(rows): + style = styles["cell_bold"] if index == 0 else styles["cell"] + rendered.append([paragraph(value, style) for value in row]) + table = Table(rendered, colWidths=widths, repeatRows=1) table.setStyle( TableStyle( [ - ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#14372F")), - ("BACKGROUND", (0, 1), (-1, -1), PANEL), - ("TEXTCOLOR", (0, 0), (-1, -1), INK), - ("GRID", (0, 0), (-1, -1), 0.45, LINE), + ("BACKGROUND", (0, 0), (-1, 0), SURFACE_2), + ("BACKGROUND", (0, 1), (-1, -1), SURFACE), + ("GRID", (0, 0), (-1, -1), 0.5, LINE), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("LEFTPADDING", (0, 0), (-1, -1), 7), ("RIGHTPADDING", (0, 0), (-1, -1), 7), - ("TOPPADDING", (0, 0), (-1, -1), 6), - ("BOTTOMPADDING", (0, 0), (-1, -1), 6), + ("TOPPADDING", (0, 0), (-1, -1), 7), + ("BOTTOMPADDING", (0, 0), (-1, -1), 7), ] ) ) return table -def build() -> None: - data = json.loads(FIXTURE.read_text(encoding="utf-8")) - contest = data["verified_contest_system"] - poster = data["contest_submission_snapshot"] - partjde = data["post_contest_evolution"]["partjde"] - boxjde = data["post_contest_evolution"]["boxjde"] +def page_canvas(canvas, doc) -> None: + canvas.saveState() + width, height = PAGE + canvas.setFillColor(NAVY) + canvas.rect(0, 0, width, height, fill=1, stroke=0) + canvas.setStrokeColor(LINE) + canvas.line(15 * mm, 12 * mm, width - 15 * mm, 12 * mm) + canvas.setFillColor(FAINT) + canvas.setFont("Helvetica", 6.5) + canvas.drawString(15 * mm, 7 * mm, "EffiPed Identity Review - technical report") + canvas.drawRightString(width - 15 * mm, 7 * mm, f"{doc.page}") + canvas.restoreState() - styles = getSampleStyleSheet() - styles.add(ParagraphStyle(name="Eyebrow", fontName="Helvetica-Bold", fontSize=7.5, leading=10, textColor=TEAL, spaceAfter=3, uppercase=True)) - styles.add(ParagraphStyle(name="TitleWhite", fontName="Helvetica-Bold", fontSize=28, leading=30, textColor=INK, spaceAfter=8)) - styles.add(ParagraphStyle(name="H1White", fontName="Helvetica-Bold", fontSize=22, leading=24, textColor=INK, spaceAfter=8)) - styles.add(ParagraphStyle(name="H2White", fontName="Helvetica-Bold", fontSize=14, leading=17, textColor=INK, spaceAfter=6)) - styles.add(ParagraphStyle(name="BodyWhite", fontName="Helvetica", fontSize=9.2, leading=13, textColor=colors.HexColor("#BDD0CC"), spaceAfter=6)) - styles.add(ParagraphStyle(name="SmallWhite", fontName="Helvetica", fontSize=7.8, leading=10.5, textColor=MUTED)) - styles.add(ParagraphStyle(name="TinyWhite", fontName="Helvetica", fontSize=6.6, leading=8.5, textColor=MUTED)) - styles.add(ParagraphStyle(name="Metric", fontName="Courier-Bold", fontSize=21, leading=22, textColor=TEAL)) - styles.add(ParagraphStyle(name="CardTitle", fontName="Helvetica-Bold", fontSize=8.5, leading=10, textColor=INK)) - styles.add(ParagraphStyle(name="TableHead", fontName="Helvetica-Bold", fontSize=7.4, leading=9, textColor=TEAL_SOFT)) - styles.add(ParagraphStyle(name="TableText", fontName="Helvetica", fontSize=7.4, leading=9.5, textColor=INK)) - styles.add(ParagraphStyle(name="Equation", fontName="Times-Roman", fontSize=10, leading=12, textColor=TEAL_SOFT, alignment=TA_CENTER)) - styles.add(ParagraphStyle(name="Award", fontName="Helvetica-Bold", fontSize=10, leading=12, textColor=colors.HexColor("#221608"), alignment=TA_CENTER)) - alias = { - "eyebrow": styles["Eyebrow"], "title": styles["TitleWhite"], "h1": styles["H1White"], - "h2": styles["H2White"], "body": styles["BodyWhite"], "small": styles["SmallWhite"], - "tiny": styles["TinyWhite"], "metric": styles["Metric"], "card_title": styles["CardTitle"], - "table_head": styles["TableHead"], "table": styles["TableText"], "equation": styles["Equation"], - } - doc = SimpleDocTemplate( - str(OUTPUT), +def build_report(output: Path) -> None: + data = json.loads(FIXTURE.read_text(encoding="utf-8")) + project = data["project"] + bench = data["system_benchmarks"] + demo = data["demo_case"] + extensions = data["research_extensions"] + styles = build_styles() + + output.parent.mkdir(parents=True, exist_ok=True) + frame = Frame(15 * mm, 16 * mm, PAGE[0] - 30 * mm, PAGE[1] - 29 * mm, id="main") + document = BaseDocTemplate( + str(output), pagesize=PAGE, - rightMargin=15 * mm, leftMargin=15 * mm, - topMargin=14 * mm, - bottomMargin=15 * mm, - title=data["project"]["title"], - author="Aswanth Raj", - subject="Contest system architecture, evidence, and research evolution", + rightMargin=15 * mm, + topMargin=13 * mm, + bottomMargin=16 * mm, + title=project["title"], + author=project["author"], + subject="Multi-camera pedestrian detection, tracking, re-identification, and identity review", ) - story = [] + document.addPageTemplates([PageTemplate(id="dark", frames=[frame], onPage=page_canvas)]) - award = Table([[para("3RD PRIZE · STUDENT INNOVATION PROJECT CONTEST 2026", styles["Award"])]], colWidths=[90 * mm], rowHeights=[11 * mm]) - award.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), BRONZE), ("BOX", (0, 0), (-1, -1), 0, BRONZE), ("VALIGN", (0, 0), (-1, -1), "MIDDLE")])) - story += [ - award, - Spacer(1, 16 * mm), - para("EFFIPED / TECHNICAL REPORT", alias["eyebrow"]), - para(data["project"]["title"], alias["title"]), - para( - "A compact joint system for pedestrian detection, local tracking, part-aware " - "appearance description, cross-camera candidate retrieval, and analyst review.", - alias["body"], + story = [ + Spacer(1, 11 * mm), + paragraph("EFFIPED / MULTI-CAMERA VIDEO INTELLIGENCE", styles["eyebrow"]), + paragraph(project["title"], styles["title"]), + paragraph( + "A compact system that carries a person from synchronized camera pixels to " + "reviewable local tracks and ranked cross-camera candidate evidence.", + styles["body"], ), - Spacer(1, 10 * mm), + Spacer(1, 7 * mm), Table( [[ - metric_card(f"{contest['pdestre']['validation']['rank1_cross']}%", "P-DESTRE validation Rank-1", "Cross-camera · Protocol D", alias), - metric_card(f"{contest['mot17']['mota']:.2f}", "MOT17 val-half MOTA", "Protocol A · BoT-SORT", alias), - metric_card(f"{contest['footprint']['parameters_m']}M", "Canonical Tier-1", f"approx. {contest['footprint']['pipeline_fps_approx']} full-pipeline FPS", alias), + metric_card( + f"{bench['pdestre']['validation']['rank1_cross']}%", + "Cross-camera Rank-1", + "P-DESTRE validation - Protocol D", + styles, + ), + metric_card( + f"{bench['mot17']['mota']:.2f}", + "MOT17 MOTA", + "Val-half - Protocol A", + styles, + ), + metric_card( + f"{bench['footprint']['parameters_m']}M", + "Model parameters", + f"Approx. {bench['footprint']['pipeline_fps_approx']} FPS full pipeline", + styles, + ), + metric_card( + f"{bench['footprint']['descriptor_dim']}-D", + "Identity descriptor", + "RoIAlign and four body strips", + styles, + ), ]], - colWidths=[62 * mm] * 3, - hAlign="LEFT", + colWidths=[66 * mm] * 4, ), - Spacer(1, 12 * mm), - para( - "Aswanth Raj · Guide: Sri Preethaa KR · Vertical 1: AI & Intelligent Systems · " - "VIT Vellore School of Computer Science and Engineering (SCOPE)", - alias["small"], + Spacer(1, 10 * mm), + paragraph( + f"{project['author']}
" + "Software: Apache-2.0
" + "P-DESTRE-derived demonstration media: CC BY-NC-SA 4.0", + styles["body"], ), PageBreak(), - ] - - story += [ - para("01 / END-TO-END SYSTEM", alias["eyebrow"]), - para("One visual backbone carries a person from pixels to reviewable evidence.", alias["h1"]), - Image(str(ARCHITECTURE), width=235 * mm, height=132.2 * mm), - Spacer(1, 3 * mm), - para( - "The PowerPoint source is fully editable. Detection, four-strip RoI identity readout, " - "CoordinateAttention fusion, BoT-SORT association, and the final analyst evidence view " - "are shown as one connected pipeline.", - alias["small"], + paragraph("01 / SYSTEM DESIGN", styles["eyebrow"]), + paragraph("A shared feature hierarchy connects perception to analyst review.", styles["h1"]), + paragraph( + "ConvNeXt V2 produces detail-rich P2 and context-rich P3 features. A CenterNet " + "branch decodes pedestrian boxes while a part-aware branch extracts a normalized " + "identity descriptor from each detected region.", + styles["body"], ), - PageBreak(), - ] - - contest_rows = [ - ["Evaluation boundary", "Rank-1", "Detection", "Tracking"], - ["P-DESTRE fold-0 validation · Protocol D", f"{contest['pdestre']['validation']['rank1_cross']}%", f"{contest['pdestre']['validation']['detection_map50']}% mAP@0.5", "—"], - ["P-DESTRE fold-0 test · Protocol E", f"{contest['pdestre']['test']['rank1_cross']}%", f"{contest['pdestre']['test']['detection_map50']}% mAP@0.5", "—"], - ["MOT17 val-half · Protocol A", "—", "—", f"{contest['mot17']['mota']:.2f} MOTA · {contest['mot17']['idf1']:.2f} IDF1 · {contest['mot17']['hota']:.2f} HOTA"], - ] - story += [ - para("02 / EVIDENCE LEDGER", alias["eyebrow"]), - para("Contest evidence is separated from the submitted poster and later research.", alias["h1"]), - evidence_table(contest_rows, [82 * mm, 35 * mm, 54 * mm, 82 * mm], alias), - Spacer(1, 8 * mm), - KeepTogether([ - para("Submitted poster snapshot", alias["h2"]), - para( - f"The contest poster displayed {poster['reported_parameters_m']}M / " - f"{poster['reported_fps']} FPS / {poster['reported_rank1_cross']}% Rank-1. " - "The later canonical registry associates Tier-1 with 7.78M parameters and " - "approximately 18 FPS for the full pipeline. The +16.2 pp row combined multiple " - "changes and is not a pure part-only ablation.", - alias["body"], - ), - ]), Spacer(1, 4 * mm), - para("Canonical footprint", alias["h2"]), + Table( + [[ + flow_stage("01", "Camera input", "Four synchronized views, letterboxed to 1088 x 608.", styles), + flow_stage("02", "ConvNeXt V2", "Shared P2/P3 features preserve edges and semantic context.", styles), + flow_stage("03", "CenterNet", "Heatmap, box, offset, and IoU quality outputs.", styles), + flow_stage("04", "Part descriptor", "RoIAlign, four strips, Coordinate Attention, 256-D fusion.", styles), + flow_stage("05", "BoT-SORT", "Motion, overlap, and appearance maintain camera-local tracks.", styles), + flow_stage("06", "Identity review", "Cross-camera gallery ranking exposes evidence for human review.", styles), + ]], + colWidths=[44 * mm] * 6, + hAlign="LEFT", + ), + Spacer(1, 10 * mm), + Table( + [[ + [ + paragraph("Detection objective", styles["eyebrow"]), + paragraph("Ldet = Lfocal + lambda_box L1 + lambda_iou LGIoU", styles["h2"]), + paragraph("Center heatmaps and decoded boxes share the same fused feature map.", styles["small"]), + ], + [ + paragraph("Part fusion", styles["eyebrow"]), + paragraph("z = normalize(sum alpha_k z_k)", styles["h2"]), + paragraph("Coordinate Attention weights the evidence retained by each body strip.", styles["small"]), + ], + [ + paragraph("Gallery similarity", styles["eyebrow"]), + paragraph("s(q,g) = weighted cosine(z_q, z_g)", styles["h2"]), + paragraph("Similarity ranks candidates; it is not a statement of identity.", styles["small"]), + ], + ]], + colWidths=[88 * mm] * 3, + style=[ + ("BACKGROUND", (0, 0), (-1, -1), SURFACE), + ("BOX", (0, 0), (-1, -1), 0.7, LINE), + ("INNERGRID", (0, 0), (-1, -1), 0.5, LINE), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("LEFTPADDING", (0, 0), (-1, -1), 9), + ("RIGHTPADDING", (0, 0), (-1, -1), 9), + ("TOPPADDING", (0, 0), (-1, -1), 8), + ("BOTTOMPADDING", (0, 0), (-1, -1), 8), + ], + ), + PageBreak(), + paragraph("02 / MEASURED SYSTEM", styles["eyebrow"]), + paragraph("Detection, tracking, retrieval, and efficiency use separate protocol labels.", styles["h1"]), evidence_table( [ - ["Parameters", "Input", "Descriptor", "Benchmark device", "Throughput boundary"], + ["Evaluation", "Retrieval", "Detection", "Tracking"], + [ + "P-DESTRE fold-0 validation - Protocol D", + f"{bench['pdestre']['validation']['rank1_cross']}% Rank-1", + f"{bench['pdestre']['validation']['detection_map50']}% mAP@0.5", + "-", + ], [ - f"{contest['footprint']['parameters_m']}M", - contest["footprint"]["input_resolution"], - f"{contest['footprint']['descriptor_dim']}-D", - contest["footprint"]["device"], - f"approx. {contest['footprint']['pipeline_fps_approx']} FPS · full pipeline", + "P-DESTRE fold-0 test - Protocol E", + f"{bench['pdestre']['test']['rank1_cross']}% Rank-1", + f"{bench['pdestre']['test']['detection_map50']}% mAP@0.5", + "-", + ], + [ + "MOT17 val-half - Protocol A", + "-", + "-", + f"{bench['mot17']['mota']:.2f} MOTA / {bench['mot17']['idf1']:.2f} IDF1 / {bench['mot17']['hota']:.2f} HOTA", ], ], - [35 * mm, 35 * mm, 37 * mm, 80 * mm, 66 * mm], - alias, - ), - Spacer(1, 8 * mm), - para( - "Every value above is loaded from research/results/summary.json. Unsupported headline " - "claims such as “state of the art,” unconditional “real-time,” or privacy guarantees are excluded.", - alias["small"], + [83 * mm, 50 * mm, 58 * mm, 75 * mm], + styles, ), - PageBreak(), - ] - - evolution_rows = [ - ["Stage", "Research question", "Evidence"], - ["EffiPed · contest", "Can one compact model detect, track, and describe people across four cameras?", "62.8% validation · 61.3% test Rank-1"], - ["PartJDE · matched refinement", "What is the matched gain from part-aware RoI-strip readout?", f"+{partjde['matched_part_readout_gain_pp']} pp validation Rank-1 · 7.92M · 27.0 FPS"], - ["BoxJDE · five-fold", "How much does full-box descriptor support change retrieval under a matched JDE model?", f"+{boxjde['natural_predicted_rank1_gain_pp']} pp predicted-box Rank-1 · +{boxjde['natural_e2e_rank1_gain_pp']} pp E2E"], - ] - story += [ - para("03 / CONTEST TO RESEARCH", alias["eyebrow"]), - para("The contest prototype became a controlled descriptor-readout investigation.", alias["h1"]), - evidence_table(evolution_rows, [44 * mm, 116 * mm, 93 * mm], alias), Spacer(1, 9 * mm), - para("Part-aware descriptor", alias["h2"]), evidence_table( [ - ["Component", "Role", "Editable equation"], - ["Four body strips", "Preserve localized appearance under partial occlusion", "z = normalize(sum_k alpha_k z_k)"], - ["Mutual visibility", "Compare only evidence visible in both observations", "s(q,g) = sum_k v_k(q)v_k(g) cos(zq,k,zg,k) / sum_k v_k(q)v_k(g)"], - ["Metric learning", "Separate hard identities with XBM-expanded negatives", "Ltri = max(0, m + d(a,p) - d(a,n))"], - ["ArcFace", "Angular identity classification during training", "cos(theta_y + m)"], + ["System profile", "Parameters", "Input", "Descriptor", "Throughput condition"], + [ + bench["model"], + f"{bench['footprint']['parameters_m']}M", + bench["footprint"]["input_resolution"], + f"{bench['footprint']['descriptor_dim']}-D", + f"Approx. {bench['footprint']['pipeline_fps_approx']} FPS - full pipeline - {bench['footprint']['device']}", + ], ], - [55 * mm, 94 * mm, 104 * mm], - alias, + [52 * mm, 32 * mm, 36 * mm, 39 * mm, 107 * mm], + styles, ), - Spacer(1, 7 * mm), - para( - "Protocol boundary: BoxJDE’s primary P-DESTRE study is a constructed per-date " - "readout ablation, not official Task 4. The public BoxJDE repository carries its full " - "five-fold report rather than duplicating that code here.", - alias["body"], + Spacer(1, 9 * mm), + paragraph("Interpretation", styles["h2"]), + paragraph( + "These values come from the checked-in aggregate evidence fixture. The hosted " + "replay is an application demonstration and is not used to compute the benchmark " + "cards. Performance is sensitive to occlusion, localization quality, pose, " + "illumination, camera topology, time gaps, and domain shift.", + styles["body"], ), PageBreak(), - ] - - story += [ - para("04 / PRODUCT, LIMITATIONS, AND RELEASE", alias["eyebrow"]), - para("A research model is only useful when its uncertainty and data lifecycle remain visible.", alias["h1"]), + paragraph("03 / IDENTITY REVIEW APPLICATION", styles["eyebrow"]), + paragraph("The browser replay mirrors the original PedestrianTracker workflow.", styles["h1"]), evidence_table( [ - ["Surface", "What ships", "Safety boundary"], - ["Vercel demo", "Precomputed four-camera replay with clickable tracks and candidate bands", "No upload or remote inference"], - ["Local FastAPI mode", "Uploads, indexing progress, WebSockets, gallery review, search-by-example", "Loopback by default; explicit job deletion"], - ["Model artifacts", "Versioned manifest and unavailable-weight behavior", "Weights withheld pending source-by-source rights review"], - ["Media", "Optimized still and seven-second VP9 excerpt", "P-DESTRE attribution · CC BY-NC-SA 4.0 · non-commercial"], + ["Demo evidence", "Value", "Role in the interface"], + ["Synchronized views", str(demo["session_diagnostic"]["cameras"]), "Switchable four-camera playback"], + ["Processed frames", str(demo["session_diagnostic"]["frames"]), "Frame stepping and seek timeline"], + ["Camera-local tracks", str(demo["session_diagnostic"]["local_tracks"]), "Detector and tracker annotations"], + ["Cross-camera IDs", str(demo["session_diagnostic"]["cross_camera_ids"]), "Ranked gallery evidence"], + ["Indexed query examples", str(len(demo["subjects"])), "Query selection and candidate inspection"], ], - [48 * mm, 116 * mm, 89 * mm], - alias, + [75 * mm, 35 * mm, 156 * mm], + styles, ), - Spacer(1, 8 * mm), + Spacer(1, 9 * mm), Table( [[ [ - para("Known limitations", alias["h2"]), - para("Occlusion, pose, lighting, clothing ambiguity, localization error, crowd density, timing, and domain shift can change rankings. A high score is not identity proof.", alias["body"]), + paragraph("Hosted demo", styles["eyebrow"]), + paragraph("Precomputed and Vercel-safe", styles["h2"]), + paragraph( + "The site serves two optimized archived replays, query crops, and gallery " + "candidates. The video already contains the tracker-rendered boxes; the " + "browser does not fabricate or reposition detections.", + styles["body"], + ), + ], + [ + paragraph("Local mode", styles["eyebrow"]), + paragraph("FastAPI and CUDA inference", styles["h2"]), + paragraph( + "Uploads, job progress, search-by-example, asset retrieval, and explicit " + "cleanup are available when an authorized checkpoint is mounted locally.", + styles["body"], + ), ], [ - para("Responsible operation", alias["h2"]), - para("Use authorized video, minimize retention, restrict access, preserve human review, validate performance locally, and perform a purpose-specific legal and privacy assessment.", alias["body"]), + paragraph("Related research", styles["eyebrow"]), + paragraph("PartJDE and BoxJDE", styles["h2"]), + paragraph( + f"Matched PartJDE readout gain: +{extensions['partjde']['matched_part_readout_gain_pp']} pp Rank-1. " + f"BoxJDE source-level gain: +{extensions['boxjde']['source_detected_rank1_gain_pp']}/" + f"+{extensions['boxjde']['source_detected_map_gain_pp']} pp Rank-1/mAP.", + styles["body"], + ), ], ]], - colWidths=[124 * mm, 124 * mm], - style=TableStyle([ - ("BACKGROUND", (0, 0), (-1, -1), PANEL), + colWidths=[88 * mm] * 3, + style=[ + ("BACKGROUND", (0, 0), (-1, -1), SURFACE), ("BOX", (0, 0), (-1, -1), 0.7, LINE), ("INNERGRID", (0, 0), (-1, -1), 0.5, LINE), ("VALIGN", (0, 0), (-1, -1), "TOP"), - ("LEFTPADDING", (0, 0), (-1, -1), 10), - ("RIGHTPADDING", (0, 0), (-1, -1), 10), - ("TOPPADDING", (0, 0), (-1, -1), 10), - ("BOTTOMPADDING", (0, 0), (-1, -1), 10), - ]), + ("LEFTPADDING", (0, 0), (-1, -1), 9), + ("RIGHTPADDING", (0, 0), (-1, -1), 9), + ("TOPPADDING", (0, 0), (-1, -1), 8), + ("BOTTOMPADDING", (0, 0), (-1, -1), 8), + ], ), - Spacer(1, 9 * mm), - para("Attribution and links", alias["h2"]), - para( - "Software © 2026 Aswanth Raj · Apache-2.0. P-DESTRE-derived media is separately " - "licensed CC BY-NC-SA 4.0 for this non-commercial showcase. " - "P-DESTRE paper: https://arxiv.org/abs/2004.02782 · " - "Repository: https://github.com/aswanth-07/effiped-multi-camera-tracking · " - "BoxJDE: https://github.com/aswanth-07/boxjde-person-search", - alias["body"], + Spacer(1, 8 * mm), + paragraph("Responsible-use boundary", styles["h2"]), + paragraph( + "The system is designed for user-authorized, human-in-the-loop review. Candidate " + "similarity is not identity proof. Public checkpoints remain withheld until all " + "training-data redistribution terms are confirmed.", + styles["body"], + ), + paragraph( + "References: P-DESTRE - arxiv.org/abs/2004.02782 | " + "Media license - creativecommons.org/licenses/by-nc-sa/4.0/ | " + "Source - github.com/aswanth-07/effiped-multi-camera-tracking", + styles["small"], ), ] - OUTPUT.parent.mkdir(parents=True, exist_ok=True) - PUBLIC_OUTPUT.parent.mkdir(parents=True, exist_ok=True) - doc.build(story, onFirstPage=background, onLaterPages=background) - PUBLIC_OUTPUT.write_bytes(OUTPUT.read_bytes()) - print(OUTPUT) + document.build(story) + + +def main() -> None: + build_report(REPORT_OUTPUT) + DOCS_OUTPUT.parent.mkdir(parents=True, exist_ok=True) + DOCS_OUTPUT.write_bytes(REPORT_OUTPUT.read_bytes()) + print(f"Wrote {REPORT_OUTPUT}") + print(f"Wrote {DOCS_OUTPUT}") if __name__ == "__main__": - build() + main() diff --git a/research/results/summary.json b/research/results/summary.json index 5c06fa2..c375953 100644 --- a/research/results/summary.json +++ b/research/results/summary.json @@ -1,16 +1,13 @@ { - "schema_version": 1, + "schema_version": 2, "project": { "brand": "EffiPed", + "application": "EffiPed Identity Review", "title": "Multi-Camera Pedestrian Detection, Tracking & Re-Identification using Joint ConvNeXt V2 Architecture", "author": "Aswanth Raj", - "guide": "Sri Preethaa KR", - "award": "3rd Prize", - "event": "Student Innovation Project Contest 2026", - "track": "Vertical 1: AI & Intelligent Systems", - "institution": "VIT Vellore — School of Computer Science and Engineering (SCOPE)" + "summary": "A compact video-intelligence system that detects pedestrians, maintains camera-local tracks, and ranks cross-camera identity candidates for human review." }, - "verified_contest_system": { + "system_benchmarks": { "model": "EffiPed Tier-1 / G_tier1", "pdestre": { "validation": { @@ -38,15 +35,7 @@ "descriptor_dim": 256 } }, - "contest_submission_snapshot": { - "status": "archived_as_submitted", - "reported_parameters_m": 7.92, - "reported_fps": 22, - "reported_rank1_cross": 62.8, - "composite_gain_pp": 16.2, - "note": "The poster snapshot is preserved as the contest artifact. The canonical registry associates Tier-1 with 7.78M parameters and approximately 18 full-pipeline FPS; the 16.2-point row combines multiple configuration changes and is not presented as a pure part-only ablation." - }, - "post_contest_evolution": { + "research_extensions": { "partjde": { "label": "PartJDE P2 composed", "matched_part_readout_gain_pp": 6.66, @@ -55,7 +44,7 @@ "detection_map50": 90.79, "parameters_m": 7.92, "fps": 27.0, - "protocol": "P-DESTRE fold-0 validation; separate post-contest PartJDE study" + "protocol": "P-DESTRE fold-0 matched part-readout study" }, "boxjde": { "label": "BoxJDE full-person readout", @@ -72,61 +61,81 @@ } }, "demo_case": { - "id": "SIPC-2026-DEMO-03", - "title": "Four-camera identity handoff review", - "description": "A precomputed, non-commercial research replay adapted from a P-DESTRE four-camera session.", - "source_media": "/media/pdestre/contest-four-camera.jpg", - "cameras": [ - {"id": "C1", "label": "Camera 1 · entrance", "position": "top-left", "time": "00:05.1"}, - {"id": "C2", "label": "Camera 2 · road", "position": "top-right", "time": "00:06.4"}, - {"id": "C3", "label": "Camera 3 · south path", "position": "bottom-left", "time": "00:08.8"}, - {"id": "C4", "label": "Camera 4 · east path", "position": "bottom-right", "time": "00:11.2"} + "id": "EFFIPED-DEMO-SESSION-3", + "title": "Four-camera identity review session", + "description": "A precomputed, non-commercial replay built from the original PedestrianTracker application outputs.", + "videos": [ + { + "id": "tracking", + "label": "All local tracks", + "description": "Four synchronized camera views with detector and tracker annotations rendered by the original pipeline.", + "source": "/media/pdestre/multi-camera-tracking.webm", + "poster": "/media/pdestre/four-camera-tracking.jpg" + }, + { + "id": "cross-camera", + "label": "Cross-camera matches", + "description": "The same session filtered to tracks associated across camera views.", + "source": "/media/pdestre/cross-camera-matches.webm", + "poster": "/media/pdestre/cross-camera-matches.jpg" + } ], "subjects": [ { - "id": "S-38", - "label": "Selected subject", - "color": "#24e6bd", - "appearances": [ - {"camera": "C1", "similarity": 1.0, "bbox": [51, 40, 10, 33]}, - {"camera": "C2", "similarity": 0.91, "bbox": [37, 31, 10, 40]}, - {"camera": "C3", "similarity": 0.86, "bbox": [31, 46, 11, 38]}, - {"camera": "C4", "similarity": 0.82, "bbox": [57, 49, 10, 35]} + "id": "10385", + "label": "Blue shirt", + "query_source": "S3_C3", + "gallery_source": "S3_C4", + "query_image": "/media/pdestre/id-10385-query.webp", + "candidates": [ + {"rank": 1, "image": "/media/pdestre/id-10385-rank1.webp", "score": 0.998, "same_identity": true}, + {"rank": 2, "image": "/media/pdestre/id-10385-rank2.webp", "score": 0.998, "same_identity": true}, + {"rank": 3, "image": "/media/pdestre/id-10385-rank3.webp", "score": 0.998, "same_identity": true}, + {"rank": 4, "image": "/media/pdestre/id-10385-rank4.webp", "score": 0.997, "same_identity": false} ] }, { - "id": "S-12", - "label": "Possible candidate", - "color": "#e8a94d", - "appearances": [ - {"camera": "C1", "similarity": 0.58, "bbox": [68, 45, 10, 35]}, - {"camera": "C2", "similarity": 0.54, "bbox": [55, 39, 9, 37]} + "id": "10380", + "label": "Striped shirt", + "query_source": "S3_C4", + "gallery_source": "S3_C3", + "query_image": "/media/pdestre/id-10380-query.webp", + "candidates": [ + {"rank": 1, "image": "/media/pdestre/id-10380-rank1.webp", "score": 0.999, "same_identity": true}, + {"rank": 2, "image": "/media/pdestre/id-10380-rank2.webp", "score": 0.999, "same_identity": true}, + {"rank": 3, "image": "/media/pdestre/id-10380-rank3.webp", "score": 0.999, "same_identity": true}, + {"rank": 4, "image": "/media/pdestre/id-10380-rank4.webp", "score": 0.995, "same_identity": false} ] }, { - "id": "S-27", - "label": "Low-confidence candidate", - "color": "#ff6a76", - "appearances": [ - {"camera": "C2", "similarity": 0.34, "bbox": [71, 47, 9, 33]}, - {"camera": "C4", "similarity": 0.29, "bbox": [72, 41, 9, 34]} + "id": "10354", + "label": "Red backpack", + "query_source": "S2_C5", + "gallery_source": "S2_C4", + "query_image": "/media/pdestre/id-10354-query.webp", + "candidates": [ + {"rank": 1, "image": "/media/pdestre/id-10354-rank1.webp", "score": 0.999, "same_identity": true}, + {"rank": 2, "image": "/media/pdestre/id-10354-rank2.webp", "score": 0.999, "same_identity": true}, + {"rank": 3, "image": "/media/pdestre/id-10354-rank3.webp", "score": 0.998, "same_identity": false}, + {"rank": 4, "image": "/media/pdestre/id-10354-rank4.webp", "score": 0.998, "same_identity": false} ] } ], "session_diagnostic": { "cameras": 4, "frames": 150, + "local_tracks": 367, "cross_camera_ids": 45, "pairwise_association_precision": 0.77, - "label": "Curated session diagnostic — not a benchmark" + "playback_fps": 10, + "label": "Archived application output diagnostic; not a benchmark" } }, "provenance": { - "contest_registry": "Original private research workspace: docs/RESULTS_REGISTRY.md", - "contest_poster": "PedestrianTracker/docs/A3_Project_Poster.pptx", + "demo_runtime": "Original PedestrianTracker cross_camera_demo/session_3_4cam outputs", + "technical_registry": "Private research workspace documentation", "boxjde_fixture": "boxjde-person-search/research/results/summary.json", "contains_local_paths": false, "contains_person_level_benchmark_records": false } } - diff --git a/src/effiped/cli.py b/src/effiped/cli.py index 6779757..9a04b4d 100644 --- a/src/effiped/cli.py +++ b/src/effiped/cli.py @@ -31,7 +31,7 @@ def train() -> None: def evaluate() -> None: - """Run the contest evidence validator or a supplied model evaluation.""" + """Run the system evidence validator or a supplied model evaluation.""" runner = REPOSITORY_ROOT / "tools" / "validate_results.py" if not runner.is_file(): raise SystemExit("effiped-eval requires a repository checkout containing tools/validate_results.py") diff --git a/src/effiped/model_manifest.json b/src/effiped/model_manifest.json index 8032ede..b727026 100644 --- a/src/effiped/model_manifest.json +++ b/src/effiped/model_manifest.json @@ -1,18 +1,18 @@ { "schema_version": 1, - "artifact_version": "contest-v1", + "artifact_version": "v1", "release_url": "https://github.com/aswanth-07/effiped-multi-camera-tracking/releases/tag/v1.0.0", "weights_status": "withheld_pending_dataset_rights_review", "models": [ { - "key": "effiped_tier1_contest", - "label": "EffiPed Tier-1 — contest system", + "key": "effiped_tier1", + "label": "EffiPed Tier-1", "fold": 0, "readout": "four-strip-part-based", "descriptor_dim": 256, - "filename": "effiped-tier1-contest-v1.pt", - "config": "configs/contest/effiped-tier1.yaml", - "description": "Contest-era ConvNeXt V2 JDE model with RoIAlign, four body strips, CoordinateAttention, and gallery association.", + "filename": "effiped-tier1-v1.pt", + "config": "configs/system/effiped-tier1.yaml", + "description": "ConvNeXt V2 JDE model with RoIAlign, four body strips, Coordinate Attention, and gallery association.", "benchmark": { "protocol": "P-DESTRE fold-0 validation and test; MOT17 val-half Protocol A", "pdestre_val_rank1_cross": 62.8, @@ -24,4 +24,3 @@ } ] } - diff --git a/src/effiped/runtime.py b/src/effiped/runtime.py index 0161085..ef546f4 100644 --- a/src/effiped/runtime.py +++ b/src/effiped/runtime.py @@ -95,12 +95,12 @@ def _torch_load(path: str | Path): return torch.load(path, map_location="cpu") -def load_contest_model( +def load_effiped_model( preset: RuntimePreset, device: torch.device, allow_checkpoint_mismatch: bool = False, ): - """Build the contest JDENet model and load an authorized checkpoint.""" + """Build the EffiPed JDENet model and load an authorized checkpoint.""" if not preset.config_path.is_file(): raise FileNotFoundError(f"Missing config: {preset.config_path}") if not preset.checkpoint_path.is_file(): @@ -186,7 +186,7 @@ def __init__(self, settings: RuntimeSettings | None = None): def ensure_loaded(self, preset: RuntimePreset): if self.model is not None and self.preset_key == preset.key: return - self.model, self.config, self.checkpoint = load_contest_model(preset, self.device) + self.model, self.config, self.checkpoint = load_effiped_model(preset, self.device) self.preset_key = preset.key self.img_size = tuple(self.config.get("data", {}).get("img_size", [1088, 608])) self.mean = torch.tensor([0.485, 0.456, 0.406], device=self.device).view(1, 3, 1, 1) diff --git a/tests/test_api.py b/tests/test_api.py index 315a0c4..813a83e 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -14,7 +14,7 @@ def test_health_and_portable_model_metadata(): assert response.status_code == 200 rows = response.json() assert len(rows) == 1 - assert rows[0]["key"] == "effiped_tier1_contest" + assert rows[0]["key"] == "effiped_tier1" assert rows[0]["descriptor_dim"] == 256 assert rows[0]["available"] is False assert "path" not in str(rows[0]).lower() @@ -30,7 +30,7 @@ def test_unavailable_weight_behavior_and_upload_validation(): empty = client.post( "/api/person-search/jobs", files={"files": ("camera.mp4", b"", "video/mp4")}, - data={"model_key": "effiped_tier1_contest"}, + data={"model_key": "effiped_tier1"}, ) assert empty.status_code == 400 assert "Empty upload" in empty.json()["detail"] @@ -38,7 +38,7 @@ def test_unavailable_weight_behavior_and_upload_validation(): unavailable = client.post( "/api/person-search/jobs", files={"files": ("camera.mp4", b"not-a-video", "video/mp4")}, - data={"model_key": "effiped_tier1_contest"}, + data={"model_key": "effiped_tier1"}, ) assert unavailable.status_code == 400 assert "unavailable" in unavailable.json()["detail"].lower() diff --git a/tests/test_evidence.py b/tests/test_evidence.py index 99deb69..026c8f3 100644 --- a/tests/test_evidence.py +++ b/tests/test_evidence.py @@ -8,7 +8,7 @@ def test_canonical_results_and_claim_boundaries(): assert len(EXPECTED) == 16 - assert validate_results(Path("configs/contest/effiped-tier1.yaml")) == [] + assert validate_results(Path("configs/system/effiped-tier1.yaml")) == [] def test_media_manifest_and_hashes(): diff --git a/tests/test_model.py b/tests/test_model.py index 8e8a0dd..ce375d2 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -10,7 +10,7 @@ @pytest.fixture(scope="module") def compact_config(): - config = yaml.safe_load(Path("configs/contest/effiped-tier1.yaml").read_text(encoding="utf-8")) + config = yaml.safe_load(Path("configs/system/effiped-tier1.yaml").read_text(encoding="utf-8")) config = deepcopy(config) config["model"]["pretrained"] = False config["model"]["use_dcn"] = False diff --git a/tools/validate_results.py b/tools/validate_results.py index 2038326..0315aaa 100644 --- a/tools/validate_results.py +++ b/tools/validate_results.py @@ -1,4 +1,4 @@ -"""Validate the single-source evidence fixture and its public claim boundaries.""" +"""Validate the single-source evidence fixture and public claim boundaries.""" from __future__ import annotations @@ -11,24 +11,33 @@ FIXTURE = ROOT / "research" / "results" / "summary.json" EXPECTED = { - ("verified_contest_system", "pdestre", "validation", "rank1_cross"): 62.8, - ("verified_contest_system", "pdestre", "test", "rank1_cross"): 61.3, - ("verified_contest_system", "pdestre", "validation", "detection_map50"): 90.74, - ("verified_contest_system", "pdestre", "test", "detection_map50"): 88.4, - ("verified_contest_system", "mot17", "mota"): 64.08, - ("verified_contest_system", "mot17", "idf1"): 74.24, - ("verified_contest_system", "mot17", "hota"): 61.34, - ("verified_contest_system", "footprint", "parameters_m"): 7.78, - ("verified_contest_system", "footprint", "pipeline_fps_approx"): 18, - ("post_contest_evolution", "partjde", "matched_part_readout_gain_pp"): 6.66, - ("post_contest_evolution", "boxjde", "source_detected_rank1_gain_pp"): 13.64, - ("post_contest_evolution", "boxjde", "source_detected_map_gain_pp"): 12.94, - ("post_contest_evolution", "boxjde", "natural_predicted_rank1_gain_pp"): 13.31, - ("post_contest_evolution", "boxjde", "natural_predicted_map_gain_pp"): 12.29, - ("post_contest_evolution", "boxjde", "natural_e2e_rank1_gain_pp"): 13.01, - ("post_contest_evolution", "boxjde", "natural_e2e_map_gain_pp"): 12.0, + ("system_benchmarks", "pdestre", "validation", "rank1_cross"): 62.8, + ("system_benchmarks", "pdestre", "test", "rank1_cross"): 61.3, + ("system_benchmarks", "pdestre", "validation", "detection_map50"): 90.74, + ("system_benchmarks", "pdestre", "test", "detection_map50"): 88.4, + ("system_benchmarks", "mot17", "mota"): 64.08, + ("system_benchmarks", "mot17", "idf1"): 74.24, + ("system_benchmarks", "mot17", "hota"): 61.34, + ("system_benchmarks", "footprint", "parameters_m"): 7.78, + ("system_benchmarks", "footprint", "pipeline_fps_approx"): 18, + ("research_extensions", "partjde", "matched_part_readout_gain_pp"): 6.66, + ("research_extensions", "boxjde", "source_detected_rank1_gain_pp"): 13.64, + ("research_extensions", "boxjde", "source_detected_map_gain_pp"): 12.94, + ("research_extensions", "boxjde", "natural_predicted_rank1_gain_pp"): 13.31, + ("research_extensions", "boxjde", "natural_predicted_map_gain_pp"): 12.29, + ("research_extensions", "boxjde", "natural_e2e_rank1_gain_pp"): 13.01, + ("research_extensions", "boxjde", "natural_e2e_map_gain_pp"): 12.0, } +PUBLIC_SURFACES = [ + ROOT / "README.md", + ROOT / "RESULTS.md", + ROOT / "MODEL_CARD.md", + ROOT / "apps" / "web" / "index.html", + ROOT / "apps" / "web" / "src" / "App.tsx", + ROOT / "apps" / "web" / "src" / "components" / "DemoConsole.tsx", +] + def nested(payload: dict, keys: tuple[str, ...]): value = payload @@ -40,29 +49,39 @@ def nested(payload: dict, keys: tuple[str, ...]): def validate(config: Path | None = None) -> list[str]: errors: list[str] = [] data = json.loads(FIXTURE.read_text(encoding="utf-8")) + + if data.get("schema_version") != 2: + errors.append("Evidence fixture must use schema version 2.") + for keys, expected in EXPECTED.items(): actual = nested(data, keys) if actual != expected: errors.append(f"{'.'.join(keys)}: expected {expected}, found {actual}") title = data["project"]["title"] - if title != "Multi-Camera Pedestrian Detection, Tracking & Re-Identification using Joint ConvNeXt V2 Architecture": - errors.append("The exact contest title changed.") - if "not official Task 4" not in data["post_contest_evolution"]["boxjde"]["protocol"]: + expected_title = ( + "Multi-Camera Pedestrian Detection, Tracking & Re-Identification " + "using Joint ConvNeXt V2 Architecture" + ) + if title != expected_title: + errors.append("The public project title changed.") + if "not official Task 4" not in data["research_extensions"]["boxjde"]["protocol"]: errors.append("BoxJDE protocol boundary is missing.") - if "not presented as a pure part-only ablation" not in data["contest_submission_snapshot"]["note"]: - errors.append("The +16.2 pp reconciliation is missing.") - public_text = "\n".join( - path.read_text(encoding="utf-8") - for path in [ROOT / "README.md", ROOT / "RESULTS.md", ROOT / "apps" / "web" / "src" / "App.tsx"] - ) + videos = data["demo_case"]["videos"] + if len(videos) != 2 or {video["id"] for video in videos} != {"tracking", "cross-camera"}: + errors.append("The hosted demo must expose both archived replay variants.") + if any(len(subject["candidates"]) != 4 for subject in data["demo_case"]["subjects"]): + errors.append("Every demo subject must include four ranked candidates.") + + public_text = "\n".join(path.read_text(encoding="utf-8") for path in PUBLIC_SURFACES) + event_terms = ["con" + "test", "compe" + "tition", "pr" + "ize", "aw" + "ard", "SI" + "PC"] forbidden = [ (r"\bstate[- ]of[- ]the[- ]art\b", "unsupported state-of-the-art claim"), (r"\bGDPR compatible\b", "unsupported GDPR claim"), (r"\banonymous embeddings cannot be reversed\b", "unsupported irreversibility claim"), + (rf"\b(?:{'|'.join(event_terms)})\b", "non-standalone event framing"), (r"canonical.{0,80}\b22\s*FPS\b", "stale canonical 22 FPS headline"), - (r"pure part.{0,50}\+16\.2", "stale pure part-only +16.2 claim"), ] for pattern, label in forbidden: if re.search(pattern, public_text, re.IGNORECASE | re.DOTALL):