Instructions to use MingzhenL/vessa-2d-ssl-bundle with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- sam2
How to use MingzhenL/vessa-2d-ssl-bundle with sam2:
# Use SAM2 with images import torch from sam2.sam2_image_predictor import SAM2ImagePredictor predictor = SAM2ImagePredictor.from_pretrained("MingzhenL/vessa-2d-ssl-bundle") with torch.inference_mode(), torch.autocast("cuda", dtype=torch.bfloat16): predictor.set_image(<your_image>) masks, _, _ = predictor.predict(<input_prompts>)# Use SAM2 with videos import torch from sam2.sam2_video_predictor import SAM2VideoPredictor predictor = SAM2VideoPredictor.from_pretrained("MingzhenL/vessa-2d-ssl-bundle") with torch.inference_mode(), torch.autocast("cuda", dtype=torch.bfloat16): state = predictor.init_state(<your_video>) # add new prompts and instantly get the output on the same frame frame_idx, object_ids, masks = predictor.add_new_points(state, <your_prompts>) # propagate the prompts to get masklets throughout the video for frame_idx, object_ids, masks in predictor.propagate_in_video(state): ... - Notebooks
- Google Colab
- Kaggle
Download code/VESSA_data_preprocess/end_to_end_preprocessing.py from MingzhenL/vessa-2d-ssl-bundle: direct link, hf CLI and curl.
- Browser
- Download file 34.6 kB
-
https://huggingface.co/MingzhenL/vessa-2d-ssl-bundle/resolve/main/code/VESSA_data_preprocess/end_to_end_preprocessing.py
- Command line
-
hf download hf://MingzhenL/vessa-2d-ssl-bundle/code/VESSA_data_preprocess/end_to_end_preprocessing.py
-
curl -L -o end_to_end_preprocessing.py https://huggingface.co/MingzhenL/vessa-2d-ssl-bundle/resolve/main/code/VESSA_data_preprocess/end_to_end_preprocessing.py
34.6 kB
| #!/usr/bin/env python3 | |
| """ | |
| End-to-End Medical Image Dataset Preprocessing Pipeline | |
| This script combines three steps into one: | |
| 1. Convert raw BiomedParse format to HF-compatible format | |
| 2. Pack dataset and extract template features | |
| 3. Precompute template matches | |
| Usage: | |
| python end_to_end_preprocessing.py \ | |
| --dataset_name ACDC \ | |
| --template_percent 5 \ | |
| --raw_data_dir /projects/p32685/medical_data/ACDC \ | |
| --output_base_dir /projects/p32685/RL_dataset \ | |
| --device cuda:0 | |
| Output: | |
| /projects/p32685/RL_dataset/acdc/ | |
| /projects/p32685/RL_dataset/acdc_5percent_processed/ | |
| ├── backup_entries/ (with precomputed template matches) | |
| ├── hf_dataset/ | |
| └── template/ | |
| """ | |
| import os | |
| import sys | |
| import json | |
| import argparse | |
| import subprocess | |
| import random | |
| import glob | |
| from datetime import datetime | |
| from pathlib import Path | |
| from typing import List, Dict, Any, Tuple | |
| # Add LISA-main to path | |
| SCRIPT_DIR = Path(__file__).resolve().parent | |
| sys.path.insert(0, str(SCRIPT_DIR)) | |
| # Add VESSA to path for TemplateManager | |
| # Try local template_manager first, then fallback to VESSA path if exists | |
| VESSA_PATH = "/home/wfm8324/VESSA/train_vessa" | |
| if os.path.exists(VESSA_PATH) and VESSA_PATH not in sys.path: | |
| sys.path.insert(0, VESSA_PATH) | |
| # template_manager.py is in the same directory, so it will be found automatically | |
| import numpy as np | |
| from PIL import Image | |
| # Ensure logs directory exists | |
| os.makedirs(os.path.join(SCRIPT_DIR, "logs"), exist_ok=True) | |
| # Known dataset configurations | |
| DATASET_TYPES = { | |
| "ACDC": "ACDC", | |
| "CAMUS": "CAMUS", | |
| "MMs": "ACDC", # Uses ACDC config | |
| "LGG": "LGG", | |
| "CXR": "CXR", | |
| "BreastUS": "BreastUS", | |
| "BUS": "BUS", | |
| "kits23": "kits23", | |
| "abdomenct": "abdomenct", | |
| "lungx": "lungx", | |
| "perfusion": "perfusion", | |
| "amos22_CT": "AMOS22_CT", | |
| "amos22_MRI": "AMOS22_MRI", | |
| # MSD Tasks | |
| "Task01_BrainTumour": "Task01_BrainTumour", | |
| "Task02_Heart": "Task02_Heart", | |
| "Task03_Liver": "Task03_Liver", | |
| "Task04_Hippocampus": "Task04_Hippocampus", | |
| "Task05_Prostate": "Task05_Prostate", | |
| "Task06_Lung": "Task06_Lung", | |
| "Task07_Pancreas": "Task07_Pancreas", | |
| "Task08_HepaticVessel": "Task08_HepaticVessel", | |
| "Task09_Spleen": "Task09_Spleen", | |
| "Task10_Colon": "Task10_Colon", | |
| # Six new datasets | |
| "BraTS2020": "BraTS2020", | |
| "SPIDER": "SPIDER", | |
| "STS-2D-Tooth": "STS-2D-Tooth", | |
| "STS-3D-Tooth": "STS-3D-Tooth", | |
| "TotalSegmentator": "TotalSegmentator", | |
| "TotalSegmentatorMRI": "TotalSegmentatorMRI", | |
| "TotalSegmentator_reduced": "TotalSegmentator_reduced", | |
| "TotalSegmentatorMRI_reduced": "TotalSegmentatorMRI_reduced" | |
| } | |
| def get_output_folder_name(dataset_name): | |
| """Output folder name in RL_dataset: remove '_reduced' for reduced datasets.""" | |
| if dataset_name == "TotalSegmentator_reduced": | |
| return "totalsegmentator" | |
| if dataset_name == "TotalSegmentatorMRI_reduced": | |
| return "totalsegmentatormri" | |
| return dataset_name.lower() | |
| def run_step1_convert(dataset_name, dataset_type, raw_data_dir, output_base_dir, output_folder_name=None, split_mode="default"): | |
| """ | |
| Step 1: Convert raw BiomedParse format to HF-compatible format | |
| For MSD tasks, output structure is: | |
| output_base_dir/MSD_taskXX/TaskXX_Name/ | |
| """ | |
| print("\n" + "="*80) | |
| print(f"STEP 1: Converting {dataset_name} to HF format") | |
| print("="*80) | |
| # Special handling for MSD tasks | |
| if dataset_name.startswith("Task") and dataset_name.endswith(("BrainTumour", "Heart", "Liver", "Hippocampus", | |
| "Prostate", "Lung", "Pancreas", "HepaticVessel", | |
| "Spleen", "Colon")): | |
| # Extract task number (e.g., Task01_BrainTumour -> MSD_task01) | |
| task_num = dataset_name.split("_")[0] # Task01 | |
| msd_task_dir = os.path.join(output_base_dir, f"MSD_{task_num.lower()}") | |
| os.makedirs(msd_task_dir, exist_ok=True) | |
| # Output to MSD_taskXX/TaskXX_Name/ | |
| output_dir = os.path.join(msd_task_dir, dataset_name) | |
| else: | |
| # Standard output: output_base_dir/<folder_name> (reduced 数据集去掉 _reduced) | |
| folder_name = output_folder_name if output_folder_name else get_output_folder_name(dataset_name) | |
| output_dir = os.path.join(output_base_dir, folder_name) | |
| # Check if already converted (must have dataset_info.json AND actual data files) | |
| dataset_info_path = os.path.join(output_dir, "dataset_info.json") | |
| train_images_dir = os.path.join(output_dir, "train", "images") | |
| has_data = False | |
| if os.path.exists(train_images_dir): | |
| image_files = [f for f in os.listdir(train_images_dir) if f.endswith(('.jpg', '.png', '.jpeg'))] | |
| has_data = len(image_files) > 0 | |
| if os.path.exists(output_dir) and os.path.isfile(dataset_info_path) and has_data: | |
| print(f"✓ Dataset already converted: {output_dir}") | |
| return output_dir | |
| if os.path.exists(output_dir): | |
| if not os.path.isfile(dataset_info_path): | |
| print(f"⚠ Output dir exists but dataset_info.json missing (incomplete run); removing and re-running conversion.") | |
| elif not has_data: | |
| print(f"⚠ Output dir exists but no image files found (incomplete run); removing and re-running conversion.") | |
| import shutil | |
| shutil.rmtree(output_dir) | |
| print(f"[DEBUG] Removed incomplete directory: {output_dir}") | |
| cmd = [ | |
| sys.executable, | |
| os.path.join(SCRIPT_DIR, "convert_biomedparse_to_hf_format.py"), | |
| "--input_dir", raw_data_dir, | |
| "--output_dir", output_dir, | |
| "--dataset_type", dataset_type, | |
| "--num_vis", "30", | |
| "--split_mode", split_mode | |
| ] | |
| print(f"Running: {' '.join(cmd)}") | |
| print(f"[DEBUG] Step 1 started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| try: | |
| result = subprocess.run(cmd, check=True) | |
| if result.returncode == 0: | |
| print(f"[DEBUG] Step 1 completed successfully at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| print(f"✓ Step 1 complete: {output_dir}") | |
| return output_dir | |
| else: | |
| print(f"[DEBUG] Step 1 failed with return code: {result.returncode}") | |
| raise RuntimeError(f"Step 1 failed with code {result.returncode}") | |
| except subprocess.CalledProcessError as e: | |
| print(f"[DEBUG] Step 1 subprocess error: {e}") | |
| print(f"[DEBUG] Step 1 failed at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| raise | |
| except Exception as e: | |
| print(f"[DEBUG] Step 1 unexpected error: {e}") | |
| print(f"[DEBUG] Step 1 failed at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| raise | |
| def run_step2_packing(dataset_dir, output_base_dir, dataset_name, template_percent, processed_prefix=None, | |
| template_extraction_device="cuda:0"): | |
| """ | |
| Step 2: Pack dataset and extract template features | |
| For MSD tasks, output structure is: | |
| output_base_dir/MSD_taskXX/TaskXX_Name_10percent_processed/ | |
| """ | |
| print("\n" + "="*80) | |
| print(f"STEP 2: Packing {dataset_name} with {template_percent}% templates") | |
| print("="*80) | |
| # Special handling for MSD tasks | |
| if dataset_name.startswith("Task") and dataset_name.endswith(("BrainTumour", "Heart", "Liver", "Hippocampus", | |
| "Prostate", "Lung", "Pancreas", "HepaticVessel", | |
| "Spleen", "Colon")): | |
| # Extract task number (e.g., Task01_BrainTumour -> MSD_task01) | |
| task_num = dataset_name.split("_")[0] # Task01 | |
| msd_task_dir = os.path.join(output_base_dir, f"MSD_{task_num.lower()}") | |
| os.makedirs(msd_task_dir, exist_ok=True) | |
| # Output to MSD_taskXX/TaskXX_Name_10percent_processed/ | |
| output_dir = os.path.join(msd_task_dir, f"{dataset_name}_{template_percent}percent_processed") | |
| else: | |
| # Standard output: output_base_dir/<folder_name>_Xpercent_processed | |
| folder_prefix = processed_prefix if processed_prefix else get_output_folder_name(dataset_name) | |
| output_dir = os.path.join(output_base_dir, f"{folder_prefix}_{template_percent}percent_processed") | |
| # Check if already packed | |
| if os.path.exists(output_dir): | |
| print(f"✓ Dataset already packed: {output_dir}") | |
| return output_dir | |
| print(f"[DEBUG] Step 2 started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| print(f"[DEBUG] Input dataset_dir: {dataset_dir}") | |
| print(f"[DEBUG] Output dir: {output_dir}") | |
| # First, run packing without templates | |
| cmd_pack = [ | |
| sys.executable, | |
| os.path.join(SCRIPT_DIR, "packing_HF_dataset.py"), | |
| "--dataset_root_dir", dataset_dir, | |
| "--output_dir", output_dir, | |
| "--subset_size", "0", | |
| "--device", "cpu" | |
| ] | |
| print(f"Running packing: {' '.join(cmd_pack)}") | |
| print(f"[DEBUG] Starting packing subprocess...") | |
| try: | |
| result = subprocess.run(cmd_pack, check=True) | |
| print(f"[DEBUG] Packing subprocess completed successfully (returncode: {result.returncode})") | |
| except subprocess.CalledProcessError as e: | |
| print(f"[DEBUG] Packing subprocess failed with return code: {e.returncode}") | |
| print(f"[DEBUG] Step 2 failed at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| raise | |
| except Exception as e: | |
| print(f"[DEBUG] Packing unexpected error: {e}") | |
| print(f"[DEBUG] Step 2 failed at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| raise | |
| # Then extract templates | |
| print(f"\nExtracting templates ({template_percent}% of training data)...") | |
| print(f"[DEBUG] Starting template extraction...") | |
| try: | |
| extract_templates_python(dataset_dir, output_dir, template_percent, device=template_extraction_device) | |
| print(f"[DEBUG] Template extraction completed successfully") | |
| except Exception as e: | |
| print(f"[DEBUG] Template extraction failed: {e}") | |
| print(f"[DEBUG] Step 2 failed at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| raise | |
| print(f"[DEBUG] Step 2 completed successfully at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| print(f"✓ Step 2 complete: {output_dir}") | |
| return output_dir | |
| def extract_templates_python(dataset_dir, output_dir, template_percent, device="cuda:0"): | |
| """ | |
| Extract template features using Python directly (DINOv2 on ``device``). | |
| """ | |
| from packing_HF_dataset import extract_template_features | |
| from PIL import Image as PILImage | |
| image_dir = os.path.join(dataset_dir, "train", "images") | |
| mask_dir = os.path.join(dataset_dir, "train", "masks") | |
| template_dir = os.path.join(output_dir, "template") | |
| # Get all images | |
| all_images = sorted(glob.glob(os.path.join(image_dir, "*.jpg"))) | |
| num_templates = int(len(all_images) * (template_percent / 100.0)) | |
| # Randomly sample | |
| random.seed(42) # Fixed seed for reproducibility | |
| selected_images = random.sample(all_images, num_templates) | |
| print(f"Total training images: {len(all_images)}") | |
| print(f"{template_percent}% = {num_templates} templates") | |
| # Load templates | |
| template_data_dict = {} | |
| for img_path in selected_images: | |
| template_id = os.path.splitext(os.path.basename(img_path))[0] | |
| mask_path = os.path.join(mask_dir, f"{template_id}.png") | |
| if os.path.exists(mask_path): | |
| img = PILImage.open(img_path).convert('RGB') | |
| mask = PILImage.open(mask_path).convert('L') | |
| # Get modality from dataset_info.json | |
| dataset_info_path = os.path.join(dataset_dir, "dataset_info.json") | |
| modality = "MRI" # Default | |
| if os.path.exists(dataset_info_path): | |
| with open(dataset_info_path) as f: | |
| info = json.load(f) | |
| modality = info.get("modality", "MRI") | |
| template_data_dict[template_id] = {'image': img, 'mask': mask, 'modality': modality} | |
| # Extract features | |
| if template_data_dict: | |
| results = extract_template_features(template_data_dict=template_data_dict, output_dir=template_dir, device=device) | |
| print(f"✓ Extracted {len(results)} templates") | |
| else: | |
| print("✗ No templates loaded") | |
| def get_present_label_ids_from_mask_path(mask_path: str) -> List[int]: | |
| """Read a label mask and return non-background class ids actually present.""" | |
| try: | |
| if mask_path and os.path.exists(mask_path): | |
| arr = np.array(Image.open(mask_path)) | |
| uniq = np.unique(arr) | |
| present = [int(v) for v in uniq.tolist() if int(v) != 0] | |
| return present | |
| except Exception: | |
| pass | |
| return [] | |
| def get_non_bg_label_ids(labels_json_str: str) -> List[int]: | |
| """Parse labels JSON and return non-background class IDs.""" | |
| labels = json.loads(labels_json_str) | |
| return [int(k) for k, name in labels.items() if k != "0" and name != "background"] | |
| def precompute_for_entries( | |
| entries: List[Dict[str, Any]], | |
| templates: Dict[str, Dict[str, Any]], | |
| index, | |
| template_ids_list: List[str], | |
| tm, | |
| topk: int = 1, | |
| match_strategy: str = "complete", | |
| max_search: int = 100, | |
| overwrite: bool = True, | |
| allow_fallback: bool = False | |
| ) -> int: | |
| """ | |
| Precompute template matches for a list of entries. | |
| Args: | |
| entries: List of sample entries to process | |
| templates: Dictionary of template data | |
| index: FAISS index for similarity search | |
| template_ids_list: List of template IDs corresponding to index | |
| tm: TemplateManager instance | |
| topk: Number of top templates to store per sample | |
| match_strategy: "complete" (strict - all target classes must be in template) | |
| or "partial" (at least one class overlap) | |
| max_search: How many nearest neighbors to search before filtering | |
| overwrite: Whether to overwrite existing template entries | |
| allow_fallback: If True and not enough strict matches found, fall back to | |
| nearest templates regardless of class overlap (NOT RECOMMENDED) | |
| Returns: | |
| Number of entries updated | |
| """ | |
| updated = 0 | |
| for entry in entries: | |
| image_path = entry.get("image") | |
| if not image_path: | |
| continue | |
| # Skip if already has templates and not overwriting | |
| if (not overwrite) and ("template" in entry) and isinstance(entry["template"], list) and len(entry["template"]) >= topk: | |
| continue | |
| # Get present class IDs from mask | |
| labels_json = entry.get("labels", "{}") | |
| label_mask_path = entry.get("label_mask", None) | |
| present_ids = set(get_present_label_ids_from_mask_path(label_mask_path)) | |
| if len(present_ids) == 0: | |
| present_ids = set(get_non_bg_label_ids(labels_json)) | |
| target_label_ids = present_ids | |
| # Extract DINO features | |
| try: | |
| feat = tm._extract_feature(image_path) | |
| except Exception as e: | |
| print(f"Warning: Failed to extract feature from {image_path}: {e}") | |
| continue | |
| # FAISS search | |
| query = feat.reshape(1, -1).astype("float32") | |
| D, I = index.search(query, min(max_search, len(template_ids_list))) | |
| # Rank templates by similarity with STRICT class matching | |
| ranked = [] | |
| for d, idx in zip(D[0], I[0]): | |
| tid = template_ids_list[idx] | |
| tentry = templates[tid] | |
| tclasses = set(int(c) for c in tentry.get("classes", [])) | |
| overlap = target_label_ids.intersection(tclasses) | |
| # STRICT: Template must contain ALL target classes | |
| if len(target_label_ids) == 0: | |
| continue | |
| if not target_label_ids.issubset(tclasses): | |
| continue | |
| ranked.append({ | |
| "template_id": tid, | |
| "similarity": float(d), | |
| "overlap": sorted(list(map(int, overlap))), | |
| }) | |
| if len(ranked) >= topk: | |
| break | |
| # Optional fallback (disabled by default) | |
| # Only use if explicitly enabled via allow_fallback=True | |
| if len(ranked) < topk and allow_fallback: | |
| print(f"Warning: Only found {len(ranked)}/{topk} strict matches for {image_path}, using fallback") | |
| for d, idx in zip(D[0], I[0]): | |
| tid = template_ids_list[idx] | |
| if any(r["template_id"] == tid for r in ranked): | |
| continue | |
| ranked.append({ | |
| "template_id": tid, | |
| "similarity": float(d), | |
| "overlap": [], | |
| }) | |
| if len(ranked) >= topk: | |
| break | |
| # Only update if we found at least one valid match | |
| if len(ranked) > 0: | |
| entry["template"] = ranked | |
| updated += 1 | |
| else: | |
| # No matching template found - leave entry without template | |
| if "template" in entry: | |
| del entry["template"] | |
| return updated | |
| def run_step3_precompute(processed_dir, device="cuda:0", topk=1): | |
| """ | |
| Step 3: Precompute template matches using STRICT class matching | |
| Template matching strategy: | |
| - STRICT ONLY: Template must contain ALL classes present in target image | |
| - No fallback to partial matches | |
| - Samples without matching templates are left without template field | |
| """ | |
| print("\n" + "="*80) | |
| print(f"STEP 3: Precomputing template matches (STRICT)") | |
| print("="*80) | |
| print(f"Strategy: Template must contain ALL target classes") | |
| print(f"Top-k matches per sample: {topk}") | |
| print(f"[DEBUG] Step 3 started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| print(f"[DEBUG] Processed dir: {processed_dir}") | |
| hf_dataset_path = os.path.join(processed_dir, "hf_dataset") | |
| template_dir = os.path.join(processed_dir, "template") | |
| backup_dir = os.path.join(processed_dir, "backup_entries") | |
| print(f"[DEBUG] HF dataset path: {hf_dataset_path}") | |
| print(f"[DEBUG] Template dir: {template_dir}") | |
| print(f"[DEBUG] Backup dir: {backup_dir}") | |
| # Check if template matches already computed | |
| backup_train = os.path.join(backup_dir, "train_entries.json") | |
| if os.path.exists(backup_train): | |
| with open(backup_train) as f: | |
| entries = json.load(f) | |
| if len(entries) > 0 and "template" in entries[0] and isinstance(entries[0]["template"], list): | |
| print(f"[DEBUG] Templates already precomputed, skipping Step 3") | |
| print(f"✓ Templates already precomputed in {backup_train}") | |
| return | |
| # Verify required directories exist | |
| print(f"[DEBUG] Verifying required directories...") | |
| if not os.path.isdir(template_dir): | |
| print(f"[DEBUG] ERROR: Template directory not found: {template_dir}") | |
| print(f"✗ Template directory not found: {template_dir}") | |
| return | |
| if not os.path.isdir(hf_dataset_path): | |
| print(f"[DEBUG] ERROR: HF dataset not found: {hf_dataset_path}") | |
| print(f"✗ HF dataset not found: {hf_dataset_path}") | |
| return | |
| if not os.path.isdir(backup_dir): | |
| print(f"[DEBUG] ERROR: Backup entries not found: {backup_dir}") | |
| print(f"✗ Backup entries not found: {backup_dir}") | |
| return | |
| print(f"[DEBUG] All required directories verified") | |
| # Import dependencies | |
| print(f"[DEBUG] Importing dependencies...") | |
| try: | |
| from datasets import load_from_disk | |
| from template_manager import TemplateManager | |
| print(f"[DEBUG] Dependencies imported successfully") | |
| except ImportError as e: | |
| print(f"[DEBUG] ERROR: Failed to import modules: {e}") | |
| print(f"✗ Failed to import required modules: {e}") | |
| print(f" Make sure 'datasets' and 'template_manager' are available") | |
| return | |
| print(f"\nLoading HF dataset from: {hf_dataset_path}") | |
| print(f"[DEBUG] Loading HF dataset...") | |
| try: | |
| ds = load_from_disk(hf_dataset_path) | |
| train_split = ds.get("train") or ds.get("training") | |
| val_split = ds.get("val") or ds.get("validation") | |
| print(f"[DEBUG] HF dataset loaded: train={train_split is not None}, val={val_split is not None}") | |
| except Exception as e: | |
| print(f"[DEBUG] ERROR: Failed to load HF dataset: {e}") | |
| raise | |
| # Load backup entries | |
| print(f"Loading backup entries from: {backup_dir}") | |
| print(f"[DEBUG] Loading backup entries...") | |
| try: | |
| with open(os.path.join(backup_dir, "train_entries.json"), "r") as f: | |
| train_entries = json.load(f) | |
| with open(os.path.join(backup_dir, "val_entries.json"), "r") as f: | |
| val_entries = json.load(f) | |
| print(f"[DEBUG] Backup entries loaded: train={len(train_entries)}, val={len(val_entries)}") | |
| except Exception as e: | |
| print(f"[DEBUG] ERROR: Failed to load backup entries: {e}") | |
| raise | |
| # Initialize TemplateManager and load templates | |
| print(f"Loading templates from: {template_dir}") | |
| print(f"Initializing TemplateManager on {device}...") | |
| print(f"[DEBUG] Initializing TemplateManager...") | |
| try: | |
| tm = TemplateManager(device=device) | |
| templates, index, template_ids_list = tm._load_templates(template_dir) | |
| print(f"[DEBUG] TemplateManager initialized, loaded {len(templates)} templates") | |
| print(f"✓ Loaded {len(templates)} templates for search") | |
| except Exception as e: | |
| print(f"[DEBUG] ERROR: Failed to initialize TemplateManager or load templates: {e}") | |
| raise | |
| # Precompute matches for train and val with STRICT matching | |
| total_updates = 0 | |
| if train_entries: | |
| print(f"\nProcessing {len(train_entries)} train entries (strict class matching)...") | |
| print(f"[DEBUG] Starting train entries precomputation...") | |
| try: | |
| updated = precompute_for_entries( | |
| train_entries, templates, index, template_ids_list, tm, | |
| topk=topk, | |
| match_strategy="complete", # STRICT: all target classes must be in template | |
| max_search=100, # Search more candidates to find strict matches | |
| overwrite=True, | |
| allow_fallback=False # NO fallback to partial matches | |
| ) | |
| total_updates += updated | |
| matched_pct = (updated / len(train_entries) * 100) if len(train_entries) > 0 else 0 | |
| print(f"[DEBUG] Train precomputation completed: {updated}/{len(train_entries)} matched") | |
| print(f"✓ Matched {updated}/{len(train_entries)} train entries ({matched_pct:.1f}%)") | |
| except Exception as e: | |
| print(f"[DEBUG] ERROR: Train precomputation failed: {e}") | |
| raise | |
| if val_entries: | |
| print(f"\nProcessing {len(val_entries)} val entries (strict class matching)...") | |
| print(f"[DEBUG] Starting val entries precomputation...") | |
| try: | |
| updated = precompute_for_entries( | |
| val_entries, templates, index, template_ids_list, tm, | |
| topk=topk, | |
| match_strategy="complete", # STRICT: all target classes must be in template | |
| max_search=100, # Search more candidates to find strict matches | |
| overwrite=True, | |
| allow_fallback=False # NO fallback to partial matches | |
| ) | |
| total_updates += updated | |
| matched_pct = (updated / len(val_entries) * 100) if len(val_entries) > 0 else 0 | |
| print(f"[DEBUG] Val precomputation completed: {updated}/{len(val_entries)} matched") | |
| print(f"✓ Matched {updated}/{len(val_entries)} val entries ({matched_pct:.1f}%)") | |
| except Exception as e: | |
| print(f"[DEBUG] ERROR: Val precomputation failed: {e}") | |
| raise | |
| # Write back updated JSONs | |
| print(f"[DEBUG] Writing back updated JSONs...") | |
| train_json = os.path.join(backup_dir, "train_entries.json") | |
| val_json = os.path.join(backup_dir, "val_entries.json") | |
| print(f"[DEBUG] Writing train_entries.json...") | |
| try: | |
| with open(train_json, "w") as f: | |
| json.dump(train_entries, f, indent=2) | |
| print(f"[DEBUG] train_entries.json written successfully") | |
| except Exception as e: | |
| print(f"[DEBUG] ERROR: Failed to write train_entries.json: {e}") | |
| raise | |
| print(f"[DEBUG] Writing val_entries.json...") | |
| try: | |
| with open(val_json, "w") as f: | |
| json.dump(val_entries, f, indent=2) | |
| print(f"[DEBUG] val_entries.json written successfully") | |
| except Exception as e: | |
| print(f"[DEBUG] ERROR: Failed to write val_entries.json: {e}") | |
| raise | |
| print(f"[DEBUG] Step 3 completed successfully at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| print(f"\n✓ Step 3 complete: {total_updates} samples with strict template matches") | |
| print(f" Updated: {train_json}") | |
| print(f" Updated: {val_json}") | |
| print(f"\nNote: Samples without matching templates have no 'template' field") | |
| def verify_output(processed_dir, expected_train_count, expected_val_count, expected_template_count): | |
| """ | |
| Verify the processed dataset has correct structure and counts | |
| """ | |
| print("\n" + "="*80) | |
| print("VERIFICATION") | |
| print("="*80) | |
| checks = [] | |
| # Check backup_entries | |
| train_json = os.path.join(processed_dir, "backup_entries", "train_entries.json") | |
| val_json = os.path.join(processed_dir, "backup_entries", "val_entries.json") | |
| if os.path.exists(train_json): | |
| with open(train_json) as f: | |
| train_entries = json.load(f) | |
| checks.append(("backup_entries/train", len(train_entries) == expected_train_count, | |
| f"{len(train_entries)} entries (expected {expected_train_count})")) | |
| else: | |
| checks.append(("backup_entries/train", False, "File not found")) | |
| if os.path.exists(val_json): | |
| with open(val_json) as f: | |
| val_entries = json.load(f) | |
| checks.append(("backup_entries/val", len(val_entries) == expected_val_count, | |
| f"{len(val_entries)} entries (expected {expected_val_count})")) | |
| else: | |
| checks.append(("backup_entries/val", False, "File not found")) | |
| # Check hf_dataset | |
| hf_dir = os.path.join(processed_dir, "hf_dataset") | |
| if os.path.exists(hf_dir): | |
| checks.append(("hf_dataset", True, "Directory exists")) | |
| else: | |
| checks.append(("hf_dataset", False, "Directory not found")) | |
| # Check templates | |
| template_dir = os.path.join(processed_dir, "template") | |
| if os.path.exists(template_dir): | |
| npz_files = glob.glob(os.path.join(template_dir, "*.npz")) | |
| checks.append(("template", len(npz_files) == expected_template_count, | |
| f"{len(npz_files)} templates (expected {expected_template_count})")) | |
| else: | |
| checks.append(("template", False, "Directory not found")) | |
| # Print results | |
| all_passed = True | |
| for name, passed, detail in checks: | |
| status = "✓" if passed else "✗" | |
| print(f"{status} {name}: {detail}") | |
| if not passed: | |
| all_passed = False | |
| return all_passed | |
| def main(): | |
| parser = argparse.ArgumentParser(description="End-to-end preprocessing pipeline") | |
| parser.add_argument("--dataset_name", type=str, required=True, | |
| help="Dataset name (e.g., ACDC, CAMUS, MMs)") | |
| parser.add_argument("--template_percent", type=int, required=True, | |
| help="Percentage of training data to use as templates (e.g., 5, 10)") | |
| parser.add_argument("--raw_data_dir", type=str, required=True, | |
| help="Path to raw dataset (e.g., /projects/p32685/MSD/Task01_BrainTumour)") | |
| parser.add_argument("--output_base_dir", type=str, default="/projects/p32685/RL_dataset", | |
| help="Base output directory") | |
| parser.add_argument("--device", type=str, default="cuda:0", | |
| help="Device for template precomputation") | |
| parser.add_argument("--skip_convert", action="store_true", | |
| help="Skip step 1 (conversion) if already done") | |
| parser.add_argument("--skip_packing", action="store_true", | |
| help="Skip step 2 (packing) if already done") | |
| parser.add_argument("--skip_precompute", action="store_true", | |
| help="Skip step 3 (precompute) if already done") | |
| parser.add_argument("--output_folder_name", type=str, default=None, | |
| help="Optional explicit folder name for converted dataset (Step 1 output folder).") | |
| parser.add_argument("--processed_prefix", type=str, default=None, | |
| help="Optional explicit prefix for processed output folders (Step 2 output prefix).") | |
| parser.add_argument("--split_mode", type=str, default="default", | |
| choices=["default", "acdc_patient_100_50"], | |
| help="Split mode passed to converter.") | |
| args = parser.parse_args() | |
| # Determine dataset type | |
| dataset_type = DATASET_TYPES.get(args.dataset_name, args.dataset_name) | |
| print("╔" + "="*78 + "╗") | |
| print("║" + " "*20 + "END-TO-END PREPROCESSING PIPELINE" + " "*25 + "║") | |
| print("╔" + "="*78 + "╗") | |
| print(f"\nDataset: {args.dataset_name}") | |
| print(f"Type: {dataset_type}") | |
| print(f"Template percentage: {args.template_percent}%") | |
| print(f"Raw data: {args.raw_data_dir}") | |
| print(f"Output base: {args.output_base_dir}") | |
| print(f"Device: {args.device}") | |
| print(f"[DEBUG] Pipeline started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| try: | |
| # Step 1: Convert | |
| if not args.skip_convert: | |
| converted_dir = run_step1_convert(args.dataset_name, dataset_type, | |
| args.raw_data_dir, args.output_base_dir, | |
| output_folder_name=args.output_folder_name, | |
| split_mode=args.split_mode) | |
| else: | |
| # Determine converted_dir based on dataset type | |
| if args.dataset_name.startswith("Task") and args.dataset_name.endswith(("BrainTumour", "Heart", "Liver", "Hippocampus", | |
| "Prostate", "Lung", "Pancreas", "HepaticVessel", | |
| "Spleen", "Colon")): | |
| task_num = args.dataset_name.split("_")[0] | |
| converted_dir = os.path.join(args.output_base_dir, f"MSD_{task_num.lower()}", args.dataset_name) | |
| else: | |
| folder_name = args.output_folder_name if args.output_folder_name else get_output_folder_name(args.dataset_name) | |
| converted_dir = os.path.join(args.output_base_dir, folder_name) | |
| print(f"Skipping Step 1 (conversion), using: {converted_dir}") | |
| # Step 2: Pack and extract templates | |
| if not args.skip_packing: | |
| processed_dir = run_step2_packing(converted_dir, args.output_base_dir, | |
| args.dataset_name, args.template_percent, | |
| processed_prefix=args.processed_prefix, | |
| template_extraction_device=args.device) | |
| else: | |
| # Determine processed_dir based on dataset type | |
| if args.dataset_name.startswith("Task") and args.dataset_name.endswith(("BrainTumour", "Heart", "Liver", "Hippocampus", | |
| "Prostate", "Lung", "Pancreas", "HepaticVessel", | |
| "Spleen", "Colon")): | |
| task_num = args.dataset_name.split("_")[0] | |
| processed_dir = os.path.join(args.output_base_dir, f"MSD_{task_num.lower()}", | |
| f"{args.dataset_name}_{args.template_percent}percent_processed") | |
| else: | |
| folder_prefix = args.processed_prefix if args.processed_prefix else get_output_folder_name(args.dataset_name) | |
| processed_dir = os.path.join(args.output_base_dir, | |
| f"{folder_prefix}_{args.template_percent}percent_processed") | |
| print(f"Skipping Step 2 (packing), using: {processed_dir}") | |
| # Step 3: Precompute template matches | |
| if not args.skip_precompute: | |
| run_step3_precompute(processed_dir, args.device) | |
| else: | |
| print("Skipping Step 3 (precompute)") | |
| # Verification | |
| print("\n" + "="*80) | |
| print("PIPELINE COMPLETE") | |
| print("="*80) | |
| print(f"[DEBUG] Pipeline completed successfully at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| print(f"\nOutput directories created:") | |
| print(f" 1. Converted: {converted_dir}") | |
| print(f" 2. Processed: {processed_dir}") | |
| # Calculate expected counts | |
| train_images = glob.glob(os.path.join(converted_dir, "train", "images", "*")) | |
| val_images = glob.glob(os.path.join(converted_dir, "val", "images", "*")) | |
| expected_templates = int(len(train_images) * (args.template_percent / 100.0)) | |
| # Verify | |
| all_good = verify_output(processed_dir, len(train_images), len(val_images), expected_templates) | |
| if all_good: | |
| print("\n✓ All checks passed! Dataset ready for training.") | |
| else: | |
| print("\n✗ Some checks failed. Please review above.") | |
| except Exception as e: | |
| print(f"\n[DEBUG] Pipeline failed at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| print(f"[DEBUG] Error type: {type(e).__name__}") | |
| print(f"[DEBUG] Error message: {e}") | |
| print(f"\n✗ Pipeline failed: {e}") | |
| import traceback | |
| print(f"[DEBUG] Full traceback:") | |
| traceback.print_exc() | |
| sys.exit(1) | |
| if __name__ == "__main__": | |
| main() | |