Deploy MODUS 3-tab any-to-any demo (ZeroGPU)
Browse filesThis view is limited to 50 files because it contains too many changes. Β See raw diff
- .gitattributes +0 -35
- README.md +25 -5
- app.py +116 -0
- conf/modalities/bagel_stage3_exact3cond.yaml +191 -0
- conf/modalities/hunyuan_image_3.yaml +31 -0
- conf/modalities/instruction.yaml +201 -0
- conf/modalities/instruction_10modality_stage2.yaml +142 -0
- conf/modalities/instruction_16mod_instr.yaml +259 -0
- conf/modalities/instruction_16mod_stage1.yaml +243 -0
- conf/modalities/instruction_16mod_stage1_rgbcond.yaml +266 -0
- conf/modalities/instruction_16mod_stage2.yaml +272 -0
- conf/modalities/instruction_2d_only_stage1.yaml +65 -0
- conf/modalities/instruction_9modality_stage1.yaml +114 -0
- conf/modalities/instruction_9modality_stage3.yaml +115 -0
- conf/modalities/instruction_hunyuan_16mod_stage1.yaml +257 -0
- conf/modalities/instruction_hunyuan_16mod_stage2.yaml +250 -0
- conf/modalities/instruction_stage2.yaml +204 -0
- conf/modalities/instruction_t2i_only_stage1.yaml +27 -0
- conf/modalities/legacy.yaml +38 -0
- conf/modalities/rebuttal_rgb2target.yaml +116 -0
- conf/modalities/stage1_oversample_bbox2rgb_dinolocal2rgb.yaml +203 -0
- core/__init__.py +11 -0
- core/modality.py +394 -0
- core/model_registry.py +47 -0
- core/tokenizer_utils.py +257 -0
- data/__init__.py +2 -0
- data/any2any_preprocess/_build_preview_and_montage.py +52 -0
- data/any2any_preprocess/_cast_preview_images.py +34 -0
- data/any2any_preprocess/_sb_full_rebuild.sh +18 -0
- data/any2any_preprocess/_sb_recompress_normal_sample.sh +24 -0
- data/any2any_preprocess/_sb_upload.sh +21 -0
- data/any2any_preprocess/_verify_normal_q95.py +34 -0
- data/any2any_preprocess/build_full_release.py +108 -0
- data/any2any_preprocess/check_vqa.py +188 -0
- data/any2any_preprocess/generate_parquest_grounding_canny_dino_global.py +205 -0
- data/any2any_preprocess/generate_parquet.py +229 -0
- data/any2any_preprocess/generate_parquet_clip448_imagebind.py +318 -0
- data/any2any_preprocess/generate_parquet_grounding.py +583 -0
- data/any2any_preprocess/generate_parquet_grounding_canny_dino.py +627 -0
- data/any2any_preprocess/generate_parquet_json.py +69 -0
- data/any2any_preprocess/generate_parquet_vlm_sft.py +278 -0
- data/any2any_preprocess/hf_upload.py +26 -0
- data/any2any_preprocess/parquet_visualize.py +408 -0
- data/any2any_preprocess/process_grounding.py +346 -0
- data/any2any_preprocess/recompress_normal_jpeg.py +90 -0
- data/any2any_preprocess/run_full_rebuild.py +82 -0
- data/any2any_preprocess/upload_full.py +26 -0
- data/any2any_preprocess/vqa_convert_parquet_to_jsonl.py +312 -0
- data/bundled_parquet_info/README.md +43 -0
- data/bundled_parquet_info/blip3o_rgb_caption_depth_normal_det_seg_grounding2_canny_dino_global_clip448_imagebind.json +0 -0
.gitattributes
DELETED
|
@@ -1,35 +0,0 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,13 +1,33 @@
|
|
| 1 |
---
|
| 2 |
title: MODUS
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
python_version: '3.13'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: MODUS
|
| 3 |
+
emoji: π¨
|
| 4 |
+
colorFrom: indigo
|
| 5 |
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 4.44.1
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
short_description: MODUS any-to-any multimodal demo (16 aligned modalities)
|
| 11 |
---
|
| 12 |
|
| 13 |
+
# MODUS β any-to-any multimodal demo
|
| 14 |
+
|
| 15 |
+
Three-tab Gradio demo for the MODUS 16-modality any-to-any model:
|
| 16 |
+
|
| 17 |
+
1. **Any-to-Any** β one condition modality (image or caption) β any set of target
|
| 18 |
+
modalities, shown in a gallery (4M-style).
|
| 19 |
+
2. **Chained** β condition β intermediate (bridge) β target, showing both.
|
| 20 |
+
3. **Representation Analysis** β RGBβDepth/Normal conditioned on
|
| 21 |
+
{ViT only, VAE only, ViT+VAE}, side by side.
|
| 22 |
+
|
| 23 |
+
## Setup (Space Settings β Variables and secrets)
|
| 24 |
+
- **Hardware:** ZeroGPU.
|
| 25 |
+
- **`HF_TOKEN`** (secret): a *read* token with access to the gated weights repo
|
| 26 |
+
`mqye/modus-16mod-stage3`.
|
| 27 |
+
- Optional **`MODUS_WEIGHTS_REPO`**: defaults to `mqye/modus-16mod-stage3`.
|
| 28 |
+
|
| 29 |
+
The weights (bf16, ~30GB) + VAE + config + tokenizer are pulled once at startup
|
| 30 |
+
via `snapshot_download`; the model is built on CPU (~5min) and moved to the
|
| 31 |
+
ZeroGPU GPU per request. Inference config is baked in: modality config
|
| 32 |
+
`conf/modalities/instruction_16mod_stage2.yaml`, `use_instruction` off for the
|
| 33 |
+
generic modalities (seg/det/grounding keep their own instructions).
|
app.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""MODUS any-to-any demo β HuggingFace Space entrypoint (ZeroGPU).
|
| 3 |
+
|
| 4 |
+
Thin ZeroGPU wrapper over the existing 3-tab demo backend (``demo_modus.py`` +
|
| 5 |
+
``demo_my/*``). The model is loaded ONCE at startup on CPU (no GPU, no time
|
| 6 |
+
limit); each inference call moves it to the ZeroGPU-provided GPU via the
|
| 7 |
+
``@spaces.GPU`` decorator (moving ~30GB bf16 over PCIe is seconds, whereas the
|
| 8 |
+
build+weight-load is ~5min and must NOT happen inside the GPU-time window).
|
| 9 |
+
|
| 10 |
+
Space setup (Settings -> Variables and secrets):
|
| 11 |
+
HF_TOKEN read token for the gated weights repo (below)
|
| 12 |
+
Optional env (have sane defaults):
|
| 13 |
+
MODUS_WEIGHTS_REPO gated HF model repo with the bf16 weights + config + VAE
|
| 14 |
+
"""
|
| 15 |
+
import os
|
| 16 |
+
import sys
|
| 17 |
+
|
| 18 |
+
try:
|
| 19 |
+
import torch
|
| 20 |
+
print(f"[app] torch={torch.__version__} python={sys.version.split()[0]}", flush=True)
|
| 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")
|
| 30 |
+
os.environ.setdefault("MODUS_DEMO_USE_EMA", "0")
|
| 31 |
+
|
| 32 |
+
WEIGHTS_REPO = os.environ.get("MODUS_WEIGHTS_REPO", "mqye/modus-16mod-stage3")
|
| 33 |
+
|
| 34 |
+
# ββ Pull the gated weights once (model.safetensors + ae + config + tokenizer) ββββ
|
| 35 |
+
from huggingface_hub import snapshot_download # noqa: E402
|
| 36 |
+
|
| 37 |
+
_weights_dir = snapshot_download(
|
| 38 |
+
repo_id=WEIGHTS_REPO,
|
| 39 |
+
repo_type="model",
|
| 40 |
+
token=os.environ.get("HF_TOKEN"),
|
| 41 |
+
)
|
| 42 |
+
# The snapshot dir holds BOTH the checkpoint (model.safetensors) and the base
|
| 43 |
+
# config/tokenizer/VAE, so it serves as CHECKPOINT_PATH and MODEL_PATH at once.
|
| 44 |
+
os.environ["MODUS_DEMO_CHECKPOINT"] = _weights_dir
|
| 45 |
+
os.environ["BAGEL_MODEL_PATH"] = _weights_dir
|
| 46 |
+
print(f"[app] weights ready at {_weights_dir}", flush=True)
|
| 47 |
+
|
| 48 |
+
import spaces # noqa: E402 (ZeroGPU)
|
| 49 |
+
|
| 50 |
+
# diffusers 0.20 (imported by the fourm VQVAE feature tokenizers) does
|
| 51 |
+
# `from huggingface_hub import cached_download`, which was removed in hub>=0.26
|
| 52 |
+
# (the Space has 0.36). Shim it to hf_hub_download so `import diffusers` succeeds
|
| 53 |
+
# and the dino/clip/imagebind tokenizers can load.
|
| 54 |
+
import huggingface_hub as _hh # noqa: E402
|
| 55 |
+
if not hasattr(_hh, "cached_download"):
|
| 56 |
+
_hh.cached_download = _hh.hf_hub_download
|
| 57 |
+
|
| 58 |
+
# Importing demo_modus builds the UI + backend and reads the env set above.
|
| 59 |
+
import demo_modus # noqa: E402
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
# ββ ZeroGPU: wrap the two inference entry points so each call gets a GPU slice βββ
|
| 63 |
+
# tab1/tab2 call demo_modus.run_task; tab3 calls demo_modus.run_representation_task.
|
| 64 |
+
# The tab handlers resolve these by module-global name at call time, so replacing
|
| 65 |
+
# the module attribute makes them use the GPU-wrapped versions.
|
| 66 |
+
# duration = max GPU-seconds ZeroGPU reserves per call. The Chained tab runs TWO
|
| 67 |
+
# generations inside a single run_task call (~60s+), so 60 is too tight ("GPU task
|
| 68 |
+
# aborted"); 120 covers chained + cold-start model materialisation. (With PRO the
|
| 69 |
+
# reservation size is a non-issue quota-wise.)
|
| 70 |
+
_GPU_DURATION = int(os.environ.get("MODUS_GPU_DURATION", "120"))
|
| 71 |
+
demo_modus.run_task = spaces.GPU(duration=_GPU_DURATION)(demo_modus.run_task)
|
| 72 |
+
# Tab3 runs THREE generations (vit/vae/both) per click. Wrapping run_representation_task
|
| 73 |
+
# would take three separate GPU acquisitions in one handler β the 3rd hits "Expired
|
| 74 |
+
# ZeroGPU proxy token". Instead wrap the whole tab3 HANDLER so all three run inside a
|
| 75 |
+
# single GPU session (one token, one reservation big enough for 3 gens).
|
| 76 |
+
demo_modus.tab3_generate = spaces.GPU(duration=180)(demo_modus.tab3_generate)
|
| 77 |
+
|
| 78 |
+
# Load the model ONCE at startup. The heavy CPU work (build arch + read 30GB
|
| 79 |
+
# weights, ~5min) runs here, OUTSIDE any @spaces.GPU window; the model's .cuda()
|
| 80 |
+
# moves are deferred by `spaces` and materialise on the first GPU call.
|
| 81 |
+
try:
|
| 82 |
+
demo_modus.HOLDER.ensure_loaded()
|
| 83 |
+
print("[app] model loaded (CPU) at startup", flush=True)
|
| 84 |
+
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).
|
| 91 |
+
# Patch gradio_client's schema helpers to tolerate bool schemas.
|
| 92 |
+
try:
|
| 93 |
+
import gradio_client.utils as _gcu
|
| 94 |
+
|
| 95 |
+
_orig_j2p = _gcu._json_schema_to_python_type
|
| 96 |
+
|
| 97 |
+
def _safe_j2p(schema, defs=None):
|
| 98 |
+
if isinstance(schema, bool):
|
| 99 |
+
return "Any"
|
| 100 |
+
return _orig_j2p(schema, defs)
|
| 101 |
+
|
| 102 |
+
_gcu._json_schema_to_python_type = _safe_j2p
|
| 103 |
+
|
| 104 |
+
_orig_get_type = _gcu.get_type
|
| 105 |
+
|
| 106 |
+
def _safe_get_type(schema):
|
| 107 |
+
if isinstance(schema, bool):
|
| 108 |
+
return "Any"
|
| 109 |
+
return _orig_get_type(schema)
|
| 110 |
+
|
| 111 |
+
_gcu.get_type = _safe_get_type
|
| 112 |
+
except Exception as _e:
|
| 113 |
+
print(f"[app] gradio_client schema patch skipped: {_e}", flush=True)
|
| 114 |
+
|
| 115 |
+
demo = demo_modus.build_ui()
|
| 116 |
+
demo.queue().launch(server_name="0.0.0.0", server_port=7860, show_api=False)
|
conf/modalities/bagel_stage3_exact3cond.yaml
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 Bytedance Ltd. and/or its affiliates.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
data/any2any_preprocess/_build_preview_and_montage.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,583 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,627 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|