mqye commited on
Commit
e3ca470
Β·
1 Parent(s): 31797ca

Deploy MODUS 3-tab any-to-any demo (ZeroGPU)

Browse files
This view is limited to 50 files because it contains too many changes. Β  See raw diff
Files changed (50) hide show
  1. app.py +47 -0
  2. conf/modalities/bagel_stage3_exact3cond.yaml +0 -191
  3. conf/modalities/hunyuan_image_3.yaml +0 -31
  4. conf/modalities/instruction.yaml +0 -201
  5. conf/modalities/instruction_10modality_stage2.yaml +0 -142
  6. conf/modalities/instruction_16mod_instr.yaml +0 -259
  7. conf/modalities/instruction_16mod_stage1.yaml +0 -243
  8. conf/modalities/instruction_16mod_stage1_rgbcond.yaml +0 -266
  9. conf/modalities/instruction_16mod_stage2.yaml +0 -272
  10. conf/modalities/instruction_2d_only_stage1.yaml +0 -65
  11. conf/modalities/instruction_9modality_stage1.yaml +0 -114
  12. conf/modalities/instruction_9modality_stage3.yaml +0 -115
  13. conf/modalities/instruction_hunyuan_16mod_stage1.yaml +0 -257
  14. conf/modalities/instruction_hunyuan_16mod_stage2.yaml +0 -250
  15. conf/modalities/instruction_stage2.yaml +0 -204
  16. conf/modalities/instruction_t2i_only_stage1.yaml +0 -27
  17. conf/modalities/legacy.yaml +0 -38
  18. conf/modalities/rebuttal_rgb2target.yaml +0 -116
  19. conf/modalities/stage1_oversample_bbox2rgb_dinolocal2rgb.yaml +0 -203
  20. core/__init__.py +0 -11
  21. core/modality.py +0 -394
  22. core/model_registry.py +0 -47
  23. core/tokenizer_utils.py +0 -257
  24. data/__init__.py +0 -2
  25. data/any2any_preprocess/_build_preview_and_montage.py +0 -52
  26. data/any2any_preprocess/_cast_preview_images.py +0 -34
  27. data/any2any_preprocess/_sb_full_rebuild.sh +0 -18
  28. data/any2any_preprocess/_sb_recompress_normal_sample.sh +0 -24
  29. data/any2any_preprocess/_sb_upload.sh +0 -21
  30. data/any2any_preprocess/_verify_normal_q95.py +0 -34
  31. data/any2any_preprocess/build_full_release.py +0 -108
  32. data/any2any_preprocess/check_vqa.py +0 -188
  33. data/any2any_preprocess/generate_parquest_grounding_canny_dino_global.py +0 -205
  34. data/any2any_preprocess/generate_parquet.py +0 -229
  35. data/any2any_preprocess/generate_parquet_clip448_imagebind.py +0 -318
  36. data/any2any_preprocess/generate_parquet_grounding.py +0 -583
  37. data/any2any_preprocess/generate_parquet_grounding_canny_dino.py +0 -627
  38. data/any2any_preprocess/generate_parquet_json.py +0 -69
  39. data/any2any_preprocess/generate_parquet_vlm_sft.py +0 -278
  40. data/any2any_preprocess/hf_upload.py +0 -26
  41. data/any2any_preprocess/parquet_visualize.py +0 -408
  42. data/any2any_preprocess/process_grounding.py +0 -346
  43. data/any2any_preprocess/recompress_normal_jpeg.py +0 -90
  44. data/any2any_preprocess/run_full_rebuild.py +0 -82
  45. data/any2any_preprocess/upload_full.py +0 -26
  46. data/any2any_preprocess/vqa_convert_parquet_to_jsonl.py +0 -312
  47. data/bundled_parquet_info/README.md +0 -43
  48. data/bundled_parquet_info/blip3o_rgb_caption_depth_normal_det_seg_grounding2_canny_dino_global_clip448_imagebind.json +0 -0
  49. data/bundled_parquet_info/blip3o_rgb_caption_depth_normal_det_seg_grounding_canny_dino_global_clip448_imagebind.json +0 -0
  50. data/bundled_parquet_info/blip3o_rgb_caption_depth_normal_det_seg_grounding_canny_dino_global_clip448_imagebind_samseg_samedge_cocodet.json +0 -0
app.py CHANGED
@@ -21,9 +21,42 @@ try:
21
  except Exception as _e:
22
  print(f"[app] torch import failed: {_e}", flush=True)
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  # ── Env MUST be set before importing the demo backend (it reads these at import) ─
25
  os.environ.setdefault("MODUS_NO_MEAN_RESIZING", "1") # avoid gradio-BLAS deadlock
26
  os.environ.setdefault("MODUS_FORCE_SDPA_ATTN", "1") # no flash-attn on the Space
 
27
  os.environ.setdefault("MODUS_DEMO_MODALITY_CONFIG",
28
  "conf/modalities/instruction_16mod_stage2.yaml")
29
  os.environ.setdefault("MODUS_DEMO_MODEL_NAME", "bagel_from_json")
@@ -85,6 +118,20 @@ except Exception as e: # surface in UI, retry lazily on first request
85
  demo_modus.HOLDER.load_error = str(e)
86
  print(f"[app] startup model load failed: {e}", flush=True)
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  # Work around a gradio 4.44.1 bug: get_api_info() (called when rendering the main
89
  # "/" route) crashes with `TypeError: argument of type 'bool' is not iterable` when
90
  # a component's JSON schema contains a bool (additionalProperties: true/false).
 
21
  except Exception as _e:
22
  print(f"[app] torch import failed: {_e}", flush=True)
23
 
24
+ # Teach PIL to decode in-the-wild formats (AVIF, iPhone HEIC). gradio opens each
25
+ # uploaded file with PIL.Image.open before it reaches our code, so registering
26
+ # these openers here (import-time) is what lets those uploads work at all.
27
+ try:
28
+ import pillow_avif # noqa: F401 (registers the AVIF opener on import)
29
+ except Exception as _e:
30
+ print(f"[app] pillow-avif-plugin unavailable: {_e}", flush=True)
31
+ try:
32
+ from pillow_heif import register_heif_opener
33
+ register_heif_opener()
34
+ except Exception as _e:
35
+ print(f"[app] pillow-heif unavailable: {_e}", flush=True)
36
+
37
+ # ── Install the MODUS backend from the (private) GitHub repo at startup ──────────
38
+ # Single source of truth: this Space only carries app.py + fourm/ + test_images/;
39
+ # the demo/inference code (demo_modus, any2any, modeling, data, core, conf, ...)
40
+ # is pip-installed from EPFL-VILAB/Modus. Needs a GH_TOKEN Space secret with read
41
+ # access. --no-deps so it does NOT touch the ZeroGPU torch stack.
42
+ import subprocess # noqa: E402
43
+ _GH = os.environ.get("GH_TOKEN")
44
+ if _GH:
45
+ print("[app] installing modus backend from EPFL-VILAB/Modus ...", flush=True)
46
+ subprocess.run(
47
+ [sys.executable, "-m", "pip", "install", "--no-deps", "--quiet",
48
+ "--force-reinstall", "--no-cache-dir", # always pull the latest repo HEAD
49
+ f"git+https://x-access-token:{_GH}@github.com/EPFL-VILAB/Modus.git"],
50
+ check=True,
51
+ )
52
+ print("[app] modus backend installed.", flush=True)
53
+ else:
54
+ print("[app] GH_TOKEN not set; expecting the modus backend to be present.", flush=True)
55
+
56
  # ── Env MUST be set before importing the demo backend (it reads these at import) ─
57
  os.environ.setdefault("MODUS_NO_MEAN_RESIZING", "1") # avoid gradio-BLAS deadlock
58
  os.environ.setdefault("MODUS_FORCE_SDPA_ATTN", "1") # no flash-attn on the Space
59
+ os.environ.setdefault("MODUS_TORCHVISION_FREE", "1") # ZeroGPU torch has no torchvision
60
  os.environ.setdefault("MODUS_DEMO_MODALITY_CONFIG",
61
  "conf/modalities/instruction_16mod_stage2.yaml")
62
  os.environ.setdefault("MODUS_DEMO_MODEL_NAME", "bagel_from_json")
 
118
  demo_modus.HOLDER.load_error = str(e)
119
  print(f"[app] startup model load failed: {e}", flush=True)
120
 
121
+ # The demo backend is pip-installed (site-packages), so demo_modus.REPO_ROOT points
122
+ # into site-packages and its `test_images/` lookup finds nothing. The example images
123
+ # live in THIS Space repo (CWD /home/user/app/test_images), so point the example
124
+ # gallery there before build_ui() reads it.
125
+ _EX_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_images")
126
+ def _space_example_images():
127
+ if not os.path.isdir(_EX_DIR):
128
+ return []
129
+ return [os.path.join(_EX_DIR, f) for f in sorted(os.listdir(_EX_DIR))
130
+ if f.lower().endswith((".jpg", ".jpeg", ".png", ".webp"))
131
+ and "_seg." not in f.lower()] # exclude precomputed seg previews
132
+ demo_modus._example_images = _space_example_images
133
+ print(f"[app] {len(_space_example_images())} example images from {_EX_DIR}", flush=True)
134
+
135
  # Work around a gradio 4.44.1 bug: get_api_info() (called when rendering the main
136
  # "/" route) crashes with `TypeError: argument of type 'bool' is not iterable` when
137
  # a component's JSON schema contains a bool (additionalProperties: true/false).
conf/modalities/bagel_stage3_exact3cond.yaml DELETED
@@ -1,191 +0,0 @@
1
- # Stage 3 modality config for the standalone BAGEL exact-3-condition project.
2
- # Intentionally duplicated from instruction_stage2.yaml so this project can
3
- # evolve independently without affecting MODUS experiments.
4
-
5
- modalities:
6
- - name: text
7
- id: 0
8
- kind: text
9
- start_token_key: bos_token_id
10
- end_token_key: eos_token_id
11
-
12
- - name: caption
13
- id: 1
14
- kind: text
15
- start_token_key: bos_token_id
16
- end_token_key: eos_token_id
17
- represent_vae: true
18
-
19
- - name: rgb
20
- id: 2
21
- kind: image
22
- start_token_key: start_of_image
23
- end_token_key: end_of_image
24
- represent_vit: true
25
- represent_vae: true
26
-
27
- - name: depth
28
- id: 3
29
- kind: image
30
- start_token_key: start_of_image
31
- end_token_key: end_of_image
32
- represent_vit: true
33
- represent_vae: true
34
-
35
- - name: normal
36
- id: 4
37
- kind: image
38
- start_token_key: start_of_image
39
- end_token_key: end_of_image
40
- represent_vit: true
41
- represent_vae: true
42
-
43
- - name: det
44
- id: 5
45
- kind: codebook
46
- start_token_key: start_of_det
47
- end_token_key: end_of_det
48
- represent_vae: true
49
- pos_embed_size: 4
50
- apply_pos_embed_in_forward: true
51
- pos_embed_name: grounding
52
- loss:
53
- reweight: true
54
- reweight_min_w: 0.005
55
- start_token: "<|det_start|>"
56
- end_token: "<|det_end|>"
57
- extra_tokens: ["<|box_start|>", "<|box_end|>"]
58
- code_token_groups:
59
- - token_format: "<|x1_{i:03d}|>"
60
- start: 0
61
- end: 999
62
- - token_format: "<|y1_{i:03d}|>"
63
- start: 0
64
- end: 999
65
- - token_format: "<|x2_{i:03d}|>"
66
- start: 0
67
- end: 999
68
- - token_format: "<|y2_{i:03d}|>"
69
- start: 0
70
- end: 999
71
- - token_format: "<|score_{i:02d}|>"
72
- start: 0
73
- end: 99
74
- inference_decode_method: detection
75
- inference_max_tokens: 1000
76
- inference_cfg_uncond: text
77
- inference_add_instruction: false
78
-
79
- - name: seg
80
- id: 6
81
- kind: image
82
- start_token_key: start_of_image
83
- end_token_key: end_of_image
84
- represent_vit: true
85
- represent_vae: true
86
-
87
- - name: canny
88
- id: 7
89
- kind: image
90
- start_token_key: start_of_image
91
- end_token_key: end_of_image
92
- represent_vit: true
93
- represent_vae: true
94
-
95
- - name: dino
96
- id: 8
97
- kind: codebook
98
- start_token_key: start_of_dino
99
- end_token_key: end_of_dino
100
- pos_embed_size: 16
101
- apply_pos_embed_in_forward: true
102
- represent_vae: true
103
- start_token: "<|dino_start|>"
104
- end_token: "<|dino_end|>"
105
- code_vocab_size: 8192
106
- code_token_format: "<|dino_{i:04d}|>"
107
- external_tokenizer_kind: vqvae
108
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14-global_8k_16_224"
109
- inference_decode_method: dino
110
- inference_max_tokens: 17
111
- inference_cfg_uncond: img
112
- inference_cfg_img_scale: 1.0
113
-
114
- - name: dinolocal
115
- id: 9
116
- kind: codebook
117
- start_token_key: start_of_dinolocal
118
- end_token_key: end_of_dinolocal
119
- pos_embed_size: 1024
120
- apply_pos_embed_in_forward: true
121
- represent_vae: true
122
- start_token: "<|dinolocal_start|>"
123
- end_token: "<|dinolocal_end|>"
124
- code_vocab_size: 8192
125
- code_token_format: "<|dinolocal_{i:04d}|>"
126
- codebook_spatial_shape: [32, 32]
127
- external_tokenizer_kind: vqvae
128
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14_8k_224-448"
129
- inference_decode_method: dinolocal
130
- inference_max_tokens: 1025
131
- inference_cfg_uncond: img
132
- inference_cfg_img_scale: 1.0
133
-
134
- - name: clip
135
- id: 10
136
- kind: codebook
137
- start_token_key: start_of_clip
138
- end_token_key: end_of_clip
139
- pos_embed_size: 784
140
- apply_pos_embed_in_forward: true
141
- represent_vae: true
142
- start_token: "<|clip_start|>"
143
- end_token: "<|clip_end|>"
144
- code_vocab_size: 8192
145
- code_token_format: "<|clip_{i:04d}|>"
146
- codebook_spatial_shape: [28, 28]
147
- external_tokenizer_kind: vqvae
148
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_CLIP-B16_8k_224-448"
149
- inference_decode_method: clip
150
- inference_max_tokens: 785
151
- inference_cfg_uncond: img
152
- inference_cfg_img_scale: 1.0
153
-
154
- - name: imagebind
155
- id: 11
156
- kind: codebook
157
- start_token_key: start_of_imagebind
158
- end_token_key: end_of_imagebind
159
- pos_embed_size: 16
160
- apply_pos_embed_in_forward: true
161
- represent_vae: true
162
- start_token: "<|imagebind_start|>"
163
- end_token: "<|imagebind_end|>"
164
- code_vocab_size: 8192
165
- code_token_format: "<|imagebind_{i:04d}|>"
166
- external_tokenizer_kind: vqvae
167
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14-global_8k_16_224"
168
- inference_decode_method: imagebind
169
- inference_max_tokens: 17
170
- inference_cfg_uncond: img
171
- inference_cfg_img_scale: 1.0
172
-
173
- - name: imagebindlocal
174
- id: 12
175
- kind: codebook
176
- start_token_key: start_of_imagebindlocal
177
- end_token_key: end_of_imagebindlocal
178
- pos_embed_size: 1024
179
- apply_pos_embed_in_forward: true
180
- represent_vae: true
181
- start_token: "<|imagebindlocal_start|>"
182
- end_token: "<|imagebindlocal_end|>"
183
- code_vocab_size: 8192
184
- code_token_format: "<|imagebindlocal_{i:04d}|>"
185
- codebook_spatial_shape: [32, 32]
186
- external_tokenizer_kind: vqvae
187
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14_8k_224-448"
188
- inference_decode_method: imagebindlocal
189
- inference_max_tokens: 1025
190
- inference_cfg_uncond: img
191
- inference_cfg_img_scale: 1.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/hunyuan_image_3.yaml DELETED
@@ -1,31 +0,0 @@
1
- # Modality configuration for HunyuanImage-3.0 training.
2
- #
3
- # Only the RGB image modality is needed for text-to-image generation.
4
- # Token keys (start_of_image / end_of_image) are populated by
5
- # data_utils.add_special_tokens(), which adds <|vision_start|> and
6
- # <|vision_end|> to the tokenizer when they are absent.
7
- # These serve as sequence delimiters; actual image patches are placed at
8
- # packed_vae_token_indexes and overwritten by the model's patch_embed.
9
-
10
- modalities:
11
- - name: text
12
- id: 0
13
- kind: text
14
- start_token_key: bos_token_id
15
- end_token_key: eos_token_id
16
-
17
- - name: caption
18
- id: 1
19
- kind: text
20
- start_token_key: bos_token_id
21
- end_token_key: eos_token_id
22
- represent_vae: true
23
- conditions: [rgb]
24
-
25
- - name: rgb
26
- id: 2
27
- kind: image
28
- start_token_key: start_of_image
29
- end_token_key: end_of_image
30
- represent_vae: true
31
- represent_vit: true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/instruction.yaml DELETED
@@ -1,201 +0,0 @@
1
- modalities:
2
- - name: text
3
- id: 0
4
- kind: text
5
- start_token_key: bos_token_id
6
- end_token_key: eos_token_id
7
-
8
- - name: caption
9
- id: 1
10
- kind: text
11
- start_token_key: bos_token_id
12
- end_token_key: eos_token_id
13
- represent_vae: true
14
- conditions: [rgb, grounding, dino, dinolocal, clip, imagebind, imagebindlocal]
15
-
16
- - name: rgb
17
- id: 2
18
- kind: image
19
- start_token_key: start_of_image
20
- end_token_key: end_of_image
21
- represent_vit: true
22
- represent_vae: true
23
- conditions: [caption, grounding, dino, dinolocal, clip, imagebind, imagebindlocal]
24
-
25
- - name: depth
26
- id: 3
27
- kind: image
28
- start_token_key: start_of_image
29
- end_token_key: end_of_image
30
- represent_vit: true
31
- represent_vae: true
32
-
33
- - name: normal
34
- id: 4
35
- kind: image
36
- start_token_key: start_of_image
37
- end_token_key: end_of_image
38
- represent_vit: true
39
- represent_vae: true
40
-
41
- - name: det
42
- id: 5
43
- kind: codebook
44
- start_token_key: start_of_det
45
- end_token_key: end_of_det
46
- represent_vae: true
47
- pos_embed_size: 4
48
- apply_pos_embed_in_forward: true
49
- pos_embed_name: grounding # checkpoint compat: attr is "grounding_pos_embed"
50
- conditions: [rgb]
51
- loss:
52
- reweight: true
53
- reweight_min_w: 0.005
54
- start_token: "<|det_start|>"
55
- end_token: "<|det_end|>"
56
- extra_tokens: ["<|box_start|>", "<|box_end|>"]
57
- code_token_groups:
58
- - token_format: "<|x1_{i:03d}|>"
59
- start: 0
60
- end: 999
61
- - token_format: "<|y1_{i:03d}|>"
62
- start: 0
63
- end: 999
64
- - token_format: "<|x2_{i:03d}|>"
65
- start: 0
66
- end: 999
67
- - token_format: "<|y2_{i:03d}|>"
68
- start: 0
69
- end: 999
70
- - token_format: "<|score_{i:02d}|>"
71
- start: 0
72
- end: 99
73
- # Inference pipeline config
74
- inference_decode_method: detection
75
- inference_max_tokens: 1000
76
- inference_cfg_uncond: text
77
- inference_add_instruction: false # det uses start_of_det token, not text instruction
78
-
79
- - name: seg
80
- id: 6
81
- kind: image
82
- start_token_key: start_of_image
83
- end_token_key: end_of_image
84
- represent_vit: true
85
- represent_vae: true
86
-
87
- - name: canny
88
- id: 7
89
- kind: image
90
- start_token_key: start_of_image
91
- end_token_key: end_of_image
92
- represent_vit: true
93
- represent_vae: true
94
-
95
- - name: dino
96
- id: 8
97
- kind: codebook
98
- start_token_key: start_of_dino
99
- end_token_key: end_of_dino
100
- pos_embed_size: 16
101
- apply_pos_embed_in_forward: true
102
- represent_vae: true
103
- conditions: [rgb]
104
- start_token: "<|dino_start|>"
105
- end_token: "<|dino_end|>"
106
- code_vocab_size: 8192
107
- code_token_format: "<|dino_{i:04d}|>"
108
- external_tokenizer_kind: vqvae
109
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14-global_8k_16_224"
110
- # Inference pipeline config
111
- inference_decode_method: dino
112
- inference_max_tokens: 17
113
- inference_cfg_uncond: img
114
- inference_cfg_img_scale: 1.0
115
-
116
- - name: dinolocal
117
- id: 9
118
- kind: codebook
119
- start_token_key: start_of_dinolocal
120
- end_token_key: end_of_dinolocal
121
- pos_embed_size: 1024
122
- apply_pos_embed_in_forward: true
123
- represent_vae: true
124
- conditions: [rgb]
125
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
126
- start_token: "<|dinolocal_start|>"
127
- end_token: "<|dinolocal_end|>"
128
- code_vocab_size: 8192
129
- code_token_format: "<|dinolocal_{i:04d}|>"
130
- external_tokenizer_kind: vqvae
131
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14_8k_224-448"
132
- # Inference pipeline config
133
- inference_decode_method: dinolocal
134
- inference_max_tokens: 1025
135
- inference_cfg_uncond: img
136
- inference_cfg_img_scale: 1.0
137
-
138
- - name: clip
139
- id: 10
140
- kind: codebook
141
- start_token_key: start_of_clip
142
- end_token_key: end_of_clip
143
- pos_embed_size: 784
144
- apply_pos_embed_in_forward: true
145
- represent_vae: true
146
- conditions: [rgb]
147
- codebook_spatial_shape: [28, 28] # 784 tokens, 28Γ—28 grid β†’ 2D RoPE
148
- start_token: "<|clip_start|>"
149
- end_token: "<|clip_end|>"
150
- code_vocab_size: 8192
151
- code_token_format: "<|clip_{i:04d}|>"
152
- external_tokenizer_kind: vqvae
153
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_CLIP-B16_8k_224-448"
154
- # Inference pipeline config
155
- inference_decode_method: clip
156
- inference_max_tokens: 785
157
- inference_cfg_uncond: img
158
- inference_cfg_img_scale: 1.0
159
-
160
- - name: imagebind
161
- id: 11
162
- kind: codebook
163
- start_token_key: start_of_imagebind
164
- end_token_key: end_of_imagebind
165
- pos_embed_size: 16
166
- apply_pos_embed_in_forward: true
167
- represent_vae: true
168
- conditions: [rgb]
169
- start_token: "<|imagebind_start|>"
170
- end_token: "<|imagebind_end|>"
171
- code_vocab_size: 8192
172
- code_token_format: "<|imagebind_{i:04d}|>"
173
- external_tokenizer_kind: vqvae
174
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14-global_8k_16_224"
175
- # Inference pipeline config
176
- inference_decode_method: imagebind
177
- inference_max_tokens: 17
178
- inference_cfg_uncond: img
179
- inference_cfg_img_scale: 1.0
180
-
181
- - name: imagebindlocal
182
- id: 12
183
- kind: codebook
184
- start_token_key: start_of_imagebindlocal
185
- end_token_key: end_of_imagebindlocal
186
- pos_embed_size: 1024
187
- apply_pos_embed_in_forward: true
188
- represent_vae: true
189
- conditions: [rgb]
190
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
191
- start_token: "<|imagebindlocal_start|>"
192
- end_token: "<|imagebindlocal_end|>"
193
- code_vocab_size: 8192
194
- code_token_format: "<|imagebindlocal_{i:04d}|>"
195
- external_tokenizer_kind: vqvae
196
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14_8k_224-448"
197
- # Inference pipeline config
198
- inference_decode_method: imagebindlocal
199
- inference_max_tokens: 1025
200
- inference_cfg_uncond: img
201
- inference_cfg_img_scale: 1.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/instruction_10modality_stage2.yaml DELETED
@@ -1,142 +0,0 @@
1
- # Stage 2 modality config β€” unconstrained conditions.
2
- #
3
- # Same modality definitions as instruction.yaml, but with ALL condition
4
- # restrictions removed so every modality can be conditioned on every other.
5
-
6
- modalities:
7
- - name: text
8
- id: 0
9
- kind: text
10
- start_token_key: bos_token_id
11
- end_token_key: eos_token_id
12
-
13
- - name: caption
14
- id: 1
15
- kind: text
16
- start_token_key: bos_token_id
17
- end_token_key: eos_token_id
18
- represent_vae: true
19
- # conditions: unconstrained (any modality can condition caption)
20
-
21
- - name: rgb
22
- id: 2
23
- kind: image
24
- start_token_key: start_of_image
25
- end_token_key: end_of_image
26
- represent_vit: true
27
- represent_vae: true
28
- # conditions: unconstrained
29
-
30
- - name: depth
31
- id: 3
32
- kind: image
33
- start_token_key: start_of_image
34
- end_token_key: end_of_image
35
- represent_vit: true
36
- represent_vae: true
37
-
38
- - name: normal
39
- id: 4
40
- kind: image
41
- start_token_key: start_of_image
42
- end_token_key: end_of_image
43
- represent_vit: true
44
- represent_vae: true
45
-
46
- - name: det
47
- id: 5
48
- kind: codebook
49
- start_token_key: start_of_det
50
- end_token_key: end_of_det
51
- represent_vae: true
52
- pos_embed_size: 4
53
- apply_pos_embed_in_forward: true
54
- pos_embed_name: grounding # checkpoint compat: attr is "grounding_pos_embed"
55
- # conditions: unconstrained
56
- loss:
57
- reweight: true
58
- reweight_min_w: 0.005
59
- start_token: "<|det_start|>"
60
- end_token: "<|det_end|>"
61
- extra_tokens: ["<|box_start|>", "<|box_end|>"]
62
- code_token_groups:
63
- - token_format: "<|x1_{i:03d}|>"
64
- start: 0
65
- end: 999
66
- - token_format: "<|y1_{i:03d}|>"
67
- start: 0
68
- end: 999
69
- - token_format: "<|x2_{i:03d}|>"
70
- start: 0
71
- end: 999
72
- - token_format: "<|y2_{i:03d}|>"
73
- start: 0
74
- end: 999
75
- - token_format: "<|score_{i:02d}|>"
76
- start: 0
77
- end: 99
78
- # Inference pipeline config
79
- inference_decode_method: detection
80
- inference_max_tokens: 1000
81
- inference_cfg_uncond: text
82
- inference_add_instruction: false # det uses start_of_det token, not text instruction
83
-
84
- - name: seg
85
- id: 6
86
- kind: image
87
- start_token_key: start_of_image
88
- end_token_key: end_of_image
89
- represent_vit: true
90
- represent_vae: true
91
-
92
- - name: canny
93
- id: 7
94
- kind: image
95
- start_token_key: start_of_image
96
- end_token_key: end_of_image
97
- represent_vit: true
98
- represent_vae: true
99
-
100
- - name: dino
101
- id: 8
102
- kind: codebook
103
- start_token_key: start_of_dino
104
- end_token_key: end_of_dino
105
- pos_embed_size: 16
106
- apply_pos_embed_in_forward: true
107
- represent_vae: true
108
- # conditions: unconstrained
109
- start_token: "<|dino_start|>"
110
- end_token: "<|dino_end|>"
111
- code_vocab_size: 8192
112
- code_token_format: "<|dino_{i:04d}|>"
113
- external_tokenizer_kind: vqvae
114
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14-global_8k_16_224"
115
- # Inference pipeline config
116
- inference_decode_method: dino
117
- inference_max_tokens: 17
118
- inference_cfg_uncond: img
119
- inference_cfg_img_scale: 1.0
120
-
121
- - name: dinolocal
122
- id: 9
123
- kind: codebook
124
- start_token_key: start_of_dinolocal
125
- end_token_key: end_of_dinolocal
126
- pos_embed_size: 1024
127
- apply_pos_embed_in_forward: true
128
- represent_vae: true
129
- # conditions: unconstrained
130
- start_token: "<|dinolocal_start|>"
131
- end_token: "<|dinolocal_end|>"
132
- code_vocab_size: 8192
133
- code_token_format: "<|dinolocal_{i:04d}|>"
134
- external_tokenizer_kind: vqvae
135
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14_8k_224-448"
136
- # Inference pipeline config
137
- inference_decode_method: dinolocal
138
- inference_max_tokens: 1025
139
- inference_cfg_uncond: img
140
- inference_cfg_img_scale: 1.0
141
-
142
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/instruction_16mod_instr.yaml DELETED
@@ -1,259 +0,0 @@
1
- modalities:
2
- - name: text
3
- id: 0
4
- kind: text
5
- start_token_key: bos_token_id
6
- end_token_key: eos_token_id
7
-
8
- - name: caption
9
- id: 1
10
- kind: text
11
- start_token_key: bos_token_id
12
- end_token_key: eos_token_id
13
- represent_vae: true
14
-
15
- - name: rgb
16
- id: 2
17
- kind: image
18
- start_token_key: start_of_image
19
- end_token_key: end_of_image
20
- represent_vit: true
21
- represent_vae: true
22
-
23
- - name: depth
24
- id: 3
25
- kind: image
26
- start_token_key: start_of_depth # REPLACE: per-modality start (token already exists in base vocab)
27
- start_token: "<|depth_start|>"
28
- end_token_key: end_of_image # shared end (image end is a structural terminator)
29
- represent_vit: true
30
- represent_vae: true
31
-
32
- - name: normal
33
- id: 4
34
- kind: image
35
- start_token_key: start_of_normal # REPLACE: per-modality start (token already exists in base vocab)
36
- start_token: "<|normal_start|>"
37
- end_token_key: end_of_image
38
- represent_vit: true
39
- represent_vae: true
40
-
41
- - name: det
42
- id: 5
43
- kind: codebook
44
- start_token_key: start_of_det
45
- end_token_key: end_of_det
46
- represent_vae: true
47
- # NOTE: det's learnable pos_embed (grounding_pos_embed, size 4) is REMOVED here.
48
- # The forward pos_embed loop is keyed by token-id range, and cocodet reuses
49
- # det's x1/y1/x2/y2 tokens; with the pos_embed on, a multi-box cocodet sample
50
- # (>4 coord tokens) would index the size-4 embedding out of bounds. det is not
51
- # trained in modus (only present for coord-token-id alignment), so dropping its
52
- # pos_embed is safe. reweight also removed (no reweight for det/cocodet).
53
- start_token: "<|det_start|>"
54
- end_token: "<|det_end|>"
55
- extra_tokens: ["<|box_start|>", "<|box_end|>"]
56
- code_token_groups:
57
- - token_format: "<|x1_{i:03d}|>"
58
- start: 0
59
- end: 999
60
- - token_format: "<|y1_{i:03d}|>"
61
- start: 0
62
- end: 999
63
- - token_format: "<|x2_{i:03d}|>"
64
- start: 0
65
- end: 999
66
- - token_format: "<|y2_{i:03d}|>"
67
- start: 0
68
- end: 999
69
- - token_format: "<|score_{i:02d}|>"
70
- start: 0
71
- end: 99
72
- # Inference pipeline config
73
- inference_decode_method: detection
74
- inference_max_tokens: 1000
75
- inference_cfg_uncond: text
76
- inference_add_instruction: false # det uses start_of_det token, not text instruction
77
-
78
- - name: seg
79
- id: 6
80
- kind: image
81
- start_token_key: start_of_seg # REPLACE: per-modality start (NEW token)
82
- start_token: "<|seg_start|>"
83
- end_token_key: end_of_image
84
- represent_vit: true
85
- represent_vae: true
86
-
87
- - name: canny
88
- id: 7
89
- kind: image
90
- start_token_key: start_of_canny # REPLACE: per-modality start (NEW token)
91
- start_token: "<|canny_start|>"
92
- end_token_key: end_of_image
93
- represent_vit: true
94
- represent_vae: true
95
-
96
- - name: dino
97
- id: 8
98
- kind: codebook
99
- start_token_key: start_of_dino
100
- end_token_key: end_of_dino
101
- pos_embed_size: 16
102
- apply_pos_embed_in_forward: true
103
- represent_vae: true
104
- start_token: "<|dino_start|>"
105
- end_token: "<|dino_end|>"
106
- code_vocab_size: 8192
107
- code_token_format: "<|dino_{i:04d}|>"
108
- external_tokenizer_kind: vqvae
109
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14-global_8k_16_224"
110
- # Inference pipeline config
111
- inference_decode_method: dino
112
- inference_max_tokens: 17
113
- inference_cfg_uncond: img
114
- inference_cfg_img_scale: 1.0
115
-
116
- - name: dinolocal
117
- id: 9
118
- kind: codebook
119
- start_token_key: start_of_dinolocal
120
- end_token_key: end_of_dinolocal
121
- pos_embed_size: 1024
122
- apply_pos_embed_in_forward: true
123
- represent_vae: true
124
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
125
- start_token: "<|dinolocal_start|>"
126
- end_token: "<|dinolocal_end|>"
127
- code_vocab_size: 8192
128
- code_token_format: "<|dinolocal_{i:04d}|>"
129
- external_tokenizer_kind: vqvae
130
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14_8k_224-448"
131
- # Inference pipeline config
132
- inference_decode_method: dinolocal
133
- inference_max_tokens: 1025
134
- inference_cfg_uncond: img
135
- inference_cfg_img_scale: 1.0
136
-
137
- - name: clip
138
- id: 10
139
- kind: codebook
140
- start_token_key: start_of_clip
141
- end_token_key: end_of_clip
142
- pos_embed_size: 784
143
- apply_pos_embed_in_forward: true
144
- represent_vae: true
145
- codebook_spatial_shape: [28, 28] # 784 tokens, 28Γ—28 grid β†’ 2D RoPE
146
- start_token: "<|clip_start|>"
147
- end_token: "<|clip_end|>"
148
- code_vocab_size: 8192
149
- code_token_format: "<|clip_{i:04d}|>"
150
- external_tokenizer_kind: vqvae
151
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_CLIP-B16_8k_224-448"
152
- # Inference pipeline config
153
- inference_decode_method: clip
154
- inference_max_tokens: 785
155
- inference_cfg_uncond: img
156
- inference_cfg_img_scale: 1.0
157
-
158
- - name: imagebind
159
- id: 11
160
- kind: codebook
161
- start_token_key: start_of_imagebind
162
- end_token_key: end_of_imagebind
163
- pos_embed_size: 16
164
- apply_pos_embed_in_forward: true
165
- represent_vae: true
166
- start_token: "<|imagebind_start|>"
167
- end_token: "<|imagebind_end|>"
168
- code_vocab_size: 8192
169
- code_token_format: "<|imagebind_{i:04d}|>"
170
- external_tokenizer_kind: vqvae
171
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14-global_8k_16_224"
172
- # Inference pipeline config
173
- inference_decode_method: imagebind
174
- inference_max_tokens: 17
175
- inference_cfg_uncond: img
176
- inference_cfg_img_scale: 1.0
177
-
178
- - name: imagebindlocal
179
- id: 12
180
- kind: codebook
181
- start_token_key: start_of_imagebindlocal
182
- end_token_key: end_of_imagebindlocal
183
- pos_embed_size: 1024
184
- apply_pos_embed_in_forward: true
185
- represent_vae: true
186
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
187
- start_token: "<|imagebindlocal_start|>"
188
- end_token: "<|imagebindlocal_end|>"
189
- code_vocab_size: 8192
190
- code_token_format: "<|imagebindlocal_{i:04d}|>"
191
- external_tokenizer_kind: vqvae
192
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14_8k_224-448"
193
- # Inference pipeline config
194
- inference_decode_method: imagebindlocal
195
- inference_max_tokens: 1025
196
- inference_cfg_uncond: img
197
- inference_cfg_img_scale: 1.0
198
-
199
- # ── NEW: cocodet (Pix2seq detection). stage1 = rgbβ†’cocodet only.
200
- # Coords reuse det's x1/y1/x2/y2 tokens (dedup, 0 new); class = COCO-id-aligned
201
- # tokens (~91 new). dispersed_code_tokens β†’ CE is gathered over the
202
- # non-contiguous {coords βˆͺ class βˆͺ end} set, not a contiguous range.
203
- # No reweight, no learnable pos_embed (RoPE + token-id carry order/role).
204
- - name: cocodet
205
- id: 13
206
- kind: codebook
207
- start_token_key: start_of_cocodet
208
- end_token_key: end_of_cocodet
209
- start_token: "<|cocodet_start|>"
210
- end_token: "<|cocodet_end|>"
211
- represent_vae: true
212
- dispersed_code_tokens: true
213
- code_token_groups:
214
- - token_format: "<|x1_{i:03d}|>"
215
- start: 0
216
- end: 999
217
- - token_format: "<|y1_{i:03d}|>"
218
- start: 0
219
- end: 999
220
- - token_format: "<|x2_{i:03d}|>"
221
- start: 0
222
- end: 999
223
- - token_format: "<|y2_{i:03d}|>"
224
- start: 0
225
- end: 999
226
- - token_format: "<|coco_cls_{i:02d}|>"
227
- start: 0
228
- end: 90
229
- # Inference pipeline config.
230
- # decode_method is 'cocodet' (NOT 'detection') β€” cocodet's output is
231
- # coords+class per box ending on cocodet_end, a DIFFERENT format from the
232
- # grounding/det coords-only decode (generate_detection_coordonly). Handled by
233
- # the dedicated model.generate_cocodet + inferencer.gen_cocodet (which do NOT
234
- # touch the grounding decode).
235
- inference_decode_method: cocodet
236
- inference_max_tokens: 1000
237
- inference_cfg_uncond: text
238
- inference_add_instruction: false
239
-
240
- # ── NEW image modalities (stage2): SAM segmentation / edge. Mirror seg/canny
241
- # (direct-PNG image, VIT+VAE, MSE). REPLACE: per-modality start token, shared
242
- # end_of_image. These start tokens are NEW (created via start_token field).
243
- - name: samseg
244
- id: 14
245
- kind: image
246
- start_token_key: start_of_samseg
247
- start_token: "<|samseg_start|>"
248
- end_token_key: end_of_image
249
- represent_vit: true
250
- represent_vae: true
251
-
252
- - name: samedge
253
- id: 15
254
- kind: image
255
- start_token_key: start_of_samedge
256
- start_token: "<|samedge_start|>"
257
- end_token_key: end_of_image
258
- represent_vit: true
259
- represent_vae: true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/instruction_16mod_stage1.yaml DELETED
@@ -1,243 +0,0 @@
1
- modalities:
2
- - name: text
3
- id: 0
4
- kind: text
5
- start_token_key: bos_token_id
6
- end_token_key: eos_token_id
7
-
8
- - name: caption
9
- id: 1
10
- kind: text
11
- start_token_key: bos_token_id
12
- end_token_key: eos_token_id
13
- represent_vae: true
14
- conditions: [rgb, grounding, dino, dinolocal, clip, imagebind, imagebindlocal]
15
-
16
- - name: rgb
17
- id: 2
18
- kind: image
19
- start_token_key: start_of_image
20
- end_token_key: end_of_image
21
- represent_vit: true
22
- represent_vae: true
23
- conditions: [caption, grounding, dino, dinolocal, clip, imagebind, imagebindlocal]
24
-
25
- - name: depth
26
- id: 3
27
- kind: image
28
- start_token_key: start_of_image
29
- end_token_key: end_of_image
30
- represent_vit: true
31
- represent_vae: true
32
-
33
- - name: normal
34
- id: 4
35
- kind: image
36
- start_token_key: start_of_image
37
- end_token_key: end_of_image
38
- represent_vit: true
39
- represent_vae: true
40
-
41
- - name: det
42
- id: 5
43
- kind: codebook
44
- start_token_key: start_of_det
45
- end_token_key: end_of_det
46
- represent_vae: true
47
- # NOTE: det's learnable pos_embed (grounding_pos_embed, size 4) is REMOVED here.
48
- # The forward pos_embed loop is keyed by token-id range, and cocodet reuses
49
- # det's x1/y1/x2/y2 tokens; with the pos_embed on, a multi-box cocodet sample
50
- # (>4 coord tokens) would index the size-4 embedding out of bounds. det is not
51
- # trained in modus (only present for coord-token-id alignment), so dropping its
52
- # pos_embed is safe. reweight also removed (no reweight for det/cocodet).
53
- conditions: [rgb]
54
- start_token: "<|det_start|>"
55
- end_token: "<|det_end|>"
56
- extra_tokens: ["<|box_start|>", "<|box_end|>"]
57
- code_token_groups:
58
- - token_format: "<|x1_{i:03d}|>"
59
- start: 0
60
- end: 999
61
- - token_format: "<|y1_{i:03d}|>"
62
- start: 0
63
- end: 999
64
- - token_format: "<|x2_{i:03d}|>"
65
- start: 0
66
- end: 999
67
- - token_format: "<|y2_{i:03d}|>"
68
- start: 0
69
- end: 999
70
- - token_format: "<|score_{i:02d}|>"
71
- start: 0
72
- end: 99
73
- # Inference pipeline config
74
- inference_decode_method: detection
75
- inference_max_tokens: 1000
76
- inference_cfg_uncond: text
77
- inference_add_instruction: false # det uses start_of_det token, not text instruction
78
-
79
- - name: seg
80
- id: 6
81
- kind: image
82
- start_token_key: start_of_image
83
- end_token_key: end_of_image
84
- represent_vit: true
85
- represent_vae: true
86
-
87
- - name: canny
88
- id: 7
89
- kind: image
90
- start_token_key: start_of_image
91
- end_token_key: end_of_image
92
- represent_vit: true
93
- represent_vae: true
94
-
95
- - name: dino
96
- id: 8
97
- kind: codebook
98
- start_token_key: start_of_dino
99
- end_token_key: end_of_dino
100
- pos_embed_size: 16
101
- apply_pos_embed_in_forward: true
102
- represent_vae: true
103
- conditions: [rgb]
104
- start_token: "<|dino_start|>"
105
- end_token: "<|dino_end|>"
106
- code_vocab_size: 8192
107
- code_token_format: "<|dino_{i:04d}|>"
108
- external_tokenizer_kind: vqvae
109
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14-global_8k_16_224"
110
- # Inference pipeline config
111
- inference_decode_method: dino
112
- inference_max_tokens: 17
113
- inference_cfg_uncond: img
114
- inference_cfg_img_scale: 1.0
115
-
116
- - name: dinolocal
117
- id: 9
118
- kind: codebook
119
- start_token_key: start_of_dinolocal
120
- end_token_key: end_of_dinolocal
121
- pos_embed_size: 1024
122
- apply_pos_embed_in_forward: true
123
- represent_vae: true
124
- conditions: [rgb]
125
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
126
- start_token: "<|dinolocal_start|>"
127
- end_token: "<|dinolocal_end|>"
128
- code_vocab_size: 8192
129
- code_token_format: "<|dinolocal_{i:04d}|>"
130
- external_tokenizer_kind: vqvae
131
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14_8k_224-448"
132
- # Inference pipeline config
133
- inference_decode_method: dinolocal
134
- inference_max_tokens: 1025
135
- inference_cfg_uncond: img
136
- inference_cfg_img_scale: 1.0
137
-
138
- - name: clip
139
- id: 10
140
- kind: codebook
141
- start_token_key: start_of_clip
142
- end_token_key: end_of_clip
143
- pos_embed_size: 784
144
- apply_pos_embed_in_forward: true
145
- represent_vae: true
146
- conditions: [rgb]
147
- codebook_spatial_shape: [28, 28] # 784 tokens, 28Γ—28 grid β†’ 2D RoPE
148
- start_token: "<|clip_start|>"
149
- end_token: "<|clip_end|>"
150
- code_vocab_size: 8192
151
- code_token_format: "<|clip_{i:04d}|>"
152
- external_tokenizer_kind: vqvae
153
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_CLIP-B16_8k_224-448"
154
- # Inference pipeline config
155
- inference_decode_method: clip
156
- inference_max_tokens: 785
157
- inference_cfg_uncond: img
158
- inference_cfg_img_scale: 1.0
159
-
160
- - name: imagebind
161
- id: 11
162
- kind: codebook
163
- start_token_key: start_of_imagebind
164
- end_token_key: end_of_imagebind
165
- pos_embed_size: 16
166
- apply_pos_embed_in_forward: true
167
- represent_vae: true
168
- conditions: [rgb]
169
- start_token: "<|imagebind_start|>"
170
- end_token: "<|imagebind_end|>"
171
- code_vocab_size: 8192
172
- code_token_format: "<|imagebind_{i:04d}|>"
173
- external_tokenizer_kind: vqvae
174
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14-global_8k_16_224"
175
- # Inference pipeline config
176
- inference_decode_method: imagebind
177
- inference_max_tokens: 17
178
- inference_cfg_uncond: img
179
- inference_cfg_img_scale: 1.0
180
-
181
- - name: imagebindlocal
182
- id: 12
183
- kind: codebook
184
- start_token_key: start_of_imagebindlocal
185
- end_token_key: end_of_imagebindlocal
186
- pos_embed_size: 1024
187
- apply_pos_embed_in_forward: true
188
- represent_vae: true
189
- conditions: [rgb]
190
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
191
- start_token: "<|imagebindlocal_start|>"
192
- end_token: "<|imagebindlocal_end|>"
193
- code_vocab_size: 8192
194
- code_token_format: "<|imagebindlocal_{i:04d}|>"
195
- external_tokenizer_kind: vqvae
196
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14_8k_224-448"
197
- # Inference pipeline config
198
- inference_decode_method: imagebindlocal
199
- inference_max_tokens: 1025
200
- inference_cfg_uncond: img
201
- inference_cfg_img_scale: 1.0
202
-
203
- # ── NEW: cocodet (Pix2seq detection). stage1 = rgbβ†’cocodet only.
204
- # Coords reuse det's x1/y1/x2/y2 tokens (dedup, 0 new); class = COCO-id-aligned
205
- # tokens (~91 new). dispersed_code_tokens β†’ CE is gathered over the
206
- # non-contiguous {coords βˆͺ class βˆͺ end} set, not a contiguous range.
207
- # No reweight, no learnable pos_embed (RoPE + token-id carry order/role).
208
- - name: cocodet
209
- id: 13
210
- kind: codebook
211
- start_token_key: start_of_cocodet
212
- end_token_key: end_of_cocodet
213
- start_token: "<|cocodet_start|>"
214
- end_token: "<|cocodet_end|>"
215
- represent_vae: true
216
- conditions: [rgb]
217
- dispersed_code_tokens: true
218
- code_token_groups:
219
- - token_format: "<|x1_{i:03d}|>"
220
- start: 0
221
- end: 999
222
- - token_format: "<|y1_{i:03d}|>"
223
- start: 0
224
- end: 999
225
- - token_format: "<|x2_{i:03d}|>"
226
- start: 0
227
- end: 999
228
- - token_format: "<|y2_{i:03d}|>"
229
- start: 0
230
- end: 999
231
- - token_format: "<|coco_cls_{i:02d}|>"
232
- start: 0
233
- end: 90
234
- # Inference pipeline config.
235
- # decode_method is 'cocodet' (NOT 'detection') β€” cocodet's output is
236
- # coords+class per box ending on cocodet_end, a DIFFERENT format from the
237
- # grounding/det coords-only decode (generate_detection_coordonly). Handled by
238
- # the dedicated model.generate_cocodet + inferencer.gen_cocodet (which do NOT
239
- # touch the grounding decode).
240
- inference_decode_method: cocodet
241
- inference_max_tokens: 1000
242
- inference_cfg_uncond: text
243
- inference_add_instruction: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/instruction_16mod_stage1_rgbcond.yaml DELETED
@@ -1,266 +0,0 @@
1
- modalities:
2
- - name: text
3
- id: 0
4
- kind: text
5
- start_token_key: bos_token_id
6
- end_token_key: eos_token_id
7
-
8
- - name: caption
9
- id: 1
10
- kind: text
11
- start_token_key: bos_token_id
12
- end_token_key: eos_token_id
13
- represent_vae: true
14
-
15
- - name: rgb
16
- id: 2
17
- kind: image
18
- start_token_key: start_of_image
19
- end_token_key: end_of_image
20
- represent_vit: true
21
- represent_vae: true
22
-
23
- - name: depth
24
- id: 3
25
- kind: image
26
- start_token_key: start_of_depth # REPLACE: per-modality start (token already exists in base vocab)
27
- start_token: "<|depth_start|>"
28
- end_token_key: end_of_image # shared end (image end is a structural terminator)
29
- represent_vit: true
30
- represent_vae: true
31
-
32
- - name: normal
33
- id: 4
34
- kind: image
35
- start_token_key: start_of_normal # REPLACE: per-modality start (token already exists in base vocab)
36
- start_token: "<|normal_start|>"
37
- end_token_key: end_of_image
38
- represent_vit: true
39
- represent_vae: true
40
-
41
- - name: det
42
- id: 5
43
- kind: codebook
44
- start_token_key: start_of_det
45
- conditions: ["rgb"] # stage1 rgbcond: condition on rgb only (rgb->X warmup)
46
- end_token_key: end_of_det
47
- represent_vae: true
48
- # NOTE: det's learnable pos_embed (grounding_pos_embed, size 4) is REMOVED here.
49
- # The forward pos_embed loop is keyed by token-id range, and cocodet reuses
50
- # det's x1/y1/x2/y2 tokens; with the pos_embed on, a multi-box cocodet sample
51
- # (>4 coord tokens) would index the size-4 embedding out of bounds. det is not
52
- # trained in modus (only present for coord-token-id alignment), so dropping its
53
- # pos_embed is safe. reweight also removed (no reweight for det/cocodet).
54
- start_token: "<|det_start|>"
55
- end_token: "<|det_end|>"
56
- extra_tokens: ["<|box_start|>", "<|box_end|>"]
57
- code_token_groups:
58
- - token_format: "<|x1_{i:03d}|>"
59
- start: 0
60
- end: 999
61
- - token_format: "<|y1_{i:03d}|>"
62
- start: 0
63
- end: 999
64
- - token_format: "<|x2_{i:03d}|>"
65
- start: 0
66
- end: 999
67
- - token_format: "<|y2_{i:03d}|>"
68
- start: 0
69
- end: 999
70
- - token_format: "<|score_{i:02d}|>"
71
- start: 0
72
- end: 99
73
- # Inference pipeline config
74
- inference_decode_method: detection
75
- inference_max_tokens: 1000
76
- inference_cfg_uncond: text
77
- inference_add_instruction: false # det uses start_of_det token, not text instruction
78
-
79
- - name: seg
80
- id: 6
81
- kind: image
82
- start_token_key: start_of_seg # REPLACE: per-modality start (NEW token)
83
- start_token: "<|seg_start|>"
84
- end_token_key: end_of_image
85
- represent_vit: true
86
- represent_vae: true
87
-
88
- - name: canny
89
- id: 7
90
- kind: image
91
- start_token_key: start_of_canny # REPLACE: per-modality start (NEW token)
92
- start_token: "<|canny_start|>"
93
- end_token_key: end_of_image
94
- represent_vit: true
95
- represent_vae: true
96
-
97
- - name: dino
98
- id: 8
99
- kind: codebook
100
- start_token_key: start_of_dino
101
- conditions: ["rgb"] # stage1 rgbcond: condition on rgb only (rgb->X warmup)
102
- end_token_key: end_of_dino
103
- pos_embed_size: 16
104
- apply_pos_embed_in_forward: true
105
- represent_vae: true
106
- start_token: "<|dino_start|>"
107
- end_token: "<|dino_end|>"
108
- code_vocab_size: 8192
109
- code_token_format: "<|dino_{i:04d}|>"
110
- external_tokenizer_kind: vqvae
111
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14-global_8k_16_224"
112
- # Inference pipeline config
113
- inference_decode_method: dino
114
- inference_max_tokens: 17
115
- inference_cfg_uncond: img
116
- inference_cfg_img_scale: 1.0
117
-
118
- - name: dinolocal
119
- id: 9
120
- kind: codebook
121
- start_token_key: start_of_dinolocal
122
- conditions: ["rgb"] # stage1 rgbcond: condition on rgb only (rgb->X warmup)
123
- end_token_key: end_of_dinolocal
124
- pos_embed_size: 1024
125
- apply_pos_embed_in_forward: true
126
- represent_vae: true
127
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
128
- start_token: "<|dinolocal_start|>"
129
- end_token: "<|dinolocal_end|>"
130
- code_vocab_size: 8192
131
- code_token_format: "<|dinolocal_{i:04d}|>"
132
- external_tokenizer_kind: vqvae
133
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14_8k_224-448"
134
- # Inference pipeline config
135
- inference_decode_method: dinolocal
136
- inference_max_tokens: 1025
137
- inference_cfg_uncond: img
138
- inference_cfg_img_scale: 1.0
139
-
140
- - name: clip
141
- id: 10
142
- kind: codebook
143
- start_token_key: start_of_clip
144
- conditions: ["rgb"] # stage1 rgbcond: condition on rgb only (rgb->X warmup)
145
- end_token_key: end_of_clip
146
- pos_embed_size: 784
147
- apply_pos_embed_in_forward: true
148
- represent_vae: true
149
- codebook_spatial_shape: [28, 28] # 784 tokens, 28Γ—28 grid β†’ 2D RoPE
150
- start_token: "<|clip_start|>"
151
- end_token: "<|clip_end|>"
152
- code_vocab_size: 8192
153
- code_token_format: "<|clip_{i:04d}|>"
154
- external_tokenizer_kind: vqvae
155
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_CLIP-B16_8k_224-448"
156
- # Inference pipeline config
157
- inference_decode_method: clip
158
- inference_max_tokens: 785
159
- inference_cfg_uncond: img
160
- inference_cfg_img_scale: 1.0
161
-
162
- - name: imagebind
163
- id: 11
164
- kind: codebook
165
- start_token_key: start_of_imagebind
166
- conditions: ["rgb"] # stage1 rgbcond: condition on rgb only (rgb->X warmup)
167
- end_token_key: end_of_imagebind
168
- pos_embed_size: 16
169
- apply_pos_embed_in_forward: true
170
- represent_vae: true
171
- start_token: "<|imagebind_start|>"
172
- end_token: "<|imagebind_end|>"
173
- code_vocab_size: 8192
174
- code_token_format: "<|imagebind_{i:04d}|>"
175
- external_tokenizer_kind: vqvae
176
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14-global_8k_16_224"
177
- # Inference pipeline config
178
- inference_decode_method: imagebind
179
- inference_max_tokens: 17
180
- inference_cfg_uncond: img
181
- inference_cfg_img_scale: 1.0
182
-
183
- - name: imagebindlocal
184
- id: 12
185
- kind: codebook
186
- start_token_key: start_of_imagebindlocal
187
- conditions: ["rgb"] # stage1 rgbcond: condition on rgb only (rgb->X warmup)
188
- end_token_key: end_of_imagebindlocal
189
- pos_embed_size: 1024
190
- apply_pos_embed_in_forward: true
191
- represent_vae: true
192
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
193
- start_token: "<|imagebindlocal_start|>"
194
- end_token: "<|imagebindlocal_end|>"
195
- code_vocab_size: 8192
196
- code_token_format: "<|imagebindlocal_{i:04d}|>"
197
- external_tokenizer_kind: vqvae
198
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14_8k_224-448"
199
- # Inference pipeline config
200
- inference_decode_method: imagebindlocal
201
- inference_max_tokens: 1025
202
- inference_cfg_uncond: img
203
- inference_cfg_img_scale: 1.0
204
-
205
- # ── NEW: cocodet (Pix2seq detection). stage1 = rgbβ†’cocodet only.
206
- # Coords reuse det's x1/y1/x2/y2 tokens (dedup, 0 new); class = COCO-id-aligned
207
- # tokens (~91 new). dispersed_code_tokens β†’ CE is gathered over the
208
- # non-contiguous {coords βˆͺ class βˆͺ end} set, not a contiguous range.
209
- # No reweight, no learnable pos_embed (RoPE + token-id carry order/role).
210
- - name: cocodet
211
- id: 13
212
- kind: codebook
213
- start_token_key: start_of_cocodet
214
- conditions: ["rgb"] # stage1 rgbcond: condition on rgb only (rgb->X warmup)
215
- end_token_key: end_of_cocodet
216
- start_token: "<|cocodet_start|>"
217
- end_token: "<|cocodet_end|>"
218
- represent_vae: true
219
- dispersed_code_tokens: true
220
- code_token_groups:
221
- - token_format: "<|x1_{i:03d}|>"
222
- start: 0
223
- end: 999
224
- - token_format: "<|y1_{i:03d}|>"
225
- start: 0
226
- end: 999
227
- - token_format: "<|x2_{i:03d}|>"
228
- start: 0
229
- end: 999
230
- - token_format: "<|y2_{i:03d}|>"
231
- start: 0
232
- end: 999
233
- - token_format: "<|coco_cls_{i:02d}|>"
234
- start: 0
235
- end: 90
236
- # Inference pipeline config.
237
- # decode_method is 'cocodet' (NOT 'detection') β€” cocodet's output is
238
- # coords+class per box ending on cocodet_end, a DIFFERENT format from the
239
- # grounding/det coords-only decode (generate_detection_coordonly). Handled by
240
- # the dedicated model.generate_cocodet + inferencer.gen_cocodet (which do NOT
241
- # touch the grounding decode).
242
- inference_decode_method: cocodet
243
- inference_max_tokens: 1000
244
- inference_cfg_uncond: text
245
- inference_add_instruction: false
246
-
247
- # ── NEW image modalities (stage2): SAM segmentation / edge. Mirror seg/canny
248
- # (direct-PNG image, VIT+VAE, MSE). REPLACE: per-modality start token, shared
249
- # end_of_image. These start tokens are NEW (created via start_token field).
250
- - name: samseg
251
- id: 14
252
- kind: image
253
- start_token_key: start_of_samseg
254
- start_token: "<|samseg_start|>"
255
- end_token_key: end_of_image
256
- represent_vit: true
257
- represent_vae: true
258
-
259
- - name: samedge
260
- id: 15
261
- kind: image
262
- start_token_key: start_of_samedge
263
- start_token: "<|samedge_start|>"
264
- end_token_key: end_of_image
265
- represent_vit: true
266
- represent_vae: true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/instruction_16mod_stage2.yaml DELETED
@@ -1,272 +0,0 @@
1
- modalities:
2
- - name: text
3
- id: 0
4
- kind: text
5
- start_token_key: bos_token_id
6
- end_token_key: eos_token_id
7
- inference_add_instruction: false # stage2/3 trained with use_target_instruction=false (no generic instr)
8
-
9
- - name: caption
10
- id: 1
11
- kind: text
12
- start_token_key: bos_token_id
13
- end_token_key: eos_token_id
14
- represent_vae: true
15
- inference_add_instruction: false # any2caption (stage2) trained without a target instruction
16
-
17
- - name: rgb
18
- id: 2
19
- kind: image
20
- start_token_key: start_of_image
21
- end_token_key: end_of_image
22
- represent_vit: true
23
- represent_vae: true
24
- inference_add_instruction: false # generic [start X] not trained in stage2/3 (use_target_instruction=false)
25
-
26
- - name: depth
27
- id: 3
28
- kind: image
29
- start_token_key: start_of_depth # REPLACE: per-modality start (token already exists in base vocab)
30
- start_token: "<|depth_start|>"
31
- end_token_key: end_of_image # shared end (image end is a structural terminator)
32
- represent_vit: true
33
- represent_vae: true
34
- inference_add_instruction: false
35
-
36
- - name: normal
37
- id: 4
38
- kind: image
39
- start_token_key: start_of_normal # REPLACE: per-modality start (token already exists in base vocab)
40
- start_token: "<|normal_start|>"
41
- end_token_key: end_of_image
42
- represent_vit: true
43
- represent_vae: true
44
- inference_add_instruction: false
45
-
46
- - name: det
47
- id: 5
48
- kind: codebook
49
- start_token_key: start_of_det
50
- end_token_key: end_of_det
51
- represent_vae: true
52
- # NOTE: det's learnable pos_embed (grounding_pos_embed, size 4) is REMOVED here.
53
- # The forward pos_embed loop is keyed by token-id range, and cocodet reuses
54
- # det's x1/y1/x2/y2 tokens; with the pos_embed on, a multi-box cocodet sample
55
- # (>4 coord tokens) would index the size-4 embedding out of bounds. det is not
56
- # trained in modus (only present for coord-token-id alignment), so dropping its
57
- # pos_embed is safe. reweight also removed (no reweight for det/cocodet).
58
- start_token: "<|det_start|>"
59
- end_token: "<|det_end|>"
60
- extra_tokens: ["<|box_start|>", "<|box_end|>"]
61
- code_token_groups:
62
- - token_format: "<|x1_{i:03d}|>"
63
- start: 0
64
- end: 999
65
- - token_format: "<|y1_{i:03d}|>"
66
- start: 0
67
- end: 999
68
- - token_format: "<|x2_{i:03d}|>"
69
- start: 0
70
- end: 999
71
- - token_format: "<|y2_{i:03d}|>"
72
- start: 0
73
- end: 999
74
- - token_format: "<|score_{i:02d}|>"
75
- start: 0
76
- end: 99
77
- # Inference pipeline config
78
- inference_decode_method: detection
79
- inference_max_tokens: 1000
80
- inference_cfg_uncond: text
81
- inference_add_instruction: false # det uses start_of_det token, not text instruction
82
-
83
- - name: seg
84
- id: 6
85
- kind: image
86
- start_token_key: start_of_seg # REPLACE: per-modality start (NEW token)
87
- start_token: "<|seg_start|>"
88
- end_token_key: end_of_image
89
- represent_vit: true
90
- represent_vae: true
91
-
92
- - name: canny
93
- id: 7
94
- kind: image
95
- start_token_key: start_of_canny # REPLACE: per-modality start (NEW token)
96
- start_token: "<|canny_start|>"
97
- end_token_key: end_of_image
98
- represent_vit: true
99
- represent_vae: true
100
- inference_add_instruction: false
101
-
102
- - name: dino
103
- id: 8
104
- kind: codebook
105
- start_token_key: start_of_dino
106
- end_token_key: end_of_dino
107
- pos_embed_size: 16
108
- apply_pos_embed_in_forward: true
109
- represent_vae: true
110
- start_token: "<|dino_start|>"
111
- end_token: "<|dino_end|>"
112
- code_vocab_size: 8192
113
- code_token_format: "<|dino_{i:04d}|>"
114
- external_tokenizer_kind: vqvae
115
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14-global_8k_16_224"
116
- # Inference pipeline config
117
- inference_decode_method: dino
118
- inference_max_tokens: 17
119
- inference_cfg_uncond: img
120
- inference_cfg_img_scale: 1.0
121
- inference_add_instruction: false # start_of_dino token triggers it; generic instr not in stage2/3
122
-
123
- - name: dinolocal
124
- id: 9
125
- kind: codebook
126
- start_token_key: start_of_dinolocal
127
- end_token_key: end_of_dinolocal
128
- pos_embed_size: 1024
129
- apply_pos_embed_in_forward: true
130
- represent_vae: true
131
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
132
- start_token: "<|dinolocal_start|>"
133
- end_token: "<|dinolocal_end|>"
134
- code_vocab_size: 8192
135
- code_token_format: "<|dinolocal_{i:04d}|>"
136
- external_tokenizer_kind: vqvae
137
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14_8k_224-448"
138
- # Inference pipeline config
139
- inference_decode_method: dinolocal
140
- inference_max_tokens: 1025
141
- inference_cfg_uncond: img
142
- inference_cfg_img_scale: 1.0
143
- inference_add_instruction: false
144
-
145
- - name: clip
146
- id: 10
147
- kind: codebook
148
- start_token_key: start_of_clip
149
- end_token_key: end_of_clip
150
- pos_embed_size: 784
151
- apply_pos_embed_in_forward: true
152
- represent_vae: true
153
- codebook_spatial_shape: [28, 28] # 784 tokens, 28Γ—28 grid β†’ 2D RoPE
154
- start_token: "<|clip_start|>"
155
- end_token: "<|clip_end|>"
156
- code_vocab_size: 8192
157
- code_token_format: "<|clip_{i:04d}|>"
158
- external_tokenizer_kind: vqvae
159
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_CLIP-B16_8k_224-448"
160
- # Inference pipeline config
161
- inference_decode_method: clip
162
- inference_max_tokens: 785
163
- inference_cfg_uncond: img
164
- inference_cfg_img_scale: 1.0
165
- inference_add_instruction: false
166
-
167
- - name: imagebind
168
- id: 11
169
- kind: codebook
170
- start_token_key: start_of_imagebind
171
- end_token_key: end_of_imagebind
172
- pos_embed_size: 16
173
- apply_pos_embed_in_forward: true
174
- represent_vae: true
175
- start_token: "<|imagebind_start|>"
176
- end_token: "<|imagebind_end|>"
177
- code_vocab_size: 8192
178
- code_token_format: "<|imagebind_{i:04d}|>"
179
- external_tokenizer_kind: vqvae
180
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14-global_8k_16_224"
181
- # Inference pipeline config
182
- inference_decode_method: imagebind
183
- inference_max_tokens: 17
184
- inference_cfg_uncond: img
185
- inference_cfg_img_scale: 1.0
186
- inference_add_instruction: false
187
-
188
- - name: imagebindlocal
189
- id: 12
190
- kind: codebook
191
- start_token_key: start_of_imagebindlocal
192
- end_token_key: end_of_imagebindlocal
193
- pos_embed_size: 1024
194
- apply_pos_embed_in_forward: true
195
- represent_vae: true
196
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
197
- start_token: "<|imagebindlocal_start|>"
198
- end_token: "<|imagebindlocal_end|>"
199
- code_vocab_size: 8192
200
- code_token_format: "<|imagebindlocal_{i:04d}|>"
201
- external_tokenizer_kind: vqvae
202
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14_8k_224-448"
203
- # Inference pipeline config
204
- inference_decode_method: imagebindlocal
205
- inference_max_tokens: 1025
206
- inference_cfg_uncond: img
207
- inference_cfg_img_scale: 1.0
208
- inference_add_instruction: false
209
-
210
- # ── NEW: cocodet (Pix2seq detection). stage1 = rgbβ†’cocodet only.
211
- # Coords reuse det's x1/y1/x2/y2 tokens (dedup, 0 new); class = COCO-id-aligned
212
- # tokens (~91 new). dispersed_code_tokens β†’ CE is gathered over the
213
- # non-contiguous {coords βˆͺ class βˆͺ end} set, not a contiguous range.
214
- # No reweight, no learnable pos_embed (RoPE + token-id carry order/role).
215
- - name: cocodet
216
- id: 13
217
- kind: codebook
218
- start_token_key: start_of_cocodet
219
- end_token_key: end_of_cocodet
220
- start_token: "<|cocodet_start|>"
221
- end_token: "<|cocodet_end|>"
222
- represent_vae: true
223
- dispersed_code_tokens: true
224
- code_token_groups:
225
- - token_format: "<|x1_{i:03d}|>"
226
- start: 0
227
- end: 999
228
- - token_format: "<|y1_{i:03d}|>"
229
- start: 0
230
- end: 999
231
- - token_format: "<|x2_{i:03d}|>"
232
- start: 0
233
- end: 999
234
- - token_format: "<|y2_{i:03d}|>"
235
- start: 0
236
- end: 999
237
- - token_format: "<|coco_cls_{i:02d}|>"
238
- start: 0
239
- end: 90
240
- # Inference pipeline config.
241
- # decode_method is 'cocodet' (NOT 'detection') β€” cocodet's output is
242
- # coords+class per box ending on cocodet_end, a DIFFERENT format from the
243
- # grounding/det coords-only decode (generate_detection_coordonly). Handled by
244
- # the dedicated model.generate_cocodet + inferencer.gen_cocodet (which do NOT
245
- # touch the grounding decode).
246
- inference_decode_method: cocodet
247
- inference_max_tokens: 1000
248
- inference_cfg_uncond: text
249
- inference_add_instruction: false
250
-
251
- # ── NEW image modalities (stage2): SAM segmentation / edge. Mirror seg/canny
252
- # (direct-PNG image, VIT+VAE, MSE). REPLACE: per-modality start token, shared
253
- # end_of_image. These start tokens are NEW (created via start_token field).
254
- - name: samseg
255
- id: 14
256
- kind: image
257
- start_token_key: start_of_samseg
258
- start_token: "<|samseg_start|>"
259
- end_token_key: end_of_image
260
- represent_vit: true
261
- represent_vae: true
262
- inference_add_instruction: false
263
-
264
- - name: samedge
265
- id: 15
266
- kind: image
267
- start_token_key: start_of_samedge
268
- start_token: "<|samedge_start|>"
269
- end_token_key: end_of_image
270
- represent_vit: true
271
- represent_vae: true
272
- inference_add_instruction: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/instruction_2d_only_stage1.yaml DELETED
@@ -1,65 +0,0 @@
1
- # 2D-only stage1 modality registry.
2
- #
3
- # Keeps text/caption and image-like modalities only. Drops DET/DINO/CLIP/
4
- # ImageBind codebook modalities so this run can isolate whether 1D/codebook CE
5
- # is the main source of Hunyuan T2I prior drift.
6
-
7
- modalities:
8
- - name: text
9
- id: 0
10
- kind: text
11
- start_token_key: bos_token_id
12
- end_token_key: eos_token_id
13
-
14
- - name: caption
15
- id: 1
16
- kind: text
17
- start_token_key: bos_token_id
18
- end_token_key: eos_token_id
19
- represent_vae: true
20
- conditions: [rgb, depth, normal, seg, canny]
21
-
22
- - name: rgb
23
- id: 2
24
- kind: image
25
- start_token_key: start_of_image
26
- end_token_key: end_of_image
27
- represent_vit: true
28
- represent_vae: true
29
- conditions: [caption, depth, normal, seg, canny]
30
-
31
- - name: depth
32
- id: 3
33
- kind: image
34
- start_token_key: start_of_image
35
- end_token_key: end_of_image
36
- represent_vit: true
37
- represent_vae: true
38
- conditions: [caption, rgb, normal, seg, canny]
39
-
40
- - name: normal
41
- id: 4
42
- kind: image
43
- start_token_key: start_of_image
44
- end_token_key: end_of_image
45
- represent_vit: true
46
- represent_vae: true
47
- conditions: [caption, rgb, depth, seg, canny]
48
-
49
- - name: seg
50
- id: 6
51
- kind: image
52
- start_token_key: start_of_image
53
- end_token_key: end_of_image
54
- represent_vit: true
55
- represent_vae: true
56
- conditions: [caption, rgb, depth, normal, canny]
57
-
58
- - name: canny
59
- id: 7
60
- kind: image
61
- start_token_key: start_of_image
62
- end_token_key: end_of_image
63
- represent_vit: true
64
- represent_vae: true
65
- conditions: [caption, rgb, depth, normal, seg]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/instruction_9modality_stage1.yaml DELETED
@@ -1,114 +0,0 @@
1
- modalities:
2
- - name: text
3
- id: 0
4
- kind: text
5
- start_token_key: bos_token_id
6
- end_token_key: eos_token_id
7
-
8
- - name: caption
9
- id: 1
10
- kind: text
11
- start_token_key: bos_token_id
12
- end_token_key: eos_token_id
13
- represent_vae: true
14
- conditions: [rgb, grounding, dino]
15
-
16
- - name: rgb
17
- id: 2
18
- kind: image
19
- start_token_key: start_of_image
20
- end_token_key: end_of_image
21
- represent_vit: true
22
- represent_vae: true
23
- conditions: [caption, grounding, dino]
24
-
25
- - name: depth
26
- id: 3
27
- kind: image
28
- start_token_key: start_of_image
29
- end_token_key: end_of_image
30
- represent_vit: true
31
- represent_vae: true
32
-
33
- - name: normal
34
- id: 4
35
- kind: image
36
- start_token_key: start_of_image
37
- end_token_key: end_of_image
38
- represent_vit: true
39
- represent_vae: true
40
-
41
- - name: det
42
- id: 5
43
- kind: codebook
44
- start_token_key: start_of_det
45
- end_token_key: end_of_det
46
- represent_vae: true
47
- pos_embed_size: 4
48
- apply_pos_embed_in_forward: true
49
- pos_embed_name: grounding # checkpoint compat: attr is "grounding_pos_embed"
50
- conditions: [rgb]
51
- loss:
52
- reweight: true
53
- reweight_min_w: 0.005
54
- start_token: "<|det_start|>"
55
- end_token: "<|det_end|>"
56
- extra_tokens: ["<|box_start|>", "<|box_end|>"]
57
- code_token_groups:
58
- - token_format: "<|x1_{i:03d}|>"
59
- start: 0
60
- end: 999
61
- - token_format: "<|y1_{i:03d}|>"
62
- start: 0
63
- end: 999
64
- - token_format: "<|x2_{i:03d}|>"
65
- start: 0
66
- end: 999
67
- - token_format: "<|y2_{i:03d}|>"
68
- start: 0
69
- end: 999
70
- - token_format: "<|score_{i:02d}|>"
71
- start: 0
72
- end: 99
73
- # Inference pipeline config
74
- inference_decode_method: detection
75
- inference_max_tokens: 1000
76
- inference_cfg_uncond: text
77
- inference_add_instruction: false # det uses start_of_det token, not text instruction
78
-
79
- - name: seg
80
- id: 6
81
- kind: image
82
- start_token_key: start_of_image
83
- end_token_key: end_of_image
84
- represent_vit: true
85
- represent_vae: true
86
-
87
- - name: canny
88
- id: 7
89
- kind: image
90
- start_token_key: start_of_image
91
- end_token_key: end_of_image
92
- represent_vit: true
93
- represent_vae: true
94
-
95
- - name: dino
96
- id: 8
97
- kind: codebook
98
- start_token_key: start_of_dino
99
- end_token_key: end_of_dino
100
- pos_embed_size: 16
101
- apply_pos_embed_in_forward: true
102
- represent_vae: true
103
- conditions: [rgb]
104
- start_token: "<|dino_start|>"
105
- end_token: "<|dino_end|>"
106
- code_vocab_size: 8192
107
- code_token_format: "<|dino_{i:04d}|>"
108
- external_tokenizer_kind: vqvae
109
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14-global_8k_16_224"
110
- # Inference pipeline config
111
- inference_decode_method: dino
112
- inference_max_tokens: 17
113
- inference_cfg_uncond: img
114
- inference_cfg_img_scale: 1.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/instruction_9modality_stage3.yaml DELETED
@@ -1,115 +0,0 @@
1
- # Stage 3 modality config β€” 9 modalities (text + 8 task), unconstrained conditions.
2
- #
3
- # Mirrors instruction_9modality_stage1.yaml but drops every `conditions: [...]`
4
- # restriction so any modality can be conditioned on any other (matches the old
5
- # stage3 training run that was originally configured via CLI flags only).
6
- # det reweight + det/dino learnable pos-embeds are preserved.
7
-
8
- modalities:
9
- - name: text
10
- id: 0
11
- kind: text
12
- start_token_key: bos_token_id
13
- end_token_key: eos_token_id
14
-
15
- - name: caption
16
- id: 1
17
- kind: text
18
- start_token_key: bos_token_id
19
- end_token_key: eos_token_id
20
- represent_vae: true
21
-
22
- - name: rgb
23
- id: 2
24
- kind: image
25
- start_token_key: start_of_image
26
- end_token_key: end_of_image
27
- represent_vit: true
28
- represent_vae: true
29
-
30
- - name: depth
31
- id: 3
32
- kind: image
33
- start_token_key: start_of_image
34
- end_token_key: end_of_image
35
- represent_vit: true
36
- represent_vae: true
37
-
38
- - name: normal
39
- id: 4
40
- kind: image
41
- start_token_key: start_of_image
42
- end_token_key: end_of_image
43
- represent_vit: true
44
- represent_vae: true
45
-
46
- - name: det
47
- id: 5
48
- kind: codebook
49
- start_token_key: start_of_det
50
- end_token_key: end_of_det
51
- represent_vae: true
52
- pos_embed_size: 4
53
- apply_pos_embed_in_forward: true
54
- pos_embed_name: grounding # checkpoint compat: attr is "grounding_pos_embed"
55
- loss:
56
- reweight: true
57
- reweight_min_w: 0.005
58
- start_token: "<|det_start|>"
59
- end_token: "<|det_end|>"
60
- extra_tokens: ["<|box_start|>", "<|box_end|>"]
61
- code_token_groups:
62
- - token_format: "<|x1_{i:03d}|>"
63
- start: 0
64
- end: 999
65
- - token_format: "<|y1_{i:03d}|>"
66
- start: 0
67
- end: 999
68
- - token_format: "<|x2_{i:03d}|>"
69
- start: 0
70
- end: 999
71
- - token_format: "<|y2_{i:03d}|>"
72
- start: 0
73
- end: 999
74
- - token_format: "<|score_{i:02d}|>"
75
- start: 0
76
- end: 99
77
- inference_decode_method: detection
78
- inference_max_tokens: 1000
79
- inference_cfg_uncond: text
80
- inference_add_instruction: false
81
-
82
- - name: seg
83
- id: 6
84
- kind: image
85
- start_token_key: start_of_image
86
- end_token_key: end_of_image
87
- represent_vit: true
88
- represent_vae: true
89
-
90
- - name: canny
91
- id: 7
92
- kind: image
93
- start_token_key: start_of_image
94
- end_token_key: end_of_image
95
- represent_vit: true
96
- represent_vae: true
97
-
98
- - name: dino
99
- id: 8
100
- kind: codebook
101
- start_token_key: start_of_dino
102
- end_token_key: end_of_dino
103
- pos_embed_size: 16
104
- apply_pos_embed_in_forward: true
105
- represent_vae: true
106
- start_token: "<|dino_start|>"
107
- end_token: "<|dino_end|>"
108
- code_vocab_size: 8192
109
- code_token_format: "<|dino_{i:04d}|>"
110
- external_tokenizer_kind: vqvae
111
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14-global_8k_16_224"
112
- inference_decode_method: dino
113
- inference_max_tokens: 17
114
- inference_cfg_uncond: img
115
- inference_cfg_img_scale: 1.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/instruction_hunyuan_16mod_stage1.yaml DELETED
@@ -1,257 +0,0 @@
1
- modalities:
2
- - name: text
3
- id: 0
4
- kind: text
5
- start_token_key: bos_token_id
6
- end_token_key: eos_token_id
7
-
8
- - name: caption
9
- id: 1
10
- kind: text
11
- start_token_key: bos_token_id
12
- end_token_key: eos_token_id
13
- represent_vae: true
14
- conditions: [rgb, grounding, dino, dinolocal, clip, imagebind, imagebindlocal]
15
-
16
- - name: rgb
17
- id: 2
18
- kind: image
19
- start_token_key: start_of_image
20
- end_token_key: end_of_image
21
- represent_vit: true
22
- represent_vae: true
23
- conditions: [caption, grounding, dino, dinolocal, clip, imagebind, imagebindlocal]
24
-
25
- - name: depth
26
- id: 3
27
- kind: image
28
- start_token_key: start_of_image
29
- end_token_key: end_of_image
30
- represent_vit: true
31
- represent_vae: true
32
-
33
- - name: normal
34
- id: 4
35
- kind: image
36
- start_token_key: start_of_image
37
- end_token_key: end_of_image
38
- represent_vit: true
39
- represent_vae: true
40
-
41
- - name: det
42
- id: 5
43
- kind: codebook
44
- start_token_key: start_of_det
45
- end_token_key: end_of_det
46
- represent_vae: true
47
- # det's learnable pos_embed (size 4) + reweight are REMOVED so cocodet β€” which
48
- # reuses det's x1/y1/x2/y2 tokens (coco_cls is cocodet's own) β€” can carry >4
49
- # coord tokens per sample without indexing the size-4 pos_embed out of bounds.
50
- # det is not a target here (only present for coord-token-id alignment);
51
- # grounding reuses these tokens and is now RoPE-only for its bbox coords.
52
- conditions: [rgb]
53
- start_token: "<|det_start|>"
54
- end_token: "<|det_end|>"
55
- extra_tokens: ["<|box_start|>", "<|box_end|>"]
56
- code_token_groups:
57
- - token_format: "<|x1_{i:03d}|>"
58
- start: 0
59
- end: 999
60
- - token_format: "<|y1_{i:03d}|>"
61
- start: 0
62
- end: 999
63
- - token_format: "<|x2_{i:03d}|>"
64
- start: 0
65
- end: 999
66
- - token_format: "<|y2_{i:03d}|>"
67
- start: 0
68
- end: 999
69
- - token_format: "<|score_{i:02d}|>"
70
- start: 0
71
- end: 99
72
- # Inference pipeline config
73
- inference_decode_method: detection
74
- inference_max_tokens: 1000
75
- inference_cfg_uncond: text
76
- inference_add_instruction: false # det uses start_of_det token, not text instruction
77
-
78
- - name: seg
79
- id: 6
80
- kind: image
81
- start_token_key: start_of_image
82
- end_token_key: end_of_image
83
- represent_vit: true
84
- represent_vae: true
85
-
86
- - name: canny
87
- id: 7
88
- kind: image
89
- start_token_key: start_of_image
90
- end_token_key: end_of_image
91
- represent_vit: true
92
- represent_vae: true
93
-
94
- - name: dino
95
- id: 8
96
- kind: codebook
97
- start_token_key: start_of_dino
98
- end_token_key: end_of_dino
99
- pos_embed_size: 16
100
- apply_pos_embed_in_forward: true
101
- represent_vae: true
102
- conditions: [rgb]
103
- start_token: "<|dino_start|>"
104
- end_token: "<|dino_end|>"
105
- code_vocab_size: 8192
106
- code_token_format: "<|dino_{i:04d}|>"
107
- external_tokenizer_kind: vqvae
108
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14-global_8k_16_224"
109
- # Inference pipeline config
110
- inference_decode_method: dino
111
- inference_max_tokens: 17
112
- inference_cfg_uncond: img
113
- inference_cfg_img_scale: 1.0
114
-
115
- - name: dinolocal
116
- id: 9
117
- kind: codebook
118
- start_token_key: start_of_dinolocal
119
- end_token_key: end_of_dinolocal
120
- pos_embed_size: 1024
121
- apply_pos_embed_in_forward: true
122
- represent_vae: true
123
- conditions: [rgb]
124
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
125
- start_token: "<|dinolocal_start|>"
126
- end_token: "<|dinolocal_end|>"
127
- code_vocab_size: 8192
128
- code_token_format: "<|dinolocal_{i:04d}|>"
129
- external_tokenizer_kind: vqvae
130
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14_8k_224-448"
131
- # Inference pipeline config
132
- inference_decode_method: dinolocal
133
- inference_max_tokens: 1025
134
- inference_cfg_uncond: img
135
- inference_cfg_img_scale: 1.0
136
-
137
- - name: clip
138
- id: 10
139
- kind: codebook
140
- start_token_key: start_of_clip
141
- end_token_key: end_of_clip
142
- pos_embed_size: 784
143
- apply_pos_embed_in_forward: true
144
- represent_vae: true
145
- conditions: [rgb]
146
- codebook_spatial_shape: [28, 28] # 784 tokens, 28Γ—28 grid β†’ 2D RoPE
147
- start_token: "<|clip_start|>"
148
- end_token: "<|clip_end|>"
149
- code_vocab_size: 8192
150
- code_token_format: "<|clip_{i:04d}|>"
151
- external_tokenizer_kind: vqvae
152
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_CLIP-B16_8k_224-448"
153
- # Inference pipeline config
154
- inference_decode_method: clip
155
- inference_max_tokens: 785
156
- inference_cfg_uncond: img
157
- inference_cfg_img_scale: 1.0
158
-
159
- - name: imagebind
160
- id: 11
161
- kind: codebook
162
- start_token_key: start_of_imagebind
163
- end_token_key: end_of_imagebind
164
- pos_embed_size: 16
165
- apply_pos_embed_in_forward: true
166
- represent_vae: true
167
- conditions: [rgb]
168
- start_token: "<|imagebind_start|>"
169
- end_token: "<|imagebind_end|>"
170
- code_vocab_size: 8192
171
- code_token_format: "<|imagebind_{i:04d}|>"
172
- external_tokenizer_kind: vqvae
173
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14-global_8k_16_224"
174
- # Inference pipeline config
175
- inference_decode_method: imagebind
176
- inference_max_tokens: 17
177
- inference_cfg_uncond: img
178
- inference_cfg_img_scale: 1.0
179
-
180
- - name: imagebindlocal
181
- id: 12
182
- kind: codebook
183
- start_token_key: start_of_imagebindlocal
184
- end_token_key: end_of_imagebindlocal
185
- pos_embed_size: 1024
186
- apply_pos_embed_in_forward: true
187
- represent_vae: true
188
- conditions: [rgb]
189
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
190
- start_token: "<|imagebindlocal_start|>"
191
- end_token: "<|imagebindlocal_end|>"
192
- code_vocab_size: 8192
193
- code_token_format: "<|imagebindlocal_{i:04d}|>"
194
- external_tokenizer_kind: vqvae
195
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14_8k_224-448"
196
- # Inference pipeline config
197
- inference_decode_method: imagebindlocal
198
- inference_max_tokens: 1025
199
- inference_cfg_uncond: img
200
- inference_cfg_img_scale: 1.0
201
-
202
- # ── cocodet (Pix2seq detection). Coords reuse det's x1/y1/x2/y2 tokens (dedup,
203
- # 0 new); class = COCO-id-aligned tokens (~91 new). dispersed_code_tokens β†’ CE
204
- # is gathered over the non-contiguous {coords βˆͺ class βˆͺ end} set, not a
205
- # contiguous range. No reweight, no learnable pos_embed (RoPE + token-id).
206
- - name: cocodet
207
- id: 13
208
- kind: codebook
209
- start_token_key: start_of_cocodet
210
- end_token_key: end_of_cocodet
211
- start_token: "<|cocodet_start|>"
212
- end_token: "<|cocodet_end|>"
213
- represent_vae: true
214
- conditions: [rgb] # rgb→cocodet, consistent with det/dino/clip in this config
215
- dispersed_code_tokens: true
216
- code_token_groups:
217
- - token_format: "<|x1_{i:03d}|>"
218
- start: 0
219
- end: 999
220
- - token_format: "<|y1_{i:03d}|>"
221
- start: 0
222
- end: 999
223
- - token_format: "<|x2_{i:03d}|>"
224
- start: 0
225
- end: 999
226
- - token_format: "<|y2_{i:03d}|>"
227
- start: 0
228
- end: 999
229
- - token_format: "<|coco_cls_{i:02d}|>"
230
- start: 0
231
- end: 90
232
- # Inference: dedicated model.generate_cocodet + inferencer.gen_cocodet
233
- # (coords+class per box ending on cocodet_end; NOT the grounding/det decode).
234
- inference_decode_method: cocodet
235
- inference_max_tokens: 1000
236
- inference_cfg_uncond: text
237
- inference_add_instruction: false
238
-
239
- # ── SAM segmentation / edge. Mirror seg/canny (direct-PNG image, ViT+VAE, MSE).
240
- # Per-modality start token, shared end_of_image. Start tokens are NEW.
241
- - name: samseg
242
- id: 14
243
- kind: image
244
- start_token_key: start_of_samseg
245
- start_token: "<|samseg_start|>"
246
- end_token_key: end_of_image
247
- represent_vit: true
248
- represent_vae: true
249
-
250
- - name: samedge
251
- id: 15
252
- kind: image
253
- start_token_key: start_of_samedge
254
- start_token: "<|samedge_start|>"
255
- end_token_key: end_of_image
256
- represent_vit: true
257
- represent_vae: true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/instruction_hunyuan_16mod_stage2.yaml DELETED
@@ -1,250 +0,0 @@
1
- modalities:
2
- - name: text
3
- id: 0
4
- kind: text
5
- start_token_key: bos_token_id
6
- end_token_key: eos_token_id
7
-
8
- - name: caption
9
- id: 1
10
- kind: text
11
- start_token_key: bos_token_id
12
- end_token_key: eos_token_id
13
- represent_vae: true
14
- conditions: [rgb, grounding, dino, dinolocal, clip, imagebind, imagebindlocal]
15
-
16
- - name: rgb
17
- id: 2
18
- kind: image
19
- start_token_key: start_of_image
20
- end_token_key: end_of_image
21
- represent_vit: true
22
- represent_vae: true
23
- conditions: [caption, grounding, dino, dinolocal, clip, imagebind, imagebindlocal]
24
-
25
- - name: depth
26
- id: 3
27
- kind: image
28
- start_token_key: start_of_image
29
- end_token_key: end_of_image
30
- represent_vit: true
31
- represent_vae: true
32
-
33
- - name: normal
34
- id: 4
35
- kind: image
36
- start_token_key: start_of_image
37
- end_token_key: end_of_image
38
- represent_vit: true
39
- represent_vae: true
40
-
41
- - name: det
42
- id: 5
43
- kind: codebook
44
- start_token_key: start_of_det
45
- end_token_key: end_of_det
46
- represent_vae: true
47
- # det's learnable pos_embed (size 4) + reweight are REMOVED so cocodet β€” which
48
- # reuses det's x1/y1/x2/y2 tokens (coco_cls is cocodet's own) β€” can carry >4
49
- # coord tokens per sample without indexing the size-4 pos_embed out of bounds.
50
- # det is not a target here (only present for coord-token-id alignment);
51
- # grounding reuses these tokens and is now RoPE-only for its bbox coords.
52
- start_token: "<|det_start|>"
53
- end_token: "<|det_end|>"
54
- extra_tokens: ["<|box_start|>", "<|box_end|>"]
55
- code_token_groups:
56
- - token_format: "<|x1_{i:03d}|>"
57
- start: 0
58
- end: 999
59
- - token_format: "<|y1_{i:03d}|>"
60
- start: 0
61
- end: 999
62
- - token_format: "<|x2_{i:03d}|>"
63
- start: 0
64
- end: 999
65
- - token_format: "<|y2_{i:03d}|>"
66
- start: 0
67
- end: 999
68
- - token_format: "<|score_{i:02d}|>"
69
- start: 0
70
- end: 99
71
- # Inference pipeline config
72
- inference_decode_method: detection
73
- inference_max_tokens: 1000
74
- inference_cfg_uncond: text
75
- inference_add_instruction: false # det uses start_of_det token, not text instruction
76
-
77
- - name: seg
78
- id: 6
79
- kind: image
80
- start_token_key: start_of_image
81
- end_token_key: end_of_image
82
- represent_vit: true
83
- represent_vae: true
84
-
85
- - name: canny
86
- id: 7
87
- kind: image
88
- start_token_key: start_of_image
89
- end_token_key: end_of_image
90
- represent_vit: true
91
- represent_vae: true
92
-
93
- - name: dino
94
- id: 8
95
- kind: codebook
96
- start_token_key: start_of_dino
97
- end_token_key: end_of_dino
98
- pos_embed_size: 16
99
- apply_pos_embed_in_forward: true
100
- represent_vae: true
101
- start_token: "<|dino_start|>"
102
- end_token: "<|dino_end|>"
103
- code_vocab_size: 8192
104
- code_token_format: "<|dino_{i:04d}|>"
105
- external_tokenizer_kind: vqvae
106
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14-global_8k_16_224"
107
- # Inference pipeline config
108
- inference_decode_method: dino
109
- inference_max_tokens: 17
110
- inference_cfg_uncond: img
111
- inference_cfg_img_scale: 1.0
112
-
113
- - name: dinolocal
114
- id: 9
115
- kind: codebook
116
- start_token_key: start_of_dinolocal
117
- end_token_key: end_of_dinolocal
118
- pos_embed_size: 1024
119
- apply_pos_embed_in_forward: true
120
- represent_vae: true
121
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
122
- start_token: "<|dinolocal_start|>"
123
- end_token: "<|dinolocal_end|>"
124
- code_vocab_size: 8192
125
- code_token_format: "<|dinolocal_{i:04d}|>"
126
- external_tokenizer_kind: vqvae
127
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14_8k_224-448"
128
- # Inference pipeline config
129
- inference_decode_method: dinolocal
130
- inference_max_tokens: 1025
131
- inference_cfg_uncond: img
132
- inference_cfg_img_scale: 1.0
133
-
134
- - name: clip
135
- id: 10
136
- kind: codebook
137
- start_token_key: start_of_clip
138
- end_token_key: end_of_clip
139
- pos_embed_size: 784
140
- apply_pos_embed_in_forward: true
141
- represent_vae: true
142
- codebook_spatial_shape: [28, 28] # 784 tokens, 28Γ—28 grid β†’ 2D RoPE
143
- start_token: "<|clip_start|>"
144
- end_token: "<|clip_end|>"
145
- code_vocab_size: 8192
146
- code_token_format: "<|clip_{i:04d}|>"
147
- external_tokenizer_kind: vqvae
148
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_CLIP-B16_8k_224-448"
149
- # Inference pipeline config
150
- inference_decode_method: clip
151
- inference_max_tokens: 785
152
- inference_cfg_uncond: img
153
- inference_cfg_img_scale: 1.0
154
-
155
- - name: imagebind
156
- id: 11
157
- kind: codebook
158
- start_token_key: start_of_imagebind
159
- end_token_key: end_of_imagebind
160
- pos_embed_size: 16
161
- apply_pos_embed_in_forward: true
162
- represent_vae: true
163
- start_token: "<|imagebind_start|>"
164
- end_token: "<|imagebind_end|>"
165
- code_vocab_size: 8192
166
- code_token_format: "<|imagebind_{i:04d}|>"
167
- external_tokenizer_kind: vqvae
168
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14-global_8k_16_224"
169
- # Inference pipeline config
170
- inference_decode_method: imagebind
171
- inference_max_tokens: 17
172
- inference_cfg_uncond: img
173
- inference_cfg_img_scale: 1.0
174
-
175
- - name: imagebindlocal
176
- id: 12
177
- kind: codebook
178
- start_token_key: start_of_imagebindlocal
179
- end_token_key: end_of_imagebindlocal
180
- pos_embed_size: 1024
181
- apply_pos_embed_in_forward: true
182
- represent_vae: true
183
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
184
- start_token: "<|imagebindlocal_start|>"
185
- end_token: "<|imagebindlocal_end|>"
186
- code_vocab_size: 8192
187
- code_token_format: "<|imagebindlocal_{i:04d}|>"
188
- external_tokenizer_kind: vqvae
189
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14_8k_224-448"
190
- # Inference pipeline config
191
- inference_decode_method: imagebindlocal
192
- inference_max_tokens: 1025
193
- inference_cfg_uncond: img
194
- inference_cfg_img_scale: 1.0
195
-
196
- # ── cocodet (Pix2seq detection). Coords reuse det's x1/y1/x2/y2 tokens (dedup,
197
- # 0 new); class = COCO-id-aligned tokens (~91 new). dispersed_code_tokens β†’ CE
198
- # is gathered over the non-contiguous {coords βˆͺ class βˆͺ end} set, not a
199
- # contiguous range. No reweight, no learnable pos_embed (RoPE + token-id).
200
- - name: cocodet
201
- id: 13
202
- kind: codebook
203
- start_token_key: start_of_cocodet
204
- end_token_key: end_of_cocodet
205
- start_token: "<|cocodet_start|>"
206
- end_token: "<|cocodet_end|>"
207
- represent_vae: true
208
- dispersed_code_tokens: true
209
- code_token_groups:
210
- - token_format: "<|x1_{i:03d}|>"
211
- start: 0
212
- end: 999
213
- - token_format: "<|y1_{i:03d}|>"
214
- start: 0
215
- end: 999
216
- - token_format: "<|x2_{i:03d}|>"
217
- start: 0
218
- end: 999
219
- - token_format: "<|y2_{i:03d}|>"
220
- start: 0
221
- end: 999
222
- - token_format: "<|coco_cls_{i:02d}|>"
223
- start: 0
224
- end: 90
225
- # Inference: dedicated model.generate_cocodet + inferencer.gen_cocodet
226
- # (coords+class per box ending on cocodet_end; NOT the grounding/det decode).
227
- inference_decode_method: cocodet
228
- inference_max_tokens: 1000
229
- inference_cfg_uncond: text
230
- inference_add_instruction: false
231
-
232
- # ── SAM segmentation / edge. Mirror seg/canny (direct-PNG image, ViT+VAE, MSE).
233
- # Per-modality start token, shared end_of_image. Start tokens are NEW.
234
- - name: samseg
235
- id: 14
236
- kind: image
237
- start_token_key: start_of_samseg
238
- start_token: "<|samseg_start|>"
239
- end_token_key: end_of_image
240
- represent_vit: true
241
- represent_vae: true
242
-
243
- - name: samedge
244
- id: 15
245
- kind: image
246
- start_token_key: start_of_samedge
247
- start_token: "<|samedge_start|>"
248
- end_token_key: end_of_image
249
- represent_vit: true
250
- represent_vae: true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/instruction_stage2.yaml DELETED
@@ -1,204 +0,0 @@
1
- # Stage 2 modality config β€” unconstrained conditions.
2
- #
3
- # Same modality definitions as instruction.yaml, but with ALL condition
4
- # restrictions removed so every modality can be conditioned on every other.
5
-
6
- modalities:
7
- - name: text
8
- id: 0
9
- kind: text
10
- start_token_key: bos_token_id
11
- end_token_key: eos_token_id
12
-
13
- - name: caption
14
- id: 1
15
- kind: text
16
- start_token_key: bos_token_id
17
- end_token_key: eos_token_id
18
- represent_vae: true
19
- # conditions: unconstrained (any modality can condition caption)
20
-
21
- - name: rgb
22
- id: 2
23
- kind: image
24
- start_token_key: start_of_image
25
- end_token_key: end_of_image
26
- represent_vit: true
27
- represent_vae: true
28
- # conditions: unconstrained
29
-
30
- - name: depth
31
- id: 3
32
- kind: image
33
- start_token_key: start_of_image
34
- end_token_key: end_of_image
35
- represent_vit: true
36
- represent_vae: true
37
-
38
- - name: normal
39
- id: 4
40
- kind: image
41
- start_token_key: start_of_image
42
- end_token_key: end_of_image
43
- represent_vit: true
44
- represent_vae: true
45
-
46
- - name: det
47
- id: 5
48
- kind: codebook
49
- start_token_key: start_of_det
50
- end_token_key: end_of_det
51
- represent_vae: true
52
- pos_embed_size: 4
53
- apply_pos_embed_in_forward: true
54
- pos_embed_name: grounding # checkpoint compat: attr is "grounding_pos_embed"
55
- # conditions: unconstrained
56
- loss:
57
- reweight: true
58
- reweight_min_w: 0.005
59
- start_token: "<|det_start|>"
60
- end_token: "<|det_end|>"
61
- extra_tokens: ["<|box_start|>", "<|box_end|>"]
62
- code_token_groups:
63
- - token_format: "<|x1_{i:03d}|>"
64
- start: 0
65
- end: 999
66
- - token_format: "<|y1_{i:03d}|>"
67
- start: 0
68
- end: 999
69
- - token_format: "<|x2_{i:03d}|>"
70
- start: 0
71
- end: 999
72
- - token_format: "<|y2_{i:03d}|>"
73
- start: 0
74
- end: 999
75
- - token_format: "<|score_{i:02d}|>"
76
- start: 0
77
- end: 99
78
- # Inference pipeline config
79
- inference_decode_method: detection
80
- inference_max_tokens: 1000
81
- inference_cfg_uncond: text
82
- inference_add_instruction: false # det uses start_of_det token, not text instruction
83
-
84
- - name: seg
85
- id: 6
86
- kind: image
87
- start_token_key: start_of_image
88
- end_token_key: end_of_image
89
- represent_vit: true
90
- represent_vae: true
91
-
92
- - name: canny
93
- id: 7
94
- kind: image
95
- start_token_key: start_of_image
96
- end_token_key: end_of_image
97
- represent_vit: true
98
- represent_vae: true
99
-
100
- - name: dino
101
- id: 8
102
- kind: codebook
103
- start_token_key: start_of_dino
104
- end_token_key: end_of_dino
105
- pos_embed_size: 16
106
- apply_pos_embed_in_forward: true
107
- represent_vae: true
108
- # conditions: unconstrained
109
- start_token: "<|dino_start|>"
110
- end_token: "<|dino_end|>"
111
- code_vocab_size: 8192
112
- code_token_format: "<|dino_{i:04d}|>"
113
- external_tokenizer_kind: vqvae
114
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14-global_8k_16_224"
115
- # Inference pipeline config
116
- inference_decode_method: dino
117
- inference_max_tokens: 17
118
- inference_cfg_uncond: img
119
- inference_cfg_img_scale: 1.0
120
-
121
- - name: dinolocal
122
- id: 9
123
- kind: codebook
124
- start_token_key: start_of_dinolocal
125
- end_token_key: end_of_dinolocal
126
- pos_embed_size: 1024
127
- apply_pos_embed_in_forward: true
128
- represent_vae: true
129
- # conditions: unconstrained
130
- start_token: "<|dinolocal_start|>"
131
- end_token: "<|dinolocal_end|>"
132
- code_vocab_size: 8192
133
- code_token_format: "<|dinolocal_{i:04d}|>"
134
- external_tokenizer_kind: vqvae
135
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14_8k_224-448"
136
- # Inference pipeline config
137
- inference_decode_method: dinolocal
138
- inference_max_tokens: 1025
139
- inference_cfg_uncond: img
140
- inference_cfg_img_scale: 1.0
141
-
142
- - name: clip
143
- id: 10
144
- kind: codebook
145
- start_token_key: start_of_clip
146
- end_token_key: end_of_clip
147
- pos_embed_size: 784
148
- apply_pos_embed_in_forward: true
149
- represent_vae: true
150
- # conditions: unconstrained
151
- start_token: "<|clip_start|>"
152
- end_token: "<|clip_end|>"
153
- code_vocab_size: 8192
154
- code_token_format: "<|clip_{i:04d}|>"
155
- external_tokenizer_kind: vqvae
156
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_CLIP-B16_8k_224-448"
157
- # Inference pipeline config
158
- inference_decode_method: clip
159
- inference_max_tokens: 785
160
- inference_cfg_uncond: img
161
- inference_cfg_img_scale: 1.0
162
-
163
- - name: imagebind
164
- id: 11
165
- kind: codebook
166
- start_token_key: start_of_imagebind
167
- end_token_key: end_of_imagebind
168
- pos_embed_size: 16
169
- apply_pos_embed_in_forward: true
170
- represent_vae: true
171
- # conditions: unconstrained
172
- start_token: "<|imagebind_start|>"
173
- end_token: "<|imagebind_end|>"
174
- code_vocab_size: 8192
175
- code_token_format: "<|imagebind_{i:04d}|>"
176
- external_tokenizer_kind: vqvae
177
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14-global_8k_16_224"
178
- # Inference pipeline config
179
- inference_decode_method: imagebind
180
- inference_max_tokens: 17
181
- inference_cfg_uncond: img
182
- inference_cfg_img_scale: 1.0
183
-
184
- - name: imagebindlocal
185
- id: 12
186
- kind: codebook
187
- start_token_key: start_of_imagebindlocal
188
- end_token_key: end_of_imagebindlocal
189
- pos_embed_size: 1024
190
- apply_pos_embed_in_forward: true
191
- represent_vae: true
192
- # conditions: unconstrained
193
- start_token: "<|imagebindlocal_start|>"
194
- end_token: "<|imagebindlocal_end|>"
195
- code_vocab_size: 8192
196
- code_token_format: "<|imagebindlocal_{i:04d}|>"
197
- external_tokenizer_kind: vqvae
198
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14_8k_224-448"
199
- # Inference pipeline config
200
- inference_decode_method: imagebindlocal
201
- inference_max_tokens: 1025
202
- inference_cfg_uncond: img
203
- inference_cfg_img_scale: 1.0
204
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/instruction_t2i_only_stage1.yaml DELETED
@@ -1,27 +0,0 @@
1
- # v65 = T2I-only stage1 modality registry.
2
- # Keeps text + caption + rgb only. Drops every other modality.
3
- # Pair with `hunyuan_image_3_v65_t2i_only.yaml`.
4
-
5
- modalities:
6
- - name: text
7
- id: 0
8
- kind: text
9
- start_token_key: bos_token_id
10
- end_token_key: eos_token_id
11
-
12
- - name: caption
13
- id: 1
14
- kind: text
15
- start_token_key: bos_token_id
16
- end_token_key: eos_token_id
17
- represent_vae: true
18
- conditions: [rgb]
19
-
20
- - name: rgb
21
- id: 2
22
- kind: image
23
- start_token_key: start_of_image
24
- end_token_key: end_of_image
25
- represent_vit: true
26
- represent_vae: true
27
- conditions: [caption]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/legacy.yaml DELETED
@@ -1,38 +0,0 @@
1
- modalities:
2
- - name: text
3
- id: 0
4
- kind: text
5
- start_token_key: bos_token_id
6
- end_token_key: eos_token_id
7
-
8
- - name: caption
9
- id: 1
10
- kind: text
11
- start_token_key: start_of_caption
12
- end_token_key: end_of_caption
13
-
14
- - name: rgb
15
- id: 2
16
- kind: image
17
- start_token_key: start_of_image
18
- end_token_key: end_of_image
19
- represent_vit: true
20
- represent_vae: true
21
-
22
- - name: depth
23
- id: 3
24
- kind: image
25
- start_token_key: start_of_depth
26
- end_token_key: end_of_depth
27
- represent_vit: true
28
- represent_vae: true
29
-
30
- - name: normal
31
- id: 4
32
- kind: image
33
- start_token_key: start_of_normal
34
- end_token_key: end_of_normal
35
- represent_vit: true
36
- represent_vae: true
37
-
38
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/rebuttal_rgb2target.yaml DELETED
@@ -1,116 +0,0 @@
1
- modalities:
2
- - name: text
3
- id: 0
4
- kind: text
5
- start_token_key: bos_token_id
6
- end_token_key: eos_token_id
7
-
8
- - name: caption
9
- id: 1
10
- kind: text
11
- start_token_key: bos_token_id
12
- end_token_key: eos_token_id
13
- represent_vae: true
14
- conditions: [rgb, grounding, dino]
15
-
16
- - name: rgb
17
- id: 2
18
- kind: image
19
- start_token_key: start_of_image
20
- end_token_key: end_of_image
21
- represent_vit: true
22
- represent_vae: true
23
- conditions: [caption, grounding, dino]
24
-
25
- - name: depth
26
- id: 3
27
- kind: image
28
- start_token_key: start_of_image
29
- end_token_key: end_of_image
30
- represent_vit: true
31
- represent_vae: true
32
- conditions: [rgb]
33
-
34
- - name: normal
35
- id: 4
36
- kind: image
37
- start_token_key: start_of_image
38
- end_token_key: end_of_image
39
- represent_vit: true
40
- represent_vae: true
41
- conditions: [rgb]
42
-
43
- - name: det
44
- id: 5
45
- kind: codebook
46
- start_token_key: start_of_det
47
- end_token_key: end_of_det
48
- represent_vae: true
49
- pos_embed_size: 4
50
- apply_pos_embed_in_forward: true
51
- pos_embed_name: grounding # checkpoint compat: attr is "grounding_pos_embed"
52
- conditions: [rgb]
53
- loss:
54
- reweight: true
55
- reweight_min_w: 0.005
56
- start_token: "<|det_start|>"
57
- end_token: "<|det_end|>"
58
- extra_tokens: ["<|box_start|>", "<|box_end|>"]
59
- code_token_groups:
60
- - token_format: "<|x1_{i:03d}|>"
61
- start: 0
62
- end: 999
63
- - token_format: "<|y1_{i:03d}|>"
64
- start: 0
65
- end: 999
66
- - token_format: "<|x2_{i:03d}|>"
67
- start: 0
68
- end: 999
69
- - token_format: "<|y2_{i:03d}|>"
70
- start: 0
71
- end: 999
72
- - token_format: "<|score_{i:02d}|>"
73
- start: 0
74
- end: 99
75
- # Inference pipeline config
76
- inference_decode_method: detection
77
- inference_max_tokens: 1000
78
- inference_cfg_uncond: text
79
- inference_add_instruction: false # det uses start_of_det token, not text instruction
80
-
81
- - name: seg
82
- id: 6
83
- kind: image
84
- start_token_key: start_of_image
85
- end_token_key: end_of_image
86
- represent_vit: true
87
- represent_vae: true
88
-
89
- - name: canny
90
- id: 7
91
- kind: image
92
- start_token_key: start_of_image
93
- end_token_key: end_of_image
94
- represent_vit: true
95
- represent_vae: true
96
-
97
- - name: dino
98
- id: 8
99
- kind: codebook
100
- start_token_key: start_of_dino
101
- end_token_key: end_of_dino
102
- pos_embed_size: 16
103
- apply_pos_embed_in_forward: true
104
- represent_vae: true
105
- conditions: [rgb]
106
- start_token: "<|dino_start|>"
107
- end_token: "<|dino_end|>"
108
- code_vocab_size: 8192
109
- code_token_format: "<|dino_{i:04d}|>"
110
- external_tokenizer_kind: vqvae
111
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14-global_8k_16_224"
112
- # Inference pipeline config
113
- inference_decode_method: dino
114
- inference_max_tokens: 17
115
- inference_cfg_uncond: img
116
- inference_cfg_img_scale: 1.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
conf/modalities/stage1_oversample_bbox2rgb_dinolocal2rgb.yaml DELETED
@@ -1,203 +0,0 @@
1
- modalities:
2
- - name: text
3
- id: 0
4
- kind: text
5
- start_token_key: bos_token_id
6
- end_token_key: eos_token_id
7
-
8
- - name: caption
9
- id: 1
10
- kind: text
11
- start_token_key: bos_token_id
12
- end_token_key: eos_token_id
13
- represent_vae: true
14
- conditions: [rgb, grounding, dinolocal]
15
- condition_probs: [0.1, 0.45, 0.45]
16
-
17
- - name: rgb
18
- id: 2
19
- kind: image
20
- start_token_key: start_of_image
21
- end_token_key: end_of_image
22
- represent_vit: true
23
- represent_vae: true
24
- conditions: [caption, grounding, dinolocal]
25
- condition_probs: [0.1, 0.45, 0.45]
26
-
27
- - name: depth
28
- id: 3
29
- kind: image
30
- start_token_key: start_of_image
31
- end_token_key: end_of_image
32
- represent_vit: true
33
- represent_vae: true
34
-
35
- - name: normal
36
- id: 4
37
- kind: image
38
- start_token_key: start_of_image
39
- end_token_key: end_of_image
40
- represent_vit: true
41
- represent_vae: true
42
-
43
- - name: det
44
- id: 5
45
- kind: codebook
46
- start_token_key: start_of_det
47
- end_token_key: end_of_det
48
- represent_vae: true
49
- pos_embed_size: 4
50
- apply_pos_embed_in_forward: true
51
- pos_embed_name: grounding # checkpoint compat: attr is "grounding_pos_embed"
52
- conditions: [rgb]
53
- loss:
54
- reweight: true
55
- reweight_min_w: 0.005
56
- start_token: "<|det_start|>"
57
- end_token: "<|det_end|>"
58
- extra_tokens: ["<|box_start|>", "<|box_end|>"]
59
- code_token_groups:
60
- - token_format: "<|x1_{i:03d}|>"
61
- start: 0
62
- end: 999
63
- - token_format: "<|y1_{i:03d}|>"
64
- start: 0
65
- end: 999
66
- - token_format: "<|x2_{i:03d}|>"
67
- start: 0
68
- end: 999
69
- - token_format: "<|y2_{i:03d}|>"
70
- start: 0
71
- end: 999
72
- - token_format: "<|score_{i:02d}|>"
73
- start: 0
74
- end: 99
75
- # Inference pipeline config
76
- inference_decode_method: detection
77
- inference_max_tokens: 1000
78
- inference_cfg_uncond: text
79
- inference_add_instruction: false # det uses start_of_det token, not text instruction
80
-
81
- - name: seg
82
- id: 6
83
- kind: image
84
- start_token_key: start_of_image
85
- end_token_key: end_of_image
86
- represent_vit: true
87
- represent_vae: true
88
-
89
- - name: canny
90
- id: 7
91
- kind: image
92
- start_token_key: start_of_image
93
- end_token_key: end_of_image
94
- represent_vit: true
95
- represent_vae: true
96
-
97
- - name: dino
98
- id: 8
99
- kind: codebook
100
- start_token_key: start_of_dino
101
- end_token_key: end_of_dino
102
- pos_embed_size: 16
103
- apply_pos_embed_in_forward: true
104
- represent_vae: true
105
- conditions: [rgb]
106
- start_token: "<|dino_start|>"
107
- end_token: "<|dino_end|>"
108
- code_vocab_size: 8192
109
- code_token_format: "<|dino_{i:04d}|>"
110
- external_tokenizer_kind: vqvae
111
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14-global_8k_16_224"
112
- # Inference pipeline config
113
- inference_decode_method: dino
114
- inference_max_tokens: 17
115
- inference_cfg_uncond: img
116
- inference_cfg_img_scale: 1.0
117
-
118
- - name: dinolocal
119
- id: 9
120
- kind: codebook
121
- start_token_key: start_of_dinolocal
122
- end_token_key: end_of_dinolocal
123
- pos_embed_size: 1024
124
- apply_pos_embed_in_forward: true
125
- represent_vae: true
126
- conditions: [rgb]
127
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
128
- start_token: "<|dinolocal_start|>"
129
- end_token: "<|dinolocal_end|>"
130
- code_vocab_size: 8192
131
- code_token_format: "<|dinolocal_{i:04d}|>"
132
- external_tokenizer_kind: vqvae
133
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_DINOv2-B14_8k_224-448"
134
- # Inference pipeline config
135
- inference_decode_method: dinolocal
136
- inference_max_tokens: 1025
137
- inference_cfg_uncond: img
138
- inference_cfg_img_scale: 1.0
139
-
140
- - name: clip
141
- id: 10
142
- kind: codebook
143
- start_token_key: start_of_clip
144
- end_token_key: end_of_clip
145
- pos_embed_size: 784
146
- apply_pos_embed_in_forward: true
147
- represent_vae: true
148
- conditions: [rgb]
149
- codebook_spatial_shape: [28, 28] # 784 tokens, 28Γ—28 grid β†’ 2D RoPE
150
- start_token: "<|clip_start|>"
151
- end_token: "<|clip_end|>"
152
- code_vocab_size: 8192
153
- code_token_format: "<|clip_{i:04d}|>"
154
- external_tokenizer_kind: vqvae
155
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_CLIP-B16_8k_224-448"
156
- # Inference pipeline config
157
- inference_decode_method: clip
158
- inference_max_tokens: 785
159
- inference_cfg_uncond: img
160
- inference_cfg_img_scale: 1.0
161
-
162
- - name: imagebind
163
- id: 11
164
- kind: codebook
165
- start_token_key: start_of_imagebind
166
- end_token_key: end_of_imagebind
167
- pos_embed_size: 16
168
- apply_pos_embed_in_forward: true
169
- represent_vae: true
170
- conditions: [rgb]
171
- start_token: "<|imagebind_start|>"
172
- end_token: "<|imagebind_end|>"
173
- code_vocab_size: 8192
174
- code_token_format: "<|imagebind_{i:04d}|>"
175
- external_tokenizer_kind: vqvae
176
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14-global_8k_16_224"
177
- # Inference pipeline config
178
- inference_decode_method: imagebind
179
- inference_max_tokens: 17
180
- inference_cfg_uncond: img
181
- inference_cfg_img_scale: 1.0
182
-
183
- - name: imagebindlocal
184
- id: 12
185
- kind: codebook
186
- start_token_key: start_of_imagebindlocal
187
- end_token_key: end_of_imagebindlocal
188
- pos_embed_size: 1024
189
- apply_pos_embed_in_forward: true
190
- represent_vae: true
191
- conditions: [rgb]
192
- codebook_spatial_shape: [32, 32] # 1024 tokens, 32Γ—32 grid β†’ 2D RoPE
193
- start_token: "<|imagebindlocal_start|>"
194
- end_token: "<|imagebindlocal_end|>"
195
- code_vocab_size: 8192
196
- code_token_format: "<|imagebindlocal_{i:04d}|>"
197
- external_tokenizer_kind: vqvae
198
- external_tokenizer_repo: "EPFL-VILAB/4M_tokenizers_ImageBind-H14_8k_224-448"
199
- # Inference pipeline config
200
- inference_decode_method: imagebindlocal
201
- inference_max_tokens: 1025
202
- inference_cfg_uncond: img
203
- inference_cfg_img_scale: 1.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/__init__.py DELETED
@@ -1,11 +0,0 @@
1
- """
2
- Core utilities for MODUS.
3
-
4
- This package is intentionally small and dependency-light so it can be used by:
5
- - training scripts
6
- - inference/demo scripts
7
- - dataset packing
8
- - model code
9
- """
10
-
11
-
 
 
 
 
 
 
 
 
 
 
 
 
core/modality.py DELETED
@@ -1,394 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass, field as dc_field, replace as dc_replace
4
- from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple
5
-
6
-
7
- TokenRange = Tuple[int, int] # (base_id, length)
8
-
9
-
10
- @dataclass(frozen=True)
11
- class LossConfig:
12
- """Per-modality loss configuration (lives in the modality YAML)."""
13
- reweight: bool = False
14
- reweight_min_w: float = 0.005
15
- reweight_det_vocab_dim: int = 4000 # only used when reweight=True for coordinate codebooks
16
-
17
-
18
- @dataclass(frozen=True)
19
- class ModalitySpec:
20
- """
21
- Config-driven definition of a single modality.
22
-
23
- Every tunable per-modality knob lives here so that the training script,
24
- model forward, and dataset packing can all be driven from one YAML.
25
- """
26
-
27
- name: str
28
- id: int
29
- start_token_key: str
30
- end_token_key: str
31
- kind: str = "text" # text | image | codebook
32
-
33
- # Token range ``(base, length)`` for CE-loss vocab slicing.
34
- # Codebook modalities get their own range from the tokenizer; text
35
- # modalities are auto-assigned ``(0, text_vocab_end)`` in
36
- # ``ModalityRegistry.from_config``. Image modalities (ViT/VAE)
37
- # never produce CE tokens, so they keep ``None``.
38
- code_token_range: Optional[TokenRange] = None
39
-
40
- # Explicit, possibly **non-contiguous** set of CE-loss token ids. Used for
41
- # modalities whose alphabet is dispersed across the vocab and therefore
42
- # cannot be expressed as a single ``(base, length)`` range (e.g. cocodet,
43
- # which reuses det's low-vocab coordinate tokens + new high-vocab class/end
44
- # tokens). When set, the model does gather-CE over exactly these ids instead
45
- # of the contiguous-slice path. ``None`` for every existing modality.
46
- code_token_ids: Optional[Tuple[int, ...]] = None
47
-
48
- # Learnable positional embedding for code tokens.
49
- pos_embed_size: Optional[int] = None
50
- apply_pos_embed_in_forward: bool = False
51
-
52
- # Image representation flags (used by dataset packing and inferencer).
53
- represent_vit: bool = True
54
- represent_vae: bool = False
55
-
56
- # Which other modalities may condition this one during training.
57
- # ``None`` means "use all available"; an explicit list restricts.
58
- conditions: Optional[Tuple[str, ...]] = None
59
- # Optional sampling probabilities aligned with ``conditions`` order.
60
- # If omitted, the dataset uses a uniform distribution.
61
- condition_probs: Optional[Tuple[float, ...]] = None
62
-
63
- # Per-modality loss parameters (CE smoothing / reweighting).
64
- loss: LossConfig = LossConfig()
65
-
66
- # Override the model attribute name for the pos-embed (for checkpoint compat).
67
- # Defaults to ``name`` (e.g. modality "dino" β†’ attr ``dino_pos_embed``).
68
- # Set to e.g. "grounding" for det β†’ ``grounding_pos_embed``.
69
- pos_embed_name: Optional[str] = None
70
-
71
- # 2D spatial shape for codebook modalities (h, w).
72
- # None β†’ 1D sequential (uses text region of build_2d_rope, gets sequential positions).
73
- # Set e.g. (32, 32) for dinolocal, (28, 28) for clip β†’ gets 2D RoPE same as VAE latents.
74
- codebook_spatial_shape: Optional[Tuple[int, int]] = None
75
-
76
- # External tokenizer for inference decoding/encoding (e.g. DINO VQVAE).
77
- external_tokenizer_repo: Optional[str] = None
78
- external_tokenizer_kind: str = "vqvae"
79
-
80
- # ── Inference-time configuration ─────────────────────────────────────
81
- # These fields drive the unified inference pipeline so that the
82
- # inferencer does not need per-modality if/elif chains.
83
-
84
- # Decode method: "auto" resolves from ``kind`` (image→image, text→text).
85
- # Explicit values: "image", "text", "detection", "dino".
86
- inference_decode_method: str = "auto"
87
-
88
- # Maximum AR tokens for text / codebook decoding (None β†’ inferencer default).
89
- inference_max_tokens: Optional[int] = None
90
-
91
- # Which CFG unconditional context to use when this modality is a *target*:
92
- # "text" β†’ cfg_text_context (drop text, keep image) e.g. detection
93
- # "img" β†’ cfg_img_context (drop image, keep text) e.g. dino
94
- # "both" β†’ dual CFG with both contexts e.g. image generation
95
- # "none" β†’ no CFG e.g. plain text
96
- inference_cfg_uncond: str = "auto"
97
-
98
- # Whether to prepend "[start {name} x10]" instruction before generation.
99
- inference_add_instruction: bool = True
100
-
101
- # Default CFG image scale for this modality when it is the *target*.
102
- # ``None`` β†’ use the global default from the base config.
103
- inference_cfg_img_scale: Optional[float] = None
104
-
105
-
106
- class ModalityRegistry:
107
- """Runtime registry consumed by dataset packing, model forward, and inferencer."""
108
-
109
- def __init__(self, specs: Iterable[ModalitySpec]):
110
- specs = list(specs)
111
- if len(specs) == 0:
112
- raise ValueError("ModalityRegistry requires at least one ModalitySpec.")
113
-
114
- self._by_name: Dict[str, ModalitySpec] = {}
115
- self._by_id: Dict[int, ModalitySpec] = {}
116
- for s in specs:
117
- if s.name in self._by_name:
118
- raise ValueError(f"Duplicate modality name: {s.name}")
119
- if s.id in self._by_id:
120
- raise ValueError(f"Duplicate modality id: {s.id}")
121
- self._by_name[s.name] = s
122
- self._by_id[s.id] = s
123
-
124
- # ── lookups ──────────────────────────────────────────────────────────
125
-
126
- @property
127
- def specs(self) -> List[ModalitySpec]:
128
- return list(self._by_name.values())
129
-
130
- def get(self, name: str) -> ModalitySpec:
131
- return self._by_name[name]
132
-
133
- def get_by_id(self, modality_id: int) -> ModalitySpec:
134
- return self._by_id[modality_id]
135
-
136
- def name_to_id(self) -> Dict[str, int]:
137
- return {k: v.id for k, v in self._by_name.items()}
138
-
139
- def id_to_name(self) -> Dict[int, str]:
140
- return {k: v.name for k, v in self._by_id.items()}
141
-
142
- def modality_name(self, modality_id: int) -> str:
143
- spec = self._by_id.get(modality_id)
144
- return spec.name if spec is not None else f"unknown_{modality_id}"
145
-
146
- def start_token_key(self, name: str) -> str:
147
- return self._by_name[name].start_token_key
148
-
149
- def end_token_key(self, name: str) -> str:
150
- return self._by_name[name].end_token_key
151
-
152
- def start_token_id(self, new_token_ids: Mapping[str, Any], name: str) -> int:
153
- return int(new_token_ids[self.start_token_key(name)])
154
-
155
- def end_token_id(self, new_token_ids: Mapping[str, Any], name: str) -> int:
156
- return int(new_token_ids[self.end_token_key(name)])
157
-
158
- def code_token_range(self, name: str) -> Optional[TokenRange]:
159
- return self._by_name[name].code_token_range
160
-
161
- def has_codebook_modalities(self) -> bool:
162
- """True if any registered modality is ``kind == 'codebook'``."""
163
- return any(s.kind == "codebook" for s in self._by_name.values())
164
-
165
- def conditions_for(self, target_modality: str) -> Optional[List[str]]:
166
- """
167
- Return the allowed conditioning modalities for *target_modality*.
168
-
169
- ``None`` means "no restriction β€” use all available".
170
- """
171
- spec = self._by_name.get(target_modality)
172
- if spec is None or spec.conditions is None:
173
- return None
174
- return list(spec.conditions)
175
-
176
- def condition_probs_for(self, target_modality: str) -> Optional[List[float]]:
177
- """
178
- Return optional condition sampling probabilities for *target_modality*.
179
-
180
- The returned probabilities align with ``conditions_for(target_modality)``.
181
- ``None`` means "use uniform condition sampling".
182
- """
183
- spec = self._by_name.get(target_modality)
184
- if spec is None or spec.condition_probs is None:
185
- return None
186
- return list(spec.condition_probs)
187
-
188
- def resolve_decode_method(self, name: str) -> str:
189
- """Return the concrete decode method for *name* (resolves ``"auto"``)."""
190
- spec = self._by_name[name]
191
- if spec.inference_decode_method != "auto":
192
- return spec.inference_decode_method
193
- if spec.kind == "image":
194
- return "image"
195
- if spec.kind == "text":
196
- return "text"
197
- # codebook β€” guess from name
198
- if "det" in spec.name:
199
- return "detection"
200
- if spec.name == "dinolocal":
201
- return "dinolocal"
202
- if "dino" in spec.name:
203
- return "dino"
204
- return "text"
205
-
206
- def resolve_cfg_uncond(self, name: str) -> str:
207
- """Return the concrete CFG-uncond context type (resolves ``"auto"``)."""
208
- spec = self._by_name[name]
209
- if spec.inference_cfg_uncond != "auto":
210
- return spec.inference_cfg_uncond
211
- dm = self.resolve_decode_method(name)
212
- if dm == "image":
213
- return "both"
214
- if dm == "detection":
215
- return "text"
216
- if dm in ("dino", "dinolocal"):
217
- return "img"
218
- return "none"
219
-
220
- def resolve_cfg_img_scale(self, name: str, default: float = 2.0) -> float:
221
- """Return the per-modality CFG image scale, or *default* if not specified."""
222
- spec = self._by_name[name]
223
- if spec.inference_cfg_img_scale is not None:
224
- return spec.inference_cfg_img_scale
225
- return default
226
-
227
- def needs_external_tokenizer(self, name: str) -> bool:
228
- """Return True if the modality has an external tokenizer (e.g. DINO VQVAE)."""
229
- spec = self._by_name.get(name)
230
- return spec is not None and spec.external_tokenizer_repo is not None
231
-
232
- def modalities_with_forward_pos_embed(self) -> List[ModalitySpec]:
233
- return [
234
- s
235
- for s in self._by_name.values()
236
- if s.pos_embed_size is not None and s.apply_pos_embed_in_forward
237
- ]
238
-
239
- # ── construction from YAML dict ─────────────────��───────────────────
240
-
241
- @staticmethod
242
- def from_config(
243
- cfg: Any,
244
- *,
245
- token_ranges: Optional[Mapping[str, TokenRange]] = None,
246
- code_token_ids: Optional[Mapping[str, List[int]]] = None,
247
- ) -> "ModalityRegistry":
248
- """
249
- Build a registry from the parsed YAML config dict.
250
-
251
- Expected structure::
252
-
253
- cfg["modalities"]: list[{name, id, start_token_key, end_token_key, kind, ...}]
254
-
255
- ``token_ranges`` maps modality name β†’ ``(base, length)`` and is typically
256
- computed after tokenizer token additions.
257
- """
258
- if cfg is None:
259
- raise ValueError("cfg is required")
260
-
261
- # Accept either ``{"modalities": [...]}`` or a bare list.
262
- modalities = cfg.get("modalities") if isinstance(cfg, dict) else cfg
263
- if modalities is None:
264
- raise ValueError("No 'modalities' key found in cfg")
265
-
266
- specs: List[ModalitySpec] = []
267
- token_ranges = dict(token_ranges or {})
268
- code_token_ids = dict(code_token_ids or {})
269
-
270
- for m in modalities:
271
- name = str(m["name"])
272
-
273
- # Resolve code_token_range: prefer runtime-computed, fall back to cfg.
274
- _range = token_ranges.get(name)
275
- if _range is None and m.get("code_token_range") is not None:
276
- _range = tuple(m["code_token_range"])
277
-
278
- # Resolve explicit (possibly dispersed) code_token_ids, if any.
279
- _ids = code_token_ids.get(name)
280
- if _ids is None and m.get("code_token_ids") is not None:
281
- _ids = m["code_token_ids"]
282
- _ids = tuple(int(i) for i in _ids) if _ids is not None else None
283
-
284
- # pos_embed_size may be None.
285
- _pos = m.get("pos_embed_size")
286
- pos_embed_size = int(_pos) if _pos is not None else None
287
-
288
- # Parse conditions (list of strings or None).
289
- _conds = m.get("conditions")
290
- conditions = tuple(_conds) if _conds is not None else None
291
- _cond_probs = m.get("condition_probs")
292
- condition_probs = tuple(float(x) for x in _cond_probs) if _cond_probs is not None else None
293
- if condition_probs is not None:
294
- if conditions is None:
295
- raise ValueError(
296
- f"Modality '{name}' sets 'condition_probs' without 'conditions'."
297
- )
298
- if len(condition_probs) != len(conditions):
299
- raise ValueError(
300
- f"Modality '{name}' has {len(conditions)} conditions but "
301
- f"{len(condition_probs)} condition_probs."
302
- )
303
- if any(p < 0.0 for p in condition_probs):
304
- raise ValueError(f"Modality '{name}' has negative values in condition_probs.")
305
- probs_sum = float(sum(condition_probs))
306
- if abs(probs_sum - 1.0) > 1e-6:
307
- raise ValueError(
308
- f"Modality '{name}' condition_probs must sum to 1.0, got {probs_sum}."
309
- )
310
-
311
- # Parse per-modality loss config.
312
- _loss_dict = m.get("loss") or {}
313
- loss_cfg = LossConfig(
314
- reweight=bool(_loss_dict.get("reweight", False)),
315
- reweight_min_w=float(_loss_dict.get("reweight_min_w", 0.005)),
316
- reweight_det_vocab_dim=int(_loss_dict.get("reweight_det_vocab_dim", 4000)),
317
- )
318
-
319
- _pe_name = m.get("pos_embed_name")
320
- pe_name = str(_pe_name) if _pe_name is not None else None
321
-
322
- # 2D spatial shape for codebook modalities.
323
- _shape = m.get("codebook_spatial_shape")
324
- codebook_spatial_shape = tuple(int(x) for x in _shape) if _shape is not None else None
325
-
326
- # Inference-time fields (optional in YAML; sensible defaults).
327
- _infer = m.get("inference") or {}
328
- _infer_decode = str(_infer.get("decode_method", m.get("inference_decode_method", "auto")))
329
- _infer_max = _infer.get("max_tokens", m.get("inference_max_tokens"))
330
- _infer_cfg = str(_infer.get("cfg_uncond", m.get("inference_cfg_uncond", "auto")))
331
- _infer_add_instr = bool(_infer.get("add_instruction", m.get("inference_add_instruction", True)))
332
- _infer_cfg_img = _infer.get("cfg_img_scale", m.get("inference_cfg_img_scale"))
333
- _infer_cfg_img = float(_infer_cfg_img) if _infer_cfg_img is not None else None
334
-
335
- spec = ModalitySpec(
336
- name=name,
337
- id=int(m["id"]),
338
- start_token_key=str(m["start_token_key"]),
339
- end_token_key=str(m["end_token_key"]),
340
- kind=str(m.get("kind", "text")),
341
- code_token_range=_range,
342
- code_token_ids=_ids,
343
- pos_embed_size=pos_embed_size,
344
- apply_pos_embed_in_forward=bool(m.get("apply_pos_embed_in_forward", False)),
345
- represent_vit=bool(m.get("represent_vit", True)),
346
- represent_vae=bool(m.get("represent_vae", False)),
347
- conditions=conditions,
348
- condition_probs=condition_probs,
349
- loss=loss_cfg,
350
- pos_embed_name=pe_name,
351
- codebook_spatial_shape=codebook_spatial_shape,
352
- external_tokenizer_repo=m.get("external_tokenizer_repo"),
353
- external_tokenizer_kind=str(m.get("external_tokenizer_kind", "vqvae")),
354
- inference_decode_method=_infer_decode,
355
- inference_max_tokens=int(_infer_max) if _infer_max is not None else None,
356
- inference_cfg_uncond=_infer_cfg,
357
- inference_add_instruction=_infer_add_instr,
358
- inference_cfg_img_scale=_infer_cfg_img,
359
- )
360
- specs.append(spec)
361
-
362
- # ── Auto-assign text token range to text-kind modalities ─────────
363
- # Find text_vocab_end = min base across all codebook ranges.
364
- text_vocab_end: Optional[int] = None
365
- for s in specs:
366
- if s.code_token_range is not None:
367
- cb_base, _ = s.code_token_range
368
- text_vocab_end = int(cb_base) if text_vocab_end is None else min(text_vocab_end, int(cb_base))
369
-
370
- if text_vocab_end is not None:
371
- text_range: TokenRange = (0, text_vocab_end)
372
- specs = [
373
- dc_replace(s, code_token_range=text_range)
374
- if s.kind == "text" and s.code_token_range is None else s
375
- for s in specs
376
- ]
377
-
378
- return ModalityRegistry(specs)
379
-
380
-
381
- def infer_contiguous_token_range(token_ids: List[int]) -> TokenRange:
382
- """Given a list of token IDs, infer ``(base, length)`` and validate contiguity."""
383
- if len(token_ids) == 0:
384
- raise ValueError("token_ids cannot be empty")
385
- token_ids_sorted = sorted(int(x) for x in token_ids)
386
- base = token_ids_sorted[0]
387
- length = len(token_ids_sorted)
388
- expected = list(range(base, base + length))
389
- if token_ids_sorted != expected:
390
- raise ValueError(
391
- "Token IDs are not contiguous; cannot represent as a range efficiently. "
392
- f"base={base}, length={length}, first10={token_ids_sorted[:10]}"
393
- )
394
- return base, length
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/model_registry.py DELETED
@@ -1,47 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from typing import Any, Callable, Dict, Optional, Tuple
5
-
6
-
7
- ModelBuilder = Callable[..., Any]
8
-
9
-
10
- class ModelRegistry:
11
- """
12
- Minimal model registry to decouple scripts from concrete model classes.
13
-
14
- Goal:
15
- - training/inference scripts choose `model.name` in config
16
- - swapping architectures doesn't require editing scripts, only adding a builder registration
17
- """
18
-
19
- def __init__(self):
20
- self._builders: Dict[str, ModelBuilder] = {}
21
-
22
- def register(self, name: str, builder: ModelBuilder) -> None:
23
- if name in self._builders:
24
- raise ValueError(f"Model '{name}' is already registered.")
25
- self._builders[name] = builder
26
-
27
- def build(self, name: str, **kwargs) -> Any:
28
- if name not in self._builders:
29
- known = ", ".join(sorted(self._builders.keys()))
30
- raise KeyError(f"Unknown model '{name}'. Known: {known}")
31
- return self._builders[name](**kwargs)
32
-
33
-
34
- GLOBAL_MODEL_REGISTRY = ModelRegistry()
35
-
36
-
37
- def register_model(name: str) -> Callable[[ModelBuilder], ModelBuilder]:
38
- def _decorator(fn: ModelBuilder) -> ModelBuilder:
39
- GLOBAL_MODEL_REGISTRY.register(name, fn)
40
- return fn
41
- return _decorator
42
-
43
-
44
- def build_model(name: str, **kwargs) -> Any:
45
- return GLOBAL_MODEL_REGISTRY.build(name, **kwargs)
46
-
47
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/tokenizer_utils.py DELETED
@@ -1,257 +0,0 @@
1
- """
2
- Config-driven tokenizer setup.
3
-
4
- All modality tokens (delimiters, codebook entries) are specified in the modality
5
- YAML and added here. No hard-coded ``use_det`` / ``use_dino`` flags.
6
- """
7
- from __future__ import annotations
8
-
9
- from dataclasses import dataclass
10
- from typing import Any, Dict, List, Optional, Tuple
11
-
12
- from core.modality import TokenRange, infer_contiguous_token_range
13
- from data.data_utils import add_special_tokens
14
-
15
-
16
- @dataclass
17
- class TokenizerArtifacts:
18
- """Return value of :func:`build_tokenizer_and_special_tokens`."""
19
- tokenizer: Any
20
- new_token_ids: Dict[str, Any]
21
- token_ranges: Dict[str, TokenRange]
22
- # Explicit dispersed CE-token id sets, for modalities that set
23
- # ``dispersed_code_tokens: true`` (e.g. cocodet). modality name -> id list.
24
- code_token_ids: Dict[str, List[int]]
25
-
26
-
27
- def load_base_tokenizer(*, model_args: Any, training_args: Any):
28
- """Load the base tokenizer (before any token additions).
29
-
30
- Dispatches to HunyuanImage3TokenizerFast when model_name == 'hunyuan_image_3',
31
- otherwise falls back to the standard Qwen2Tokenizer.
32
- """
33
- if getattr(model_args, "model_name", "bagel") == "hunyuan_image_3":
34
- from modeling.hunyuan_image_3.src.tokenization_hunyuan_image_3 import (
35
- HunyuanImage3TokenizerFast,
36
- )
37
- return HunyuanImage3TokenizerFast.from_pretrained(
38
- model_args.model_path,
39
- model_version="HunyuanImage-3.0-Instruct",
40
- )
41
-
42
- from modeling.qwen2.tokenization_qwen2 import Qwen2Tokenizer
43
-
44
- pretrained_path = (
45
- model_args.model_path
46
- if getattr(training_args, "finetune_from_hf", False)
47
- else model_args.llm_path
48
- )
49
- return Qwen2Tokenizer.from_pretrained(pretrained_path)
50
-
51
-
52
- # ── internal helpers ─────────────────────────────────────────────────────────
53
-
54
- def _add_tokens(tokenizer, tokens: List[str]) -> None:
55
- """Add tokens in order, deduplicating while preserving first occurrence."""
56
- seen: set = set()
57
- ordered: List[str] = []
58
- for t in tokens:
59
- if t not in seen:
60
- seen.add(t)
61
- ordered.append(t)
62
- tokenizer.add_tokens(ordered)
63
-
64
-
65
- def _ensure_token_key(
66
- tokenizer,
67
- new_token_ids: Dict[str, Any],
68
- *,
69
- token_key: str,
70
- token_str: Optional[str],
71
- ) -> None:
72
- """
73
- Make sure ``new_token_ids[token_key]`` exists, creating the token if needed.
74
- """
75
- if token_key in new_token_ids:
76
- return
77
- if hasattr(tokenizer, token_key):
78
- new_token_ids[token_key] = int(getattr(tokenizer, token_key))
79
- return
80
- if token_str is None:
81
- raise KeyError(
82
- f"Token key '{token_key}' not in tokenizer or new_token_ids and no token_str provided."
83
- )
84
- try:
85
- tokenizer.add_special_tokens({"additional_special_tokens": [token_str]})
86
- except Exception:
87
- tokenizer.add_tokens([token_str])
88
- new_token_ids[token_key] = int(tokenizer.convert_tokens_to_ids(token_str))
89
-
90
-
91
- def _code_tokens_from_cfg(m: Dict[str, Any]) -> List[str]:
92
- """Build the list of code-token strings from a modality config dict."""
93
-
94
- # Preferred: explicit groups (e.g. DET coordinate tokens).
95
- groups = m.get("code_token_groups")
96
- if groups is not None:
97
- out: List[str] = []
98
- for g in groups:
99
- if not isinstance(g, dict):
100
- g = dict(g)
101
- token_format = str(g["token_format"])
102
- start = int(g.get("start", 0))
103
- end = int(g["end"])
104
- for i in range(start, end + 1):
105
- out.append(token_format.format(
106
- i=i,
107
- prefix=m.get("code_token_prefix", m.get("name")),
108
- ))
109
- return out
110
-
111
- # Fallback: contiguous vocab with format / prefix.
112
- vocab = m.get("code_vocab_size")
113
- if vocab is None:
114
- raise ValueError(
115
- f"Codebook modality '{m.get('name')}' needs 'code_vocab_size' or 'code_token_groups'."
116
- )
117
- vocab = int(vocab)
118
- token_format = str(m.get("code_token_format", "<|{prefix}_{i:04d}|>"))
119
- prefix = str(m.get("code_token_prefix", m.get("name")))
120
- return [token_format.format(prefix=prefix, i=i) for i in range(vocab)]
121
-
122
-
123
- def _add_modality_tokens(
124
- tokenizer,
125
- *,
126
- m: Dict[str, Any],
127
- new_token_ids: Dict[str, Any],
128
- token_ranges: Dict[str, TokenRange],
129
- code_token_ids_out: Dict[str, List[int]],
130
- deferred_tokens: List[Any],
131
- ) -> None:
132
- """
133
- Add all tokens for one modality entry (text/image/codebook) from its YAML dict.
134
-
135
- For **codebook** modalities the addition order is *code tokens first, then
136
- delimiter / extra tokens*. This matches the ordering used when the
137
- original checkpoint was trained so that token IDs remain identical.
138
- """
139
- kind = str(m.get("kind", "text"))
140
- start_key = str(m["start_token_key"])
141
- end_key = str(m["end_token_key"])
142
-
143
- if kind != "codebook":
144
- # Image/text modalities: resolve already-existing start/end tokens now
145
- # (shared start_of_image, or start_of_depth/normal added by
146
- # add_special_tokens). Any genuinely NEW token (e.g. a per-modality
147
- # start_of_seg/canny/samseg/samedge under REPLACE) is DEFERRED and added
148
- # at the very tail in build_tokenizer_and_special_tokens β€” so introducing
149
- # an image modality with its own start token does NOT shift the ids of
150
- # codebook tokens added later in the loop, preserving cross-stage ckpt
151
- # alignment.
152
- vocab = tokenizer.get_vocab()
153
- for token_key, token_str in ((start_key, m.get("start_token")), (end_key, m.get("end_token"))):
154
- if token_key in new_token_ids:
155
- continue
156
- if token_str is not None and str(token_str) in vocab:
157
- new_token_ids[token_key] = int(vocab[str(token_str)])
158
- elif token_str is not None:
159
- deferred_tokens.append((token_key, str(token_str)))
160
- else:
161
- _ensure_token_key(tokenizer, new_token_ids, token_key=token_key, token_str=None)
162
- return
163
-
164
- # ── codebook modality ────────────────────────────────────────────────
165
- code_tokens = _code_tokens_from_cfg(m)
166
-
167
- start_tok = m.get("start_token")
168
- end_tok = m.get("end_token")
169
- delim_tokens: List[str] = []
170
- if start_tok is not None:
171
- delim_tokens.append(str(start_tok))
172
- if end_tok is not None:
173
- delim_tokens.append(str(end_tok))
174
- delim_tokens.extend(str(t) for t in (m.get("extra_tokens") or []))
175
-
176
- # One batch: code tokens first, delimiters after β†’ matches checkpoint ordering.
177
- _add_tokens(tokenizer, code_tokens + delim_tokens)
178
-
179
- # Populate delimiter keys.
180
- if start_tok is not None and start_key not in new_token_ids:
181
- new_token_ids[start_key] = int(tokenizer.convert_tokens_to_ids(str(start_tok)))
182
- elif start_key not in new_token_ids:
183
- _ensure_token_key(tokenizer, new_token_ids, token_key=start_key, token_str=None)
184
- if end_tok is not None and end_key not in new_token_ids:
185
- new_token_ids[end_key] = int(tokenizer.convert_tokens_to_ids(str(end_tok)))
186
- elif end_key not in new_token_ids:
187
- _ensure_token_key(tokenizer, new_token_ids, token_key=end_key, token_str=None)
188
-
189
- name = str(m["name"])
190
- token_ids = [int(tokenizer.convert_tokens_to_ids(t)) for t in code_tokens]
191
- if bool(m.get("dispersed_code_tokens", False)):
192
- # Dispersed alphabet (e.g. cocodet: reused det coords at low vocab ids +
193
- # new class tokens at the tail) β€” cannot be a single contiguous range.
194
- # Record the explicit CE-token id set = all code tokens + the end
195
- # delimiter (a CE target / stop token). The start delimiter is
196
- # input-only, so it is excluded.
197
- ids = list(token_ids)
198
- end_id = new_token_ids.get(end_key)
199
- if end_id is not None:
200
- ids.append(int(end_id))
201
- code_token_ids_out[name] = ids
202
- else:
203
- # Record contiguous code-token range.
204
- token_ranges[name] = infer_contiguous_token_range(token_ids)
205
-
206
-
207
- # ── public API ───────────────────────────────────────────────────────────────
208
-
209
- def build_tokenizer_and_special_tokens(
210
- tokenizer,
211
- *,
212
- modalities_cfg: Dict[str, Any],
213
- ) -> TokenizerArtifacts:
214
- """
215
- Fully config-driven tokenizer setup.
216
-
217
- 1. Adds universal special tokens (``<|im_start|>``, ``<|vision_start|>``, …)
218
- via the legacy ``add_special_tokens`` helper (for checkpoint compat).
219
- 2. Iterates over ``modalities_cfg["modalities"]`` and adds delimiter / code
220
- tokens for each entry.
221
-
222
- Returns a :class:`TokenizerArtifacts` with the final tokenizer, a dict of
223
- all special-token IDs, and a dict of code-token ranges for codebook modalities.
224
- """
225
- token_ranges: Dict[str, TokenRange] = {}
226
- code_token_ids: Dict[str, List[int]] = {}
227
- deferred_tokens: List[Any] = []
228
-
229
- # Step 1: universal special tokens (checkpoint-compatible ordering).
230
- tokenizer, new_token_ids, _num = add_special_tokens(tokenizer)
231
-
232
- # Step 2: per-modality tokens from YAML.
233
- modalities = modalities_cfg.get("modalities", modalities_cfg)
234
- for m in modalities:
235
- if not isinstance(m, dict):
236
- m = dict(m)
237
- _add_modality_tokens(
238
- tokenizer,
239
- m=m,
240
- new_token_ids=new_token_ids,
241
- token_ranges=token_ranges,
242
- code_token_ids_out=code_token_ids,
243
- deferred_tokens=deferred_tokens,
244
- )
245
-
246
- # Step 3: append deferred NEW image-modality start/end tokens at the tail
247
- # (after all codebook tokens) so they never shift existing ids across stages.
248
- for token_key, token_str in deferred_tokens:
249
- if token_key not in new_token_ids:
250
- _ensure_token_key(tokenizer, new_token_ids, token_key=token_key, token_str=token_str)
251
-
252
- return TokenizerArtifacts(
253
- tokenizer=tokenizer,
254
- new_token_ids=new_token_ids,
255
- token_ranges=token_ranges,
256
- code_token_ids=code_token_ids,
257
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/__init__.py DELETED
@@ -1,2 +0,0 @@
1
- # Copyright 2025 Bytedance Ltd. and/or its affiliates.
2
- # SPDX-License-Identifier: Apache-2.0
 
 
 
data/any2any_preprocess/_build_preview_and_montage.py DELETED
@@ -1,52 +0,0 @@
1
- import io, os
2
- import pyarrow.parquet as pq
3
- from PIL import Image, ImageDraw
4
-
5
- BASE_SRC = ('datasets/blip3o/parquet_rgb_caption_depth_normal_det_seg_grounding'
6
- '_canny_dino_global_clip448_imagebind_samseg_samedge_cocodet')
7
- Q95 = 'datasets/blip3o/parquet_16mod_normalq95_sample/sa_000000.parquet'
8
- PREVIEW_DIR = 'datasets/blip3o/modus_preview'
9
- MONTAGE = 'eval_outputs/modus_hero_montage.png'
10
- N_ROWS = 500
11
-
12
- # ---- 1. print sample uids per source to confirm the alignment key ----
13
- print('==== uid samples (alignment key check) ====', flush=True)
14
- for f in ['sa_000000.parquet', 'webdataset_shard_000.parquet', 'webdataset_JDB_2.parquet']:
15
- p = f'{BASE_SRC}/{f}'
16
- t = pq.ParquetFile(p).read_row_group(0, columns=['uid'])
17
- uids = t.column('uid').to_pylist()[:4]
18
- print(f' {f}: {uids}', flush=True)
19
-
20
- # ---- 2. build small preview parquet (subset of q95-compressed sa_000000) ----
21
- os.makedirs(PREVIEW_DIR, exist_ok=True)
22
- pf = pq.ParquetFile(Q95)
23
- table = pf.read_row_group(0).slice(0, N_ROWS)
24
- dst = f'{PREVIEW_DIR}/modus_preview_sa_500.parquet'
25
- pq.write_table(table, dst, compression='snappy')
26
- print(f'\npreview: {dst} rows={table.num_rows} size={os.path.getsize(dst)/1e6:.1f} MB', flush=True)
27
-
28
- # ---- 3. hero montage: rows=samples, cols=modalities ----
29
- MODS = ['rgb', 'depth', 'normal', 'canny', 'sam_seg', 'sam_edge']
30
- N_SHOW = 5
31
- cell, pad, hdr = 300, 6, 26
32
- cols = {m: table.column(m).to_pylist()[:N_SHOW] for m in MODS}
33
-
34
- def dec(b):
35
- if b is None: return None
36
- if isinstance(b, memoryview): b = b.tobytes()
37
- return Image.open(io.BytesIO(b)).convert('RGB')
38
-
39
- W = len(MODS)*(cell+pad)+pad
40
- H = hdr + N_SHOW*(cell+pad)+pad
41
- grid = Image.new('RGB', (W, H), 'white')
42
- d = ImageDraw.Draw(grid)
43
- for c, m in enumerate(MODS):
44
- d.text((pad+c*(cell+pad)+4, 6), m, fill='black')
45
- for r in range(N_SHOW):
46
- for c, m in enumerate(MODS):
47
- im = dec(cols[m][r])
48
- if im is None: continue
49
- grid.paste(im.resize((cell, cell)), (pad+c*(cell+pad), hdr+pad+r*(cell+pad)))
50
- os.makedirs(os.path.dirname(MONTAGE), exist_ok=True)
51
- grid.save(MONTAGE)
52
- print(f'montage: {MONTAGE} ({W}x{H})', flush=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/_cast_preview_images.py DELETED
@@ -1,34 +0,0 @@
1
- """Cast the preview parquet's raw-bytes image columns to the HF `Image` feature
2
- so the dataset viewer renders thumbnails."""
3
- import json
4
- import os
5
- from datasets import load_dataset, Image, Value
6
-
7
- SRC = 'datasets/blip3o/modus_preview/modus_preview_sa_500.parquet'
8
- OUT_DIR = 'datasets/blip3o/modus_preview_upload/data'
9
- OUT = f'{OUT_DIR}/modus_preview_sa_500.parquet'
10
- IMG_COLS = ['rgb', 'depth', 'normal', 'canny', 'sam_seg', 'sam_edge']
11
-
12
- # Display-name order requested by the user (uid=key first; det_seg + grounding
13
- # raw blobs moved to the very end).
14
- ORDER = [
15
- 'uid', 'rgb', 'depth', 'normal', 'sam_seg', 'canny', 'sam_edge',
16
- 'coco_det', 'caption', 'dino_global', 'dino', 'clip448',
17
- 'imagebind_global', 'imagebind', 'det_seg', 'grounding',
18
- ]
19
-
20
- ds = load_dataset('parquet', data_files=SRC, split='train')
21
- print('loaded:', ds.num_rows, 'rows; features before:', list(ds.features.keys()))
22
- for c in IMG_COLS:
23
- ds = ds.cast_column(c, Image())
24
- # Flatten deep-nested blobs to JSON strings so the HF viewer worker doesn't OOM.
25
- for c in ['det_seg', 'grounding']:
26
- ds = ds.map(lambda ex, col=c: {col: json.dumps(ex[col], default=str, ensure_ascii=False)})
27
- ds = ds.cast_column(c, Value('string'))
28
- assert set(ORDER) == set(ds.column_names), set(ORDER) ^ set(ds.column_names)
29
- ds = ds.select_columns(ORDER)
30
- print('column order:', ds.column_names)
31
-
32
- os.makedirs(OUT_DIR, exist_ok=True)
33
- ds.to_parquet(OUT)
34
- print(f'wrote {OUT} size={os.path.getsize(OUT)/1e6:.1f} MB', flush=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/_sb_full_rebuild.sh DELETED
@@ -1,18 +0,0 @@
1
- #!/bin/bash
2
- #SBATCH --job-name=modus_full_rebuild
3
- #SBATCH --partition=debug
4
- #SBATCH --time=01:30:00
5
- #SBATCH --nodes=1
6
- #SBATCH --ntasks-per-node=1
7
- #SBATCH --cpus-per-task=128
8
- #SBATCH --mem=700GB
9
- #SBATCH --account=a143
10
- #SBATCH --output=logs/full_rebuild_%j.out
11
- #SBATCH --error=logs/full_rebuild_%j.err
12
- #SBATCH --environment=/iopsstor/scratch/cscs/mye/workspace/BAGEL/environment/bagel.toml
13
-
14
- M=/iopsstor/scratch/cscs/mye/workspace/MODUS_RELEASE/MODUS
15
- srun --environment=/iopsstor/scratch/cscs/mye/workspace/BAGEL/environment/bagel.toml \
16
- bash -c "cd $M && export PYTHONPATH='$M/data/any2any_preprocess:\$PYTHONPATH' && \
17
- python3 data/any2any_preprocess/run_full_rebuild.py \
18
- --num-tasks 1 --task-id 0 --concurrency 64"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/_sb_recompress_normal_sample.sh DELETED
@@ -1,24 +0,0 @@
1
- #!/bin/bash
2
- #SBATCH --job-name=norm_q95_smp
3
- #SBATCH --partition=debug
4
- #SBATCH --time=01:30:00
5
- #SBATCH --nodes=1
6
- #SBATCH --ntasks-per-node=1
7
- #SBATCH --cpus-per-task=8
8
- #SBATCH --mem=64GB
9
- #SBATCH --account=a143
10
- #SBATCH --output=logs/norm_q95_sample_%j.out
11
- #SBATCH --error=logs/norm_q95_sample_%j.err
12
- #SBATCH --environment=/iopsstor/scratch/cscs/mye/workspace/BAGEL/environment/bagel.toml
13
-
14
- M=/iopsstor/scratch/cscs/mye/workspace/MODUS_RELEASE/MODUS
15
- SRC="$M/datasets/blip3o/parquet_rgb_caption_depth_normal_det_seg_grounding_canny_dino_global_clip448_imagebind_samseg_samedge_cocodet"
16
- OUT="$M/datasets/blip3o/parquet_16mod_normalq95_sample"
17
-
18
- srun --environment=/iopsstor/scratch/cscs/mye/workspace/BAGEL/environment/bagel.toml \
19
- bash -c "cd $M && python3 data/any2any_preprocess/recompress_normal_jpeg.py \
20
- --out_dir '$OUT' \
21
- '$SRC/sa_000000.parquet' \
22
- '$SRC/sa_000001.parquet' \
23
- '$SRC/sa_000002.parquet' \
24
- '$SRC/sa_000003.parquet'"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/_sb_upload.sh DELETED
@@ -1,21 +0,0 @@
1
- #!/bin/bash
2
- #SBATCH --job-name=modus_upload
3
- #SBATCH --partition=debug
4
- #SBATCH --time=01:30:00
5
- #SBATCH --nodes=1
6
- #SBATCH --ntasks-per-node=1
7
- #SBATCH --cpus-per-task=32
8
- #SBATCH --mem=200GB
9
- #SBATCH --account=a143
10
- #SBATCH --output=logs/upload_%j.out
11
- #SBATCH --error=logs/upload_%j.err
12
- #SBATCH --environment=/iopsstor/scratch/cscs/mye/workspace/BAGEL/environment/bagel.toml
13
-
14
- # Resumable upload of the whole modus_full folder (all configs) + README.
15
- M=/iopsstor/scratch/cscs/mye/workspace/MODUS_RELEASE/MODUS
16
- srun --environment=/iopsstor/scratch/cscs/mye/workspace/BAGEL/environment/bagel.toml \
17
- bash -c "cd $M && export HF_TOKEN_FILE=/users/mye/.hf_token && \
18
- python3 -c \"from huggingface_hub import HfApi; t=open('/users/mye/.hf_token').read().strip(); \
19
- HfApi(token=t).upload_file(path_or_fileobj='datasets/blip3o/modus_preview_upload/README.md', path_in_repo='README.md', repo_id='epfl-vilab-modus/MODUS-16Modality', repo_type='dataset', commit_message='full configs card'); print('README ok')\" ; \
20
- python3 data/any2any_preprocess/upload_full.py \
21
- epfl-vilab-modus/MODUS-16Modality datasets/blip3o/modus_full '*/*.parquet'"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/_verify_normal_q95.py DELETED
@@ -1,34 +0,0 @@
1
- import io, os
2
- import pyarrow.parquet as pq
3
- from PIL import Image
4
-
5
- SRC = ('datasets/blip3o/parquet_rgb_caption_depth_normal_det_seg_grounding'
6
- '_canny_dino_global_clip448_imagebind_samseg_samedge_cocodet/sa_000000.parquet')
7
- NEW = 'datasets/blip3o/parquet_16mod_normalq95_sample/sa_000000.parquet'
8
- OUT = 'eval_outputs/normal_q95_check'
9
- os.makedirs(OUT, exist_ok=True)
10
-
11
- def load(path, col, n):
12
- t = pq.ParquetFile(path).read_row_group(0, columns=[col])
13
- vals = t.column(col).to_pylist()[:n]
14
- out = []
15
- for b in vals:
16
- if isinstance(b, memoryview): b = b.tobytes()
17
- out.append(Image.open(io.BytesIO(b)).convert('RGB'))
18
- return out
19
-
20
- N = 3
21
- rgb = load(SRC, 'rgb', N)
22
- nsrc = load(SRC, 'normal', N)
23
- nnew = load(NEW, 'normal', N)
24
-
25
- cell = 384
26
- grid = Image.new('RGB', (cell*3, cell*N), 'white')
27
- for r in range(N):
28
- for c, im in enumerate([rgb[r], nsrc[r], nnew[r]]):
29
- grid.paste(im.resize((cell, cell)), (c*cell, r*cell))
30
- path = f'{OUT}/rgb_normalPNG_normalJPEGq95.png'
31
- grid.save(path)
32
- print(f'saved {path} (cols: rgb | normal-PNG(src) | normal-JPEGq95(new))', flush=True)
33
- for r in range(N):
34
- print(f' row{r}: normal src size={nsrc[r].size} new size={nnew[r].size}', flush=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/build_full_release.py DELETED
@@ -1,108 +0,0 @@
1
- """Build the MODUS full-release parquet for one source config.
2
-
3
- Per config:
4
- sa1b / journeydb : annotations-only -> DROP ['rgb','caption'] ; recompress normal->JPEG q95
5
- cc12m : self-contained -> keep all columns ; recompress normal->JPEG q95
6
-
7
- Streams by row group (bounded memory). Resumable: skips outputs that already
8
- exist and are complete; writes to a .tmp then atomically renames.
9
-
10
- Usage:
11
- python build_full_release.py --config sa1b --out_dir <dir> --workers 8 file1.parquet [...]
12
- """
13
- import argparse
14
- import io
15
- import os
16
- from concurrent.futures import ProcessPoolExecutor, as_completed
17
-
18
- import pyarrow as pa
19
- import pyarrow.parquet as pq
20
- from PIL import Image
21
-
22
- DROP = {'sa1b': ['rgb', 'caption'], 'journeydb': ['rgb', 'caption'], 'cc12m': []}
23
- JPEG_QUALITY = 95
24
-
25
-
26
- def _recompress_normal(b):
27
- if b is None:
28
- return None
29
- if isinstance(b, memoryview):
30
- b = b.tobytes()
31
- with Image.open(io.BytesIO(b)) as im:
32
- img = im.convert('RGB')
33
- out = io.BytesIO()
34
- img.save(out, format='JPEG', quality=JPEG_QUALITY)
35
- img.close()
36
- return out.getvalue()
37
-
38
-
39
- def process_one(src, dst, drop_cols):
40
- if os.path.exists(dst) and os.path.getsize(dst) > 0:
41
- return src, 'skip', os.path.getsize(dst)
42
- tmp = dst + '.tmp'
43
- pf = pq.ParquetFile(src)
44
- names = pf.schema_arrow.names
45
- keep = [n for n in names if n not in drop_cols]
46
- out_schema = pa.schema([pf.schema_arrow.field(n) for n in keep])
47
- has_normal = 'normal' in keep
48
- norm_field = out_schema.field('normal') if has_normal else None
49
- ni = keep.index('normal') if has_normal else -1
50
-
51
- # Stream in small batches so peak memory is bounded regardless of row-group size.
52
- writer = pq.ParquetWriter(tmp, out_schema, compression='snappy')
53
- try:
54
- for batch in pf.iter_batches(batch_size=128, columns=keep):
55
- t = pa.Table.from_batches([batch], schema=out_schema)
56
- if has_normal:
57
- col = [_recompress_normal(b) for b in t.column('normal').to_pylist()]
58
- t = t.set_column(ni, norm_field, pa.array(col, type=norm_field.type))
59
- writer.write_table(t)
60
- del t
61
- finally:
62
- writer.close()
63
- os.replace(tmp, dst)
64
- return src, 'done', os.path.getsize(dst)
65
-
66
-
67
- def main():
68
- ap = argparse.ArgumentParser()
69
- ap.add_argument('--config', required=True, choices=list(DROP))
70
- ap.add_argument('--out_dir', required=True)
71
- ap.add_argument('--workers', type=int, default=8)
72
- ap.add_argument('files', nargs='+')
73
- args = ap.parse_args()
74
- os.makedirs(args.out_dir, exist_ok=True)
75
- drop_cols = DROP[args.config]
76
-
77
- tasks = [(f, os.path.join(args.out_dir, os.path.basename(f)), drop_cols)
78
- for f in args.files]
79
- done = skip = 0
80
-
81
- if args.workers == 1:
82
- # Sequential, in-process (used when spawned as a per-chunk subprocess so
83
- # the OS reclaims all memory on exit β€” no leak accumulation).
84
- for src, dst, dc in tasks:
85
- _, status, _ = process_one(src, dst, dc)
86
- done += status == 'done'
87
- skip += status == 'skip'
88
- print(f'{status} {os.path.basename(src)}', flush=True)
89
- print(f'CHUNK {args.config}: {done} built, {skip} skipped', flush=True)
90
- return
91
-
92
- tot_out = 0
93
- with ProcessPoolExecutor(max_workers=args.workers) as ex:
94
- futs = [ex.submit(process_one, *t) for t in tasks]
95
- for i, fu in enumerate(as_completed(futs), 1):
96
- src, status, sz = fu.result()
97
- tot_out += sz
98
- done += status == 'done'
99
- skip += status == 'skip'
100
- if i % 10 == 0 or status == 'done':
101
- print(f'[{i}/{len(tasks)}] {status} {os.path.basename(src)} '
102
- f'done={done} skip={skip} out={tot_out/1e9:.1f}GB', flush=True)
103
- print(f'CONFIG {args.config}: {done} built, {skip} skipped, '
104
- f'total_out={tot_out/1e9:.1f}GB', flush=True)
105
-
106
-
107
- if __name__ == '__main__':
108
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/check_vqa.py DELETED
@@ -1,188 +0,0 @@
1
- import os
2
- import json
3
- import argparse
4
- from typing import Dict, Any, Tuple, List
5
-
6
-
7
- def is_valid_name(name: Any) -> bool:
8
- return isinstance(name, str) and len(name.strip()) > 0
9
-
10
-
11
- def process_labels_jsonl(images_dir: str, labels_path: str, max_lines: int = -1) -> Tuple[Dict[str, Any], List[str]]:
12
- stats: Dict[str, Any] = dict(
13
- num_records=0,
14
- num_invalid_json=0,
15
- num_image_refs=0,
16
- num_images_missing=0,
17
- num_unique_images_missing=0,
18
- num_video_refs=0,
19
- num_videos_missing=0,
20
- )
21
- missing_images_unique = set()
22
-
23
- with open(labels_path, "r") as f:
24
- for line_idx, line in enumerate(f):
25
- if max_lines > 0 and line_idx >= max_lines:
26
- break
27
- try:
28
- record = json.loads(line)
29
- except Exception:
30
- stats["num_invalid_json"] += 1
31
- continue
32
-
33
- stats["num_records"] += 1
34
-
35
- # Check images (string or list)
36
- if "image" in record:
37
- if isinstance(record["image"], list):
38
- image_names = record["image"]
39
- else:
40
- image_names = [record["image"]]
41
-
42
- for name in image_names:
43
- stats["num_image_refs"] += 1
44
- if not is_valid_name(name):
45
- stats["num_images_missing"] += 1
46
- missing_images_unique.add(str(name))
47
- continue
48
- image_path = os.path.join(images_dir, name)
49
- if not os.path.isfile(image_path):
50
- stats["num_images_missing"] += 1
51
- missing_images_unique.add(name)
52
-
53
- # Check video if present (optional)
54
- if "video" in record:
55
- stats["num_video_refs"] += 1
56
- video_name = record["video"]
57
- if not is_valid_name(video_name):
58
- stats["num_videos_missing"] += 1
59
- else:
60
- video_path = os.path.join(images_dir, video_name)
61
- if not os.path.isfile(video_path):
62
- stats["num_videos_missing"] += 1
63
-
64
- stats["num_unique_images_missing"] = len(missing_images_unique)
65
- # Return up to some examples of missing files for debugging
66
- sample_missing = list(missing_images_unique)[:50]
67
- return stats, sample_missing
68
-
69
-
70
- def scan_base_dir(base_dir: str, max_lines: int = -1) -> Dict[str, Any]:
71
- summary: Dict[str, Any] = dict(per_dataset={}, totals={})
72
- totals = dict(
73
- datasets_scanned=0,
74
- num_records=0,
75
- num_invalid_json=0,
76
- num_image_refs=0,
77
- num_images_missing=0,
78
- num_unique_images_missing=0, # accumulated across datasets (sum, not set-union)
79
- num_video_refs=0,
80
- num_videos_missing=0,
81
- )
82
-
83
- if not os.path.isdir(base_dir):
84
- raise FileNotFoundError(f"Base dir does not exist: {base_dir}")
85
-
86
- # Pre-collect datasets that have labels.jsonl for better progress logging
87
- ds_entries = []
88
- for entry in sorted(os.listdir(base_dir)):
89
- ds_dir = os.path.join(base_dir, entry)
90
- if not os.path.isdir(ds_dir):
91
- continue
92
- labels_path = os.path.join(ds_dir, "label", "labels.jsonl")
93
- if os.path.isfile(labels_path):
94
- ds_entries.append(entry)
95
-
96
- total_datasets = len(ds_entries)
97
- for idx, entry in enumerate(ds_entries, 1):
98
- ds_dir = os.path.join(base_dir, entry)
99
- images_dir = os.path.join(ds_dir, "images")
100
- labels_path = os.path.join(ds_dir, "label", "labels.jsonl")
101
-
102
- print(f"[{idx}/{total_datasets}] Scanning {entry} ...", flush=True)
103
-
104
- totals["datasets_scanned"] += 1
105
- ds_stats, sample_missing = process_labels_jsonl(images_dir, labels_path, max_lines=max_lines)
106
-
107
- summary["per_dataset"][entry] = dict(stats=ds_stats, sample_missing_images=sample_missing)
108
-
109
- # Accumulate totals (note: unique missing is summed per dataset)
110
- for k in [
111
- "num_records",
112
- "num_invalid_json",
113
- "num_image_refs",
114
- "num_images_missing",
115
- "num_unique_images_missing",
116
- "num_video_refs",
117
- "num_videos_missing",
118
- ]:
119
- totals[k] += ds_stats[k]
120
-
121
- # Immediate per-dataset summary line
122
- pct = 100.0 * ds_stats["num_images_missing"] / max(1, ds_stats["num_image_refs"])
123
- print(
124
- f" -> records={ds_stats['num_records']}, image_refs={ds_stats['num_image_refs']}, "
125
- f"missing_images={ds_stats['num_images_missing']} ({pct:.3f}%), videos_missing={ds_stats['num_videos_missing']}",
126
- flush=True,
127
- )
128
-
129
- summary["totals"] = totals
130
- return summary
131
-
132
-
133
- def main():
134
- parser = argparse.ArgumentParser(description="Check VQA JSONL image/video references against images directories.")
135
- parser.add_argument(
136
- "--base_dir",
137
- type=str,
138
- default="./datasets/llava_onevision_vqa",
139
- help="Root directory containing per-dataset subfolders with images/ and label/labels.jsonl",
140
- )
141
- parser.add_argument(
142
- "--max_lines",
143
- type=int,
144
- default=-1,
145
- help="If >0, limit number of lines per JSONL file for a faster sample-based check.",
146
- )
147
- parser.add_argument(
148
- "--out_json",
149
- type=str,
150
- default="",
151
- help="Optional path to write a JSON summary report.",
152
- )
153
-
154
- args = parser.parse_args()
155
-
156
- summary = scan_base_dir(args.base_dir, max_lines=args.max_lines)
157
-
158
- totals = summary["totals"]
159
- print("==== VQA Consistency Check ====")
160
- print(
161
- f"Datasets: {totals['datasets_scanned']} | Records: {totals['num_records']} | "
162
- f"Image refs: {totals['num_image_refs']} | Missing images: {totals['num_images_missing']} | "
163
- f"Video refs: {totals['num_video_refs']} | Missing videos: {totals['num_videos_missing']}"
164
- )
165
- if totals["num_image_refs"] > 0:
166
- miss_pct = 100.0 * totals["num_images_missing"] / max(1, totals["num_image_refs"])
167
- print(f"Image missing rate: {miss_pct:.4f}%")
168
-
169
- # Print a few per-dataset highlights
170
- for ds_name, data in list(summary["per_dataset"].items())[:20]:
171
- s = data["stats"]
172
- pct = 100.0 * s["num_images_missing"] / max(1, s["num_image_refs"])
173
- print(
174
- f"- {ds_name}: records={s['num_records']}, image_refs={s['num_image_refs']}, "
175
- f"missing_images={s['num_images_missing']} ({pct:.3f}%)"
176
- )
177
-
178
- if args.out_json:
179
- with open(args.out_json, "w") as f:
180
- json.dump(summary, f)
181
- print(f"Summary written to {args.out_json}")
182
-
183
-
184
- if __name__ == "__main__":
185
- main()
186
-
187
-
188
- # python data/any2any_preprocess/check_vqa.py --base_dir ./datasets/llava_onevision_vqa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/generate_parquest_grounding_canny_dino_global.py DELETED
@@ -1,205 +0,0 @@
1
- import os
2
- import io
3
- import glob
4
- import tarfile
5
- import logging
6
- from typing import Dict, List, Optional
7
-
8
- import numpy as np
9
- import pandas as pd
10
-
11
-
12
- # Set up logging
13
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
14
- logger = logging.getLogger(__name__)
15
-
16
-
17
- class GlobalDinoReplacer:
18
- def __init__(self, base_dir: str,
19
- input_parquet_dir: Optional[str] = None,
20
- global_dino_dir: Optional[str] = None,
21
- output_parquet_dir: Optional[str] = None) -> None:
22
- self.base_dir = base_dir
23
-
24
- # Defaults aligned with generate_parquet_grounding_canny_dino.py
25
- self.input_parquet_dir = input_parquet_dir or os.path.join(
26
- base_dir, 'parquet_rgb_caption_depth_normal_det_seg_grounding_canny_dino'
27
- )
28
- self.global_dino_dir = global_dino_dir or os.path.join(base_dir, 'dinov2_global')
29
- self.output_parquet_dir = output_parquet_dir or os.path.join(
30
- base_dir, 'parquet_rgb_caption_depth_normal_det_seg_grounding_canny_dino_global'
31
- )
32
-
33
- os.makedirs(self.output_parquet_dir, exist_ok=True)
34
-
35
- def _list_input_parquets(self) -> List[str]:
36
- files = glob.glob(os.path.join(self.input_parquet_dir, '*.parquet'))
37
- return sorted(files)
38
-
39
- def _load_global_dino_from_tar(self, tar_path: str) -> Dict[str, np.ndarray]:
40
- """Load all uid -> flattened tokens from a dinov2_global tar."""
41
- results: Dict[str, np.ndarray] = {}
42
-
43
- if not os.path.exists(tar_path):
44
- logger.warning(f"Global DINO tar not found: {tar_path}")
45
- return results
46
-
47
- try:
48
- with tarfile.open(tar_path, 'r') as tar:
49
- for member in tar.getmembers():
50
- if not member.isfile():
51
- continue
52
- name = member.name
53
- if not name.endswith('_tokens_global.npy'):
54
- continue
55
-
56
- file_obj = tar.extractfile(member)
57
- if file_obj is None:
58
- continue
59
-
60
- content = file_obj.read()
61
- npy = np.load(io.BytesIO(content))
62
-
63
- # Derive uid by stripping extension and the trailing `_tokens`
64
- uid = os.path.splitext(name)[0]
65
- if uid.endswith('_tokens_global'):
66
- uid = uid[:-len('_tokens_global')]
67
-
68
- results[uid] = npy.flatten()
69
- except Exception as e:
70
- logger.error(f"Failed to read {tar_path}: {e}")
71
-
72
- return results
73
-
74
- def _replace_dino_column(self, df: pd.DataFrame, uid_to_tokens: Dict[str, np.ndarray],
75
- strict: bool = False) -> pd.DataFrame:
76
- if 'uid' not in df.columns:
77
- raise ValueError("Input parquet missing required 'uid' column")
78
-
79
- num_rows = len(df)
80
- replaced = 0
81
- missing = 0
82
-
83
- # Prepare a new column without mutating during iteration to avoid pandas warnings
84
- new_dino_column: List[object] = []
85
- for _, row in df.iterrows():
86
- uid = row['uid']
87
- tokens = uid_to_tokens.get(uid)
88
- if tokens is None:
89
- missing += 1
90
- if strict:
91
- new_dino_column.append(None)
92
- else:
93
- # Keep original if present
94
- new_dino_column.append(row.get('dino_global', None))
95
- else:
96
- new_dino_column.append(tokens)
97
- replaced += 1
98
-
99
- logger.info(f"Replaced DINO tokens for {replaced}/{num_rows} rows; missing {missing}")
100
-
101
- if strict and missing > 0:
102
- logger.error("Strict mode: some UIDs are missing global DINO tokens")
103
-
104
- df = df.copy()
105
- df['dino_global'] = new_dino_column
106
- return df
107
-
108
- def process_one_parquet(self, parquet_path: str, overwrite: bool = False, strict: bool = False) -> Optional[str]:
109
- base_name = os.path.splitext(os.path.basename(parquet_path))[0]
110
- out_path = os.path.join(self.output_parquet_dir, f"{base_name}.parquet")
111
-
112
- if os.path.exists(out_path) and not overwrite:
113
- logger.info(f"Output exists, skipping: {out_path}")
114
- return out_path
115
-
116
- # Load the input parquet
117
- df = pd.read_parquet(parquet_path, engine='pyarrow')
118
-
119
- # Load corresponding global dino tar
120
- global_dino_tar = os.path.join(self.global_dino_dir, f"{base_name}.tar")
121
- logger.info(f"Loading global DINO tokens from: {global_dino_tar}")
122
- uid_to_tokens = self._load_global_dino_from_tar(global_dino_tar)
123
-
124
-
125
- if not uid_to_tokens:
126
- logger.warning(f"No tokens loaded from {global_dino_tar}; leaving original 'dino' values")
127
-
128
- # Replace column
129
- df = self._replace_dino_column(df, uid_to_tokens, strict=strict)
130
-
131
- # Persist
132
- df.to_parquet(
133
- out_path,
134
- index=False,
135
- engine='pyarrow',
136
- compression='snappy',
137
- row_group_size=1000,
138
- )
139
- logger.info(f"Wrote updated parquet with global DINO to: {out_path}")
140
- return out_path
141
-
142
- def process_all(self, overwrite: bool = False, strict: bool = False,
143
- start_from: int = 0) -> List[str]:
144
- inputs = self._list_input_parquets()
145
- if start_from > 0:
146
- inputs = inputs[start_from:]
147
-
148
- if not inputs:
149
- logger.error("No input parquet files found")
150
- return []
151
-
152
- logger.info(f"Found {len(inputs)} parquet files to update")
153
- outputs: List[str] = []
154
- for parquet_path in inputs:
155
- try:
156
- result = self.process_one_parquet(parquet_path, overwrite=overwrite, strict=strict)
157
- if result:
158
- outputs.append(result)
159
- except Exception as e:
160
- logger.error(f"Error processing {parquet_path}: {e}")
161
-
162
- logger.info(f"Successfully wrote {len(outputs)} updated parquet files")
163
- return outputs
164
-
165
-
166
- def main() -> None:
167
- import argparse
168
-
169
- parser = argparse.ArgumentParser(description='Replace DINO tokens in existing parquet files with dinov2_global tokens')
170
- parser.add_argument('--base_dir', type=str, default='./datasets/blip3o',
171
- help='Base directory (default: ./datasets/blip3o)')
172
- parser.add_argument('--input_parquet_dir', type=str, default=None,
173
- help='Input parquet directory; defaults to parquet_rgb_caption_depth_normal_det_seg_grounding_canny_dino')
174
- parser.add_argument('--global_dino_dir', type=str, default=None,
175
- help='Directory containing dinov2_global tars; defaults to base_dir/dinov2_global')
176
- parser.add_argument('--output_parquet_dir', type=str, default=None,
177
- help='Output parquet directory; defaults to parquet_*_dino_global')
178
- parser.add_argument('--overwrite', action='store_true', help='Overwrite existing output parquet if present')
179
- parser.add_argument('--strict', action='store_true',
180
- help='If set, missing UIDs in global DINO will set dino=None and log error')
181
- parser.add_argument('--start_from', type=int, default=0,
182
- help='Index to start processing from (for resuming)')
183
-
184
- args = parser.parse_args()
185
-
186
- replacer = GlobalDinoReplacer(
187
- base_dir=args.base_dir,
188
- input_parquet_dir=args.input_parquet_dir,
189
- global_dino_dir=args.global_dino_dir,
190
- output_parquet_dir=args.output_parquet_dir,
191
- )
192
-
193
- outputs = replacer.process_all(overwrite=args.overwrite, strict=args.strict, start_from=args.start_from)
194
- if outputs:
195
- logger.info("Outputs written:")
196
- for p in outputs:
197
- logger.info(f" - {p}")
198
- else:
199
- logger.error("No outputs were created")
200
-
201
-
202
- if __name__ == '__main__':
203
- main()
204
-
205
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/generate_parquet.py DELETED
@@ -1,229 +0,0 @@
1
- import os
2
- import tarfile
3
- import pandas as pd
4
- import numpy as np
5
- from PIL import Image
6
- import io
7
- import glob
8
- import json
9
- from tqdm import tqdm
10
- import logging
11
-
12
- # Set up logging
13
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
14
- logger = logging.getLogger(__name__)
15
-
16
- class TarProcessor:
17
- def __init__(self, base_dir):
18
- self.base_dir = base_dir
19
- self.rgb_caption_dir = os.path.join(base_dir, 'datasets')
20
- self.depth_dir = os.path.join(base_dir, 'datasets_depth')
21
- self.normal_dir = os.path.join(base_dir, 'datasets_normal')
22
- self.det_seg_dir = os.path.join(base_dir, 'datasets_seg_swinb')
23
- self.parquet_dir = os.path.join(base_dir, 'parquet_rgb_caption_depth_normal_det_seg')
24
-
25
- # Create parquet directory if it doesn't exist
26
- os.makedirs(self.parquet_dir, exist_ok=True)
27
-
28
- def get_tar_files(self):
29
- """Get all tar files from rgb_caption directory"""
30
- tar_files = glob.glob(os.path.join(self.rgb_caption_dir, '*.tar'))
31
- return sorted(tar_files)
32
-
33
- def extract_from_tar(self, tar_path, file_type):
34
- """
35
- Extract images and captions from tar file
36
-
37
- Args:
38
- tar_path: Path to tar file
39
- file_type: 'rgb_caption', 'depth', 'normal', 'det_seg'
40
- """
41
- data = {}
42
-
43
- try:
44
- with tarfile.open(tar_path, 'r') as tar:
45
- for member in tar.getmembers():
46
- if member.isfile():
47
- filename = member.name
48
- uid = os.path.splitext(filename)[0] # Remove extension
49
-
50
- # Extract file content
51
- f = tar.extractfile(member)
52
- if f is None:
53
- continue
54
-
55
- content = f.read()
56
-
57
- if file_type == 'rgb_caption':
58
- # Handle RGB + caption files
59
- if filename.endswith('.jpg') or filename.endswith('.jpeg') or filename.endswith('.png'):
60
- data[uid] = {'image': content, 'caption': None}
61
- elif filename.endswith('.txt'):
62
- # Find corresponding image
63
- base_name = os.path.splitext(filename)[0]
64
- if base_name in data:
65
- data[base_name]['caption'] = content.decode('utf-8').strip()
66
- else:
67
- data[base_name] = {'image': None, 'caption': content.decode('utf-8').strip()}
68
-
69
- elif file_type == 'depth':
70
- # Handle depth maps (PNG format)
71
- if filename.endswith('.png'):
72
- data[uid] = content
73
-
74
- elif file_type == 'normal':
75
- # Handle normal maps (RGB format)
76
- if filename.endswith('.jpg') or filename.endswith('.jpeg') or filename.endswith('.png'):
77
- data[uid] = content
78
- elif file_type == 'det_seg':
79
- # Handle detection maps (JSON format)
80
- if filename.endswith('.json'):
81
- # Decode bytes to string and parse JSON
82
- json_str = content.decode('utf-8')
83
- json_data = json.loads(json_str)
84
- data[uid] = json_data
85
- except Exception as e:
86
- logger.error(f"Error processing {tar_path}: {e}")
87
- return {}
88
-
89
- return data
90
-
91
- def process_tar_file(self, rgb_caption_tar):
92
- """Process a single tar file and create corresponding parquet"""
93
-
94
- # Extract base name (e.g., 'sa_000000' from 'sa_000000.tar')
95
- base_name = os.path.splitext(os.path.basename(rgb_caption_tar))[0]
96
-
97
- # Construct paths for corresponding depth and normal tar files
98
- depth_tar = os.path.join(self.depth_dir, f"{base_name}.tar")
99
- normal_tar = os.path.join(self.normal_dir, f"{base_name}.tar")
100
- det_seg_tar = os.path.join(self.det_seg_dir, f"{base_name}.tar")
101
-
102
- logger.info(f"Processing {base_name}")
103
-
104
- # Check if corresponding files exist
105
- if not os.path.exists(depth_tar):
106
- logger.warning(f"Depth tar file not found: {depth_tar}")
107
- return None
108
- if not os.path.exists(normal_tar):
109
- logger.warning(f"Normal tar file not found: {normal_tar}")
110
- return None
111
- if not os.path.exists(det_seg_tar):
112
- logger.warning(f"Det seg tar file not found: {det_seg_tar}")
113
- return None
114
-
115
- # Extract data from all three tar files
116
- logger.info(f"Extracting from rgb_caption: {rgb_caption_tar}")
117
- rgb_caption_data = self.extract_from_tar(rgb_caption_tar, 'rgb_caption')
118
-
119
- logger.info(f"Extracting from depth: {depth_tar}")
120
- depth_data = self.extract_from_tar(depth_tar, 'depth')
121
-
122
- logger.info(f"Extracting from normal: {normal_tar}")
123
- normal_data = self.extract_from_tar(normal_tar, 'normal')
124
-
125
- logger.info(f"Extracting from det_seg: {det_seg_tar}")
126
- det_seg_data = self.extract_from_tar(det_seg_tar, 'det_seg')
127
-
128
- # Find common UIDs
129
- logger.info(f"rgb_caption_data.keys() - det_seg_data.keys(): {len(set(rgb_caption_data.keys())) - len(set(det_seg_data.keys()))}")
130
- common_uids = set(rgb_caption_data.keys()) & set(depth_data.keys()) & set(normal_data.keys()) & set(det_seg_data.keys())
131
-
132
- if not common_uids:
133
- logger.warning(f"No common UIDs found for {base_name}")
134
- return None
135
-
136
- logger.info(f"Found {len(common_uids)} common UIDs")
137
-
138
- # Sort UIDs for consistent ordering
139
- sorted_uids = sorted(common_uids)
140
- logger.info(f"Sorted {len(sorted_uids)} UIDs")
141
-
142
- # Create DataFrame
143
- rows = []
144
- for uid in tqdm(sorted_uids, desc=f"Processing {base_name}"):
145
- rgb_info = rgb_caption_data[uid]
146
-
147
- # Skip if missing image or caption
148
- if rgb_info['image'] is None or rgb_info['caption'] is None:
149
- continue
150
-
151
- row = {
152
- 'uid': uid,
153
- 'rgb': rgb_info['image'],
154
- 'caption': rgb_info['caption'],
155
- 'depth': depth_data[uid],
156
- 'normal': normal_data[uid],
157
- 'det_seg': det_seg_data[uid]
158
- }
159
- rows.append(row)
160
-
161
- if not rows:
162
- logger.warning(f"No valid rows for {base_name}")
163
- return None
164
-
165
- # Create DataFrame
166
- df = pd.DataFrame(rows)
167
-
168
- # Debug information
169
- logger.info(f"DataFrame shape: {df.shape}")
170
- if not df.empty:
171
- logger.info(f"Sample UIDs: {df['uid'].head().tolist()}")
172
- logger.info(f"Data types: {df.dtypes.to_dict()}")
173
-
174
- # Save to parquet with better settings for dataset compatibility
175
- output_path = os.path.join(self.parquet_dir, f"{base_name}.parquet")
176
-
177
- # Use better parquet settings for dataset compatibility
178
- df.to_parquet(
179
- output_path,
180
- index=False,
181
- engine='pyarrow',
182
- compression='snappy',
183
- row_group_size=1000 # Smaller row group size for more groups
184
- )
185
- logger.info(f"Saved {len(df)} rows to {output_path}")
186
-
187
- return output_path
188
-
189
- def process_all_tars(self):
190
- """Process all tar files and create corresponding parquet files"""
191
- tar_files = self.get_tar_files()
192
-
193
- if not tar_files:
194
- logger.error("No tar files found in rgb_caption directory")
195
- return
196
-
197
- logger.info(f"Found {len(tar_files)} tar files to process")
198
-
199
- processed_files = []
200
- for tar_file in tar_files:
201
- try:
202
- output_path = self.process_tar_file(tar_file)
203
- if output_path:
204
- processed_files.append(output_path)
205
- except Exception as e:
206
- logger.error(f"Error processing {tar_file}: {e}")
207
-
208
- logger.info(f"Successfully processed {len(processed_files)} files")
209
- return processed_files
210
-
211
- def main():
212
- # Set the base directory
213
- base_dir = './datasets/blip3o'
214
-
215
- # Create processor
216
- processor = TarProcessor(base_dir)
217
-
218
- # Process all tar files
219
- processed_files = processor.process_all_tars()
220
-
221
- if processed_files:
222
- logger.info(f"Successfully created {len(processed_files)} parquet files:")
223
- for file in processed_files:
224
- logger.info(f" - {file}")
225
- else:
226
- logger.error("No parquet files were created")
227
-
228
- if __name__ == "__main__":
229
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/generate_parquet_clip448_imagebind.py DELETED
@@ -1,318 +0,0 @@
1
- import os
2
- import io
3
- import glob
4
- import tarfile
5
- import logging
6
- from typing import Dict, List, Optional, Tuple
7
-
8
- import numpy as np
9
- import pandas as pd
10
- from tqdm import tqdm
11
-
12
-
13
- # Set up logging
14
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- class MultiModalEnricher:
19
- """Read existing parquet files and add clip448 and imagebind columns from tar archives."""
20
-
21
- def __init__(self, base_dir: str,
22
- input_parquet_dir: Optional[str] = None,
23
- clip448_dir: Optional[str] = None,
24
- imagebind_dir: Optional[str] = None,
25
- output_parquet_dir: Optional[str] = None) -> None:
26
- self.base_dir = base_dir
27
-
28
- # Defaults
29
- self.input_parquet_dir = input_parquet_dir or os.path.join(
30
- base_dir, 'parquet_rgb_caption_depth_normal_det_seg_grounding_canny_dino_global'
31
- )
32
- self.clip448_dir = clip448_dir or os.path.join(base_dir, 'datasets_clip448')
33
- self.imagebind_dir = imagebind_dir or os.path.join(base_dir, 'datasets_imagebind')
34
- self.output_parquet_dir = output_parquet_dir or os.path.join(
35
- base_dir, 'parquet_rgb_caption_depth_normal_det_seg_grounding_canny_dino_global_clip448_imagebind'
36
- )
37
-
38
- os.makedirs(self.output_parquet_dir, exist_ok=True)
39
-
40
- # ------------------------------------------------------------------
41
- # Listing helpers
42
- # ------------------------------------------------------------------
43
-
44
- def _list_input_parquets(self) -> List[str]:
45
- files = glob.glob(os.path.join(self.input_parquet_dir, '*.parquet'))
46
- return sorted(files)
47
-
48
- # ------------------------------------------------------------------
49
- # Tar loaders
50
- # ------------------------------------------------------------------
51
-
52
- def _load_clip448_from_tar(self, tar_path: str) -> Dict[str, np.ndarray]:
53
- """Load uid -> flattened local tokens from a clip448 tar.
54
-
55
- Files inside: {uid}_tokens.npy (shape (28,28) int64)
56
- """
57
- results: Dict[str, np.ndarray] = {}
58
-
59
- if not os.path.exists(tar_path):
60
- logger.warning(f"CLIP448 tar not found: {tar_path}")
61
- return results
62
-
63
- try:
64
- with tarfile.open(tar_path, 'r') as tar:
65
- for member in tar.getmembers():
66
- if not member.isfile():
67
- continue
68
- name = member.name
69
- if not name.endswith('_tokens.npy'):
70
- continue
71
-
72
- file_obj = tar.extractfile(member)
73
- if file_obj is None:
74
- continue
75
-
76
- content = file_obj.read()
77
- npy = np.load(io.BytesIO(content))
78
-
79
- # Derive uid: strip .npy then _tokens
80
- uid = os.path.splitext(name)[0] # remove .npy
81
- if uid.endswith('_tokens'):
82
- uid = uid[:-len('_tokens')]
83
-
84
- results[uid] = npy.flatten()
85
- except Exception as e:
86
- logger.error(f"Failed to read CLIP448 tar {tar_path}: {e}")
87
-
88
- return results
89
-
90
- def _load_imagebind_from_tar(self, tar_path: str) -> Tuple[Dict[str, np.ndarray], Dict[str, np.ndarray]]:
91
- """Load uid -> flattened tokens from an imagebind tar.
92
-
93
- Files inside:
94
- {uid}_tokens.npy (local, shape (32,32) int64)
95
- {uid}_tokens_global.npy (global, shape (16,1,1) int64)
96
-
97
- Returns (local_map, global_map).
98
- """
99
- local_results: Dict[str, np.ndarray] = {}
100
- global_results: Dict[str, np.ndarray] = {}
101
-
102
- if not os.path.exists(tar_path):
103
- logger.warning(f"ImageBind tar not found: {tar_path}")
104
- return local_results, global_results
105
-
106
- try:
107
- with tarfile.open(tar_path, 'r') as tar:
108
- for member in tar.getmembers():
109
- if not member.isfile():
110
- continue
111
- name = member.name
112
- if not name.endswith('.npy'):
113
- continue
114
-
115
- file_obj = tar.extractfile(member)
116
- if file_obj is None:
117
- continue
118
-
119
- content = file_obj.read()
120
- npy = np.load(io.BytesIO(content))
121
-
122
- base = os.path.splitext(name)[0] # remove .npy
123
-
124
- if base.endswith('_tokens_global'):
125
- uid = base[:-len('_tokens_global')]
126
- global_results[uid] = npy.flatten()
127
- elif base.endswith('_tokens'):
128
- uid = base[:-len('_tokens')]
129
- local_results[uid] = npy.flatten()
130
- except Exception as e:
131
- logger.error(f"Failed to read ImageBind tar {tar_path}: {e}")
132
-
133
- return local_results, global_results
134
-
135
- # ------------------------------------------------------------------
136
- # Column addition
137
- # ------------------------------------------------------------------
138
-
139
- def _add_columns(self, df: pd.DataFrame,
140
- clip448_map: Dict[str, np.ndarray],
141
- ib_local_map: Dict[str, np.ndarray],
142
- ib_global_map: Dict[str, np.ndarray],
143
- strict: bool = False) -> pd.DataFrame:
144
- """Add clip448, imagebind, and imagebind_global columns to *df*."""
145
-
146
- if 'uid' not in df.columns:
147
- raise ValueError("Input parquet missing required 'uid' column")
148
-
149
- num_rows = len(df)
150
- stats = {'clip448': 0, 'imagebind': 0, 'imagebind_global': 0,
151
- 'clip448_miss': 0, 'imagebind_miss': 0, 'imagebind_global_miss': 0}
152
-
153
- clip448_col: List[object] = []
154
- ib_local_col: List[object] = []
155
- ib_global_col: List[object] = []
156
-
157
- for _, row in df.iterrows():
158
- uid = row['uid']
159
-
160
- # clip448
161
- tokens = clip448_map.get(uid)
162
- if tokens is None:
163
- stats['clip448_miss'] += 1
164
- clip448_col.append(row.get('clip448', None) if not strict else None)
165
- else:
166
- clip448_col.append(tokens)
167
- stats['clip448'] += 1
168
-
169
- # imagebind local
170
- tokens = ib_local_map.get(uid)
171
- if tokens is None:
172
- stats['imagebind_miss'] += 1
173
- ib_local_col.append(row.get('imagebind', None) if not strict else None)
174
- else:
175
- ib_local_col.append(tokens)
176
- stats['imagebind'] += 1
177
-
178
- # imagebind global
179
- tokens = ib_global_map.get(uid)
180
- if tokens is None:
181
- stats['imagebind_global_miss'] += 1
182
- ib_global_col.append(row.get('imagebind_global', None) if not strict else None)
183
- else:
184
- ib_global_col.append(tokens)
185
- stats['imagebind_global'] += 1
186
-
187
- logger.info(
188
- f"Rows: {num_rows} | "
189
- f"clip448: {stats['clip448']} ok / {stats['clip448_miss']} miss | "
190
- f"imagebind: {stats['imagebind']} ok / {stats['imagebind_miss']} miss | "
191
- f"imagebind_global: {stats['imagebind_global']} ok / {stats['imagebind_global_miss']} miss"
192
- )
193
-
194
- if strict and (stats['clip448_miss'] or stats['imagebind_miss'] or stats['imagebind_global_miss']):
195
- logger.error("Strict mode: some UIDs are missing tokens from one or more modalities")
196
-
197
- df = df.copy()
198
- df['clip448'] = clip448_col
199
- df['imagebind'] = ib_local_col
200
- df['imagebind_global'] = ib_global_col
201
- return df
202
-
203
- # ------------------------------------------------------------------
204
- # Processing
205
- # ------------------------------------------------------------------
206
-
207
- def process_one_parquet(self, parquet_path: str, overwrite: bool = False,
208
- strict: bool = False) -> Optional[str]:
209
- base_name = os.path.splitext(os.path.basename(parquet_path))[0]
210
- out_path = os.path.join(self.output_parquet_dir, f"{base_name}.parquet")
211
-
212
- if os.path.exists(out_path) and not overwrite:
213
- logger.info(f"Output exists, skipping: {out_path}")
214
- return out_path
215
-
216
- # Load existing parquet
217
- df = pd.read_parquet(parquet_path, engine='pyarrow')
218
-
219
- # Load clip448 tar
220
- clip448_tar = os.path.join(self.clip448_dir, f"{base_name}.tar")
221
- logger.info(f"Loading CLIP448 tokens from: {clip448_tar}")
222
- clip448_map = self._load_clip448_from_tar(clip448_tar)
223
-
224
- # Load imagebind tar
225
- imagebind_tar = os.path.join(self.imagebind_dir, f"{base_name}.tar")
226
- logger.info(f"Loading ImageBind tokens from: {imagebind_tar}")
227
- ib_local_map, ib_global_map = self._load_imagebind_from_tar(imagebind_tar)
228
-
229
- if not clip448_map:
230
- logger.warning(f"No CLIP448 tokens loaded from {clip448_tar}")
231
- if not ib_local_map:
232
- logger.warning(f"No ImageBind local tokens loaded from {imagebind_tar}")
233
- if not ib_global_map:
234
- logger.warning(f"No ImageBind global tokens loaded from {imagebind_tar}")
235
-
236
- # Add new columns
237
- df = self._add_columns(df, clip448_map, ib_local_map, ib_global_map, strict=strict)
238
-
239
- # Persist
240
- df.to_parquet(
241
- out_path,
242
- index=False,
243
- engine='pyarrow',
244
- compression='snappy',
245
- row_group_size=1000,
246
- )
247
- logger.info(f"Wrote enriched parquet to: {out_path}")
248
- return out_path
249
-
250
- def process_all(self, overwrite: bool = False, strict: bool = False,
251
- start_from: int = 0) -> List[str]:
252
- inputs = self._list_input_parquets()
253
- if start_from > 0:
254
- inputs = inputs[start_from:]
255
-
256
- if not inputs:
257
- logger.error("No input parquet files found")
258
- return []
259
-
260
- logger.info(f"Found {len(inputs)} parquet files to enrich")
261
- outputs: List[str] = []
262
- for parquet_path in tqdm(inputs, desc="Enriching parquets", unit="file"):
263
- try:
264
- result = self.process_one_parquet(parquet_path, overwrite=overwrite, strict=strict)
265
- if result:
266
- outputs.append(result)
267
- except Exception as e:
268
- logger.error(f"Error processing {parquet_path}: {e}")
269
-
270
- logger.info(f"Successfully wrote {len(outputs)} enriched parquet files")
271
- return outputs
272
-
273
-
274
- def main() -> None:
275
- import argparse
276
-
277
- parser = argparse.ArgumentParser(
278
- description='Add CLIP448 and ImageBind tokens to existing parquet files'
279
- )
280
- parser.add_argument('--base_dir', type=str, default='./datasets/blip3o',
281
- help='Base directory (default: ./datasets/blip3o)')
282
- parser.add_argument('--input_parquet_dir', type=str, default=None,
283
- help='Input parquet directory; defaults to parquet_*_dino_global')
284
- parser.add_argument('--clip448_dir', type=str, default=None,
285
- help='Directory containing clip448 tars; defaults to base_dir/datasets_clip448')
286
- parser.add_argument('--imagebind_dir', type=str, default=None,
287
- help='Directory containing imagebind tars; defaults to base_dir/datasets_imagebind')
288
- parser.add_argument('--output_parquet_dir', type=str, default=None,
289
- help='Output parquet directory; defaults to parquet_*_clip448_imagebind')
290
- parser.add_argument('--overwrite', action='store_true',
291
- help='Overwrite existing output parquet if present')
292
- parser.add_argument('--strict', action='store_true',
293
- help='If set, missing UIDs will set column=None and log error')
294
- parser.add_argument('--start_from', type=int, default=0,
295
- help='Index to start processing from (for resuming)')
296
-
297
- args = parser.parse_args()
298
-
299
- enricher = MultiModalEnricher(
300
- base_dir=args.base_dir,
301
- input_parquet_dir=args.input_parquet_dir,
302
- clip448_dir=args.clip448_dir,
303
- imagebind_dir=args.imagebind_dir,
304
- output_parquet_dir=args.output_parquet_dir,
305
- )
306
-
307
- outputs = enricher.process_all(overwrite=args.overwrite, strict=args.strict, start_from=args.start_from)
308
- if outputs:
309
- logger.info("Outputs written:")
310
- for p in outputs:
311
- logger.info(f" - {p}")
312
- else:
313
- logger.error("No outputs were created")
314
-
315
-
316
- if __name__ == '__main__':
317
- main()
318
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/generate_parquet_grounding.py DELETED
@@ -1,583 +0,0 @@
1
- import os
2
- import tarfile
3
- import pandas as pd
4
- import numpy as np
5
- from PIL import Image
6
- import io
7
- import glob
8
- import json
9
- from tqdm import tqdm
10
- import logging
11
- import pycocotools.mask as mask_util
12
- import matplotlib.pyplot as plt
13
-
14
- # Set up logging
15
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
16
- logger = logging.getLogger(__name__)
17
-
18
- class GroundingProcessor:
19
- def __init__(self, base_dir):
20
- self.base_dir = base_dir
21
- self.rgb_caption_dir = os.path.join(base_dir, 'datasets')
22
- self.depth_dir = os.path.join(base_dir, 'datasets_depth')
23
- self.normal_dir = os.path.join(base_dir, 'datasets_normal')
24
- self.det_seg_dir = os.path.join(base_dir, 'datasets_seg_swinb')
25
- self.grounding_dir = os.path.join(base_dir, 'datasets_grounding') # Pre-processed grounding tars
26
- self.parquet_dir = os.path.join(base_dir, 'parquet_rgb_caption_depth_normal_det_seg_grounding')
27
-
28
- # Create parquet directory if it doesn't exist
29
- os.makedirs(self.parquet_dir, exist_ok=True)
30
-
31
- def get_tar_files(self):
32
- """Get all tar files from rgb_caption directory"""
33
- tar_files = glob.glob(os.path.join(self.rgb_caption_dir, '*.tar'))
34
- return sorted(tar_files)
35
-
36
-
37
- def extract_from_tar(self, tar_path, file_type):
38
- """
39
- Extract data from tar file
40
-
41
- Args:
42
- tar_path: Path to tar file
43
- file_type: 'rgb_caption', 'depth', 'normal', 'det_seg', 'grounding'
44
- """
45
- data = {}
46
-
47
- try:
48
- with tarfile.open(tar_path, 'r') as tar:
49
- for member in tar.getmembers():
50
- if member.isfile():
51
- filename = member.name
52
- uid = os.path.splitext(filename)[0] # Remove extension
53
-
54
- # Extract file content
55
- f = tar.extractfile(member)
56
- if f is None:
57
- continue
58
-
59
- content = f.read()
60
-
61
- if file_type == 'rgb_caption':
62
- # Handle RGB + caption files
63
- if filename.endswith('.jpg') or filename.endswith('.jpeg') or filename.endswith('.png'):
64
- data[uid] = {'image': content, 'caption': None}
65
- elif filename.endswith('.txt'):
66
- # Find corresponding image
67
- base_name = os.path.splitext(filename)[0]
68
- if base_name in data:
69
- data[base_name]['caption'] = content.decode('utf-8').strip()
70
- else:
71
- data[base_name] = {'image': None, 'caption': content.decode('utf-8').strip()}
72
-
73
- elif file_type == 'depth':
74
- # Handle depth maps (PNG format)
75
- if filename.endswith('.png'):
76
- data[uid] = content
77
-
78
- elif file_type == 'normal':
79
- # Handle normal maps (RGB format)
80
- if filename.endswith('.jpg') or filename.endswith('.jpeg') or filename.endswith('.png'):
81
- data[uid] = content
82
-
83
- elif file_type == 'det_seg':
84
- # Handle detection maps (JSON format)
85
- if filename.endswith('.json'):
86
- # Decode bytes to string and parse JSON
87
- json_str = content.decode('utf-8')
88
- json_data = json.loads(json_str)
89
- data[uid] = json_data
90
-
91
- elif file_type == 'grounding':
92
- # Handle grounding JSON files
93
- if filename.endswith('.json'):
94
- # Decode bytes to string and parse JSON
95
- json_str = content.decode('utf-8')
96
- json_data = json.loads(json_str)
97
- data[uid] = json_data
98
- except Exception as e:
99
- logger.error(f"Error processing {tar_path}: {e}")
100
- return {}
101
-
102
- return data
103
-
104
-
105
- def extract_grounding_data(self, grounding_data):
106
- """Extract grounding information from JSON data"""
107
- if not grounding_data:
108
- return None
109
-
110
- # Get the first (and usually only) key which is the image name
111
- image_key = list(grounding_data.keys())[0]
112
- image_data = grounding_data[image_key]
113
-
114
- # Extract objects and their bounding boxes
115
- objects = image_data.get('objects', [])
116
- floating_objects = image_data.get('floating_objects', [])
117
-
118
- # Extract grounding relationships
119
- relationships = image_data.get('relationships', {})
120
- grounding_phrases = relationships.get('grounding', [])
121
-
122
- # Extract captions with grounding details
123
- short_captions = image_data.get('short_captions', [])
124
- dense_caption = image_data.get('dense_caption', {})
125
-
126
- return {
127
- 'objects': objects,
128
- 'floating_objects': floating_objects,
129
- 'grounding_phrases': grounding_phrases,
130
- 'short_captions': short_captions,
131
- 'dense_caption': dense_caption
132
- }
133
-
134
- def normalize_bbox(self, bbox, image_size):
135
- """Normalize bbox to 0-999 based on image dimensions"""
136
- if not bbox or len(bbox) != 4:
137
- return []
138
-
139
- # RLE format: size = [width, height]
140
- img_height, img_width = image_size
141
-
142
- x1, y1, x2, y2 = bbox
143
-
144
- # Normalize to 0-999 and clamp values
145
- norm_x1 = max(0, min(999, int((x1 / img_width) * 999)))
146
- norm_y1 = max(0, min(999, int((y1 / img_height) * 999)))
147
- norm_x2 = max(0, min(999, int((x2 / img_width) * 999)))
148
- norm_y2 = max(0, min(999, int((y2 / img_height) * 999)))
149
-
150
- return [norm_x1, norm_y1, norm_x2, norm_y2]
151
-
152
-
153
- def resize_and_crop_bbox(self, bbox, orig_size, target_size=512):
154
- """
155
- Resize and center-crop a bounding box using CLIP/BLIP-style transform.
156
-
157
- Args:
158
- bbox: list or tuple [x1, y1, x2, y2] in pixel coords.
159
- orig_size: [W, H] original image size.
160
- target_size: int, final square crop size (default=512).
161
-
162
- Returns:
163
- [x1', y1', x2', y2'] after resize+crop.
164
- Also returns (scale, left, top) for debugging.
165
- """
166
- if not bbox or len(bbox) != 4:
167
- return []
168
-
169
- H, W = orig_size
170
- x1, y1, x2, y2 = bbox
171
-
172
- # --- 1. Resize (keep aspect ratio, short side -> target_size) ---
173
- scale = target_size / min(W, H)
174
- new_W, new_H = W * scale, H * scale
175
-
176
- # --- 2. Center crop to target_sizeΓ—target_size ---
177
- left = max(0, (new_W - target_size) / 2)
178
- top = max(0, (new_H - target_size) / 2)
179
-
180
- # --- 3. Apply transform to bbox ---
181
- x1_new = x1 * scale - left
182
- y1_new = y1 * scale - top
183
- x2_new = x2 * scale - left
184
- y2_new = y2 * scale - top
185
-
186
- # Clip to [0, target_size]
187
- x1_new, y1_new, x2_new, y2_new = np.clip(
188
- [x1_new, y1_new, x2_new, y2_new],
189
- 0, target_size
190
- )
191
-
192
- return [x1_new, y1_new, x2_new, y2_new]
193
-
194
-
195
- def visualize_rle_mask(self, rle_data, save_path=None):
196
- """Visualize RLE mask from segmentation data"""
197
- try:
198
- # Decode RLE to binary mask
199
- mask = mask_util.decode(rle_data)
200
-
201
- # Create visualization
202
- fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6))
203
-
204
- # Show mask
205
- ax1.imshow(mask, cmap='gray')
206
- ax1.set_title('RLE Mask')
207
- ax1.axis('off')
208
-
209
- # Show mask with bbox overlay
210
- ax2.imshow(mask, cmap='gray', alpha=0.7)
211
-
212
- # Add bbox if available
213
- if 'bbox' in rle_data:
214
- bbox = rle_data['bbox']
215
- x1, y1, x2, y2 = bbox
216
- width = x2 - x1
217
- height = y2 - y1
218
- rect = plt.Rectangle((x1, y1), width, height,
219
- fill=False, edgecolor='red', linewidth=2)
220
- ax2.add_patch(rect)
221
- ax2.set_title('Mask + Bbox')
222
- else:
223
- ax2.set_title('Mask Only')
224
- ax2.axis('off')
225
-
226
- plt.tight_layout()
227
-
228
- if save_path:
229
- plt.savefig(save_path, dpi=150, bbox_inches='tight')
230
- logger.info(f"Mask visualization saved to {save_path}")
231
- else:
232
- plt.show()
233
-
234
- plt.close()
235
-
236
- except Exception as e:
237
- logger.error(f"Error visualizing RLE mask: {e}")
238
-
239
- def visualize_object_mask(self, grounding_data, uid, object_id=0, save_path=None):
240
- """Visualize a specific object's mask from grounding data"""
241
- try:
242
- if not grounding_data or 'objects' not in grounding_data:
243
- logger.warning(f"No objects found for {uid}")
244
- return
245
-
246
- objects = grounding_data['objects']
247
- if object_id >= len(objects):
248
- logger.warning(f"Object ID {object_id} not found for {uid}")
249
- return
250
-
251
- obj = objects[object_id]
252
- if 'segmentation' not in obj:
253
- logger.warning(f"No segmentation found for object {object_id} in {uid}")
254
- return
255
-
256
- segmentation = obj['segmentation']
257
- bbox = obj.get('bbox', [])
258
-
259
- # Create RLE data for visualization
260
- rle_data = {
261
- 'size': segmentation['size'],
262
- 'counts': segmentation['counts'],
263
- 'bbox': bbox
264
- }
265
-
266
- # Visualize
267
- if save_path:
268
- save_path_with_id = save_path.replace('.png', f'_obj_{object_id}.png')
269
- else:
270
- save_path_with_id = None
271
-
272
- self.visualize_rle_mask(rle_data, save_path_with_id)
273
-
274
- except Exception as e:
275
- logger.error(f"Error visualizing object mask for {uid}: {e}")
276
-
277
- def create_grounding_samples(self, grounding_data, uid):
278
- """Create training samples from grounding data"""
279
- if not grounding_data:
280
- return []
281
-
282
- samples = []
283
-
284
- # Get image size from first object's segmentation
285
- image_size = None
286
- if grounding_data['objects'] and 'segmentation' in grounding_data['objects'][0]:
287
- image_size = grounding_data['objects'][0]['segmentation']['size']
288
- assert image_size is not None
289
- else:
290
- logger.warning(f"No segmentation data found for {uid}, skipping bbox processing")
291
- return []
292
-
293
- # Process grounding phrases
294
- for phrase_info in grounding_data['grounding_phrases']:
295
- phrase = phrase_info['phrase']
296
- object_ids = phrase_info['object_ids']
297
-
298
- # Find corresponding objects and their bboxes
299
- for obj_id in object_ids:
300
- for obj in grounding_data['objects']:
301
- if obj.get('id') == obj_id:
302
- bbox = obj.get('bbox', [])
303
-
304
- # Process bbox if valid
305
- if bbox and len(bbox) == 4:
306
- bbox = self.resize_and_crop_bbox(bbox, image_size, 512)
307
- bbox = self.normalize_bbox(bbox, [512, 512])
308
- else:
309
- bbox = []
310
-
311
- sample = {
312
- 'uid': uid,
313
- 'phrase': phrase,
314
- 'object_id': obj_id,
315
- 'bbox': bbox,
316
- 'labels': obj.get('labels', []),
317
- 'attributes': obj.get('attributes', []),
318
- 'sample_type': 'grounding_phrase',
319
- 'segmentation': obj.get('segmentation', []),
320
- }
321
- samples.append(sample)
322
- break
323
-
324
- # Process short captions with details
325
- for caption_info in grounding_data['short_captions']:
326
- caption = caption_info['caption']
327
- for detail in caption_info.get('details', []):
328
- phrase = detail['phrase']
329
- object_id = detail.get('id')
330
- bbox = detail.get('bbox', [])
331
-
332
- # Process bbox if valid
333
- if bbox and len(bbox) == 4:
334
- bbox = self.resize_and_crop_bbox(bbox, image_size, 512)
335
- bbox = self.normalize_bbox(bbox, [512, 512])
336
- else:
337
- bbox = []
338
-
339
- tokens_positive = detail.get('tokens_positive', [])
340
-
341
- sample = {
342
- 'uid': uid,
343
- 'phrase': phrase,
344
- 'caption_id': object_id,
345
- 'bbox': bbox,
346
- 'tokens_positive': tokens_positive,
347
- 'full_caption': caption,
348
- 'sample_type': 'short_caption'
349
- }
350
- samples.append(sample)
351
-
352
- # # Process dense caption
353
- # if grounding_data['dense_caption']:
354
- # dense_caption = grounding_data['dense_caption']
355
- # caption = dense_caption.get('caption', '')
356
- # for detail in dense_caption.get('details', []):
357
- # phrase = detail['phrase']
358
- # object_ids = detail.get('ids', [])
359
- # bboxes = detail.get('bbox', [])
360
- # tokens_positive = detail.get('tokens_positive', [])
361
-
362
- # sample = {
363
- # 'uid': uid,
364
- # 'phrase': phrase,
365
- # 'object_ids': object_ids,
366
- # 'bboxes': bboxes,
367
- # 'tokens_positive': tokens_positive,
368
- # 'full_caption': caption,
369
- # 'sample_type': 'dense_caption'
370
- # }
371
- # samples.append(sample)
372
-
373
- return samples
374
-
375
- def process_tar_file(self, rgb_caption_tar):
376
- """Process a single tar file and create corresponding parquet"""
377
-
378
- # Extract base name (e.g., 'sa_000000' from 'sa_000000.tar')
379
- base_name = os.path.splitext(os.path.basename(rgb_caption_tar))[0]
380
-
381
- # Construct paths for corresponding depth, normal, det_seg, grounding tar files
382
- depth_tar = os.path.join(self.depth_dir, f"{base_name}.tar")
383
- normal_tar = os.path.join(self.normal_dir, f"{base_name}.tar")
384
- det_seg_tar = os.path.join(self.det_seg_dir, f"{base_name}.tar")
385
- grounding_tar = os.path.join(self.grounding_dir, f"{base_name}.tar")
386
-
387
- logger.info(f"Processing {base_name}")
388
-
389
- # Check if corresponding files exist
390
- if not os.path.exists(depth_tar):
391
- logger.warning(f"Depth tar file not found: {depth_tar}")
392
- return None
393
- if not os.path.exists(normal_tar):
394
- logger.warning(f"Normal tar file not found: {normal_tar}")
395
- return None
396
- if not os.path.exists(det_seg_tar):
397
- logger.warning(f"Det seg tar file not found: {det_seg_tar}")
398
- return None
399
- # Initialize grounding_data
400
- grounding_data = {}
401
-
402
- if not os.path.exists(grounding_tar):
403
- logger.warning(f"Grounding tar file not found: {grounding_tar} - will process without grounding data")
404
- else:
405
- logger.info(f"Extracting from grounding: {grounding_tar}")
406
- grounding_data = self.extract_from_tar(grounding_tar, 'grounding')
407
-
408
- # Extract data from all tar files
409
- logger.info(f"Extracting from rgb_caption: {rgb_caption_tar}")
410
- rgb_caption_data = self.extract_from_tar(rgb_caption_tar, 'rgb_caption')
411
-
412
- logger.info(f"Extracting from depth: {depth_tar}")
413
- depth_data = self.extract_from_tar(depth_tar, 'depth')
414
-
415
- logger.info(f"Extracting from normal: {normal_tar}")
416
- normal_data = self.extract_from_tar(normal_tar, 'normal')
417
-
418
- logger.info(f"Extracting from det_seg: {det_seg_tar}")
419
- det_seg_data = self.extract_from_tar(det_seg_tar, 'det_seg')
420
-
421
- # Find common UIDs (grounding is optional)
422
- common_uids = set(rgb_caption_data.keys()) & set(depth_data.keys()) & set(normal_data.keys()) & set(det_seg_data.keys())
423
-
424
- if not common_uids:
425
- logger.warning(f"No common UIDs found for {base_name}")
426
- return None
427
-
428
- logger.info(f"Found {len(common_uids)} common UIDs")
429
-
430
- # Sort UIDs for consistent ordering
431
- sorted_uids = sorted(common_uids)
432
-
433
- # Create samples for each UID
434
- all_samples = []
435
-
436
- for uid in sorted_uids:
437
- rgb_info = rgb_caption_data[uid]
438
-
439
- # Skip if missing image or caption
440
- if rgb_info['image'] is None or rgb_info['caption'] is None:
441
- continue
442
-
443
- # Create base sample with all modalities
444
- sample = {
445
- 'uid': uid,
446
- 'rgb': rgb_caption_data[uid]['image'],
447
- 'caption': rgb_caption_data[uid]['caption'],
448
- 'depth': depth_data[uid],
449
- 'normal': normal_data[uid],
450
- 'det_seg': det_seg_data[uid]
451
- }
452
-
453
- # Add grounding data if available
454
- if uid in grounding_data:
455
- extracted_grounding = self.extract_grounding_data(grounding_data[uid])
456
- if extracted_grounding:
457
- raw_image = Image.open(io.BytesIO(rgb_caption_data[uid]['image']))
458
- raw_image_size = raw_image.size
459
- assert raw_image_size == (512, 512), f"Raw image size is {raw_image_size} but expected (512, 512) for {uid}"
460
- grounding_samples = self.create_grounding_samples(extracted_grounding, uid)
461
- if grounding_samples:
462
- sample['grounding'] = grounding_samples
463
-
464
- # visualize_segmentation = sample['grounding'][1]['segmentation']
465
- # self.visualize_rle_mask(visualize_segmentation,save_path=f"visualize_{uid}_1.png")
466
- all_samples.append(sample)
467
-
468
- if not all_samples:
469
- logger.warning(f"No samples found for {base_name}")
470
- return None
471
-
472
- # Create DataFrame
473
- df = pd.DataFrame(all_samples)
474
-
475
- # Debug information
476
- logger.info(f"DataFrame shape: {df.shape}")
477
- if not df.empty:
478
- logger.info(f"Sample UIDs: {df['uid'].head().tolist()}")
479
- # Count grounding samples
480
- grounding_counts = []
481
- for _, row in df.iterrows():
482
- if 'grounding' in row and row['grounding'] is not None and isinstance(row['grounding'], list):
483
- grounding_counts.append(len(row['grounding']))
484
- else:
485
- grounding_counts.append(0)
486
- if grounding_counts:
487
- logger.info(f"Grounding samples per row: {np.mean(grounding_counts):.2f} avg, {max(grounding_counts)} max")
488
- else:
489
- logger.info("No grounding samples found")
490
-
491
- # Save to parquet
492
- output_path = os.path.join(self.parquet_dir, f"{base_name}.parquet")
493
-
494
- df.to_parquet(
495
- output_path,
496
- index=False,
497
- engine='pyarrow',
498
- compression='snappy',
499
- row_group_size=1000
500
- )
501
- logger.info(f"Saved {len(df)} grounding samples to {output_path}")
502
-
503
- return output_path
504
-
505
- def process_all_tars(self):
506
- """Process all tar files and create corresponding parquet files"""
507
- tar_files = self.get_tar_files()
508
-
509
- if not tar_files:
510
- logger.error("No tar files found in rgb_caption directory")
511
- return
512
-
513
- logger.info(f"Found {len(tar_files)} tar files to process")
514
-
515
- processed_files = []
516
- for tar_file in tar_files:
517
- try:
518
- output_path = self.process_tar_file(tar_file)
519
- if output_path:
520
- processed_files.append(output_path)
521
- except Exception as e:
522
- logger.error(f"Error processing {tar_file}: {e}")
523
-
524
- logger.info(f"Successfully processed {len(processed_files)} files")
525
- return processed_files
526
-
527
- def main():
528
- import argparse
529
-
530
- parser = argparse.ArgumentParser(description='Generate parquet files with grounding data')
531
- parser.add_argument('--base_dir', type=str, default='./datasets/blip3o',
532
- help='Base directory (default: ./datasets/blip3o)')
533
- parser.add_argument('--visualize_mask', action='store_true',
534
- help='Visualize RLE masks for debugging')
535
- parser.add_argument('--visualize_tar', type=str, default=None,
536
- help='Specific tar file to visualize (e.g., sa_000001.tar)')
537
- parser.add_argument('--visualize_uid', type=str, default=None,
538
- help='Specific UID to visualize (e.g., sa_1)')
539
- parser.add_argument('--visualize_object_id', type=int, default=0,
540
- help='Object ID to visualize (default: 0)')
541
-
542
- args = parser.parse_args()
543
-
544
- # Set the base directory
545
- base_dir = args.base_dir
546
-
547
- # Create processor
548
- processor = GroundingProcessor(base_dir)
549
-
550
- # Visualize mask if requested
551
- if args.visualize_mask and args.visualize_tar:
552
- logger.info(f"Visualizing mask for {args.visualize_tar}, object {args.visualize_object_id}")
553
-
554
- # Find the grounding tar file for this UID
555
- grounding_tar = os.path.join(processor.grounding_dir, f"{args.visualize_tar}.tar")
556
- if os.path.exists(grounding_tar):
557
- grounding_data = processor.extract_from_tar(grounding_tar, 'grounding')
558
- if args.visualize_uid in grounding_data:
559
- extracted_grounding = processor.extract_grounding_data(grounding_data[args.visualize_uid])
560
- if extracted_grounding:
561
- save_path = f"mask_visualization_{args.visualize_uid}.png"
562
- processor.visualize_object_mask(extracted_grounding, args.visualize_uid,
563
- args.visualize_object_id, save_path)
564
- else:
565
- logger.error(f"No grounding data found for {args.visualize_uid}")
566
- else:
567
- logger.error(f"UID {args.visualize_uid} not found in grounding tar")
568
- else:
569
- logger.error(f"Grounding tar not found: {grounding_tar}")
570
- return
571
-
572
- # Process all tar files
573
- processed_files = processor.process_all_tars()
574
-
575
- if processed_files:
576
- logger.info(f"Successfully created {len(processed_files)} grounding parquet files:")
577
- for file in processed_files:
578
- logger.info(f" - {file}")
579
- else:
580
- logger.error("No grounding parquet files were created")
581
-
582
- if __name__ == "__main__":
583
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/generate_parquet_grounding_canny_dino.py DELETED
@@ -1,627 +0,0 @@
1
- import os
2
- import tarfile
3
- import pandas as pd
4
- import numpy as np
5
- from PIL import Image
6
- import io
7
- import glob
8
- import json
9
- from tqdm import tqdm
10
- import logging
11
- import pycocotools.mask as mask_util
12
- import matplotlib.pyplot as plt
13
-
14
- # Set up logging
15
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
16
- logger = logging.getLogger(__name__)
17
-
18
- class GroundingProcessor:
19
- def __init__(self, base_dir):
20
- self.base_dir = base_dir
21
- self.rgb_caption_dir = os.path.join(base_dir, 'datasets')
22
- self.depth_dir = os.path.join(base_dir, 'datasets_depth')
23
- self.normal_dir = os.path.join(base_dir, 'datasets_normal')
24
- self.det_seg_dir = os.path.join(base_dir, 'datasets_seg_swinb')
25
- self.grounding_dir = os.path.join(base_dir, 'datasets_grounding') # Pre-processed grounding tars
26
- self.canny_dir = os.path.join(base_dir, 'datasets_cannyedge')
27
- self.dino_dir = os.path.join(base_dir, 'dinov2')
28
- self.parquet_dir = os.path.join(base_dir, 'parquet_rgb_caption_depth_normal_det_seg_grounding_canny_dino')
29
-
30
- # Create parquet directory if it doesn't exist
31
- os.makedirs(self.parquet_dir, exist_ok=True)
32
-
33
- def get_tar_files(self):
34
- """Get all tar files from rgb_caption directory"""
35
- tar_files = glob.glob(os.path.join(self.rgb_caption_dir, '*.tar'))
36
- return sorted(tar_files)
37
-
38
-
39
- def extract_from_tar(self, tar_path, file_type):
40
- """
41
- Extract data from tar file
42
-
43
- Args:
44
- tar_path: Path to tar file
45
- file_type: 'rgb_caption', 'depth', 'normal', 'det_seg', 'grounding', 'canny', 'dino'
46
- """
47
- data = {}
48
-
49
- try:
50
- with tarfile.open(tar_path, 'r') as tar:
51
- for member in tar.getmembers():
52
- if member.isfile():
53
- filename = member.name
54
- uid = os.path.splitext(filename)[0] # Remove extension
55
-
56
- # Extract file content
57
- f = tar.extractfile(member)
58
- if f is None:
59
- continue
60
-
61
- content = f.read()
62
-
63
- if file_type == 'rgb_caption':
64
- # Handle RGB + caption files
65
- if filename.endswith('.jpg') or filename.endswith('.jpeg') or filename.endswith('.png'):
66
- data[uid] = {'image': content, 'caption': None}
67
- elif filename.endswith('.txt'):
68
- # Find corresponding image
69
- base_name = os.path.splitext(filename)[0]
70
- if base_name in data:
71
- data[base_name]['caption'] = content.decode('utf-8').strip()
72
- else:
73
- data[base_name] = {'image': None, 'caption': content.decode('utf-8').strip()}
74
-
75
- elif file_type == 'depth':
76
- # Handle depth maps (PNG format)
77
- if filename.endswith('.png'):
78
- data[uid] = content
79
-
80
- elif file_type == 'normal':
81
- # Handle normal maps (RGB format)
82
- if filename.endswith('.jpg') or filename.endswith('.jpeg') or filename.endswith('.png'):
83
- data[uid] = content
84
-
85
- elif file_type == 'det_seg':
86
- # Handle detection maps (JSON format)
87
- if filename.endswith('.json'):
88
- # Decode bytes to string and parse JSON
89
- json_str = content.decode('utf-8')
90
- json_data = json.loads(json_str)
91
- data[uid] = json_data
92
-
93
- elif file_type == 'grounding':
94
- # Handle grounding JSON files
95
- if filename.endswith('.json'):
96
- # Decode bytes to string and parse JSON
97
- json_str = content.decode('utf-8')
98
- json_data = json.loads(json_str)
99
- data[uid] = json_data
100
-
101
- elif file_type == 'canny':
102
- # Handle Canny maps (PNG format, similar to depth)
103
- if filename.endswith('.png'):
104
- data[uid] = content
105
-
106
- elif file_type == 'dino':
107
- # Handle DINOv2 features (NPY format)
108
- if filename.endswith('_tokens.npy'):
109
- # Load NPY data from bytes
110
- npy_data = np.load(io.BytesIO(content))
111
- uid = uid[:-len('_tokens')]
112
- # Flatten the array for parquet storage
113
- data[uid] = npy_data.flatten()
114
- except Exception as e:
115
- logger.error(f"Error processing {tar_path}: {e}")
116
- return {}
117
-
118
- return data
119
-
120
- def extract_grounding_data(self, grounding_data):
121
- """Extract grounding information from JSON data"""
122
- if not grounding_data:
123
- return None
124
-
125
- # Get the first (and usually only) key which is the image name
126
- image_key = list(grounding_data.keys())[0]
127
- image_data = grounding_data[image_key]
128
-
129
- # Extract objects and their bounding boxes
130
- objects = image_data.get('objects', [])
131
- floating_objects = image_data.get('floating_objects', [])
132
-
133
- # Extract grounding relationships
134
- relationships = image_data.get('relationships', {})
135
- grounding_phrases = relationships.get('grounding', [])
136
-
137
- # Extract captions with grounding details
138
- short_captions = image_data.get('short_captions', [])
139
- dense_caption = image_data.get('dense_caption', {})
140
-
141
- return {
142
- 'objects': objects,
143
- 'floating_objects': floating_objects,
144
- 'grounding_phrases': grounding_phrases,
145
- 'short_captions': short_captions,
146
- 'dense_caption': dense_caption
147
- }
148
-
149
- def normalize_bbox(self, bbox, image_size):
150
- """Normalize bbox to 0-999 based on image dimensions"""
151
- if not bbox or len(bbox) != 4:
152
- return []
153
-
154
- # RLE format: size = [width, height]
155
- img_height, img_width = image_size
156
-
157
- x1, y1, x2, y2 = bbox
158
-
159
- # Normalize to 0-999 and clamp values
160
- norm_x1 = max(0, min(999, int((x1 / img_width) * 999)))
161
- norm_y1 = max(0, min(999, int((y1 / img_height) * 999)))
162
- norm_x2 = max(0, min(999, int((x2 / img_width) * 999)))
163
- norm_y2 = max(0, min(999, int((y2 / img_height) * 999)))
164
-
165
- return [norm_x1, norm_y1, norm_x2, norm_y2]
166
-
167
-
168
- def resize_and_crop_bbox(self, bbox, orig_size, target_size=512):
169
- """
170
- Resize and center-crop a bounding box using CLIP/BLIP-style transform.
171
-
172
- Args:
173
- bbox: list or tuple [x1, y1, x2, y2] in pixel coords.
174
- orig_size: [W, H] original image size.
175
- target_size: int, final square crop size (default=512).
176
-
177
- Returns:
178
- [x1', y1', x2', y2'] after resize+crop.
179
- Also returns (scale, left, top) for debugging.
180
- """
181
- if not bbox or len(bbox) != 4:
182
- return []
183
-
184
- H, W = orig_size
185
- x1, y1, x2, y2 = bbox
186
-
187
- # --- 1. Resize (keep aspect ratio, short side -> target_size) ---
188
- scale = target_size / min(W, H)
189
- new_W, new_H = W * scale, H * scale
190
-
191
- # --- 2. Center crop to target_sizeΓ—target_size ---
192
- left = max(0, (new_W - target_size) / 2)
193
- top = max(0, (new_H - target_size) / 2)
194
-
195
- # --- 3. Apply transform to bbox ---
196
- x1_new = x1 * scale - left
197
- y1_new = y1 * scale - top
198
- x2_new = x2 * scale - left
199
- y2_new = y2 * scale - top
200
-
201
- # Clip to [0, target_size]
202
- x1_new, y1_new, x2_new, y2_new = np.clip(
203
- [x1_new, y1_new, x2_new, y2_new],
204
- 0, target_size
205
- )
206
-
207
- return [x1_new, y1_new, x2_new, y2_new]
208
-
209
-
210
- def visualize_rle_mask(self, rle_data, save_path=None):
211
- """Visualize RLE mask from segmentation data"""
212
- try:
213
- # Decode RLE to binary mask
214
- mask = mask_util.decode(rle_data)
215
-
216
- # Create visualization
217
- fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6))
218
-
219
- # Show mask
220
- ax1.imshow(mask, cmap='gray')
221
- ax1.set_title('RLE Mask')
222
- ax1.axis('off')
223
-
224
- # Show mask with bbox overlay
225
- ax2.imshow(mask, cmap='gray', alpha=0.7)
226
-
227
- # Add bbox if available
228
- if 'bbox' in rle_data:
229
- bbox = rle_data['bbox']
230
- x1, y1, x2, y2 = bbox
231
- width = x2 - x1
232
- height = y2 - y1
233
- rect = plt.Rectangle((x1, y1), width, height,
234
- fill=False, edgecolor='red', linewidth=2)
235
- ax2.add_patch(rect)
236
- ax2.set_title('Mask + Bbox')
237
- else:
238
- ax2.set_title('Mask Only')
239
- ax2.axis('off')
240
-
241
- plt.tight_layout()
242
-
243
- if save_path:
244
- plt.savefig(save_path, dpi=150, bbox_inches='tight')
245
- logger.info(f"Mask visualization saved to {save_path}")
246
- else:
247
- plt.show()
248
-
249
- plt.close()
250
-
251
- except Exception as e:
252
- logger.error(f"Error visualizing RLE mask: {e}")
253
-
254
- def visualize_object_mask(self, grounding_data, uid, object_id=0, save_path=None):
255
- """Visualize a specific object's mask from grounding data"""
256
- try:
257
- if not grounding_data or 'objects' not in grounding_data:
258
- logger.warning(f"No objects found for {uid}")
259
- return
260
-
261
- objects = grounding_data['objects']
262
- if object_id >= len(objects):
263
- logger.warning(f"Object ID {object_id} not found for {uid}")
264
- return
265
-
266
- obj = objects[object_id]
267
- if 'segmentation' not in obj:
268
- logger.warning(f"No segmentation found for object {object_id} in {uid}")
269
- return
270
-
271
- segmentation = obj['segmentation']
272
- bbox = obj.get('bbox', [])
273
-
274
- # Create RLE data for visualization
275
- rle_data = {
276
- 'size': segmentation['size'],
277
- 'counts': segmentation['counts'],
278
- 'bbox': bbox
279
- }
280
-
281
- # Visualize
282
- if save_path:
283
- save_path_with_id = save_path.replace('.png', f'_obj_{object_id}.png')
284
- else:
285
- save_path_with_id = None
286
-
287
- self.visualize_rle_mask(rle_data, save_path_with_id)
288
-
289
- except Exception as e:
290
- logger.error(f"Error visualizing object mask for {uid}: {e}")
291
-
292
- def create_grounding_samples(self, grounding_data, uid):
293
- """Create training samples from grounding data"""
294
- if not grounding_data:
295
- return []
296
-
297
- samples = []
298
-
299
- # Get image size from first object's segmentation
300
- image_size = None
301
- if grounding_data['objects'] and 'segmentation' in grounding_data['objects'][0]:
302
- image_size = grounding_data['objects'][0]['segmentation']['size']
303
- assert image_size is not None
304
- else:
305
- logger.warning(f"No segmentation data found for {uid}, skipping bbox processing")
306
- return []
307
-
308
- # Process grounding phrases
309
- for phrase_info in grounding_data['grounding_phrases']:
310
- phrase = phrase_info['phrase']
311
- object_ids = phrase_info['object_ids']
312
-
313
- # Find corresponding objects and their bboxes
314
- for obj_id in object_ids:
315
- for obj in grounding_data['objects']:
316
- if obj.get('id') == obj_id:
317
- bbox = obj.get('bbox', [])
318
-
319
- # Process bbox if valid
320
- if bbox and len(bbox) == 4:
321
- bbox = self.resize_and_crop_bbox(bbox, image_size, 512)
322
- bbox = self.normalize_bbox(bbox, [512, 512])
323
- else:
324
- bbox = []
325
-
326
- sample = {
327
- 'uid': uid,
328
- 'phrase': phrase,
329
- 'object_id': obj_id,
330
- 'bbox': bbox,
331
- 'labels': obj.get('labels', []),
332
- 'attributes': obj.get('attributes', []),
333
- 'sample_type': 'grounding_phrase',
334
- 'segmentation': obj.get('segmentation', []),
335
- }
336
- samples.append(sample)
337
- break
338
-
339
- # Process short captions with details
340
- for caption_info in grounding_data['short_captions']:
341
- caption = caption_info['caption']
342
- for detail in caption_info.get('details', []):
343
- phrase = detail['phrase']
344
- object_id = detail.get('id')
345
- bbox = detail.get('bbox', [])
346
-
347
- # Process bbox if valid
348
- if bbox and len(bbox) == 4:
349
- bbox = self.resize_and_crop_bbox(bbox, image_size, 512)
350
- bbox = self.normalize_bbox(bbox, [512, 512])
351
- else:
352
- bbox = []
353
-
354
- tokens_positive = detail.get('tokens_positive', [])
355
-
356
- sample = {
357
- 'uid': uid,
358
- 'phrase': phrase,
359
- 'caption_id': object_id,
360
- 'bbox': bbox,
361
- 'tokens_positive': tokens_positive,
362
- 'full_caption': caption,
363
- 'sample_type': 'short_caption'
364
- }
365
- samples.append(sample)
366
-
367
- # # Process dense caption
368
- # if grounding_data['dense_caption']:
369
- # dense_caption = grounding_data['dense_caption']
370
- # caption = dense_caption.get('caption', '')
371
- # for detail in dense_caption.get('details', []):
372
- # phrase = detail['phrase']
373
- # object_ids = detail.get('ids', [])
374
- # bboxes = detail.get('bbox', [])
375
- # tokens_positive = detail.get('tokens_positive', [])
376
-
377
- # sample = {
378
- # 'uid': uid,
379
- # 'phrase': phrase,
380
- # 'object_ids': object_ids,
381
- # 'bboxes': bboxes,
382
- # 'tokens_positive': tokens_positive,
383
- # 'full_caption': caption,
384
- # 'sample_type': 'dense_caption'
385
- # }
386
- # samples.append(sample)
387
-
388
- return samples
389
-
390
- def process_tar_file(self, rgb_caption_tar):
391
- """Process a single tar file and create corresponding parquet"""
392
-
393
- # Extract base name (e.g., 'sa_000000' from 'sa_000000.tar')
394
- base_name = os.path.splitext(os.path.basename(rgb_caption_tar))[0]
395
-
396
- # Construct paths for corresponding depth, normal, det_seg, grounding, canny, dino tar files
397
- depth_tar = os.path.join(self.depth_dir, f"{base_name}.tar")
398
- normal_tar = os.path.join(self.normal_dir, f"{base_name}.tar")
399
- det_seg_tar = os.path.join(self.det_seg_dir, f"{base_name}.tar")
400
- grounding_tar = os.path.join(self.grounding_dir, f"{base_name}.tar")
401
- canny_tar = os.path.join(self.canny_dir, f"{base_name}.tar")
402
- dino_tar = os.path.join(self.dino_dir, f"{base_name}.tar")
403
-
404
- logger.info(f"Processing {base_name}")
405
-
406
- # Check if corresponding files exist
407
- if not os.path.exists(depth_tar):
408
- logger.warning(f"Depth tar file not found: {depth_tar}")
409
- return None
410
- if not os.path.exists(normal_tar):
411
- logger.warning(f"Normal tar file not found: {normal_tar}")
412
- return None
413
- if not os.path.exists(det_seg_tar):
414
- logger.warning(f"Det seg tar file not found: {det_seg_tar}")
415
- return None
416
- if not os.path.exists(canny_tar):
417
- logger.warning(f"Canny tar file not found: {canny_tar}")
418
- return None
419
- if not os.path.exists(dino_tar):
420
- logger.warning(f"DINOv2 tar file not found: {dino_tar}")
421
- return None
422
- # Initialize grounding_data
423
- grounding_data = {}
424
-
425
- if not os.path.exists(grounding_tar):
426
- logger.warning(f"Grounding tar file not found: {grounding_tar} - will process without grounding data")
427
- else:
428
- logger.info(f"Extracting from grounding: {grounding_tar}")
429
- grounding_data = self.extract_from_tar(grounding_tar, 'grounding')
430
-
431
- # Extract data from all tar files
432
- logger.info(f"Extracting from rgb_caption: {rgb_caption_tar}")
433
- rgb_caption_data = self.extract_from_tar(rgb_caption_tar, 'rgb_caption')
434
-
435
- logger.info(f"Extracting from depth: {depth_tar}")
436
- depth_data = self.extract_from_tar(depth_tar, 'depth')
437
-
438
- logger.info(f"Extracting from normal: {normal_tar}")
439
- normal_data = self.extract_from_tar(normal_tar, 'normal')
440
-
441
- logger.info(f"Extracting from det_seg: {det_seg_tar}")
442
- det_seg_data = self.extract_from_tar(det_seg_tar, 'det_seg')
443
-
444
- logger.info(f"Extracting from canny: {canny_tar}")
445
- canny_data = self.extract_from_tar(canny_tar, 'canny')
446
-
447
- logger.info(f"Extracting from dino: {dino_tar}")
448
- dino_data = self.extract_from_tar(dino_tar, 'dino')
449
-
450
- # Find common UIDs (grounding is optional)
451
- common_uids = set(rgb_caption_data.keys()) & set(depth_data.keys()) & set(normal_data.keys()) & set(det_seg_data.keys()) & set(canny_data.keys()) & set(dino_data.keys())
452
-
453
- if not common_uids:
454
- logger.warning(f"No common UIDs found for {base_name}")
455
- return None
456
-
457
- logger.info(f"Found {len(common_uids)} common UIDs")
458
-
459
- # Sort UIDs for consistent ordering
460
- sorted_uids = sorted(common_uids)
461
-
462
- # Create samples for each UID
463
- all_samples = []
464
-
465
- for uid in sorted_uids:
466
- rgb_info = rgb_caption_data[uid]
467
-
468
- # Skip if missing image or caption
469
- if rgb_info['image'] is None or rgb_info['caption'] is None:
470
- continue
471
-
472
- # Create base sample with all modalities
473
- sample = {
474
- 'uid': uid,
475
- 'rgb': rgb_caption_data[uid]['image'],
476
- 'caption': rgb_caption_data[uid]['caption'],
477
- 'depth': depth_data[uid],
478
- 'normal': normal_data[uid],
479
- 'det_seg': det_seg_data[uid],
480
- 'canny': canny_data[uid],
481
- 'dino': dino_data[uid]
482
- }
483
-
484
- # Add grounding data if available
485
- if uid in grounding_data:
486
- extracted_grounding = self.extract_grounding_data(grounding_data[uid])
487
- if extracted_grounding:
488
- raw_image = Image.open(io.BytesIO(rgb_caption_data[uid]['image']))
489
- raw_image_size = raw_image.size
490
- assert raw_image_size == (512, 512), f"Raw image size is {raw_image_size} but expected (512, 512) for {uid}"
491
- grounding_samples = self.create_grounding_samples(extracted_grounding, uid)
492
- if grounding_samples:
493
- sample['grounding'] = grounding_samples
494
-
495
- # visualize_segmentation = sample['grounding'][1]['segmentation']
496
- # self.visualize_rle_mask(visualize_segmentation,save_path=f"visualize_{uid}_1.png")
497
- all_samples.append(sample)
498
-
499
- if not all_samples:
500
- logger.warning(f"No samples found for {base_name}")
501
- return None
502
-
503
- # Create DataFrame
504
- df = pd.DataFrame(all_samples)
505
-
506
- # Debug information
507
- logger.info(f"DataFrame shape: {df.shape}")
508
- if not df.empty:
509
- logger.info(f"Sample UIDs: {df['uid'].head().tolist()}")
510
- # Count grounding samples
511
- grounding_counts = []
512
- for _, row in df.iterrows():
513
- if 'grounding' in row and row['grounding'] is not None and isinstance(row['grounding'], list):
514
- grounding_counts.append(len(row['grounding']))
515
- else:
516
- grounding_counts.append(0)
517
- if grounding_counts:
518
- logger.info(f"Grounding samples per row: {np.mean(grounding_counts):.2f} avg, {max(grounding_counts)} max")
519
- else:
520
- logger.info("No grounding samples found")
521
-
522
- # Count DINOv2 features
523
- dino_counts = []
524
- for _, row in df.iterrows():
525
- if 'dino' in row and row['dino'] is not None:
526
- dino_counts.append(1)
527
- else:
528
- dino_counts.append(0)
529
- if dino_counts:
530
- logger.info(f"DINOv2 features loaded: {sum(dino_counts)}/{len(dino_counts)} samples")
531
- else:
532
- logger.info("No DINOv2 features found")
533
-
534
- # Save to parquet
535
- output_path = os.path.join(self.parquet_dir, f"{base_name}.parquet")
536
-
537
- df.to_parquet(
538
- output_path,
539
- index=False,
540
- engine='pyarrow',
541
- compression='snappy',
542
- row_group_size=1000
543
- )
544
- logger.info(f"Saved {len(df)} grounding samples to {output_path}")
545
-
546
- return output_path
547
-
548
- def process_all_tars(self):
549
- """Process all tar files and create corresponding parquet files"""
550
- tar_files = self.get_tar_files()
551
-
552
- if not tar_files:
553
- logger.error("No tar files found in rgb_caption directory")
554
- return
555
-
556
- tar_files = tar_files[1373:]
557
- logger.info(f"Found {len(tar_files)} tar files to process")
558
-
559
- processed_files = []
560
- for tar_file in tar_files:
561
- try:
562
- output_path = self.process_tar_file(tar_file)
563
- if output_path:
564
- processed_files.append(output_path)
565
- except Exception as e:
566
- logger.error(f"Error processing {tar_file}: {e}")
567
-
568
- logger.info(f"Successfully processed {len(processed_files)} files")
569
- return processed_files
570
-
571
- def main():
572
- import argparse
573
-
574
- parser = argparse.ArgumentParser(description='Generate parquet files with grounding data')
575
- parser.add_argument('--base_dir', type=str, default='./datasets/blip3o',
576
- help='Base directory (default: ./datasets/blip3o)')
577
- parser.add_argument('--visualize_mask', action='store_true',
578
- help='Visualize RLE masks for debugging')
579
- parser.add_argument('--visualize_tar', type=str, default=None,
580
- help='Specific tar file to visualize (e.g., sa_000001.tar)')
581
- parser.add_argument('--visualize_uid', type=str, default=None,
582
- help='Specific UID to visualize (e.g., sa_1)')
583
- parser.add_argument('--visualize_object_id', type=int, default=0,
584
- help='Object ID to visualize (default: 0)')
585
-
586
- args = parser.parse_args()
587
-
588
- # Set the base directory
589
- base_dir = args.base_dir
590
-
591
- # Create processor
592
- processor = GroundingProcessor(base_dir)
593
-
594
- # Visualize mask if requested
595
- if args.visualize_mask and args.visualize_tar:
596
- logger.info(f"Visualizing mask for {args.visualize_tar}, object {args.visualize_object_id}")
597
-
598
- # Find the grounding tar file for this UID
599
- grounding_tar = os.path.join(processor.grounding_dir, f"{args.visualize_tar}.tar")
600
- if os.path.exists(grounding_tar):
601
- grounding_data = processor.extract_from_tar(grounding_tar, 'grounding')
602
- if args.visualize_uid in grounding_data:
603
- extracted_grounding = processor.extract_grounding_data(grounding_data[args.visualize_uid])
604
- if extracted_grounding:
605
- save_path = f"mask_visualization_{args.visualize_uid}.png"
606
- processor.visualize_object_mask(extracted_grounding, args.visualize_uid,
607
- args.visualize_object_id, save_path)
608
- else:
609
- logger.error(f"No grounding data found for {args.visualize_uid}")
610
- else:
611
- logger.error(f"UID {args.visualize_uid} not found in grounding tar")
612
- else:
613
- logger.error(f"Grounding tar not found: {grounding_tar}")
614
- return
615
-
616
- # Process all tar files
617
- processed_files = processor.process_all_tars()
618
-
619
- if processed_files:
620
- logger.info(f"Successfully created {len(processed_files)} grounding parquet files:")
621
- for file in processed_files:
622
- logger.info(f" - {file}")
623
- else:
624
- logger.error("No grounding parquet files were created")
625
-
626
- if __name__ == "__main__":
627
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/generate_parquet_json.py DELETED
@@ -1,69 +0,0 @@
1
- import os
2
- import json
3
- import argparse
4
- import pyarrow.parquet as pq
5
- from pathlib import Path
6
-
7
- def get_parquet_info(file_path):
8
- """Get information about a parquet file"""
9
- try:
10
- parquet_file = pq.ParquetFile(file_path)
11
- num_row_groups = parquet_file.num_row_groups
12
-
13
- # Calculate total number of rows
14
- total_rows = 0
15
- for i in range(num_row_groups):
16
- total_rows += parquet_file.metadata.row_group(i).num_rows
17
-
18
- return {
19
- "num_row_groups": num_row_groups,
20
- "num_rows": total_rows
21
- }
22
- except Exception as e:
23
- print(f"Error reading {file_path}: {e}")
24
- return None
25
-
26
- def generate_parquet_info(parquet_dir=None, output_file=None):
27
- """Generate information for all parquet files in the parquet folder"""
28
- parquet_dir = Path(parquet_dir) if parquet_dir is not None else Path("./datasets/blip3o/parquet_rgb_caption_depth_normal_det_seg_grounding_canny_dino_global_clip448_imagebind")
29
- output_file = Path(output_file) if output_file is not None else Path("./datasets/blip3o/parquet_info/blip3o_rgb_caption_depth_normal_det_seg_grounding_canny_dino_global_clip448_imagebind.json")
30
-
31
- # Create output directory if it doesn't exist
32
- output_file.parent.mkdir(parents=True, exist_ok=True)
33
-
34
- # Get all parquet files
35
- parquet_files = list(parquet_dir.glob("*.parquet"))
36
- print(f"Found {len(parquet_files)} parquet files")
37
-
38
- # Process each file
39
- parquet_info = {}
40
-
41
- for i, parquet_file in enumerate(parquet_files):
42
- print(f"Processing {i+1}/{len(parquet_files)}: {parquet_file.name}")
43
-
44
- # Get relative path from current directory using parquet_dir
45
- relative_path = str((parquet_dir / parquet_file.name))
46
- if not relative_path.startswith("./"):
47
- relative_path = f"./{relative_path}"
48
-
49
- info = get_parquet_info(parquet_file)
50
- if info:
51
- parquet_info[relative_path] = info
52
- print(f"Processed {parquet_file.name} successfully")
53
- print(info)
54
- else:
55
- print(f"Failed to process {parquet_file.name}")
56
-
57
- # Save to JSON file
58
- with open(output_file, 'w') as f:
59
- json.dump(parquet_info, f, indent=2, sort_keys=True)
60
-
61
- print(f"Saved parquet information to {output_file}")
62
- print(f"Processed {len(parquet_info)} files successfully")
63
-
64
- if __name__ == "__main__":
65
- parser = argparse.ArgumentParser()
66
- parser.add_argument("--parquet_dir", default=None)
67
- parser.add_argument("--output_file", default=None)
68
- args = parser.parse_args()
69
- generate_parquet_info(parquet_dir=args.parquet_dir, output_file=args.output_file)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/generate_parquet_vlm_sft.py DELETED
@@ -1,278 +0,0 @@
1
- # Copyright 2025 Bytedance Ltd. and/or its affiliates.
2
- # SPDX-License-Identifier: Apache-2.0
3
- """Bundle vlm_sft (llava-onevision style jsonl + loose image files) into
4
- parquet shards loadable by SftParquetIterableDataset (data/vlm_dataset.py).
5
-
6
- Why: llava-onevision is ~4M small files, impractical to upload to / stream
7
- from S3. The shards follow the blip3o convention: few large parquet files +
8
- a parquet_info JSON, sharded by (file, row_group) at load time.
9
-
10
- Equivalence: the jsonl loader selects samples per subset via
11
- rng = random.Random(); rng.seed(shuffle_seed); rng.shuffle(lines)
12
- lines = lines[:num_used_data]
13
- (SftJSONLIterableDataset.get_data_paths). This script replicates that
14
- selection EXACTLY (taking shuffle_lines / shuffle_seed / num_used_data from
15
- the same training yaml), so the parquet rows are precisely the records the
16
- jsonl loader would have used, in the same shuffled order.
17
-
18
- Row schema:
19
- record: str original jsonl line, verbatim
20
- image_bytes: list<binary> raw image files (no re-encode), in the order
21
- the jsonl loader's valid_paths would produce
22
-
23
- Skipped at conversion (mirroring the jsonl loader's runtime skips):
24
- - records with 'video' (frame sampling can't be baked into bytes)
25
- - records whose 'image' field resolves to zero existing files
26
-
27
- Usage (single process, all subsets of the group):
28
- python3 data/any2any_preprocess/generate_parquet_vlm_sft.py \
29
- --config data/configs/modus_stage1.yaml --group vlm_sft \
30
- --out-dir ./datasets/llava_onevision_vqa_parquet
31
-
32
- Parallelize across subsets with disjoint --subsets lists (e.g. sbatch array);
33
- each invocation only rewrites its own subsets' shards and merge-updates the
34
- shared parquet_info JSON. Already-converted subsets are skipped unless
35
- --overwrite is given.
36
-
37
- After conversion, point the training config at the new group:
38
- vlm_sft_parquet:
39
- dataset_names:
40
- - llava_onevision_vqa
41
- image_transform_args: { ...same as the old vlm_sft block... }
42
- is_mandatory: true
43
- weight: <same as before>
44
- (no num_used_data / shuffle_lines needed β€” selection is baked in)
45
- """
46
-
47
- import argparse
48
- import json
49
- import os
50
- import random
51
- import re
52
- import sys
53
- from concurrent.futures import ThreadPoolExecutor
54
-
55
- import pyarrow as pa
56
- import pyarrow.parquet as pq
57
- import yaml
58
-
59
- sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
60
- from data.dataset_info import DATASET_INFO # noqa: E402
61
- from data.parquet_utils import apply_data_root_override, read_file_bytes # noqa: E402
62
-
63
- SCHEMA = pa.schema([
64
- pa.field('record', pa.string()),
65
- pa.field('image_bytes', pa.list_(pa.binary())),
66
- ])
67
-
68
-
69
- def sanitize(name):
70
- # subset names contain '(', ',', ')' etc. β€” make a filesystem-safe stem
71
- return re.sub(r'[^0-9A-Za-z_.-]+', '_', name).strip('_')
72
-
73
-
74
- def _read_image_bytes(path):
75
- if path.startswith('s3://'):
76
- return read_file_bytes(path)
77
- with open(path, 'rb') as f:
78
- return f.read()
79
-
80
-
81
- def _load_record_images(args):
82
- """Worker: (raw_line, image_dir) -> (raw_line, image_bytes list, status).
83
- status: 'ok' | 'skip_video' | 'skip_missing' | 'skip_badjson'."""
84
- raw_line, image_dir = args
85
- try:
86
- data_item = json.loads(raw_line)
87
- except Exception:
88
- return raw_line, None, 'skip_badjson'
89
- if 'video' in data_item:
90
- return raw_line, None, 'skip_video'
91
- image_bytes = []
92
- if 'image' in data_item:
93
- images = data_item['image'] if isinstance(data_item['image'], list) else [data_item['image']]
94
- for img_name in images:
95
- if not (isinstance(img_name, str) and img_name.strip() != ''):
96
- continue
97
- img_path = os.path.join(image_dir, img_name)
98
- try:
99
- image_bytes.append(_read_image_bytes(img_path))
100
- except (OSError, IOError):
101
- continue # mirrors the jsonl loader dropping invalid paths
102
- if len(image_bytes) == 0:
103
- return raw_line, None, 'skip_missing'
104
- return raw_line, image_bytes, 'ok'
105
-
106
-
107
- def select_lines(jsonl_path, num_used_data, shuffle_lines, shuffle_seed):
108
- """Replicates SftJSONLIterableDataset.get_data_paths line selection."""
109
- jsonl_path = apply_data_root_override(jsonl_path)
110
- if jsonl_path.startswith('s3://'):
111
- raw_data = read_file_bytes(jsonl_path).decode('utf-8').splitlines(keepends=True)
112
- else:
113
- with open(jsonl_path, 'r') as f:
114
- raw_data = f.readlines()
115
- if shuffle_lines:
116
- rng = random.Random()
117
- rng.seed(shuffle_seed)
118
- rng.shuffle(raw_data)
119
- return raw_data[:num_used_data]
120
-
121
-
122
- def convert_subset(subset, jsonl_path, image_dir, num_used_data, shuffle_lines,
123
- shuffle_seed, train_dir, rows_per_file, row_group_size,
124
- io_threads, info_key_dir):
125
- """Convert one subset to parquet shards. Returns (shard_info, stats):
126
- shard_info: {info_key: {'num_row_groups': int, 'num_rows': int}}."""
127
- image_dir = apply_data_root_override(image_dir)
128
- lines = select_lines(jsonl_path, num_used_data, shuffle_lines, shuffle_seed)
129
- print(f'[convert] {subset}: selected {len(lines)} lines (quota {num_used_data})')
130
-
131
- stem = sanitize(subset)
132
- stats = {'ok': 0, 'skip_video': 0, 'skip_missing': 0, 'skip_badjson': 0}
133
- shard_info = {}
134
- part = 0
135
- records, images = [], []
136
-
137
- def flush():
138
- nonlocal part, records, images
139
- if not records:
140
- return
141
- fname = f'{stem}__part{part:04d}.parquet'
142
- out_path = os.path.join(train_dir, fname)
143
- table = pa.Table.from_arrays(
144
- [pa.array(records, type=pa.string()),
145
- pa.array(images, type=pa.list_(pa.binary()))],
146
- schema=SCHEMA,
147
- )
148
- pq.write_table(table, out_path, compression='snappy',
149
- row_group_size=row_group_size)
150
- meta = pq.ParquetFile(out_path).metadata
151
- shard_info[os.path.join(info_key_dir, fname)] = {
152
- 'num_row_groups': meta.num_row_groups,
153
- 'num_rows': meta.num_rows,
154
- }
155
- part += 1
156
- records, images = [], []
157
-
158
- with ThreadPoolExecutor(max_workers=io_threads) as ex:
159
- # batch the submissions: Executor.map submits everything up front and
160
- # holds completed results until iterated, so mapping the whole subset
161
- # at once would keep every image of the subset in memory.
162
- for start in range(0, len(lines), rows_per_file):
163
- chunk = lines[start:start + rows_per_file]
164
- for raw_line, image_bytes, status in ex.map(
165
- _load_record_images, ((l, image_dir) for l in chunk)
166
- ):
167
- stats[status] += 1
168
- if status != 'ok':
169
- continue
170
- records.append(raw_line)
171
- images.append(image_bytes)
172
- if len(records) >= rows_per_file:
173
- flush()
174
- flush()
175
- return shard_info, stats
176
-
177
-
178
- def main():
179
- # DATASET_INFO uses './datasets/...' paths relative to the repo root;
180
- # run from there regardless of the caller's cwd (e.g. container default
181
- # workdir). Pass absolute --config/--out-dir or repo-root-relative ones.
182
- os.chdir(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
183
-
184
- parser = argparse.ArgumentParser(description=__doc__)
185
- parser.add_argument('--config', required=True,
186
- help='training data yaml (e.g. data/configs/modus_stage1.yaml)')
187
- parser.add_argument('--group', default='vlm_sft',
188
- help='dataset group in the yaml / DATASET_INFO')
189
- parser.add_argument('--out-dir', default='./datasets/llava_onevision_vqa_parquet')
190
- parser.add_argument('--subsets', nargs='*', default=None,
191
- help='only convert these subsets (default: all in the yaml)')
192
- parser.add_argument('--rows-per-file', type=int, default=5000)
193
- parser.add_argument('--row-group-size', type=int, default=200)
194
- parser.add_argument('--io-threads', type=int, default=16)
195
- parser.add_argument('--overwrite', action='store_true',
196
- help='re-convert subsets whose shards already exist')
197
- args = parser.parse_args()
198
-
199
- with open(args.config, 'r') as f:
200
- cfg = yaml.safe_load(f)[args.group]
201
- dataset_names = cfg['dataset_names']
202
- num_used_data = cfg['num_used_data']
203
- shuffle_lines = cfg.get('shuffle_lines', False)
204
- shuffle_seed = cfg.get('shuffle_seed', 0)
205
- if len(dataset_names) != len(num_used_data):
206
- # The jsonl loader zips the two lists, silently truncating the longer
207
- # one (this happens in the real configs: subsets commented out of
208
- # dataset_names leave stale num_used_data rows). Replicate that
209
- # pairing exactly β€” equivalence with current training behavior.
210
- print(f'[warn] dataset_names ({len(dataset_names)}) and num_used_data '
211
- f'({len(num_used_data)}) differ; zip-truncating like the jsonl loader')
212
-
213
- stems = [sanitize(s) for s in dataset_names]
214
- assert len(set(stems)) == len(stems), 'sanitized subset names collide'
215
-
216
- train_dir = os.path.join(args.out_dir, 'train')
217
- info_dir = os.path.join(args.out_dir, 'parquet_info')
218
- os.makedirs(train_dir, exist_ok=True)
219
- os.makedirs(info_dir, exist_ok=True)
220
- info_path = os.path.join(info_dir, 'llava_onevision_vqa.json')
221
- # info keys use the configured-style relative path; loaders fall back to
222
- # basename matching anyway, so the prefix only matters cosmetically.
223
- info_key_dir = train_dir
224
-
225
- selected = args.subsets if args.subsets else dataset_names
226
- unknown = [s for s in selected if s not in dataset_names]
227
- assert not unknown, f'subsets not in the yaml group: {unknown}'
228
-
229
- totals = {'ok': 0, 'skip_video': 0, 'skip_missing': 0, 'skip_badjson': 0}
230
- for subset, n_used in zip(dataset_names, num_used_data):
231
- if subset not in selected:
232
- continue
233
- stem = sanitize(subset)
234
- # A subset counts as done only if its shards are in the info JSON,
235
- # which is merge-updated AFTER the whole subset finishes. Files on
236
- # disk without an info entry are partial output of an interrupted
237
- # run β€” delete and re-convert.
238
- done_in_info = set()
239
- if os.path.exists(info_path) and not args.overwrite:
240
- with open(info_path, 'r') as f:
241
- done_in_info = {os.path.basename(k) for k in json.load(f)
242
- if os.path.basename(k).startswith(f'{stem}__part')}
243
- existing = [f for f in os.listdir(train_dir)
244
- if f.startswith(f'{stem}__part') and f.endswith('.parquet')]
245
- if done_in_info and set(existing) >= done_in_info:
246
- print(f'[skip] {subset}: {len(done_in_info)} shard(s) already converted')
247
- continue
248
- for f in existing:
249
- os.remove(os.path.join(train_dir, f))
250
-
251
- meta_info = DATASET_INFO[args.group][subset]
252
- shard_info, stats = convert_subset(
253
- subset, meta_info['jsonl_path'], meta_info['data_dir'], n_used,
254
- shuffle_lines, shuffle_seed, train_dir, args.rows_per_file,
255
- args.row_group_size, args.io_threads, info_key_dir,
256
- )
257
- for k in totals:
258
- totals[k] += stats[k]
259
- print(f'[done] {subset}: {stats}')
260
-
261
- # merge-update the shared parquet_info JSON after each subset so
262
- # partial runs / parallel invocations stay resumable.
263
- merged = {}
264
- if os.path.exists(info_path):
265
- with open(info_path, 'r') as f:
266
- merged = json.load(f)
267
- merged.update(shard_info)
268
- tmp_path = info_path + f'.tmp.{os.getpid()}'
269
- with open(tmp_path, 'w') as f:
270
- json.dump(merged, f, indent=1)
271
- os.replace(tmp_path, info_path)
272
-
273
- print(f'[all done] totals: {totals}')
274
- print(f'parquet_info: {info_path}')
275
-
276
-
277
- if __name__ == '__main__':
278
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/hf_upload.py DELETED
@@ -1,26 +0,0 @@
1
- """Upload a folder to a HuggingFace dataset repo.
2
-
3
- Token is read from the file at $HF_TOKEN_FILE (never passed on the command line
4
- so it does not appear in logs / process listings).
5
-
6
- Usage:
7
- HF_TOKEN_FILE=/users/mye/.hf_token python hf_upload.py <repo_id> <folder>
8
- """
9
- import os
10
- import sys
11
-
12
- from huggingface_hub import HfApi
13
-
14
- repo_id = sys.argv[1]
15
- folder = sys.argv[2]
16
- token = open(os.environ['HF_TOKEN_FILE']).read().strip()
17
-
18
- api = HfApi(token=token)
19
- api.create_repo(repo_id, repo_type='dataset', exist_ok=True)
20
- api.upload_folder(
21
- repo_id=repo_id,
22
- repo_type='dataset',
23
- folder_path=folder,
24
- commit_message='Add MODUS 16-mod preview (500 SA-1B samples + hero + card)',
25
- )
26
- print(f'DONE: https://huggingface.co/datasets/{repo_id}', flush=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/parquet_visualize.py DELETED
@@ -1,408 +0,0 @@
1
- import pandas as pd
2
- import pyarrow as pa
3
- import pyarrow.parquet as pq
4
- import os
5
- import numpy as np
6
- from pathlib import Path
7
- from PIL import Image, ImageDraw, ImageFont
8
- import json
9
- import io
10
- import matplotlib.pyplot as plt
11
- import matplotlib.patches as patches
12
- from matplotlib.patches import Rectangle
13
- import cv2
14
-
15
- def make_json_serializable(obj):
16
- """
17
- Convert numpy arrays and other non-serializable objects to JSON-serializable format.
18
- """
19
- if isinstance(obj, np.ndarray):
20
- return obj.tolist()
21
- elif isinstance(obj, dict):
22
- return {key: make_json_serializable(value) for key, value in obj.items()}
23
- elif isinstance(obj, list):
24
- return [make_json_serializable(item) for item in obj]
25
- elif isinstance(obj, (np.integer, np.floating)):
26
- return obj.item()
27
- elif hasattr(obj, 'tolist'): # Handle other numpy-like objects
28
- return obj.tolist()
29
- elif hasattr(obj, 'item'): # Handle other numpy scalar types
30
- return obj.item()
31
- else:
32
- return obj
33
-
34
- def decode_rle_to_mask(rle_counts, size):
35
- """
36
- Decode RLE (Run Length Encoding) to binary mask.
37
- This is a simplified RLE decoder for COCO format.
38
-
39
- Args:
40
- rle_counts (str): RLE encoded string
41
- size (list): [width, height] of the mask
42
-
43
- Returns:
44
- numpy.ndarray: Binary mask of shape (height, width)
45
- """
46
- try:
47
- # Try to use pycocotools if available
48
- import pycocotools.mask as mask_util
49
-
50
- # Create RLE dict
51
- rle = {
52
- 'counts': rle_counts,
53
- 'size': size
54
- }
55
-
56
- # Decode to binary mask
57
- binary_mask = mask_util.decode(rle)
58
- return binary_mask
59
- except ImportError:
60
- # Fallback: create a simple mask visualization without decoding
61
- print("Warning: pycocotools not available, creating placeholder mask")
62
- height, width = size[1], size[0] # size is [width, height]
63
- return np.zeros((height, width), dtype=np.uint8)
64
-
65
- def simple_rle_decode(rle_counts, size):
66
- """
67
- Simple RLE decoder for COCO format masks.
68
- This is a basic implementation that should work for most cases.
69
- """
70
- try:
71
- height, width = size[1], size[0] # size is [width, height]
72
- mask = np.zeros(height * width, dtype=np.uint8)
73
-
74
- # Parse the RLE counts
75
- counts = []
76
- current_num = ""
77
- for char in rle_counts:
78
- if char.isdigit() or char in '-+':
79
- current_num += char
80
- else:
81
- if current_num:
82
- counts.append(int(current_num))
83
- current_num = ""
84
- if current_num:
85
- counts.append(int(current_num))
86
-
87
- # Decode RLE
88
- pos = 0
89
- for i, count in enumerate(counts):
90
- if i % 2 == 0: # Even indices are background (0)
91
- pos += count
92
- else: # Odd indices are foreground (1)
93
- mask[pos:pos+count] = 1
94
- pos += count
95
-
96
- # Reshape to 2D
97
- mask = mask.reshape((height, width))
98
- return mask
99
- except Exception as e:
100
- print(f"Error in simple RLE decode: {e}")
101
- height, width = size[1], size[0]
102
- return np.zeros((height, width), dtype=np.uint8)
103
-
104
- def visualize_detection_data(det_data, rgb_image, output_dir, filename_base):
105
- """
106
- Visualize detection data with bounding boxes and masks overlaid on RGB image.
107
-
108
- Args:
109
- det_data (dict): Detection data containing instances
110
- rgb_image (PIL.Image): RGB image to overlay on
111
- output_dir (str): Output directory
112
- filename_base (str): Base filename for saving
113
- """
114
- try:
115
- # Make a deep copy of the original data to avoid modifying it
116
- import copy
117
- original_det_data = make_json_serializable(copy.deepcopy(det_data))
118
- # Convert PIL image to numpy array
119
- img_array = np.array(rgb_image)
120
- height, width = img_array.shape[:2]
121
-
122
- # Create figure with subplots
123
- fig, axes = plt.subplots(1, 3, figsize=(18, 6))
124
-
125
- # Original image
126
- axes[0].imshow(img_array)
127
- axes[0].set_title('Original Image')
128
- axes[0].axis('off')
129
-
130
- # Image with bounding boxes
131
- axes[1].imshow(img_array)
132
- axes[1].set_title('Bounding Boxes')
133
- axes[1].axis('off')
134
-
135
- # Image with masks
136
- axes[2].imshow(img_array)
137
- axes[2].set_title('Segmentation Masks')
138
- axes[2].axis('off')
139
-
140
- # Colors for different categories
141
- colors = ['red', 'blue', 'green', 'yellow', 'purple', 'orange', 'cyan', 'magenta']
142
-
143
- # Process each instance
144
- for i, instance in enumerate(det_data.get('instances', [])):
145
- color = colors[i % len(colors)]
146
- category = instance.get('category', 'unknown')
147
- score = instance.get('score', 0.0)
148
- bbox = instance.get('bbox', [])
149
-
150
- # Draw bounding box
151
- if len(bbox) == 4:
152
- x1, y1, x2, y2 = bbox
153
- rect = Rectangle((x1, y1), x2-x1, y2-y1,
154
- linewidth=2, edgecolor=color, facecolor='none')
155
- axes[1].add_patch(rect)
156
-
157
- # Add label
158
- label = f"{category}: {score:.2f}"
159
- axes[1].text(x1, y1-5, label, color=color, fontsize=8,
160
- bbox=dict(boxstyle="round,pad=0.3", facecolor='white', alpha=0.7))
161
-
162
- # Draw segmentation mask
163
- segmentation = instance.get('segmentation', {})
164
- mask_drawn = False
165
-
166
- if 'counts' in segmentation and 'size' in segmentation:
167
- # Try multiple decoding methods
168
- mask = None
169
-
170
- # Method 1: Try pycocotools
171
- try:
172
- mask = decode_rle_to_mask(segmentation['counts'], segmentation['size'])
173
- if mask.size > 0 and np.any(mask):
174
- print(f"Successfully decoded mask for {category} using pycocotools")
175
- except:
176
- pass
177
-
178
- # Method 2: Try simple RLE decoder
179
- if mask is None or not np.any(mask):
180
- try:
181
- mask = simple_rle_decode(segmentation['counts'], segmentation['size'])
182
- if mask.size > 0 and np.any(mask):
183
- print(f"Successfully decoded mask for {category} using simple decoder")
184
- except Exception as e:
185
- print(f"Simple RLE decode failed for {category}: {e}")
186
-
187
- # If we have a valid mask, visualize it
188
- if mask is not None and mask.size > 0 and np.any(mask):
189
- # Create a more visible mask visualization
190
- # Method 1: Colored overlay
191
- mask_colored = np.zeros((height, width, 4))
192
- color_rgb = np.array(plt.cm.colors.to_rgb(color)) * 255
193
- mask_colored[:, :, :3] = color_rgb
194
- mask_colored[:, :, 3] = mask * 0.6 # More opaque
195
-
196
- # Apply mask overlay
197
- axes[2].imshow(mask_colored.astype(np.uint8), alpha=0.7)
198
-
199
- # Method 2: Also draw contour
200
- from matplotlib.patches import Polygon
201
- contours, _ = cv2.findContours(mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
202
- for contour in contours:
203
- if len(contour) > 2:
204
- contour = contour.reshape(-1, 2)
205
- polygon = Polygon(contour, fill=False, edgecolor=color, linewidth=2)
206
- axes[2].add_patch(polygon)
207
-
208
- mask_drawn = True
209
-
210
- # Fallback: if no mask was drawn, create a simple mask from bounding box
211
- if not mask_drawn and len(bbox) == 4:
212
- x1, y1, x2, y2 = bbox
213
- # Create a simple rectangular mask
214
- mask = np.zeros((height, width), dtype=np.uint8)
215
- x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
216
- # Ensure coordinates are within bounds
217
- x1, y1 = max(0, x1), max(0, y1)
218
- x2, y2 = min(width, x2), min(height, y2)
219
-
220
- if x2 > x1 and y2 > y1:
221
- mask[y1:y2, x1:x2] = 1
222
-
223
- # Create colored mask overlay
224
- mask_colored = np.zeros((height, width, 4))
225
- color_rgb = np.array(plt.cm.colors.to_rgb(color)) * 255
226
- mask_colored[:, :, :3] = color_rgb
227
- mask_colored[:, :, 3] = mask * 0.4 # Semi-transparent
228
-
229
- # Apply mask overlay
230
- axes[2].imshow(mask_colored.astype(np.uint8), alpha=0.6)
231
-
232
- # Also draw the bounding box outline
233
- rect = Rectangle((x1, y1), x2-x1, y2-y1,
234
- linewidth=2, edgecolor=color, facecolor='none')
235
- axes[2].add_patch(rect)
236
-
237
- # Save the visualization
238
- output_path = os.path.join(output_dir, f"{filename_base}_detection_visualization.png")
239
- plt.tight_layout()
240
- plt.savefig(output_path, dpi=150, bbox_inches='tight')
241
- plt.close()
242
-
243
- print(f" Saved detection visualization: {filename_base}_detection_visualization.png")
244
-
245
- # Also save the JSON data for reference
246
- json_path = os.path.join(output_dir, f"{filename_base}_det_seg.json")
247
- try:
248
- with open(json_path, 'w') as f:
249
- json.dump(original_det_data, f, indent=2)
250
- print(f" Saved detection JSON: {filename_base}_det_seg.json")
251
- except Exception as json_error:
252
- print(f" JSON save error: {json_error}")
253
- # Try to save a simplified version
254
- try:
255
- simplified_data = {
256
- 'image_id': original_det_data.get('image_id', 'unknown'),
257
- 'instances': []
258
- }
259
- for instance in original_det_data.get('instances', []):
260
- simplified_instance = {
261
- 'category': instance.get('category', 'unknown'),
262
- 'score': float(instance.get('score', 0.0)),
263
- 'bbox': [float(x) for x in instance.get('bbox', [])],
264
- 'area': float(instance.get('area', 0.0))
265
- }
266
- simplified_data['instances'].append(simplified_instance)
267
-
268
- with open(json_path, 'w') as f:
269
- json.dump(simplified_data, f, indent=2)
270
- print(f" Saved simplified detection JSON: {filename_base}_det_seg.json")
271
- except Exception as e2:
272
- print(f" Failed to save even simplified JSON: {e2}")
273
-
274
- except Exception as e:
275
- print(f" Error visualizing detection data: {e}")
276
-
277
- def visualize_parquet_examples(input_file, output_dir, max_rows=None):
278
- """
279
- Visualize examples from a parquet file by saving each row as separate files.
280
- Saves images as PNG and text data as TXT files with uid_column_name naming format.
281
-
282
- Args:
283
- input_file (str): Path to the input parquet file
284
- output_dir (str): Directory to save the visualization files
285
- max_rows (int): Maximum number of rows to process (None for all rows)
286
- """
287
-
288
- # Create output directory if it doesn't exist
289
- os.makedirs(output_dir, exist_ok=True)
290
-
291
- print(f"Processing {input_file}...")
292
-
293
- # Read the parquet file
294
- df = pd.read_parquet(input_file)
295
- print(f"Total rows in file: {len(df)}")
296
- print(f"Columns: {list(df.columns)}")
297
-
298
- # Limit rows if max_rows is specified
299
- if max_rows is not None:
300
- df = df.head(max_rows)
301
- print(f"Processing only first {max_rows} rows")
302
-
303
- # Display first few rows to understand the data structure
304
- print("\nFirst few rows:")
305
- print(df.head(3))
306
-
307
- # Process each row
308
- for index, row in df.iterrows():
309
- print(f"Processing row {index}...")
310
-
311
- filename_base = None
312
-
313
- # Process each column in the row
314
- for col_name, value in row.items():
315
- if col_name == 'uid':
316
- filename_base = value
317
- elif col_name in ['rgb', 'depth', 'normal'] and filename_base is not None:
318
- # Handle image data (binary)
319
- if value is not None and isinstance(value, bytes):
320
- try:
321
- # Convert binary data to PIL Image
322
- image = Image.open(io.BytesIO(value))
323
-
324
- # Create filename
325
- filename = f"{filename_base}_{col_name}.png"
326
- filepath = os.path.join(output_dir, filename)
327
-
328
- # Save as PNG
329
- image.save(filepath, 'PNG')
330
- print(f" Saved {col_name} image: {filename}")
331
-
332
- except Exception as e:
333
- print(f" Error processing {col_name} image: {e}")
334
-
335
- elif col_name == 'caption' and filename_base is not None:
336
- # Handle text data
337
- if value is not None:
338
- try:
339
- filename = f"{filename_base}_captions.txt"
340
- filepath = os.path.join(output_dir, filename)
341
-
342
- # Save text data
343
- with open(filepath, 'w', encoding='utf-8') as f:
344
- if isinstance(value, str):
345
- f.write(value)
346
- else:
347
- f.write(str(value))
348
- print(f" Saved caption: {filename}")
349
-
350
- except Exception as e:
351
- print(f" Error processing caption: {e}")
352
- elif col_name == 'det_seg' and filename_base is not None:
353
- # Handle detection maps (JSON format)
354
- if value is not None:
355
- try:
356
- # Get the RGB image for visualization
357
- rgb_image = None
358
- if 'rgb' in row and row['rgb'] is not None:
359
- rgb_image = Image.open(io.BytesIO(row['rgb']))
360
-
361
- # Visualize detection data with bounding boxes and masks
362
- if rgb_image is not None:
363
- visualize_detection_data(value, rgb_image, output_dir, filename_base)
364
- else:
365
- # If no RGB image, just save the JSON
366
- filename = f"{filename_base}_det_seg.json"
367
- filepath = os.path.join(output_dir, filename)
368
- with open(filepath, 'w') as f:
369
- serializable_data = make_json_serializable(value)
370
- json.dump(serializable_data, f, indent=2)
371
- print(f" Saved detection JSON: {filename}")
372
-
373
- except Exception as e:
374
- print(f" Error processing detection data: {e}")
375
-
376
-
377
-
378
- print(f"\nVisualization completed! Files saved to: {output_dir}")
379
-
380
- # List the created files
381
- created_files = os.listdir(output_dir)
382
- print(f"Created {len(created_files)} files:")
383
- for file in sorted(created_files)[:10]: # Show first 10 files
384
- print(f" {file}")
385
- if len(created_files) > 10:
386
- print(f" ... and {len(created_files) - 10} more files")
387
-
388
- def main():
389
- # Configuration
390
- input_file = "./datasets/blip3o/parquet_rgb_caption_depth_normal_det_seg/sa_000002.parquet"
391
- output_dir = "./datasets/blip3o/parquet_rgb_caption_depth_normal_det_seg_visualize"
392
- max_rows = 30 # Process only first 30 rows
393
-
394
- # Check if input file exists
395
- if not os.path.exists(input_file):
396
- print(f"Error: Input file {input_file} not found!")
397
- return
398
-
399
- # Create output directory
400
- os.makedirs(output_dir, exist_ok=True)
401
-
402
- # Visualize the parquet examples
403
- visualize_parquet_examples(input_file, output_dir, max_rows)
404
-
405
- print("\nVisualization completed successfully!")
406
-
407
- if __name__ == "__main__":
408
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/process_grounding.py DELETED
@@ -1,346 +0,0 @@
1
- import os
2
- import tarfile
3
- import json
4
- import glob
5
- import io
6
- from tqdm import tqdm
7
- import logging
8
-
9
- # Set up logging
10
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
11
- logger = logging.getLogger(__name__)
12
-
13
- class GroundingProcessor:
14
- def __init__(self, base_dir):
15
- self.base_dir = base_dir
16
- self.rgb_caption_dir = os.path.join(base_dir, 'datasets/blip3o/datasets')
17
- self.grounding_source_dir = os.path.join(base_dir, 'datasets/glamm')
18
- self.grounding_output_dir = os.path.join(base_dir, 'datasets/blip3o/datasets_grounding')
19
- self.grounding_cache_dir = os.path.join(base_dir, 'datasets/blip3o/grounding_cache')
20
-
21
- # Create output directories if they don't exist
22
- os.makedirs(self.grounding_output_dir, exist_ok=True)
23
- os.makedirs(self.grounding_cache_dir, exist_ok=True)
24
-
25
- def get_rgb_tar_files(self):
26
- """Get all RGB tar files from datasets directory"""
27
- tar_files = glob.glob(os.path.join(self.rgb_caption_dir, '*.tar'))
28
- return sorted(tar_files)
29
-
30
- def get_grounding_tar_files(self):
31
- """Get all grounding tar files from part directories"""
32
- grounding_tars = []
33
- for part in ['part_1', 'part_2', 'part_3', 'part_4']:
34
- part_dir = os.path.join(self.grounding_source_dir, part)
35
- if os.path.exists(part_dir):
36
- part_tars = glob.glob(os.path.join(part_dir, '*.tar.gz'))
37
- grounding_tars.extend(part_tars)
38
- return sorted(grounding_tars)
39
-
40
- def extract_uids_from_rgb_tar(self, tar_path):
41
- """Extract all UIDs from RGB tar file"""
42
- uids = set()
43
-
44
- try:
45
- with tarfile.open(tar_path, 'r') as tar:
46
- for member in tar.getmembers():
47
- if member.isfile():
48
- filename = member.name
49
- uid = os.path.splitext(filename)[0] # Remove extension
50
- uids.add(uid)
51
- except Exception as e:
52
- logger.error(f"Error extracting UIDs from {tar_path}: {e}")
53
- return set()
54
-
55
- return uids
56
-
57
- def build_grounding_index(self):
58
- """Build an index of all grounding data: uid -> (tar_path, member_name)"""
59
- logger.info("Building grounding data index...")
60
- grounding_index = {}
61
-
62
- grounding_tars = self.get_grounding_tar_files()
63
-
64
- for tar_path in tqdm(grounding_tars, desc="Indexing grounding tars"):
65
- try:
66
- with tarfile.open(tar_path, 'r') as tar:
67
- for member in tar.getmembers():
68
- if member.isfile() and member.name.endswith('.json'):
69
- uid = os.path.splitext(member.name)[0]
70
- grounding_index[uid] = (tar_path, member.name)
71
- except Exception as e:
72
- logger.warning(f"Error indexing {tar_path}: {e}")
73
- continue
74
-
75
- logger.info(f"Built index with {len(grounding_index)} grounding entries")
76
- return grounding_index
77
-
78
- def pre_extract_all_grounding_data_to_disk(self):
79
- """Pre-extract all grounding data to disk cache for fastest access"""
80
- logger.info("Pre-extracting all grounding data to disk cache...")
81
-
82
- grounding_tars = self.get_grounding_tar_files()
83
- cache_index = {}
84
-
85
- for tar_path in tqdm(grounding_tars, desc="Pre-extracting grounding data to disk"):
86
- try:
87
- with tarfile.open(tar_path, 'r') as tar:
88
- for member in tar.getmembers():
89
- if member.isfile() and member.name.endswith('.json'):
90
- uid = os.path.splitext(member.name)[0]
91
-
92
- # Extract JSON content
93
- f = tar.extractfile(member)
94
- if f is not None:
95
- content = f.read()
96
- json_str = content.decode('utf-8')
97
- json_data = json.loads(json_str)
98
-
99
- # Save to disk cache
100
- cache_file = os.path.join(self.grounding_cache_dir, f"{uid}.json")
101
- with open(cache_file, 'w') as cache_f:
102
- json.dump(json_data, cache_f, indent=2)
103
-
104
- cache_index[uid] = cache_file
105
- except Exception as e:
106
- logger.warning(f"Error pre-extracting from {tar_path}: {e}")
107
- continue
108
-
109
- # Save index file
110
- index_file = os.path.join(self.grounding_cache_dir, "index.json")
111
- with open(index_file, 'w') as f:
112
- json.dump(cache_index, f, indent=2)
113
-
114
- logger.info(f"Pre-extracted {len(cache_index)} grounding entries to disk cache")
115
- return cache_index
116
-
117
- def load_grounding_cache_index(self):
118
- """Load the grounding cache index"""
119
- index_file = os.path.join(self.grounding_cache_dir, "index.json")
120
- if os.path.exists(index_file):
121
- with open(index_file, 'r') as f:
122
- return json.load(f)
123
- return {}
124
-
125
- def load_grounding_data_from_cache(self, uids):
126
- """Load grounding data from disk cache for specific UIDs"""
127
- uid_to_grounding = {}
128
-
129
- for uid in uids:
130
- cache_file = os.path.join(self.grounding_cache_dir, f"{uid}.json")
131
- if os.path.exists(cache_file):
132
- try:
133
- with open(cache_file, 'r') as f:
134
- json_data = json.load(f)
135
- uid_to_grounding[uid] = json_data
136
- except Exception as e:
137
- logger.warning(f"Error loading {cache_file}: {e}")
138
- continue
139
-
140
- return uid_to_grounding
141
-
142
- def find_grounding_data_for_uids_fast(self, uids, grounding_data):
143
- """Find grounding JSON data for given UIDs using pre-extracted data - FASTEST"""
144
- uid_to_grounding = {}
145
-
146
- for uid in uids:
147
- if uid in grounding_data:
148
- uid_to_grounding[uid] = grounding_data[uid]
149
-
150
- return uid_to_grounding
151
-
152
- def find_grounding_data_for_uids(self, uids, grounding_index):
153
- """Find grounding JSON data for given UIDs using pre-built index with batch processing"""
154
- uid_to_grounding = {}
155
-
156
- # Group UIDs by tar_path to minimize tar file opens
157
- tar_to_uids = {}
158
- for uid in uids:
159
- if uid in grounding_index:
160
- tar_path, member_name = grounding_index[uid]
161
- if tar_path not in tar_to_uids:
162
- tar_to_uids[tar_path] = []
163
- tar_to_uids[tar_path].append((uid, member_name))
164
-
165
- # Process each tar file once, extracting all needed UIDs
166
- for tar_path, uid_members in tar_to_uids.items():
167
- try:
168
- with tarfile.open(tar_path, 'r') as tar:
169
- for uid, member_name in uid_members:
170
- try:
171
- member = tar.getmember(member_name)
172
- f = tar.extractfile(member)
173
- if f is not None:
174
- content = f.read()
175
- json_str = content.decode('utf-8')
176
- json_data = json.loads(json_str)
177
- uid_to_grounding[uid] = json_data
178
- except Exception as e:
179
- logger.warning(f"Error extracting {uid} from {tar_path}: {e}")
180
- continue
181
- except Exception as e:
182
- logger.warning(f"Error opening {tar_path}: {e}")
183
- continue
184
-
185
- return uid_to_grounding
186
-
187
- def create_grounding_tar(self, rgb_tar_path, uid_to_grounding):
188
- """Create a grounding tar file with the same structure as RGB tar"""
189
-
190
- # Extract base name (e.g., 'sa_000000' from 'sa_000000.tar')
191
- base_name = os.path.splitext(os.path.basename(rgb_tar_path))[0]
192
- output_tar_path = os.path.join(self.grounding_output_dir, f"{base_name}.tar")
193
-
194
- logger.info(f"Creating grounding tar: {output_tar_path}")
195
-
196
- try:
197
- with tarfile.open(output_tar_path, 'w') as output_tar:
198
- for uid, grounding_data in uid_to_grounding.items():
199
- # Create JSON content
200
- json_str = json.dumps(grounding_data, indent=2)
201
- json_bytes = json_str.encode('utf-8')
202
-
203
- # Add to tar with same naming convention as RGB
204
- tarinfo = tarfile.TarInfo(name=f"{uid}.json")
205
- tarinfo.size = len(json_bytes)
206
- output_tar.addfile(tarinfo, io.BytesIO(json_bytes))
207
-
208
- logger.info(f"Created grounding tar with {len(uid_to_grounding)} UIDs: {output_tar_path}")
209
- return output_tar_path
210
-
211
- except Exception as e:
212
- logger.error(f"Error creating grounding tar {output_tar_path}: {e}")
213
- return None
214
-
215
- def process_single_rgb_tar(self, rgb_tar_path, grounding_data=None, grounding_index=None, use_cache=False):
216
- """Process a single RGB tar file and create corresponding grounding tar"""
217
-
218
- # Extract base name
219
- base_name = os.path.splitext(os.path.basename(rgb_tar_path))[0]
220
-
221
- logger.info(f"Processing {base_name}")
222
-
223
- # Extract UIDs from RGB tar
224
- uids = self.extract_uids_from_rgb_tar(rgb_tar_path)
225
-
226
- if not uids:
227
- logger.warning(f"No UIDs found in {rgb_tar_path}")
228
- return None
229
-
230
- logger.info(f"Found {len(uids)} UIDs in {base_name}")
231
-
232
- # Find grounding data for these UIDs
233
- if use_cache:
234
- # Use disk cache (fastest and memory efficient)
235
- uid_to_grounding = self.load_grounding_data_from_cache(uids)
236
- elif grounding_data is not None:
237
- # Use pre-extracted data (fastest)
238
- uid_to_grounding = self.find_grounding_data_for_uids_fast(uids, grounding_data)
239
- elif grounding_index is not None:
240
- # Use index-based lookup (faster than original)
241
- uid_to_grounding = self.find_grounding_data_for_uids(uids, grounding_index)
242
- else:
243
- logger.error("No grounding data or index provided")
244
- return None
245
-
246
- if not uid_to_grounding:
247
- logger.warning(f"No grounding data found for UIDs in {base_name}")
248
- return None
249
-
250
- logger.info(f"Found grounding data for {len(uid_to_grounding)}/{len(uids)} UIDs")
251
-
252
- # Create grounding tar file
253
- output_path = self.create_grounding_tar(rgb_tar_path, uid_to_grounding)
254
-
255
- return output_path
256
-
257
- def process_all_rgb_tars(self, use_cache=True, pre_extract_cache=False):
258
- """Process all RGB tar files and create corresponding grounding tars"""
259
- rgb_tars = self.get_rgb_tar_files()
260
-
261
- if not rgb_tars:
262
- logger.error("No RGB tar files found")
263
- return
264
-
265
- logger.info(f"Found {len(rgb_tars)} RGB tar files to process")
266
-
267
- if use_cache:
268
- # Check if cache exists
269
- cache_index = self.load_grounding_cache_index()
270
-
271
- if not cache_index:
272
- if pre_extract_cache:
273
- logger.info("Pre-extracting grounding data to disk cache...")
274
- self.pre_extract_all_grounding_data_to_disk()
275
- else:
276
- logger.error("No cache found. Use --pre_extract_cache to create cache first.")
277
- return
278
-
279
- logger.info("Using disk cache method (fastest and memory efficient)")
280
- processed_files = []
281
- for rgb_tar in tqdm(rgb_tars, desc="Processing RGB tars"):
282
- try:
283
- output_path = self.process_single_rgb_tar(rgb_tar, use_cache=True)
284
- if output_path:
285
- processed_files.append(output_path)
286
- except Exception as e:
287
- logger.error(f"Error processing {rgb_tar}: {e}")
288
- else:
289
- # Use index-based method (faster than original, less memory)
290
- logger.info("Using index-based method (faster, less memory)")
291
- grounding_index = self.build_grounding_index()
292
-
293
- processed_files = []
294
- for rgb_tar in tqdm(rgb_tars, desc="Processing RGB tars"):
295
- try:
296
- output_path = self.process_single_rgb_tar(rgb_tar, grounding_index=grounding_index)
297
- if output_path:
298
- processed_files.append(output_path)
299
- except Exception as e:
300
- logger.error(f"Error processing {rgb_tar}: {e}")
301
-
302
- logger.info(f"Successfully processed {len(processed_files)} grounding tar files")
303
- return processed_files
304
-
305
- def main():
306
- import argparse
307
-
308
- parser = argparse.ArgumentParser(description='Process grounding data')
309
- parser.add_argument('--use_cache', action='store_true', default=True,
310
- help='Use disk cache method (fastest and memory efficient)')
311
- parser.add_argument('--pre_extract_cache', action='store_true',
312
- help='Pre-extract grounding data to disk cache')
313
- parser.add_argument('--base_dir', type=str, default='./',
314
- help='Base directory (default: ./)')
315
-
316
- args = parser.parse_args()
317
-
318
- # Set the base directory
319
- base_dir = args.base_dir
320
-
321
- # Create processor
322
- processor = GroundingProcessor(base_dir)
323
-
324
- # Process all RGB tar files
325
- processed_files = processor.process_all_rgb_tars(
326
- use_cache=args.use_cache,
327
- pre_extract_cache=args.pre_extract_cache
328
- )
329
-
330
- if processed_files:
331
- logger.info(f"Successfully created {len(processed_files)} grounding tar files:")
332
- for file in processed_files:
333
- logger.info(f" - {file}")
334
- else:
335
- logger.error("No grounding tar files were created")
336
-
337
- if __name__ == "__main__":
338
- main()
339
-
340
-
341
-
342
- # This will take time but only needs to be done once
343
- # python data/any2any_preprocess/process_grounding.py --pre_extract_cache
344
-
345
- # This will be extremely fast
346
- # python data/any2any_preprocess/process_grounding.py --use_cache
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/recompress_normal_jpeg.py DELETED
@@ -1,90 +0,0 @@
1
- """Recompress the `normal` column of the 16-mod parquet from PNG to JPEG q95.
2
-
3
- The `normal` column stores raw image bytes (Marigold normal maps, pass-through
4
- PNG) and accounts for ~65% of on-disk size while being ~6x larger than the
5
- JPEG-encoded `rgb` column. Re-encoding normal as JPEG q95 shrinks it to
6
- roughly rgb size with negligible quality loss.
7
-
8
- Every other column is passed through untouched; the Arrow schema is identical
9
- to the source, so downstream dataloader / HF viewer see no structural change.
10
-
11
- Usage:
12
- python recompress_normal_jpeg.py --out_dir <new_folder> file1.parquet [file2 ...]
13
- """
14
- import argparse
15
- import io
16
- import os
17
-
18
- import pyarrow as pa
19
- import pyarrow.parquet as pq
20
- from PIL import Image
21
-
22
- JPEG_QUALITY = 95
23
-
24
-
25
- def recompress_normal_bytes(b):
26
- if b is None:
27
- return None
28
- if isinstance(b, memoryview):
29
- b = b.tobytes()
30
- img = Image.open(io.BytesIO(b)).convert('RGB')
31
- out = io.BytesIO()
32
- img.save(out, format='JPEG', quality=JPEG_QUALITY)
33
- return out.getvalue()
34
-
35
-
36
- def process_file(src, out_dir):
37
- dst = os.path.join(out_dir, os.path.basename(src))
38
- pf = pq.ParquetFile(src)
39
- schema = pf.schema_arrow
40
- assert 'normal' in schema.names, f'no normal column in {src}'
41
- norm_idx = schema.names.index('normal')
42
-
43
- reported_fmt = False
44
- writer = pq.ParquetWriter(dst, schema, compression='snappy')
45
- try:
46
- for rg in range(pf.num_row_groups):
47
- table = pf.read_row_group(rg)
48
- normal_col = table.column('normal').to_pylist()
49
-
50
- if not reported_fmt:
51
- for b in normal_col:
52
- if b is not None:
53
- bb = b.tobytes() if isinstance(b, memoryview) else b
54
- fmt = Image.open(io.BytesIO(bb)).format
55
- print(f' source normal format = {fmt}', flush=True)
56
- reported_fmt = True
57
- break
58
-
59
- new_normal = [recompress_normal_bytes(b) for b in normal_col]
60
- new_col = pa.array(new_normal, type=schema.field('normal').type)
61
- table = table.set_column(norm_idx, schema.field('normal'), new_col)
62
- writer.write_table(table)
63
- finally:
64
- writer.close()
65
-
66
- s0, s1 = os.path.getsize(src), os.path.getsize(dst)
67
- print(f' {os.path.basename(src)}: {s0/1e9:.2f} GB -> {s1/1e9:.2f} GB '
68
- f'({100*(1-s1/s0):.0f}% smaller)', flush=True)
69
- return s0, s1
70
-
71
-
72
- def main():
73
- ap = argparse.ArgumentParser()
74
- ap.add_argument('--out_dir', required=True)
75
- ap.add_argument('files', nargs='+')
76
- args = ap.parse_args()
77
- os.makedirs(args.out_dir, exist_ok=True)
78
-
79
- tot0 = tot1 = 0
80
- for f in args.files:
81
- print(f'[{os.path.basename(f)}]', flush=True)
82
- s0, s1 = process_file(f, args.out_dir)
83
- tot0 += s0
84
- tot1 += s1
85
- print(f'TOTAL: {tot0/1e9:.2f} GB -> {tot1/1e9:.2f} GB '
86
- f'({100*(1-tot1/tot0):.0f}% smaller)', flush=True)
87
-
88
-
89
- if __name__ == '__main__':
90
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/run_full_rebuild.py DELETED
@@ -1,82 +0,0 @@
1
- """Slurm driver: rebuild a strided slice of the full dataset.
2
-
3
- Robust against memory leaks: each small chunk of files is processed by a FRESH
4
- subprocess (build_full_release.py --workers 1) that exits afterwards, so the OS
5
- reclaims all memory β€” no accumulation across the run. `concurrency` such
6
- subprocesses run at once. Resumable via process_one's skip.
7
- """
8
- import argparse
9
- import os
10
- import subprocess
11
- import sys
12
- from concurrent.futures import ThreadPoolExecutor, as_completed
13
-
14
- SRC = ('datasets/blip3o/parquet_rgb_caption_depth_normal_det_seg_grounding'
15
- '_canny_dino_global_clip448_imagebind_samseg_samedge_cocodet')
16
- OUT = 'datasets/blip3o/modus_full'
17
- WORKER = 'data/any2any_preprocess/build_full_release.py'
18
- CHUNK = 4
19
-
20
-
21
- def config_of(fname):
22
- if fname.startswith('sa_'):
23
- return 'sa1b'
24
- if fname.startswith('webdataset_JDB_'):
25
- return 'journeydb'
26
- if fname.startswith('webdataset_shard_'):
27
- return 'cc12m'
28
- return None
29
-
30
-
31
- def run_chunk(cfg, files):
32
- cmd = [sys.executable, WORKER, '--config', cfg,
33
- '--out_dir', os.path.join(OUT, cfg), '--workers', '1'] + files
34
- r = subprocess.run(cmd, capture_output=True, text=True)
35
- return cfg, len(files), r.returncode, (r.stderr or '')[-300:]
36
-
37
-
38
- def main():
39
- ap = argparse.ArgumentParser()
40
- ap.add_argument('--num-tasks', type=int, required=True)
41
- ap.add_argument('--task-id', type=int, required=True)
42
- ap.add_argument('--concurrency', type=int, default=48)
43
- args = ap.parse_args()
44
-
45
- for cfg in ('sa1b', 'journeydb', 'cc12m'):
46
- os.makedirs(os.path.join(OUT, cfg), exist_ok=True)
47
-
48
- items = []
49
- for fn in sorted(os.listdir(SRC)):
50
- if fn.endswith('.parquet') and config_of(fn):
51
- items.append((config_of(fn), os.path.join(SRC, fn)))
52
- mine = items[args.task_id::args.num_tasks]
53
-
54
- # group into per-config chunks
55
- chunks = []
56
- bycfg = {}
57
- for cfg, src in mine:
58
- bycfg.setdefault(cfg, []).append(src)
59
- for cfg, fs in bycfg.items():
60
- for i in range(0, len(fs), CHUNK):
61
- chunks.append((cfg, fs[i:i + CHUNK]))
62
-
63
- print(f'task {args.task_id}/{args.num_tasks}: {len(mine)} files in '
64
- f'{len(chunks)} chunks, concurrency={args.concurrency}', flush=True)
65
- done_files = fail = 0
66
- with ThreadPoolExecutor(max_workers=args.concurrency) as ex:
67
- futs = [ex.submit(run_chunk, cfg, fs) for cfg, fs in chunks]
68
- for i, fu in enumerate(as_completed(futs), 1):
69
- cfg, n, rc, err = fu.result()
70
- if rc == 0:
71
- done_files += n
72
- else:
73
- fail += n
74
- print(f' CHUNK FAIL {cfg} rc={rc}: {err}', flush=True)
75
- if i % 20 == 0:
76
- print(f'[{i}/{len(chunks)} chunks] files_ok={done_files} '
77
- f'fail={fail}', flush=True)
78
- print(f'TASK COMPLETE: {done_files} files ok, {fail} failed', flush=True)
79
-
80
-
81
- if __name__ == '__main__':
82
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/upload_full.py DELETED
@@ -1,26 +0,0 @@
1
- """Resumable upload of the full-release folder to the HF dataset repo.
2
-
3
- Uses upload_large_folder (xet, multi-commit, resumable). Uploads folder_path
4
- contents to the repo root, preserving the sa1b/ journeydb/ cc12m/ structure.
5
- Re-running skips already-uploaded files.
6
-
7
- Usage:
8
- HF_TOKEN_FILE=/users/mye/.hf_token python upload_full.py <repo_id> <folder> [allow_glob]
9
- """
10
- import os
11
- import sys
12
-
13
- from huggingface_hub import HfApi
14
-
15
- repo_id = sys.argv[1]
16
- folder = sys.argv[2]
17
- allow = list(sys.argv[3:]) # zero or more allow_patterns
18
- token = open(os.environ['HF_TOKEN_FILE']).read().strip()
19
-
20
- api = HfApi(token=token)
21
- api.create_repo(repo_id, repo_type='dataset', exist_ok=True)
22
- kw = dict(repo_id=repo_id, repo_type='dataset', folder_path=folder, print_report=True)
23
- if allow:
24
- kw['allow_patterns'] = allow
25
- api.upload_large_folder(**kw)
26
- print(f'UPLOAD PASS DONE: {folder} {allow} -> {repo_id}', flush=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/any2any_preprocess/vqa_convert_parquet_to_jsonl.py DELETED
@@ -1,312 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Convert LLaVA OneVision parquet files to JSONL format matching llava_ov_si.jsonl
4
-
5
- Single-folder usage (backwards compatible):
6
- python vqa_convert_parquet_to_jsonl.py <parquet_dir> <output_jsonl> --image-dir <image_dir> [--max-rows N]
7
-
8
- Multi-folder usage (process all immediate subfolders of <parquet_root>):
9
- python vqa_convert_parquet_to_jsonl.py <parquet_root> dummy.jsonl --process-all --output-root <output_root> [--max-rows N]
10
-
11
- Notes:
12
- - In multi-folder mode, outputs are written per subfolder under <output_root>/<subfolder>/{images,label/labels.jsonl}
13
- - The script prints the sorted folder index and name, and a global image counter every N (default 1000)
14
- """
15
-
16
- import argparse
17
- import json
18
- import os
19
- import glob
20
- from pathlib import Path
21
- import pandas as pd
22
- from PIL import Image
23
- import io
24
- from tqdm import tqdm
25
- def _save_pil_image_as_jpeg(pil_image: Image.Image, image_path: str):
26
- """
27
- Ensure the PIL image is compatible with JPEG (no alpha), then save.
28
- """
29
- try:
30
- # Convert to RGB if image has alpha channel or unsupported mode for JPEG
31
- if pil_image.mode not in ("RGB", "L"):
32
- pil_image = pil_image.convert("RGB")
33
- pil_image.save(image_path, format="JPEG")
34
- except Exception:
35
- # As a fallback, force convert to RGB and retry
36
- pil_image = pil_image.convert("RGB")
37
- pil_image.save(image_path, format="JPEG")
38
-
39
-
40
-
41
- def convert_parquet_to_jsonl(
42
- parquet_dir: str,
43
- output_jsonl: str,
44
- image_dir: str = None,
45
- max_rows: int = None,
46
- sample_id_offset: int = 0,
47
- progress: dict | None = None,
48
- ):
49
- """
50
- Convert parquet files to JSONL format compatible with llava_ov_si.jsonl
51
-
52
- Args:
53
- parquet_dir: Directory containing parquet files (can be subdirectory)
54
- output_jsonl: Output JSONL file path
55
- image_dir: Directory to save images (optional, if None saves with dataset)
56
- max_rows: Maximum number of rows to process (for testing)
57
- sample_id_offset: Starting ID offset (for merging multiple datasets)
58
- progress: Optional dict to track global progress across folders. Expected keys:
59
- {"count": int, "next_milestone": int, "interval": int}
60
- """
61
-
62
- # Find all parquet files in the directory
63
- parquet_pattern = os.path.join(parquet_dir, "train-*.parquet")
64
- parquet_files = sorted(glob.glob(parquet_pattern))
65
-
66
- if not parquet_files:
67
- print(f"Error: No parquet files found in {parquet_dir}")
68
- return
69
-
70
- print(f"Found {len(parquet_files)} parquet file(s)")
71
-
72
- # Create image directory if specified
73
- if image_dir:
74
- os.makedirs(image_dir, exist_ok=True)
75
-
76
- all_entries = []
77
- current_id = sample_id_offset
78
-
79
- for parquet_file in parquet_files:
80
- print(f"\nProcessing {parquet_file}...")
81
- df = pd.read_parquet(parquet_file)
82
-
83
- for idx, row in tqdm(df.iterrows(), total=len(df)):
84
- if max_rows and len(all_entries) >= max_rows:
85
- break
86
-
87
- # Extract conversations - handle numpy array conversion
88
- conversations = row["conversations"]
89
- if hasattr(conversations, "tolist"):
90
- conversations = conversations.tolist()
91
-
92
- # Handle image - extract from binary data and save
93
- image_filename = None
94
- if row["image"] is not None:
95
- try:
96
- # The image data is stored as binary bytes
97
- image_data = row["image"]
98
-
99
- # Generate filename from ID
100
- image_filename = f"{current_id}.jpg"
101
-
102
- if image_dir:
103
- image_path = os.path.join(image_dir, image_filename)
104
- else:
105
- # Save in same directory as parquet file
106
- image_dir_local = os.path.dirname(parquet_file)
107
- image_path = os.path.join(
108
- image_dir_local, image_filename
109
- )
110
-
111
- # Convert image data to PIL Image and save
112
- if isinstance(image_data, dict):
113
- # Image is stored as a dict with 'bytes' key containing image data
114
- if (
115
- "bytes" in image_data
116
- and image_data["bytes"] is not None
117
- ):
118
- image_bytes = image_data["bytes"]
119
- pil_image = Image.open(io.BytesIO(image_bytes))
120
- _save_pil_image_as_jpeg(pil_image, image_path)
121
- else:
122
- print(
123
- f"Warning: Dict image data doesn't contain 'bytes' key for row {idx}"
124
- )
125
- continue
126
- elif isinstance(image_data, bytes):
127
- # Try to open as PIL Image from bytes
128
- pil_image = Image.open(io.BytesIO(image_data))
129
- _save_pil_image_as_jpeg(pil_image, image_path)
130
- elif isinstance(image_data, str):
131
- # If it's a string, try to decode it
132
- image_bytes = image_data.encode(
133
- "latin-1"
134
- ) # Convert string to bytes
135
- pil_image = Image.open(io.BytesIO(image_bytes))
136
- _save_pil_image_as_jpeg(pil_image, image_path)
137
- else:
138
- print(
139
- f"Warning: Unknown image data type {type(image_data)} for row {idx}"
140
- )
141
- continue
142
-
143
- except Exception as e:
144
- print(f"Error processing image for row {idx}: {e}")
145
- continue
146
-
147
- # Create JSONL entry matching llava_ov_si.jsonl format
148
- entry = {
149
- "id": current_id,
150
- "image": image_filename,
151
- "conversations": conversations,
152
- }
153
-
154
- all_entries.append(entry)
155
- current_id += 1
156
-
157
- # Update global progress if provided
158
- if progress is not None:
159
- progress["count"] += 1
160
- if progress["count"] >= progress.get("next_milestone", 1000):
161
- print(
162
- f"[global] Processed {progress['count']} images total"
163
- )
164
- interval = progress.get("interval", 1000)
165
- progress["next_milestone"] = progress["count"] + interval
166
-
167
- if max_rows and len(all_entries) >= max_rows:
168
- break
169
-
170
- if max_rows and len(all_entries) >= max_rows:
171
- break
172
-
173
- # Write JSONL file
174
- print(f"\nWriting {len(all_entries)} entries to {output_jsonl}...")
175
- with open(output_jsonl, "w", encoding="utf-8") as f:
176
- for entry in all_entries:
177
- f.write(json.dumps(entry, ensure_ascii=False) + "\n")
178
-
179
- print(f"βœ“ Converted {len(all_entries)} samples to {output_jsonl}")
180
- if image_dir:
181
- print(f"βœ“ Images saved to {image_dir}")
182
-
183
-
184
- def main():
185
- parser = argparse.ArgumentParser(
186
- description="Convert LLaVA OneVision parquet files to JSONL format"
187
- )
188
- parser.add_argument(
189
- "parquet_dir",
190
- help="Directory containing parquet files, or a root directory with subfolders",
191
- )
192
- parser.add_argument("output_jsonl", help="Output JSONL file path (single-folder mode)")
193
- parser.add_argument(
194
- "--image-dir",
195
- default=None,
196
- help="Directory to save images (default: same as parquet directory)",
197
- )
198
- parser.add_argument(
199
- "--max-rows",
200
- type=int,
201
- default=None,
202
- help="Maximum number of rows to process (for testing)",
203
- )
204
- parser.add_argument(
205
- "--id-offset",
206
- type=int,
207
- default=0,
208
- help="Starting ID offset (for merging datasets)",
209
- )
210
- parser.add_argument(
211
- "--process-all",
212
- action="store_true",
213
- help="Process all immediate subfolders in parquet_dir and write per-folder outputs under --output-root",
214
- )
215
- parser.add_argument(
216
- "--output-root",
217
- default=None,
218
- help="Base output directory when using --process-all",
219
- )
220
- parser.add_argument(
221
- "--print-every",
222
- type=int,
223
- default=1000,
224
- help="Global image progress interval for multi-folder mode",
225
- )
226
- parser.add_argument(
227
- "--start-index",
228
- type=int,
229
- default=1,
230
- help="1-based index of subfolder to start processing from (resume)",
231
- )
232
-
233
- args = parser.parse_args()
234
-
235
- if args.process_all:
236
- if not args.output_root:
237
- raise SystemExit("--output-root is required when using --process-all")
238
-
239
- # Discover immediate subfolders and sort
240
- if not os.path.isdir(args.parquet_dir):
241
- raise SystemExit(f"Input root '{args.parquet_dir}' is not a directory")
242
-
243
- subfolders = [
244
- d
245
- for d in sorted(os.listdir(args.parquet_dir))
246
- if os.path.isdir(os.path.join(args.parquet_dir, d))
247
- ]
248
-
249
- # Filter to only those having parquet files
250
- filtered_subfolders = []
251
- for d in subfolders:
252
- pattern = os.path.join(args.parquet_dir, d, "train-*.parquet")
253
- if glob.glob(pattern):
254
- filtered_subfolders.append(d)
255
- subfolders = filtered_subfolders
256
-
257
- total = len(subfolders)
258
- print(f"Found {total} subfolder(s) to process under {args.parquet_dir}")
259
-
260
- # Compute starting index (1-based) and slice subfolders for resume
261
- start_index = max(1, int(args.start_index)) if args.start_index else 1
262
- if start_index > total:
263
- print(f"Start index {start_index} is beyond total {total}; nothing to do.")
264
- return
265
- subfolders_to_process = subfolders[start_index - 1 :]
266
-
267
- # Prepare global progress
268
- progress = {
269
- "count": 0,
270
- "next_milestone": args.print_every,
271
- "interval": args.print_every,
272
- }
273
- # When resuming at folder index 41, hardcode the global counter baseline
274
- if start_index == 41:
275
- progress["count"] = 1380000
276
- progress["next_milestone"] = progress["count"] + progress["interval"]
277
-
278
- for idx, sub in enumerate(subfolders_to_process, start=start_index):
279
- print(f"\n[{idx}/{total}] Processing folder: {sub}")
280
- sub_in = os.path.join(args.parquet_dir, sub)
281
- sub_out_images = os.path.join(args.output_root, sub, "images")
282
- sub_out_label_dir = os.path.join(args.output_root, sub, "label")
283
- os.makedirs(sub_out_images, exist_ok=True)
284
- os.makedirs(sub_out_label_dir, exist_ok=True)
285
- sub_out_jsonl = os.path.join(sub_out_label_dir, "labels.jsonl")
286
-
287
- convert_parquet_to_jsonl(
288
- parquet_dir=sub_in,
289
- output_jsonl=sub_out_jsonl,
290
- image_dir=sub_out_images,
291
- max_rows=args.max_rows,
292
- sample_id_offset=args.id_offset,
293
- progress=progress,
294
- )
295
- else:
296
- convert_parquet_to_jsonl(
297
- parquet_dir=args.parquet_dir,
298
- output_jsonl=args.output_jsonl,
299
- image_dir=args.image_dir,
300
- max_rows=args.max_rows,
301
- sample_id_offset=args.id_offset,
302
- )
303
-
304
-
305
- if __name__ == "__main__":
306
- main()
307
-
308
-
309
- # python data/any2any_preprocess/vqa_convert_parquet_to_jsonl.py datasets/llava_onevision/ai2d\(cauldron\,llava_format\)/ datasets/llava_onevision_vqa/label/ai2d_converted.jsonl --image-dir datasets/llava_onevision_vqa/images
310
-
311
- # python data/any2any_preprocess/vqa_convert_parquet_to_jsonl.py datasets/llava_onevision dummy.jsonl --process-all --output-root datasets/llava_onevision_vqa
312
- # python data/any2any_preprocess/vqa_convert_parquet_to_jsonl.py datasets/llava_onevision dummy.jsonl --process-all --output-root datasets/llava_onevision_vqa --start-index 41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/bundled_parquet_info/README.md DELETED
@@ -1,43 +0,0 @@
1
- # bundled_parquet_info β€” repo-tracked parquet metadata (auto-fallback)
2
-
3
- Repo-tracked copies of the parquet-info JSONs used by BAGEL training.
4
- **The code reads this directory automatically**: `data/dataset_base.py`
5
- falls back to `data/bundled_parquet_info/<basename>` whenever the
6
- configured live path (`./datasets/blip3o/parquet_info/…`, a symlink chain
7
- that only exists on the original cluster) is missing. When the live path
8
- exists it still wins β€” behavior on the original cluster is unchanged.
9
-
10
- ## Files
11
-
12
- | File | Files listed | Rows | Used by |
13
- |---|---|---|---|
14
- | `…_grounding_canny_dino_global_clip448_imagebind.json` | 2891 (1000 `sa_*` + 1891 `webdataset_*`) | 29.2M | 13mod: all target groups except grounding (any2rgb / depth / normal / caption / canny / dino / dinolocal / clip / imagebind / imagebindlocal) |
15
- | `…_grounding2_canny_dino_global_clip448_imagebind.json` | 1000 (`sa_*` only) | 11.1M | 13mod: `unified_any2grounding` only |
16
- | `…_clip448_imagebind_samseg_samedge_cocodet.json` | β€” | β€” | 16mod runs (adds cocodet / samseg / samedge) |
17
- | `llava_onevision_vqa.json` | β€” | β€” | VLM-SFT (llava-onevision) |
18
-
19
- **Why grounding has two JSONs:** grounding annotations (GLaMM phrase+bbox,
20
- matched by UID) only exist for the SA-1B portion of the parquet set. The
21
- `webdataset_*` shards have no `grounding` column at all (verified via
22
- parquet footer schema). So when grounding is the generation TARGET, the
23
- loader must restrict itself to the `sa_*` files β€” that restricted file
24
- list is the `grounding2` JSON. Both JSONs point into the SAME `data_dir`.
25
-
26
- ## Setting up on a new machine
27
-
28
- 1. Get the parquet data itself (β‰ˆ several TB, NOT in this repo):
29
- `parquet_rgb_caption_depth_normal_det_seg_grounding_canny_dino_global_clip448_imagebind/`
30
- (2891 parquet files).
31
- 2. Place (or symlink) it at
32
- `datasets/blip3o/parquet_rgb_caption_depth_normal_det_seg_grounding_canny_dino_global_clip448_imagebind/`
33
- relative to the repo root.
34
- 3. parquet-info JSONs: **no action needed** β€” the loader automatically
35
- falls back to this directory when the live
36
- `datasets/blip3o/parquet_info/` path is absent
37
- (`data/dataset_base.py`, "Fall back to the copy bundled in the repo").
38
- 4. Note the JSON keys are relative paths
39
- (`./datasets/blip3o/parquet_.../sa_000000.parquet`), so always launch
40
- training from the repo root.
41
-
42
- If the parquet set ever changes, regenerate with
43
- `data/any2any_preprocess/generate_parquet_json.py` and refresh these copies.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/bundled_parquet_info/blip3o_rgb_caption_depth_normal_det_seg_grounding2_canny_dino_global_clip448_imagebind.json DELETED
The diff for this file is too large to render. See raw diff
 
data/bundled_parquet_info/blip3o_rgb_caption_depth_normal_det_seg_grounding_canny_dino_global_clip448_imagebind.json DELETED
The diff for this file is too large to render. See raw diff
 
data/bundled_parquet_info/blip3o_rgb_caption_depth_normal_det_seg_grounding_canny_dino_global_clip448_imagebind_samseg_samedge_cocodet.json DELETED
The diff for this file is too large to render. See raw diff