tianma Cursor commited on
Commit
5c11913
·
1 Parent(s): 223873d

Add task-metric eval path and Cascade Mask R-CNN + Swin-B for det/instance.

Browse files

Wire --with-metrics for TAIC/C-TAIC, point detection and instance to the official Cascade Mask R-CNN Swin-B zoo (out_channels=128), and document task-network setup for server runs.

Co-authored-by: Cursor <cursoragent@cursor.com>

README.md CHANGED
@@ -5,36 +5,46 @@ Official codebase for the paper **FlexICM: A Flexible Image Coding for Machines
5
  Built on the **TIC (Transformer-based Image Compression)** base codec, this repository implements:
6
 
7
  - **TAIC (Base Layer)**: five single-task codecs that decode task intermediate features `h` **without** full image reconstruction
8
- - **C-TAIC (Extension Layer)**: three multi-task scenarios that condition on the base-layer latent \(\hat{y}_b\) via cross-attention
9
 
10
  ## Five Tasks and Three Scenarios
11
 
12
  ### TAIC (five task codecs)
13
 
14
- | Task | Teacher / Task Network | Feature Alignment | Metric |
15
- |------|------------------------|-------------------|--------|
16
- | Object Detection | Faster R-CNN + **Swin-B** | FPN `P2..P6` (Eq. 2) | mAP-bbox |
17
- | Semantic Segmentation | UPerNet + **Swin-B** | FPN `P2..P6` | mIoU |
18
- | Instance Segmentation | Mask R-CNN + **Swin-B** | FPN `P2..P6` | mAP-mask |
19
- | Panoptic Segmentation | MaskFormer + **Swin-B** | Stages `F1..F4` (Eq. 3) | PQ |
20
- | Pose Estimation | **HigherHRNet** | Stages `F1..F4` | mAP-OKS |
 
 
 
 
21
 
22
  ### C-TAIC (three scenarios)
23
 
24
- | Scenario | Base Layer | Extension Layer |
25
- |----------|------------|-----------------|
26
- | **s1** | Object Detection | Instance Segmentation |
27
- | **s2** | Semantic Segmentation | Panoptic Segmentation |
28
- | **s3** | Object Detection | Pose Estimation |
 
 
29
 
30
  ---
31
 
 
 
32
  ## Environment Setup
33
 
34
  > **Important:** Codec training **requires** task networks (teachers) to be available.
35
- > The loss \(D\) is computed from frozen teacher features, so you cannot train TAIC / C-TAIC
36
  > with only the codec packages. Install the teacher stack in **Task networks (teachers)** before the first training run.
37
 
 
 
38
  ### Recommended environment
39
 
40
  - Ubuntu / RHEL, **CUDA 11.7+**, single **NVIDIA A100** (paper setting)
@@ -48,13 +58,19 @@ pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118
48
  pip install -r requirements.txt
49
  ```
50
 
 
 
51
  ### Core codec dependencies
52
 
53
- | Package | Role |
54
- |---------|------|
55
- | `compressai` | EntropyBottleneck / GaussianConditional / conv-deconv |
56
- | `timm` | **Required** Swin-B teacher backbone for feature alignment |
57
- | `PyYAML` | Training configs |
 
 
 
 
58
 
59
  ### Task networks (teachers) — **required before training**
60
 
@@ -62,22 +78,25 @@ Teachers are already implemented in `flexicm/tasks/` and are constructed automat
62
  `scripts/train_taic.py` / `scripts/train_ctaic.py` via `build_teacher(...)`.
63
  You still must install their runtime dependencies and allow pretrained weights to download.
64
 
65
- | Task | Teacher used in training | What you need installed |
66
- |------|--------------------------|-------------------------|
67
- | Detection / Instance / Semantic / Panoptic | Swin-B backbone (+ FPN or stages) via `timm` | `timm` (from `requirements.txt`); first run downloads ImageNet-pretrained Swin-B |
68
- | Pose | HigherHRNet-style HRNet stem (original HRNet, not Swin) | Implemented in-repo; no extra package beyond PyTorch |
 
 
 
69
 
70
  Checklist before training:
71
 
72
  1. `pip install -r requirements.txt` (includes `timm`)
73
- 2. Machine can reach the internet **or** you have cached `timm` Swin-B weights (for the four Swin tasks)
74
  3. Verify teachers import cleanly:
75
 
76
  ```bash
77
  python -c "from flexicm.tasks import build_teacher; build_teacher('detection'); print('teachers ok')"
78
  ```
79
 
80
- Without a working teacher, training will fail when computing the feature-alignment term \(D\).
81
 
82
  ### Task heads for metric evaluation
83
 
@@ -87,12 +106,13 @@ To evaluate paper metrics (mAP / mIoU / PQ / OKS) with full task heads, also ins
87
  pip install -U openmim
88
  mim install mmengine mmcv
89
  mim install mmdet mmsegmentation mmpose
90
- # or Detectron2 (alternative for detection / instance evaluation)
91
  ```
92
 
93
- Recommended official weights (same model families as the paper):
 
 
94
 
95
- - **Faster / Mask R-CNN + Swin-B**: MMDetection Model Zoo
96
  - **UPerNet + Swin-B**: MMSegmentation Model Zoo
97
  - **MaskFormer + Swin-B**: MMDetection / Mask2Former
98
  - **HigherHRNet**: MMPose Model Zoo (**HRNet backbone**)
@@ -101,6 +121,8 @@ These full heads are **not** required to start codec training; they are for fina
101
 
102
  ---
103
 
 
 
104
  ## Repository Layout
105
 
106
  ```
@@ -139,8 +161,12 @@ Eval / codec-test configs: `configs/eval/`.
139
 
140
  ---
141
 
 
 
142
  ## Dataset Preparation
143
 
 
 
144
  ### COCO-2017 (detection / instance / semantic / panoptic)
145
 
146
  ```text
@@ -175,6 +201,8 @@ Set in the corresponding YAML:
175
  dataset_path: "/data/coco2017"
176
  ```
177
 
 
 
178
  ### COCO-WholeBody (pose estimation)
179
 
180
  Pose uses the same COCO `train2017/val2017` images plus WholeBody keypoint annotations:
@@ -201,16 +229,20 @@ Edit points: `flexicm/data/datasets.py`, `flexicm/tasks/swin_teacher.py`, `flexi
201
 
202
  ---
203
 
 
 
204
  ## Base Codec (TIC) Checkpoints
205
 
206
  The paper uses the same TIC pretrained weights as AdaptiveICMH / TransTIC:
207
 
208
- | Quality | λ (paper) | Checkpoint |
209
- |:-------:|:---------:|------------|
210
- | 1 | 0.0035 | [base_codec_1](https://github.com/NYCU-MAPL/TransTIC/releases/download/v1.0/base_codec_1.pth.tar) |
211
- | 2 | 0.0067 | [base_codec_2](https://github.com/NYCU-MAPL/TransTIC/releases/download/v1.0/base_codec_2.pth.tar) |
212
- | 3 | 0.0130 | [base_codec_3](https://github.com/NYCU-MAPL/TransTIC/releases/download/v1.0/base_codec_3.pth.tar) |
213
- | 4 | 0.0250 | [base_codec_4](https://github.com/NYCU-MAPL/TransTIC/releases/download/v1.0/base_codec_4.pth.tar) |
 
 
214
 
215
  ```bash
216
  bash scripts/download_base_codecs.sh
@@ -231,9 +263,10 @@ Trained TAIC / C-TAIC weights for eval should be placed under `checkpoints/taic/
231
  `checkpoints/ctaic/` (see `checkpoints/README.md`). Until then, each quality folder
232
  contains a `PLACEHOLDER` file.
233
 
234
-
235
  ---
236
 
 
 
237
  ## Training
238
 
239
  Paper settings:
@@ -245,6 +278,8 @@ Paper settings:
245
 
246
  > If GPU memory is insufficient, reduce `batch_size` (optionally use gradient accumulation to approximate the paper effective batch).
247
 
 
 
248
  ### Train five TAIC models
249
 
250
  ```bash
@@ -260,7 +295,7 @@ Trainable modules: **encoder SFMA + Task Connector**; TIC trunk is frozen.
260
 
261
  ### Train three C-TAIC scenarios
262
 
263
- Requires a trained **base TAIC** checkpoint (to provide \(\hat{y}_b\)) and Stage-1 weights for the extension task.
264
 
265
  ```bash
266
  # ---- s1: det → instance ----
@@ -278,10 +313,12 @@ python scripts/train_ctaic.py -c configs/ctaic/s3_det_pose.yaml --stage 2
278
 
279
  Stage meanings:
280
 
281
- | Stage | Mode | Trainable modules | `ŷ_b` |
282
- |:-----:|------|-------------------|-------|
283
- | 1 | TAIC mode | SFMA + Task Connector | not used |
284
- | 2 | C-TAIC mode | Prompt Generator + Condition Generator | from frozen base TAIC AD output |
 
 
285
 
286
  Check these config fields:
287
 
@@ -293,64 +330,113 @@ stage1_checkpoint: # Stage-1 result loaded in Stage 2
293
 
294
  ---
295
 
 
 
296
  ## Codec Test
297
 
 
298
 
299
- For C-TAIC, reported `bpp` is **extension-layer only** (base-layer rate is excluded), matching the paper.
300
 
301
- ### Prepare checkpoints
 
 
 
 
 
302
 
303
- 1. Train models (or copy trained weights) into the `checkpoints/` tree — Download checkpoints.
304
- 2. Edit `dataset_path` / `gpu_id` in `configs/eval/*.yaml`
305
 
306
- ### Test TAIC (five tasks)
 
 
 
 
 
 
307
 
308
  ```bash
309
  python scripts/eval_taic.py -c configs/eval/taic_detection.yaml
310
- python scripts/eval_taic.py -c configs/eval/taic_semantic.yaml
311
- python scripts/eval_taic.py -c configs/eval/taic_instance.yaml
312
- python scripts/eval_taic.py -c configs/eval/taic_panoptic.yaml
313
- python scripts/eval_taic.py -c configs/eval/taic_pose.yaml
314
-
315
- # optional: also measure actual entropy-coded bitstream bpp
316
  python scripts/eval_taic.py -c configs/eval/taic_detection.yaml --actual-bpp
317
-
318
- # optional: smoke test on a few batches
319
  python scripts/eval_taic.py -c configs/eval/taic_detection.yaml --max-batches 10
 
 
 
320
  ```
321
 
322
- Results JSON is written under `logs/eval_taic/<task>/<quality>/`.
 
 
 
 
 
 
 
 
323
 
324
- ### Test C-TAIC (three scenarios)
 
 
 
 
 
 
 
 
 
 
 
325
 
326
  ```bash
327
- python scripts/eval_ctaic.py -c configs/eval/ctaic_s1.yaml
328
- python scripts/eval_ctaic.py -c configs/eval/ctaic_s2.yaml
329
- python scripts/eval_ctaic.py -c configs/eval/ctaic_s3.yaml
 
 
 
330
 
331
- # disable base-layer conditioning (TAIC-mode / graceful degradation)
332
- python scripts/eval_ctaic.py -c configs/eval/ctaic_s1.yaml --no-condition
333
 
334
- python scripts/eval_ctaic.py -c configs/eval/ctaic_s1.yaml --actual-bpp
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  ```
336
 
337
- Results JSON is written under `logs/eval_ctaic/<scenario>/<quality>/`.
338
 
339
- > Task rate–accuracy curves (mAP / mIoU / PQ / OKS vs bpp) are **not** included yet.
 
 
 
 
340
 
341
- ---
342
 
343
- ## Code Map to the Paper
344
 
345
- | Paper component | Code location |
346
- |-----------------|---------------|
347
- | SFMA | `flexicm/models/sfma.py` |
348
- | Task Connector | `flexicm/models/task_connector.py` |
349
- | TAIC | `flexicm/models/taic.py` |
350
- | C-TAIC + two-stage freeze | `flexicm/models/ctaic.py` |
351
- | Prompt / Mask / Cd | `flexicm/models/conditional.py` |
352
- | Cross-attention (Q from features; K/V include prompts) | `flexicm/models/cross_attention.py` |
353
- | \(R+\lambda D\) | `flexicm/tasks/losses.py` |
354
- | Five teachers | `flexicm/tasks/__init__.py` |
 
 
 
 
 
355
 
 
 
 
356
 
 
5
  Built on the **TIC (Transformer-based Image Compression)** base codec, this repository implements:
6
 
7
  - **TAIC (Base Layer)**: five single-task codecs that decode task intermediate features `h` **without** full image reconstruction
8
+ - **C-TAIC (Extension Layer)**: three multi-task scenarios that condition on the base-layer latent \hat{y}_b via cross-attention
9
 
10
  ## Five Tasks and Three Scenarios
11
 
12
  ### TAIC (five task codecs)
13
 
14
+
15
+ | Task | Teacher / Task Network | Feature Alignment | Metric |
16
+ | --------------------- | ------------------------------- | ----------------------- | -------- |
17
+ | Object Detection | Faster R-CNN + **Swin-B** | FPN `P2..P6` (Eq. 2) | mAP-bbox |
18
+ | Semantic Segmentation | UPerNet + **Swin-B** | FPN `P2..P6` | mIoU |
19
+ | Instance Segmentation | Cascade Mask R-CNN + **Swin-B** | FPN `P2..P6` | mAP-mask |
20
+ | Panoptic Segmentation | MaskFormer + **Swin-B** | Stages `F1..F4` (Eq. 3) | PQ |
21
+ | Pose Estimation | **HigherHRNet** | Stages `F1..F4` | mAP-OKS |
22
+
23
+
24
+
25
 
26
  ### C-TAIC (three scenarios)
27
 
28
+
29
+ | Scenario | Base Layer | Extension Layer |
30
+ | -------- | --------------------- | --------------------- |
31
+ | **s1** | Object Detection | Instance Segmentation |
32
+ | **s2** | Semantic Segmentation | Panoptic Segmentation |
33
+ | **s3** | Object Detection | Pose Estimation |
34
+
35
 
36
  ---
37
 
38
+
39
+
40
  ## Environment Setup
41
 
42
  > **Important:** Codec training **requires** task networks (teachers) to be available.
43
+ > The loss D is computed from frozen teacher features, so you cannot train TAIC / C-TAIC
44
  > with only the codec packages. Install the teacher stack in **Task networks (teachers)** before the first training run.
45
 
46
+
47
+
48
  ### Recommended environment
49
 
50
  - Ubuntu / RHEL, **CUDA 11.7+**, single **NVIDIA A100** (paper setting)
 
58
  pip install -r requirements.txt
59
  ```
60
 
61
+
62
+
63
  ### Core codec dependencies
64
 
65
+
66
+ | Package | Role |
67
+ | ------------ | ---------------------------------------------------------- |
68
+ | `compressai` | EntropyBottleneck / GaussianConditional / conv-deconv |
69
+ | `timm` | **Required** Swin-B teacher backbone for feature alignment |
70
+ | `PyYAML` | Training configs |
71
+
72
+
73
+
74
 
75
  ### Task networks (teachers) — **required before training**
76
 
 
78
  `scripts/train_taic.py` / `scripts/train_ctaic.py` via `build_teacher(...)`.
79
  You still must install their runtime dependencies and allow pretrained weights to download.
80
 
81
+
82
+ | Task | Teacher used in training | What you need installed |
83
+ | -------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------- |
84
+ | Detection / Instance | Swin-B (`timm`) for FPN alignment; metric head = Cascade Mask R-CNN + Swin-B | `timm`; ImageNet Swin-B on first run |
85
+ | Semantic / Panoptic | Swin-B (`timm`) | `timm`; ImageNet Swin-B on first run |
86
+ | Pose | HigherHRNet-style HRNet stem (original HRNet, not Swin) | Implemented in-repo; no extra package beyond PyTorch |
87
+
88
 
89
  Checklist before training:
90
 
91
  1. `pip install -r requirements.txt` (includes `timm`)
92
+ 2. Machine can reach the internet **or** you have cached `timm` Swin weights
93
  3. Verify teachers import cleanly:
94
 
95
  ```bash
96
  python -c "from flexicm.tasks import build_teacher; build_teacher('detection'); print('teachers ok')"
97
  ```
98
 
99
+ Without a working teacher, training will fail when computing the feature-alignment term D.
100
 
101
  ### Task heads for metric evaluation
102
 
 
106
  pip install -U openmim
107
  mim install mmengine mmcv
108
  mim install mmdet mmsegmentation mmpose
 
109
  ```
110
 
111
+ Official detection / instance weights from
112
+ [Swin-Transformer-Object-Detection](https://github.com/SwinTransformer/Swin-Transformer-Object-Detection)
113
+ (see `configs/task_networks/README.md`):
114
 
115
+ - **Cascade Mask R-CNN + Swin-B** (detection mAP-bbox **and** instance mAP-mask; same weights)
116
  - **UPerNet + Swin-B**: MMSegmentation Model Zoo
117
  - **MaskFormer + Swin-B**: MMDetection / Mask2Former
118
  - **HigherHRNet**: MMPose Model Zoo (**HRNet backbone**)
 
121
 
122
  ---
123
 
124
+
125
+
126
  ## Repository Layout
127
 
128
  ```
 
161
 
162
  ---
163
 
164
+
165
+
166
  ## Dataset Preparation
167
 
168
+
169
+
170
  ### COCO-2017 (detection / instance / semantic / panoptic)
171
 
172
  ```text
 
201
  dataset_path: "/data/coco2017"
202
  ```
203
 
204
+
205
+
206
  ### COCO-WholeBody (pose estimation)
207
 
208
  Pose uses the same COCO `train2017/val2017` images plus WholeBody keypoint annotations:
 
229
 
230
  ---
231
 
232
+
233
+
234
  ## Base Codec (TIC) Checkpoints
235
 
236
  The paper uses the same TIC pretrained weights as AdaptiveICMH / TransTIC:
237
 
238
+
239
+ | Quality | λ (paper) | Checkpoint |
240
+ | ------- | --------- | ------------------------------------------------------------------------------------------------- |
241
+ | 1 | 0.0035 | [base_codec_1](https://github.com/NYCU-MAPL/TransTIC/releases/download/v1.0/base_codec_1.pth.tar) |
242
+ | 2 | 0.0067 | [base_codec_2](https://github.com/NYCU-MAPL/TransTIC/releases/download/v1.0/base_codec_2.pth.tar) |
243
+ | 3 | 0.0130 | [base_codec_3](https://github.com/NYCU-MAPL/TransTIC/releases/download/v1.0/base_codec_3.pth.tar) |
244
+ | 4 | 0.0250 | [base_codec_4](https://github.com/NYCU-MAPL/TransTIC/releases/download/v1.0/base_codec_4.pth.tar) |
245
+
246
 
247
  ```bash
248
  bash scripts/download_base_codecs.sh
 
263
  `checkpoints/ctaic/` (see `checkpoints/README.md`). Until then, each quality folder
264
  contains a `PLACEHOLDER` file.
265
 
 
266
  ---
267
 
268
+
269
+
270
  ## Training
271
 
272
  Paper settings:
 
278
 
279
  > If GPU memory is insufficient, reduce `batch_size` (optionally use gradient accumulation to approximate the paper effective batch).
280
 
281
+
282
+
283
  ### Train five TAIC models
284
 
285
  ```bash
 
295
 
296
  ### Train three C-TAIC scenarios
297
 
298
+ Requires a trained **base TAIC** checkpoint (to provide \hat{y}_b) and Stage-1 weights for the extension task.
299
 
300
  ```bash
301
  # ---- s1: det → instance ----
 
313
 
314
  Stage meanings:
315
 
316
+
317
+ | Stage | Mode | Trainable modules | `ŷ_b` |
318
+ | ----- | ----------- | -------------------------------------- | ------------------------------- |
319
+ | 1 | TAIC mode | SFMA + Task Connector | not used |
320
+ | 2 | C-TAIC mode | Prompt Generator + Condition Generator | from frozen base TAIC AD output |
321
+
322
 
323
  Check these config fields:
324
 
 
330
 
331
  ---
332
 
333
+
334
+
335
  ## Codec Test
336
 
337
+ Codec test measures **compression statistics**:
338
 
 
339
 
340
+ | Metric | Meaning |
341
+ | ------------ | ----------------------------------------------------------------- |
342
+ | `bpp` | Likelihood bitrate R |
343
+ | `distortion` | Feature alignment D (Eq. 2 or Eq. 3) |
344
+ | `loss` | R + \lambda D |
345
+ | `actual_bpp` | Optional: real bitstream size after `compress()` / `decompress()` |
346
 
 
 
347
 
348
+ For C-TAIC, reported `bpp` is **extension-layer only** (base-layer rate is excluded), matching the paper.
349
+
350
+ ### Prepare codec checkpoints
351
+
352
+ 1. Copy trained weights into `checkpoints/taic/` or `checkpoints/ctaic/` (see `checkpoints/README.md`)
353
+ 2. Remove the local `PLACEHOLDER` once `checkpoint_best_loss.pth.tar` is present
354
+ 3. Edit `dataset_path` / `gpu_id` in `configs/eval/*.yaml`
355
 
356
  ```bash
357
  python scripts/eval_taic.py -c configs/eval/taic_detection.yaml
 
 
 
 
 
 
358
  python scripts/eval_taic.py -c configs/eval/taic_detection.yaml --actual-bpp
 
 
359
  python scripts/eval_taic.py -c configs/eval/taic_detection.yaml --max-batches 10
360
+
361
+ python scripts/eval_ctaic.py -c configs/eval/ctaic_s1.yaml
362
+ python scripts/eval_ctaic.py -c configs/eval/ctaic_s1.yaml --no-condition
363
  ```
364
 
365
+ ---
366
+
367
+
368
+
369
+ ## Task-network metric evaluation
370
+
371
+ To reproduce paper rate–accuracy numbers you must **also** load the official pretrained
372
+ **task networks** and run metrics on COCO val:
373
+
374
 
375
+ | Task | Task network | Metric |
376
+ | --------- | ---------------------------- | -------- |
377
+ | Detection | Cascade Mask R-CNN + Swin-B | mAP-bbox |
378
+ | Instance | Cascade Mask R-CNN + Swin-B | mAP-mask |
379
+ | Semantic | UPerNet + Swin-B | mIoU |
380
+ | Panoptic | MaskFormer + Swin-B | PQ |
381
+ | Pose | HigherHRNet (HRNet backbone) | mAP-OKS |
382
+
383
+
384
+ Pipeline: `image → codec → h → truncated task net (from Stage2 / FPN) → metric`.
385
+
386
+ ### Install metric dependencies
387
 
388
  ```bash
389
+ pip install pycocotools
390
+ pip install -U openmim
391
+ mim install mmengine mmcv mmdet mmsegmentation mmpose
392
+ # optional for PQ:
393
+ # pip install git+https://github.com/cocodataset/panopticapi.git
394
+ ```
395
 
 
 
396
 
397
+
398
+ ### Prepare task-network configs & checkpoints
399
+
400
+ 1. Put / symlink real OpenMMLab configs under `configs/task_networks/`
401
+ (see `configs/task_networks/README.md`; current `*.py` files are stubs)
402
+ 2. Download official weights to:
403
+
404
+ ```text
405
+ checkpoints/task_networks/
406
+ ├── detection/model.pth
407
+ ├── instance/model.pth
408
+ ├── semantic/model.pth
409
+ ├── panoptic/model.pth
410
+ └── pose/model.pth
411
  ```
412
 
413
+ 1. Set in each `configs/eval/*.yaml`:
414
 
415
+ ```yaml
416
+ task_config: "./configs/task_networks/<real_config>.py"
417
+ task_checkpoint: "./checkpoints/task_networks/<task>/model.pth"
418
+ ann_file: "annotations/instances_val2017.json"
419
+ ```
420
 
 
421
 
 
422
 
423
+ ### Run codec + metrics
424
+
425
+ ```bash
426
+ python scripts/eval_taic.py -c configs/eval/taic_detection.yaml --with-metrics
427
+ python scripts/eval_taic.py -c configs/eval/taic_instance.yaml --with-metrics
428
+ python scripts/eval_taic.py -c configs/eval/taic_semantic.yaml --with-metrics
429
+ python scripts/eval_taic.py -c configs/eval/taic_panoptic.yaml --with-metrics
430
+ python scripts/eval_taic.py -c configs/eval/taic_pose.yaml --with-metrics
431
+
432
+ python scripts/eval_ctaic.py -c configs/eval/ctaic_s1.yaml --with-metrics
433
+ python scripts/eval_ctaic.py -c configs/eval/ctaic_s2.yaml --with-metrics
434
+ python scripts/eval_ctaic.py -c configs/eval/ctaic_s3.yaml --with-metrics
435
+ ```
436
+
437
+ JSON results (codec + task metrics) are written under `logs/eval_taic/` or `logs/eval_ctaic/`.
438
 
439
+ > Detection / instance metric paths are the most complete (COCO bbox via pycocotools).
440
+ > Semantic mIoU needs a GT label loader; panoptic PQ needs `panopticapi` + GT folders;
441
+ > pose-from-`h` may need a HigherHRNet stem hook for your exact MMPose version.
442
 
checkpoints/README.md CHANGED
@@ -70,3 +70,23 @@ python scripts/eval_ctaic.py -c configs/eval/ctaic_s1.yaml
70
 
71
  Scripts refuse to run if a `PLACEHOLDER` file is still present or if the `.pth.tar` is missing.
72
  Task rate–accuracy metrics are not implemented in these scripts yet.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
  Scripts refuse to run if a `PLACEHOLDER` file is still present or if the `.pth.tar` is missing.
72
  Task rate–accuracy metrics are not implemented in these scripts yet.
73
+
74
+ ## Task networks (for metric evaluation)
75
+
76
+ ```text
77
+ checkpoints/task_networks/
78
+ ├── detection/model.pth
79
+ ├── instance/model.pth
80
+ ├── semantic/model.pth
81
+ ├── panoptic/model.pth
82
+ └── pose/model.pth
83
+ ```
84
+
85
+ These are **official pretrained task networks** (not codec weights).
86
+ Required when running:
87
+
88
+ ```bash
89
+ python scripts/eval_taic.py -c configs/eval/taic_detection.yaml --with-metrics
90
+ ```
91
+
92
+ See `configs/task_networks/README.md` for config/checkpoint pairing.
checkpoints/task_networks/detection/PLACEHOLDER ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ PLACEHOLDER: put the official pretrained task-network weight here as model.pth
2
+ Also set task_config in configs/eval/*.yaml to the matching OpenMMLab config.
checkpoints/task_networks/instance/PLACEHOLDER ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ PLACEHOLDER: put the official pretrained task-network weight here as model.pth
2
+ Also set task_config in configs/eval/*.yaml to the matching OpenMMLab config.
checkpoints/task_networks/panoptic/PLACEHOLDER ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ PLACEHOLDER: put the official pretrained task-network weight here as model.pth
2
+ Also set task_config in configs/eval/*.yaml to the matching OpenMMLab config.
checkpoints/task_networks/pose/PLACEHOLDER ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ PLACEHOLDER: put the official pretrained task-network weight here as model.pth
2
+ Also set task_config in configs/eval/*.yaml to the matching OpenMMLab config.
checkpoints/task_networks/semantic/PLACEHOLDER ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ PLACEHOLDER: put the official pretrained task-network weight here as model.pth
2
+ Also set task_config in configs/eval/*.yaml to the matching OpenMMLab config.
configs/ctaic/s1_det_instance.yaml CHANGED
@@ -22,3 +22,5 @@ cuda: true
22
  save: true
23
  seed: 42
24
  pretrained_backbone: true
 
 
 
22
  save: true
23
  seed: 42
24
  pretrained_backbone: true
25
+ out_channels: 128 # extension = Cascade Mask R-CNN + Swin-B
26
+ align_mode: "fpn"
configs/eval/ctaic_s1.yaml CHANGED
@@ -1,12 +1,14 @@
1
- # Codec test config for C-TAIC scenario s1 (det -> instance)
2
  scenario: "s1"
3
  dataset_path: "/data/coco2017"
4
  split: "val2017"
 
5
  quality_level: 1
6
  lmbda: 0.0035
7
  checkpoint: "./checkpoints/ctaic/s1_det_instance/stage2/1/checkpoint_best_loss.pth.tar"
8
  base_taic_checkpoint: "./checkpoints/taic/detection/1/checkpoint_best_loss.pth.tar"
9
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
 
 
10
  gpu_id: 0
11
  cuda: true
12
  test_batch_size: 1
 
 
1
  scenario: "s1"
2
  dataset_path: "/data/coco2017"
3
  split: "val2017"
4
+ ann_file: "annotations/instances_val2017.json"
5
  quality_level: 1
6
  lmbda: 0.0035
7
  checkpoint: "./checkpoints/ctaic/s1_det_instance/stage2/1/checkpoint_best_loss.pth.tar"
8
  base_taic_checkpoint: "./checkpoints/taic/detection/1/checkpoint_best_loss.pth.tar"
9
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
10
+ task_config: "./configs/task_networks/cascade_mask_rcnn_swin_base_coco.py"
11
+ task_checkpoint: "./checkpoints/task_networks/instance/model.pth"
12
  gpu_id: 0
13
  cuda: true
14
  test_batch_size: 1
configs/eval/ctaic_s2.yaml CHANGED
@@ -1,11 +1,15 @@
1
  scenario: "s2"
2
  dataset_path: "/data/coco2017"
3
  split: "val2017"
 
4
  quality_level: 1
5
  lmbda: 0.0035
6
  checkpoint: "./checkpoints/ctaic/s2_sem_panoptic/stage2/1/checkpoint_best_loss.pth.tar"
7
  base_taic_checkpoint: "./checkpoints/taic/semantic/1/checkpoint_best_loss.pth.tar"
8
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
 
 
 
9
  gpu_id: 0
10
  cuda: true
11
  test_batch_size: 1
 
1
  scenario: "s2"
2
  dataset_path: "/data/coco2017"
3
  split: "val2017"
4
+ ann_file: "annotations/panoptic_val2017.json"
5
  quality_level: 1
6
  lmbda: 0.0035
7
  checkpoint: "./checkpoints/ctaic/s2_sem_panoptic/stage2/1/checkpoint_best_loss.pth.tar"
8
  base_taic_checkpoint: "./checkpoints/taic/semantic/1/checkpoint_best_loss.pth.tar"
9
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
10
+ task_config: "./configs/task_networks/maskformer_swin-b_coco.py"
11
+ task_checkpoint: "./checkpoints/task_networks/panoptic/model.pth"
12
+ panoptic_gt_folder: "/data/coco2017/annotations/panoptic_val2017"
13
  gpu_id: 0
14
  cuda: true
15
  test_batch_size: 1
configs/eval/ctaic_s3.yaml CHANGED
@@ -1,11 +1,14 @@
1
  scenario: "s3"
2
  dataset_path: "/data/coco2017"
3
  split: "val2017"
 
4
  quality_level: 1
5
  lmbda: 0.0035
6
  checkpoint: "./checkpoints/ctaic/s3_det_pose/stage2/1/checkpoint_best_loss.pth.tar"
7
  base_taic_checkpoint: "./checkpoints/taic/detection/1/checkpoint_best_loss.pth.tar"
8
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
 
 
9
  gpu_id: 0
10
  cuda: true
11
  test_batch_size: 1
 
1
  scenario: "s3"
2
  dataset_path: "/data/coco2017"
3
  split: "val2017"
4
+ ann_file: "annotations/coco_wholebody_val_v1.0.json"
5
  quality_level: 1
6
  lmbda: 0.0035
7
  checkpoint: "./checkpoints/ctaic/s3_det_pose/stage2/1/checkpoint_best_loss.pth.tar"
8
  base_taic_checkpoint: "./checkpoints/taic/detection/1/checkpoint_best_loss.pth.tar"
9
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
10
+ task_config: "./configs/task_networks/higherhrnet_w32_coco_wholebody.py"
11
+ task_checkpoint: "./checkpoints/task_networks/pose/model.pth"
12
  gpu_id: 0
13
  cuda: true
14
  test_batch_size: 1
configs/eval/taic_detection.yaml CHANGED
@@ -1,12 +1,15 @@
1
- # Codec test config for TAIC / detection
2
- # Place real weights at checkpoint path (replace PLACEHOLDER).
3
  task: "detection"
4
  dataset_path: "/data/coco2017"
5
  split: "val2017"
 
6
  quality_level: 1
7
  lmbda: 0.0035
8
  checkpoint: "./checkpoints/taic/detection/1/checkpoint_best_loss.pth.tar"
9
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
 
 
 
10
  gpu_id: 0
11
  cuda: true
12
  test_batch_size: 1
 
1
+ # TAIC eval: codec test + optional --with-metrics
 
2
  task: "detection"
3
  dataset_path: "/data/coco2017"
4
  split: "val2017"
5
+ ann_file: "annotations/instances_val2017.json"
6
  quality_level: 1
7
  lmbda: 0.0035
8
  checkpoint: "./checkpoints/taic/detection/1/checkpoint_best_loss.pth.tar"
9
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
10
+ # official task network (required for --with-metrics)
11
+ task_config: "./configs/task_networks/cascade_mask_rcnn_swin_base_coco.py"
12
+ task_checkpoint: "./checkpoints/task_networks/detection/model.pth"
13
  gpu_id: 0
14
  cuda: true
15
  test_batch_size: 1
configs/eval/taic_instance.yaml CHANGED
@@ -1,12 +1,15 @@
1
- # Codec test config for TAIC / instance
2
- # Place real weights at checkpoint path (replace PLACEHOLDER).
3
  task: "instance"
4
  dataset_path: "/data/coco2017"
5
  split: "val2017"
 
6
  quality_level: 1
7
  lmbda: 0.0035
8
  checkpoint: "./checkpoints/taic/instance/1/checkpoint_best_loss.pth.tar"
9
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
 
 
 
10
  gpu_id: 0
11
  cuda: true
12
  test_batch_size: 1
 
1
+ # TAIC eval: codec test + optional --with-metrics
 
2
  task: "instance"
3
  dataset_path: "/data/coco2017"
4
  split: "val2017"
5
+ ann_file: "annotations/instances_val2017.json"
6
  quality_level: 1
7
  lmbda: 0.0035
8
  checkpoint: "./checkpoints/taic/instance/1/checkpoint_best_loss.pth.tar"
9
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
10
+ # official task network (required for --with-metrics)
11
+ task_config: "./configs/task_networks/cascade_mask_rcnn_swin_base_coco.py"
12
+ task_checkpoint: "./checkpoints/task_networks/instance/model.pth"
13
  gpu_id: 0
14
  cuda: true
15
  test_batch_size: 1
configs/eval/taic_panoptic.yaml CHANGED
@@ -1,12 +1,15 @@
1
- # Codec test config for TAIC / panoptic
2
- # Place real weights at checkpoint path (replace PLACEHOLDER).
3
  task: "panoptic"
4
  dataset_path: "/data/coco2017"
5
  split: "val2017"
 
6
  quality_level: 1
7
  lmbda: 0.0035
8
  checkpoint: "./checkpoints/taic/panoptic/1/checkpoint_best_loss.pth.tar"
9
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
 
 
 
10
  gpu_id: 0
11
  cuda: true
12
  test_batch_size: 1
 
1
+ # TAIC eval: codec test + optional --with-metrics
 
2
  task: "panoptic"
3
  dataset_path: "/data/coco2017"
4
  split: "val2017"
5
+ ann_file: "annotations/panoptic_val2017.json"
6
  quality_level: 1
7
  lmbda: 0.0035
8
  checkpoint: "./checkpoints/taic/panoptic/1/checkpoint_best_loss.pth.tar"
9
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
10
+ # official task network (required for --with-metrics)
11
+ task_config: "./configs/task_networks/maskformer_swin-b_coco.py"
12
+ task_checkpoint: "./checkpoints/task_networks/panoptic/model.pth"
13
  gpu_id: 0
14
  cuda: true
15
  test_batch_size: 1
configs/eval/taic_pose.yaml CHANGED
@@ -1,12 +1,15 @@
1
- # Codec test config for TAIC / pose
2
- # Place real weights at checkpoint path (replace PLACEHOLDER).
3
  task: "pose"
4
  dataset_path: "/data/coco2017"
5
  split: "val2017"
 
6
  quality_level: 1
7
  lmbda: 0.0035
8
  checkpoint: "./checkpoints/taic/pose/1/checkpoint_best_loss.pth.tar"
9
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
 
 
 
10
  gpu_id: 0
11
  cuda: true
12
  test_batch_size: 1
 
1
+ # TAIC eval: codec test + optional --with-metrics
 
2
  task: "pose"
3
  dataset_path: "/data/coco2017"
4
  split: "val2017"
5
+ ann_file: "annotations/coco_wholebody_val_v1.0.json"
6
  quality_level: 1
7
  lmbda: 0.0035
8
  checkpoint: "./checkpoints/taic/pose/1/checkpoint_best_loss.pth.tar"
9
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
10
+ # official task network (required for --with-metrics)
11
+ task_config: "./configs/task_networks/higherhrnet_w32_coco_wholebody.py"
12
+ task_checkpoint: "./checkpoints/task_networks/pose/model.pth"
13
  gpu_id: 0
14
  cuda: true
15
  test_batch_size: 1
configs/eval/taic_semantic.yaml CHANGED
@@ -1,12 +1,15 @@
1
- # Codec test config for TAIC / semantic
2
- # Place real weights at checkpoint path (replace PLACEHOLDER).
3
  task: "semantic"
4
  dataset_path: "/data/coco2017"
5
  split: "val2017"
 
6
  quality_level: 1
7
  lmbda: 0.0035
8
  checkpoint: "./checkpoints/taic/semantic/1/checkpoint_best_loss.pth.tar"
9
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
 
 
 
10
  gpu_id: 0
11
  cuda: true
12
  test_batch_size: 1
 
1
+ # TAIC eval: codec test + optional --with-metrics
 
2
  task: "semantic"
3
  dataset_path: "/data/coco2017"
4
  split: "val2017"
5
+ ann_file: "annotations/panoptic_val2017.json"
6
  quality_level: 1
7
  lmbda: 0.0035
8
  checkpoint: "./checkpoints/taic/semantic/1/checkpoint_best_loss.pth.tar"
9
  base_codec: "./checkpoints/base_codec/base_codec_1.pth.tar"
10
+ # official task network (required for --with-metrics)
11
+ task_config: "./configs/task_networks/upernet_swin-b_coco.py"
12
+ task_checkpoint: "./checkpoints/task_networks/semantic/model.pth"
13
  gpu_id: 0
14
  cuda: true
15
  test_batch_size: 1
configs/taic/detection.yaml CHANGED
@@ -1,4 +1,4 @@
1
- # FlexICM TAIC - Object Detection (Faster R-CNN + Swin-B)
2
  root: "logs"
3
  exp_name: "taic_detection"
4
  task: "detection"
 
1
+ # FlexICM TAIC - Object Detection (Cascade Mask R-CNN + Swin-B)
2
  root: "logs"
3
  exp_name: "taic_detection"
4
  task: "detection"
configs/taic/instance.yaml CHANGED
@@ -1,3 +1,4 @@
 
1
  root: "logs"
2
  exp_name: "taic_instance"
3
  task: "instance"
@@ -17,5 +18,5 @@ cuda: true
17
  save: true
18
  seed: 42
19
  pretrained_backbone: true
20
- out_channels: 128
21
  align_mode: "fpn"
 
1
+ # FlexICM TAIC - Instance Segmentation (Cascade Mask R-CNN + Swin-B)
2
  root: "logs"
3
  exp_name: "taic_instance"
4
  task: "instance"
 
18
  save: true
19
  seed: 42
20
  pretrained_backbone: true
21
+ out_channels: 128 # Cascade Mask R-CNN + Swin-B F1
22
  align_mode: "fpn"
configs/task_networks/README.md ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Task-network configs (for metric evaluation)
2
+
3
+ Official detection / instance weights come from
4
+ [Swin-Transformer-Object-Detection](https://github.com/SwinTransformer/Swin-Transformer-Object-Detection).
5
+
6
+ Detection and instance segmentation share the same **Cascade Mask R-CNN + Swin-B**
7
+ checkpoint; metrics differ by head output (`bbox` vs `mask`).
8
+
9
+ | Task | Model | Official source |
10
+ |------|-------|-----------------|
11
+ | detection | **Cascade Mask R-CNN + Swin-B** | [config](https://github.com/SwinTransformer/Swin-Transformer-Object-Detection/blob/master/configs/swin/cascade_mask_rcnn_swin_base_patch4_window7_mstrain_480-800_giou_4conv1f_adamw_3x_coco.py) / [ckpt](https://github.com/SwinTransformer/storage/releases/download/v1.0.2/cascade_mask_rcnn_swin_base_patch4_window7.pth) |
12
+ | instance | **Cascade Mask R-CNN + Swin-B** (same) | same config / checkpoint as detection |
13
+ | semantic | UPerNet + Swin-B | MMSegmentation UPerNet Swin-B |
14
+ | panoptic | MaskFormer + Swin-B | MMDetection MaskFormer Swin-B |
15
+ | pose | HigherHRNet-W32 | MMPose HigherHRNet COCO-WholeBody (HRNet backbone) |
16
+
17
+ ### Download detection / instance checkpoints
18
+
19
+ ```bash
20
+ mkdir -p checkpoints/task_networks/detection checkpoints/task_networks/instance
21
+
22
+ # Cascade Mask R-CNN + Swin-B (shared by detection bbox + instance mask)
23
+ CKPT_URL=https://github.com/SwinTransformer/storage/releases/download/v1.0.2/cascade_mask_rcnn_swin_base_patch4_window7.pth
24
+ curl -L -o checkpoints/task_networks/detection/model.pth "$CKPT_URL"
25
+ cp checkpoints/task_networks/detection/model.pth checkpoints/task_networks/instance/model.pth
26
+ rm -f checkpoints/task_networks/detection/PLACEHOLDER checkpoints/task_networks/instance/PLACEHOLDER
27
+ ```
28
+
29
+ Symlink or copy the official config over the stub:
30
+
31
+ ```bash
32
+ ln -sf /path/to/Swin-Transformer-Object-Detection/configs/swin/cascade_mask_rcnn_swin_base_patch4_window7_mstrain_480-800_giou_4conv1f_adamw_3x_coco.py \
33
+ configs/task_networks/cascade_mask_rcnn_swin_base_coco.py
34
+ ```
35
+
36
+ Eval YAML fields:
37
+
38
+ ```yaml
39
+ # detection (mAP-bbox) and instance (mAP-mask) share the same config/weights
40
+ task_config: "./configs/task_networks/cascade_mask_rcnn_swin_base_coco.py"
41
+ task_checkpoint: "./checkpoints/task_networks/detection/model.pth" # or .../instance/model.pth
42
+ ```
43
+
44
+ **Channel note:** Swin-B F1 has 128 channels; both detection and instance TAIC use `out_channels: 128`.
configs/task_networks/cascade_mask_rcnn_swin_base_coco.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # STUB / pointer config for Cascade Mask R-CNN + Swin-B (official Swin detection zoo).
2
+ #
3
+ # Source repo: https://github.com/SwinTransformer/Swin-Transformer-Object-Detection
4
+ # Official config:
5
+ # configs/swin/cascade_mask_rcnn_swin_base_patch4_window7_mstrain_480-800_giou_4conv1f_adamw_3x_coco.py
6
+ # Official checkpoint:
7
+ # https://github.com/SwinTransformer/storage/releases/download/v1.0.2/cascade_mask_rcnn_swin_base_patch4_window7.pth
8
+ #
9
+ # Replace this file by copying/symlinking the real config from that repo, then set
10
+ # task_checkpoint to the downloaded .pth under checkpoints/task_networks/detection/.
11
+ #
12
+ # Used for FlexICM detection (mAP-bbox) and instance segmentation (mAP-mask).
13
+ # Backbone F1 channels = 128 (Swin-B).
14
+
15
+ raise RuntimeError(
16
+ "Replace configs/task_networks/cascade_mask_rcnn_swin_base_coco.py with the official "
17
+ "Swin-Transformer-Object-Detection config: "
18
+ "cascade_mask_rcnn_swin_base_patch4_window7_mstrain_480-800_giou_4conv1f_adamw_3x_coco.py"
19
+ )
configs/task_networks/higherhrnet_w32_coco_wholebody.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # STUB: replace this file with a real OpenMMLab config (or symlink to your mmdet/mmseg/mmpose config).
2
+ # Expected model family: higherhrnet_w32_coco_wholebody
3
+ # See configs/task_networks/README.md
4
+ raise RuntimeError(
5
+ "Replace configs/task_networks/higherhrnet_w32_coco_wholebody.py with a real OpenMMLab config "
6
+ "(copy/symlink from mmdet/mmseg/mmpose)."
7
+ )
configs/task_networks/maskformer_swin-b_coco.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # STUB: replace this file with a real OpenMMLab config (or symlink to your mmdet/mmseg/mmpose config).
2
+ # Expected model family: maskformer_swin-b_coco
3
+ # See configs/task_networks/README.md
4
+ raise RuntimeError(
5
+ "Replace configs/task_networks/maskformer_swin-b_coco.py with a real OpenMMLab config "
6
+ "(copy/symlink from mmdet/mmseg/mmpose)."
7
+ )
configs/task_networks/upernet_swin-b_coco.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # STUB: replace this file with a real OpenMMLab config (or symlink to your mmdet/mmseg/mmpose config).
2
+ # Expected model family: upernet_swin-b_coco
3
+ # See configs/task_networks/README.md
4
+ raise RuntimeError(
5
+ "Replace configs/task_networks/upernet_swin-b_coco.py with a real OpenMMLab config "
6
+ "(copy/symlink from mmdet/mmseg/mmpose)."
7
+ )
flexicm/data/__init__.py CHANGED
@@ -6,12 +6,16 @@ from .datasets import (
6
  build_train_transform,
7
  collate_keep,
8
  )
 
9
 
10
  __all__ = [
11
  "COCOImageDataset",
12
  "COCOWholeBodyImageDataset",
13
  "ImageFolderDataset",
 
 
14
  "build_test_transform",
15
  "build_train_transform",
16
  "collate_keep",
 
17
  ]
 
6
  build_train_transform,
7
  collate_keep,
8
  )
9
+ from .coco_eval import COCOEvalDataset, TASK_ANN_FILES, coco_eval_collate
10
 
11
  __all__ = [
12
  "COCOImageDataset",
13
  "COCOWholeBodyImageDataset",
14
  "ImageFolderDataset",
15
+ "COCOEvalDataset",
16
+ "TASK_ANN_FILES",
17
  "build_test_transform",
18
  "build_train_transform",
19
  "collate_keep",
20
+ "coco_eval_collate",
21
  ]
flexicm/data/coco_eval.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """COCO-style evaluation datasets that return image + annotation paths/ids."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ import torch
10
+ from PIL import Image
11
+ from torch.utils.data import Dataset
12
+ from torchvision import transforms
13
+
14
+
15
+ class COCOEvalDataset(Dataset):
16
+ """COCO val images with annotation ids for metric evaluation.
17
+
18
+ Returns a dict:
19
+ image: FloatTensor CxHxW in [0,1]
20
+ image_id: int
21
+ file_name: str
22
+ height, width: int
23
+ path: str
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ coco_root: str,
29
+ ann_file: str,
30
+ image_prefix: str = "val2017",
31
+ transform=None,
32
+ ):
33
+ self.coco_root = coco_root
34
+ self.image_dir = os.path.join(coco_root, image_prefix)
35
+ self.ann_file = ann_file if os.path.isabs(ann_file) else os.path.join(coco_root, ann_file)
36
+ self.transform = transform or transforms.ToTensor()
37
+
38
+ with open(self.ann_file) as f:
39
+ coco = json.load(f)
40
+ self.images: List[Dict[str, Any]] = sorted(coco["images"], key=lambda x: x["id"])
41
+ self.categories = coco.get("categories", [])
42
+
43
+ def __len__(self):
44
+ return len(self.images)
45
+
46
+ def __getitem__(self, index: int) -> Dict[str, Any]:
47
+ info = self.images[index]
48
+ path = os.path.join(self.image_dir, info["file_name"])
49
+ img = Image.open(path).convert("RGB")
50
+ tensor = self.transform(img)
51
+ return {
52
+ "image": tensor,
53
+ "image_id": int(info["id"]),
54
+ "file_name": info["file_name"],
55
+ "height": int(info["height"]),
56
+ "width": int(info["width"]),
57
+ "path": path,
58
+ }
59
+
60
+
61
+ def coco_eval_collate(batch: List[Dict[str, Any]]) -> Dict[str, Any]:
62
+ """Collate that keeps variable-size images as a list (batch_size usually 1)."""
63
+ return {
64
+ "images": [b["image"] for b in batch],
65
+ "image_ids": [b["image_id"] for b in batch],
66
+ "file_names": [b["file_name"] for b in batch],
67
+ "heights": [b["height"] for b in batch],
68
+ "widths": [b["width"] for b in batch],
69
+ "paths": [b["path"] for b in batch],
70
+ }
71
+
72
+
73
+ # Default annotation files relative to coco_root
74
+ TASK_ANN_FILES = {
75
+ "detection": "annotations/instances_val2017.json",
76
+ "instance": "annotations/instances_val2017.json",
77
+ "semantic": "annotations/panoptic_val2017.json", # or stuff; override in config
78
+ "panoptic": "annotations/panoptic_val2017.json",
79
+ "pose": "annotations/coco_wholebody_val_v1.0.json",
80
+ }
flexicm/tasks/__init__.py CHANGED
@@ -1,8 +1,8 @@
1
  """Task-specific frozen teachers for FlexICM feature alignment.
2
 
3
  Five tasks (paper Sec.III.A / IV.A):
4
- 1. Object detection - Faster R-CNN + Swin-B (FPN P2-P6)
5
- 2. Instance segmentation - Mask R-CNN + Swin-B (FPN P2-P6)
6
  3. Semantic segmentation - UPerNet + Swin-B (FPN P2-P6)
7
  4. Panoptic segmentation - MaskFormer + Swin-B (stages F1-F4)
8
  5. Pose estimation - HigherHRNet (original HRNet backbone)
@@ -24,15 +24,22 @@ from flexicm.tasks.swin_teacher import SwinStageTeacher
24
 
25
 
26
  class DetectionTeacher(nn.Module):
27
- """Faster R-CNN / Mask R-CNN style: align FPN P2..P6."""
 
 
 
28
 
29
  align_mode = "fpn"
30
- out_channels = 128 # Swin-B F1
31
 
32
  def __init__(self, pretrained_backbone: bool = True, task: str = "detection"):
33
  super().__init__()
34
  self.task = task
35
- self.backbone = SwinStageTeacher(pretrained=pretrained_backbone, use_fpn=True)
 
 
 
 
36
  freeze_module(self)
37
 
38
  def gt_features(self, images: torch.Tensor) -> Dict[str, torch.Tensor]:
@@ -194,7 +201,7 @@ TASK_META = {
194
  },
195
  "instance": {
196
  "align_mode": "fpn",
197
- "out_channels": 128,
198
  "metric": "mAP-mask",
199
  "dataset": "coco",
200
  },
 
1
  """Task-specific frozen teachers for FlexICM feature alignment.
2
 
3
  Five tasks (paper Sec.III.A / IV.A):
4
+ 1. Object detection - Cascade Mask R-CNN + Swin-B (official Swin det zoo; mAP-bbox)
5
+ 2. Instance segmentation - Cascade Mask R-CNN + Swin-B (same zoo; mAP-mask)
6
  3. Semantic segmentation - UPerNet + Swin-B (FPN P2-P6)
7
  4. Panoptic segmentation - MaskFormer + Swin-B (stages F1-F4)
8
  5. Pose estimation - HigherHRNet (original HRNet backbone)
 
24
 
25
 
26
  class DetectionTeacher(nn.Module):
27
+ """Detection / instance teacher for FPN feature alignment.
28
+
29
+ Both tasks use Cascade Mask R-CNN + Swin-B (F1 = 128-d).
30
+ """
31
 
32
  align_mode = "fpn"
33
+ out_channels = 128
34
 
35
  def __init__(self, pretrained_backbone: bool = True, task: str = "detection"):
36
  super().__init__()
37
  self.task = task
38
+ self.backbone = SwinStageTeacher(
39
+ pretrained=pretrained_backbone,
40
+ use_fpn=True,
41
+ swin_variant="base",
42
+ )
43
  freeze_module(self)
44
 
45
  def gt_features(self, images: torch.Tensor) -> Dict[str, torch.Tensor]:
 
201
  },
202
  "instance": {
203
  "align_mode": "fpn",
204
+ "out_channels": 128, # Cascade Mask R-CNN + Swin-B F1
205
  "metric": "mAP-mask",
206
  "dataset": "coco",
207
  },
flexicm/tasks/metric_eval.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """End-to-end codec + task-network metric evaluation loop."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ import torch
8
+
9
+ from flexicm.utils.codec_test import crop_feature_to_image, pad_for_codec
10
+ from flexicm.tasks.metric_runners import TaskMetricRunner, build_metric_runner
11
+
12
+
13
+ @torch.no_grad()
14
+ def run_task_metric_eval(
15
+ codec,
16
+ runner: TaskMetricRunner,
17
+ loader,
18
+ device: str,
19
+ ann_file: str,
20
+ use_condition: bool = False,
21
+ base_codec=None,
22
+ align_divisor: int = 256,
23
+ max_batches: Optional[int] = None,
24
+ finalize_kwargs: Optional[Dict[str, Any]] = None,
25
+ ) -> Dict[str, float]:
26
+ """For each image: codec -> h -> truncated task net -> accumulate -> metrics."""
27
+ codec.eval()
28
+ predictions: List[Any] = []
29
+
30
+ for i, batch in enumerate(loader):
31
+ if max_batches is not None and i >= max_batches:
32
+ break
33
+
34
+ # Support both plain image batches and COCOEval collate dicts
35
+ if isinstance(batch, dict) and "images" in batch:
36
+ images_list = batch["images"]
37
+ metas = []
38
+ for j in range(len(images_list)):
39
+ metas.append(
40
+ dict(
41
+ image_id=batch["image_ids"][j],
42
+ height=batch["heights"][j],
43
+ width=batch["widths"][j],
44
+ path=batch["paths"][j],
45
+ file_name=batch["file_names"][j],
46
+ )
47
+ )
48
+ else:
49
+ # Tensor batch Bx3xHxW without coco ids — skip metric (needs image_id)
50
+ raise RuntimeError(
51
+ "Task-metric eval requires COCOEvalDataset + coco_eval_collate "
52
+ "(image_id / height / width)."
53
+ )
54
+
55
+ for image, meta in zip(images_list, metas):
56
+ image = image.unsqueeze(0).to(device)
57
+ _, _, H, W = image.shape
58
+ x, _ = pad_for_codec(image, divisor=align_divisor, device=device)
59
+
60
+ y_b = None
61
+ if use_condition and base_codec is not None:
62
+ y_b = base_codec(x)["y_hat"]
63
+ out = codec(x, y_b_hat=y_b, use_condition=True)
64
+ elif hasattr(codec, "forward") and use_condition is False and base_codec is None:
65
+ out = codec(x)
66
+ else:
67
+ # CTAIC without condition
68
+ if hasattr(codec, "forward"):
69
+ try:
70
+ out = codec(x, y_b_hat=None, use_condition=False)
71
+ except TypeError:
72
+ out = codec(x)
73
+ else:
74
+ out = codec(x)
75
+
76
+ h = crop_feature_to_image(out["h"], (H, W))
77
+ pred = runner.predict_from_h(h, meta)
78
+ predictions.append(pred)
79
+
80
+ if i % 20 == 0:
81
+ print(f"[metric] processed batch {i}/{len(loader)}")
82
+
83
+ metrics = runner.finalize(predictions, ann_file, **(finalize_kwargs or {}))
84
+ return metrics
flexicm/tasks/metric_runners.py ADDED
@@ -0,0 +1,479 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Task-network metric runners: load official checkpoints and evaluate from codec feature h.
2
+
3
+ Paper flow (Sec.III.A):
4
+ codec -> h (H/4 x W/4 x C) -> truncated task network (from Stage 2 / FPN) -> task output
5
+ then compute mAP-bbox / mAP-mask / mIoU / PQ / mAP-OKS.
6
+
7
+ Requires optional packages:
8
+ pip install pycocotools
9
+ mim install mmdet mmsegmentation mmpose # plus mmengine mmcv
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ from abc import ABC, abstractmethod
16
+ from typing import Any, Dict, List, Optional, Tuple
17
+
18
+ import torch
19
+ import torch.nn as nn
20
+ import torch.nn.functional as F
21
+
22
+
23
+ class TaskMetricRunner(ABC):
24
+ """Unified interface for end-task evaluation from decoded feature h."""
25
+
26
+ metric_name: str = "metric"
27
+
28
+ def __init__(self, device: str = "cuda"):
29
+ self.device = device
30
+ self.model = None
31
+
32
+ @abstractmethod
33
+ def load(self, config_path: str, checkpoint_path: str) -> None:
34
+ ...
35
+
36
+ @abstractmethod
37
+ def predict_from_h(
38
+ self,
39
+ h: torch.Tensor,
40
+ img_meta: Dict[str, Any],
41
+ ) -> Any:
42
+ """Run truncated task net starting from feature h."""
43
+ ...
44
+
45
+ @abstractmethod
46
+ def finalize(self, predictions: List[Any], ann_file: str, **kwargs) -> Dict[str, float]:
47
+ """Aggregate predictions vs GT annotations into scalar metrics."""
48
+ ...
49
+
50
+
51
+ def _require_mmdet():
52
+ try:
53
+ import mmdet # noqa: F401
54
+ from mmdet.apis import init_detector
55
+ except ImportError as e:
56
+ raise ImportError(
57
+ "Full task-metric evaluation requires MMDetection.\n"
58
+ " pip install -U openmim && mim install mmengine mmcv mmdet"
59
+ ) from e
60
+ return init_detector
61
+
62
+
63
+ def _require_mmseg():
64
+ try:
65
+ from mmseg.apis import init_model
66
+ except ImportError as e:
67
+ raise ImportError(
68
+ "Semantic segmentation metric evaluation requires MMSegmentation.\n"
69
+ " mim install mmsegmentation"
70
+ ) from e
71
+ return init_model
72
+
73
+
74
+ def _require_mmpose():
75
+ try:
76
+ from mmpose.apis import init_model
77
+ except ImportError as e:
78
+ raise ImportError(
79
+ "Pose metric evaluation requires MMPose.\n"
80
+ " mim install mmpose"
81
+ ) from e
82
+ return init_model
83
+
84
+
85
+ def swin_feats_from_h(backbone: nn.Module, h: torch.Tensor) -> Tuple[torch.Tensor, ...]:
86
+ """Treat h as Swin F1 (stage-0 output) and run remaining stages.
87
+
88
+ Compatible with MMDet/MMSeg SwinTransformer that exposes `.stages` / `.layers`.
89
+ """
90
+ stages = None
91
+ for name in ("stages", "layers"):
92
+ if hasattr(backbone, name):
93
+ stages = getattr(backbone, name)
94
+ break
95
+ if stages is None:
96
+ raise RuntimeError("Backbone has no stages/layers; cannot inject h as F1")
97
+
98
+ outs = [h]
99
+ x = h
100
+ # Stage 0 already produced F1 (=h); run stages 1..N-1
101
+ for i in range(1, len(stages)):
102
+ x = stages[i](x)
103
+ if isinstance(x, (tuple, list)):
104
+ x = x[0]
105
+ # MMDet Swin may return NCHW already
106
+ if x.dim() == 4 and x.shape[1] < x.shape[-1] and x.shape[-1] in (96, 128, 192, 256, 384, 512, 768, 1024):
107
+ # likely NHWC
108
+ x = x.permute(0, 3, 1, 2).contiguous()
109
+ outs.append(x)
110
+
111
+ # Apply per-stage norms if present (mmdet Swin)
112
+ if hasattr(backbone, "num_features") or hasattr(backbone, "out_indices"):
113
+ norm_outs = []
114
+ for i, out in enumerate(outs):
115
+ norm_name = f"norm{i}"
116
+ if hasattr(backbone, norm_name):
117
+ nchw = out
118
+ # LayerNorm over channel last in some impls
119
+ norm = getattr(backbone, norm_name)
120
+ if nchw.shape[1] == getattr(norm, "normalized_shape", [nchw.shape[1]])[0] if hasattr(norm, "normalized_shape") else True:
121
+ # try NCHW LayerNorm via transpose
122
+ try:
123
+ y = nchw.permute(0, 2, 3, 1)
124
+ y = norm(y)
125
+ nchw = y.permute(0, 3, 1, 2).contiguous()
126
+ except Exception:
127
+ nchw = out
128
+ norm_outs.append(nchw)
129
+ else:
130
+ norm_outs.append(out)
131
+ return tuple(norm_outs)
132
+ return tuple(outs)
133
+
134
+
135
+ class DetectionMetricRunner(TaskMetricRunner):
136
+ """Cascade Mask R-CNN + Swin-B (official zoo) — mAP-bbox / mAP-mask."""
137
+
138
+ def __init__(self, device: str = "cuda", with_mask: bool = False):
139
+ super().__init__(device)
140
+ self.with_mask = with_mask
141
+ self.metric_name = "mAP-mask" if with_mask else "mAP-bbox"
142
+ self._results: List[Dict] = []
143
+
144
+ def load(self, config_path: str, checkpoint_path: str) -> None:
145
+ init_detector = _require_mmdet()
146
+ self.model = init_detector(config_path, checkpoint_path, device=self.device)
147
+ self.model.eval()
148
+
149
+ @torch.no_grad()
150
+ def predict_from_h(self, h: torch.Tensor, img_meta: Dict[str, Any]) -> Dict[str, Any]:
151
+ assert self.model is not None
152
+ # h: 1xCx(H/4)x(W/4)
153
+ backbone = self.model.backbone
154
+ feats = swin_feats_from_h(backbone, h)
155
+ if hasattr(self.model, "neck") and self.model.neck is not None:
156
+ feats = self.model.neck(feats)
157
+
158
+ # Build a minimal img_metas / data_samples for mmdet 3.x or 2.x
159
+ H, W = int(img_meta["height"]), int(img_meta["width"])
160
+ try:
161
+ # MMDet 3.x style
162
+ from mmdet.structures import DetDataSample
163
+ from mmengine.structures import InstanceData
164
+
165
+ data_sample = DetDataSample()
166
+ data_sample.set_metainfo(
167
+ dict(
168
+ img_shape=(H, W),
169
+ ori_shape=(H, W),
170
+ pad_shape=(H, W),
171
+ scale_factor=(1.0, 1.0),
172
+ img_id=img_meta.get("image_id"),
173
+ )
174
+ )
175
+ # Use RPN + ROI heads with injected feats
176
+ if hasattr(self.model, "extract_feat"):
177
+ # bypass extract_feat by calling predict with feats if supported
178
+ pass
179
+ rpn_results_list = self.model.rpn_head.predict(feats, [data_sample], rescale=False)
180
+ results_list = self.model.roi_head.predict(
181
+ feats, rpn_results_list, [data_sample], rescale=True
182
+ )
183
+ pred = results_list[0]
184
+ inst = pred.pred_instances
185
+ out = {
186
+ "image_id": img_meta["image_id"],
187
+ "bboxes": inst.bboxes.detach().cpu(),
188
+ "scores": inst.scores.detach().cpu(),
189
+ "labels": inst.labels.detach().cpu(),
190
+ }
191
+ if self.with_mask and hasattr(inst, "masks") and inst.masks is not None:
192
+ out["masks"] = inst.masks.to_ndarray() if hasattr(inst.masks, "to_ndarray") else inst.masks.detach().cpu()
193
+ return out
194
+ except Exception:
195
+ # Fallback MMDet 2.x
196
+ img_metas = [
197
+ dict(
198
+ img_shape=(H, W, 3),
199
+ ori_shape=(H, W, 3),
200
+ pad_shape=(H, W, 3),
201
+ scale_factor=1.0,
202
+ flip=False,
203
+ )
204
+ ]
205
+ proposal_list = self.model.rpn_head.simple_test_rpn(feats, img_metas)
206
+ det_results = self.model.roi_head.simple_test(
207
+ feats, proposal_list, img_metas, rescale=True
208
+ )
209
+ # det_results: list of (bboxes_per_class) or (bboxes, segm)
210
+ return {"image_id": img_meta["image_id"], "raw": det_results[0]}
211
+
212
+ def finalize(self, predictions: List[Any], ann_file: str, **kwargs) -> Dict[str, float]:
213
+ from pycocotools.coco import COCO
214
+ from pycocotools.cocoeval import COCOeval
215
+ import numpy as np
216
+
217
+ coco_gt = COCO(ann_file)
218
+ coco_results = []
219
+ for pred in predictions:
220
+ if pred is None:
221
+ continue
222
+ if "raw" in pred:
223
+ # mmdet 2.x format: list[ndarray(n,5)] per class
224
+ raw = pred["raw"]
225
+ bbox_results = raw[0] if isinstance(raw, tuple) else raw
226
+ for label, bboxes in enumerate(bbox_results):
227
+ for row in bboxes:
228
+ x1, y1, x2, y2, score = row[:5]
229
+ coco_results.append(
230
+ {
231
+ "image_id": int(pred["image_id"]),
232
+ "category_id": int(coco_gt.getCatIds()[label])
233
+ if label < len(coco_gt.getCatIds())
234
+ else int(label + 1),
235
+ "bbox": [float(x1), float(y1), float(x2 - x1), float(y2 - y1)],
236
+ "score": float(score),
237
+ }
238
+ )
239
+ continue
240
+
241
+ bboxes = pred["bboxes"].numpy()
242
+ scores = pred["scores"].numpy()
243
+ labels = pred["labels"].numpy()
244
+ cat_ids = coco_gt.getCatIds()
245
+ for box, score, label in zip(bboxes, scores, labels):
246
+ x1, y1, x2, y2 = box.tolist()
247
+ cat_id = int(cat_ids[int(label)]) if int(label) < len(cat_ids) else int(label) + 1
248
+ coco_results.append(
249
+ {
250
+ "image_id": int(pred["image_id"]),
251
+ "category_id": cat_id,
252
+ "bbox": [x1, y1, x2 - x1, y2 - y1],
253
+ "score": float(score),
254
+ }
255
+ )
256
+
257
+ if not coco_results:
258
+ return {self.metric_name: 0.0}
259
+
260
+ coco_dt = coco_gt.loadRes(coco_results)
261
+ coco_eval = COCOeval(coco_gt, coco_dt, iouType="bbox")
262
+ coco_eval.evaluate()
263
+ coco_eval.accumulate()
264
+ coco_eval.summarize()
265
+ metrics = {"mAP-bbox": float(coco_eval.stats[0])}
266
+
267
+ if self.with_mask:
268
+ # Mask eval requires segmentation results in COCO format; if unavailable, skip
269
+ try:
270
+ coco_eval_m = COCOeval(coco_gt, coco_dt, iouType="segm")
271
+ coco_eval_m.evaluate()
272
+ coco_eval_m.accumulate()
273
+ coco_eval_m.summarize()
274
+ metrics["mAP-mask"] = float(coco_eval_m.stats[0])
275
+ except Exception as e:
276
+ metrics["mAP-mask_error"] = str(e)
277
+ return metrics
278
+
279
+
280
+ class SemanticMetricRunner(TaskMetricRunner):
281
+ """UPerNet (MMSeg) — metric: mIoU."""
282
+
283
+ metric_name = "mIoU"
284
+
285
+ def load(self, config_path: str, checkpoint_path: str) -> None:
286
+ init_model = _require_mmseg()
287
+ self.model = init_model(config_path, checkpoint_path, device=self.device)
288
+ self.model.eval()
289
+ self._preds = []
290
+
291
+ @torch.no_grad()
292
+ def predict_from_h(self, h: torch.Tensor, img_meta: Dict[str, Any]) -> Dict[str, Any]:
293
+ assert self.model is not None
294
+ backbone = self.model.backbone
295
+ feats = swin_feats_from_h(backbone, h)
296
+ seg_logits = self.model.decode_head(feats)
297
+ if isinstance(seg_logits, (tuple, list)):
298
+ seg_logits = seg_logits[0]
299
+ H, W = int(img_meta["height"]), int(img_meta["width"])
300
+ seg = F.interpolate(seg_logits, size=(H, W), mode="bilinear", align_corners=False)
301
+ pred = seg.argmax(dim=1)[0].detach().cpu().numpy()
302
+ return {"image_id": img_meta["image_id"], "seg": pred, "path": img_meta.get("path")}
303
+
304
+ def finalize(self, predictions: List[Any], ann_file: str, **kwargs) -> Dict[str, float]:
305
+ """Compute mIoU if GT semantic maps are provided via kwargs['gt_dir'] or panoptic conversion.
306
+
307
+ For a minimal working path, expects kwargs['gt_seg_loader'](image_id)->HxW label map.
308
+ """
309
+ gt_loader = kwargs.get("gt_seg_loader")
310
+ if gt_loader is None:
311
+ return {
312
+ "mIoU": float("nan"),
313
+ "note": "Provide gt_seg_loader or use panoptic stuff GT to compute mIoU",
314
+ }
315
+
316
+ import numpy as np
317
+
318
+ num_classes = int(kwargs.get("num_classes", 133))
319
+ intersect = np.zeros(num_classes, dtype=np.float64)
320
+ union = np.zeros(num_classes, dtype=np.float64)
321
+ for pred in predictions:
322
+ gt = gt_loader(pred["image_id"])
323
+ pr = pred["seg"]
324
+ if gt.shape != pr.shape:
325
+ # nearest resize pred already at image size; skip mismatch
326
+ continue
327
+ for c in range(num_classes):
328
+ pb = pr == c
329
+ gb = gt == c
330
+ inter = np.logical_and(pb, gb).sum()
331
+ uni = np.logical_or(pb, gb).sum()
332
+ intersect[c] += inter
333
+ union[c] += uni
334
+ ious = intersect / np.maximum(union, 1)
335
+ valid = union > 0
336
+ miou = float(ious[valid].mean()) if valid.any() else 0.0
337
+ return {"mIoU": miou}
338
+
339
+
340
+ class PanopticMetricRunner(TaskMetricRunner):
341
+ """MaskFormer (MMDet) — metric: PQ."""
342
+
343
+ metric_name = "PQ"
344
+
345
+ def load(self, config_path: str, checkpoint_path: str) -> None:
346
+ init_detector = _require_mmdet()
347
+ self.model = init_detector(config_path, checkpoint_path, device=self.device)
348
+ self.model.eval()
349
+
350
+ @torch.no_grad()
351
+ def predict_from_h(self, h: torch.Tensor, img_meta: Dict[str, Any]) -> Dict[str, Any]:
352
+ assert self.model is not None
353
+ # MaskFormer typically uses backbone features F1..F4 directly
354
+ backbone = self.model.backbone
355
+ feats = swin_feats_from_h(backbone, h)
356
+ H, W = int(img_meta["height"]), int(img_meta["width"])
357
+ try:
358
+ from mmdet.structures import DetDataSample
359
+
360
+ data_sample = DetDataSample()
361
+ data_sample.set_metainfo(
362
+ dict(img_shape=(H, W), ori_shape=(H, W), pad_shape=(H, W), img_id=img_meta["image_id"])
363
+ )
364
+ # panoptic head path differs by version; store feats for custom head call
365
+ if hasattr(self.model, "panoptic_head"):
366
+ results = self.model.panoptic_head.predict(feats, [data_sample], rescale=True)
367
+ return {"image_id": img_meta["image_id"], "panoptic": results[0]}
368
+ if hasattr(self.model, "simple_test"):
369
+ # older API expects image tensor; not ideal for h-injection
370
+ return {"image_id": img_meta["image_id"], "feats_only": True, "error": "need panoptic_head"}
371
+ except Exception as e:
372
+ return {"image_id": img_meta["image_id"], "error": str(e)}
373
+ return {"image_id": img_meta["image_id"], "error": "unsupported MaskFormer API"}
374
+
375
+ def finalize(self, predictions: List[Any], ann_file: str, **kwargs) -> Dict[str, float]:
376
+ # Full PQ needs panopticapi; keep a clear placeholder result if preds incomplete
377
+ try:
378
+ from panopticapi.evaluation import pq_compute
379
+ except ImportError:
380
+ return {
381
+ "PQ": float("nan"),
382
+ "note": "Install panopticapi and provide GT panoptic folder to compute PQ",
383
+ }
384
+ gt_folder = kwargs.get("gt_folder")
385
+ pred_folder = kwargs.get("pred_folder")
386
+ if not gt_folder or not pred_folder:
387
+ return {"PQ": float("nan"), "note": "Need gt_folder and pred_folder for pq_compute"}
388
+ results = pq_compute(ann_file, kwargs.get("pred_json"), gt_folder, pred_folder)
389
+ return {"PQ": float(results["All"]["pq"])}
390
+
391
+
392
+ class PoseMetricRunner(TaskMetricRunner):
393
+ """HigherHRNet (MMPose, original HRNet backbone) — metric: mAP-OKS."""
394
+
395
+ metric_name = "mAP-OKS"
396
+
397
+ def load(self, config_path: str, checkpoint_path: str) -> None:
398
+ init_model = _require_mmpose()
399
+ self.model = init_model(config_path, checkpoint_path, device=self.device)
400
+ self.model.eval()
401
+
402
+ @torch.no_grad()
403
+ def predict_from_h(self, h: torch.Tensor, img_meta: Dict[str, Any]) -> Dict[str, Any]:
404
+ """Inject h as early HRNet feature when possible; else return error guidance.
405
+
406
+ HigherHRNet uses HRNet (not Swin). Codec `out_channels` should match stem width
407
+ (default 32). Full keypoint head wiring depends on mmpose version.
408
+ """
409
+ assert self.model is not None
410
+ try:
411
+ # Best-effort: if backbone has stage transitions, set first stream feature to h
412
+ backbone = self.model.backbone if hasattr(self.model, "backbone") else self.model
413
+ # Many mmpose models expect full image; document limitation
414
+ if hasattr(self.model, "predict"):
415
+ # Without image path, we only support feature injection hooks if present
416
+ return {
417
+ "image_id": img_meta["image_id"],
418
+ "error": (
419
+ "HigherHRNet-from-h requires a project-specific backbone hook; "
420
+ "set pose.eval_from_image=true in config to run image-based fallback "
421
+ "after optional RGB decode, or implement HRNet stem replacement."
422
+ ),
423
+ }
424
+ except Exception as e:
425
+ return {"image_id": img_meta["image_id"], "error": str(e)}
426
+ return {"image_id": img_meta["image_id"], "error": "pose from-h not hooked"}
427
+
428
+ def finalize(self, predictions: List[Any], ann_file: str, **kwargs) -> Dict[str, float]:
429
+ # Standard COCO keypoint eval when predictions are in COCO format
430
+ valid = [p for p in predictions if p and "keypoints" in p]
431
+ if not valid:
432
+ return {
433
+ "mAP-OKS": float("nan"),
434
+ "note": "No keypoint predictions; implement HigherHRNet-from-h or provide COCO-format preds",
435
+ }
436
+ from pycocotools.coco import COCO
437
+ from pycocotools.cocoeval import COCOeval
438
+
439
+ coco_gt = COCO(ann_file)
440
+ coco_dt = coco_gt.loadRes(valid)
441
+ ev = COCOeval(coco_gt, coco_dt, iouType="keypoints")
442
+ ev.evaluate()
443
+ ev.accumulate()
444
+ ev.summarize()
445
+ return {"mAP-OKS": float(ev.stats[0])}
446
+
447
+
448
+ def build_metric_runner(task: str, device: str = "cuda") -> TaskMetricRunner:
449
+ task = task.lower()
450
+ if task in ("detection", "det", "object_detection"):
451
+ return DetectionMetricRunner(device=device, with_mask=False)
452
+ if task in ("instance", "instance_seg", "instance_segmentation"):
453
+ return DetectionMetricRunner(device=device, with_mask=True)
454
+ if task in ("semantic", "semantic_seg", "semantic_segmentation"):
455
+ return SemanticMetricRunner(device=device)
456
+ if task in ("panoptic", "panoptic_seg", "panoptic_segmentation"):
457
+ return PanopticMetricRunner(device=device)
458
+ if task in ("pose", "pose_estimation"):
459
+ return PoseMetricRunner(device=device)
460
+ raise ValueError(f"Unknown task for metric runner: {task}")
461
+
462
+
463
+ # Suggested OpenMMLab config names (user must download matching weights)
464
+ DEFAULT_TASK_NET_CONFIGS = {
465
+ # Official Swin-Transformer-Object-Detection zoo (same Cascade Mask R-CNN + Swin-B)
466
+ "detection": "configs/task_networks/cascade_mask_rcnn_swin_base_coco.py", # mAP-bbox
467
+ "instance": "configs/task_networks/cascade_mask_rcnn_swin_base_coco.py", # mAP-mask
468
+ "semantic": "configs/task_networks/upernet_swin-b_coco.py",
469
+ "panoptic": "configs/task_networks/maskformer_swin-b_coco.py",
470
+ "pose": "configs/task_networks/higherhrnet_w32_coco_wholebody.py",
471
+ }
472
+
473
+ DEFAULT_TASK_NET_CKPTS = {
474
+ "detection": "checkpoints/task_networks/detection/model.pth",
475
+ "instance": "checkpoints/task_networks/instance/model.pth",
476
+ "semantic": "checkpoints/task_networks/semantic/model.pth",
477
+ "panoptic": "checkpoints/task_networks/panoptic/model.pth",
478
+ "pose": "checkpoints/task_networks/pose/model.pth",
479
+ }
flexicm/tasks/swin_teacher.py CHANGED
@@ -1,6 +1,8 @@
1
- """Swin-B backbone helpers shared by detection / segmentation teachers.
 
 
 
2
 
3
- Matches Fig.1(c): Stage depths [2,2,18,2], F1 at H/4 with C=128 (Swin-B).
4
  h from TAIC replaces F1 and is fed into Stage 2 onward.
5
  """
6
 
@@ -14,6 +16,12 @@ import torch.nn.functional as F
14
 
15
  from flexicm.tasks.losses import freeze_module
16
 
 
 
 
 
 
 
17
 
18
  class SimpleFPN(nn.Module):
19
  """Lightweight FPN producing P2..P6 from F1..F4 (channels -> fpn_dim)."""
@@ -36,45 +44,58 @@ class SimpleFPN(nn.Module):
36
  return {"p2": p2, "p3": p3, "p4": p4, "p5": p5, "p6": p6}
37
 
38
 
39
- def build_swin_b_backbone(pretrained: bool = True):
40
- """Build Swin-B via timm; returns backbone module with forward_features stages."""
41
  try:
42
  import timm
43
  except ImportError as e:
44
- raise ImportError("Please install timm to use Swin-B teachers: pip install timm") from e
 
 
 
 
45
 
46
- # features_only gives list of stage outputs
47
  model = timm.create_model(
48
- "swin_base_patch4_window7_224",
49
  pretrained=pretrained,
50
  features_only=True,
51
  out_indices=(0, 1, 2, 3),
52
- img_size=224, # overridden dynamically by dynamic image size support in newer timm
53
  )
54
  return model
55
 
56
 
 
 
 
 
 
57
  class SwinStageTeacher(nn.Module):
58
  """
59
- Extract F1..F4 from a Swin-B backbone.
60
  Truncated path: treat input h as F1, run remaining stages.
61
  """
62
 
63
- def __init__(self, pretrained: bool = True, use_fpn: bool = True, fpn_dim: int = 256):
 
 
 
 
 
 
64
  super().__init__()
65
- self.backbone = freeze_module(build_swin_b_backbone(pretrained=pretrained))
66
- # timm swin_base features_only channel dims
67
- self.feat_channels = list(self.backbone.feature_info.channels()) # typically [128,256,512,1024]
 
 
 
68
  self.use_fpn = use_fpn
69
  if use_fpn:
70
  self.fpn = freeze_module(SimpleFPN(self.feat_channels, fpn_dim=fpn_dim))
71
  else:
72
  self.fpn = None
73
 
74
- # Build stage modules for truncated forward from F1.
75
- # timm Swin features_only structure varies; we use a practical approach:
76
- # full forward for GT; for truncated, interpolate/project h and run full backbone
77
- # with early feature replacement via forward hooks when possible.
78
  self._f1_dim = self.feat_channels[0]
79
 
80
  @property
 
1
+ """Swin backbone helpers shared by detection / segmentation teachers.
2
+
3
+ Swin-B: F1 at H/4 with C=128 (Cascade Mask R-CNN / UPerNet / MaskFormer).
4
+ Optional Swin-T/S variants are supported via `swin_variant` for experiments.
5
 
 
6
  h from TAIC replaces F1 and is fed into Stage 2 onward.
7
  """
8
 
 
16
 
17
  from flexicm.tasks.losses import freeze_module
18
 
19
+ _SWIN_TIMM_NAMES = {
20
+ "base": "swin_base_patch4_window7_224",
21
+ "tiny": "swin_tiny_patch4_window7_224",
22
+ "small": "swin_small_patch4_window7_224",
23
+ }
24
+
25
 
26
  class SimpleFPN(nn.Module):
27
  """Lightweight FPN producing P2..P6 from F1..F4 (channels -> fpn_dim)."""
 
44
  return {"p2": p2, "p3": p3, "p4": p4, "p5": p5, "p6": p6}
45
 
46
 
47
+ def build_swin_backbone(pretrained: bool = True, swin_variant: str = "base"):
48
+ """Build Swin via timm; returns backbone with features_only stages."""
49
  try:
50
  import timm
51
  except ImportError as e:
52
+ raise ImportError("Please install timm to use Swin teachers: pip install timm") from e
53
+
54
+ key = swin_variant.lower().replace("swin-", "").replace("swin_", "")
55
+ if key not in _SWIN_TIMM_NAMES:
56
+ raise ValueError(f"Unknown swin_variant={swin_variant!r}; expected one of {list(_SWIN_TIMM_NAMES)}")
57
 
 
58
  model = timm.create_model(
59
+ _SWIN_TIMM_NAMES[key],
60
  pretrained=pretrained,
61
  features_only=True,
62
  out_indices=(0, 1, 2, 3),
63
+ img_size=224,
64
  )
65
  return model
66
 
67
 
68
+ def build_swin_b_backbone(pretrained: bool = True):
69
+ """Backward-compatible alias for Swin-B."""
70
+ return build_swin_backbone(pretrained=pretrained, swin_variant="base")
71
+
72
+
73
  class SwinStageTeacher(nn.Module):
74
  """
75
+ Extract F1..F4 from a Swin backbone (base / tiny / small).
76
  Truncated path: treat input h as F1, run remaining stages.
77
  """
78
 
79
+ def __init__(
80
+ self,
81
+ pretrained: bool = True,
82
+ use_fpn: bool = True,
83
+ fpn_dim: int = 256,
84
+ swin_variant: str = "base",
85
+ ):
86
  super().__init__()
87
+ self.swin_variant = swin_variant
88
+ self.backbone = freeze_module(
89
+ build_swin_backbone(pretrained=pretrained, swin_variant=swin_variant)
90
+ )
91
+ # timm: Swin-B [128,256,512,1024], Swin-T [96,192,384,768]
92
+ self.feat_channels = list(self.backbone.feature_info.channels())
93
  self.use_fpn = use_fpn
94
  if use_fpn:
95
  self.fpn = freeze_module(SimpleFPN(self.feat_channels, fpn_dim=fpn_dim))
96
  else:
97
  self.fpn = None
98
 
 
 
 
 
99
  self._f1_dim = self.feat_channels[0]
100
 
101
  @property
requirements.txt CHANGED
@@ -7,12 +7,13 @@ Pillow>=8.0
7
  tqdm>=4.60
8
  numpy>=1.20
9
  einops>=0.4.0
 
10
 
11
- # Optional — full task-network evaluation (mAP / mIoU / PQ / OKS)
12
  # openmim
13
  # mmengine
14
  # mmcv
15
  # mmdet
16
  # mmsegmentation
17
  # mmpose
18
- # detectron2
 
7
  tqdm>=4.60
8
  numpy>=1.20
9
  einops>=0.4.0
10
+ pycocotools>=2.0.6
11
 
12
+ # Optional — required for --with-metrics (full task-network evaluation)
13
  # openmim
14
  # mmengine
15
  # mmcv
16
  # mmdet
17
  # mmsegmentation
18
  # mmpose
19
+ # panopticapi # for PQ
scripts/eval_ctaic.py CHANGED
@@ -1,14 +1,10 @@
1
  #!/usr/bin/env python3
2
- """Codec test for C-TAIC (extension layer).
3
 
4
- Reports extension-layer likelihood bpp and feature distortion D (paper: bpp excludes base layer).
5
- Optional actual bitstream bpp via compress/decompress.
6
- Does NOT compute task metrics (mAP / mIoU / PQ / OKS) — those come later.
7
-
8
- Example:
9
  python scripts/eval_ctaic.py -c configs/eval/ctaic_s1.yaml
10
- python scripts/eval_ctaic.py -c configs/eval/ctaic_s1.yaml --no-condition # TAIC-mode ablation
11
- python scripts/eval_ctaic.py -c configs/eval/ctaic_s1.yaml --actual-bpp
12
  """
13
 
14
  from __future__ import annotations
@@ -27,9 +23,16 @@ if REPO_ROOT not in sys.path:
27
  sys.path.insert(0, REPO_ROOT)
28
 
29
  from flexicm.data import COCOImageDataset, COCOWholeBodyImageDataset, ImageFolderDataset, build_test_transform
 
30
  from flexicm.models import CTAIC, TAIC
31
  from flexicm.tasks import TASK_META, build_teacher
32
  from flexicm.tasks.losses import TAICCriterion
 
 
 
 
 
 
33
  from flexicm.utils.codec_test import resolve_ckpt, test_ctaic_loader
34
  from flexicm.utils.train_utils import load_checkpoint_dict, load_yaml_config, set_seed
35
 
@@ -41,14 +44,15 @@ SCENARIOS = {
41
 
42
 
43
  def parse_args(argv):
44
- parser = argparse.ArgumentParser("Codec test: FlexICM C-TAIC")
45
- parser.add_argument("-c", "--config", required=True, help="configs/eval/ctaic_*.yaml")
46
  given, remaining = parser.parse_known_args(argv)
47
  cfg_path = given.config if os.path.isabs(given.config) else os.path.join(REPO_ROOT, given.config)
48
  cfg = load_yaml_config(cfg_path)
49
  parser.set_defaults(**cfg)
50
  parser.add_argument("--actual-bpp", action="store_true")
51
- parser.add_argument("--no-condition", action="store_true", help="Disable base-layer conditioning (TAIC mode)")
 
52
  parser.add_argument("--max-batches", type=int, default=None)
53
  parser.add_argument("--split", type=str, default=None)
54
  args = parser.parse_args(remaining)
@@ -57,10 +61,12 @@ def parse_args(argv):
57
  args.actual_bpp = True
58
  if "--no-condition" in argv:
59
  args.no_condition = True
 
 
60
  return args
61
 
62
 
63
- def build_loader(args, ext_task, device):
64
  split = args.split or getattr(args, "split", None) or "val2017"
65
  tf = build_test_transform()
66
  root = args.dataset_path
@@ -81,6 +87,24 @@ def build_loader(args, ext_task, device):
81
  )
82
 
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  def main(argv):
85
  args = parse_args(argv)
86
  set_seed(getattr(args, "seed", 42))
@@ -118,38 +142,23 @@ def main(argv):
118
  teacher = build_teacher(ext_task, pretrained_backbone=getattr(args, "pretrained_backbone", True))
119
  teacher = teacher.to(device).eval()
120
  criterion = TAICCriterion(lmbda=lmbda, align_mode=align_mode)
 
121
 
122
- loader = build_loader(args, ext_task, device)
123
- print(
124
- f"Test set size: {len(loader.dataset)} device={device} "
125
- f"scenario={scenario} use_condition={use_condition}"
126
- )
127
-
128
- result = test_ctaic_loader(
129
  net,
130
  base,
131
  teacher,
132
- loader,
133
  criterion,
134
  device,
135
  use_condition=use_condition,
136
- align_divisor=256,
137
  run_actual_bpp=bool(getattr(args, "actual_bpp", False)),
138
  max_batches=args.max_batches,
139
  )
140
-
141
  print("==== C-TAIC codec test summary ====")
142
- for k, v in result.items():
143
- if isinstance(v, float):
144
- print(f" {k}: {v:.6f}")
145
- else:
146
- print(f" {k}: {v}")
147
 
148
- out_dir = getattr(args, "result_dir", None) or os.path.join(
149
- REPO_ROOT, "logs", "eval_ctaic", scenario, str(getattr(args, "quality_level", 1))
150
- )
151
- os.makedirs(out_dir, exist_ok=True)
152
- out_json = os.path.join(out_dir, f"codec_test_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json")
153
  payload = {
154
  "scenario": scenario,
155
  "base_task": base_task,
@@ -157,10 +166,50 @@ def main(argv):
157
  "checkpoint": ext_ckpt,
158
  "base_taic_checkpoint": base_ckpt,
159
  "config": args.config,
160
- "result": result,
161
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  with open(out_json, "w") as f:
163
- json.dump(payload, f, indent=2)
164
  print(f"Wrote {out_json}")
165
  return 0
166
 
 
1
  #!/usr/bin/env python3
2
+ """Test / eval for C-TAIC: codec stats + optional full task-network metrics.
3
 
4
+ Examples:
 
 
 
 
5
  python scripts/eval_ctaic.py -c configs/eval/ctaic_s1.yaml
6
+ python scripts/eval_ctaic.py -c configs/eval/ctaic_s1.yaml --with-metrics
7
+ python scripts/eval_ctaic.py -c configs/eval/ctaic_s1.yaml --no-condition --with-metrics
8
  """
9
 
10
  from __future__ import annotations
 
23
  sys.path.insert(0, REPO_ROOT)
24
 
25
  from flexicm.data import COCOImageDataset, COCOWholeBodyImageDataset, ImageFolderDataset, build_test_transform
26
+ from flexicm.data.coco_eval import COCOEvalDataset, TASK_ANN_FILES, coco_eval_collate
27
  from flexicm.models import CTAIC, TAIC
28
  from flexicm.tasks import TASK_META, build_teacher
29
  from flexicm.tasks.losses import TAICCriterion
30
+ from flexicm.tasks.metric_eval import run_task_metric_eval
31
+ from flexicm.tasks.metric_runners import (
32
+ DEFAULT_TASK_NET_CKPTS,
33
+ DEFAULT_TASK_NET_CONFIGS,
34
+ build_metric_runner,
35
+ )
36
  from flexicm.utils.codec_test import resolve_ckpt, test_ctaic_loader
37
  from flexicm.utils.train_utils import load_checkpoint_dict, load_yaml_config, set_seed
38
 
 
44
 
45
 
46
  def parse_args(argv):
47
+ parser = argparse.ArgumentParser("Test FlexICM C-TAIC (codec + optional task metrics)")
48
+ parser.add_argument("-c", "--config", required=True)
49
  given, remaining = parser.parse_known_args(argv)
50
  cfg_path = given.config if os.path.isabs(given.config) else os.path.join(REPO_ROOT, given.config)
51
  cfg = load_yaml_config(cfg_path)
52
  parser.set_defaults(**cfg)
53
  parser.add_argument("--actual-bpp", action="store_true")
54
+ parser.add_argument("--no-condition", action="store_true")
55
+ parser.add_argument("--with-metrics", action="store_true")
56
  parser.add_argument("--max-batches", type=int, default=None)
57
  parser.add_argument("--split", type=str, default=None)
58
  args = parser.parse_args(remaining)
 
61
  args.actual_bpp = True
62
  if "--no-condition" in argv:
63
  args.no_condition = True
64
+ if "--with-metrics" in argv:
65
+ args.with_metrics = True
66
  return args
67
 
68
 
69
+ def build_codec_loader(args, ext_task, device):
70
  split = args.split or getattr(args, "split", None) or "val2017"
71
  tf = build_test_transform()
72
  root = args.dataset_path
 
87
  )
88
 
89
 
90
+ def build_metric_loader(args, ext_task, device):
91
+ split = args.split or getattr(args, "split", None) or "val2017"
92
+ ann_rel = getattr(args, "ann_file", None) or TASK_ANN_FILES.get(ext_task)
93
+ ann_file = ann_rel if os.path.isabs(ann_rel) else os.path.join(args.dataset_path, ann_rel)
94
+ dataset = COCOEvalDataset(
95
+ args.dataset_path, ann_file=ann_file, image_prefix=split, transform=build_test_transform()
96
+ )
97
+ loader = DataLoader(
98
+ dataset,
99
+ batch_size=1,
100
+ shuffle=False,
101
+ num_workers=getattr(args, "num_workers", 4),
102
+ pin_memory=(device == "cuda"),
103
+ collate_fn=coco_eval_collate,
104
+ )
105
+ return loader, ann_file
106
+
107
+
108
  def main(argv):
109
  args = parse_args(argv)
110
  set_seed(getattr(args, "seed", 42))
 
142
  teacher = build_teacher(ext_task, pretrained_backbone=getattr(args, "pretrained_backbone", True))
143
  teacher = teacher.to(device).eval()
144
  criterion = TAICCriterion(lmbda=lmbda, align_mode=align_mode)
145
+ codec_loader = build_codec_loader(args, ext_task, device)
146
 
147
+ codec_result = test_ctaic_loader(
 
 
 
 
 
 
148
  net,
149
  base,
150
  teacher,
151
+ codec_loader,
152
  criterion,
153
  device,
154
  use_condition=use_condition,
 
155
  run_actual_bpp=bool(getattr(args, "actual_bpp", False)),
156
  max_batches=args.max_batches,
157
  )
 
158
  print("==== C-TAIC codec test summary ====")
159
+ for k, v in codec_result.items():
160
+ print(f" {k}: {v:.6f}" if isinstance(v, float) else f" {k}: {v}")
 
 
 
161
 
 
 
 
 
 
162
  payload = {
163
  "scenario": scenario,
164
  "base_task": base_task,
 
166
  "checkpoint": ext_ckpt,
167
  "base_taic_checkpoint": base_ckpt,
168
  "config": args.config,
169
+ "codec_result": codec_result,
170
  }
171
+
172
+ if getattr(args, "with_metrics", False):
173
+ task_cfg = getattr(args, "task_config", None) or DEFAULT_TASK_NET_CONFIGS[ext_task]
174
+ task_ckpt = getattr(args, "task_checkpoint", None) or DEFAULT_TASK_NET_CKPTS[ext_task]
175
+ if not os.path.isabs(task_cfg):
176
+ task_cfg = os.path.join(REPO_ROOT, task_cfg)
177
+ task_ckpt = resolve_ckpt(task_ckpt, REPO_ROOT, label=f"{ext_task} task-network checkpoint")
178
+
179
+ print(f"[metric] loading extension task network:\n config={task_cfg}\n ckpt={task_ckpt}")
180
+ runner = build_metric_runner(ext_task, device=device)
181
+ runner.load(task_cfg, task_ckpt)
182
+
183
+ metric_loader, ann_file = build_metric_loader(args, ext_task, device)
184
+ metrics = run_task_metric_eval(
185
+ net,
186
+ runner,
187
+ metric_loader,
188
+ device,
189
+ ann_file=ann_file,
190
+ use_condition=use_condition,
191
+ base_codec=base if use_condition else None,
192
+ max_batches=args.max_batches,
193
+ finalize_kwargs={
194
+ "gt_folder": getattr(args, "panoptic_gt_folder", None),
195
+ "pred_folder": getattr(args, "panoptic_pred_folder", None),
196
+ "num_classes": getattr(args, "num_classes", 133),
197
+ },
198
+ )
199
+ print("==== C-TAIC task metric summary ====")
200
+ for k, v in metrics.items():
201
+ print(f" {k}: {v}")
202
+ payload["task_config"] = task_cfg
203
+ payload["task_checkpoint"] = task_ckpt
204
+ payload["task_metrics"] = metrics
205
+
206
+ out_dir = getattr(args, "result_dir", None) or os.path.join(
207
+ REPO_ROOT, "logs", "eval_ctaic", scenario, str(getattr(args, "quality_level", 1))
208
+ )
209
+ os.makedirs(out_dir, exist_ok=True)
210
+ out_json = os.path.join(out_dir, f"eval_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json")
211
  with open(out_json, "w") as f:
212
+ json.dump(payload, f, indent=2, default=str)
213
  print(f"Wrote {out_json}")
214
  return 0
215
 
scripts/eval_taic.py CHANGED
@@ -1,12 +1,17 @@
1
  #!/usr/bin/env python3
2
- """Codec test for TAIC (base layer).
3
 
4
- Reports likelihood bpp, feature-alignment distortion D, and optional actual bitstream bpp.
5
- Does NOT compute task metrics (mAP / mIoU / PQ / OKS) — those come later.
6
 
7
- Example:
 
 
 
 
8
  python scripts/eval_taic.py -c configs/eval/taic_detection.yaml
9
- python scripts/eval_taic.py -c configs/eval/taic_detection.yaml --actual-bpp
 
10
  """
11
 
12
  from __future__ import annotations
@@ -30,31 +35,41 @@ from flexicm.data import (
30
  ImageFolderDataset,
31
  build_test_transform,
32
  )
 
33
  from flexicm.models import TAIC
34
  from flexicm.tasks import TASK_META, build_teacher
35
  from flexicm.tasks.losses import TAICCriterion
 
 
 
 
 
 
36
  from flexicm.utils.codec_test import resolve_ckpt, test_taic_loader
37
  from flexicm.utils.train_utils import load_checkpoint_dict, load_yaml_config, set_seed
38
 
39
 
40
  def parse_args(argv):
41
- parser = argparse.ArgumentParser("Codec test: FlexICM TAIC")
42
  parser.add_argument("-c", "--config", required=True, help="configs/eval/taic_*.yaml")
43
  given, remaining = parser.parse_known_args(argv)
44
  cfg_path = given.config if os.path.isabs(given.config) else os.path.join(REPO_ROOT, given.config)
45
  cfg = load_yaml_config(cfg_path)
46
  parser.set_defaults(**cfg)
47
- parser.add_argument("--actual-bpp", action="store_true", help="Also run compress/decompress bpp")
48
- parser.add_argument("--max-batches", type=int, default=None, help="Limit batches for a smoke test")
49
- parser.add_argument("--split", type=str, default=None, help="Override image split folder, e.g. val2017")
 
50
  args = parser.parse_args(remaining)
51
  args.config = cfg_path
52
- if given.__dict__.get("actual_bpp") or "--actual-bpp" in argv:
53
  args.actual_bpp = True
 
 
54
  return args
55
 
56
 
57
- def build_loader(args, device):
58
  split = args.split or getattr(args, "split", None) or "val2017"
59
  tf = build_test_transform()
60
  root = args.dataset_path
@@ -66,7 +81,6 @@ def build_loader(args, device):
66
  dataset = COCOImageDataset(root, split, tf)
67
  else:
68
  dataset = ImageFolderDataset(root, tf)
69
-
70
  return DataLoader(
71
  dataset,
72
  batch_size=getattr(args, "test_batch_size", 1),
@@ -76,6 +90,29 @@ def build_loader(args, device):
76
  )
77
 
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  def main(argv):
80
  args = parse_args(argv)
81
  set_seed(getattr(args, "seed", 42))
@@ -98,44 +135,77 @@ def main(argv):
98
  print(f"load_state_dict: missing={len(missing.missing_keys)} unexpected={len(missing.unexpected_keys)}")
99
  net.eval()
100
 
 
101
  teacher = build_teacher(task, pretrained_backbone=getattr(args, "pretrained_backbone", True))
102
  teacher = teacher.to(device).eval()
103
  criterion = TAICCriterion(lmbda=lmbda, align_mode=align_mode)
 
 
104
 
105
- loader = build_loader(args, device)
106
- print(f"Test set size: {len(loader.dataset)} device={device} task={task}")
107
-
108
- result = test_taic_loader(
109
  net,
110
  teacher,
111
- loader,
112
  criterion,
113
  device,
114
  align_divisor=256,
115
  run_actual_bpp=bool(getattr(args, "actual_bpp", False)),
116
  max_batches=args.max_batches,
117
  )
118
-
119
  print("==== TAIC codec test summary ====")
120
- for k, v in result.items():
121
- if isinstance(v, float):
122
- print(f" {k}: {v:.6f}")
123
- else:
124
- print(f" {k}: {v}")
125
 
126
- out_dir = getattr(args, "result_dir", None) or os.path.join(
127
- REPO_ROOT, "logs", "eval_taic", task, str(getattr(args, "quality_level", 1))
128
- )
129
- os.makedirs(out_dir, exist_ok=True)
130
- out_json = os.path.join(out_dir, f"codec_test_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json")
131
  payload = {
132
  "task": task,
133
  "checkpoint": ckpt,
134
  "config": args.config,
135
- "result": result,
136
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  with open(out_json, "w") as f:
138
- json.dump(payload, f, indent=2)
139
  print(f"Wrote {out_json}")
140
  return 0
141
 
 
1
  #!/usr/bin/env python3
2
+ """Test / eval for TAIC: codec stats + optional full task-network metrics.
3
 
4
+ Codec-only (default):
5
+ bpp, feature distortion D, loss; optional --actual-bpp
6
 
7
+ With task metrics (--with-metrics):
8
+ also load official task-network config/checkpoint, run truncated task net from h,
9
+ report mAP-bbox / mAP-mask / mIoU / PQ / mAP-OKS (task-dependent).
10
+
11
+ Examples:
12
  python scripts/eval_taic.py -c configs/eval/taic_detection.yaml
13
+ python scripts/eval_taic.py -c configs/eval/taic_detection.yaml --with-metrics
14
+ python scripts/eval_taic.py -c configs/eval/taic_detection.yaml --with-metrics --max-batches 50
15
  """
16
 
17
  from __future__ import annotations
 
35
  ImageFolderDataset,
36
  build_test_transform,
37
  )
38
+ from flexicm.data.coco_eval import COCOEvalDataset, TASK_ANN_FILES, coco_eval_collate
39
  from flexicm.models import TAIC
40
  from flexicm.tasks import TASK_META, build_teacher
41
  from flexicm.tasks.losses import TAICCriterion
42
+ from flexicm.tasks.metric_eval import run_task_metric_eval
43
+ from flexicm.tasks.metric_runners import (
44
+ DEFAULT_TASK_NET_CKPTS,
45
+ DEFAULT_TASK_NET_CONFIGS,
46
+ build_metric_runner,
47
+ )
48
  from flexicm.utils.codec_test import resolve_ckpt, test_taic_loader
49
  from flexicm.utils.train_utils import load_checkpoint_dict, load_yaml_config, set_seed
50
 
51
 
52
  def parse_args(argv):
53
+ parser = argparse.ArgumentParser("Test FlexICM TAIC (codec + optional task metrics)")
54
  parser.add_argument("-c", "--config", required=True, help="configs/eval/taic_*.yaml")
55
  given, remaining = parser.parse_known_args(argv)
56
  cfg_path = given.config if os.path.isabs(given.config) else os.path.join(REPO_ROOT, given.config)
57
  cfg = load_yaml_config(cfg_path)
58
  parser.set_defaults(**cfg)
59
+ parser.add_argument("--actual-bpp", action="store_true")
60
+ parser.add_argument("--with-metrics", action="store_true", help="Run full task-network metrics")
61
+ parser.add_argument("--max-batches", type=int, default=None)
62
+ parser.add_argument("--split", type=str, default=None)
63
  args = parser.parse_args(remaining)
64
  args.config = cfg_path
65
+ if "--actual-bpp" in argv:
66
  args.actual_bpp = True
67
+ if "--with-metrics" in argv:
68
+ args.with_metrics = True
69
  return args
70
 
71
 
72
+ def build_codec_loader(args, device):
73
  split = args.split or getattr(args, "split", None) or "val2017"
74
  tf = build_test_transform()
75
  root = args.dataset_path
 
81
  dataset = COCOImageDataset(root, split, tf)
82
  else:
83
  dataset = ImageFolderDataset(root, tf)
 
84
  return DataLoader(
85
  dataset,
86
  batch_size=getattr(args, "test_batch_size", 1),
 
90
  )
91
 
92
 
93
+ def build_metric_loader(args, device):
94
+ split = args.split or getattr(args, "split", None) or "val2017"
95
+ ann_rel = getattr(args, "ann_file", None) or TASK_ANN_FILES.get(args.task)
96
+ if ann_rel is None:
97
+ raise ValueError(f"No ann_file for task={args.task}")
98
+ ann_file = ann_rel if os.path.isabs(ann_rel) else os.path.join(args.dataset_path, ann_rel)
99
+ dataset = COCOEvalDataset(
100
+ args.dataset_path,
101
+ ann_file=ann_file,
102
+ image_prefix=split,
103
+ transform=build_test_transform(),
104
+ )
105
+ loader = DataLoader(
106
+ dataset,
107
+ batch_size=1,
108
+ shuffle=False,
109
+ num_workers=getattr(args, "num_workers", 4),
110
+ pin_memory=(device == "cuda"),
111
+ collate_fn=coco_eval_collate,
112
+ )
113
+ return loader, ann_file
114
+
115
+
116
  def main(argv):
117
  args = parse_args(argv)
118
  set_seed(getattr(args, "seed", 42))
 
135
  print(f"load_state_dict: missing={len(missing.missing_keys)} unexpected={len(missing.unexpected_keys)}")
136
  net.eval()
137
 
138
+ # ---- codec test ----
139
  teacher = build_teacher(task, pretrained_backbone=getattr(args, "pretrained_backbone", True))
140
  teacher = teacher.to(device).eval()
141
  criterion = TAICCriterion(lmbda=lmbda, align_mode=align_mode)
142
+ codec_loader = build_codec_loader(args, device)
143
+ print(f"[codec] test set size: {len(codec_loader.dataset)} device={device} task={task}")
144
 
145
+ codec_result = test_taic_loader(
 
 
 
146
  net,
147
  teacher,
148
+ codec_loader,
149
  criterion,
150
  device,
151
  align_divisor=256,
152
  run_actual_bpp=bool(getattr(args, "actual_bpp", False)),
153
  max_batches=args.max_batches,
154
  )
 
155
  print("==== TAIC codec test summary ====")
156
+ for k, v in codec_result.items():
157
+ print(f" {k}: {v:.6f}" if isinstance(v, float) else f" {k}: {v}")
 
 
 
158
 
 
 
 
 
 
159
  payload = {
160
  "task": task,
161
  "checkpoint": ckpt,
162
  "config": args.config,
163
+ "codec_result": codec_result,
164
  }
165
+
166
+ # ---- optional task metrics ----
167
+ if getattr(args, "with_metrics", False):
168
+ task_cfg = getattr(args, "task_config", None) or DEFAULT_TASK_NET_CONFIGS[task]
169
+ task_ckpt = getattr(args, "task_checkpoint", None) or DEFAULT_TASK_NET_CKPTS[task]
170
+ if not os.path.isabs(task_cfg):
171
+ task_cfg = os.path.join(REPO_ROOT, task_cfg)
172
+ task_ckpt = resolve_ckpt(task_ckpt, REPO_ROOT, label=f"{task} task-network checkpoint")
173
+
174
+ print(f"[metric] loading task network:\n config={task_cfg}\n ckpt={task_ckpt}")
175
+ runner = build_metric_runner(task, device=device)
176
+ runner.load(task_cfg, task_ckpt)
177
+
178
+ metric_loader, ann_file = build_metric_loader(args, device)
179
+ print(f"[metric] COCO eval images: {len(metric_loader.dataset)} ann={ann_file}")
180
+ metrics = run_task_metric_eval(
181
+ net,
182
+ runner,
183
+ metric_loader,
184
+ device,
185
+ ann_file=ann_file,
186
+ use_condition=False,
187
+ base_codec=None,
188
+ max_batches=args.max_batches,
189
+ finalize_kwargs={
190
+ "gt_folder": getattr(args, "panoptic_gt_folder", None),
191
+ "pred_folder": getattr(args, "panoptic_pred_folder", None),
192
+ "num_classes": getattr(args, "num_classes", 133),
193
+ },
194
+ )
195
+ print("==== TAIC task metric summary ====")
196
+ for k, v in metrics.items():
197
+ print(f" {k}: {v}")
198
+ payload["task_config"] = task_cfg
199
+ payload["task_checkpoint"] = task_ckpt
200
+ payload["task_metrics"] = metrics
201
+
202
+ out_dir = getattr(args, "result_dir", None) or os.path.join(
203
+ REPO_ROOT, "logs", "eval_taic", task, str(getattr(args, "quality_level", 1))
204
+ )
205
+ os.makedirs(out_dir, exist_ok=True)
206
+ out_json = os.path.join(out_dir, f"eval_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json")
207
  with open(out_json, "w") as f:
208
+ json.dump(payload, f, indent=2, default=str)
209
  print(f"Wrote {out_json}")
210
  return 0
211