"""MMRB task dataloader.""" from __future__ import annotations import glob import json import os import re from typing import Any from reflact.datasets.base import SplitDataLoader # ── Raw data loading utilities (for preprocessing / standalone eval) ───── def _load_json(path: str) -> Any: with open(path, encoding="utf-8") as f: return json.load(f) def _iter_data_files(data_path: str) -> list[str]: if not data_path: return [] if os.path.isfile(data_path): return [data_path] if os.path.isdir(data_path): nested = glob.glob(os.path.join(data_path, "**", "*_human.json"), recursive=True) flat = glob.glob(os.path.join(data_path, "*_human.json")) return sorted(set(nested + flat)) return [] def _normalize_space(text: str) -> str: return re.sub(r"\s+", " ", str(text or "").strip()) def _normalize_item(item: dict, row_idx: int, source_path: str) -> dict | None: question = _normalize_space(item.get("question") or "") answer = _normalize_space(item.get("answer") or "") raw_image_paths = item.get("image_paths") or [] if not question or not answer or not isinstance(raw_image_paths, list) or not raw_image_paths: return None base_dir = os.path.dirname(source_path) image_paths: list[str] = [] for raw_path in raw_image_paths: rel = str(raw_path or "").strip() if not rel: continue abs_path = rel if os.path.isabs(rel) else os.path.abspath(os.path.join(base_dir, rel)) if os.path.exists(abs_path): image_paths.append(abs_path) if not image_paths: return None options_raw = item.get("options") or [] options = [_normalize_space(opt) for opt in options_raw if _normalize_space(opt)] source = _normalize_space(item.get("source") or "unknown") subtask = _normalize_space(item.get("subtask") or "unknown") item_index = item.get("index", row_idx) item_id = f"{source}:{subtask}:{item_index}" return { "id": item_id, "source": source, "subtask": subtask, "task_type": subtask, "question": question, "answer": answer, "options": options, "is_choice": bool(options), "image_paths": image_paths, "reasoning_steps": item.get("reasoning_steps") or [], "annotation_time": item.get("annotation_time"), "source_path": os.path.abspath(source_path), } def load_items(data_path: str) -> list[dict]: """Load and normalise MMRB items from JSON files.""" files = _iter_data_files(data_path) if not files: raise ValueError( "MMRB requires data_path to be a *_human.json file or a directory " "containing extracted MMRB subtask folders." ) items: list[dict] = [] for path in files: raw = _load_json(path) if not isinstance(raw, list): raise ValueError(f"Expected JSON array in {path}, got {type(raw).__name__}") for row_idx, item in enumerate(raw): if not isinstance(item, dict): continue norm = _normalize_item(item, row_idx=row_idx, source_path=path) if norm is not None: items.append(norm) if not items: raise ValueError(f"No valid MMRB items loaded from {data_path}") return items # ── Dataloader ─────────────────────────────────────────────────────────── class MMRBDataLoader(SplitDataLoader): """MMRB dataloader.""" def __init__( self, split_dir: str = "", data_path: str = "", split_mode: str = "ratio", split_ratio: str = "2:1:7", split_seed: int = 42, split_output_dir: str = "", seed: int = 42, limit: int = 0, **kwargs, ) -> None: super().__init__( split_dir=split_dir, data_path=data_path, split_mode=split_mode, split_ratio=split_ratio, split_seed=split_seed, split_output_dir=split_output_dir, seed=seed, limit=limit, ) self._task_types: list[str] = [] def load_raw_items(self, data_path: str) -> list[dict]: return load_items(data_path) def setup(self, cfg: dict) -> None: super().setup(cfg) all_items = self.train_items + self.val_items + self.test_items task_types = { item.get("subtask") or item.get("task_type") or "unknown" for item in all_items } self._task_types = sorted(task_types) def get_task_types(self) -> list[str]: return list(self._task_types)