{ "cells": [ { "cell_type": "markdown", "id": "a49b039b", "metadata": {}, "source": [ "# SimpleTrack Deep Dive - 05: AMOTA / AMOTP Metrics & End-to-End on the Real BEVFusion JSON\n", "\n", "Finally we (1) define the tracking metrics nuScenes uses to score SimpleTrack, and (2) run the modules built in NB02-04 on the **actual BEVFusion detections** exported by `perception/docker/run_pipeline.sh` (`results_nusc.json`), producing per-scene tracks and a nuScenes tracking-submission JSON." ] }, { "cell_type": "code", "execution_count": 1, "id": "b07275a3", "metadata": { "execution": { "iopub.execute_input": "2026-08-02T03:39:38.620491Z", "iopub.status.busy": "2026-08-02T03:39:38.620225Z", "iopub.status.idle": "2026-08-02T03:39:39.164605Z", "shell.execute_reply": "2026-08-02T03:39:39.164034Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "IN_CONTAINER : True\n", "SIMPLETRACK_SRC : /workspace/perception/simpletrack exists= True\n", "NUSC_ROOT : /data/nuscenes exists= True\n", "RESULTS_DIR : /results exists= True\n" ] } ], "source": [ "# --- Common bootstrap (safe to run first in any SimpleTrack notebook) ---\n", "import os, sys, math, json, textwrap\n", "import numpy as np\n", "\n", "def _first_existing(paths, default):\n", " for p in paths:\n", " if p and os.path.isdir(p):\n", " return p\n", " return default\n", "\n", "IN_CONTAINER = os.path.isdir(\"/workspace/perception\")\n", "SIMPLETRACK_SRC = os.environ.get(\"SIMPLETRACK_SRC\") or _first_existing(\n", " [\"/workspace/perception/simpletrack\",\n", " \"/home/nadurthi/repos/deeplearning/perception/simpletrack\"],\n", " \"/home/nadurthi/repos/deeplearning/perception/simpletrack\")\n", "NUSC_ROOT = os.environ.get(\"NUSC_ROOT\") or _first_existing(\n", " [\"/data/nuscenes\", \"/mnt/mydata/nuscenes/mini_root\", \"/mnt/mydata/nuscenes\"],\n", " \"/mnt/mydata/nuscenes\")\n", "RESULTS_DIR = os.environ.get(\"BEVFUSION_RESULTS_DIR\") or _first_existing(\n", " [\"/results\", \"/mnt/mydata/nuscenes/results\"], \"/mnt/mydata/nuscenes/results\")\n", "\n", "if SIMPLETRACK_SRC and os.path.isdir(SIMPLETRACK_SRC) and SIMPLETRACK_SRC not in sys.path:\n", " sys.path.insert(0, SIMPLETRACK_SRC)\n", "\n", "import matplotlib\n", "if os.environ.get(\"MPLBACKEND\") is None:\n", " try: matplotlib.use(\"Agg\")\n", " except Exception: pass\n", "import matplotlib.pyplot as plt\n", "import matplotlib.patches as mpatches\n", "np.set_printoptions(precision=3, suppress=True)\n", "\n", "print(\"IN_CONTAINER :\", IN_CONTAINER)\n", "print(\"SIMPLETRACK_SRC :\", SIMPLETRACK_SRC, \"exists=\", os.path.isdir(SIMPLETRACK_SRC))\n", "print(\"NUSC_ROOT :\", NUSC_ROOT, \"exists=\", os.path.isdir(NUSC_ROOT))\n", "print(\"RESULTS_DIR :\", RESULTS_DIR, \"exists=\", os.path.isdir(RESULTS_DIR))\n" ] }, { "cell_type": "code", "execution_count": 2, "id": "29ef1097", "metadata": { "execution": { "iopub.execute_input": "2026-08-02T03:39:39.167109Z", "iopub.status.busy": "2026-08-02T03:39:39.166811Z", "iopub.status.idle": "2026-08-02T03:39:39.173999Z", "shell.execute_reply": "2026-08-02T03:39:39.173564Z" } }, "outputs": [], "source": [ "# --- Reusable BEV geometry + drawing helpers (oriented boxes in the ground plane) ---\n", "def box_corners_2d(cx, cy, w, l, yaw):\n", " \"\"\"4 corners (l=along-heading, w=lateral) of a BEV box, CCW.\"\"\"\n", " c, s = np.cos(yaw), np.sin(yaw)\n", " R = np.array([[c, -s], [s, c]])\n", " local = np.array([[ l/2, w/2], [ l/2, -w/2], [-l/2, -w/2], [-l/2, w/2]])\n", " return (local @ R.T) + np.array([cx, cy])\n", "\n", "def draw_box(ax, cx, cy, w, l, yaw, color='C0', lw=2, label=None, alpha=1.0, ls='-'):\n", " cc = box_corners_2d(cx, cy, w, l, yaw)\n", " cc = np.vstack([cc, cc[0]])\n", " ax.plot(cc[:, 0], cc[:, 1], color=color, lw=lw, alpha=alpha, ls=ls)\n", " # heading tick\n", " hc, hs = np.cos(yaw), np.sin(yaw)\n", " ax.plot([cx, cx + hc*l*0.6], [cy, cy + hs*l*0.6], color=color, lw=lw, alpha=alpha)\n", " if label:\n", " ax.text(cx, cy, str(label), color=color, fontsize=9, ha='center', va='center')\n", "\n", "def new_bev_ax(title, lim=None, figsize=(6,6)):\n", " fig, ax = plt.subplots(figsize=figsize)\n", " ax.set_aspect('equal'); ax.grid(alpha=0.3)\n", " ax.set_xlabel('x (m)'); ax.set_ylabel('y (m)'); ax.set_title(title)\n", " if lim: ax.set_xlim(lim[0], lim[1]); ax.set_ylim(lim[2], lim[3])\n", " return fig, ax\n" ] }, { "cell_type": "markdown", "id": "169e0f49", "metadata": {}, "source": [ "## 1. From CLEAR-MOT to AMOTA/AMOTP\n", "Classic **CLEAR-MOT** at a single matching threshold gives:\n", "$$ \\text{MOTA} = 1 - \\frac{\\text{FP} + \\text{FN} + \\text{IDS}}{\\text{GT}}, \\qquad \\text{MOTP} = \\frac{\\sum \\text{matched localization error}}{\\#\\text{matches}}. $$\n", "MOTA folds false-positives, misses and id-switches into one recall-like score; MOTP measures how *tight* the matched boxes are.\n", "\n", "nuScenes reports the **integrated** versions that sweep the recall axis by varying the detection score threshold (this is why SimpleTrack cares so much about score handling):\n", "$$ \\text{AMOTA} = \\frac{1}{n-1}\\sum_{r\\in\\{\\frac{1}{n-1},\\dots,1\\}} \\text{MOTAR}(r), \\qquad \\text{MOTAR}(r) = \\max\\!\\Big(0,\\ 1 - \\frac{\\text{IDS}_r + \\text{FP}_r + \\text{FN}_r - (1-r)\\cdot\\text{GT}}{r\\cdot \\text{GT}}\\Big). $$\n", "$$ \\text{AMOTP} = \\frac{1}{n-1}\\sum_r \\frac{\\sum_{i} d_{i,r}}{\\text{TP}_r}, $$\n", "i.e. **AMOTA** = recall-averaged MOTA (higher is better, in $[0,1]$) and **AMOTP** = recall-averaged localization error in metres (lower is better). Matching in nuScenes tracking uses **2 m center distance** (same family as the detection mAP in the BEVFusion series, not IoU)." ] }, { "cell_type": "code", "execution_count": 3, "id": "4e4b5097", "metadata": { "execution": { "iopub.execute_input": "2026-08-02T03:39:39.175874Z", "iopub.status.busy": "2026-08-02T03:39:39.175488Z", "iopub.status.idle": "2026-08-02T03:39:39.181951Z", "shell.execute_reply": "2026-08-02T03:39:39.181533Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "clear_mot() ready.\n" ] } ], "source": [ "# A compact single-threshold CLEAR-MOT to build intuition (nuScenes integrates this over recall).\n", "def clear_mot(gt_by_frame, trk_by_frame, dist_thr=2.0):\n", " \"\"\"gt_by_frame[t] = list of (gid, x, y); trk_by_frame[t] = list of (tid, x, y).\n", " Greedy nearest matching within dist_thr; counts FP/FN/IDS + localization error.\"\"\"\n", " from scipy.optimize import linear_sum_assignment\n", " FP=FN=IDS=TP=0; err=0.0; prev={} # prev: gid -> tid from last frame\n", " for t in sorted(gt_by_frame):\n", " G=gt_by_frame[t]; Tk=trk_by_frame.get(t, [])\n", " if G and Tk:\n", " C=np.array([[np.hypot(g[1]-k[1],g[2]-k[2]) for k in Tk] for g in G])\n", " ri,ci=linear_sum_assignment(C); matched_g=set(); matched_t=set()\n", " for i,j in zip(ri,ci):\n", " if C[i,j]<=dist_thr:\n", " TP+=1; err+=C[i,j]; matched_g.add(i); matched_t.add(j)\n", " gid=G[i][0]; tid=Tk[j][0]\n", " if gid in prev and prev[gid]!=tid: IDS+=1\n", " prev[gid]=tid\n", " FN+=len(G)-len(matched_g); FP+=len(Tk)-len(matched_t)\n", " else:\n", " FN+=len(G); FP+=len(Tk)\n", " GT=sum(len(v) for v in gt_by_frame.values())\n", " MOTA=1-(FP+FN+IDS)/max(GT,1); MOTP=err/max(TP,1)\n", " return dict(MOTA=MOTA, MOTP=MOTP, FP=FP, FN=FN, IDS=IDS, TP=TP, GT=GT)\n", "print('clear_mot() ready.')\n" ] }, { "cell_type": "markdown", "id": "3a92a91c", "metadata": {}, "source": [ "## 2. Sanity check the metric on a perfect vs a switched tracker\n", "A perfect tracker (ids == gt ids) should give MOTA=1, IDS=0. A tracker that swaps two ids mid-sequence should show IDS>0 and lower MOTA." ] }, { "cell_type": "code", "execution_count": 4, "id": "276ea752", "metadata": { "execution": { "iopub.execute_input": "2026-08-02T03:39:39.184129Z", "iopub.status.busy": "2026-08-02T03:39:39.183916Z", "iopub.status.idle": "2026-08-02T03:39:39.290593Z", "shell.execute_reply": "2026-08-02T03:39:39.290042Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "perfect : {'MOTA': 1.0, 'MOTP': 0.0, 'FP': 0, 'FN': 0, 'IDS': 0, 'TP': 20, 'GT': 20}\n", "switched: {'MOTA': 0.95, 'MOTP': 0.0, 'FP': 0, 'FN': 0, 'IDS': 1, 'TP': 20, 'GT': 20}\n", "\n", "Note the single IDS and the MOTA drop for the switched tracker.\n" ] } ], "source": [ "T=10\n", "gt={t:[(0,-10+2*t,0.0),(1,10-2*t,1.0)] for t in range(T)}\n", "perfect={t:[(100,-10+2*t,0.0),(200,10-2*t,1.0)] for t in range(T)} # consistent ids\n", "switched={t:[(100,-10+2*t,0.0),(200 if t<5 else 300,10-2*t,1.0)] for t in range(T)} # id changes at t=5\n", "print('perfect :', clear_mot(gt, perfect))\n", "print('switched:', clear_mot(gt, switched))\n", "print('\\nNote the single IDS and the MOTA drop for the switched tracker.')\n" ] }, { "cell_type": "markdown", "id": "de144eff", "metadata": {}, "source": [ "## 3. Load the REAL BEVFusion detection JSON\n", "This is the output of the detection stage in the BEVFusion notebook series / `run_pipeline.sh`. Each entry is a nuScenes detection with a center `translation`, `size`, `rotation` (quaternion), `velocity`, `detection_name`, and `detection_score`. If the JSON is absent (pipeline not yet run) we fall back to the toy scene so the notebook still executes." ] }, { "cell_type": "code", "execution_count": 5, "id": "9ca868e5", "metadata": { "execution": { "iopub.execute_input": "2026-08-02T03:39:39.292544Z", "iopub.status.busy": "2026-08-02T03:39:39.292294Z", "iopub.status.idle": "2026-08-02T03:39:39.421792Z", "shell.execute_reply": "2026-08-02T03:39:39.421280Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "loaded /results/bevfusion_det/results_nusc.json\n", " sample tokens: 81 total detections: 12322\n", " example keys: ['sample_token', 'translation', 'size', 'rotation', 'velocity', 'detection_name', 'detection_score', 'attribute_name']\n", " example: pedestrian score 0.837 xyz [ 598.09 1642.11 1.9 ]\n" ] } ], "source": [ "import glob\n", "cand = [os.path.join(RESULTS_DIR,'bevfusion_det','results_nusc.json'),\n", " os.path.join(RESULTS_DIR,'bevfusion_eval','results_nusc.json')]\n", "cand += glob.glob(os.path.join(RESULTS_DIR,'**','results_nusc.json'), recursive=True)\n", "DET_JSON = next((p for p in cand if os.path.exists(p)), None)\n", "results = None\n", "if DET_JSON:\n", " results = json.load(open(DET_JSON)).get('results', {})\n", " ntok=len(results); nbox=sum(len(v) for v in results.values())\n", " print(f'loaded {DET_JSON}\\n sample tokens: {ntok} total detections: {nbox}')\n", " k=next(iter(results)); b=results[k][0]\n", " print(' example keys:', list(b.keys()))\n", " print(' example:', b.get('detection_name'), 'score', round(b.get('detection_score',0),3),\n", " 'xyz', np.round(b.get('translation'),2))\n", "else:\n", " print('No BEVFusion results_nusc.json under', RESULTS_DIR)\n", " print('Run: perception/docker/run_pipeline.sh (or the BEVFusion NB06) to produce it.')\n", " print('Falling back to the synthetic scene for this demo.')\n" ] }, { "cell_type": "markdown", "id": "828e94fc", "metadata": {}, "source": [ "## 4. Run our from-scratch SimpleTrack modules on one real scene\n", "We order a scene's frames by timestamp, convert quaternions to yaw, run per-class two-stage association + KF + life-cycle (NB02-04), and visualize the resulting tracks in BEV. Because nuScenes tracking is per-scene and needs the sample->scene ordering from the devkit, we group by the tokens present in the JSON and process whichever ordering is available; the point here is to demonstrate the tracker on real detector boxes end-to-end." ] }, { "cell_type": "code", "execution_count": 6, "id": "12247d81", "metadata": { "execution": { "iopub.execute_input": "2026-08-02T03:39:39.423753Z", "iopub.status.busy": "2026-08-02T03:39:39.423514Z", "iopub.status.idle": "2026-08-02T03:39:39.441145Z", "shell.execute_reply": "2026-08-02T03:39:39.440604Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "standalone real-data tracker ready.\n" ] } ], "source": [ "def quat_to_yaw(q):\n", " # q = [w,x,y,z]; yaw about +z\n", " w,x,y,z=q; return np.arctan2(2*(w*z+x*y), 1-2*(y*y+z*z))\n", "\n", "def dets_from_result(boxes, cls='car', score_min=0.15):\n", " out=[]\n", " for b in boxes:\n", " if b.get('detection_name')!=cls: continue\n", " s=b.get('detection_score',0.0)\n", " if s(l along heading? use size order)\n", " yaw=quat_to_yaw(b['rotation'])\n", " out.append(dict(box=(tx,ty,b['size'][0],b['size'][1],yaw), score=s))\n", " return out\n", "\n", "# Minimal standalone tracker (mirrors NB04) so this NB runs alone.\n", "import itertools\n", "def _poly(cx,cy,w,l,yaw): return box_corners_2d(cx,cy,w,l,yaw)\n", "def _giou(a,b):\n", " try:\n", " from shapely.geometry import Polygon, MultiPoint\n", " pa,pb=Polygon(_poly(*a)),Polygon(_poly(*b)); u=pa.union(pb).area\n", " iou=pa.intersection(pb).area/u if u>0 else 0.0\n", " C=MultiPoint([tuple(p) for p in np.vstack([_poly(*a),_poly(*b)])]).convex_hull.area\n", " return iou-(C-u)/C if C>0 else iou\n", " except Exception:\n", " return -np.hypot(a[0]-b[0],a[1]-b[1])/5.0\n", "def _assoc(tr,de,gate):\n", " if not tr or not de: return [],list(range(len(tr))),list(range(len(de)))\n", " C=np.array([[-_giou(t,d) for d in de] for t in tr])\n", " try:\n", " from scipy.optimize import linear_sum_assignment\n", " Cc=C.copy();Cc[Cc>gate]=1e6;ri,ci=linear_sum_assignment(Cc)\n", " m=[(int(i),int(j)) for i,j in zip(ri,ci) if C[i,j]<=gate]\n", " except Exception:\n", " m=[];ur=set();uc=set()\n", " for i,j in np.dstack(np.unravel_index(np.argsort(C,axis=None),C.shape))[0]:\n", " if i in ur or j in uc or C[i,j]>gate: continue\n", " m.append((int(i),int(j)));ur.add(i);uc.add(j)\n", " umt=[i for i in range(len(tr)) if i not in {a for a,_ in m}]\n", " umd=[j for j in range(len(de)) if j not in {b for _,b in m}]\n", " return m,umt,umd\n", "class T2:\n", " ids=itertools.count(0)\n", " def __init__(s,d,min_hits=2,max_age=3):\n", " s.id=next(T2.ids); s.box=np.array(d['box'],float); s.v=np.zeros(2)\n", " s.hits=1; s.tsu=0; s.state='tentative'; s.mh=min_hits; s.ma=max_age\n", " def predict(s): s.box[0]+=s.v[0]; s.box[1]+=s.v[1]; s.tsu+=1\n", " def update(s,d):\n", " nc=np.array(d['box'][:2]); s.v=0.5*s.v+0.5*(nc-s.box[:2]); s.box=np.array(d['box'],float)\n", " s.hits+=1; s.tsu=0\n", " if s.hits>=s.mh: s.state='confirmed'\n", " @property\n", " def dead(s): return s.tsu>s.ma or (s.state=='tentative' and s.tsu>0)\n", "def track_scene(frames, tau_high=0.4, gate=0.4, min_hits=2, max_age=3):\n", " T2.ids=itertools.count(0); tracks=[]; out=[]\n", " for D in frames:\n", " hi=[d for d in D if d['score']>=tau_high]; lo=[d for d in D if d['score']4} {'first':>6} {'last':>5} {'len':>4}\")\n", "for tid in sorted(life)[:12]:\n", " fr=life[tid]; print(f' {tid:>4} {min(fr):>6} {max(fr):>5} {len(fr):>4}')\n", "if len(life)>12: print(f' ... (+{len(life)-12} more tracks)')\n" ] }, { "cell_type": "code", "execution_count": 8, "id": "06f69c71", "metadata": { "execution": { "iopub.execute_input": "2026-08-02T03:39:47.480812Z", "iopub.status.busy": "2026-08-02T03:39:47.480485Z", "iopub.status.idle": "2026-08-02T03:39:49.202624Z", "shell.execute_reply": "2026-08-02T03:39:49.202012Z" } }, "outputs": [], "source": [ "# ---- Visualization 1: all track boxes over time in BEV (one color per id, fade = time) ----\n", "fig, ax = new_bev_ax('SimpleTrack (from-scratch) tracks over time', figsize=(10,8))\n", "cmap=plt.cm.tab20\n", "for t,fr in enumerate(tracks_per_frame):\n", " for (tid,box) in fr:\n", " draw_box(ax, box[0],box[1],box[2],box[3],box[4], color=cmap(tid%20), lw=1.2, alpha=0.25+0.75*t/max(len(frames),1))\n", "ax.scatter([0],[0],marker='*',s=200,c='k',label='ego')\n", "ax.set_title(f'{src}\\n(one color per track id; box fades light->solid with time)')\n", "ax.legend(loc='upper right')\n", "plt.tight_layout(); plt.savefig('nb05_real_tracks.png', dpi=80); plt.show()\n" ] }, { "cell_type": "code", "execution_count": 9, "id": "cd4910e9", "metadata": { "execution": { "iopub.execute_input": "2026-08-02T03:39:49.205102Z", "iopub.status.busy": "2026-08-02T03:39:49.204754Z", "iopub.status.idle": "2026-08-02T03:39:49.629263Z", "shell.execute_reply": "2026-08-02T03:39:49.628675Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "If the detector produced coherent per-frame boxes, each moving object yields one continuous line.\n" ] } ], "source": [ "# ---- Visualization 2: track center trajectories (shows ID continuity across frames) ----\n", "traj=defaultdict(list)\n", "for fr in tracks_per_frame:\n", " for (tid,box) in fr: traj[tid].append((box[0],box[1]))\n", "fig, ax = new_bev_ax('SimpleTrack trajectories (center of each track id)', figsize=(10,8))\n", "for tid,pts in traj.items():\n", " p=np.array(pts)\n", " ax.plot(p[:,0],p[:,1],'-o',ms=3,lw=1.5,color=cmap(tid%20),label=f'id {tid}' if len(pts)>=3 else None)\n", "ax.scatter([0],[0],marker='*',s=200,c='k')\n", "h,l=ax.get_legend_handles_labels()\n", "if l: ax.legend(loc='upper right', fontsize=8, ncol=2)\n", "ax.set_title(f'{src}\\ntrack-center trajectories (a single line per persistent id = stable tracking)')\n", "plt.tight_layout(); plt.savefig('nb05_real_trajectories.png', dpi=80); plt.show()\n", "print('If the detector produced coherent per-frame boxes, each moving object yields one continuous line.')\n" ] }, { "cell_type": "markdown", "id": "afd82f68", "metadata": {}, "source": [ "## 5. Using the vendored SimpleTrack + official nuScenes AMOTA (the real submission path)\n", "The from-scratch tracker above makes every step transparent. For an official score you run the **vendored SimpleTrack** on the full detection JSON and evaluate with the **nuScenes tracking devkit**, which computes the integrated AMOTA/AMOTP defined in §1. Inside the container:\n", "\n", "```bash\n", "# 1) detections already produced by the BEVFusion pipeline -> /results/bevfusion_det/results_nusc.json\n", "# 2) run the vendored tracker (adjust to its CLI):\n", "python /workspace/perception/simpletrack/tools/main_nuscenes.py \\\n", " --result_path /results/bevfusion_det/results_nusc.json \\\n", " --data_folder /data/nuscenes \\\n", " --output_folder /results/simpletrack\n", "# 3) score with the nuScenes tracking eval:\n", "python -m nuscenes.eval.tracking.evaluate \\\n", " /results/simpletrack/tracking_result.json \\\n", " --output_dir /results/simpletrack_eval \\\n", " --eval_set mini_val --dataroot /data/nuscenes --version v1.0-mini\n", "```\n", "\n", "The probe below reports whether the vendored source + devkit are importable in this container so you know the official path is available (it degrades gracefully to the from-scratch demo otherwise)." ] }, { "cell_type": "code", "execution_count": 10, "id": "b8bedf2b", "metadata": { "execution": { "iopub.execute_input": "2026-08-02T03:39:49.631318Z", "iopub.status.busy": "2026-08-02T03:39:49.631054Z", "iopub.status.idle": "2026-08-02T03:39:49.794385Z", "shell.execute_reply": "2026-08-02T03:39:49.793824Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ok] numpy/scipy/shapely\n", "[ok] nuscenes-devkit\n", "[ok] nuscenes tracking eval\n", "[ok] vendored simpletrack\n", "\n", "Either way you now understand every module SimpleTrack uses: KF (NB02), GIoU+Hungarian (NB03),\n", "two-stage association + life-cycle/re-ID (NB04), and AMOTA/AMOTP (this NB).\n" ] } ], "source": [ "def probe(name, fn):\n", " try: fn(); print(f'[ok] {name}')\n", " except Exception as e: print(f'[missing] {name}: {type(e).__name__}: {e}')\n", "probe('numpy/scipy/shapely', lambda: (__import__('scipy'), __import__('shapely')))\n", "probe('nuscenes-devkit', lambda: __import__('nuscenes'))\n", "probe('nuscenes tracking eval', lambda: __import__('nuscenes.eval.tracking.evaluate', fromlist=['x']))\n", "probe('vendored simpletrack', lambda: (os.path.isdir(SIMPLETRACK_SRC) and __import__('mot_3d')) or (_ for _ in ()).throw(ImportError('mot_3d not importable')))\n", "print('\\nEither way you now understand every module SimpleTrack uses: KF (NB02), GIoU+Hungarian (NB03),')\n", "print('two-stage association + life-cycle/re-ID (NB04), and AMOTA/AMOTP (this NB).')\n" ] }, { "cell_type": "markdown", "id": "62ce98b0", "metadata": {}, "source": [ "## 6. Recap of the SimpleTrack series\n", "* **NB01** — MOT problem, notation, and the 4-module tracking-by-detection pipeline.\n", "* **NB02** — Kalman filter derivation, a runnable 3D box KF, CV vs CA, yaw wrap-around.\n", "* **NB03** — L2 / IoU / **GIoU** affinities (and why SimpleTrack picks GIoU), Hungarian matching, gating.\n", "* **NB04** — SimpleTrack's **two-stage** association, count-based **life-cycle**, id assignment and motion-based **re-identification** across occlusions.\n", "* **NB05** — **AMOTA/AMOTP** metrics and running the modules on the **real BEVFusion JSON**.\n", "\n", "Together with the BEVFusion series next door, you have the complete **detect -> track** perception stack, code-anchored and runnable in the single `bevfusion:cu113` container via `perception/docker/run_notebooks.sh`." ] } ], "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 5 }