diff --git a/code/envs/seggen-controlnet.yml b/code/envs/seggen-controlnet.yml new file mode 100644 index 0000000000000000000000000000000000000000..98ed37c10897f2bf083673d0b32b6c49bf7ca79a --- /dev/null +++ b/code/envs/seggen-controlnet.yml @@ -0,0 +1,18 @@ +# Isolated env for ControlNet (pytorch-lightning 1.5 + old torch). Needs SD v1.5 ckpt. +# Generative-augmentation baseline only; never mix with the main env. +name: seggen-controlnet +channels: [conda-forge] +dependencies: + - python=3.10 + - pip + - pip: + # torch 1.12.1 (cu113 wheels bundle their own runtime): + # pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113 + - pytorch-lightning==1.5.0 + - omegaconf==2.1.1 + - einops==0.3.0 + - transformers==4.19.2 + - open-clip-torch==2.0.2 + - gradio==3.16.2 + - opencv-python-headless + - basicsr==1.4.2 diff --git a/code/envs/umamba.yml b/code/envs/umamba.yml index c52c1e6c4ac794248d02d1fa62af4dddbd0d83ec..89d02411791de87245f678ae44e5103cb54fd57c 100644 --- a/code/envs/umamba.yml +++ b/code/envs/umamba.yml @@ -1,16 +1,25 @@ -# Isolated env for U-Mamba reference baseline. mamba_ssm needs compilation; the repo -# targets CUDA 11.8 + torch 2.0.1. Compilation on newer stacks may fail — pin to 11.8. -# AMP can NaN in Mamba layers: use the NoAMP trainer variant. +# Isolated env for U-Mamba reference baseline (nnU-Net v2.1.1 + Mamba). +# VALIDATED on the A100 server (2026-06-05): mamba CUDA kernel + UMambaBot trainer +# run end-to-end. mamba_ssm/causal-conv1d are installed from PREBUILT WHEELS to +# avoid local CUDA compilation (system nvcc 12.8 != torch cu118). +# +# Reproduce with these exact commands (NOT `conda env create -f`; pip-driven): +# +# conda create -n umamba python=3.10 -y && conda activate umamba +# pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 +# pip install https://github.com/Dao-AILab/causal-conv1d/releases/download/v1.2.0.post2/causal_conv1d-1.2.0.post2+cu118torch2.0cxx11abiFALSE-cp310-cp310-linux_x86_64.whl +# pip install https://github.com/state-spaces/mamba/releases/download/v1.2.0.post1/mamba_ssm-1.2.0.post1+cu118torch2.0cxx11abiFALSE-cp310-cp310-linux_x86_64.whl +# pip install -e sota/U-Mamba/umamba +# # critical pins (otherwise: transformers drops GreedySearchDecoderOnlyOutput; opencv/numpy clash): +# pip install "numpy<2" "transformers==4.38.2" "opencv-python-headless==4.9.0.80" +# +# Train (uses nnU-Net format from framework/nnunet_convert.py; A100 only — bf16/sm_80): +# export CUDA_VISIBLE_DEVICES=4 +# nnUNetv2_plan_and_preprocess -d -c 2d # umamba env does its OWN 2.1.1 preprocess +# cp /Dataset_*/splits_final.json $nnUNet_preprocessed/Dataset_*/splits_final.json +# nnUNetv2_train 2d 0 -tr nnUNetTrainerUMambaBot +# # AMP can NaN in Mamba: if so use -tr nnUNetTrainerUMambaEncNoAMP name: umamba channels: [conda-forge] dependencies: - python=3.10 - - pip - - pip: - # pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 - # then (in order): - # pip install causal-conv1d>=1.2.0 - # pip install mamba-ssm --no-cache-dir - # cd sota/U-Mamba/umamba && pip install -e . - - simpleitk - - nibabel diff --git a/code/framework/common/subset.py b/code/framework/common/subset.py deleted file mode 100644 index f19f1b8102474a6bad5c950d10c1474c30903d3c..0000000000000000000000000000000000000000 --- a/code/framework/common/subset.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Deterministic low-data subset selection. - -The SAME function is used by (a) the segmentation trainer's real-data loader and -(b) the pixel-diffusion generator's data loader, so that a low-data experiment at -fraction f trains the generator on EXACTLY the real images the segmenter sees — -no leakage, no mismatch, fully reproducible. - -Selection is driven only by (fraction, fraction_seed) and the SORTED list of -items, so it is independent of the training seed: the 3 segmentation seeds all -use the identical real subset, only their weight init differs. -""" -from __future__ import annotations - -import math -import random -from typing import List, Sequence, TypeVar - -T = TypeVar("T") - - -def select_fraction(items: Sequence[T], fraction: float, seed: int = 0) -> List[T]: - """Return a deterministic subset of `items` of size ceil(fraction*N). - - items: any sequence (e.g. list of (image_path, mask_path) tuples). It is - sorted by repr() first so order of discovery never affects the subset. - fraction: in (0, 1]. >=1 returns all items (sorted copy). - seed: subset seed (NOT the training seed). Fixed across training seeds. - """ - ordered = sorted(items, key=lambda x: repr(x)) - n = len(ordered) - if fraction >= 1.0 or n == 0: - return ordered - if fraction <= 0.0: - raise ValueError(f"fraction must be in (0,1], got {fraction}") - k = max(1, math.ceil(fraction * n)) - rng = random.Random(seed) - idx = list(range(n)) - rng.shuffle(idx) - keep = sorted(idx[:k]) - return [ordered[i] for i in keep] diff --git a/code/framework/config.py b/code/framework/config.py index 78e3203011f33a1042f6de8d7313ec2edb3cdee1..53789a42763ca0cd75689a53db1f773ef17b3cbf 100644 --- a/code/framework/config.py +++ b/code/framework/config.py @@ -33,13 +33,6 @@ class Config: # Points at a dir laid out like a split: /{images,masks}/. synth_train_dir: str = "" # "" = real data only (no generative augmentation) - # low-data regime: train on a deterministic fraction of the REAL train split. - # The SAME subset is used by the PixDiff generator (framework.common.subset), so - # real and real+synth arms never disagree. fraction_seed is FIXED across the 3 - # training seeds (only weight init varies), so the data subset stays constant. - train_fraction: float = 1.0 # 1.0 = full data; e.g. 0.1 = 10% - fraction_seed: int = 0 # subset seed (independent of `seed`) - # ---- augmentation (conventional baseline tier) ---- aug: str = "standard" # none | standard | strong (albumentations online) aug_backend: str = "albumentations" # albumentations | monai diff --git a/code/framework/config.py.bak b/code/framework/config.py.bak deleted file mode 100644 index 53789a42763ca0cd75689a53db1f773ef17b3cbf..0000000000000000000000000000000000000000 --- a/code/framework/config.py.bak +++ /dev/null @@ -1,109 +0,0 @@ -"""Unified experiment configuration. - -A single dataclass drives every run. Values can come from (in priority order): - 1. command-line flags (argparse) 2. a YAML file (--config) 3. dataclass defaults. - -The same config object is used by train.py / test.py so that a training run and -its evaluation are guaranteed to agree on dataset, model, image size, etc. -""" -from __future__ import annotations - -import argparse -import dataclasses -from dataclasses import dataclass, field, asdict -from typing import Optional, List - -import yaml - - -@dataclass -class Config: - # ---- experiment identity ---- - exp_name: str = "default" # results////seed/ - seed: int = 0 - - # ---- data ---- - data_root: str = "dataset/processed_unified" - dataset: str = "cvc_clinicdb" # folder name under data_root - protocol: str = "official" # e.g. official / fold01 ... - in_channels: int = 0 # 0 = auto-detect from metadata/first image - num_classes: int = 0 # 0 = auto-detect from metadata/masks (incl. background) - img_size: int = 256 # square resize target (Swin/TransUNet need 224) - # extra synthetic (image,mask) pairs to MERGE into the train split. - # Points at a dir laid out like a split: /{images,masks}/. - synth_train_dir: str = "" # "" = real data only (no generative augmentation) - - # ---- augmentation (conventional baseline tier) ---- - aug: str = "standard" # none | standard | strong (albumentations online) - aug_backend: str = "albumentations" # albumentations | monai - normalize: str = "auto" # auto(imagenet for RGB, 0.5 for gray) | imagenet | none - - # ---- model ---- - arch: str = "unet" # see models/registry.py REGISTRY - encoder: str = "resnet34" # SMP encoder name (ignored by non-SMP archs) - encoder_weights: str = "imagenet" # imagenet | none - pretrained_ckpt: str = "" # ViT/Swin pretrain for transunet/swinunet (optional) - - # ---- optimization ---- - epochs: int = 100 - batch_size: int = 16 # per-GPU batch size - lr: float = 1e-4 - weight_decay: float = 1e-4 - optimizer: str = "adamw" # adamw | sgd - scheduler: str = "poly" # poly | cosine | none - warmup_epochs: int = 0 - loss: str = "ce_dice" # ce_dice | ce | dice - num_workers: int = 8 - grad_clip: float = 0.0 # 0 = disabled - - # ---- precision / hardware ---- - amp: str = "bf16" # bf16(A100+) | fp16(V100) | fp32 - # DDP is driven by torchrun env vars (RANK/WORLD_SIZE/LOCAL_RANK); nothing to set here. - - # ---- evaluation / logging ---- - val_interval: int = 5 # epochs between validations - min_epochs: int = 0 # never early-stop before this many epochs - patience: int = 0 # early-stop after this many epochs w/o val improvement (0 = off) - save_interval: int = 0 # 0 = only save best + last - include_background: bool = False # include class 0 in reported Dice/IoU - compute_hd95: bool = True - out_root: str = "results" - resume: str = "" # path to checkpoint to resume from - visualize: bool = True # save overlays at test time - vis_max: int = 32 # max number of overlay images to save - - def out_dir(self) -> str: - return f"{self.out_root}/{self.exp_name}/{self.dataset}_{self.protocol}/{self.arch}/seed{self.seed}" - - def to_yaml(self, path: str) -> None: - with open(path, "w") as f: - yaml.safe_dump(asdict(self), f, sort_keys=False, allow_unicode=True) - - @classmethod - def from_args(cls, argv: Optional[List[str]] = None) -> "Config": - # First pass: only grab --config so YAML can set defaults that flags then override. - pre = argparse.ArgumentParser(add_help=False) - pre.add_argument("--config", type=str, default="") - known, _ = pre.parse_known_args(argv) - - base = cls() - if known.config: - with open(known.config) as f: - ydata = yaml.safe_load(f) or {} - base = dataclasses.replace(base, **{k: v for k, v in ydata.items() - if k in {f.name for f in dataclasses.fields(cls)}}) - - p = argparse.ArgumentParser(parents=[pre], - description="SegGen unified segmentation framework") - for f in dataclasses.fields(cls): - default = getattr(base, f.name) - if f.type is bool or isinstance(default, bool): - # support --flag / --no-flag - p.add_argument(f"--{f.name}", dest=f.name, action="store_true", default=default) - p.add_argument(f"--no-{f.name}", dest=f.name, action="store_false") - else: - p.add_argument(f"--{f.name}", type=type(default) if default is not None else str, - default=default) - ns = p.parse_args(argv) - kwargs = {f.name: getattr(ns, f.name) for f in dataclasses.fields(cls)} - return cls(**kwargs) diff --git a/code/framework/data/loaders.py b/code/framework/data/loaders.py index 6c6c0596827e42bda720440013b5745716875131..c7c654390151dce124c5d38f6b08099b47c6f687 100644 --- a/code/framework/data/loaders.py +++ b/code/framework/data/loaders.py @@ -17,7 +17,6 @@ def build_dataset(cfg, split: str) -> UnifiedSegDataset: data_root=cfg.data_root, dataset=cfg.dataset, protocol=cfg.protocol, split=split, transform=None, in_channels=cfg.in_channels, num_classes=cfg.num_classes, synth_dir=synth, - train_fraction=cfg.train_fraction, fraction_seed=cfg.fraction_seed, ) ds.transform = build_transform(cfg.img_size, ds.in_channels, train=train, aug=cfg.aug, normalize=cfg.normalize) diff --git a/code/framework/data/loaders.py.bak b/code/framework/data/loaders.py.bak deleted file mode 100644 index c7c654390151dce124c5d38f6b08099b47c6f687..0000000000000000000000000000000000000000 --- a/code/framework/data/loaders.py.bak +++ /dev/null @@ -1,40 +0,0 @@ -"""Build datasets / dataloaders from a Config, consistent across train & test.""" -from __future__ import annotations - -from torch.utils.data import DataLoader -from torch.utils.data.distributed import DistributedSampler - -from .unified_dataset import UnifiedSegDataset -from .transforms import build_transform -from ..engine.distributed import is_dist - - -def build_dataset(cfg, split: str) -> UnifiedSegDataset: - train = (split == "train") - synth = cfg.synth_train_dir if train else "" - # construct without transform first so in_channels/num_classes auto-detect runs - ds = UnifiedSegDataset( - data_root=cfg.data_root, dataset=cfg.dataset, protocol=cfg.protocol, split=split, - transform=None, in_channels=cfg.in_channels, num_classes=cfg.num_classes, - synth_dir=synth, - ) - ds.transform = build_transform(cfg.img_size, ds.in_channels, train=train, - aug=cfg.aug, normalize=cfg.normalize) - return ds - - -def build_loader(cfg, split: str, ds: UnifiedSegDataset) -> DataLoader: - train = (split == "train") - sampler = None - if is_dist(): - sampler = DistributedSampler(ds, shuffle=train, drop_last=train) - return DataLoader( - ds, - batch_size=cfg.batch_size, - shuffle=(train and sampler is None), - sampler=sampler, - num_workers=cfg.num_workers, - pin_memory=True, - drop_last=(train and sampler is None), - persistent_workers=cfg.num_workers > 0, - ) diff --git a/code/framework/data/unified_dataset.py b/code/framework/data/unified_dataset.py index 9e4b133e1a947c8080a0aca3d30b9b0bd76890e9..f42557fd8d239d19696480cef1a1641ccd64b888 100644 --- a/code/framework/data/unified_dataset.py +++ b/code/framework/data/unified_dataset.py @@ -124,8 +124,7 @@ class UnifiedSegDataset(Dataset): def __init__(self, data_root: str, dataset: str, protocol: str, split: str, transform: Optional[Callable] = None, in_channels: int = 0, num_classes: int = 0, - synth_dir: str = "", - train_fraction: float = 1.0, fraction_seed: int = 0): + synth_dir: str = ""): self.data_root = data_root self.dataset = dataset self.split = split @@ -142,12 +141,6 @@ class UnifiedSegDataset(Dataset): if not pairs: raise RuntimeError(f"no (image,mask) pairs found in {split_dir}") - # low-data: deterministically subsample REAL train pairs (the generator uses - # the SAME select_fraction, so no leakage) BEFORE merging any synthetic data. - if split == "train" and train_fraction < 1.0: - from ..common.subset import select_fraction - pairs = select_fraction(pairs, train_fraction, fraction_seed) - # optionally merge synthetic (image,mask) pairs into the (train) split if synth_dir and os.path.isdir(synth_dir): sp = _pair_by_glob(synth_dir if os.path.isdir(os.path.join(synth_dir, "images")) diff --git a/code/framework/data/unified_dataset.py.bak b/code/framework/data/unified_dataset.py.bak deleted file mode 100644 index f42557fd8d239d19696480cef1a1641ccd64b888..0000000000000000000000000000000000000000 --- a/code/framework/data/unified_dataset.py.bak +++ /dev/null @@ -1,180 +0,0 @@ -"""Dataset reader for the standardized `processed_unified` layout. - -Expected layout (see dataset/SEGMENTATION_WORKSPACE_README.md): - ////images/*.png - ////masks/*.png - //metadata.json (optional, preferred) - //manifest.jsonl (optional) - -Returns per item: {"image": FloatTensor[C,H,W], "mask": LongTensor[H,W], "name": str}. - -Binary and multi-class masks are both supported: masks keep their integer class -ids (0..C-1). Auto-detection of in_channels / num_classes falls back to scanning -files when metadata is absent, so the loader is robust to missing metadata. -""" -from __future__ import annotations - -import json -import os -from glob import glob -from typing import Optional, Callable, List, Tuple - -import numpy as np -import cv2 -from torch.utils.data import Dataset - - -_MODALITY_CHANNELS = { # hint table; only used when metadata lacks in_channels - "rgb": 3, "fundus": 3, "colonoscopy": 3, "endoscopy": 3, "histopathology": 3, - "ultrasound": 1, "mri": 1, "ct": 1, "grayscale": 1, -} - -# Documented class counts (incl. background). metadata.json on the server has no -# num_classes field, so this table is the fast, reliable primary source; unknown -# datasets fall back to a FULL scan of the mask set (accurate but slower). -_KNOWN_NUM_CLASSES = { - "cvc_clinicdb": 2, "kvasir_seg": 2, "fives": 2, "busi": 2, - "refuge2": 3, "acdc_png": 4, - "idridd_segmentation": 6, "pannuke_semantic": 6, -} - - -def _read_metadata(data_root: str, dataset: str) -> dict: - path = os.path.join(data_root, dataset, "metadata.json") - if os.path.isfile(path): - try: - with open(path) as f: - return json.load(f) - except Exception: - return {} - return {} - - -def _pair_from_manifest(split_dir: str, manifest: str) -> Optional[List[Tuple[str, str]]]: - if not os.path.isfile(manifest): - return None - pairs = [] - base = os.path.dirname(manifest) - with open(manifest) as f: - for line in f: - line = line.strip() - if not line: - continue - rec = json.loads(line) - img = rec.get("image") or rec.get("image_path") or rec.get("img") - msk = rec.get("mask") or rec.get("mask_path") or rec.get("label") - if img is None or msk is None: - return None - # manifest paths may be relative to dataset root or absolute - ip = img if os.path.isabs(img) else os.path.join(base, img) - mp = msk if os.path.isabs(msk) else os.path.join(base, msk) - # only keep entries that fall under this split dir - if os.path.normpath(split_dir) in os.path.normpath(ip): - pairs.append((ip, mp)) - return pairs or None - - -def _pair_by_glob(split_dir: str) -> List[Tuple[str, str]]: - img_dir = os.path.join(split_dir, "images") - msk_dir = os.path.join(split_dir, "masks") - imgs = sorted(glob(os.path.join(img_dir, "*"))) - pairs = [] - for ip in imgs: - stem = os.path.splitext(os.path.basename(ip))[0] - # mask may share extension or be .png - cands = glob(os.path.join(msk_dir, stem + ".*")) - if not cands: - continue - pairs.append((ip, cands[0])) - return pairs - - -def detect_in_channels(meta: dict, sample_img: Optional[str]) -> int: - if meta.get("in_channels"): - return int(meta["in_channels"]) - mod = str(meta.get("modality", "")).lower() - for k, v in _MODALITY_CHANNELS.items(): - if k in mod: - return v - if sample_img and os.path.isfile(sample_img): - im = cv2.imread(sample_img, cv2.IMREAD_UNCHANGED) - if im is not None and im.ndim == 3 and im.shape[2] >= 3: - return 3 - return 1 - - -def detect_num_classes(meta: dict, mask_paths: List[str], dataset: str = "") -> int: - if dataset in _KNOWN_NUM_CLASSES: - return _KNOWN_NUM_CLASSES[dataset] - if meta.get("num_classes"): - return int(meta["num_classes"]) - # unknown dataset: scan ALL masks so a rare class is never missed - vals = set() - for mp in mask_paths: - m = cv2.imread(mp, cv2.IMREAD_GRAYSCALE) - if m is not None: - vals.update(np.unique(m).tolist()) - if not vals: - return 2 - maxv = max(vals) - return int(maxv) + 1 if maxv >= 1 else 2 - - -class UnifiedSegDataset(Dataset): - def __init__(self, data_root: str, dataset: str, protocol: str, split: str, - transform: Optional[Callable] = None, - in_channels: int = 0, num_classes: int = 0, - synth_dir: str = ""): - self.data_root = data_root - self.dataset = dataset - self.split = split - self.transform = transform - - split_dir = os.path.join(data_root, dataset, protocol, split) - if not os.path.isdir(split_dir): - raise FileNotFoundError( - f"split dir not found: {split_dir}\n" - f"(data is prepared separately; see dataset/ scripts)") - - manifest = os.path.join(data_root, dataset, "manifest.jsonl") - pairs = _pair_from_manifest(split_dir, manifest) or _pair_by_glob(split_dir) - if not pairs: - raise RuntimeError(f"no (image,mask) pairs found in {split_dir}") - - # optionally merge synthetic (image,mask) pairs into the (train) split - if synth_dir and os.path.isdir(synth_dir): - sp = _pair_by_glob(synth_dir if os.path.isdir(os.path.join(synth_dir, "images")) - else os.path.dirname(synth_dir)) - pairs = pairs + sp - - self.pairs = pairs - meta = _read_metadata(data_root, dataset) - self.in_channels = in_channels or detect_in_channels(meta, pairs[0][0]) - self.num_classes = num_classes or detect_num_classes(meta, [p[1] for p in pairs], dataset) - - def __len__(self) -> int: - return len(self.pairs) - - def _load_image(self, path: str) -> np.ndarray: - if self.in_channels == 1: - im = cv2.imread(path, cv2.IMREAD_GRAYSCALE) - if im is None: - raise IOError(f"cannot read image {path}") - return im[:, :, None] # H,W,1 - im = cv2.imread(path, cv2.IMREAD_COLOR) # BGR - if im is None: - raise IOError(f"cannot read image {path}") - return cv2.cvtColor(im, cv2.COLOR_BGR2RGB) # H,W,3 - - def __getitem__(self, idx: int): - ip, mp = self.pairs[idx] - image = self._load_image(ip) - mask = cv2.imread(mp, cv2.IMREAD_GRAYSCALE) - if mask is None: - raise IOError(f"cannot read mask {mp}") - mask = mask.astype(np.int64) - - if self.transform is not None: - image, mask = self.transform(image, mask) - return {"image": image, "mask": mask, - "name": os.path.splitext(os.path.basename(ip))[0]} diff --git a/code/framework/eval_ckpt.py b/code/framework/eval_ckpt.py deleted file mode 100644 index b07cf7cf3608816b0f86b950266c56cbe50b5c2b..0000000000000000000000000000000000000000 --- a/code/framework/eval_ckpt.py +++ /dev/null @@ -1,120 +0,0 @@ -"""Evaluate a trained framework checkpoint on a chosen dataset/split, with the full -metric set INCLUDING boundary metrics. Two uses, one script (no retraining): - - * C7 (boundary fidelity, in-domain): point --eval_dataset at the same dataset to - recompute boundary-Dice / NSD from a saved best.pth. - * C4 (cross-center): set --eval_dataset/--eval_protocol to a DIFFERENT but - label-compatible dataset (e.g. train busi? no — train cvc_clinicdb, eval - kvasir_seg; both binary polyp). num_classes/in_channels must match. - -Run from project root (…/NPJ), env seggen: - CUDA_VISIBLE_DEVICES=5 python -m framework.eval_ckpt \ - --ckpt results/baselines/cvc_clinicdb_official/unet/seed0/best.pth \ - --arch unet --encoder resnet50 \ - --data_root $DR --dataset cvc_clinicdb --protocol official \ - --eval_dataset kvasir_seg --eval_protocol official \ - --out_json results/crosscenter/cvc2kvasir_unet_seed0.json -""" -from __future__ import annotations - -import argparse -import json -import os -import sys - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -import numpy as np -import torch -from torch.utils.data import DataLoader - -from framework.config import Config # noqa: F401 (kept for parity / future YAML use) -from framework.models.registry import build_model, required_img_size -from framework.data.unified_dataset import UnifiedSegDataset -from framework.data.transforms import build_transform -from framework.metrics.metrics import per_image_metrics -from framework.metrics.boundary import boundary_metrics - - -def get_args(): - p = argparse.ArgumentParser("Evaluate a checkpoint (in-domain or cross-center) + boundary metrics") - p.add_argument("--ckpt", required=True) - p.add_argument("--arch", default="unet") - p.add_argument("--encoder", default="resnet50") - p.add_argument("--data_root", required=True) - p.add_argument("--dataset", required=True, help="dataset the ckpt was TRAINED on (sets in_ch/num_classes)") - p.add_argument("--protocol", required=True) - p.add_argument("--eval_dataset", default="", help="dataset to evaluate ON (default = --dataset)") - p.add_argument("--eval_protocol", default="", help="default = --protocol") - p.add_argument("--split", default="test") - p.add_argument("--img_size", type=int, default=256) - p.add_argument("--normalize", default="auto") - p.add_argument("--tol", type=float, default=2.0) - p.add_argument("--batch_size", type=int, default=16) - p.add_argument("--num_workers", type=int, default=6) - p.add_argument("--out_json", default="") - return p.parse_args() - - -def main(): - a = get_args() - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - img_size = required_img_size(a.arch) or a.img_size - eval_ds_name = a.eval_dataset or a.dataset - eval_proto = a.eval_protocol or a.protocol - - # in_ch / num_classes are fixed by the TRAIN dataset (how the model was built) - train_probe = UnifiedSegDataset(a.data_root, a.dataset, a.protocol, "test", transform=None) - in_ch, n_cls = train_probe.in_channels, train_probe.num_classes - - model = build_model(a.arch, in_channels=in_ch, num_classes=n_cls, img_size=img_size, - encoder=a.encoder, encoder_weights="none", pretrained_ckpt="") - ckpt = torch.load(a.ckpt, map_location="cpu", weights_only=False) - model.load_state_dict(ckpt["model"]) - model.to(device).eval() - - tf = build_transform(img_size, in_ch, train=False, aug="none", normalize=a.normalize) - ds = UnifiedSegDataset(a.data_root, eval_ds_name, eval_proto, a.split, - transform=tf, in_channels=in_ch, num_classes=n_cls) - if ds.num_classes != n_cls: - raise ValueError(f"label mismatch: train num_classes={n_cls} vs eval={ds.num_classes} " - f"({a.dataset}->{eval_ds_name}) — not label-compatible for cross-center.") - cross = (eval_ds_name != a.dataset) or (eval_proto != a.protocol) - print(f"[eval] ckpt={os.path.basename(a.ckpt)} train={a.dataset}/{a.protocol} " - f"eval={eval_ds_name}/{eval_proto}/{a.split} cross_center={cross} " - f"in_ch={in_ch} num_classes={n_cls} n={len(ds)}", flush=True) - - loader = DataLoader(ds, batch_size=a.batch_size, shuffle=False, num_workers=a.num_workers) - recs = [] - for batch in loader: - img = batch["image"].to(device, non_blocking=True) - msk = batch["mask"].numpy() - with torch.no_grad(): - pred = model(img).argmax(1).cpu().numpy() - for i in range(pred.shape[0]): - m = per_image_metrics(pred[i], msk[i], n_cls, - include_background=False, compute_hd95=True) - b = boundary_metrics(pred[i], msk[i], n_cls, tol=a.tol) - m.update(b) - recs.append(m) - - keys = [k for k, val in recs[0].items() if isinstance(val, (int, float))] # skip per_class dict - summary = {} - for k in keys: - v = np.array([r[k] for r in recs], dtype=np.float64) - v = v[~np.isnan(v)] - summary[k] = {"mean": round(float(v.mean()), 4) if v.size else None, - "std": round(float(v.std()), 4) if v.size else None, "n": int(v.size)} - out = {"ckpt": a.ckpt, "train": f"{a.dataset}/{a.protocol}", - "eval": f"{eval_ds_name}/{eval_proto}/{a.split}", "cross_center": cross, - "num_images": len(ds), "metrics": summary} - print(json.dumps(out, indent=2), flush=True) - if a.out_json: - os.makedirs(os.path.dirname(os.path.abspath(a.out_json)) or ".", exist_ok=True) - with open(a.out_json, "w") as f: - json.dump(out, f, indent=2) - print(f"[eval] wrote {a.out_json}", flush=True) - - -if __name__ == "__main__": - main() diff --git a/code/framework/metrics/boundary.py b/code/framework/metrics/boundary.py deleted file mode 100644 index 39cf69e90bad6489ef0b082ec43bcd6f4d2114c1..0000000000000000000000000000000000000000 --- a/code/framework/metrics/boundary.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Boundary-localized segmentation metrics (for the C7 boundary-fidelity analysis). - -Two standard measures, computed per foreground class then averaged: - * Normalized Surface Dice (NSD) @ tol: fraction of pred/gt surface points within - `tol` pixels of the other surface (Nikolov et al.); the medical-standard - boundary metric. - * Boundary-Dice @ tol: Dice between the tol-dilated pred and gt boundaries. - -Recomputable post-hoc from saved predictions/checkpoints, so adding it never -requires retraining (see framework/eval_ckpt.py). -""" -from __future__ import annotations - -import numpy as np - -try: - from scipy.ndimage import binary_erosion, binary_dilation, distance_transform_edt - _HAVE_SCIPY = True -except Exception: # pragma: no cover - _HAVE_SCIPY = False - - -def _surface(bin_mask: np.ndarray) -> np.ndarray: - return bin_mask ^ binary_erosion(bin_mask) - - -def _nsd_binary(pred: np.ndarray, gt: np.ndarray, tol: float) -> float: - sp, sg = _surface(pred), _surface(gt) - ssum = sp.sum() + sg.sum() - if sp.sum() == 0 and sg.sum() == 0: - return 1.0 - if ssum == 0: - return 0.0 - dt_to_gt = distance_transform_edt(~sg) - dt_to_pred = distance_transform_edt(~sp) - pred_close = (dt_to_gt[sp] <= tol).sum() - gt_close = (dt_to_pred[sg] <= tol).sum() - return float((pred_close + gt_close) / ssum) - - -def _bdice_binary(pred: np.ndarray, gt: np.ndarray, tol: int) -> float: - sp, sg = _surface(pred), _surface(gt) - if sp.sum() == 0 and sg.sum() == 0: - return 1.0 - spd = binary_dilation(sp, iterations=int(tol)) - sgd = binary_dilation(sg, iterations=int(tol)) - denom = spd.sum() + sgd.sum() - if denom == 0: - return 0.0 - return float(2.0 * (spd & sgd).sum() / denom) - - -def boundary_metrics(pred: np.ndarray, gt: np.ndarray, num_classes: int, - tol: float = 2.0) -> dict: - """Mean over foreground classes (1..num_classes-1). NaN if scipy missing.""" - if not _HAVE_SCIPY: - return {"nsd": float("nan"), "boundary_dice": float("nan")} - nsds, bdices = [], [] - for c in range(1, num_classes): - p, g = (pred == c), (gt == c) - if g.sum() == 0 and p.sum() == 0: - continue - nsds.append(_nsd_binary(p, g, tol)) - bdices.append(_bdice_binary(p, g, int(round(tol)))) - return { - "nsd": float(np.mean(nsds)) if nsds else float("nan"), - "boundary_dice": float(np.mean(bdices)) if bdices else float("nan"), - } diff --git a/code/framework/common/__init__.py b/code/framework/synth/__init__.py similarity index 100% rename from code/framework/common/__init__.py rename to code/framework/synth/__init__.py diff --git a/code/framework/synth/generative_baselines.py b/code/framework/synth/generative_baselines.py new file mode 100644 index 0000000000000000000000000000000000000000..337e9f40fb91ce03990411d3cfbde24c92147937 --- /dev/null +++ b/code/framework/synth/generative_baselines.py @@ -0,0 +1,112 @@ +"""Orchestration for the generative-augmentation SOTA baselines (category B). + +These methods are compared against our SegGen method. Each runs in its OWN conda +env (see envs/) because their dependency stacks conflict with the main framework. +The shared contract: every generator must emit paired (image, mask) into + + ///synth_/{images,masks}/ + +which the unified trainer then merges into the train split via --synth_train_dir. + +Kept baselines: + * SegGuidedDiff (diffusion, mask->image, medical, modern stack) -- best fit, USE-AS-IS + * SPADE (GAN, mask->image) -- ADAPT (needs sync_bn) + * ControlNet (diffusion, SD-finetune, mask->image) -- ADAPT (needs SD ckpt) + +Dropped (per scoping): StyleGAN2-ADA (no masks), LDM (dep hell + AE training). + +This module only BUILDS the commands + assembles the standard synth dir; it does +not import the repos (they live in separate envs). Run the printed commands in the +matching env, then call assemble_synth_dir() (env-agnostic) to lay out pairs. +""" +from __future__ import annotations + +import os +import shutil +from glob import glob + +SOTA = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sota")) + + +def assemble_synth_dir(generated_images_dir: str, masks_source_dir: str, + out_dir: str, strip_prefix: str = "condon_", + link: bool = True) -> int: + """Pair each generated image with the real mask it was conditioned on. + + Mask-conditioned generators name outputs after the conditioning mask + (SegGuidedDiff: 'condon_.png'). We recover the mask name, copy/link + the matching real mask, and place both under out_dir/{images,masks}/. + Returns the number of pairs assembled. + """ + img_out = os.path.join(out_dir, "images") + msk_out = os.path.join(out_dir, "masks") + os.makedirs(img_out, exist_ok=True) + os.makedirs(msk_out, exist_ok=True) + + n = 0 + for gp in sorted(glob(os.path.join(generated_images_dir, "*"))): + base = os.path.basename(gp) + stem = os.path.splitext(base)[0] + if strip_prefix and stem.startswith(strip_prefix): + mask_stem = stem[len(strip_prefix):] + else: + mask_stem = stem + cands = glob(os.path.join(masks_source_dir, mask_stem + ".*")) + if not cands: + continue + out_name = f"synth_{n:06d}" + dst_img = os.path.join(img_out, out_name + os.path.splitext(base)[1]) + dst_msk = os.path.join(msk_out, out_name + os.path.splitext(cands[0])[1]) + _place(gp, dst_img, link) + _place(cands[0], dst_msk, link) + n += 1 + return n + + +def _place(src, dst, link): + if os.path.exists(dst): + os.remove(dst) + if link: + os.symlink(os.path.abspath(src), dst) + else: + shutil.copy2(src, dst) + + +# ---- command builders (printed into run.sh; run in the matching conda env) ---- + +def segguideddiff_cmds(data_root, dataset, protocol, num_classes, in_channels, + img_size=256, epochs=400, sample_size=1000): + repo = os.path.join(SOTA, "segmentation-guided-diffusion") + img_dir = f"{data_root}/{dataset}/{protocol}/train/images" + seg_dir = f"{data_root}/{dataset}/{protocol}/train/masks" + train = (f"cd {repo} && python main.py --mode train --model_type DDIM " + f"--img_size {img_size} --num_img_channels {in_channels} --dataset {dataset} " + f"--img_dir {img_dir} --seg_dir {seg_dir} --segmentation_guided " + f"--num_segmentation_classes {num_classes} --num_epochs {epochs}") + synth = (f"cd {repo} && python main.py --mode eval_many --model_type DDIM " + f"--img_size {img_size} --num_img_channels {in_channels} --dataset {dataset} " + f"--seg_dir {seg_dir} --segmentation_guided " + f"--num_segmentation_classes {num_classes} --eval_sample_size {sample_size}") + return train, synth + + +def spade_cmds(data_root, dataset, protocol, num_classes, img_size=256, niter=100): + repo = os.path.join(SOTA, "SPADE") + img_dir = f"{data_root}/{dataset}/{protocol}/train/images" + lab_dir = f"{data_root}/{dataset}/{protocol}/train/masks" + setup = (f"cd {repo}/models/networks && " + f"git clone https://github.com/vacancy/Synchronized-BatchNorm-PyTorch && " + f"cp -r Synchronized-BatchNorm-PyTorch/sync_batchnorm .") + train = (f"cd {repo} && python train.py --name {dataset}_spade --dataset_mode custom " + f"--label_dir {lab_dir} --image_dir {img_dir} --label_nc {num_classes} " + f"--no_instance --crop_size {img_size} --load_size {int(img_size*1.12)} --niter {niter}") + synth = (f"cd {repo} && python test.py --name {dataset}_spade --dataset_mode custom " + f"--label_dir {lab_dir} --image_dir {img_dir} --label_nc {num_classes} " + f"--no_instance --results_dir ./synth_{dataset}") + return setup, train, synth + + +def controlnet_notes(): + return ("ControlNet: download SD v1.5 (~4GB), run tool_add_control.py, write a " + "MyDataset that colorizes integer masks to RGB hints + triples grayscale " + "images to 3ch, then tutorial_train.py. Run in env seggen-controlnet.") diff --git a/code/scripts/a100_nnum_eval512.sh b/code/scripts/a100_nnum_eval512.sh new file mode 100644 index 0000000000000000000000000000000000000000..a13259ac3b134f0784096b4d6c83537b49bd6494 --- /dev/null +++ b/code/scripts/a100_nnum_eval512.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# nnU-Net + U-Mamba @512 re-eval on a100 (CPU-only, does NOT touch GPUs). +# Re-scores cached predTs/predTs_umamba at eval_size 512 into results/unified512/ +# so they join the unified-512 table. ~64 cells, parallel (concurrency MAXJ). +set -u +cd /home/wzhang/LSC/Code/NPJ +source /opt/anaconda3/etc/profile.d/conda.sh +conda activate seggen +export OMP_NUM_THREADS=4 MKL_NUM_THREADS=4 OPENBLAS_NUM_THREADS=4 NUMEXPR_NUM_THREADS=4 +DR=/home/wzhang/LSC/Dataset/Segmentation/processed_unified +RAW=/home/wzhang/LSC/Code/NPJ/nnunet_workspace/raw +PRED_NN=/home/wzhang/LSC/Code/NPJ/nnunet_workspace/predTs +PRED_UM=/home/wzhang/LSC/Code/NPJ/nnunet_workspace/predTs_umamba +MAXJ=10 + +declare -A DS=( + [1]=cvc_clinicdb:official [2]=kvasir_seg:official [3]=fives:official + [4]=refuge2:official [5]=busi:fold01 [6]=idridd_segmentation:fold01 + [7]=acdc_png:official [8]=pannuke_semantic:fold01 [9]=medsegdb_isic2018:holdout + [10]=medsegdb_kits19:fold01 [11]=pannuke_semantic:fold02 [12]=pannuke_semantic:fold03 +) + +run=0 +for id in 1 2 3 4 5 6 7 8 9 10 11 12; do + IFS=: read -r ds proto <<< "${DS[$id]}" + for f in 0 1 2; do + for ap in "nnunet:$PRED_NN" "umamba:$PRED_UM"; do + arch=${ap%%:*}; pred=${ap#*:} + outdir=$pred/d${id}_f${f} + [ -d "$outdir" ] && ls -A "$outdir"/*.png >/dev/null 2>&1 || continue + ( + python framework/nnunet_eval.py --data_root "$DR" --dataset "$ds" --protocol "$proto" \ + --raw "$RAW" --dataset_id "$id" --fold "$f" --pred_dir "$outdir" --arch "$arch" \ + --exp_name unified512 --eval_size 512 \ + > /tmp/nnum512_${arch}_d${id}_f${f}.log 2>&1 \ + && echo "[ok] $arch d${id}_f${f} ($ds $proto)" \ + || echo "[FAIL] $arch d${id}_f${f} ($ds $proto)" + ) & + run=$((run+1)) + if [ "$run" -ge "$MAXJ" ]; then wait -n; run=$((run-1)); fi + done + done +done +wait +echo "NNUM_EVAL512_DONE" +n=$(find results/unified512 -path '*/nnunet/*/metrics.json' -o -path '*/umamba/*/metrics.json' 2>/dev/null | wc -l) +echo "unified512 nnunet+umamba metrics.json count: $n" diff --git a/code/scripts/a100_swin_transunet_3seed_eval512.py b/code/scripts/a100_swin_transunet_3seed_eval512.py new file mode 100644 index 0000000000000000000000000000000000000000..db58f869673018f86d5420dd372c6735dc287b07 --- /dev/null +++ b/code/scripts/a100_swin_transunet_3seed_eval512.py @@ -0,0 +1,58 @@ +"""Fill SwinUNet/TransUNet to FULL 3-seed @512 on a100 (their per-seed best.pth live here). +For every results/baselines//{swinunet,transunet}/seed/best.pth, copy it into the +unified512 tree and run eval_at_res.py --eval_size 512 --exp_name unified512. GPU 4/5 only +(A100 80G; PCI_BUS_ID). 64 evals. Then metrics.json get transferred to h800 + re-aggregated. +""" +import os, glob, shutil, subprocess, time + +CODE = "/home/wzhang/LSC/Code/NPJ" +DATA = "/home/wzhang/LSC/Dataset/Segmentation/processed_unified" +PY = "/opt/anaconda3/envs/seggen/bin/python" +BASE = CODE + "/results/baselines" # source per-seed weights +UNI = CODE + "/results/unified512" # eval_at_res writes here (out_root=results rel to CODE) +LOGD = "/tmp/sw_tr_3seed_logs"; os.makedirs(LOGD, exist_ok=True) +SLOTS = [4, 4, 4, 5, 5, 5] # GPU 4/5, 3 co-located evals each + +jobs = [] +for arch in ("swinunet", "transunet"): + for w in sorted(glob.glob(f"{BASE}/*/{arch}/seed*/best.pth")): + parts = w.split("/") + cell, seed = parts[-4], parts[-2] # , seedN + sd = int(seed.replace("seed", "")) + # parse cell -> dataset, protocol + ds, proto = None, None + for p in ("official", "holdout", "fold01", "fold02", "fold03"): + if cell.endswith("_" + p): + ds, proto = cell[:-(len(p) + 1)], p; break + out = f"{UNI}/{cell}/{arch}/{seed}" + jobs.append({"ds": ds, "proto": proto, "arch": arch, "seed": sd, "w": w, + "out": out, "mj": out + "/metrics.json", "tag": f"{cell}_{arch}_s{sd}"}) + +pending = [j for j in jobs if not os.path.isfile(j["mj"])] +print(f"[3seed] total={len(jobs)} done={len(jobs)-len(pending)} pending={len(pending)}", flush=True) + +def make_cmd(j, gpu): + enc = "R50-ViT-B_16" if j["arch"] == "transunet" else "resnet50" + os.makedirs(j["out"], exist_ok=True) + shutil.copy(j["w"], j["out"] + "/best.pth") + return (f"export CUDA_DEVICE_ORDER=PCI_BUS_ID CUDA_VISIBLE_DEVICES={gpu} " + f"OMP_NUM_THREADS=8 MKL_NUM_THREADS=8 OPENBLAS_NUM_THREADS=8 && cd {CODE} && " + f"{PY} framework/eval_at_res.py --data_root {DATA} --dataset {j['ds']} " + f"--protocol {j['proto']} --arch {j['arch']} --seed {j['seed']} --eval_size 512 " + f"--exp_name unified512 --encoder {enc}") + +running = {}; free = list(SLOTS); i = 0; ok = fail = 0 +while i < len(pending) or running: + while free and i < len(pending): + gpu = free.pop(0); j = pending[i]; i += 1 + lf = open(f"{LOGD}/{j['tag']}.log", "w") + p = subprocess.Popen(["bash", "-lc", make_cmd(j, gpu)], stdout=lf, stderr=subprocess.STDOUT) + running[id(p)] = (p, j, lf, gpu); print(f"[launch] gpu{gpu} {j['tag']}", flush=True) + time.sleep(6) + for k, (p, j, lf, gpu) in list(running.items()): + if p.poll() is not None: + lf.close(); okj = os.path.isfile(j["mj"]); ok += okj; fail += (not okj) + print(f"[finish] gpu{gpu} {j['tag']} rc={p.returncode} ok={okj}", flush=True) + del running[k]; free.append(gpu) +print(f"[3seed] ALL DONE ok={ok} fail={fail}", flush=True) +print("SWTR_3SEED_DONE", flush=True) diff --git a/code/scripts/h800_cache_data.sh b/code/scripts/h800_cache_data.sh new file mode 100644 index 0000000000000000000000000000000000000000..8aa5fa82903373528d2e97f131be0e0f7c6810a5 --- /dev/null +++ b/code/scripts/h800_cache_data.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Cache the dataset from the slow JuiceFS share to local RAID /data/temp for fast +# training reads. Parallel per-dataset cp -a (preserves pannuke hard links), overlaps +# JuiceFS small-file read latency. Run detached; log to /tmp/cache_data.log. +SRC=/mnt/tidal-alsh-share2/dataset/qinshengqian/research/c3/NPJ-ACM/Data +DST=/data/temp/NPJ-ACM/Data +mkdir -p "$DST" +echo "[start] $(date +%T)" +for d in acdc_png busi cvc_clinicdb fives idridd_segmentation kvasir_seg \ + medsegdb_isic2018 medsegdb_kits19 pannuke_semantic refuge2; do + ( cp -a "$SRC/$d" "$DST/" && echo "done $d $(date +%T)" ) & +done +wait +echo "CACHE_DONE $(date +%T)" diff --git a/code/scripts/h800_fetch_data.py b/code/scripts/h800_fetch_data.py new file mode 100644 index 0000000000000000000000000000000000000000..d93c8b80d6b5e03db2df5750da5cfa0145dd368b --- /dev/null +++ b/code/scripts/h800_fetch_data.py @@ -0,0 +1,31 @@ +"""On h800: download GenSegDataset tars from HF (via proxy+token), extract into Data/, +then remove the tars. Produces the processed_unified layout under Data/.""" +import os, glob, tarfile +from huggingface_hub import snapshot_download + +BASE = "/mnt/tidal-alsh-share2/dataset/qinshengqian/research/c3/NPJ-ACM/Data" +TARS = os.path.join(BASE, "_tars") + +print("[1] downloading tars ...", flush=True) +snapshot_download("MaybeRichard/GenSegDataset", repo_type="dataset", + allow_patterns=["*.tar", "README.md"], local_dir=TARS) + +print("[2] extracting ...", flush=True) +for t in sorted(glob.glob(os.path.join(TARS, "*.tar"))): + print(" extract", os.path.basename(t), flush=True) + with tarfile.open(t) as tf: + tf.extractall(BASE) + +rd = os.path.join(TARS, "README.md") +if os.path.isfile(rd): + os.replace(rd, os.path.join(BASE, "README.md")) + +print("[3] cleanup tars ...", flush=True) +for t in glob.glob(os.path.join(TARS, "*.tar")): + os.remove(t) +try: + os.rmdir(TARS) +except OSError: + pass + +print("DONE_DATA", flush=True) diff --git a/code/scripts/h800_parallel_extract.sh b/code/scripts/h800_parallel_extract.sh new file mode 100644 index 0000000000000000000000000000000000000000..261c15972e151edf2e5553e827e5c44946bc777e --- /dev/null +++ b/code/scripts/h800_parallel_extract.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Parallel extraction of the remaining GenSegDataset tars on h800's slow network share. +# Big archives are split by member-list into N chunks, each extracted by a separate +# `tar -x -T ` process, to saturate the share's parallel small-file throughput. +set -u +BASE=/mnt/tidal-alsh-share2/dataset/qinshengqian/research/c3/NPJ-ACM/Data +TARS=$BASE/_tars +WORK=/tmp/pextract +mkdir -p "$WORK" + +# dataset -> parallel chunk count (kits19 = most files) +launch_ds() { + local ds=$1 n=$2 tar="$TARS/$1.tar" + [ -f "$tar" ] || { echo "MISSING $tar"; return; } + if [ "$n" -le 1 ]; then + tar -xf "$tar" -C "$BASE" & + else + tar -tf "$tar" | grep -v '/$' > "$WORK/$ds.list" + split -n "l/$n" -d "$WORK/$ds.list" "$WORK/$ds.chunk." + for c in "$WORK/$ds.chunk."*; do + tar -xf "$tar" -C "$BASE" -T "$c" & + done + fi +} + +echo "[start] $(date +%T) launching parallel extraction" +launch_ds medsegdb_kits19 8 +launch_ds pannuke_semantic 4 +launch_ds refuge2 1 +echo "launched $(jobs -p | wc -l) parallel tar streams" +wait +echo "PEXTRACT_DONE $(date +%T)" + +# cleanup tars + work +rm -f "$TARS"/*.tar +rmdir "$TARS" 2>/dev/null || true +rm -rf "$WORK" +echo "CLEANUP_DONE $(date +%T)" diff --git a/code/scripts/h800_run_unified512.py b/code/scripts/h800_run_unified512.py new file mode 100644 index 0000000000000000000000000000000000000000..5f96e5468d4d19acc7fca3d764751307e573673d --- /dev/null +++ b/code/scripts/h800_run_unified512.py @@ -0,0 +1,80 @@ +"""8-GPU pool runner for the UNIFIED-512 conv retrain on h800 (L20Y x8). +Mirrors the baseline grid: 4 conv archs x the existing (dataset,protocol,seed) cells, +all at img_size 512. 1 job per GPU, resumable (skips cells whose metrics.json exists), +per-job log. occupy.py auto-yields. Run detached; tail /tmp/unified512_runner.log. +""" +import os, sys, subprocess, time + +CODE = "/mnt/tidal-alsh-share2/dataset/qinshengqian/research/c3/NPJ-ACM/Code" +DATA = "/data/temp/NPJ-ACM/Data" +WORK = "/data/temp/NPJ-ACM/work" # CWD; results -> WORK/results/unified512/... +PY = "/data/temp/miniconda3/envs/seggen/bin/python" +LOGD = WORK + "/logs_unified512" +PROXY = "http://10.140.15.68:3128" +NGPU, IMG, BATCH, EPOCHS = 8, 512, 8, 300 +ARCHS = ["unet", "unetpp", "deeplabv3plus", "attention_unet"] +CELLS = [ # (dataset, protocol, [seeds]) -- matches existing baseline structure + ("acdc_png", "official", [0, 1, 2]), + ("busi", "fold01", [0, 1, 2]), + ("cvc_clinicdb", "official", [0, 1, 2]), + ("fives", "official", [0, 1, 2]), + ("idridd_segmentation", "fold01", [0, 1, 2]), + ("kvasir_seg", "official", [0, 1, 2]), + ("medsegdb_isic2018", "holdout", [0, 1, 2]), + ("medsegdb_kits19", "fold01", [0, 1, 2]), + ("refuge2", "official", [0, 1, 2]), + ("pannuke_semantic", "fold01", [0, 1, 2]), + ("pannuke_semantic", "fold02", [0]), + ("pannuke_semantic", "fold03", [0]), +] +os.makedirs(LOGD, exist_ok=True) +os.makedirs(WORK, exist_ok=True) + +jobs = [] +for ds, proto, seeds in CELLS: + for arch in ARCHS: + for s in seeds: + out = f"{WORK}/results/unified512/{ds}_{proto}/{arch}/seed{s}/metrics.json" + jobs.append({"ds": ds, "proto": proto, "arch": arch, "seed": s, "out": out, + "tag": f"{ds}_{proto}_{arch}_s{s}"}) + +def make_cmd(j, gpu): + return ( + f"export CUDA_DEVICE_ORDER=PCI_BUS_ID CUDA_VISIBLE_DEVICES={gpu} " + f"OMP_NUM_THREADS=8 MKL_NUM_THREADS=8 OPENBLAS_NUM_THREADS=8 " + f"https_proxy={PROXY} http_proxy={PROXY} && cd {WORK} && " + f"{PY} {CODE}/framework/train.py --data_root {DATA} --dataset {j['ds']} --protocol {j['proto']} " + f"--arch {j['arch']} --img_size {IMG} --batch_size {BATCH} --num_workers 8 --amp bf16 " + f"--exp_name unified512 --seed {j['seed']} --no-visualize --encoder resnet50 --encoder_weights imagenet " + f"--epochs {EPOCHS} && " + f"{PY} {CODE}/framework/test.py --data_root {DATA} --dataset {j['ds']} --protocol {j['proto']} " + f"--arch {j['arch']} --img_size {IMG} --exp_name unified512 --seed {j['seed']} --encoder resnet50" + ) + +pending = [j for j in jobs if not os.path.isfile(j["out"])] +print(f"[runner] total={len(jobs)} done={len(jobs)-len(pending)} pending={len(pending)}", flush=True) + +running = {} # gpu -> (Popen, job, start) +free = list(range(NGPU)) +done = ok = fail = 0 +i = 0 +while i < len(pending) or running: + while free and i < len(pending): + gpu = free.pop(0) + j = pending[i]; i += 1 + lf = open(f"{LOGD}/{j['tag']}.log", "w") + p = subprocess.Popen(["bash", "-lc", make_cmd(j, gpu)], stdout=lf, stderr=subprocess.STDOUT) + running[gpu] = (p, j, time.time(), lf) + print(f"[launch] gpu{gpu} {j['tag']}", flush=True) + time.sleep(20) + for gpu, (p, j, st, lf) in list(running.items()): + if p.poll() is not None: + lf.close(); done += 1 + okj = os.path.isfile(j["out"]) + ok += okj; fail += (not okj) + mins = (time.time() - st) / 60 + print(f"[finish] gpu{gpu} {j['tag']} rc={p.returncode} ok={okj} " + f"{mins:.0f}min ({done}/{len(pending)} done, {fail} failed)", flush=True) + del running[gpu]; free.append(gpu); free.sort() +print(f"[runner] ALL DONE. ok={ok} fail={fail} of {len(pending)}", flush=True) +print("UNIFIED512_RUNNER_DONE", flush=True) diff --git a/code/scripts/h800_setup_seggen.sh b/code/scripts/h800_setup_seggen.sh new file mode 100644 index 0000000000000000000000000000000000000000..a217792498b242ef24347959f77cc069c482991a --- /dev/null +++ b/code/scripts/h800_setup_seggen.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Build the seggen conda env on h800 (L20Y / CUDA 12.8). torch installed FIRST (cu128) +# so SMP/MONAI don't pull a mismatched torch. Run detached; log to /tmp/seggen_env.log. +set -e +export https_proxy=http://10.140.15.68:3128 http_proxy=http://10.140.15.68:3128 +CONDA=/data/temp/miniconda3 +PROXY=http://10.140.15.68:3128 + +echo "[1] create env (python 3.11) -- conda-forge only, avoids defaults-channel ToS block" +$CONDA/bin/conda create -y -n seggen -c conda-forge --override-channels python=3.11 pip + +PIP="$CONDA/envs/seggen/bin/pip" +echo "[2] torch cu128 (host CUDA 12.8, >2.6)" +$PIP install --proxy "$PROXY" torch torchvision --index-url https://download.pytorch.org/whl/cu128 + +echo "[3] seg stack (torch already present -> no reinstall)" +$PIP install --proxy "$PROXY" \ + segmentation-models-pytorch albumentations==2.0.8 monai medpy \ + opencv-python-headless numpy pyyaml timm einops ml-collections tqdm \ + diffusers==0.21.4 datasets==2.14.5 + +echo "[4] verify" +$CONDA/envs/seggen/bin/python - <<'PY' +import torch, segmentation_models_pytorch, monai, albumentations, timm, cv2 +print("torch", torch.__version__, "| cuda", torch.version.cuda, "| avail", torch.cuda.is_available(), "| ndev", torch.cuda.device_count()) +print("smp ok, monai ok, albumentations ok, timm ok, cv2", cv2.__version__) +PY +echo "SEGGEN_ENV_DONE" diff --git a/code/scripts/h800_swin_transunet_eval512.py b/code/scripts/h800_swin_transunet_eval512.py new file mode 100644 index 0000000000000000000000000000000000000000..6e7b7a8b968bb9bafaad829dfb0db7894f78330f --- /dev/null +++ b/code/scripts/h800_swin_transunet_eval512.py @@ -0,0 +1,68 @@ +"""Phase-1 of unified-512 re-eval on h800: re-score SwinUNet/TransUNet at eval_size 512. +These are res-locked (224/256) so we DON'T retrain — we load their HF-curated best-seed +weights, run at native input, resize preds+GT to 512, write metrics.json into the +unified512 results tree. 12 cells x {swinunet, transunet} = 24 evals, 8-GPU pool. +""" +import os, glob, shutil, subprocess, time + +CODE = "/mnt/tidal-alsh-share2/dataset/qinshengqian/research/c3/NPJ-ACM/Code" +DATA = "/data/temp/NPJ-ACM/Data" +WORK = "/data/temp/NPJ-ACM/work" +PY = "/data/temp/miniconda3/envs/seggen/bin/python" +HFW = WORK + "/hf_weights/weights/framework" # /{swinunet,transunet}.pth +RES = WORK + "/results/unified512" # eval_at_res writes here (out_root=results rel to WORK) +LOGD = WORK + "/logs_eval512"; os.makedirs(LOGD, exist_ok=True) +PROXY = "http://10.140.15.68:3128" +PROTOS = ["official", "holdout", "fold01", "fold02", "fold03"] +NGPU = 8 + +def split_cell(cell): + for p in PROTOS: + if cell.endswith("_" + p): + return cell[:-(len(p) + 1)], p + raise ValueError("bad cell " + cell) + +jobs = [] +for cell_dir in sorted(glob.glob(HFW + "/*")): + cell = os.path.basename(cell_dir) + ds, proto = split_cell(cell) + for arch in ("swinunet", "transunet"): + w = f"{cell_dir}/{arch}.pth" + if not os.path.isfile(w): + continue + out = f"{RES}/{cell}/{arch}/seed0" + jobs.append({"ds": ds, "proto": proto, "arch": arch, "w": w, "out": out, + "tag": f"{cell}_{arch}", "mj": f"{out}/metrics.json"}) + +pending = [j for j in jobs if not os.path.isfile(j["mj"])] +print(f"[eval512] total={len(jobs)} done={len(jobs)-len(pending)} pending={len(pending)}", flush=True) + +def make_cmd(j, gpu): + enc = "R50-ViT-B_16" if j["arch"] == "transunet" else "resnet50" + # place the HF weight as best.pth where eval_at_res.py expects it + os.makedirs(j["out"], exist_ok=True) + shutil.copy(j["w"], j["out"] + "/best.pth") + return ( + f"export CUDA_DEVICE_ORDER=PCI_BUS_ID CUDA_VISIBLE_DEVICES={gpu} " + f"OMP_NUM_THREADS=8 MKL_NUM_THREADS=8 OPENBLAS_NUM_THREADS=8 " + f"https_proxy={PROXY} http_proxy={PROXY} && cd {WORK} && " + f"{PY} {CODE}/framework/eval_at_res.py --data_root {DATA} --dataset {j['ds']} " + f"--protocol {j['proto']} --arch {j['arch']} --seed 0 --eval_size 512 " + f"--exp_name unified512 --encoder {enc}" + ) + +running = {}; free = list(range(NGPU)); i = 0; ok = fail = 0 +while i < len(pending) or running: + while free and i < len(pending): + gpu = free.pop(0); j = pending[i]; i += 1 + lf = open(f"{LOGD}/{j['tag']}.log", "w") + p = subprocess.Popen(["bash", "-lc", make_cmd(j, gpu)], stdout=lf, stderr=subprocess.STDOUT) + running[gpu] = (p, j, lf); print(f"[launch] gpu{gpu} {j['tag']}", flush=True) + time.sleep(8) + for gpu, (p, j, lf) in list(running.items()): + if p.poll() is not None: + lf.close(); okj = os.path.isfile(j["mj"]); ok += okj; fail += (not okj) + print(f"[finish] gpu{gpu} {j['tag']} rc={p.returncode} ok={okj}", flush=True) + del running[gpu]; free.append(gpu); free.sort() +print(f"[eval512] ALL DONE ok={ok} fail={fail}", flush=True) +print("SWIN_TRANSUNET_EVAL512_DONE", flush=True) diff --git a/code/scripts/hf_update_unified512.py b/code/scripts/hf_update_unified512.py new file mode 100644 index 0000000000000000000000000000000000000000..60afddabb90d57b03748d2d2923e6fb6cfbbb898 --- /dev/null +++ b/code/scripts/hf_update_unified512.py @@ -0,0 +1,26 @@ +"""Push the unified-512 deliverables to HF MaybeRichard/GenSeg-Baselines (replacing the +old 256 report): enhanced aggregate.py (code) + unified-512 summary.* + all metrics.json +(results/). Run with the write token + the REAL HF endpoint (local env defaults to the +hf-mirror download mirror, which does NOT accept authenticated writes). + HF_ENDPOINT=https://huggingface.co T= python3 scripts/hf_update_unified512.py +""" +import os +from huggingface_hub import HfApi + +REPO = "MaybeRichard/GenSeg-Baselines" +LOCAL = "/home/richard/Documents/Code/ZJU/SegGen" +api = HfApi(token=os.environ["T"], endpoint="https://huggingface.co") + +print("[1] code: enhanced aggregate.py") +api.upload_file(path_or_fileobj=f"{LOCAL}/framework/report/aggregate.py", repo_id=REPO, + repo_type="model", path_in_repo="code/framework/report/aggregate.py", + commit_message="aggregate.py: unified-512 intro + per-class + Wilcoxon") + +print("[2] results: unified-512 summary.* + all metrics.json -> results/") +api.upload_folder(folder_path=f"{LOCAL}/results/unified512", repo_id=REPO, repo_type="model", + path_in_repo="results", + allow_patterns=["**/metrics.json", "summary.html", "summary.csv", + "summary.md", "summary.tex"], + commit_message="results: unified-512 (8 methods @512, per-class + significance)") + +print("DONE") diff --git a/code/scripts/hf_upload_gensegdataset.py b/code/scripts/hf_upload_gensegdataset.py new file mode 100644 index 0000000000000000000000000000000000000000..ef12425e4bf7f9880d449d8b6cc71bf5d898295d --- /dev/null +++ b/code/scripts/hf_upload_gensegdataset.py @@ -0,0 +1,23 @@ +"""Upload GenSegDataset (processed_unified mirror + dataset card) to the Hugging +Face Hub as a PRIVATE dataset repo. Storage is content-addressed, so fold-duplicated +images collapse to unique blobs. Resumable via upload_large_folder.""" +import sys +from huggingface_hub import HfApi + +REPO = "MaybeRichard/GenSegDataset" +DATA = "/home/wzhang/LSC/Dataset/Segmentation/processed_unified" +CARD = "/home/wzhang/LSC/Dataset/Segmentation/GenSegDataset_README.md" + +api = HfApi() +api.create_repo(REPO, repo_type="dataset", private=True, exist_ok=True) +print("repo ready:", REPO, flush=True) + +# big data upload (resumable, dedups blobs, parallel) +api.upload_large_folder(repo_id=REPO, repo_type="dataset", folder_path=DATA) +print("folder uploaded", flush=True) + +# dataset card last so it is the final README at the repo root +api.upload_file(path_or_fileobj=CARD, path_in_repo="README.md", + repo_id=REPO, repo_type="dataset", + commit_message="add dataset card") +print("UPLOAD_DONE", flush=True) diff --git a/code/scripts/hf_upload_tars.py b/code/scripts/hf_upload_tars.py new file mode 100644 index 0000000000000000000000000000000000000000..0e48d942f8f8f4e81a9b21811ac0a136a3101274 --- /dev/null +++ b/code/scripts/hf_upload_tars.py @@ -0,0 +1,31 @@ +"""Recovery upload: ship GenSegDataset as ONE tar per subset (10 files) instead of +~110k loose PNGs, to stay under HF's 128-commit/hour limit. Resets the partially +populated repo, uploads .tar + the dataset card.""" +import os, subprocess +from huggingface_hub import HfApi + +REPO = "MaybeRichard/GenSegDataset" +DATA = "/home/wzhang/LSC/Dataset/Segmentation/processed_unified" +TARS = "/home/wzhang/LSC/Dataset/Segmentation/hf_tars" +CARD = "/home/wzhang/LSC/Dataset/Segmentation/GenSegDataset_README.md" + +os.makedirs(TARS, exist_ok=True) +for ds in sorted(os.listdir(DATA)): + if not os.path.isdir(os.path.join(DATA, ds)): + continue + out = os.path.join(TARS, ds + ".tar") + if os.path.exists(out) and os.path.getsize(out) > 0: + print("skip (exists):", ds, flush=True); continue + # -h dereferences symlinks so fold-shared images are materialized into the tar + subprocess.run(["tar", "-chf", out, "-C", DATA, ds], check=True) + print("tarred %s -> %.1f MB" % (ds, os.path.getsize(out) / 1e6), flush=True) + +api = HfApi() +api.delete_repo(REPO, repo_type="dataset", missing_ok=True) +api.create_repo(REPO, repo_type="dataset", private=True, exist_ok=True) +print("repo reset:", REPO, flush=True) + +api.upload_file(path_or_fileobj=CARD, path_in_repo="README.md", + repo_id=REPO, repo_type="dataset", commit_message="dataset card") +api.upload_large_folder(repo_id=REPO, repo_type="dataset", folder_path=TARS) +print("UPLOAD_DONE", flush=True) diff --git a/code/scripts/p1/backbones.py b/code/scripts/p1/backbones.py new file mode 100644 index 0000000000000000000000000000000000000000..25838aaf400c110ca96e6622bbfa2bb4801a8dd2 --- /dev/null +++ b/code/scripts/p1/backbones.py @@ -0,0 +1,44 @@ +"""build_backbone: instantiate one of {jit, pixelgen, deco, pixeldit} pixel-space +denoisers for mask-concat conditioning. Each returns a net callable as net(x, t, y) +-> (N, C>=img_ch, H, W); the caller slices [:, :img_ch] (backbone-agnostic decouple). +in_channels = img_channels + cond_channels; a single dummy class (num_classes=1). +Unified 'Base' tier (~130-150M) for the P1 backbone bake-off (P1 = native arch under a +common flow-matching objective; perceptual/DCT/FD losses are P2 levers, not used here).""" +import os +import sys + +_SOTA = "/home/wzhang/LSC/Code/NPJ/sota" + + +def _add(path): + if path not in sys.path: + sys.path.insert(0, path) + + +def build_backbone(backbone: str, model_name: str, img_size: int, + in_channels: int, num_classes: int = 1): + bk = backbone.lower() + if bk == "jit": + _add(os.path.join(_SOTA, "JiT")) + from model_jit import JiT_models + return JiT_models[model_name](input_size=img_size, in_channels=in_channels, + num_classes=num_classes) + if bk == "pixelgen": + _add(os.path.join(_SOTA, "PixelGen", "src", "models", "transformer")) + import importlib + jit = importlib.import_module("JiT") # PixelGen's self-contained JiT.py + return jit.JiT_models[model_name](input_size=img_size, in_channels=in_channels, + num_classes=num_classes) + if bk == "deco": + _add(os.path.join(_SOTA, "DeCo", "src", "models", "transformer")) + from dit_c2i_DeCo import PixNerDiT + return PixNerDiT(in_channels=in_channels, patch_size=16, num_groups=12, + hidden_size=768, hidden_size_x=32, num_blocks=13, + num_cond_blocks=12, num_classes=num_classes) + if bk == "pixeldit": + _add(os.path.join(_SOTA, "PixelDiT")) + from pixdit_core.pixeldit_c2i import PixDiT + return PixDiT(in_channels=in_channels, num_groups=10, hidden_size=640, + pixel_hidden_size=16, patch_depth=9, pixel_depth=4, + patch_size=16, num_classes=num_classes) + raise ValueError(f"unknown backbone: {backbone}") diff --git a/code/scripts/p1/fd_lever.py b/code/scripts/p1/fd_lever.py new file mode 100644 index 0000000000000000000000000000000000000000..1b6277a4a94c1048969ad6ef32855e922299a1cd --- /dev/null +++ b/code/scripts/p1/fd_lever.py @@ -0,0 +1,99 @@ +"""FD-lever ablation on the recommended P2 base (JiT): refine p1_jit_{ds} with FD loss +-> sample -> downstream. Compares +JiT-FD vs +JiT(native) vs real. 18 jobs on GPU0-5.""" +import os, time, json, subprocess + +ROOT = "/home/wzhang/LSC/Code/NPJ" +DR = "/home/wzhang/LSC/Dataset/Segmentation/processed_unified" +PY = "/opt/anaconda3/envs/seggen/bin/python" +GPUS = [0, 1, 2, 3, 4, 5] +os.chdir(ROOT) +LOGD = os.path.join(ROOT, "logs", "fdlever") +os.makedirs(LOGD, exist_ok=True) +def log(m): + line = f"[{time.strftime('%F %T')}] {m}" + open(os.path.join(LOGD, "status.md"), "a").write(line + "\n"); print(line, flush=True) + +DSETS = {"isic": ("medsegdb_isic2018", "holdout", 2582), "kvasir": ("kvasir_seg", "official", 800)} +NS = [50, 100]; SEEDS = [0, 1, 2] +jobs = {} +def add(jid, cmd, deps=(), done_path=None, done_min=1): + jobs[jid] = {"cmd": cmd, "deps": list(deps), "done_path": done_path, "done_min": done_min, + "state": "pending", "tries": 0, "gpu": None} + +for dk, (ds, proto, tot) in DSETS.items(): + base = f"pretrained/pixdiff/p1_jit_{dk}.pt" + out = f"pretrained/pixdiff/p1_jitfd_{dk}.pt" + cmd = (f"{PY} -m framework.synth.pixdiff.train_fd --base_ckpt {base} --data_root {DR} " + f"--dataset {ds} --protocol {proto} --train_fraction 1.0 --epochs 150 --batch_size 16 " + f"--amp bf16 --fd_weight 0.5 --out_ckpt {out} --log_interval 100") + add(f"genfd_{dk}", cmd, done_path=os.path.join(ROOT, out)) + for N in NS: + f = N / tot + sd = f"{DR}/{ds}/{proto}/synth_p1_jitfd_{dk}_f{N}" + cmd = (f"{PY} -m framework.synth.pixdiff.sample --ckpt {out} --data_root {DR} --dataset {ds} " + f"--protocol {proto} --train_fraction {f} --fraction_seed 0 --n_per_mask 4 --mask_aug " + f"--num_steps 50 --out_dir {sd}") + add(f"samp_jitfd_{dk}_N{N}", cmd, deps=[f"genfd_{dk}"], done_path=os.path.join(sd, "images"), done_min=N * 4) + for S in SEEDS: + exp = f"p1_jitfd_{dk}_N{N}" + mp = os.path.join(ROOT, f"results/{exp}/{ds}_{proto}/unet/seed{S}/metrics.json") + cmd = (f"{PY} framework/train.py --data_root {DR} --dataset {ds} --protocol {proto} --arch unet " + f"--encoder resnet50 --aug standard --epochs 400 --train_fraction {f} --fraction_seed 0 " + f"--synth_train_dir {sd} --exp_name {exp} --amp bf16 --seed {S} " + f"&& {PY} framework/test.py --data_root {DR} --dataset {ds} --protocol {proto} --arch unet " + f"--encoder resnet50 --aug standard --exp_name {exp} --seed {S}") + add(f"seg_jitfd_{dk}_N{N}_s{S}", cmd, deps=[f"samp_jitfd_{dk}_N{N}"], done_path=mp) + +def is_done(j): + p = j["done_path"] + if not p or not os.path.exists(p): return False + if os.path.isdir(p): + try: return len(os.listdir(p)) >= j["done_min"] + except OSError: return False + return True +def aggregate(): + res = {} + for dk, (ds, proto, tot) in DSETS.items(): + for N in NS: + exp = f"p1_jitfd_{dk}_N{N}"; ious = []; dices = [] + for S in SEEDS: + mp = f"results/{exp}/{ds}_{proto}/unet/seed{S}/metrics.json" + if os.path.exists(mp): + try: + m = json.load(open(mp))["metrics"]; ious.append(m["iou_mean"]); dices.append(m["dice_mean"]) + except Exception: pass + if ious: + res[f"{dk}_N{N}_jitfd"] = {"iou_mean": sum(ious) / len(ious), "dice_mean": sum(dices) / len(dices), + "n_seeds": len(ious), "iou_seeds": ious} + json.dump(res, open(os.path.join(LOGD, "fd_results.json"), "w"), indent=2) + +for jid, j in jobs.items(): + if is_done(j): j["state"] = "done" +def deps_done(j): return all(jobs[d]["state"] == "done" for d in j["deps"]) +running = {}; free = set(GPUS); last = 0 +log(f"START {len(jobs)} jobs on {GPUS} ({sum(1 for j in jobs.values() if j['state']=='done')} pre-done)") +while True: + if all(j["state"] in ("done", "failed") for j in jobs.values()): break + for jid, j in jobs.items(): + if not free: break + if j["state"] == "pending" and deps_done(j): + if is_done(j): j["state"] = "done"; continue + g = free.pop() + env = dict(os.environ, CUDA_DEVICE_ORDER="PCI_BUS_ID", CUDA_VISIBLE_DEVICES=str(g), + TORCHDYNAMO_DISABLE="1", PYTHONPATH=".", OMP_NUM_THREADS="4") + lf = open(os.path.join(LOGD, jid + ".log"), "a") + p = subprocess.Popen(j["cmd"], shell=True, env=env, stdout=lf, stderr=subprocess.STDOUT, cwd=ROOT) + running[g] = (jid, p, lf); j["state"] = "running"; j["gpu"] = g; j["tries"] += 1 + log(f"LAUNCH {jid} GPU{g} try{j['tries']}") + for g, (jid, p, lf) in list(running.items()): + rc = p.poll() + if rc is None: continue + lf.close(); del running[g]; free.add(g); j = jobs[jid] + if is_done(j): j["state"] = "done"; log(f"DONE {jid}") + elif j["tries"] < 2: j["state"] = "pending"; log(f"RETRY {jid} rc={rc}") + else: j["state"] = "failed"; log(f"FAILED {jid} rc={rc}") + if time.time() - last > 180: + cnt = {s: sum(1 for j in jobs.values() if j["state"] == s) for s in ("done", "running", "pending", "failed")} + log(f"SUMMARY {cnt}"); aggregate(); last = time.time() + time.sleep(10) +aggregate(); log("ALL DONE"); print("FD_LEVER_DONE", flush=True) diff --git a/code/scripts/p1/fd_results.json b/code/scripts/p1/fd_results.json new file mode 100644 index 0000000000000000000000000000000000000000..f6d21d0d7523ceee6fb407f4952a61eb79fb5ca0 --- /dev/null +++ b/code/scripts/p1/fd_results.json @@ -0,0 +1,42 @@ +{ + "isic_N50_jitfd": { + "iou_mean": 0.807111643913793, + "dice_mean": 0.8785879691414072, + "n_seeds": 3, + "iou_seeds": [ + 0.8075343832113462, + 0.8052902472456404, + 0.8085103012843926 + ] + }, + "isic_N100_jitfd": { + "iou_mean": 0.8194000537587307, + "dice_mean": 0.8883052260740664, + "n_seeds": 3, + "iou_seeds": [ + 0.815320694079291, + 0.8190540649488038, + 0.8238254022480974 + ] + }, + "kvasir_N50_jitfd": { + "iou_mean": 0.7743735031913296, + "dice_mean": 0.8502822525848202, + "n_seeds": 3, + "iou_seeds": [ + 0.7818383912178852, + 0.7792812954940015, + 0.7620008228621021 + ] + }, + "kvasir_N100_jitfd": { + "iou_mean": 0.8237618615778238, + "dice_mean": 0.8922327796820345, + "n_seeds": 3, + "iou_seeds": [ + 0.8173440030838561, + 0.8195464262764907, + 0.8343951553731246 + ] + } +} \ No newline at end of file diff --git a/code/scripts/p1/fid_and_viz.py b/code/scripts/p1/fid_and_viz.py new file mode 100644 index 0000000000000000000000000000000000000000..c3394e3c3a06dd2364e9fc5c7eebe884aa4028ee --- /dev/null +++ b/code/scripts/p1/fid_and_viz.py @@ -0,0 +1,130 @@ +"""FID (1-2k samples) per backbone x dataset + clear same-mask aligned viz. +A) fid-sample: train_fraction=1.0, mask_aug, n_per_mask -> ~1.6-2.6k synth; FID vs real train. +B) align-sample: f50 masks, NO aug, 1/mask -> all backbones share identical real masks -> aligned grid. +Then pytorch_fid per pair + build [mask|real|4 backbones] grids. GPU0-5 pool.""" +import os, time, json, re, subprocess +import numpy as np +import matplotlib; matplotlib.use("Agg") +import matplotlib.pyplot as plt +from PIL import Image + +ROOT = "/home/wzhang/LSC/Code/NPJ"; DR = "/home/wzhang/LSC/Dataset/Segmentation/processed_unified" +PY = "/opt/anaconda3/envs/seggen/bin/python"; GPUS = [0, 1, 2, 3, 4, 5] +os.chdir(ROOT); LOGD = os.path.join(ROOT, "logs", "fidviz"); os.makedirs(LOGD, exist_ok=True) +def log(m): + line = f"[{time.strftime('%F %T')}] {m}"; open(os.path.join(LOGD, "status.md"), "a").write(line + "\n"); print(line, flush=True) +# (ds, proto, total, npm_for_fid) +DSETS = {"isic": ("medsegdb_isic2018", "holdout", 2582, 1), + "kvasir": ("kvasir_seg", "official", 800, 2), + "busi": ("busi", "fold01", 545, 3)} +BKS = ["jit", "pixelgen", "deco", "pixeldit"]; LAB = {"jit": "JiT", "pixelgen": "PixelGen", "deco": "DeCo", "pixeldit": "PixelDiT"} +jobs = {} +def add(jid, cmd, deps=(), done_path=None, done_min=1): + jobs[jid] = {"cmd": cmd, "deps": list(deps), "done_path": done_path, "done_min": done_min, "state": "pending", "tries": 0, "gpu": None} + +for bk in BKS: + for dk, (ds, proto, tot, npm) in DSETS.items(): + ck = f"pretrained/pixdiff/p1_{bk}_{dk}.pt" + fsd = f"{DR}/{ds}/{proto}/synth_fid_{bk}_{dk}" + add(f"fidsamp_{bk}_{dk}", + f"{PY} -m framework.synth.pixdiff.sample --ckpt {ck} --data_root {DR} --dataset {ds} --protocol {proto} " + f"--train_fraction 1.0 --fraction_seed 0 --n_per_mask {npm} --mask_aug --num_steps 50 --out_dir {fsd}", + done_path=os.path.join(fsd, "images"), done_min=int(0.8 * tot * npm)) + real = f"{DR}/{ds}/{proto}/train/images" + flog = os.path.join(LOGD, f"fid_{bk}_{dk}.log"); fok = os.path.join(LOGD, f"fid_{bk}_{dk}.ok") + add(f"fid_{bk}_{dk}", + f"{PY} -m pytorch_fid {real} {fsd}/images --device cuda --batch-size 64 > {flog} 2>&1 && grep -q FID {flog} && touch {fok}", + deps=[f"fidsamp_{bk}_{dk}"], done_path=fok) + f50 = 50 / tot; asd = f"{DR}/{ds}/{proto}/synth_align_{bk}_{dk}" + add(f"alignsamp_{bk}_{dk}", + f"{PY} -m framework.synth.pixdiff.sample --ckpt {ck} --data_root {DR} --dataset {ds} --protocol {proto} " + f"--train_fraction {f50} --fraction_seed 0 --n_per_mask 1 --num_steps 50 --out_dir {asd}", + done_path=os.path.join(asd, "images"), done_min=40) + +def is_done(j): + p = j["done_path"] + if not p or not os.path.exists(p): return False + if os.path.isdir(p): + try: return len(os.listdir(p)) >= j["done_min"] + except OSError: return False + return True +for jid, j in jobs.items(): + if is_done(j): j["state"] = "done" +def deps_done(j): return all(jobs[d]["state"] == "done" for d in j["deps"]) +running = {}; free = set(GPUS); last = 0 +log(f"START {len(jobs)} jobs on {GPUS}") +while True: + if all(j["state"] in ("done", "failed") for j in jobs.values()): break + for jid, j in jobs.items(): + if not free: break + if j["state"] == "pending" and deps_done(j): + if is_done(j): j["state"] = "done"; continue + g = free.pop() + env = dict(os.environ, CUDA_DEVICE_ORDER="PCI_BUS_ID", CUDA_VISIBLE_DEVICES=str(g), TORCHDYNAMO_DISABLE="1", PYTHONPATH=".", OMP_NUM_THREADS="4") + lf = open(os.path.join(LOGD, jid + ".log"), "a") + p = subprocess.Popen(j["cmd"], shell=True, env=env, stdout=lf, stderr=subprocess.STDOUT, cwd=ROOT) + running[g] = (jid, p, lf); j["state"] = "running"; j["gpu"] = g; j["tries"] += 1 + log(f"LAUNCH {jid} GPU{g} try{j['tries']}") + for g, (jid, p, lf) in list(running.items()): + rc = p.poll() + if rc is None: continue + lf.close(); del running[g]; free.add(g); j = jobs[jid] + if is_done(j): j["state"] = "done"; log(f"DONE {jid}") + elif j["tries"] < 2: j["state"] = "pending"; log(f"RETRY {jid} rc={rc}") + else: j["state"] = "failed"; log(f"FAILED {jid} rc={rc}") + if time.time() - last > 180: + cnt = {s: sum(1 for j in jobs.values() if j["state"] == s) for s in ("done", "running", "pending", "failed")}; log(f"SUMMARY {cnt}"); last = time.time() + time.sleep(8) + +# ---- parse FID ---- +fid = {} +for bk in BKS: + for dk in DSETS: + lg = os.path.join(LOGD, f"fid_{bk}_{dk}.log") + if os.path.exists(lg): + m = re.findall(r"FID:\s*([0-9.]+)", open(lg).read()) + if m: fid[f"{dk}_{bk}"] = float(m[-1]) +json.dump(fid, open(os.path.join(LOGD, "fid_results.json"), "w"), indent=2) +log(f"FID: {fid}") + +# ---- aligned grids ([mask | real | 4 backbones], same real mask per column) ---- +def rgb(p): return np.asarray(Image.open(p).convert("RGB").resize((256, 256))) +def gray(p): return np.asarray(Image.open(p).convert("L").resize((256, 256))) +def fmap(d): + p = os.path.join(d, "images"); m = {} + if os.path.isdir(p): + for f in sorted(os.listdir(p)): + if f.endswith(".png"): m.setdefault(f[:-4].split("__")[0], os.path.join(p, f)) + return m +for dk, (ds, proto, tot, npm) in DSETS.items(): + base = f"{DR}/{ds}/{proto}"; ri, rm = f"{base}/train/images", f"{base}/train/masks" + maps = {bk: fmap(f"{base}/synth_align_{bk}_{dk}") for bk in BKS} + common = set(os.path.splitext(f)[0] for f in os.listdir(ri) if f.endswith(".png")) + for bk in BKS: common &= set(maps[bk].keys()) + common = sorted(common); ncol = min(6, len(common)) + if ncol == 0: continue + idx = [round(i * (len(common) - 1) / (ncol - 1)) for i in range(ncol)] if ncol > 1 else [0] + cases = [common[i] for i in idx] + rows = [("Conditioning mask", "mask"), ("Real image", "real")] + [(LAB[bk], bk) for bk in BKS] + fig, ax = plt.subplots(len(rows), ncol, figsize=(ncol * 1.9, len(rows) * 1.95)) + for r, (labr, kind) in enumerate(rows): + for c, bs in enumerate(cases): + a = ax[r][c] + try: + mk = gray(f"{rm}/{bs}.png") + if kind == "mask": + a.imshow(mk, cmap="gray") + elif kind == "real": + a.imshow(rgb(f"{ri}/{bs}.png")); a.contour((mk > 127).astype(float), levels=[0.5], colors=["#19f04b"], linewidths=1.0) + else: + a.imshow(rgb(maps[kind][bs])); a.contour((mk > 127).astype(float), levels=[0.5], colors=["#19f04b"], linewidths=1.0) + except Exception: + a.imshow(np.ones((256, 256, 3))); a.text(0.5, 0.5, "n/a", ha="center", va="center", transform=a.transAxes, fontsize=8) + a.set_xticks([]); a.set_yticks([]) + for s in a.spines.values(): s.set_visible(False) + if c == 0: a.set_ylabel(labr, fontsize=10, rotation=90, va="center", labelpad=8, color=("#111" if r < 2 else "#1a3b8b")) + fig.suptitle(f"{dk.upper()} — same-mask aligned: every backbone generates the SAME real mask (row 1)\n" + f"Row2=real image; rows 3-6=each backbone's mask-conditioned synthesis (green=that mask). Proves mask guidance.", fontsize=10) + plt.tight_layout(rect=[0.02, 0, 1, 0.94]); plt.savefig(f"/tmp/p1_aligned_{dk}.png", dpi=145, bbox_inches="tight", facecolor="white") + log(f"aligned grid saved /tmp/p1_aligned_{dk}.png") +log("ALL DONE"); print("FIDVIZ_DONE", flush=True) diff --git a/code/scripts/p1/fid_fixed.py b/code/scripts/p1/fid_fixed.py new file mode 100644 index 0000000000000000000000000000000000000000..d410eae4629bcc9793a70f0e48e92a45567011a4 --- /dev/null +++ b/code/scripts/p1/fid_fixed.py @@ -0,0 +1,37 @@ +"""Corrected FID: resize REAL train images to 256 (synth already 256) so pytorch_fid's +collate doesn't choke on variable native sizes (Kvasir/BUSI). FID(real256, synth) per pair.""" +import os, re, json, subprocess +from PIL import Image + +ROOT = "/home/wzhang/LSC/Code/NPJ"; DR = "/home/wzhang/LSC/Dataset/Segmentation/processed_unified" +PY = "/opt/anaconda3/envs/seggen/bin/python" +DSETS = {"isic": ("medsegdb_isic2018", "holdout"), "kvasir": ("kvasir_seg", "official"), "busi": ("busi", "fold01")} +BKS = ["jit", "pixelgen", "deco", "pixeldit"] + +real256 = {} +for dk, (ds, proto) in DSETS.items(): + src = f"{DR}/{ds}/{proto}/train/images"; dst = f"/tmp/real256_{dk}"; os.makedirs(dst, exist_ok=True) + for f in os.listdir(src): + if f.lower().endswith((".png", ".jpg", ".jpeg")): + o = f"{dst}/{os.path.splitext(f)[0]}.png" + if not os.path.exists(o): + Image.open(f"{src}/{f}").convert("RGB").resize((256, 256)).save(o) + real256[dk] = dst + print(f"[resize] real {dk}: {len(os.listdir(dst))} imgs", flush=True) + +fid = {} +for bk in BKS: + for dk, (ds, proto) in DSETS.items(): + synth = f"{DR}/{ds}/{proto}/synth_fid_{bk}_{dk}/images" + if not os.path.isdir(synth) or len(os.listdir(synth)) < 100: + print(f"[skip] {dk} {bk}: synth missing/small", flush=True); continue + env = dict(os.environ, CUDA_DEVICE_ORDER="PCI_BUS_ID", CUDA_VISIBLE_DEVICES="0") + r = subprocess.run([PY, "-m", "pytorch_fid", real256[dk], synth, "--device", "cuda", "--batch-size", "50"], + capture_output=True, text=True, env=env) + m = re.findall(r"FID:\s*([0-9.]+)", r.stdout + r.stderr) + if m: + fid[f"{dk}_{bk}"] = round(float(m[-1]), 2); print(f"[FID] {dk} {bk} = {m[-1]}", flush=True) + else: + print(f"[FAIL] {dk} {bk}: {(r.stderr or r.stdout)[-300:]}", flush=True) + json.dump(fid, open(f"{ROOT}/logs/fidviz/fid_results.json", "w"), indent=2) +print("FID_FIXED_DONE", json.dumps(fid), flush=True) diff --git a/code/scripts/p1/fid_results.json b/code/scripts/p1/fid_results.json new file mode 100644 index 0000000000000000000000000000000000000000..4b0692a61dabdfe33a9f3b6fd96a932bc4d26e24 --- /dev/null +++ b/code/scripts/p1/fid_results.json @@ -0,0 +1,14 @@ +{ + "isic_jit": 107.96, + "kvasir_jit": 109.09, + "busi_jit": 139.28, + "isic_pixelgen": 107.63, + "kvasir_pixelgen": 118.44, + "busi_pixelgen": 142.91, + "isic_deco": 141.84, + "kvasir_deco": 114.43, + "busi_deco": 174.36, + "isic_pixeldit": 109.87, + "kvasir_pixeldit": 112.83, + "busi_pixeldit": 151.82 +} \ No newline at end of file diff --git a/code/scripts/p1/gen_prdc.json b/code/scripts/p1/gen_prdc.json new file mode 100644 index 0000000000000000000000000000000000000000..a6bfacd5c088167f85f3bb28b5f3e5c2ea37345f --- /dev/null +++ b/code/scripts/p1/gen_prdc.json @@ -0,0 +1,74 @@ +{ + "isic_jit": { + "precision": 0.301, + "recall": 0.107, + "density": 0.122, + "coverage": 0.146 + }, + "kvasir_jit": { + "precision": 0.402, + "recall": 0.021, + "density": 0.169, + "coverage": 0.294 + }, + "busi_jit": { + "precision": 0.271, + "recall": 0.174, + "density": 0.103, + "coverage": 0.332 + }, + "isic_pixelgen": { + "precision": 0.319, + "recall": 0.101, + "density": 0.122, + "coverage": 0.14 + }, + "kvasir_pixelgen": { + "precision": 0.388, + "recall": 0.009, + "density": 0.159, + "coverage": 0.242 + }, + "busi_pixelgen": { + "precision": 0.275, + "recall": 0.261, + "density": 0.093, + "coverage": 0.29 + }, + "isic_deco": { + "precision": 0.14, + "recall": 0.04, + "density": 0.04, + "coverage": 0.048 + }, + "kvasir_deco": { + "precision": 0.287, + "recall": 0.01, + "density": 0.11, + "coverage": 0.195 + }, + "busi_deco": { + "precision": 0.081, + "recall": 0.042, + "density": 0.023, + "coverage": 0.088 + }, + "isic_pixeldit": { + "precision": 0.213, + "recall": 0.065, + "density": 0.077, + "coverage": 0.106 + }, + "kvasir_pixeldit": { + "precision": 0.324, + "recall": 0.027, + "density": 0.123, + "coverage": 0.201 + }, + "busi_pixeldit": { + "precision": 0.245, + "recall": 0.051, + "density": 0.077, + "coverage": 0.182 + } +} \ No newline at end of file diff --git a/code/scripts/p1/gen_prdc.py b/code/scripts/p1/gen_prdc.py new file mode 100644 index 0000000000000000000000000000000000000000..de5c64a6989d34dbddebfee348effd63612a6158 --- /dev/null +++ b/code/scripts/p1/gen_prdc.py @@ -0,0 +1,58 @@ +"""Generation-side Precision/Recall (Kynkaanniemi) + Density/Coverage (Naeem) on +InceptionV3 (pytorch-fid) features. Precision=fidelity (fake in real manifold), +Recall=diversity/coverage (real covered by fake). No sklearn: kNN via torch.cdist.""" +import os, sys, json, random +import numpy as np, torch +from PIL import Image +sys.path.insert(0, "/home/wzhang/LSC/Code/NPJ") +from framework.synth.pixdiff.fd_loss import InceptionFeatures + +DR = "/home/wzhang/LSC/Dataset/Segmentation/processed_unified" +DSETS = {"isic": ("medsegdb_isic2018", "holdout"), "kvasir": ("kvasir_seg", "official"), "busi": ("busi", "fold01")} +BKS = ["jit", "pixelgen", "deco", "pixeldit"] +dev = "cuda"; CAP = 2000; K = 5 +inc = InceptionFeatures().to(dev).eval() + +def feats(d, cap=CAP): + fs = sorted(f for f in os.listdir(d) if f.lower().endswith((".png", ".jpg", ".jpeg"))) + if len(fs) > cap: + random.seed(0); fs = random.sample(fs, cap) + out = [] + for i in range(0, len(fs), 64): + b = [] + for f in fs[i:i + 64]: + im = Image.open(os.path.join(d, f)).convert("RGB").resize((256, 256)) + b.append(torch.from_numpy(np.asarray(im)).permute(2, 0, 1).float() / 255.) + with torch.no_grad(): + out.append(inc(torch.stack(b).to(dev)).cpu()) + return torch.cat(out) + +def knn_radius(X, k): + d = torch.cdist(X, X); d.fill_diagonal_(float("inf")); return d.kthvalue(k, dim=1).values + +def prdc(R, F, k=K): + R, F = R.to(dev), F.to(dev) + rr = knn_radius(R, k); ff = knn_radius(F, k); drf = torch.cdist(R, F) + prec = (drf <= rr[:, None]).any(0).float().mean().item() + rec = (drf <= ff[None, :]).any(1).float().mean().item() + dens = ((drf <= rr[:, None]).sum(0).float().mean() / k).item() + cov = (drf <= rr[:, None]).any(1).float().mean().item() + return prec, rec, dens, cov + +realf = {} +for dk, (ds, proto) in DSETS.items(): + realf[dk] = feats(f"{DR}/{ds}/{proto}/train/images") + print(f"[real] {dk}: {realf[dk].shape}", flush=True) + +res = {} +for bk in BKS: + for dk, (ds, proto) in DSETS.items(): + sd = f"{DR}/{ds}/{proto}/synth_fid_{bk}_{dk}/images" + if not os.path.isdir(sd): + print(f"[skip] {dk} {bk}"); continue + F = feats(sd) + p, r, de, c = prdc(realf[dk], F) + res[f"{dk}_{bk}"] = {"precision": round(p, 3), "recall": round(r, 3), "density": round(de, 3), "coverage": round(c, 3)} + print(f"[PRDC] {dk} {bk}: {res[f'{dk}_{bk}']}", flush=True) + json.dump(res, open("/home/wzhang/LSC/Code/NPJ/logs/fidviz/gen_prdc.json", "w"), indent=2) +print("PRDC_DONE", flush=True) diff --git a/code/scripts/p1/make_jit_vs_fd.py b/code/scripts/p1/make_jit_vs_fd.py new file mode 100644 index 0000000000000000000000000000000000000000..6f3b27bf6751fee59f721ebca399073a35998f72 --- /dev/null +++ b/code/scripts/p1/make_jit_vs_fd.py @@ -0,0 +1,65 @@ +"""Same-mask JiT(native) vs JiT-FD comparison: does FD-perceptual sharpen the synth? +Samples JiT-FD on the SAME no-aug f50 masks the native JiT align-set used, builds +[mask | real | JiT native | JiT-FD] grids for ISIC + Kvasir.""" +import os, subprocess +import numpy as np +import matplotlib; matplotlib.use("Agg") +import matplotlib.pyplot as plt +from PIL import Image + +ROOT = "/home/wzhang/LSC/Code/NPJ"; DR = "/home/wzhang/LSC/Dataset/Segmentation/processed_unified" +PY = "/opt/anaconda3/envs/seggen/bin/python" +DSETS = {"isic": ("medsegdb_isic2018", "holdout", 2582), "kvasir": ("kvasir_seg", "official", 800)} + +def sample(ckpt, ds, proto, frac, out): + if os.path.isdir(out + "/images") and len(os.listdir(out + "/images")) >= 40: + print(f"[skip-sample] {out} exists", flush=True); return + env = dict(os.environ, CUDA_DEVICE_ORDER="PCI_BUS_ID", CUDA_VISIBLE_DEVICES="0", + TORCHDYNAMO_DISABLE="1", PYTHONPATH=".", OMP_NUM_THREADS="4") + subprocess.run([PY, "-m", "framework.synth.pixdiff.sample", "--ckpt", ckpt, "--data_root", DR, + "--dataset", ds, "--protocol", proto, "--train_fraction", str(frac), + "--fraction_seed", "0", "--n_per_mask", "1", "--num_steps", "50", "--out_dir", out], + env=env, cwd=ROOT, check=True) + print(f"[sampled] {out}", flush=True) + +def fmap(d): + p = os.path.join(d, "images"); m = {} + if os.path.isdir(p): + for f in sorted(os.listdir(p)): + if f.endswith(".png"): m.setdefault(f[:-4].split("__")[0], os.path.join(p, f)) + return m +def rgb(p): return np.asarray(Image.open(p).convert("RGB").resize((256, 256))) +def gray(p): return np.asarray(Image.open(p).convert("L").resize((256, 256))) + +for dk, (ds, proto, tot) in DSETS.items(): + f50 = 50 / tot + fd_out = f"{DR}/{ds}/{proto}/synth_alignfd_jitfd_{dk}" + sample(f"pretrained/pixdiff/p1_jitfd_{dk}.pt", ds, proto, f50, fd_out) + base = f"{DR}/{ds}/{proto}"; ri, rm = f"{base}/train/images", f"{base}/train/masks" + nat = fmap(f"{base}/synth_align_jit_{dk}"); fd = fmap(fd_out) + common = set(os.path.splitext(f)[0] for f in os.listdir(ri) if f.endswith(".png")) & set(nat) & set(fd) + common = sorted(common); ncol = min(6, len(common)) + idx = [round(i * (len(common) - 1) / (ncol - 1)) for i in range(ncol)] if ncol > 1 else [0] + cases = [common[i] for i in idx] + rows = [("Conditioning mask", "mask"), ("Real", "real"), ("JiT (native, P1)", nat), ("JiT-FD (FD-感知)", fd)] + fig, ax = plt.subplots(len(rows), ncol, figsize=(ncol * 2.1, len(rows) * 2.15)) + for r, (lab, src) in enumerate(rows): + for c, bs in enumerate(cases): + a = ax[r][c] + try: + mk = gray(f"{rm}/{bs}.png") + if src == "mask": a.imshow(mk, cmap="gray") + elif src == "real": a.imshow(rgb(f"{ri}/{bs}.png")) + else: a.imshow(rgb(src[bs])) + if src not in ("mask",): a.contour((mk > 127).astype(float), levels=[0.5], colors=["#19f04b"], linewidths=0.9) + except Exception: + a.imshow(np.ones((256, 256, 3))); a.text(0.5, 0.5, "n/a", ha="center", va="center", transform=a.transAxes) + a.set_xticks([]); a.set_yticks([]) + for s in a.spines.values(): s.set_visible(False) + if c == 0: a.set_ylabel(lab, fontsize=11, rotation=90, va="center", labelpad=8, + color=("#111" if r < 2 else "#1a3b8b"), fontweight=("bold" if r == 3 else "normal")) + fig.suptitle(f"{dk.upper()} — 原生 JiT vs JiT-FD(同掩码):FD-感知精修是否更锐?", fontsize=12) + plt.tight_layout(rect=[0.03, 0, 1, 0.95]) + out = f"/tmp/jit_vs_fd_{dk}.png"; plt.savefig(out, dpi=150, bbox_inches="tight", facecolor="white") + print(f"[grid] {out}", flush=True) +print("JIT_VS_FD_DONE", flush=True) diff --git a/code/scripts/p1/p1_busi_master.py b/code/scripts/p1/p1_busi_master.py new file mode 100644 index 0000000000000000000000000000000000000000..8efcbb0708040dd98e1cbee7ecc3a1d1d68c46ff --- /dev/null +++ b/code/scripts/p1/p1_busi_master.py @@ -0,0 +1,166 @@ +"""P1 master orchestrator: DAG scheduler over GPU0-5 for the backbone bake-off. +Phases: A) 8 generators (4 backbones x 2 datasets, amortized, 50k steps) + B) 16 sampling jobs (per gen x N in {50,100}, mask_aug n_per_mask=4) + C) 60 downstream seg runs (real + 4 backbones) x 2 ds x 2 N x 3 seeds +Single GPU per job (no DDP needed: 84 independent jobs). Retry-once on failure. +Resumable (skips done outputs). Rolling aggregate -> logs/p1master/p1_results.json.""" +import os, sys, time, json, subprocess, statistics as st + +ROOT = "/home/wzhang/LSC/Code/NPJ" +DR = "/home/wzhang/LSC/Dataset/Segmentation/processed_unified" +PY = "/opt/anaconda3/envs/seggen/bin/python" +GPUS = [0, 1, 2, 3, 4, 5] +os.chdir(ROOT) +LOGD = os.path.join(ROOT, "logs", "p1busi") +os.makedirs(LOGD, exist_ok=True) + +def log(m): + line = f"[{time.strftime('%F %T')}] {m}" + with open(os.path.join(LOGD, "status.md"), "a") as f: + f.write(line + "\n") + print(line, flush=True) + +DSETS = {"busi": ("busi", "fold01", 545)} +BKS = ["jit", "pixelgen", "deco", "pixeldit"] +NS = [50, 100] +SEEDS = [0, 1, 2] + +jobs = {} +def add(jid, cmd, deps=(), done_path=None, done_min=1): + jobs[jid] = {"cmd": cmd, "deps": list(deps), "done_path": done_path, + "done_min": done_min, "state": "pending", "tries": 0, "gpu": None} + +# Phase A: generators +for bk in BKS: + for dk, (ds, proto, tot) in DSETS.items(): + out = f"pretrained/pixdiff/p1_{bk}_{dk}.pt" + cmd = (f"{PY} -m framework.synth.pixdiff.train --data_root {DR} --dataset {ds} " + f"--protocol {proto} --backbone {bk} --img_size 256 --batch_size 16 " + f"--epochs 100000 --max_steps 50000 --lr 1e-4 --amp bf16 " + f"--train_fraction 1.0 --fraction_seed 0 --out_ckpt {out} --log_interval 500") + add(f"gen_{bk}_{dk}", cmd, done_path=os.path.join(ROOT, out)) + +# Phase B: sampling +for bk in BKS: + for dk, (ds, proto, tot) in DSETS.items(): + ck = f"pretrained/pixdiff/p1_{bk}_{dk}.pt" + for N in NS: + f = N / tot + sd = f"{DR}/{ds}/{proto}/synth_p1_{bk}_{dk}_f{N}" + cmd = (f"{PY} -m framework.synth.pixdiff.sample --ckpt {ck} --data_root {DR} " + f"--dataset {ds} --protocol {proto} --train_fraction {f} --fraction_seed 0 " + f"--n_per_mask 4 --mask_aug --num_steps 50 --out_dir {sd}") + add(f"samp_{bk}_{dk}_N{N}", cmd, deps=[f"gen_{bk}_{dk}"], + done_path=os.path.join(sd, "images"), done_min=N * 4) + +# Phase C: downstream +def mpath(exp, ds, proto, S): + return os.path.join(ROOT, f"results/{exp}/{ds}_{proto}/unet/seed{S}/metrics.json") + +def seg_cmd(ds, proto, f, exp, S, synth=None): + base = (f"{PY} framework/train.py --data_root {DR} --dataset {ds} --protocol {proto} " + f"--arch unet --encoder resnet50 --aug standard --epochs 400 " + f"--train_fraction {f} --fraction_seed 0 --exp_name {exp} --amp bf16 --seed {S}") + if synth: + base += f" --synth_train_dir {synth}" + test = (f"{PY} framework/test.py --data_root {DR} --dataset {ds} --protocol {proto} " + f"--arch unet --encoder resnet50 --aug standard --exp_name {exp} --seed {S}") + return base + " && " + test + +for dk, (ds, proto, tot) in DSETS.items(): + for N in NS: + f = N / tot + for S in SEEDS: + exp = f"p1_real_{dk}_N{N}" + add(f"seg_real_{dk}_N{N}_s{S}", seg_cmd(ds, proto, f, exp, S), + done_path=mpath(exp, ds, proto, S)) + for bk in BKS: + sd = f"{DR}/{ds}/{proto}/synth_p1_{bk}_{dk}_f{N}" + for S in SEEDS: + exp = f"p1_{bk}_{dk}_N{N}" + add(f"seg_{bk}_{dk}_N{N}_s{S}", seg_cmd(ds, proto, f, exp, S, synth=sd), + deps=[f"samp_{bk}_{dk}_N{N}"], done_path=mpath(exp, ds, proto, S)) + +def is_done(j): + p = j["done_path"] + if not p or not os.path.exists(p): + return False + if os.path.isdir(p): + try: + return len(os.listdir(p)) >= j["done_min"] + except OSError: + return False + return True + +def aggregate(): + res = {} + for dk, (ds, proto, tot) in DSETS.items(): + for N in NS: + for arm in ["real"] + BKS: + exp = f"p1_{arm}_{dk}_N{N}" + ious, dices = [], [] + for S in SEEDS: + mp = mpath(exp, ds, proto, S) + if os.path.exists(mp): + try: + m = json.load(open(mp))["metrics"] + ious.append(m["iou_mean"]); dices.append(m["dice_mean"]) + except Exception: + pass + if ious: + res[f"{dk}_N{N}_{arm}"] = { + "iou_mean": sum(ious) / len(ious), "dice_mean": sum(dices) / len(dices), + "n_seeds": len(ious), "iou_seeds": ious, "dice_seeds": dices} + json.dump(res, open(os.path.join(LOGD, "p1_results.json"), "w"), indent=2) + +for jid, j in jobs.items(): + if is_done(j): + j["state"] = "done" +def deps_done(j): + return all(jobs[d]["state"] == "done" for d in j["deps"]) + +running = {} +free = set(GPUS) +MAXTRIES = 2 +log(f"START {len(jobs)} jobs on GPUs {GPUS} ({sum(1 for j in jobs.values() if j['state']=='done')} pre-done)") +last_summary = 0 +while True: + if all(j["state"] in ("done", "failed") for j in jobs.values()): + break + for jid, j in jobs.items(): + if not free: + break + if j["state"] == "pending" and deps_done(j): + if is_done(j): + j["state"] = "done"; continue + g = free.pop() + env = dict(os.environ, CUDA_DEVICE_ORDER="PCI_BUS_ID", + CUDA_VISIBLE_DEVICES=str(g), TORCHDYNAMO_DISABLE="1", + PYTHONPATH=".", OMP_NUM_THREADS="4") + lf = open(os.path.join(LOGD, jid + ".log"), "a") + p = subprocess.Popen(j["cmd"], shell=True, env=env, stdout=lf, + stderr=subprocess.STDOUT, cwd=ROOT) + running[g] = (jid, p, lf); j["state"] = "running"; j["gpu"] = g; j["tries"] += 1 + log(f"LAUNCH {jid} GPU{g} try{j['tries']}") + for g, (jid, p, lf) in list(running.items()): + rc = p.poll() + if rc is None: + continue + lf.close(); del running[g]; free.add(g) + j = jobs[jid] + if is_done(j): + j["state"] = "done"; log(f"DONE {jid} rc={rc}") + elif j["tries"] < MAXTRIES: + j["state"] = "pending"; log(f"RETRY {jid} rc={rc}") + else: + j["state"] = "failed"; log(f"FAILED {jid} rc={rc}") + if time.time() - last_summary > 300: + cnt = {s: sum(1 for j in jobs.values() if j["state"] == s) + for s in ("done", "running", "pending", "failed")} + log(f"SUMMARY {cnt} | running={sorted(j['gpu'] for j in jobs.values() if j['state']=='running')}") + aggregate(); last_summary = time.time() + time.sleep(10) + +aggregate() +log("ALL DONE") +print("P1_MASTER_DONE", flush=True) diff --git a/code/scripts/p1/p1_busi_results.json b/code/scripts/p1/p1_busi_results.json new file mode 100644 index 0000000000000000000000000000000000000000..7dfed9fa7653fb4c6c4dee90b9100179b5934e70 --- /dev/null +++ b/code/scripts/p1/p1_busi_results.json @@ -0,0 +1,152 @@ +{ + "busi_N50_real": { + "iou_mean": 0.5264299887746967, + "dice_mean": 0.6150173362667782, + "n_seeds": 3, + "iou_seeds": [ + 0.5264031863116967, + 0.5078566908181252, + 0.5450300891942683 + ], + "dice_seeds": [ + 0.6158220746914147, + 0.5943401974078463, + 0.6348897367010737 + ] + }, + "busi_N50_jit": { + "iou_mean": 0.6034184804314947, + "dice_mean": 0.6819841561663282, + "n_seeds": 3, + "iou_seeds": [ + 0.6151347744667092, + 0.5876539837190357, + 0.6074666831087394 + ], + "dice_seeds": [ + 0.689949667483754, + 0.6696459718689409, + 0.6863568291462898 + ] + }, + "busi_N50_pixelgen": { + "iou_mean": 0.5931307319807805, + "dice_mean": 0.6689307916999434, + "n_seeds": 3, + "iou_seeds": [ + 0.5920417792314931, + 0.5799457559831734, + 0.6074046607276752 + ], + "dice_seeds": [ + 0.666457437158763, + 0.6579920482874959, + 0.6823428896535711 + ] + }, + "busi_N50_deco": { + "iou_mean": 0.5979877144880544, + "dice_mean": 0.6759540945542658, + "n_seeds": 3, + "iou_seeds": [ + 0.5926858046978442, + 0.5927788031039587, + 0.6084985356623608 + ], + "dice_seeds": [ + 0.6700259199200342, + 0.6746954264901631, + 0.6831409372526002 + ] + }, + "busi_N50_pixeldit": { + "iou_mean": 0.607193388275135, + "dice_mean": 0.6873956052246571, + "n_seeds": 3, + "iou_seeds": [ + 0.6123619477064693, + 0.611641987503641, + 0.5975762296152947 + ], + "dice_seeds": [ + 0.6897889057784018, + 0.6909085054783096, + 0.6814894044172604 + ] + }, + "busi_N100_real": { + "iou_mean": 0.5901633542778459, + "dice_mean": 0.672577797062739, + "n_seeds": 3, + "iou_seeds": [ + 0.596288318471246, + 0.5925297498972647, + 0.581671994465027 + ], + "dice_seeds": [ + 0.6805305551414621, + 0.6720643475113856, + 0.6651384885353694 + ] + }, + "busi_N100_jit": { + "iou_mean": 0.638692683336804, + "dice_mean": 0.7151948555339698, + "n_seeds": 3, + "iou_seeds": [ + 0.638767115763446, + 0.6464336918630936, + 0.6308772423838722 + ], + "dice_seeds": [ + 0.7157337836019346, + 0.7221307950206755, + 0.7077199879792992 + ] + }, + "busi_N100_pixelgen": { + "iou_mean": 0.6466251026809454, + "dice_mean": 0.7228950392456911, + "n_seeds": 3, + "iou_seeds": [ + 0.6475516801145157, + 0.6421441721654421, + 0.6501794557628784 + ], + "dice_seeds": [ + 0.7219398382769914, + 0.7182700866504312, + 0.7284751928096508 + ] + }, + "busi_N100_deco": { + "iou_mean": 0.6285762758560305, + "dice_mean": 0.7061591091011407, + "n_seeds": 3, + "iou_seeds": [ + 0.6474242343217572, + 0.6371748339087213, + 0.6011297593376128 + ], + "dice_seeds": [ + 0.7268623914793524, + 0.7158535119640104, + 0.6757614238600594 + ] + }, + "busi_N100_pixeldit": { + "iou_mean": 0.6540538953088814, + "dice_mean": 0.7316955478068173, + "n_seeds": 3, + "iou_seeds": [ + 0.6616519138889801, + 0.6470095283658505, + 0.6535002436718134 + ], + "dice_seeds": [ + 0.7383933408398845, + 0.7258913137588962, + 0.730801988821671 + ] + } +} \ No newline at end of file diff --git a/code/scripts/p1/p1_full_metrics.json b/code/scripts/p1/p1_full_metrics.json new file mode 100644 index 0000000000000000000000000000000000000000..c3389f715cc02d301de66648fd6fd899c200b4b5 --- /dev/null +++ b/code/scripts/p1/p1_full_metrics.json @@ -0,0 +1,182 @@ +{ + "isic_N50_real": { + "dice": 85.16, + "iou": 77.49, + "sensitivity": 85.34, + "precision": 90.16 + }, + "isic_N50_jit": { + "dice": 87.74, + "iou": 80.52, + "sensitivity": 88.36, + "precision": 90.69 + }, + "isic_N50_pixelgen": { + "dice": 87.9, + "iou": 80.71, + "sensitivity": 88.68, + "precision": 90.82 + }, + "isic_N50_deco": { + "dice": 87.27, + "iou": 79.96, + "sensitivity": 87.87, + "precision": 90.63 + }, + "isic_N50_pixeldit": { + "dice": 87.68, + "iou": 80.52, + "sensitivity": 88.33, + "precision": 90.58 + }, + "isic_N100_real": { + "dice": 87.31, + "iou": 80.18, + "sensitivity": 87.78, + "precision": 90.47 + }, + "isic_N100_jit": { + "dice": 88.52, + "iou": 81.66, + "sensitivity": 89.46, + "precision": 90.72 + }, + "isic_N100_pixelgen": { + "dice": 88.95, + "iou": 82.13, + "sensitivity": 90.15, + "precision": 90.83 + }, + "isic_N100_deco": { + "dice": 88.15, + "iou": 81.19, + "sensitivity": 89.85, + "precision": 89.97 + }, + "isic_N100_pixeldit": { + "dice": 88.6, + "iou": 81.7, + "sensitivity": 89.26, + "precision": 91.13 + }, + "kvasir_N50_real": { + "dice": 83.75, + "iou": 75.35, + "sensitivity": 83.39, + "precision": 88.81 + }, + "kvasir_N50_jit": { + "dice": 86.75, + "iou": 79.2, + "sensitivity": 86.93, + "precision": 89.85 + }, + "kvasir_N50_pixelgen": { + "dice": 86.16, + "iou": 78.42, + "sensitivity": 86.95, + "precision": 89.06 + }, + "kvasir_N50_deco": { + "dice": 88.03, + "iou": 80.84, + "sensitivity": 87.64, + "precision": 90.75 + }, + "kvasir_N50_pixeldit": { + "dice": 85.87, + "iou": 78.97, + "sensitivity": 85.23, + "precision": 89.44 + }, + "kvasir_N100_real": { + "dice": 86.95, + "iou": 79.5, + "sensitivity": 87.53, + "precision": 90.66 + }, + "kvasir_N100_jit": { + "dice": 88.5, + "iou": 81.62, + "sensitivity": 87.71, + "precision": 92.71 + }, + "kvasir_N100_pixelgen": { + "dice": 86.94, + "iou": 80.32, + "sensitivity": 86.37, + "precision": 91.14 + }, + "kvasir_N100_deco": { + "dice": 88.14, + "iou": 81.22, + "sensitivity": 88.95, + "precision": 90.6 + }, + "kvasir_N100_pixeldit": { + "dice": 89.52, + "iou": 82.83, + "sensitivity": 89.42, + "precision": 91.78 + }, + "busi_N50_real": { + "dice": 61.5, + "iou": 52.64, + "sensitivity": 59.37, + "precision": 72.68 + }, + "busi_N50_jit": { + "dice": 68.2, + "iou": 60.34, + "sensitivity": 68.58, + "precision": 73.26 + }, + "busi_N50_pixelgen": { + "dice": 66.89, + "iou": 59.31, + "sensitivity": 65.33, + "precision": 77.14 + }, + "busi_N50_deco": { + "dice": 67.6, + "iou": 59.8, + "sensitivity": 66.72, + "precision": 75.06 + }, + "busi_N50_pixeldit": { + "dice": 68.74, + "iou": 60.72, + "sensitivity": 67.82, + "precision": 75.91 + }, + "busi_N100_real": { + "dice": 67.26, + "iou": 59.02, + "sensitivity": 66.01, + "precision": 73.61 + }, + "busi_N100_jit": { + "dice": 71.52, + "iou": 63.87, + "sensitivity": 70.99, + "precision": 76.41 + }, + "busi_N100_pixelgen": { + "dice": 72.29, + "iou": 64.66, + "sensitivity": 72.49, + "precision": 77.16 + }, + "busi_N100_deco": { + "dice": 70.62, + "iou": 62.86, + "sensitivity": 71.52, + "precision": 74.61 + }, + "busi_N100_pixeldit": { + "dice": 73.17, + "iou": 65.41, + "sensitivity": 72.65, + "precision": 79.23 + } +} \ No newline at end of file diff --git a/code/scripts/p1/p1_gen_queue.sh b/code/scripts/p1/p1_gen_queue.sh new file mode 100644 index 0000000000000000000000000000000000000000..846aa066e21d35e3dbf696fa53a58b2691ea4dcc --- /dev/null +++ b/code/scripts/p1/p1_gen_queue.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# P1 generator-training queue: backbones x datasets, amortized (full-data), ~50k steps. +# Args: GPU [datasets] [backbones] e.g. p1_gen_queue.sh 5 "isic kvasir" "jit pixelgen deco pixeldit" +set -u +DR=/home/wzhang/LSC/Dataset/Segmentation/processed_unified +ROOT=/home/wzhang/LSC/Code/NPJ +PY=/opt/anaconda3/envs/seggen/bin/python +GPU=${1:-5} +ORDER=${2:-"isic kvasir"} +BKS=${3:-"jit pixelgen deco pixeldit"} +E="CUDA_DEVICE_ORDER=PCI_BUS_ID CUDA_VISIBLE_DEVICES=$GPU TORCHDYNAMO_DISABLE=1 PYTHONPATH=. OMP_NUM_THREADS=4" +cd "$ROOT" || exit 1 +ST=logs/p1gen_status.md +log(){ echo "[$(date '+%F %T')] $*" >> "$ST"; } +declare -A DSMAP=( [isic]="medsegdb_isic2018 holdout" [kvasir]="kvasir_seg official" ) +log "=== P1 GEN QUEUE START GPU-$GPU order=[$ORDER] bks=[$BKS] pid=$$ ===" +for dk in $ORDER; do + set -- ${DSMAP[$dk]}; ds=$1; proto=$2 + for bk in $BKS; do + out=pretrained/pixdiff/p1_${bk}_${dk}.pt + if [ -f "$out" ]; then log "SKIP $bk/$dk (ckpt exists)"; continue; fi + log "TRAIN $bk/$dk -> $out" + env $E $PY -m framework.synth.pixdiff.train --data_root "$DR" --dataset "$ds" --protocol "$proto" \ + --backbone "$bk" --img_size 256 --batch_size 16 --epochs 100000 --max_steps 50000 \ + --lr 1e-4 --amp bf16 --train_fraction 1.0 --fraction_seed 0 \ + --out_ckpt "$out" --log_interval 200 > logs/p1gen_${bk}_${dk}.log 2>&1 + rc=$? + log " done $bk/$dk rc=$rc ckpt=$([ -f "$out" ] && echo ok || echo MISSING)" + done +done +log "=== P1 GEN QUEUE DONE GPU-$GPU ===" +echo "P1GEN_DONE_$GPU" >> "$ST" diff --git a/code/scripts/p1/p1_master.py b/code/scripts/p1/p1_master.py new file mode 100644 index 0000000000000000000000000000000000000000..26aee2a20e185fe0a9b69de0bf4f5545e664a6be --- /dev/null +++ b/code/scripts/p1/p1_master.py @@ -0,0 +1,167 @@ +"""P1 master orchestrator: DAG scheduler over GPU0-5 for the backbone bake-off. +Phases: A) 8 generators (4 backbones x 2 datasets, amortized, 50k steps) + B) 16 sampling jobs (per gen x N in {50,100}, mask_aug n_per_mask=4) + C) 60 downstream seg runs (real + 4 backbones) x 2 ds x 2 N x 3 seeds +Single GPU per job (no DDP needed: 84 independent jobs). Retry-once on failure. +Resumable (skips done outputs). Rolling aggregate -> logs/p1master/p1_results.json.""" +import os, sys, time, json, subprocess, statistics as st + +ROOT = "/home/wzhang/LSC/Code/NPJ" +DR = "/home/wzhang/LSC/Dataset/Segmentation/processed_unified" +PY = "/opt/anaconda3/envs/seggen/bin/python" +GPUS = [0, 1, 2, 3, 4, 5] +os.chdir(ROOT) +LOGD = os.path.join(ROOT, "logs", "p1master") +os.makedirs(LOGD, exist_ok=True) + +def log(m): + line = f"[{time.strftime('%F %T')}] {m}" + with open(os.path.join(LOGD, "status.md"), "a") as f: + f.write(line + "\n") + print(line, flush=True) + +DSETS = {"isic": ("medsegdb_isic2018", "holdout", 2582), + "kvasir": ("kvasir_seg", "official", 800)} +BKS = ["jit", "pixelgen", "deco", "pixeldit"] +NS = [50, 100] +SEEDS = [0, 1, 2] + +jobs = {} +def add(jid, cmd, deps=(), done_path=None, done_min=1): + jobs[jid] = {"cmd": cmd, "deps": list(deps), "done_path": done_path, + "done_min": done_min, "state": "pending", "tries": 0, "gpu": None} + +# Phase A: generators +for bk in BKS: + for dk, (ds, proto, tot) in DSETS.items(): + out = f"pretrained/pixdiff/p1_{bk}_{dk}.pt" + cmd = (f"{PY} -m framework.synth.pixdiff.train --data_root {DR} --dataset {ds} " + f"--protocol {proto} --backbone {bk} --img_size 256 --batch_size 16 " + f"--epochs 100000 --max_steps 50000 --lr 1e-4 --amp bf16 " + f"--train_fraction 1.0 --fraction_seed 0 --out_ckpt {out} --log_interval 500") + add(f"gen_{bk}_{dk}", cmd, done_path=os.path.join(ROOT, out)) + +# Phase B: sampling +for bk in BKS: + for dk, (ds, proto, tot) in DSETS.items(): + ck = f"pretrained/pixdiff/p1_{bk}_{dk}.pt" + for N in NS: + f = N / tot + sd = f"{DR}/{ds}/{proto}/synth_p1_{bk}_{dk}_f{N}" + cmd = (f"{PY} -m framework.synth.pixdiff.sample --ckpt {ck} --data_root {DR} " + f"--dataset {ds} --protocol {proto} --train_fraction {f} --fraction_seed 0 " + f"--n_per_mask 4 --mask_aug --num_steps 50 --out_dir {sd}") + add(f"samp_{bk}_{dk}_N{N}", cmd, deps=[f"gen_{bk}_{dk}"], + done_path=os.path.join(sd, "images"), done_min=N * 4) + +# Phase C: downstream +def mpath(exp, ds, proto, S): + return os.path.join(ROOT, f"results/{exp}/{ds}_{proto}/unet/seed{S}/metrics.json") + +def seg_cmd(ds, proto, f, exp, S, synth=None): + base = (f"{PY} framework/train.py --data_root {DR} --dataset {ds} --protocol {proto} " + f"--arch unet --encoder resnet50 --aug standard --epochs 400 " + f"--train_fraction {f} --fraction_seed 0 --exp_name {exp} --amp bf16 --seed {S}") + if synth: + base += f" --synth_train_dir {synth}" + test = (f"{PY} framework/test.py --data_root {DR} --dataset {ds} --protocol {proto} " + f"--arch unet --encoder resnet50 --aug standard --exp_name {exp} --seed {S}") + return base + " && " + test + +for dk, (ds, proto, tot) in DSETS.items(): + for N in NS: + f = N / tot + for S in SEEDS: + exp = f"p1_real_{dk}_N{N}" + add(f"seg_real_{dk}_N{N}_s{S}", seg_cmd(ds, proto, f, exp, S), + done_path=mpath(exp, ds, proto, S)) + for bk in BKS: + sd = f"{DR}/{ds}/{proto}/synth_p1_{bk}_{dk}_f{N}" + for S in SEEDS: + exp = f"p1_{bk}_{dk}_N{N}" + add(f"seg_{bk}_{dk}_N{N}_s{S}", seg_cmd(ds, proto, f, exp, S, synth=sd), + deps=[f"samp_{bk}_{dk}_N{N}"], done_path=mpath(exp, ds, proto, S)) + +def is_done(j): + p = j["done_path"] + if not p or not os.path.exists(p): + return False + if os.path.isdir(p): + try: + return len(os.listdir(p)) >= j["done_min"] + except OSError: + return False + return True + +def aggregate(): + res = {} + for dk, (ds, proto, tot) in DSETS.items(): + for N in NS: + for arm in ["real"] + BKS: + exp = f"p1_{arm}_{dk}_N{N}" + ious, dices = [], [] + for S in SEEDS: + mp = mpath(exp, ds, proto, S) + if os.path.exists(mp): + try: + m = json.load(open(mp))["metrics"] + ious.append(m["iou_mean"]); dices.append(m["dice_mean"]) + except Exception: + pass + if ious: + res[f"{dk}_N{N}_{arm}"] = { + "iou_mean": sum(ious) / len(ious), "dice_mean": sum(dices) / len(dices), + "n_seeds": len(ious), "iou_seeds": ious, "dice_seeds": dices} + json.dump(res, open(os.path.join(LOGD, "p1_results.json"), "w"), indent=2) + +for jid, j in jobs.items(): + if is_done(j): + j["state"] = "done" +def deps_done(j): + return all(jobs[d]["state"] == "done" for d in j["deps"]) + +running = {} +free = set(GPUS) +MAXTRIES = 2 +log(f"START {len(jobs)} jobs on GPUs {GPUS} ({sum(1 for j in jobs.values() if j['state']=='done')} pre-done)") +last_summary = 0 +while True: + if all(j["state"] in ("done", "failed") for j in jobs.values()): + break + for jid, j in jobs.items(): + if not free: + break + if j["state"] == "pending" and deps_done(j): + if is_done(j): + j["state"] = "done"; continue + g = free.pop() + env = dict(os.environ, CUDA_DEVICE_ORDER="PCI_BUS_ID", + CUDA_VISIBLE_DEVICES=str(g), TORCHDYNAMO_DISABLE="1", + PYTHONPATH=".", OMP_NUM_THREADS="4") + lf = open(os.path.join(LOGD, jid + ".log"), "a") + p = subprocess.Popen(j["cmd"], shell=True, env=env, stdout=lf, + stderr=subprocess.STDOUT, cwd=ROOT) + running[g] = (jid, p, lf); j["state"] = "running"; j["gpu"] = g; j["tries"] += 1 + log(f"LAUNCH {jid} GPU{g} try{j['tries']}") + for g, (jid, p, lf) in list(running.items()): + rc = p.poll() + if rc is None: + continue + lf.close(); del running[g]; free.add(g) + j = jobs[jid] + if is_done(j): + j["state"] = "done"; log(f"DONE {jid} rc={rc}") + elif j["tries"] < MAXTRIES: + j["state"] = "pending"; log(f"RETRY {jid} rc={rc}") + else: + j["state"] = "failed"; log(f"FAILED {jid} rc={rc}") + if time.time() - last_summary > 300: + cnt = {s: sum(1 for j in jobs.values() if j["state"] == s) + for s in ("done", "running", "pending", "failed")} + log(f"SUMMARY {cnt} | running={sorted(j['gpu'] for j in jobs.values() if j['state']=='running')}") + aggregate(); last_summary = time.time() + time.sleep(10) + +aggregate() +log("ALL DONE") +print("P1_MASTER_DONE", flush=True) diff --git a/code/scripts/p1/p1_results.json b/code/scripts/p1/p1_results.json new file mode 100644 index 0000000000000000000000000000000000000000..77289d8414a07932cd7b679c5603063a79094e97 --- /dev/null +++ b/code/scripts/p1/p1_results.json @@ -0,0 +1,302 @@ +{ + "isic_N50_real": { + "iou_mean": 0.7749372989665764, + "dice_mean": 0.8516048790137111, + "n_seeds": 3, + "iou_seeds": [ + 0.7697344814191404, + 0.772716144763783, + 0.7823612707168056 + ], + "dice_seeds": [ + 0.8480779677176173, + 0.8503343900165478, + 0.8564022793069677 + ] + }, + "isic_N50_jit": { + "iou_mean": 0.8052051026596693, + "dice_mean": 0.8773763597758372, + "n_seeds": 3, + "iou_seeds": [ + 0.8038682316971465, + 0.8033287650931844, + 0.8084183111886771 + ], + "dice_seeds": [ + 0.8763739862706927, + 0.8755590664589031, + 0.8801960265979158 + ] + }, + "isic_N50_pixelgen": { + "iou_mean": 0.8071234360528656, + "dice_mean": 0.8790093239202045, + "n_seeds": 3, + "iou_seeds": [ + 0.8046804415401705, + 0.8071308385675863, + 0.80955902805084 + ], + "dice_seeds": [ + 0.8771928252649649, + 0.8781965482168992, + 0.8816385982787495 + ] + }, + "isic_N50_deco": { + "iou_mean": 0.7996043535547449, + "dice_mean": 0.8726861944567714, + "n_seeds": 3, + "iou_seeds": [ + 0.7988670932254234, + 0.8020180961470182, + 0.797927871291793 + ], + "dice_seeds": [ + 0.8724373392477919, + 0.875006987196055, + 0.8706142569264674 + ] + }, + "isic_N50_pixeldit": { + "iou_mean": 0.8051857432382189, + "dice_mean": 0.8768404429697196, + "n_seeds": 3, + "iou_seeds": [ + 0.8076451208631789, + 0.80299518868651, + 0.8049169201649677 + ], + "dice_seeds": [ + 0.8789570258524421, + 0.8757209786234587, + 0.8758433244332583 + ] + }, + "isic_N100_real": { + "iou_mean": 0.8018487332144956, + "dice_mean": 0.8731124297212501, + "n_seeds": 3, + "iou_seeds": [ + 0.8018532993620694, + 0.8030565805042732, + 0.8006363197771443 + ], + "dice_seeds": [ + 0.8742042532437185, + 0.8744577536420762, + 0.8706752822779558 + ] + }, + "isic_N100_jit": { + "iou_mean": 0.8166433570078636, + "dice_mean": 0.8852389536608354, + "n_seeds": 3, + "iou_seeds": [ + 0.8184147975006147, + 0.8152316048562138, + 0.8162836686667623 + ], + "dice_seeds": [ + 0.8866063380696619, + 0.8842458679141912, + 0.8848646549986529 + ] + }, + "isic_N100_pixelgen": { + "iou_mean": 0.8212823342374741, + "dice_mean": 0.8895036576449075, + "n_seeds": 3, + "iou_seeds": [ + 0.8203996376523117, + 0.8215948549134239, + 0.8218525101466865 + ], + "dice_seeds": [ + 0.8893210218244473, + 0.8894422995119722, + 0.8897476515983034 + ] + }, + "isic_N100_deco": { + "iou_mean": 0.8119110110180822, + "dice_mean": 0.8814897596168004, + "n_seeds": 3, + "iou_seeds": [ + 0.812474683629472, + 0.81098766389058, + 0.8122706855341947 + ], + "dice_seeds": [ + 0.8834257222572127, + 0.8798560384070768, + 0.881187518186112 + ] + }, + "isic_N100_pixeldit": { + "iou_mean": 0.8169910899725092, + "dice_mean": 0.8860181724664612, + "n_seeds": 3, + "iou_seeds": [ + 0.8168252607788484, + 0.8168659787418466, + 0.8172820303968324 + ], + "dice_seeds": [ + 0.8855665474000166, + 0.8859441754776876, + 0.8865437945216795 + ] + }, + "kvasir_N50_real": { + "iou_mean": 0.7534671958018979, + "dice_mean": 0.8375380075145843, + "n_seeds": 3, + "iou_seeds": [ + 0.7422559578123432, + 0.7636408134000111, + 0.7545048161933395 + ], + "dice_seeds": [ + 0.8244831334988446, + 0.849740539589318, + 0.8383903494555902 + ] + }, + "kvasir_N50_jit": { + "iou_mean": 0.7919630629212887, + "dice_mean": 0.867516315771424, + "n_seeds": 3, + "iou_seeds": [ + 0.7929972233023507, + 0.7943493738869872, + 0.7885425915745284 + ], + "dice_seeds": [ + 0.8676604855354516, + 0.8655865846767077, + 0.8693018771021128 + ] + }, + "kvasir_N50_pixelgen": { + "iou_mean": 0.7842259846149005, + "dice_mean": 0.8615631488519918, + "n_seeds": 3, + "iou_seeds": [ + 0.7841354377174787, + 0.7782063762034493, + 0.7903361399237733 + ], + "dice_seeds": [ + 0.858331250209457, + 0.8583852594567815, + 0.8679729368897371 + ] + }, + "kvasir_N50_deco": { + "iou_mean": 0.8083752894364583, + "dice_mean": 0.8803164279623182, + "n_seeds": 3, + "iou_seeds": [ + 0.8104997825534876, + 0.8045413607010391, + 0.8100847250548484 + ], + "dice_seeds": [ + 0.8826893496480072, + 0.8778416284559154, + 0.8804183057830322 + ] + }, + "kvasir_N50_pixeldit": { + "iou_mean": 0.7897261916004509, + "dice_mean": 0.8586578118039875, + "n_seeds": 3, + "iou_seeds": [ + 0.7733292625059578, + 0.800297355081953, + 0.7955519572134416 + ], + "dice_seeds": [ + 0.8420264952707284, + 0.8674089356274011, + 0.8665380045138328 + ] + }, + "kvasir_N100_real": { + "iou_mean": 0.7949746887997339, + "dice_mean": 0.8695256998060409, + "n_seeds": 3, + "iou_seeds": [ + 0.7966858390583808, + 0.7968998232753884, + 0.7913384040654323 + ], + "dice_seeds": [ + 0.87442242361909, + 0.8720526205988572, + 0.8621020552001751 + ] + }, + "kvasir_N100_jit": { + "iou_mean": 0.8162035292350188, + "dice_mean": 0.884963785884059, + "n_seeds": 3, + "iou_seeds": [ + 0.8044583298250717, + 0.8215198247885459, + 0.8226324330914389 + ], + "dice_seeds": [ + 0.8733364457591601, + 0.8902805661843565, + 0.8912743457086602 + ] + }, + "kvasir_N100_pixelgen": { + "iou_mean": 0.8032477917950044, + "dice_mean": 0.8693680901970753, + "n_seeds": 3, + "iou_seeds": [ + 0.7990136881750312, + 0.8131907666134136, + 0.7975389205965682 + ], + "dice_seeds": [ + 0.8653448154322447, + 0.8767741359428084, + 0.8659853192161729 + ] + }, + "kvasir_N100_deco": { + "iou_mean": 0.8121859633543412, + "dice_mean": 0.8814163311797074, + "n_seeds": 3, + "iou_seeds": [ + 0.8014463766191772, + 0.8207859220669117, + 0.8143255913769347 + ], + "dice_seeds": [ + 0.8737877150565937, + 0.885505301166682, + 0.8849559773158466 + ] + }, + "kvasir_N100_pixeldit": { + "iou_mean": 0.8282534914466804, + "dice_mean": 0.8952017769853248, + "n_seeds": 3, + "iou_seeds": [ + 0.8263046284256831, + 0.8313217018221024, + 0.8271341440922554 + ], + "dice_seeds": [ + 0.8969579025391758, + 0.8957992526085868, + 0.8928481758082121 + ] + } +} \ No newline at end of file diff --git a/code/scripts/p1/smoke_backbone.py b/code/scripts/p1/smoke_backbone.py new file mode 100644 index 0000000000000000000000000000000000000000..0e0a8db231987d889f97d59b3b4b956e6de4a16c --- /dev/null +++ b/code/scripts/p1/smoke_backbone.py @@ -0,0 +1,76 @@ +"""Smoke: port DeCo (PixNerDiT) or PixelDiT (PixDiT) into the PixDiff mask-concat scaffolding. +Backbone-agnostic decouple: build with in=img+cond, out defaults to in, take x_pred[:, :img_ch]. +Usage: python smoke_backbone.py {deco|pixeldit}""" +import os, sys +sys.path.insert(0, "/home/wzhang/LSC/Code/NPJ") +import torch, torch.nn as nn +from torch.utils.data import DataLoader +from framework.synth.pixdiff.conditioning import build_conditioner +from framework.synth.pixdiff.data import MaskCondGenDataset + +BK = sys.argv[1] +DECO = "/home/wzhang/LSC/Code/NPJ/sota/DeCo" +PIXELDIT = "/home/wzhang/LSC/Code/NPJ/sota/PixelDiT" +dev = "cuda" +DR = "/home/wzhang/LSC/Dataset/Segmentation/processed_unified" + +ds = MaskCondGenDataset(DR, "medsegdb_isic2018", "holdout", img_size=256, + train_fraction=0.02, fraction_seed=0) +cond = build_conditioner("onehot", ds.num_classes).to(dev) +img_ch, K = ds.in_channels, cond.cond_channels +in_tot = img_ch + K +print(f"[{BK}] ds n={len(ds)} img_ch={img_ch} K={K} in_tot={in_tot}", flush=True) + +if BK == "deco": + sys.path.insert(0, os.path.join(DECO, "src", "models", "transformer")) + from dit_c2i_DeCo import PixNerDiT + net = PixNerDiT(in_channels=in_tot, patch_size=16, num_groups=12, hidden_size=768, + hidden_size_x=32, num_blocks=13, num_cond_blocks=12, num_classes=1).to(dev) +elif BK == "pixeldit": + sys.path.insert(0, PIXELDIT) + from pixdit_core.pixeldit_c2i import PixDiT + net = PixDiT(in_channels=in_tot, num_groups=12, hidden_size=768, pixel_hidden_size=16, + patch_depth=12, pixel_depth=4, patch_size=16, num_classes=1).to(dev) +else: + raise SystemExit("backbone must be deco|pixeldit") + +print(f"[{BK}] params={sum(p.numel() for p in net.parameters())/1e6:.1f}M", flush=True) +opt = torch.optim.AdamW(net.parameters(), lr=1e-4) +dl = DataLoader(ds, batch_size=4, shuffle=True, drop_last=True, num_workers=2) +it = iter(dl) +def get_batch(): + global it + try: b = next(it) + except StopIteration: it = iter(dl); b = next(it) + return (b["image"], b["mask"]) if isinstance(b, dict) else (b[0], b[1]) + +net.train() +for step in range(20): + img, msk = get_batch(); img, msk = img.to(dev), msk.to(dev) + t = torch.sigmoid(torch.randn(img.size(0), device=dev) * 0.8 - 0.8).view(-1, 1, 1, 1) + e = torch.randn_like(img) + z = t * img + (1 - t) * e + v = (img - z) / (1 - t).clamp_min(5e-2) + c = cond(msk) + y = torch.zeros(img.size(0), dtype=torch.long, device=dev) + out = net(torch.cat([z, c], dim=1), t.flatten(), y) + assert out.dim() == 4 and out.shape[1] >= img_ch, f"bad out shape {tuple(out.shape)}" + x_pred = out[:, :img_ch] + v_pred = (x_pred - z) / (1 - t).clamp_min(5e-2) + loss = ((v - v_pred) ** 2).mean() + loss.backward(); opt.step(); opt.zero_grad() + if step % 5 == 0 or step == 19: + print(f"[{BK}] step {step:2d} loss {loss.item():.4f}", flush=True) + +net.eval() +with torch.no_grad(): + msk0 = msk[:2]; c0 = cond(msk0) + z = torch.randn(2, img_ch, 256, 256, device=dev) + ts = torch.linspace(0, 1, 11).tolist() + for i in range(10): + tc, dt = ts[i], ts[i + 1] - ts[i] + out = net(torch.cat([z, c0], dim=1), torch.full((2,), tc, device=dev), + torch.zeros(2, dtype=torch.long, device=dev))[:, :img_ch] + z = z + (out - z) / max(1 - tc, 5e-2) * dt + print(f"[{BK}] sample ok shape={tuple(z.shape)} range=({z.min():.2f},{z.max():.2f})", flush=True) +print(f"SMOKE_{BK.upper()}_PASS", flush=True) diff --git a/code/scripts/p1/smoke_pixelgen.py b/code/scripts/p1/smoke_pixelgen.py new file mode 100644 index 0000000000000000000000000000000000000000..98d1056af374078a2d8dea11738ed081286c07c4 --- /dev/null +++ b/code/scripts/p1/smoke_pixelgen.py @@ -0,0 +1,70 @@ +"""Smoke test: port PixelGen's JiT denoiser into the PixDiff mask-concat scaffolding. +Validates: import on server, in=img+cond/out=img build, flow-matching train step, sampling. +Tiny: 20 train steps + 10-step sample on ~50 ISIC images. No checkpoint written.""" +import os, sys +sys.path.insert(0, "/home/wzhang/LSC/Code/NPJ") # framework.* +PG = "/home/wzhang/LSC/Code/NPJ/sota/PixelGen" +sys.path.insert(0, os.path.join(PG, "src", "models", "transformer")) # JiT.py +import torch, torch.nn as nn +from torch.utils.data import DataLoader +from JiT import JiT_models, FinalLayer # PixelGen denoiser +from framework.synth.pixdiff.conditioning import build_conditioner +from framework.synth.pixdiff.data import MaskCondGenDataset + +dev = "cuda" +DR = "/home/wzhang/LSC/Dataset/Segmentation/processed_unified" +ds = MaskCondGenDataset(DR, "medsegdb_isic2018", "holdout", img_size=256, + train_fraction=0.02, fraction_seed=0) +print(f"[ds] n={len(ds)} in_ch={ds.in_channels} num_classes={ds.num_classes}", flush=True) +cond = build_conditioner("onehot", ds.num_classes).to(dev) +img_ch, K = ds.in_channels, cond.cond_channels + +net = JiT_models["JiT-B/16"](input_size=256, in_channels=img_ch + K, num_classes=1).to(dev) +if net.out_channels != img_ch: + net.out_channels = img_ch + net.final_layer = FinalLayer(net.hidden_size, net.patch_size, img_ch).to(dev) + nn.init.constant_(net.final_layer.linear.weight, 0); nn.init.constant_(net.final_layer.linear.bias, 0) + nn.init.constant_(net.final_layer.adaLN_modulation[-1].weight, 0) + nn.init.constant_(net.final_layer.adaLN_modulation[-1].bias, 0) +print(f"[net] PixelGen JiT-B/16 in={img_ch+K} out={net.out_channels} params={sum(p.numel() for p in net.parameters())/1e6:.1f}M", flush=True) + +opt = torch.optim.AdamW(net.parameters(), lr=1e-4) +dl = DataLoader(ds, batch_size=4, shuffle=True, drop_last=True, num_workers=2) +it = iter(dl) + +def get_batch(): + global it + try: b = next(it) + except StopIteration: + it = iter(dl); b = next(it) + if isinstance(b, dict): return b["image"], b["mask"] + return b[0], b[1] + +net.train() +for step in range(20): + img, msk = get_batch(); img, msk = img.to(dev), msk.to(dev) + t = torch.sigmoid(torch.randn(img.size(0), device=dev) * 0.8 - 0.8).view(-1, 1, 1, 1) + e = torch.randn_like(img) + z = t * img + (1 - t) * e + v = (img - z) / (1 - t).clamp_min(5e-2) + c = cond(msk) + y = torch.zeros(img.size(0), dtype=torch.long, device=dev) + x_pred = net(torch.cat([z, c], dim=1), t.flatten(), y) + v_pred = (x_pred - z) / (1 - t).clamp_min(5e-2) + loss = ((v - v_pred) ** 2).mean() + loss.backward(); opt.step(); opt.zero_grad() + if step % 5 == 0 or step == 19: + print(f"[train] step {step:2d} loss {loss.item():.4f}", flush=True) + +net.eval() +with torch.no_grad(): + msk0 = msk[:2]; c0 = cond(msk0) + z = torch.randn(2, img_ch, 256, 256, device=dev) + ts = torch.linspace(0, 1, 11).tolist() + for i in range(10): + tc, dt = ts[i], ts[i + 1] - ts[i] + tt = torch.full((2,), tc, device=dev) + xp = net(torch.cat([z, c0], dim=1), tt, torch.zeros(2, dtype=torch.long, device=dev)) + z = z + (xp - z) / max(1 - tc, 5e-2) * dt + print(f"[sample] ok shape={tuple(z.shape)} range=({z.min():.2f},{z.max():.2f})", flush=True) +print("SMOKE_PIXELGEN_PASS", flush=True) diff --git a/code/scripts/p1/train_fd_patched.py b/code/scripts/p1/train_fd_patched.py new file mode 100644 index 0000000000000000000000000000000000000000..7458bc6827d8cf9977dba869d25d1dad81ec6af8 --- /dev/null +++ b/code/scripts/p1/train_fd_patched.py @@ -0,0 +1,179 @@ +"""FD-Loss post-training for a PixDiff generator (env: seggen, GPU 5). + +Starts from a base PixDiff checkpoint and continues training with: + total = flow_matching_loss + fd_weight * normalized_FD_loss +where the FD term matches the generated x0 feature distribution to the real-image +reference distribution (Inception space), gated to low-noise timesteps (where x0 is +a meaningful image). This targets the blur/distribution gap the MSE objective leaves. + +Run from project root (…/NPJ): + CUDA_VISIBLE_DEVICES=5 python -m framework.synth.pixdiff.train_fd \ + --base_ckpt pretrained/pixdiff/kvasir_seg_official_f1.0.pt \ + --data_root /home/wzhang/LSC/Dataset/Segmentation/processed_unified \ + --dataset kvasir_seg --protocol official \ + --epochs 200 --lr 2e-5 --fd_weight 0.5 \ + --out_ckpt pretrained/pixdiff/kvasir_seg_official_f1.0_fd.pt +""" +from __future__ import annotations + +import argparse +import os +import sys +import time + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))) + +import numpy as np +import torch +from torch.utils.data import DataLoader + +from framework.synth.pixdiff.data import MaskCondGenDataset +from framework.synth.pixdiff.conditioning import build_conditioner +from framework.synth.pixdiff.mask_jit import MaskDenoiser +from framework.synth.pixdiff.fd_loss import ( + InceptionFeatures, FeatureQueue, compute_frechet_distance_loss, + precompute_sigma_ref_sqrt, compute_ref_stats, +) + + +def get_args(): + p = argparse.ArgumentParser("PixDiff FD-Loss post-training") + p.add_argument("--base_ckpt", required=True) + p.add_argument("--data_root", required=True) + p.add_argument("--dataset", required=True) + p.add_argument("--protocol", required=True) + p.add_argument("--train_fraction", type=float, default=1.0) + p.add_argument("--fraction_seed", type=int, default=0) + p.add_argument("--epochs", type=int, default=200) + p.add_argument("--batch_size", type=int, default=32) + p.add_argument("--lr", type=float, default=2e-5) + p.add_argument("--num_workers", type=int, default=6) + p.add_argument("--amp", default="bf16", choices=["bf16", "fp16", "fp32"]) + # FD-Loss knobs + p.add_argument("--fd_weight", type=float, default=0.5) + p.add_argument("--fd_gate_t", type=float, default=0.5, help="apply FD only when t>=this (low noise)") + p.add_argument("--queue_size", type=int, default=512) + p.add_argument("--fd_norm_eps", type=float, default=1e-2) + p.add_argument("--lpips_weight", type=float, default=0.0) + p.add_argument("--dino_weight", type=float, default=0.0) + p.add_argument("--percep_gate_t", type=float, default=0.5, help="apply perceptual only when t>=this") + p.add_argument("--ref_stats", default="", help="npz of (mu,sigma); auto path + compute if empty") + p.add_argument("--ema_decay", type=float, default=0.9999) + p.add_argument("--seed", type=int, default=0) + p.add_argument("--out_ckpt", required=True) + p.add_argument("--log_interval", type=int, default=20) + return p.parse_args() + + +def main(): + a = get_args() + torch.manual_seed(a.seed) + device = "cuda" + amp_dtype = {"bf16": torch.bfloat16, "fp16": torch.float16}.get(a.amp) + + # ---- data ---- + ds = MaskCondGenDataset(a.data_root, a.dataset, a.protocol, img_size=256, + train_fraction=a.train_fraction, fraction_seed=a.fraction_seed) + img_ch, n_cls = ds.in_channels, ds.num_classes + loader = DataLoader(ds, batch_size=a.batch_size, shuffle=True, drop_last=True, + num_workers=a.num_workers, pin_memory=True, persistent_workers=a.num_workers > 0) + print(f"[fd] {a.dataset}/{a.protocol} n={len(ds)} in_ch={img_ch} num_classes={n_cls}", flush=True) + if img_ch != 3: + print("[fd][warn] Inception expects 3ch; non-RGB dataset — FD features may be weak.", flush=True) + + # ---- model from base ckpt ---- + ckpt = torch.load(a.base_ckpt, map_location="cpu", weights_only=False) + cond = build_conditioner(ckpt.get("conditioner", "onehot"), n_cls) + model = MaskDenoiser(ckpt["model_name"], ckpt["img_size"], ckpt["img_channels"], cond, + noise_scale=ckpt.get("noise_scale", 1.0), ema_decay=a.ema_decay, backbone=ckpt.get("backbone", "jit")).to(device) + model.load_state_dict(ckpt["state_dict"]) + model._ema = [e.to(device) for e in ckpt["ema"]] if ckpt.get("ema") is not None else None + if model._ema is None: + model.ema_init() + print(f"[fd] loaded base {a.base_ckpt}", flush=True) + + # ---- FD machinery ---- + inception = InceptionFeatures().to(device).eval() + queue = FeatureQueue(size=a.queue_size, feat_dim=inception.feat_dim).to(device) + percep = None + if a.lpips_weight > 0 or a.dino_weight > 0: + from framework.synth.pixdiff.perceptual import PerceptualLoss + percep = PerceptualLoss(use_lpips=a.lpips_weight > 0, use_dino=a.dino_weight > 0, device=device) + print(f"[fd] perceptual ON lpips_w={a.lpips_weight} dino_w={a.dino_weight} gate_t={a.percep_gate_t}", flush=True) + + ref_path = a.ref_stats or a.out_ckpt.replace(".pt", "_refstats.npz") + if os.path.isfile(ref_path): + rs = np.load(ref_path); mu_ref_np, sigma_ref_np = rs["mu"], rs["sigma"] + print(f"[fd] loaded ref stats {ref_path}", flush=True) + else: + print("[fd] computing reference stats from real train images...", flush=True) + ref_loader = DataLoader(MaskCondGenDataset(a.data_root, a.dataset, a.protocol, img_size=256, + train_fraction=a.train_fraction, fraction_seed=a.fraction_seed, + hflip=False, vflip=False), + batch_size=a.batch_size, shuffle=False, num_workers=a.num_workers) + mu_ref_np, sigma_ref_np, nref = compute_ref_stats(ref_loader, inception, device) + os.makedirs(os.path.dirname(os.path.abspath(ref_path)) or ".", exist_ok=True) + np.savez(ref_path, mu=mu_ref_np, sigma=sigma_ref_np) + print(f"[fd] ref stats from {nref} imgs -> {ref_path}", flush=True) + mu_ref = torch.tensor(mu_ref_np, device=device, dtype=torch.float64) + sigma_ref = torch.tensor(sigma_ref_np, device=device, dtype=torch.float64) + sigma_ref_sqrt = precompute_sigma_ref_sqrt(sigma_ref) + + opt = torch.optim.AdamW(model._trainable(), lr=a.lr, weight_decay=0.0) + os.makedirs(os.path.dirname(os.path.abspath(a.out_ckpt)) or ".", exist_ok=True) + + def save(): + torch.save({"model_name": ckpt["model_name"], "img_size": ckpt["img_size"], + "img_channels": img_ch, "num_classes": n_cls, + "conditioner": ckpt.get("conditioner", "onehot"), + "noise_scale": ckpt.get("noise_scale", 1.0), + "state_dict": model.state_dict(), "ema": model._ema, "args": vars(a)}, a.out_ckpt) + print(f"[fd] saved {a.out_ckpt}", flush=True) + + step = 0 + for epoch in range(a.epochs): + model.train(); t0 = time.time(); run_fm = run_fd = run_fdraw = 0.0 + for batch in loader: + img = batch["image"].to(device, non_blocking=True) + mask = batch["mask"].to(device, non_blocking=True) + opt.zero_grad(set_to_none=True) + with torch.autocast("cuda", dtype=amp_dtype) if amp_dtype else _null(): + fm_loss, x_pred, t = model(img, mask, return_pred=True) + # FD term on predicted clean image, gated to low noise + gate = t >= a.fd_gate_t + fd_loss = torch.zeros((), device=device); fd_raw = 0.0 + if int(gate.sum()) >= 2: + xg = x_pred[gate].float() + feats = inception((xg.clamp(-1, 1) + 1) / 2) # (Ng,2048), grad flows + if queue.is_ready(): + mu, sigma = queue.build_feats_stats(feats) + fd = compute_frechet_distance_loss(mu_ref, sigma_ref, mu, sigma, sigma_ref_sqrt) + fd_raw = float(fd); fd_loss = fd / (fd.detach() + a.fd_norm_eps) + queue.enqueue(feats) + total = fm_loss + a.fd_weight * fd_loss + pl_lpips = pl_dino = 0.0 + if percep is not None: + pgate = t >= a.percep_gate_t + if int(pgate.sum()) >= 1: + pld = percep(x_pred[pgate].float(), img[pgate].float()) + if "lpips" in pld: + total = total + a.lpips_weight * pld["lpips"]; pl_lpips = float(pld["lpips"]) + if "dino" in pld: + total = total + a.dino_weight * pld["dino"]; pl_dino = float(pld["dino"]) + total.backward(); opt.step(); model.ema_update() + run_fm += float(fm_loss); run_fd += float(fd_loss); run_fdraw += fd_raw; step += 1 + if step % a.log_interval == 0: + print(f"[fd] ep{epoch} step{step} fm={float(fm_loss):.4f} fd_raw={fd_raw:.1f} " + f"lpips={pl_lpips:.3f} dino={pl_dino:.3f} qready={queue.is_ready()}", flush=True) + print(f"[fd] epoch {epoch} fm={run_fm/max(1,len(loader)):.4f} " + f"fd_raw={run_fdraw/max(1,len(loader)):.1f} ({time.time()-t0:.1f}s)", flush=True) + save() + + +class _null: + def __enter__(self): return self + def __exit__(self, *a): return False + + +if __name__ == "__main__": + main() diff --git a/code/sota/Swin-Unet/README.md b/code/sota/Swin-Unet/README.md new file mode 100644 index 0000000000000000000000000000000000000000..bb02314f963c3c42b49e3914103a03ee2d3ddf06 --- /dev/null +++ b/code/sota/Swin-Unet/README.md @@ -0,0 +1,64 @@ +# Swin-Unet +[ECCVW2022] The codes for the work "Swin-Unet: Unet-like Pure Transformer for Medical Image Segmentation"(https://arxiv.org/abs/2105.05537). Our paper has been accepted by ECCV 2022 MEDICAL COMPUTER VISION WORKSHOP (https://mcv-workshop.github.io/). We updated the Reproducibility. I hope this will help you to reproduce the results. + +## 1. Download pre-trained swin transformer model (Swin-T) +* [Get pre-trained model in this link] (https://drive.google.com/drive/folders/1UC3XOoezeum0uck4KBVGa8osahs6rKUY?usp=sharing): Put pretrained Swin-T into folder "pretrained_ckpt/" + +## 2. Prepare data + +- The datasets we used are provided by TransUnet's authors. [Get processed data in this link] (Synapse/BTCV: https://drive.google.com/drive/folders/1ACJEoTp-uqfFJ73qS3eUObQh52nGuzCd and ACDC: https://drive.google.com/drive/folders/1KQcrci7aKsYZi1hQoZ3T3QUtcy7b--n4). + +## 3. Environment + +- Please prepare an environment with python=3.7, and then use the command "pip install -r requirements.txt" for the dependencies. + +## 4. Train/Test + +- Run the train script on synapse dataset. The batch size we used is 24. If you do not have enough GPU memory, the bacth size can be reduced to 12 or 6 to save memory. + +- Train + +```bash +sh train.sh +# or +python train.py --dataset Synapse --cfg configs/swin_tiny_patch4_window7_224_lite.yaml --root_path your DATA_DIR --max_epochs 150 --output_dir your OUT_DIR --img_size 224 --base_lr 0.05 --batch_size 24 +``` + +- Test + +```bash +sh test.sh +# or +python test.py --dataset Synapse --cfg configs/swin_tiny_patch4_window7_224_lite.yaml --is_saveni --volume_path your DATA_DIR --output_dir your OUT_DIR --max_epoch 150 --base_lr 0.05 --img_size 224 --batch_size 24 +``` + +## Reproducibility + + +- Codes + +Our trained model is stored on the Huawei cloud. The interns do not have the right to send any files out from the internal system, so I can't share our trained model weights. Regarding how to reproduce the segmentation results presented in the paper, we discovered that different GPU types would generate different results. In our code, we carefully set the random seed, so the results should be consistent when trained multiple times on the same type of GPU. If the training does not give the same segmentation results as in the paper, it is recommended to adjust the learning rate. And, the type of GPU we used in this work is Tesla v100. Finaly, pre-training is very important for pure transformer models. In our experiments, both the encoder and decoder are initialized with pretrained weights rather than initializing the encoder with pretrained weights only. + +## References +* [TransUnet](https://github.com/Beckschen/TransUNet) +* [SwinTransformer](https://github.com/microsoft/Swin-Transformer) + +## Citation + +```bibtex +@InProceedings{swinunet, +author = {Hu Cao and Yueyue Wang and Joy Chen and Dongsheng Jiang and Xiaopeng Zhang and Qi Tian and Manning Wang}, +title = {Swin-Unet: Unet-like Pure Transformer for Medical Image Segmentation}, +booktitle = {Proceedings of the European Conference on Computer Vision Workshops(ECCVW)}, +year = {2022} +} + +@misc{cao2021swinunet, + title={Swin-Unet: Unet-like Pure Transformer for Medical Image Segmentation}, + author={Hu Cao and Yueyue Wang and Joy Chen and Dongsheng Jiang and Xiaopeng Zhang and Qi Tian and Manning Wang}, + year={2021}, + eprint={2105.05537}, + archivePrefix={arXiv}, + primaryClass={eess.IV} +} +``` diff --git a/code/sota/Swin-Unet/config.py b/code/sota/Swin-Unet/config.py new file mode 100644 index 0000000000000000000000000000000000000000..35bf199689555f3fc61fbee04daa2331f7efc181 --- /dev/null +++ b/code/sota/Swin-Unet/config.py @@ -0,0 +1,229 @@ +# -------------------------------------------------------- +# Swin Transformer +# Copyright (c) 2021 Microsoft +# Licensed under The MIT License [see LICENSE for details] +# Written by Ze Liu +# --------------------------------------------------------' + +import os +import yaml +from yacs.config import CfgNode as CN + +_C = CN() + +# Base config files +_C.BASE = [''] + +# ----------------------------------------------------------------------------- +# Data settings +# ----------------------------------------------------------------------------- +_C.DATA = CN() +# Batch size for a single GPU, could be overwritten by command line argument +_C.DATA.BATCH_SIZE = 128 +# Path to dataset, could be overwritten by command line argument +_C.DATA.DATA_PATH = '' +# Dataset name +_C.DATA.DATASET = 'imagenet' +# Input image size +_C.DATA.IMG_SIZE = 224 +# Interpolation to resize image (random, bilinear, bicubic) +_C.DATA.INTERPOLATION = 'bicubic' +# Use zipped dataset instead of folder dataset +# could be overwritten by command line argument +_C.DATA.ZIP_MODE = False +# Cache Data in Memory, could be overwritten by command line argument +_C.DATA.CACHE_MODE = 'part' +# Pin CPU memory in DataLoader for more efficient (sometimes) transfer to GPU. +_C.DATA.PIN_MEMORY = True +# Number of data loading threads +_C.DATA.NUM_WORKERS = 8 + +# ----------------------------------------------------------------------------- +# Model settings +# ----------------------------------------------------------------------------- +_C.MODEL = CN() +# Model type +_C.MODEL.TYPE = 'swin' +# Model name +_C.MODEL.NAME = 'swin_tiny_patch4_window7_224' +# Checkpoint to resume, could be overwritten by command line argument +_C.MODEL.PRETRAIN_CKPT = './pretrained_ckpt/swin_tiny_patch4_window7_224.pth' +_C.MODEL.RESUME = '' +# Number of classes, overwritten in data preparation +_C.MODEL.NUM_CLASSES = 1000 +# Dropout rate +_C.MODEL.DROP_RATE = 0.0 +# Drop path rate +_C.MODEL.DROP_PATH_RATE = 0.1 +# Label Smoothing +_C.MODEL.LABEL_SMOOTHING = 0.1 + +# Swin Transformer parameters +_C.MODEL.SWIN = CN() +_C.MODEL.SWIN.PATCH_SIZE = 4 +_C.MODEL.SWIN.IN_CHANS = 3 +_C.MODEL.SWIN.EMBED_DIM = 96 +_C.MODEL.SWIN.DEPTHS = [2, 2, 6, 2] +_C.MODEL.SWIN.DECODER_DEPTHS = [2, 2, 6, 2] +_C.MODEL.SWIN.NUM_HEADS = [3, 6, 12, 24] +_C.MODEL.SWIN.WINDOW_SIZE = 7 +_C.MODEL.SWIN.MLP_RATIO = 4. +_C.MODEL.SWIN.QKV_BIAS = True +_C.MODEL.SWIN.QK_SCALE = None +_C.MODEL.SWIN.APE = False +_C.MODEL.SWIN.PATCH_NORM = True +_C.MODEL.SWIN.FINAL_UPSAMPLE= "expand_first" + +# ----------------------------------------------------------------------------- +# Training settings +# ----------------------------------------------------------------------------- +_C.TRAIN = CN() +_C.TRAIN.START_EPOCH = 0 +_C.TRAIN.EPOCHS = 300 +_C.TRAIN.WARMUP_EPOCHS = 20 +_C.TRAIN.WEIGHT_DECAY = 0.05 +_C.TRAIN.BASE_LR = 5e-4 +_C.TRAIN.WARMUP_LR = 5e-7 +_C.TRAIN.MIN_LR = 5e-6 +# Clip gradient norm +_C.TRAIN.CLIP_GRAD = 5.0 +# Auto resume from latest checkpoint +_C.TRAIN.AUTO_RESUME = True +# Gradient accumulation steps +# could be overwritten by command line argument +_C.TRAIN.ACCUMULATION_STEPS = 0 +# Whether to use gradient checkpointing to save memory +# could be overwritten by command line argument +_C.TRAIN.USE_CHECKPOINT = False + +# LR scheduler +_C.TRAIN.LR_SCHEDULER = CN() +_C.TRAIN.LR_SCHEDULER.NAME = 'cosine' +# Epoch interval to decay LR, used in StepLRScheduler +_C.TRAIN.LR_SCHEDULER.DECAY_EPOCHS = 30 +# LR decay rate, used in StepLRScheduler +_C.TRAIN.LR_SCHEDULER.DECAY_RATE = 0.1 + +# Optimizer +_C.TRAIN.OPTIMIZER = CN() +_C.TRAIN.OPTIMIZER.NAME = 'adamw' +# Optimizer Epsilon +_C.TRAIN.OPTIMIZER.EPS = 1e-8 +# Optimizer Betas +_C.TRAIN.OPTIMIZER.BETAS = (0.9, 0.999) +# SGD momentum +_C.TRAIN.OPTIMIZER.MOMENTUM = 0.9 + +# ----------------------------------------------------------------------------- +# Augmentation settings +# ----------------------------------------------------------------------------- +_C.AUG = CN() +# Color jitter factor +_C.AUG.COLOR_JITTER = 0.4 +# Use AutoAugment policy. "v0" or "original" +_C.AUG.AUTO_AUGMENT = 'rand-m9-mstd0.5-inc1' +# Random erase prob +_C.AUG.REPROB = 0.25 +# Random erase mode +_C.AUG.REMODE = 'pixel' +# Random erase count +_C.AUG.RECOUNT = 1 +# Mixup alpha, mixup enabled if > 0 +_C.AUG.MIXUP = 0.8 +# Cutmix alpha, cutmix enabled if > 0 +_C.AUG.CUTMIX = 1.0 +# Cutmix min/max ratio, overrides alpha and enables cutmix if set +_C.AUG.CUTMIX_MINMAX = None +# Probability of performing mixup or cutmix when either/both is enabled +_C.AUG.MIXUP_PROB = 1.0 +# Probability of switching to cutmix when both mixup and cutmix enabled +_C.AUG.MIXUP_SWITCH_PROB = 0.5 +# How to apply mixup/cutmix params. Per "batch", "pair", or "elem" +_C.AUG.MIXUP_MODE = 'batch' + +# ----------------------------------------------------------------------------- +# Testing settings +# ----------------------------------------------------------------------------- +_C.TEST = CN() +# Whether to use center crop when testing +_C.TEST.CROP = True + +# ----------------------------------------------------------------------------- +# Misc +# ----------------------------------------------------------------------------- +# Mixed precision opt level, if O0, no amp is used ('O0', 'O1', 'O2') +# overwritten by command line argument +_C.AMP_OPT_LEVEL = '' +# Path to output folder, overwritten by command line argument +_C.OUTPUT = '' +# Tag of experiment, overwritten by command line argument +_C.TAG = 'default' +# Frequency to save checkpoint +_C.SAVE_FREQ = 1 +# Frequency to logging info +_C.PRINT_FREQ = 10 +# Fixed random seed +_C.SEED = 0 +# Perform evaluation only, overwritten by command line argument +_C.EVAL_MODE = False +# Test throughput only, overwritten by command line argument +_C.THROUGHPUT_MODE = False +# local rank for DistributedDataParallel, given by command line argument +_C.LOCAL_RANK = 0 + + +def _update_config_from_file(config, cfg_file): + config.defrost() + with open(cfg_file, 'r') as f: + yaml_cfg = yaml.load(f, Loader=yaml.FullLoader) + + for cfg in yaml_cfg.setdefault('BASE', ['']): + if cfg: + _update_config_from_file( + config, os.path.join(os.path.dirname(cfg_file), cfg) + ) + print('=> merge config from {}'.format(cfg_file)) + config.merge_from_file(cfg_file) + config.freeze() + + +def update_config(config, args): + _update_config_from_file(config, args.cfg) + + config.defrost() + if args.opts: + config.merge_from_list(args.opts) + + # merge from specific arguments + if args.batch_size: + config.DATA.BATCH_SIZE = args.batch_size + if args.zip: + config.DATA.ZIP_MODE = True + if args.cache_mode: + config.DATA.CACHE_MODE = args.cache_mode + if args.resume: + config.MODEL.RESUME = args.resume + if args.accumulation_steps: + config.TRAIN.ACCUMULATION_STEPS = args.accumulation_steps + if args.use_checkpoint: + config.TRAIN.USE_CHECKPOINT = True + if args.amp_opt_level: + config.AMP_OPT_LEVEL = args.amp_opt_level + if args.tag: + config.TAG = args.tag + if args.eval: + config.EVAL_MODE = True + if args.throughput: + config.THROUGHPUT_MODE = True + + config.freeze() + + +def get_config(args): + """Get a yacs CfgNode object with default values.""" + # Return a clone so that the defaults will not be altered + # This is for the "local variable" use pattern + config = _C.clone() + update_config(config, args) + + return config diff --git a/code/sota/Swin-Unet/configs/swin_tiny_patch4_window7_224_lite.yaml b/code/sota/Swin-Unet/configs/swin_tiny_patch4_window7_224_lite.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e25bd2b1578132dd205dd7be6d46eb56376a0b07 --- /dev/null +++ b/code/sota/Swin-Unet/configs/swin_tiny_patch4_window7_224_lite.yaml @@ -0,0 +1,12 @@ +MODEL: + TYPE: swin + NAME: swin_tiny_patch4_window7_224 + DROP_PATH_RATE: 0.2 + PRETRAIN_CKPT: "./pretrained_ckpt/swin_tiny_patch4_window7_224.pth" + SWIN: + FINAL_UPSAMPLE: "expand_first" + EMBED_DIM: 96 + DEPTHS: [ 2, 2, 2, 2 ] + DECODER_DEPTHS: [ 2, 2, 2, 1] + NUM_HEADS: [ 3, 6, 12, 24 ] + WINDOW_SIZE: 7 \ No newline at end of file diff --git a/code/sota/Swin-Unet/datasets/README.md b/code/sota/Swin-Unet/datasets/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c662f8e2c24f9b5a899338cfe5fcd67f43d1bba5 --- /dev/null +++ b/code/sota/Swin-Unet/datasets/README.md @@ -0,0 +1,29 @@ +# Data Preparing + +1. Access to the synapse multi-organ dataset: + 1. Sign up in the [official Synapse website](https://www.synapse.org/#!Synapse:syn3193805/wiki/) and download the dataset. Convert them to numpy format, clip the images within [-125, 275], normalize each 3D image to [0, 1], and extract 2D slices from 3D volume for training cases while keeping the 3D volume in h5 format for testing cases. + 2. You can also send an Email directly to jienengchen01 AT gmail.com to request the preprocessed data for reproduction. +2. The directory structure of the whole project is as follows: + +```bash +. +├── TransUNet +│   ├──datasets +│   │    └── dataset_*.py +│   ├──train.py +│   ├──test.py +│   └──... +├── model +│   └── vit_checkpoint +│   └── imagenet21k +│      ├── R50+ViT-B_16.npz +│      └── *.npz +└── data + └──Synapse + ├── test_vol_h5 + │   ├── case0001.npy.h5 + │   └── *.npy.h5 + └── train_npz + ├── case0005_slice000.npz + └── *.npz +``` diff --git a/code/sota/Swin-Unet/datasets/__init__.py b/code/sota/Swin-Unet/datasets/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/code/sota/Swin-Unet/datasets/dataset_synapse.py b/code/sota/Swin-Unet/datasets/dataset_synapse.py new file mode 100644 index 0000000000000000000000000000000000000000..951dc22dfb610e6340691b45e5dad8a4d84ece9d --- /dev/null +++ b/code/sota/Swin-Unet/datasets/dataset_synapse.py @@ -0,0 +1,82 @@ +import os +import random + +import h5py +import numpy as np +import torch +from scipy import ndimage +from scipy.ndimage.interpolation import zoom +from torch.utils.data import Dataset + + +def random_rot_flip(image, label): + k = np.random.randint(0, 4) + image = np.rot90(image, k) + label = np.rot90(label, k) + axis = np.random.randint(0, 2) + image = np.flip(image, axis=axis).copy() + label = np.flip(label, axis=axis).copy() + return image, label + + +def random_rotate(image, label): + angle = np.random.randint(-20, 20) + image = ndimage.rotate(image, angle, order=0, reshape=False) + label = ndimage.rotate(label, angle, order=0, reshape=False) + return image, label + + +class RandomGenerator(object): + def __init__(self, output_size): + self.output_size = output_size + + def __call__(self, sample): + image, label = sample['image'], sample['label'] + + if random.random() > 0.5: + image, label = random_rot_flip(image, label) + elif random.random() > 0.5: + image, label = random_rotate(image, label) + x, y = image.shape + if x != self.output_size[0] or y != self.output_size[1]: + image = zoom(image, (self.output_size[0] / x, self.output_size[1] / y), order=3) # why not 3? + label = zoom(label, (self.output_size[0] / x, self.output_size[1] / y), order=0) + image = torch.from_numpy(image.astype(np.float32)).unsqueeze(0) + label = torch.from_numpy(label.astype(np.float32)) + sample = {'image': image, 'label': label.long()} + return sample + + +class Synapse_dataset(Dataset): + def __init__(self, base_dir, list_dir, split, transform=None): + self.transform = transform # using transform in torch! + self.split = split + self.sample_list = open(os.path.join(list_dir, self.split + '.txt')).readlines() + self.data_dir = base_dir + + def __len__(self): + return len(self.sample_list) + + def __getitem__(self, idx): + if self.split in ["train", "val"] or self.sample_list[idx].strip('\n').split(",")[0].endswith(".npz"): + slice_name = self.sample_list[idx].strip('\n').split(",")[0] + if slice_name.endswith(".npz"): + data_path = os.path.join(self.data_dir, slice_name) + else: + data_path = os.path.join(self.data_dir, slice_name + '.npz') + data = np.load(data_path) + try: + image, label = data['image'], data['label'] + except: + image, label = data['data'], data['seg'] + else: + vol_name = self.sample_list[idx].strip('\n') + filepath = self.data_dir + "/{}.npy.h5".format(vol_name) + data = h5py.File(filepath) + image, label = data['image'][:], data['label'][:] + + sample = {'image': image, 'label': label} + if self.transform: + sample = self.transform(sample) + sample['case_name'] = self.sample_list[idx].strip('\n') + return sample diff --git a/code/sota/Swin-Unet/lists/lists_Synapse/all.lst b/code/sota/Swin-Unet/lists/lists_Synapse/all.lst new file mode 100644 index 0000000000000000000000000000000000000000..6ef047d4b8be2ea61d1621620e420a6f3c974ec2 --- /dev/null +++ b/code/sota/Swin-Unet/lists/lists_Synapse/all.lst @@ -0,0 +1,30 @@ +case0031.npy.h5 +case0007.npy.h5 +case0009.npy.h5 +case0005.npy.h5 +case0026.npy.h5 +case0039.npy.h5 +case0024.npy.h5 +case0034.npy.h5 +case0033.npy.h5 +case0030.npy.h5 +case0023.npy.h5 +case0040.npy.h5 +case0010.npy.h5 +case0021.npy.h5 +case0006.npy.h5 +case0027.npy.h5 +case0028.npy.h5 +case0037.npy.h5 +case0008.npy.h5 +case0022.npy.h5 +case0038.npy.h5 +case0036.npy.h5 +case0032.npy.h5 +case0002.npy.h5 +case0029.npy.h5 +case0003.npy.h5 +case0001.npy.h5 +case0004.npy.h5 +case0025.npy.h5 +case0035.npy.h5 diff --git a/code/sota/Swin-Unet/lists/lists_Synapse/test_vol.txt b/code/sota/Swin-Unet/lists/lists_Synapse/test_vol.txt new file mode 100644 index 0000000000000000000000000000000000000000..1c4abd53044eed5457fd1f7e0cca1c99e7222593 --- /dev/null +++ b/code/sota/Swin-Unet/lists/lists_Synapse/test_vol.txt @@ -0,0 +1,12 @@ +case0008 +case0022 +case0038 +case0036 +case0032 +case0002 +case0029 +case0003 +case0001 +case0004 +case0025 +case0035 diff --git a/code/sota/Swin-Unet/lists/lists_Synapse/train.txt b/code/sota/Swin-Unet/lists/lists_Synapse/train.txt new file mode 100644 index 0000000000000000000000000000000000000000..e58616844994a95407d1f664b79cd4e4533d41b8 --- /dev/null +++ b/code/sota/Swin-Unet/lists/lists_Synapse/train.txt @@ -0,0 +1,2211 @@ +case0031_slice000 +case0031_slice001 +case0031_slice002 +case0031_slice003 +case0031_slice004 +case0031_slice005 +case0031_slice006 +case0031_slice007 +case0031_slice008 +case0031_slice009 +case0031_slice010 +case0031_slice011 +case0031_slice012 +case0031_slice013 +case0031_slice014 +case0031_slice015 +case0031_slice016 +case0031_slice017 +case0031_slice018 +case0031_slice019 +case0031_slice020 +case0031_slice021 +case0031_slice022 +case0031_slice023 +case0031_slice024 +case0031_slice025 +case0031_slice026 +case0031_slice027 +case0031_slice028 +case0031_slice029 +case0031_slice030 +case0031_slice031 +case0031_slice032 +case0031_slice033 +case0031_slice034 +case0031_slice035 +case0031_slice036 +case0031_slice037 +case0031_slice038 +case0031_slice039 +case0031_slice040 +case0031_slice041 +case0031_slice042 +case0031_slice043 +case0031_slice044 +case0031_slice045 +case0031_slice046 +case0031_slice047 +case0031_slice048 +case0031_slice049 +case0031_slice050 +case0031_slice051 +case0031_slice052 +case0031_slice053 +case0031_slice054 +case0031_slice055 +case0031_slice056 +case0031_slice057 +case0031_slice058 +case0031_slice059 +case0031_slice060 +case0031_slice061 +case0031_slice062 +case0031_slice063 +case0031_slice064 +case0031_slice065 +case0031_slice066 +case0031_slice067 +case0031_slice068 +case0031_slice069 +case0031_slice070 +case0031_slice071 +case0031_slice072 +case0031_slice073 +case0031_slice074 +case0031_slice075 +case0031_slice076 +case0031_slice077 +case0031_slice078 +case0031_slice079 +case0031_slice080 +case0031_slice081 +case0031_slice082 +case0031_slice083 +case0031_slice084 +case0031_slice085 +case0031_slice086 +case0031_slice087 +case0031_slice088 +case0031_slice089 +case0031_slice090 +case0031_slice091 +case0031_slice092 +case0007_slice000 +case0007_slice001 +case0007_slice002 +case0007_slice003 +case0007_slice004 +case0007_slice005 +case0007_slice006 +case0007_slice007 +case0007_slice008 +case0007_slice009 +case0007_slice010 +case0007_slice011 +case0007_slice012 +case0007_slice013 +case0007_slice014 +case0007_slice015 +case0007_slice016 +case0007_slice017 +case0007_slice018 +case0007_slice019 +case0007_slice020 +case0007_slice021 +case0007_slice022 +case0007_slice023 +case0007_slice024 +case0007_slice025 +case0007_slice026 +case0007_slice027 +case0007_slice028 +case0007_slice029 +case0007_slice030 +case0007_slice031 +case0007_slice032 +case0007_slice033 +case0007_slice034 +case0007_slice035 +case0007_slice036 +case0007_slice037 +case0007_slice038 +case0007_slice039 +case0007_slice040 +case0007_slice041 +case0007_slice042 +case0007_slice043 +case0007_slice044 +case0007_slice045 +case0007_slice046 +case0007_slice047 +case0007_slice048 +case0007_slice049 +case0007_slice050 +case0007_slice051 +case0007_slice052 +case0007_slice053 +case0007_slice054 +case0007_slice055 +case0007_slice056 +case0007_slice057 +case0007_slice058 +case0007_slice059 +case0007_slice060 +case0007_slice061 +case0007_slice062 +case0007_slice063 +case0007_slice064 +case0007_slice065 +case0007_slice066 +case0007_slice067 +case0007_slice068 +case0007_slice069 +case0007_slice070 +case0007_slice071 +case0007_slice072 +case0007_slice073 +case0007_slice074 +case0007_slice075 +case0007_slice076 +case0007_slice077 +case0007_slice078 +case0007_slice079 +case0007_slice080 +case0007_slice081 +case0007_slice082 +case0007_slice083 +case0007_slice084 +case0007_slice085 +case0007_slice086 +case0007_slice087 +case0007_slice088 +case0007_slice089 +case0007_slice090 +case0007_slice091 +case0007_slice092 +case0007_slice093 +case0007_slice094 +case0007_slice095 +case0007_slice096 +case0007_slice097 +case0007_slice098 +case0007_slice099 +case0007_slice100 +case0007_slice101 +case0007_slice102 +case0007_slice103 +case0007_slice104 +case0007_slice105 +case0007_slice106 +case0007_slice107 +case0007_slice108 +case0007_slice109 +case0007_slice110 +case0007_slice111 +case0007_slice112 +case0007_slice113 +case0007_slice114 +case0007_slice115 +case0007_slice116 +case0007_slice117 +case0007_slice118 +case0007_slice119 +case0007_slice120 +case0007_slice121 +case0007_slice122 +case0007_slice123 +case0007_slice124 +case0007_slice125 +case0007_slice126 +case0007_slice127 +case0007_slice128 +case0007_slice129 +case0007_slice130 +case0007_slice131 +case0007_slice132 +case0007_slice133 +case0007_slice134 +case0007_slice135 +case0007_slice136 +case0007_slice137 +case0007_slice138 +case0007_slice139 +case0007_slice140 +case0007_slice141 +case0007_slice142 +case0007_slice143 +case0007_slice144 +case0007_slice145 +case0007_slice146 +case0007_slice147 +case0007_slice148 +case0007_slice149 +case0007_slice150 +case0007_slice151 +case0007_slice152 +case0007_slice153 +case0007_slice154 +case0007_slice155 +case0007_slice156 +case0007_slice157 +case0007_slice158 +case0007_slice159 +case0007_slice160 +case0007_slice161 +case0007_slice162 +case0009_slice000 +case0009_slice001 +case0009_slice002 +case0009_slice003 +case0009_slice004 +case0009_slice005 +case0009_slice006 +case0009_slice007 +case0009_slice008 +case0009_slice009 +case0009_slice010 +case0009_slice011 +case0009_slice012 +case0009_slice013 +case0009_slice014 +case0009_slice015 +case0009_slice016 +case0009_slice017 +case0009_slice018 +case0009_slice019 +case0009_slice020 +case0009_slice021 +case0009_slice022 +case0009_slice023 +case0009_slice024 +case0009_slice025 +case0009_slice026 +case0009_slice027 +case0009_slice028 +case0009_slice029 +case0009_slice030 +case0009_slice031 +case0009_slice032 +case0009_slice033 +case0009_slice034 +case0009_slice035 +case0009_slice036 +case0009_slice037 +case0009_slice038 +case0009_slice039 +case0009_slice040 +case0009_slice041 +case0009_slice042 +case0009_slice043 +case0009_slice044 +case0009_slice045 +case0009_slice046 +case0009_slice047 +case0009_slice048 +case0009_slice049 +case0009_slice050 +case0009_slice051 +case0009_slice052 +case0009_slice053 +case0009_slice054 +case0009_slice055 +case0009_slice056 +case0009_slice057 +case0009_slice058 +case0009_slice059 +case0009_slice060 +case0009_slice061 +case0009_slice062 +case0009_slice063 +case0009_slice064 +case0009_slice065 +case0009_slice066 +case0009_slice067 +case0009_slice068 +case0009_slice069 +case0009_slice070 +case0009_slice071 +case0009_slice072 +case0009_slice073 +case0009_slice074 +case0009_slice075 +case0009_slice076 +case0009_slice077 +case0009_slice078 +case0009_slice079 +case0009_slice080 +case0009_slice081 +case0009_slice082 +case0009_slice083 +case0009_slice084 +case0009_slice085 +case0009_slice086 +case0009_slice087 +case0009_slice088 +case0009_slice089 +case0009_slice090 +case0009_slice091 +case0009_slice092 +case0009_slice093 +case0009_slice094 +case0009_slice095 +case0009_slice096 +case0009_slice097 +case0009_slice098 +case0009_slice099 +case0009_slice100 +case0009_slice101 +case0009_slice102 +case0009_slice103 +case0009_slice104 +case0009_slice105 +case0009_slice106 +case0009_slice107 +case0009_slice108 +case0009_slice109 +case0009_slice110 +case0009_slice111 +case0009_slice112 +case0009_slice113 +case0009_slice114 +case0009_slice115 +case0009_slice116 +case0009_slice117 +case0009_slice118 +case0009_slice119 +case0009_slice120 +case0009_slice121 +case0009_slice122 +case0009_slice123 +case0009_slice124 +case0009_slice125 +case0009_slice126 +case0009_slice127 +case0009_slice128 +case0009_slice129 +case0009_slice130 +case0009_slice131 +case0009_slice132 +case0009_slice133 +case0009_slice134 +case0009_slice135 +case0009_slice136 +case0009_slice137 +case0009_slice138 +case0009_slice139 +case0009_slice140 +case0009_slice141 +case0009_slice142 +case0009_slice143 +case0009_slice144 +case0009_slice145 +case0009_slice146 +case0009_slice147 +case0009_slice148 +case0005_slice000 +case0005_slice001 +case0005_slice002 +case0005_slice003 +case0005_slice004 +case0005_slice005 +case0005_slice006 +case0005_slice007 +case0005_slice008 +case0005_slice009 +case0005_slice010 +case0005_slice011 +case0005_slice012 +case0005_slice013 +case0005_slice014 +case0005_slice015 +case0005_slice016 +case0005_slice017 +case0005_slice018 +case0005_slice019 +case0005_slice020 +case0005_slice021 +case0005_slice022 +case0005_slice023 +case0005_slice024 +case0005_slice025 +case0005_slice026 +case0005_slice027 +case0005_slice028 +case0005_slice029 +case0005_slice030 +case0005_slice031 +case0005_slice032 +case0005_slice033 +case0005_slice034 +case0005_slice035 +case0005_slice036 +case0005_slice037 +case0005_slice038 +case0005_slice039 +case0005_slice040 +case0005_slice041 +case0005_slice042 +case0005_slice043 +case0005_slice044 +case0005_slice045 +case0005_slice046 +case0005_slice047 +case0005_slice048 +case0005_slice049 +case0005_slice050 +case0005_slice051 +case0005_slice052 +case0005_slice053 +case0005_slice054 +case0005_slice055 +case0005_slice056 +case0005_slice057 +case0005_slice058 +case0005_slice059 +case0005_slice060 +case0005_slice061 +case0005_slice062 +case0005_slice063 +case0005_slice064 +case0005_slice065 +case0005_slice066 +case0005_slice067 +case0005_slice068 +case0005_slice069 +case0005_slice070 +case0005_slice071 +case0005_slice072 +case0005_slice073 +case0005_slice074 +case0005_slice075 +case0005_slice076 +case0005_slice077 +case0005_slice078 +case0005_slice079 +case0005_slice080 +case0005_slice081 +case0005_slice082 +case0005_slice083 +case0005_slice084 +case0005_slice085 +case0005_slice086 +case0005_slice087 +case0005_slice088 +case0005_slice089 +case0005_slice090 +case0005_slice091 +case0005_slice092 +case0005_slice093 +case0005_slice094 +case0005_slice095 +case0005_slice096 +case0005_slice097 +case0005_slice098 +case0005_slice099 +case0005_slice100 +case0005_slice101 +case0005_slice102 +case0005_slice103 +case0005_slice104 +case0005_slice105 +case0005_slice106 +case0005_slice107 +case0005_slice108 +case0005_slice109 +case0005_slice110 +case0005_slice111 +case0005_slice112 +case0005_slice113 +case0005_slice114 +case0005_slice115 +case0005_slice116 +case0026_slice000 +case0026_slice001 +case0026_slice002 +case0026_slice003 +case0026_slice004 +case0026_slice005 +case0026_slice006 +case0026_slice007 +case0026_slice008 +case0026_slice009 +case0026_slice010 +case0026_slice011 +case0026_slice012 +case0026_slice013 +case0026_slice014 +case0026_slice015 +case0026_slice016 +case0026_slice017 +case0026_slice018 +case0026_slice019 +case0026_slice020 +case0026_slice021 +case0026_slice022 +case0026_slice023 +case0026_slice024 +case0026_slice025 +case0026_slice026 +case0026_slice027 +case0026_slice028 +case0026_slice029 +case0026_slice030 +case0026_slice031 +case0026_slice032 +case0026_slice033 +case0026_slice034 +case0026_slice035 +case0026_slice036 +case0026_slice037 +case0026_slice038 +case0026_slice039 +case0026_slice040 +case0026_slice041 +case0026_slice042 +case0026_slice043 +case0026_slice044 +case0026_slice045 +case0026_slice046 +case0026_slice047 +case0026_slice048 +case0026_slice049 +case0026_slice050 +case0026_slice051 +case0026_slice052 +case0026_slice053 +case0026_slice054 +case0026_slice055 +case0026_slice056 +case0026_slice057 +case0026_slice058 +case0026_slice059 +case0026_slice060 +case0026_slice061 +case0026_slice062 +case0026_slice063 +case0026_slice064 +case0026_slice065 +case0026_slice066 +case0026_slice067 +case0026_slice068 +case0026_slice069 +case0026_slice070 +case0026_slice071 +case0026_slice072 +case0026_slice073 +case0026_slice074 +case0026_slice075 +case0026_slice076 +case0026_slice077 +case0026_slice078 +case0026_slice079 +case0026_slice080 +case0026_slice081 +case0026_slice082 +case0026_slice083 +case0026_slice084 +case0026_slice085 +case0026_slice086 +case0026_slice087 +case0026_slice088 +case0026_slice089 +case0026_slice090 +case0026_slice091 +case0026_slice092 +case0026_slice093 +case0026_slice094 +case0026_slice095 +case0026_slice096 +case0026_slice097 +case0026_slice098 +case0026_slice099 +case0026_slice100 +case0026_slice101 +case0026_slice102 +case0026_slice103 +case0026_slice104 +case0026_slice105 +case0026_slice106 +case0026_slice107 +case0026_slice108 +case0026_slice109 +case0026_slice110 +case0026_slice111 +case0026_slice112 +case0026_slice113 +case0026_slice114 +case0026_slice115 +case0026_slice116 +case0026_slice117 +case0026_slice118 +case0026_slice119 +case0026_slice120 +case0026_slice121 +case0026_slice122 +case0026_slice123 +case0026_slice124 +case0026_slice125 +case0026_slice126 +case0026_slice127 +case0026_slice128 +case0026_slice129 +case0026_slice130 +case0039_slice000 +case0039_slice001 +case0039_slice002 +case0039_slice003 +case0039_slice004 +case0039_slice005 +case0039_slice006 +case0039_slice007 +case0039_slice008 +case0039_slice009 +case0039_slice010 +case0039_slice011 +case0039_slice012 +case0039_slice013 +case0039_slice014 +case0039_slice015 +case0039_slice016 +case0039_slice017 +case0039_slice018 +case0039_slice019 +case0039_slice020 +case0039_slice021 +case0039_slice022 +case0039_slice023 +case0039_slice024 +case0039_slice025 +case0039_slice026 +case0039_slice027 +case0039_slice028 +case0039_slice029 +case0039_slice030 +case0039_slice031 +case0039_slice032 +case0039_slice033 +case0039_slice034 +case0039_slice035 +case0039_slice036 +case0039_slice037 +case0039_slice038 +case0039_slice039 +case0039_slice040 +case0039_slice041 +case0039_slice042 +case0039_slice043 +case0039_slice044 +case0039_slice045 +case0039_slice046 +case0039_slice047 +case0039_slice048 +case0039_slice049 +case0039_slice050 +case0039_slice051 +case0039_slice052 +case0039_slice053 +case0039_slice054 +case0039_slice055 +case0039_slice056 +case0039_slice057 +case0039_slice058 +case0039_slice059 +case0039_slice060 +case0039_slice061 +case0039_slice062 +case0039_slice063 +case0039_slice064 +case0039_slice065 +case0039_slice066 +case0039_slice067 +case0039_slice068 +case0039_slice069 +case0039_slice070 +case0039_slice071 +case0039_slice072 +case0039_slice073 +case0039_slice074 +case0039_slice075 +case0039_slice076 +case0039_slice077 +case0039_slice078 +case0039_slice079 +case0039_slice080 +case0039_slice081 +case0039_slice082 +case0039_slice083 +case0039_slice084 +case0039_slice085 +case0039_slice086 +case0039_slice087 +case0039_slice088 +case0039_slice089 +case0024_slice000 +case0024_slice001 +case0024_slice002 +case0024_slice003 +case0024_slice004 +case0024_slice005 +case0024_slice006 +case0024_slice007 +case0024_slice008 +case0024_slice009 +case0024_slice010 +case0024_slice011 +case0024_slice012 +case0024_slice013 +case0024_slice014 +case0024_slice015 +case0024_slice016 +case0024_slice017 +case0024_slice018 +case0024_slice019 +case0024_slice020 +case0024_slice021 +case0024_slice022 +case0024_slice023 +case0024_slice024 +case0024_slice025 +case0024_slice026 +case0024_slice027 +case0024_slice028 +case0024_slice029 +case0024_slice030 +case0024_slice031 +case0024_slice032 +case0024_slice033 +case0024_slice034 +case0024_slice035 +case0024_slice036 +case0024_slice037 +case0024_slice038 +case0024_slice039 +case0024_slice040 +case0024_slice041 +case0024_slice042 +case0024_slice043 +case0024_slice044 +case0024_slice045 +case0024_slice046 +case0024_slice047 +case0024_slice048 +case0024_slice049 +case0024_slice050 +case0024_slice051 +case0024_slice052 +case0024_slice053 +case0024_slice054 +case0024_slice055 +case0024_slice056 +case0024_slice057 +case0024_slice058 +case0024_slice059 +case0024_slice060 +case0024_slice061 +case0024_slice062 +case0024_slice063 +case0024_slice064 +case0024_slice065 +case0024_slice066 +case0024_slice067 +case0024_slice068 +case0024_slice069 +case0024_slice070 +case0024_slice071 +case0024_slice072 +case0024_slice073 +case0024_slice074 +case0024_slice075 +case0024_slice076 +case0024_slice077 +case0024_slice078 +case0024_slice079 +case0024_slice080 +case0024_slice081 +case0024_slice082 +case0024_slice083 +case0024_slice084 +case0024_slice085 +case0024_slice086 +case0024_slice087 +case0024_slice088 +case0024_slice089 +case0024_slice090 +case0024_slice091 +case0024_slice092 +case0024_slice093 +case0024_slice094 +case0024_slice095 +case0024_slice096 +case0024_slice097 +case0024_slice098 +case0024_slice099 +case0024_slice100 +case0024_slice101 +case0024_slice102 +case0024_slice103 +case0024_slice104 +case0024_slice105 +case0024_slice106 +case0024_slice107 +case0024_slice108 +case0024_slice109 +case0024_slice110 +case0024_slice111 +case0024_slice112 +case0024_slice113 +case0024_slice114 +case0024_slice115 +case0024_slice116 +case0024_slice117 +case0024_slice118 +case0024_slice119 +case0024_slice120 +case0024_slice121 +case0024_slice122 +case0024_slice123 +case0034_slice000 +case0034_slice001 +case0034_slice002 +case0034_slice003 +case0034_slice004 +case0034_slice005 +case0034_slice006 +case0034_slice007 +case0034_slice008 +case0034_slice009 +case0034_slice010 +case0034_slice011 +case0034_slice012 +case0034_slice013 +case0034_slice014 +case0034_slice015 +case0034_slice016 +case0034_slice017 +case0034_slice018 +case0034_slice019 +case0034_slice020 +case0034_slice021 +case0034_slice022 +case0034_slice023 +case0034_slice024 +case0034_slice025 +case0034_slice026 +case0034_slice027 +case0034_slice028 +case0034_slice029 +case0034_slice030 +case0034_slice031 +case0034_slice032 +case0034_slice033 +case0034_slice034 +case0034_slice035 +case0034_slice036 +case0034_slice037 +case0034_slice038 +case0034_slice039 +case0034_slice040 +case0034_slice041 +case0034_slice042 +case0034_slice043 +case0034_slice044 +case0034_slice045 +case0034_slice046 +case0034_slice047 +case0034_slice048 +case0034_slice049 +case0034_slice050 +case0034_slice051 +case0034_slice052 +case0034_slice053 +case0034_slice054 +case0034_slice055 +case0034_slice056 +case0034_slice057 +case0034_slice058 +case0034_slice059 +case0034_slice060 +case0034_slice061 +case0034_slice062 +case0034_slice063 +case0034_slice064 +case0034_slice065 +case0034_slice066 +case0034_slice067 +case0034_slice068 +case0034_slice069 +case0034_slice070 +case0034_slice071 +case0034_slice072 +case0034_slice073 +case0034_slice074 +case0034_slice075 +case0034_slice076 +case0034_slice077 +case0034_slice078 +case0034_slice079 +case0034_slice080 +case0034_slice081 +case0034_slice082 +case0034_slice083 +case0034_slice084 +case0034_slice085 +case0034_slice086 +case0034_slice087 +case0034_slice088 +case0034_slice089 +case0034_slice090 +case0034_slice091 +case0034_slice092 +case0034_slice093 +case0034_slice094 +case0034_slice095 +case0034_slice096 +case0034_slice097 +case0033_slice000 +case0033_slice001 +case0033_slice002 +case0033_slice003 +case0033_slice004 +case0033_slice005 +case0033_slice006 +case0033_slice007 +case0033_slice008 +case0033_slice009 +case0033_slice010 +case0033_slice011 +case0033_slice012 +case0033_slice013 +case0033_slice014 +case0033_slice015 +case0033_slice016 +case0033_slice017 +case0033_slice018 +case0033_slice019 +case0033_slice020 +case0033_slice021 +case0033_slice022 +case0033_slice023 +case0033_slice024 +case0033_slice025 +case0033_slice026 +case0033_slice027 +case0033_slice028 +case0033_slice029 +case0033_slice030 +case0033_slice031 +case0033_slice032 +case0033_slice033 +case0033_slice034 +case0033_slice035 +case0033_slice036 +case0033_slice037 +case0033_slice038 +case0033_slice039 +case0033_slice040 +case0033_slice041 +case0033_slice042 +case0033_slice043 +case0033_slice044 +case0033_slice045 +case0033_slice046 +case0033_slice047 +case0033_slice048 +case0033_slice049 +case0033_slice050 +case0033_slice051 +case0033_slice052 +case0033_slice053 +case0033_slice054 +case0033_slice055 +case0033_slice056 +case0033_slice057 +case0033_slice058 +case0033_slice059 +case0033_slice060 +case0033_slice061 +case0033_slice062 +case0033_slice063 +case0033_slice064 +case0033_slice065 +case0033_slice066 +case0033_slice067 +case0033_slice068 +case0033_slice069 +case0033_slice070 +case0033_slice071 +case0033_slice072 +case0033_slice073 +case0033_slice074 +case0033_slice075 +case0033_slice076 +case0033_slice077 +case0033_slice078 +case0033_slice079 +case0033_slice080 +case0033_slice081 +case0033_slice082 +case0033_slice083 +case0033_slice084 +case0033_slice085 +case0033_slice086 +case0033_slice087 +case0033_slice088 +case0033_slice089 +case0033_slice090 +case0033_slice091 +case0033_slice092 +case0033_slice093 +case0033_slice094 +case0033_slice095 +case0033_slice096 +case0033_slice097 +case0033_slice098 +case0033_slice099 +case0033_slice100 +case0033_slice101 +case0033_slice102 +case0033_slice103 +case0030_slice000 +case0030_slice001 +case0030_slice002 +case0030_slice003 +case0030_slice004 +case0030_slice005 +case0030_slice006 +case0030_slice007 +case0030_slice008 +case0030_slice009 +case0030_slice010 +case0030_slice011 +case0030_slice012 +case0030_slice013 +case0030_slice014 +case0030_slice015 +case0030_slice016 +case0030_slice017 +case0030_slice018 +case0030_slice019 +case0030_slice020 +case0030_slice021 +case0030_slice022 +case0030_slice023 +case0030_slice024 +case0030_slice025 +case0030_slice026 +case0030_slice027 +case0030_slice028 +case0030_slice029 +case0030_slice030 +case0030_slice031 +case0030_slice032 +case0030_slice033 +case0030_slice034 +case0030_slice035 +case0030_slice036 +case0030_slice037 +case0030_slice038 +case0030_slice039 +case0030_slice040 +case0030_slice041 +case0030_slice042 +case0030_slice043 +case0030_slice044 +case0030_slice045 +case0030_slice046 +case0030_slice047 +case0030_slice048 +case0030_slice049 +case0030_slice050 +case0030_slice051 +case0030_slice052 +case0030_slice053 +case0030_slice054 +case0030_slice055 +case0030_slice056 +case0030_slice057 +case0030_slice058 +case0030_slice059 +case0030_slice060 +case0030_slice061 +case0030_slice062 +case0030_slice063 +case0030_slice064 +case0030_slice065 +case0030_slice066 +case0030_slice067 +case0030_slice068 +case0030_slice069 +case0030_slice070 +case0030_slice071 +case0030_slice072 +case0030_slice073 +case0030_slice074 +case0030_slice075 +case0030_slice076 +case0030_slice077 +case0030_slice078 +case0030_slice079 +case0030_slice080 +case0030_slice081 +case0030_slice082 +case0030_slice083 +case0030_slice084 +case0030_slice085 +case0030_slice086 +case0030_slice087 +case0030_slice088 +case0030_slice089 +case0030_slice090 +case0030_slice091 +case0030_slice092 +case0030_slice093 +case0030_slice094 +case0030_slice095 +case0030_slice096 +case0030_slice097 +case0030_slice098 +case0030_slice099 +case0030_slice100 +case0030_slice101 +case0030_slice102 +case0030_slice103 +case0030_slice104 +case0030_slice105 +case0030_slice106 +case0030_slice107 +case0030_slice108 +case0030_slice109 +case0030_slice110 +case0030_slice111 +case0030_slice112 +case0030_slice113 +case0030_slice114 +case0030_slice115 +case0030_slice116 +case0030_slice117 +case0030_slice118 +case0030_slice119 +case0030_slice120 +case0030_slice121 +case0030_slice122 +case0030_slice123 +case0030_slice124 +case0030_slice125 +case0030_slice126 +case0030_slice127 +case0030_slice128 +case0030_slice129 +case0030_slice130 +case0030_slice131 +case0030_slice132 +case0030_slice133 +case0030_slice134 +case0030_slice135 +case0030_slice136 +case0030_slice137 +case0030_slice138 +case0030_slice139 +case0030_slice140 +case0030_slice141 +case0030_slice142 +case0030_slice143 +case0030_slice144 +case0030_slice145 +case0030_slice146 +case0030_slice147 +case0030_slice148 +case0030_slice149 +case0030_slice150 +case0030_slice151 +case0030_slice152 +case0023_slice000 +case0023_slice001 +case0023_slice002 +case0023_slice003 +case0023_slice004 +case0023_slice005 +case0023_slice006 +case0023_slice007 +case0023_slice008 +case0023_slice009 +case0023_slice010 +case0023_slice011 +case0023_slice012 +case0023_slice013 +case0023_slice014 +case0023_slice015 +case0023_slice016 +case0023_slice017 +case0023_slice018 +case0023_slice019 +case0023_slice020 +case0023_slice021 +case0023_slice022 +case0023_slice023 +case0023_slice024 +case0023_slice025 +case0023_slice026 +case0023_slice027 +case0023_slice028 +case0023_slice029 +case0023_slice030 +case0023_slice031 +case0023_slice032 +case0023_slice033 +case0023_slice034 +case0023_slice035 +case0023_slice036 +case0023_slice037 +case0023_slice038 +case0023_slice039 +case0023_slice040 +case0023_slice041 +case0023_slice042 +case0023_slice043 +case0023_slice044 +case0023_slice045 +case0023_slice046 +case0023_slice047 +case0023_slice048 +case0023_slice049 +case0023_slice050 +case0023_slice051 +case0023_slice052 +case0023_slice053 +case0023_slice054 +case0023_slice055 +case0023_slice056 +case0023_slice057 +case0023_slice058 +case0023_slice059 +case0023_slice060 +case0023_slice061 +case0023_slice062 +case0023_slice063 +case0023_slice064 +case0023_slice065 +case0023_slice066 +case0023_slice067 +case0023_slice068 +case0023_slice069 +case0023_slice070 +case0023_slice071 +case0023_slice072 +case0023_slice073 +case0023_slice074 +case0023_slice075 +case0023_slice076 +case0023_slice077 +case0023_slice078 +case0023_slice079 +case0023_slice080 +case0023_slice081 +case0023_slice082 +case0023_slice083 +case0023_slice084 +case0023_slice085 +case0023_slice086 +case0023_slice087 +case0023_slice088 +case0023_slice089 +case0023_slice090 +case0023_slice091 +case0023_slice092 +case0023_slice093 +case0023_slice094 +case0023_slice095 +case0040_slice000 +case0040_slice001 +case0040_slice002 +case0040_slice003 +case0040_slice004 +case0040_slice005 +case0040_slice006 +case0040_slice007 +case0040_slice008 +case0040_slice009 +case0040_slice010 +case0040_slice011 +case0040_slice012 +case0040_slice013 +case0040_slice014 +case0040_slice015 +case0040_slice016 +case0040_slice017 +case0040_slice018 +case0040_slice019 +case0040_slice020 +case0040_slice021 +case0040_slice022 +case0040_slice023 +case0040_slice024 +case0040_slice025 +case0040_slice026 +case0040_slice027 +case0040_slice028 +case0040_slice029 +case0040_slice030 +case0040_slice031 +case0040_slice032 +case0040_slice033 +case0040_slice034 +case0040_slice035 +case0040_slice036 +case0040_slice037 +case0040_slice038 +case0040_slice039 +case0040_slice040 +case0040_slice041 +case0040_slice042 +case0040_slice043 +case0040_slice044 +case0040_slice045 +case0040_slice046 +case0040_slice047 +case0040_slice048 +case0040_slice049 +case0040_slice050 +case0040_slice051 +case0040_slice052 +case0040_slice053 +case0040_slice054 +case0040_slice055 +case0040_slice056 +case0040_slice057 +case0040_slice058 +case0040_slice059 +case0040_slice060 +case0040_slice061 +case0040_slice062 +case0040_slice063 +case0040_slice064 +case0040_slice065 +case0040_slice066 +case0040_slice067 +case0040_slice068 +case0040_slice069 +case0040_slice070 +case0040_slice071 +case0040_slice072 +case0040_slice073 +case0040_slice074 +case0040_slice075 +case0040_slice076 +case0040_slice077 +case0040_slice078 +case0040_slice079 +case0040_slice080 +case0040_slice081 +case0040_slice082 +case0040_slice083 +case0040_slice084 +case0040_slice085 +case0040_slice086 +case0040_slice087 +case0040_slice088 +case0040_slice089 +case0040_slice090 +case0040_slice091 +case0040_slice092 +case0040_slice093 +case0040_slice094 +case0040_slice095 +case0040_slice096 +case0040_slice097 +case0040_slice098 +case0040_slice099 +case0040_slice100 +case0040_slice101 +case0040_slice102 +case0040_slice103 +case0040_slice104 +case0040_slice105 +case0040_slice106 +case0040_slice107 +case0040_slice108 +case0040_slice109 +case0040_slice110 +case0040_slice111 +case0040_slice112 +case0040_slice113 +case0040_slice114 +case0040_slice115 +case0040_slice116 +case0040_slice117 +case0040_slice118 +case0040_slice119 +case0040_slice120 +case0040_slice121 +case0040_slice122 +case0040_slice123 +case0040_slice124 +case0040_slice125 +case0040_slice126 +case0040_slice127 +case0040_slice128 +case0040_slice129 +case0040_slice130 +case0040_slice131 +case0040_slice132 +case0040_slice133 +case0040_slice134 +case0040_slice135 +case0040_slice136 +case0040_slice137 +case0040_slice138 +case0040_slice139 +case0040_slice140 +case0040_slice141 +case0040_slice142 +case0040_slice143 +case0040_slice144 +case0040_slice145 +case0040_slice146 +case0040_slice147 +case0040_slice148 +case0040_slice149 +case0040_slice150 +case0040_slice151 +case0040_slice152 +case0040_slice153 +case0040_slice154 +case0040_slice155 +case0040_slice156 +case0040_slice157 +case0040_slice158 +case0040_slice159 +case0040_slice160 +case0040_slice161 +case0040_slice162 +case0040_slice163 +case0040_slice164 +case0040_slice165 +case0040_slice166 +case0040_slice167 +case0040_slice168 +case0040_slice169 +case0040_slice170 +case0040_slice171 +case0040_slice172 +case0040_slice173 +case0040_slice174 +case0040_slice175 +case0040_slice176 +case0040_slice177 +case0040_slice178 +case0040_slice179 +case0040_slice180 +case0040_slice181 +case0040_slice182 +case0040_slice183 +case0040_slice184 +case0040_slice185 +case0040_slice186 +case0040_slice187 +case0040_slice188 +case0040_slice189 +case0040_slice190 +case0040_slice191 +case0040_slice192 +case0040_slice193 +case0040_slice194 +case0010_slice000 +case0010_slice001 +case0010_slice002 +case0010_slice003 +case0010_slice004 +case0010_slice005 +case0010_slice006 +case0010_slice007 +case0010_slice008 +case0010_slice009 +case0010_slice010 +case0010_slice011 +case0010_slice012 +case0010_slice013 +case0010_slice014 +case0010_slice015 +case0010_slice016 +case0010_slice017 +case0010_slice018 +case0010_slice019 +case0010_slice020 +case0010_slice021 +case0010_slice022 +case0010_slice023 +case0010_slice024 +case0010_slice025 +case0010_slice026 +case0010_slice027 +case0010_slice028 +case0010_slice029 +case0010_slice030 +case0010_slice031 +case0010_slice032 +case0010_slice033 +case0010_slice034 +case0010_slice035 +case0010_slice036 +case0010_slice037 +case0010_slice038 +case0010_slice039 +case0010_slice040 +case0010_slice041 +case0010_slice042 +case0010_slice043 +case0010_slice044 +case0010_slice045 +case0010_slice046 +case0010_slice047 +case0010_slice048 +case0010_slice049 +case0010_slice050 +case0010_slice051 +case0010_slice052 +case0010_slice053 +case0010_slice054 +case0010_slice055 +case0010_slice056 +case0010_slice057 +case0010_slice058 +case0010_slice059 +case0010_slice060 +case0010_slice061 +case0010_slice062 +case0010_slice063 +case0010_slice064 +case0010_slice065 +case0010_slice066 +case0010_slice067 +case0010_slice068 +case0010_slice069 +case0010_slice070 +case0010_slice071 +case0010_slice072 +case0010_slice073 +case0010_slice074 +case0010_slice075 +case0010_slice076 +case0010_slice077 +case0010_slice078 +case0010_slice079 +case0010_slice080 +case0010_slice081 +case0010_slice082 +case0010_slice083 +case0010_slice084 +case0010_slice085 +case0010_slice086 +case0010_slice087 +case0010_slice088 +case0010_slice089 +case0010_slice090 +case0010_slice091 +case0010_slice092 +case0010_slice093 +case0010_slice094 +case0010_slice095 +case0010_slice096 +case0010_slice097 +case0010_slice098 +case0010_slice099 +case0010_slice100 +case0010_slice101 +case0010_slice102 +case0010_slice103 +case0010_slice104 +case0010_slice105 +case0010_slice106 +case0010_slice107 +case0010_slice108 +case0010_slice109 +case0010_slice110 +case0010_slice111 +case0010_slice112 +case0010_slice113 +case0010_slice114 +case0010_slice115 +case0010_slice116 +case0010_slice117 +case0010_slice118 +case0010_slice119 +case0010_slice120 +case0010_slice121 +case0010_slice122 +case0010_slice123 +case0010_slice124 +case0010_slice125 +case0010_slice126 +case0010_slice127 +case0010_slice128 +case0010_slice129 +case0010_slice130 +case0010_slice131 +case0010_slice132 +case0010_slice133 +case0010_slice134 +case0010_slice135 +case0010_slice136 +case0010_slice137 +case0010_slice138 +case0010_slice139 +case0010_slice140 +case0010_slice141 +case0010_slice142 +case0010_slice143 +case0010_slice144 +case0010_slice145 +case0010_slice146 +case0010_slice147 +case0021_slice000 +case0021_slice001 +case0021_slice002 +case0021_slice003 +case0021_slice004 +case0021_slice005 +case0021_slice006 +case0021_slice007 +case0021_slice008 +case0021_slice009 +case0021_slice010 +case0021_slice011 +case0021_slice012 +case0021_slice013 +case0021_slice014 +case0021_slice015 +case0021_slice016 +case0021_slice017 +case0021_slice018 +case0021_slice019 +case0021_slice020 +case0021_slice021 +case0021_slice022 +case0021_slice023 +case0021_slice024 +case0021_slice025 +case0021_slice026 +case0021_slice027 +case0021_slice028 +case0021_slice029 +case0021_slice030 +case0021_slice031 +case0021_slice032 +case0021_slice033 +case0021_slice034 +case0021_slice035 +case0021_slice036 +case0021_slice037 +case0021_slice038 +case0021_slice039 +case0021_slice040 +case0021_slice041 +case0021_slice042 +case0021_slice043 +case0021_slice044 +case0021_slice045 +case0021_slice046 +case0021_slice047 +case0021_slice048 +case0021_slice049 +case0021_slice050 +case0021_slice051 +case0021_slice052 +case0021_slice053 +case0021_slice054 +case0021_slice055 +case0021_slice056 +case0021_slice057 +case0021_slice058 +case0021_slice059 +case0021_slice060 +case0021_slice061 +case0021_slice062 +case0021_slice063 +case0021_slice064 +case0021_slice065 +case0021_slice066 +case0021_slice067 +case0021_slice068 +case0021_slice069 +case0021_slice070 +case0021_slice071 +case0021_slice072 +case0021_slice073 +case0021_slice074 +case0021_slice075 +case0021_slice076 +case0021_slice077 +case0021_slice078 +case0021_slice079 +case0021_slice080 +case0021_slice081 +case0021_slice082 +case0021_slice083 +case0021_slice084 +case0021_slice085 +case0021_slice086 +case0021_slice087 +case0021_slice088 +case0021_slice089 +case0021_slice090 +case0021_slice091 +case0021_slice092 +case0021_slice093 +case0021_slice094 +case0021_slice095 +case0021_slice096 +case0021_slice097 +case0021_slice098 +case0021_slice099 +case0021_slice100 +case0021_slice101 +case0021_slice102 +case0021_slice103 +case0021_slice104 +case0021_slice105 +case0021_slice106 +case0021_slice107 +case0021_slice108 +case0021_slice109 +case0021_slice110 +case0021_slice111 +case0021_slice112 +case0021_slice113 +case0021_slice114 +case0021_slice115 +case0021_slice116 +case0021_slice117 +case0021_slice118 +case0021_slice119 +case0021_slice120 +case0021_slice121 +case0021_slice122 +case0021_slice123 +case0021_slice124 +case0021_slice125 +case0021_slice126 +case0021_slice127 +case0021_slice128 +case0021_slice129 +case0021_slice130 +case0021_slice131 +case0021_slice132 +case0021_slice133 +case0021_slice134 +case0021_slice135 +case0021_slice136 +case0021_slice137 +case0021_slice138 +case0021_slice139 +case0021_slice140 +case0021_slice141 +case0021_slice142 +case0006_slice000 +case0006_slice001 +case0006_slice002 +case0006_slice003 +case0006_slice004 +case0006_slice005 +case0006_slice006 +case0006_slice007 +case0006_slice008 +case0006_slice009 +case0006_slice010 +case0006_slice011 +case0006_slice012 +case0006_slice013 +case0006_slice014 +case0006_slice015 +case0006_slice016 +case0006_slice017 +case0006_slice018 +case0006_slice019 +case0006_slice020 +case0006_slice021 +case0006_slice022 +case0006_slice023 +case0006_slice024 +case0006_slice025 +case0006_slice026 +case0006_slice027 +case0006_slice028 +case0006_slice029 +case0006_slice030 +case0006_slice031 +case0006_slice032 +case0006_slice033 +case0006_slice034 +case0006_slice035 +case0006_slice036 +case0006_slice037 +case0006_slice038 +case0006_slice039 +case0006_slice040 +case0006_slice041 +case0006_slice042 +case0006_slice043 +case0006_slice044 +case0006_slice045 +case0006_slice046 +case0006_slice047 +case0006_slice048 +case0006_slice049 +case0006_slice050 +case0006_slice051 +case0006_slice052 +case0006_slice053 +case0006_slice054 +case0006_slice055 +case0006_slice056 +case0006_slice057 +case0006_slice058 +case0006_slice059 +case0006_slice060 +case0006_slice061 +case0006_slice062 +case0006_slice063 +case0006_slice064 +case0006_slice065 +case0006_slice066 +case0006_slice067 +case0006_slice068 +case0006_slice069 +case0006_slice070 +case0006_slice071 +case0006_slice072 +case0006_slice073 +case0006_slice074 +case0006_slice075 +case0006_slice076 +case0006_slice077 +case0006_slice078 +case0006_slice079 +case0006_slice080 +case0006_slice081 +case0006_slice082 +case0006_slice083 +case0006_slice084 +case0006_slice085 +case0006_slice086 +case0006_slice087 +case0006_slice088 +case0006_slice089 +case0006_slice090 +case0006_slice091 +case0006_slice092 +case0006_slice093 +case0006_slice094 +case0006_slice095 +case0006_slice096 +case0006_slice097 +case0006_slice098 +case0006_slice099 +case0006_slice100 +case0006_slice101 +case0006_slice102 +case0006_slice103 +case0006_slice104 +case0006_slice105 +case0006_slice106 +case0006_slice107 +case0006_slice108 +case0006_slice109 +case0006_slice110 +case0006_slice111 +case0006_slice112 +case0006_slice113 +case0006_slice114 +case0006_slice115 +case0006_slice116 +case0006_slice117 +case0006_slice118 +case0006_slice119 +case0006_slice120 +case0006_slice121 +case0006_slice122 +case0006_slice123 +case0006_slice124 +case0006_slice125 +case0006_slice126 +case0006_slice127 +case0006_slice128 +case0006_slice129 +case0006_slice130 +case0027_slice000 +case0027_slice001 +case0027_slice002 +case0027_slice003 +case0027_slice004 +case0027_slice005 +case0027_slice006 +case0027_slice007 +case0027_slice008 +case0027_slice009 +case0027_slice010 +case0027_slice011 +case0027_slice012 +case0027_slice013 +case0027_slice014 +case0027_slice015 +case0027_slice016 +case0027_slice017 +case0027_slice018 +case0027_slice019 +case0027_slice020 +case0027_slice021 +case0027_slice022 +case0027_slice023 +case0027_slice024 +case0027_slice025 +case0027_slice026 +case0027_slice027 +case0027_slice028 +case0027_slice029 +case0027_slice030 +case0027_slice031 +case0027_slice032 +case0027_slice033 +case0027_slice034 +case0027_slice035 +case0027_slice036 +case0027_slice037 +case0027_slice038 +case0027_slice039 +case0027_slice040 +case0027_slice041 +case0027_slice042 +case0027_slice043 +case0027_slice044 +case0027_slice045 +case0027_slice046 +case0027_slice047 +case0027_slice048 +case0027_slice049 +case0027_slice050 +case0027_slice051 +case0027_slice052 +case0027_slice053 +case0027_slice054 +case0027_slice055 +case0027_slice056 +case0027_slice057 +case0027_slice058 +case0027_slice059 +case0027_slice060 +case0027_slice061 +case0027_slice062 +case0027_slice063 +case0027_slice064 +case0027_slice065 +case0027_slice066 +case0027_slice067 +case0027_slice068 +case0027_slice069 +case0027_slice070 +case0027_slice071 +case0027_slice072 +case0027_slice073 +case0027_slice074 +case0027_slice075 +case0027_slice076 +case0027_slice077 +case0027_slice078 +case0027_slice079 +case0027_slice080 +case0027_slice081 +case0027_slice082 +case0027_slice083 +case0027_slice084 +case0027_slice085 +case0027_slice086 +case0027_slice087 +case0028_slice000 +case0028_slice001 +case0028_slice002 +case0028_slice003 +case0028_slice004 +case0028_slice005 +case0028_slice006 +case0028_slice007 +case0028_slice008 +case0028_slice009 +case0028_slice010 +case0028_slice011 +case0028_slice012 +case0028_slice013 +case0028_slice014 +case0028_slice015 +case0028_slice016 +case0028_slice017 +case0028_slice018 +case0028_slice019 +case0028_slice020 +case0028_slice021 +case0028_slice022 +case0028_slice023 +case0028_slice024 +case0028_slice025 +case0028_slice026 +case0028_slice027 +case0028_slice028 +case0028_slice029 +case0028_slice030 +case0028_slice031 +case0028_slice032 +case0028_slice033 +case0028_slice034 +case0028_slice035 +case0028_slice036 +case0028_slice037 +case0028_slice038 +case0028_slice039 +case0028_slice040 +case0028_slice041 +case0028_slice042 +case0028_slice043 +case0028_slice044 +case0028_slice045 +case0028_slice046 +case0028_slice047 +case0028_slice048 +case0028_slice049 +case0028_slice050 +case0028_slice051 +case0028_slice052 +case0028_slice053 +case0028_slice054 +case0028_slice055 +case0028_slice056 +case0028_slice057 +case0028_slice058 +case0028_slice059 +case0028_slice060 +case0028_slice061 +case0028_slice062 +case0028_slice063 +case0028_slice064 +case0028_slice065 +case0028_slice066 +case0028_slice067 +case0028_slice068 +case0028_slice069 +case0028_slice070 +case0028_slice071 +case0028_slice072 +case0028_slice073 +case0028_slice074 +case0028_slice075 +case0028_slice076 +case0028_slice077 +case0028_slice078 +case0028_slice079 +case0028_slice080 +case0028_slice081 +case0028_slice082 +case0028_slice083 +case0028_slice084 +case0028_slice085 +case0028_slice086 +case0028_slice087 +case0028_slice088 +case0037_slice000 +case0037_slice001 +case0037_slice002 +case0037_slice003 +case0037_slice004 +case0037_slice005 +case0037_slice006 +case0037_slice007 +case0037_slice008 +case0037_slice009 +case0037_slice010 +case0037_slice011 +case0037_slice012 +case0037_slice013 +case0037_slice014 +case0037_slice015 +case0037_slice016 +case0037_slice017 +case0037_slice018 +case0037_slice019 +case0037_slice020 +case0037_slice021 +case0037_slice022 +case0037_slice023 +case0037_slice024 +case0037_slice025 +case0037_slice026 +case0037_slice027 +case0037_slice028 +case0037_slice029 +case0037_slice030 +case0037_slice031 +case0037_slice032 +case0037_slice033 +case0037_slice034 +case0037_slice035 +case0037_slice036 +case0037_slice037 +case0037_slice038 +case0037_slice039 +case0037_slice040 +case0037_slice041 +case0037_slice042 +case0037_slice043 +case0037_slice044 +case0037_slice045 +case0037_slice046 +case0037_slice047 +case0037_slice048 +case0037_slice049 +case0037_slice050 +case0037_slice051 +case0037_slice052 +case0037_slice053 +case0037_slice054 +case0037_slice055 +case0037_slice056 +case0037_slice057 +case0037_slice058 +case0037_slice059 +case0037_slice060 +case0037_slice061 +case0037_slice062 +case0037_slice063 +case0037_slice064 +case0037_slice065 +case0037_slice066 +case0037_slice067 +case0037_slice068 +case0037_slice069 +case0037_slice070 +case0037_slice071 +case0037_slice072 +case0037_slice073 +case0037_slice074 +case0037_slice075 +case0037_slice076 +case0037_slice077 +case0037_slice078 +case0037_slice079 +case0037_slice080 +case0037_slice081 +case0037_slice082 +case0037_slice083 +case0037_slice084 +case0037_slice085 +case0037_slice086 +case0037_slice087 +case0037_slice088 +case0037_slice089 +case0037_slice090 +case0037_slice091 +case0037_slice092 +case0037_slice093 +case0037_slice094 +case0037_slice095 +case0037_slice096 +case0037_slice097 +case0037_slice098 diff --git a/code/sota/Swin-Unet/make_dataset_txt.py b/code/sota/Swin-Unet/make_dataset_txt.py new file mode 100644 index 0000000000000000000000000000000000000000..fbfd0f86966ddb14f1e728d80cbb3292dcb00a22 --- /dev/null +++ b/code/sota/Swin-Unet/make_dataset_txt.py @@ -0,0 +1,125 @@ +from collections import defaultdict +from itertools import chain +from os.path import join, split, exists +import numpy as np +import os + +import pandas as pd +from deep_utils import DirUtils +from argparse import ArgumentParser +from joblib import Parallel, delayed +from sklearn.model_selection import train_test_split +from tqdm import tqdm + +parser = ArgumentParser() +parser.add_argument("--split", action="store_true") +parser.add_argument("--name", default="datasets", type=str) +parser.add_argument("--n_jobs", default=10, type=int) +parser.add_argument("--data", default=".npz", type=str) +parser.add_argument("--train", action="store_true") +parser.add_argument("--nnunet", + default="/media/aicvi/11111bdb-a0c7-4342-9791-36af7eb70fc0/NNUNET_OUTPUT/nnunet_preprocessed/") + +args = parser.parse_args() + +seed = 1234 + + +def chain(lst: list[list]): + out = [] + for l in lst: + out.extend(l) + return out + + +def npz_csv(): + datasets_config = { + # 'CT_CORONARY': { + # 'data_dir': f'{args.nnunet}/Dataset002_china_narco/nnUNetPlans_2d', + # 'num_classes': 3 + 1, # plus background + # 'predict_head': 1 + # }, + 'MRI_MM': { + 'data_dir': f'{args.nnunet}/Dataset001_mm/nnUNetPlans_2d', + 'num_classes': 3 + 1, # plus background + 'predict_head': 0 + }, + } + + samples = [] + columns = ["data_dir", "predict_head", "n_classes"] + + for dataset_name, config in datasets_config.items(): + data_files = DirUtils.list_dir_full_path(config['data_dir'], interest_extensions=args.data) + split_path = config['data_dir'] + "_split" + if exists(split_path): + data = DirUtils.list_dir_full_path(split_path, return_dict=True, interest_extensions=".npz") + seg_img_samples = dict() + for key, val in tqdm(data.items(), desc="getting data"): + item = key.replace("_seg", "").replace("_img", "") + seg_img_samples[item] = val + + file_samples = defaultdict(list) + for key, val in tqdm(seg_img_samples.items(), desc="Getting final data"): + item = "_".join(k for k in key.split("_")[:-1]) + file_samples[item].append(val) + else: + file_samples = [] + if args.split: + split_path = DirUtils.split_extension(config['data_dir'], suffix="_split") + os.makedirs(split_path, exist_ok=True) + else: + split_path = None + print("Getting ready for the data splitting!") + samples_ = Parallel(n_jobs=args.n_jobs)( + delayed(process_file)(config, split_path, filepath, file_samples) for filepath in tqdm(data_files)) + samples.extend(samples_) + + train, val = train_test_split(samples) + csv_file_path = f'./lists/{args.name}/' + + train = chain(train) + val = chain(val) + os.makedirs(os.path.dirname(csv_file_path), exist_ok=True) + pd.DataFrame(train, columns=columns).to_csv(csv_file_path + "/train.txt", index=False) + pd.DataFrame(val, columns=columns).to_csv(csv_file_path + "/val.txt", index=False) + + +def process_file(config, split_path, filepath, file_samples): + filename = split(filepath)[-1].replace(".npz", "") + if split_path and filename not in file_samples: + # print(filename) + samples = [] + file_data = np.load(filepath) + img = file_data['data'] + seg = file_data['seg'] + for z_index in range(img.shape[1]): + img_ = img[:, z_index, ...] + seg_ = seg[:, z_index, ...] + img_path = join(split_path, + f"{DirUtils.split_extension(split(filepath)[-1], suffix=f'_{z_index:04}')}") + # seg_path = join(split_path, + # f"{DirUtils.split_extension(split(filepath)[-1], suffix=f'_{z_index:04}_seg')}") + if not exists(img_path): + seg_ = seg_.squeeze(0) + seg_[seg_ < 0] = 0 + np.savez(img_path, image=img_.squeeze(0), label=seg_) + samples.append( + [img_path, + config['predict_head'], + config['num_classes'], + ] + ) + # np.savez(seg_path, seg_) + else: + samples = [[ + filepath, + config['predict_head'], + config['num_classes'], + ]] + + return samples + + +if __name__ == '__main__': + npz_csv() diff --git a/code/sota/Swin-Unet/networks/swin_transformer_unet_skip_expand_decoder_sys.py b/code/sota/Swin-Unet/networks/swin_transformer_unet_skip_expand_decoder_sys.py new file mode 100644 index 0000000000000000000000000000000000000000..606370fca273e3c469502a2d981f8221f09a5691 --- /dev/null +++ b/code/sota/Swin-Unet/networks/swin_transformer_unet_skip_expand_decoder_sys.py @@ -0,0 +1,783 @@ +import torch +import torch.nn as nn +import torch.utils.checkpoint as checkpoint +from einops import rearrange +from timm.models.layers import DropPath, to_2tuple, trunc_normal_ + + +class MoEFFNGating(nn.Module): + def __init__(self, dim, hidden_dim, num_experts): + super(MoEFFNGating, self).__init__() + self.gating_network = nn.Linear(dim, dim) + self.experts = nn.ModuleList([nn.Sequential( + nn.Linear(dim, hidden_dim), + nn.GELU(), + nn.Linear(hidden_dim, dim)) for _ in range(num_experts)]) + + def forward(self, x): + weights = self.gating_network(x) + weights = torch.nn.functional.softmax(weights, dim=-1) + outputs = [expert(x) for expert in self.experts] + outputs = torch.stack(outputs, dim=0) + outputs = (weights.unsqueeze(0) * outputs).sum(dim=0) + return outputs + + +class Mlp(nn.Module): + def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.): + super().__init__() + out_features = out_features or in_features + hidden_features = hidden_features or in_features + self.fc1 = nn.Linear(in_features, hidden_features) + self.act = act_layer() + self.fc2 = nn.Linear(hidden_features, out_features) + self.drop = nn.Dropout(drop) + + def forward(self, x): + x = self.fc1(x) + x = self.act(x) + x = self.drop(x) + x = self.fc2(x) + x = self.drop(x) + return x + + +def window_partition(x, window_size): + """ + Args: + x: (B, H, W, C) + window_size (int): window size + + Returns: + windows: (num_windows*B, window_size, window_size, C) + """ + B, H, W, C = x.shape + x = x.view(B, H // window_size, window_size, W // window_size, window_size, C) + windows = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, window_size, window_size, C) + return windows + + +def window_reverse(windows, window_size, H, W): + """ + Args: + windows: (num_windows*B, window_size, window_size, C) + window_size (int): Window size + H (int): Height of image + W (int): Width of image + + Returns: + x: (B, H, W, C) + """ + B = int(windows.shape[0] / (H * W / window_size / window_size)) + x = windows.view(B, H // window_size, W // window_size, window_size, window_size, -1) + x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1) + return x + + +class WindowAttention(nn.Module): + r""" Window based multi-head self attention (W-MSA) module with relative position bias. + It supports both of shifted and non-shifted window. + + Args: + dim (int): Number of input channels. + window_size (tuple[int]): The height and width of the window. + num_heads (int): Number of attention heads. + qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True + qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set + attn_drop (float, optional): Dropout ratio of attention weight. Default: 0.0 + proj_drop (float, optional): Dropout ratio of output. Default: 0.0 + """ + + def __init__(self, dim, window_size, num_heads, qkv_bias=True, qk_scale=None, attn_drop=0., proj_drop=0.): + + super().__init__() + self.dim = dim + self.window_size = window_size # Wh, Ww + self.num_heads = num_heads + head_dim = dim // num_heads + self.scale = qk_scale or head_dim ** -0.5 + + # define a parameter table of relative position bias + self.relative_position_bias_table = nn.Parameter( + torch.zeros((2 * window_size[0] - 1) * (2 * window_size[1] - 1), num_heads)) # 2*Wh-1 * 2*Ww-1, nH + + # get pair-wise relative position index for each token inside the window + coords_h = torch.arange(self.window_size[0]) + coords_w = torch.arange(self.window_size[1]) + coords = torch.stack(torch.meshgrid([coords_h, coords_w])) # 2, Wh, Ww + coords_flatten = torch.flatten(coords, 1) # 2, Wh*Ww + relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :] # 2, Wh*Ww, Wh*Ww + relative_coords = relative_coords.permute(1, 2, 0).contiguous() # Wh*Ww, Wh*Ww, 2 + relative_coords[:, :, 0] += self.window_size[0] - 1 # shift to start from 0 + relative_coords[:, :, 1] += self.window_size[1] - 1 + relative_coords[:, :, 0] *= 2 * self.window_size[1] - 1 + relative_position_index = relative_coords.sum(-1) # Wh*Ww, Wh*Ww + self.register_buffer("relative_position_index", relative_position_index) + + self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop) + self.proj = nn.Linear(dim, dim) + self.proj_drop = nn.Dropout(proj_drop) + + trunc_normal_(self.relative_position_bias_table, std=.02) + self.softmax = nn.Softmax(dim=-1) + + def forward(self, x, mask=None): + """ + Args: + x: input features with shape of (num_windows*B, N, C) + mask: (0/-inf) mask with shape of (num_windows, Wh*Ww, Wh*Ww) or None + """ + B_, N, C = x.shape + qkv = self.qkv(x).reshape(B_, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4) + q, k, v = qkv[0], qkv[1], qkv[2] # make torchscript happy (cannot use tensor as tuple) + + q = q * self.scale + attn = (q @ k.transpose(-2, -1)) + + relative_position_bias = self.relative_position_bias_table[self.relative_position_index.view(-1)].view( + self.window_size[0] * self.window_size[1], self.window_size[0] * self.window_size[1], -1) # Wh*Ww,Wh*Ww,nH + relative_position_bias = relative_position_bias.permute(2, 0, 1).contiguous() # nH, Wh*Ww, Wh*Ww + attn = attn + relative_position_bias.unsqueeze(0) + + if mask is not None: + nW = mask.shape[0] + attn = attn.view(B_ // nW, nW, self.num_heads, N, N) + mask.unsqueeze(1).unsqueeze(0) + attn = attn.view(-1, self.num_heads, N, N) + attn = self.softmax(attn) + else: + attn = self.softmax(attn) + + attn = self.attn_drop(attn) + + x = (attn @ v).transpose(1, 2).reshape(B_, N, C) + x = self.proj(x) + x = self.proj_drop(x) + return x + + def extra_repr(self) -> str: + return f'dim={self.dim}, window_size={self.window_size}, num_heads={self.num_heads}' + + def flops(self, N): + # calculate flops for 1 window with token length of N + flops = 0 + # qkv = self.qkv(x) + flops += N * self.dim * 3 * self.dim + # attn = (q @ k.transpose(-2, -1)) + flops += self.num_heads * N * (self.dim // self.num_heads) * N + # x = (attn @ v) + flops += self.num_heads * N * N * (self.dim // self.num_heads) + # x = self.proj(x) + flops += N * self.dim * self.dim + return flops + + +class SwinTransformerBlock(nn.Module): + r""" Swin Transformer Block. + + Args: + dim (int): Number of input channels. + input_resolution (tuple[int]): Input resulotion. + num_heads (int): Number of attention heads. + window_size (int): Window size. + shift_size (int): Shift size for SW-MSA. + mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. + qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True + qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set. + drop (float, optional): Dropout rate. Default: 0.0 + attn_drop (float, optional): Attention dropout rate. Default: 0.0 + drop_path (float, optional): Stochastic depth rate. Default: 0.0 + act_layer (nn.Module, optional): Activation layer. Default: nn.GELU + norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm + """ + + def __init__(self, dim, input_resolution, num_heads, window_size=7, shift_size=0, + mlp_ratio=4., qkv_bias=True, qk_scale=None, drop=0., attn_drop=0., drop_path=0., + act_layer=nn.GELU, norm_layer=nn.LayerNorm): + super().__init__() + self.dim = dim + self.input_resolution = input_resolution + self.num_heads = num_heads + self.window_size = window_size + self.shift_size = shift_size + self.mlp_ratio = mlp_ratio + if min(self.input_resolution) <= self.window_size: + # if window size is larger than input resolution, we don't partition windows + self.shift_size = 0 + self.window_size = min(self.input_resolution) + assert 0 <= self.shift_size < self.window_size, "shift_size must in 0-window_size" + + self.norm1 = norm_layer(dim) + self.attn = WindowAttention( + dim, window_size=to_2tuple(self.window_size), num_heads=num_heads, + qkv_bias=qkv_bias, qk_scale=qk_scale, attn_drop=attn_drop, proj_drop=drop) + + self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity() + self.norm2 = norm_layer(dim) + mlp_hidden_dim = int(dim * mlp_ratio) + self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop) + + if self.shift_size > 0: + # calculate attention mask for SW-MSA + H, W = self.input_resolution + img_mask = torch.zeros((1, H, W, 1)) # 1 H W 1 + h_slices = (slice(0, -self.window_size), + slice(-self.window_size, -self.shift_size), + slice(-self.shift_size, None)) + w_slices = (slice(0, -self.window_size), + slice(-self.window_size, -self.shift_size), + slice(-self.shift_size, None)) + cnt = 0 + for h in h_slices: + for w in w_slices: + img_mask[:, h, w, :] = cnt + cnt += 1 + + mask_windows = window_partition(img_mask, self.window_size) # nW, window_size, window_size, 1 + mask_windows = mask_windows.view(-1, self.window_size * self.window_size) + attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2) + attn_mask = attn_mask.masked_fill(attn_mask != 0, float(-100.0)).masked_fill(attn_mask == 0, float(0.0)) + else: + attn_mask = None + + self.register_buffer("attn_mask", attn_mask) + + def forward(self, x): + H, W = self.input_resolution + B, L, C = x.shape + assert L == H * W, "input feature has wrong size" + + shortcut = x + x = self.norm1(x) + x = x.view(B, H, W, C) + + # cyclic shift + if self.shift_size > 0: + shifted_x = torch.roll(x, shifts=(-self.shift_size, -self.shift_size), dims=(1, 2)) + else: + shifted_x = x + + # partition windows + x_windows = window_partition(shifted_x, self.window_size) # nW*B, window_size, window_size, C + x_windows = x_windows.view(-1, self.window_size * self.window_size, C) # nW*B, window_size*window_size, C + + # W-MSA/SW-MSA + attn_windows = self.attn(x_windows, mask=self.attn_mask) # nW*B, window_size*window_size, C + + # merge windows + attn_windows = attn_windows.view(-1, self.window_size, self.window_size, C) + shifted_x = window_reverse(attn_windows, self.window_size, H, W) # B H' W' C + + # reverse cyclic shift + if self.shift_size > 0: + x = torch.roll(shifted_x, shifts=(self.shift_size, self.shift_size), dims=(1, 2)) + else: + x = shifted_x + x = x.view(B, H * W, C) + + # FFN + x = shortcut + self.drop_path(x) + x = x + self.drop_path(self.mlp(self.norm2(x))) + + return x + + def extra_repr(self) -> str: + return f"dim={self.dim}, input_resolution={self.input_resolution}, num_heads={self.num_heads}, " \ + f"window_size={self.window_size}, shift_size={self.shift_size}, mlp_ratio={self.mlp_ratio}" + + def flops(self): + flops = 0 + H, W = self.input_resolution + # norm1 + flops += self.dim * H * W + # W-MSA/SW-MSA + nW = H * W / self.window_size / self.window_size + flops += nW * self.attn.flops(self.window_size * self.window_size) + # mlp + flops += 2 * H * W * self.dim * self.dim * self.mlp_ratio + # norm2 + flops += self.dim * H * W + return flops + + +class PatchMerging(nn.Module): + r""" Patch Merging Layer. + + Args: + input_resolution (tuple[int]): Resolution of input feature. + dim (int): Number of input channels. + norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm + """ + + def __init__(self, input_resolution, dim, norm_layer=nn.LayerNorm): + super().__init__() + self.input_resolution = input_resolution + self.dim = dim + self.reduction = nn.Linear(4 * dim, 2 * dim, bias=False) + self.norm = norm_layer(4 * dim) + + def forward(self, x): + """ + x: B, H*W, C + """ + H, W = self.input_resolution + B, L, C = x.shape + assert L == H * W, "input feature has wrong size" + assert H % 2 == 0 and W % 2 == 0, f"x size ({H}*{W}) are not even." + + x = x.view(B, H, W, C) + + x0 = x[:, 0::2, 0::2, :] # B H/2 W/2 C + x1 = x[:, 1::2, 0::2, :] # B H/2 W/2 C + x2 = x[:, 0::2, 1::2, :] # B H/2 W/2 C + x3 = x[:, 1::2, 1::2, :] # B H/2 W/2 C + x = torch.cat([x0, x1, x2, x3], -1) # B H/2 W/2 4*C + x = x.view(B, -1, 4 * C) # B H/2*W/2 4*C + + x = self.norm(x) + x = self.reduction(x) + + return x + + def extra_repr(self) -> str: + return f"input_resolution={self.input_resolution}, dim={self.dim}" + + def flops(self): + H, W = self.input_resolution + flops = H * W * self.dim + flops += (H // 2) * (W // 2) * 4 * self.dim * 2 * self.dim + return flops + + +class PatchExpand(nn.Module): + def __init__(self, input_resolution, dim, dim_scale=2, norm_layer=nn.LayerNorm): + super().__init__() + self.input_resolution = input_resolution + self.dim = dim + self.expand = nn.Linear(dim, 2 * dim, bias=False) if dim_scale == 2 else nn.Identity() + self.norm = norm_layer(dim // dim_scale) + + def forward(self, x): + """ + x: B, H*W, C + """ + H, W = self.input_resolution + x = self.expand(x) + B, L, C = x.shape + assert L == H * W, "input feature has wrong size" + + x = x.view(B, H, W, C) + x = rearrange(x, 'b h w (p1 p2 c)-> b (h p1) (w p2) c', p1=2, p2=2, c=C // 4) + x = x.view(B, -1, C // 4) + x = self.norm(x) + + return x + + +class FinalPatchExpand_X4(nn.Module): + def __init__(self, input_resolution, dim, dim_scale=4, norm_layer=nn.LayerNorm): + super().__init__() + self.input_resolution = input_resolution + self.dim = dim + self.dim_scale = dim_scale + self.expand = nn.Linear(dim, 16 * dim, bias=False) + self.output_dim = dim + self.norm = norm_layer(self.output_dim) + + def forward(self, x): + """ + x: B, H*W, C + """ + H, W = self.input_resolution + x = self.expand(x) + B, L, C = x.shape + assert L == H * W, "input feature has wrong size" + + x = x.view(B, H, W, C) + x = rearrange(x, 'b h w (p1 p2 c)-> b (h p1) (w p2) c', p1=self.dim_scale, p2=self.dim_scale, + c=C // (self.dim_scale ** 2)) + x = x.view(B, -1, self.output_dim) + x = self.norm(x) + + return x + + +class BasicLayer(nn.Module): + """ A basic Swin Transformer layer for one stage. + + Args: + dim (int): Number of input channels. + input_resolution (tuple[int]): Input resolution. + depth (int): Number of blocks. + num_heads (int): Number of attention heads. + window_size (int): Local window size. + mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. + qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True + qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set. + drop (float, optional): Dropout rate. Default: 0.0 + attn_drop (float, optional): Attention dropout rate. Default: 0.0 + drop_path (float | tuple[float], optional): Stochastic depth rate. Default: 0.0 + norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm + downsample (nn.Module | None, optional): Downsample layer at the end of the layer. Default: None + use_checkpoint (bool): Whether to use checkpointing to save memory. Default: False. + """ + + def __init__(self, dim, input_resolution, depth, num_heads, window_size, + mlp_ratio=4., qkv_bias=True, qk_scale=None, drop=0., attn_drop=0., + drop_path=0., norm_layer=nn.LayerNorm, downsample=None, use_checkpoint=False): + + super().__init__() + self.dim = dim + self.input_resolution = input_resolution + self.depth = depth + self.use_checkpoint = use_checkpoint + + # build blocks + self.blocks = nn.ModuleList([ + SwinTransformerBlock(dim=dim, input_resolution=input_resolution, + num_heads=num_heads, window_size=window_size, + shift_size=0 if (i % 2 == 0) else window_size // 2, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, qk_scale=qk_scale, + drop=drop, attn_drop=attn_drop, + drop_path=drop_path[i] if isinstance(drop_path, list) else drop_path, + norm_layer=norm_layer) + for i in range(depth)]) + + # patch merging layer + if downsample is not None: + self.downsample = downsample(input_resolution, dim=dim, norm_layer=norm_layer) + else: + self.downsample = None + + def forward(self, x): + for blk in self.blocks: + if self.use_checkpoint: + x = checkpoint.checkpoint(blk, x) + else: + x = blk(x) + if self.downsample is not None: + x = self.downsample(x) + return x + + def extra_repr(self) -> str: + return f"dim={self.dim}, input_resolution={self.input_resolution}, depth={self.depth}" + + def flops(self): + flops = 0 + for blk in self.blocks: + flops += blk.flops() + if self.downsample is not None: + flops += self.downsample.flops() + return flops + + +class BasicLayer_up(nn.Module): + """ A basic Swin Transformer layer for one stage. + + Args: + dim (int): Number of input channels. + input_resolution (tuple[int]): Input resolution. + depth (int): Number of blocks. + num_heads (int): Number of attention heads. + window_size (int): Local window size. + mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. + qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True + qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set. + drop (float, optional): Dropout rate. Default: 0.0 + attn_drop (float, optional): Attention dropout rate. Default: 0.0 + drop_path (float | tuple[float], optional): Stochastic depth rate. Default: 0.0 + norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm + upsample (nn.Module | None, optional): upsample layer at the end of the layer. Default: None + use_checkpoint (bool): Whether to use checkpointing to save memory. Default: False. + """ + + def __init__(self, dim, input_resolution, depth, num_heads, window_size, + mlp_ratio=4., qkv_bias=True, qk_scale=None, drop=0., attn_drop=0., + drop_path=0., norm_layer=nn.LayerNorm, upsample=None, use_checkpoint=False): + + super().__init__() + self.dim = dim + self.input_resolution = input_resolution + self.depth = depth + self.use_checkpoint = use_checkpoint + + # build blocks + self.blocks = nn.ModuleList([ + SwinTransformerBlock(dim=dim, input_resolution=input_resolution, + num_heads=num_heads, window_size=window_size, + shift_size=0 if (i % 2 == 0) else window_size // 2, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, qk_scale=qk_scale, + drop=drop, attn_drop=attn_drop, + drop_path=drop_path[i] if isinstance(drop_path, list) else drop_path, + norm_layer=norm_layer) + for i in range(depth)]) + + # patch merging layer + if upsample is not None: + self.upsample = PatchExpand(input_resolution, dim=dim, dim_scale=2, norm_layer=norm_layer) + else: + self.upsample = None + + def forward(self, x): + for blk in self.blocks: + if self.use_checkpoint: + x = checkpoint.checkpoint(blk, x) + else: + x = blk(x) + if self.upsample is not None: + x = self.upsample(x) + return x + + +class PatchEmbed(nn.Module): + r""" Image to Patch Embedding + + Args: + img_size (int): Image size. Default: 224. + patch_size (int): Patch token size. Default: 4. + in_chans (int): Number of input image channels. Default: 3. + embed_dim (int): Number of linear projection output channels. Default: 96. + norm_layer (nn.Module, optional): Normalization layer. Default: None + """ + + def __init__(self, img_size=224, patch_size=4, in_chans=3, embed_dim=96, norm_layer=None): + super().__init__() + img_size = to_2tuple(img_size) + patch_size = to_2tuple(patch_size) + patches_resolution = [img_size[0] // patch_size[0], img_size[1] // patch_size[1]] + self.img_size = img_size + self.patch_size = patch_size + self.patches_resolution = patches_resolution + self.num_patches = patches_resolution[0] * patches_resolution[1] + + self.in_chans = in_chans + self.embed_dim = embed_dim + + self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size) + if norm_layer is not None: + self.norm = norm_layer(embed_dim) + else: + self.norm = None + + def forward(self, x): + B, C, H, W = x.shape + # FIXME look at relaxing size constraints + assert H == self.img_size[0] and W == self.img_size[1], \ + f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})." + x = self.proj(x).flatten(2).transpose(1, 2) # B Ph*Pw C + if self.norm is not None: + x = self.norm(x) + return x + + def flops(self): + Ho, Wo = self.patches_resolution + flops = Ho * Wo * self.embed_dim * self.in_chans * (self.patch_size[0] * self.patch_size[1]) + if self.norm is not None: + flops += Ho * Wo * self.embed_dim + return flops + + +class SwinTransformerSys(nn.Module): + r""" Swin Transformer + A PyTorch impl of : `Swin Transformer: Hierarchical Vision Transformer using Shifted Windows` - + https://arxiv.org/pdf/2103.14030 + + Args: + img_size (int | tuple(int)): Input image size. Default 224 + patch_size (int | tuple(int)): Patch size. Default: 4 + in_chans (int): Number of input image channels. Default: 3 + num_classes (int): Number of classes for classification head. Default: 1000 + embed_dim (int): Patch embedding dimension. Default: 96 + depths (tuple(int)): Depth of each Swin Transformer layer. + num_heads (tuple(int)): Number of attention heads in different layers. + window_size (int): Window size. Default: 7 + mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. Default: 4 + qkv_bias (bool): If True, add a learnable bias to query, key, value. Default: True + qk_scale (float): Override default qk scale of head_dim ** -0.5 if set. Default: None + drop_rate (float): Dropout rate. Default: 0 + attn_drop_rate (float): Attention dropout rate. Default: 0 + drop_path_rate (float): Stochastic depth rate. Default: 0.1 + norm_layer (nn.Module): Normalization layer. Default: nn.LayerNorm. + ape (bool): If True, add absolute position embedding to the patch embedding. Default: False + patch_norm (bool): If True, add normalization after patch embedding. Default: True + use_checkpoint (bool): Whether to use checkpointing to save memory. Default: False + """ + + def __init__(self, img_size=224, patch_size=4, in_chans=3, num_classes=1000, + embed_dim=96, depths=[2, 2, 2, 2], depths_decoder=[1, 2, 2, 2], num_heads=[3, 6, 12, 24], + window_size=7, mlp_ratio=4., qkv_bias=True, qk_scale=None, + drop_rate=0., attn_drop_rate=0., drop_path_rate=0.1, + norm_layer=nn.LayerNorm, ape=False, patch_norm=True, + use_checkpoint=False, final_upsample="expand_first", **kwargs): + super().__init__() + + print( + "SwinTransformerSys expand initial----depths:{};depths_decoder:{};drop_path_rate:{};num_classes:{}".format( + depths, + depths_decoder, drop_path_rate, num_classes)) + + self.num_classes = num_classes + self.num_layers = len(depths) + self.embed_dim = embed_dim + self.ape = ape + self.patch_norm = patch_norm + self.num_features = int(embed_dim * 2 ** (self.num_layers - 1)) + self.num_features_up = int(embed_dim * 2) + self.mlp_ratio = mlp_ratio + self.final_upsample = final_upsample + + # split image into non-overlapping patches + self.patch_embed = PatchEmbed( + img_size=img_size, patch_size=patch_size, in_chans=in_chans, embed_dim=embed_dim, + norm_layer=norm_layer if self.patch_norm else None) + num_patches = self.patch_embed.num_patches + patches_resolution = self.patch_embed.patches_resolution + self.patches_resolution = patches_resolution + + # absolute position embedding + if self.ape: + self.absolute_pos_embed = nn.Parameter(torch.zeros(1, num_patches, embed_dim)) + trunc_normal_(self.absolute_pos_embed, std=.02) + + self.pos_drop = nn.Dropout(p=drop_rate) + + # stochastic depth + dpr = [x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))] # stochastic depth decay rule + + # build encoder and bottleneck layers + self.layers = nn.ModuleList() + for i_layer in range(self.num_layers): + layer = BasicLayer(dim=int(embed_dim * 2 ** i_layer), + input_resolution=(patches_resolution[0] // (2 ** i_layer), + patches_resolution[1] // (2 ** i_layer)), + depth=depths[i_layer], + num_heads=num_heads[i_layer], + window_size=window_size, + mlp_ratio=self.mlp_ratio, + qkv_bias=qkv_bias, qk_scale=qk_scale, + drop=drop_rate, attn_drop=attn_drop_rate, + drop_path=dpr[sum(depths[:i_layer]):sum(depths[:i_layer + 1])], + norm_layer=norm_layer, + downsample=PatchMerging if (i_layer < self.num_layers - 1) else None, + use_checkpoint=use_checkpoint) + self.layers.append(layer) + + # build decoder layers + self.layers_up = nn.ModuleList() + self.concat_back_dim = nn.ModuleList() + for i_layer in range(self.num_layers): + concat_linear = nn.Linear(2 * int(embed_dim * 2 ** (self.num_layers - 1 - i_layer)), + int(embed_dim * 2 ** ( + self.num_layers - 1 - i_layer))) if i_layer > 0 else nn.Identity() + if i_layer == 0: + layer_up = PatchExpand( + input_resolution=(patches_resolution[0] // (2 ** (self.num_layers - 1 - i_layer)), + patches_resolution[1] // (2 ** (self.num_layers - 1 - i_layer))), + dim=int(embed_dim * 2 ** (self.num_layers - 1 - i_layer)), dim_scale=2, norm_layer=norm_layer) + else: + layer_up = BasicLayer_up(dim=int(embed_dim * 2 ** (self.num_layers - 1 - i_layer)), + input_resolution=( + patches_resolution[0] // (2 ** (self.num_layers - 1 - i_layer)), + patches_resolution[1] // (2 ** (self.num_layers - 1 - i_layer))), + depth=depths[(self.num_layers - 1 - i_layer)], + num_heads=num_heads[(self.num_layers - 1 - i_layer)], + window_size=window_size, + mlp_ratio=self.mlp_ratio, + qkv_bias=qkv_bias, qk_scale=qk_scale, + drop=drop_rate, attn_drop=attn_drop_rate, + drop_path=dpr[sum(depths[:(self.num_layers - 1 - i_layer)]):sum( + depths[:(self.num_layers - 1 - i_layer) + 1])], + norm_layer=norm_layer, + upsample=PatchExpand if (i_layer < self.num_layers - 1) else None, + use_checkpoint=use_checkpoint) + self.layers_up.append(layer_up) + self.concat_back_dim.append(concat_linear) + + self.norm = norm_layer(self.num_features) + self.norm_up = norm_layer(self.embed_dim) + + if self.final_upsample == "expand_first": + print("---final upsample expand_first---") + self.up = FinalPatchExpand_X4(input_resolution=(img_size // patch_size, img_size // patch_size), + dim_scale=4, dim=embed_dim) + self.output = nn.Conv2d(in_channels=embed_dim, out_channels=self.num_classes, kernel_size=1, bias=False) + + self.apply(self._init_weights) + + def _init_weights(self, m): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight, std=.02) + if isinstance(m, nn.Linear) and m.bias is not None: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.LayerNorm): + nn.init.constant_(m.bias, 0) + nn.init.constant_(m.weight, 1.0) + + @torch.jit.ignore + def no_weight_decay(self): + return {'absolute_pos_embed'} + + @torch.jit.ignore + def no_weight_decay_keywords(self): + return {'relative_position_bias_table'} + + # Encoder and Bottleneck + def forward_features(self, x): + x = self.patch_embed(x) + if self.ape: + x = x + self.absolute_pos_embed + x = self.pos_drop(x) + x_downsample = [] + + for layer in self.layers: + x_downsample.append(x) + x = layer(x) + + x = self.norm(x) # B L C + + return x, x_downsample + + # Dencoder and Skip connection + def forward_up_features(self, x, x_downsample): + for inx, layer_up in enumerate(self.layers_up): + if inx == 0: + x = layer_up(x) + else: + x = torch.cat([x, x_downsample[3 - inx]], -1) + x = self.concat_back_dim[inx](x) + x = layer_up(x) + + x = self.norm_up(x) # B L C + + return x + + def up_x4(self, x): + H, W = self.patches_resolution + B, L, C = x.shape + assert L == H * W, "input features has wrong size" + + if self.final_upsample == "expand_first": + x = self.up(x) + x = x.view(B, 4 * H, 4 * W, -1) + x = x.permute(0, 3, 1, 2) # B,C,H,W + x = self.output(x) + + return x + + def forward(self, x): + x, x_downsample = self.forward_features(x) + x = self.forward_up_features(x, x_downsample) + x = self.up_x4(x) + + return x + + def flops(self): + flops = 0 + flops += self.patch_embed.flops() + for i, layer in enumerate(self.layers): + flops += layer.flops() + flops += self.num_features * self.patches_resolution[0] * self.patches_resolution[1] // (2 ** self.num_layers) + flops += self.num_features * self.num_classes + return flops diff --git a/code/sota/Swin-Unet/networks/vision_transformer.py b/code/sota/Swin-Unet/networks/vision_transformer.py new file mode 100644 index 0000000000000000000000000000000000000000..29b43eab567895dc7fa2ae39f0766fb84da13d08 --- /dev/null +++ b/code/sota/Swin-Unet/networks/vision_transformer.py @@ -0,0 +1,89 @@ +# coding=utf-8 +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import copy +import logging +import math + +from os.path import join as pjoin + +import torch +import torch.nn as nn +import numpy as np + +from torch.nn import CrossEntropyLoss, Dropout, Softmax, Linear, Conv2d, LayerNorm +from torch.nn.modules.utils import _pair +from scipy import ndimage +from .swin_transformer_unet_skip_expand_decoder_sys import SwinTransformerSys + +logger = logging.getLogger(__name__) + +class SwinUnet(nn.Module): + def __init__(self, config, img_size=224, num_classes=21843, zero_head=False, vis=False): + super(SwinUnet, self).__init__() + self.num_classes = num_classes + self.zero_head = zero_head + self.config = config + + self.swin_unet = SwinTransformerSys(img_size=config.DATA.IMG_SIZE, + patch_size=config.MODEL.SWIN.PATCH_SIZE, + in_chans=config.MODEL.SWIN.IN_CHANS, + num_classes=self.num_classes, + embed_dim=config.MODEL.SWIN.EMBED_DIM, + depths=config.MODEL.SWIN.DEPTHS, + num_heads=config.MODEL.SWIN.NUM_HEADS, + window_size=config.MODEL.SWIN.WINDOW_SIZE, + mlp_ratio=config.MODEL.SWIN.MLP_RATIO, + qkv_bias=config.MODEL.SWIN.QKV_BIAS, + qk_scale=config.MODEL.SWIN.QK_SCALE, + drop_rate=config.MODEL.DROP_RATE, + drop_path_rate=config.MODEL.DROP_PATH_RATE, + ape=config.MODEL.SWIN.APE, + patch_norm=config.MODEL.SWIN.PATCH_NORM, + use_checkpoint=config.TRAIN.USE_CHECKPOINT) + + def forward(self, x): + if x.size()[1] == 1: + x = x.repeat(1,3,1,1) + logits = self.swin_unet(x) + return logits + + def load_from(self, config): + pretrained_path = config.MODEL.PRETRAIN_CKPT + if pretrained_path is not None: + print("pretrained_path:{}".format(pretrained_path)) + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + pretrained_dict = torch.load(pretrained_path, map_location=device) + if "model" not in pretrained_dict: + print("---start load pretrained modle by splitting---") + pretrained_dict = {k[17:]:v for k,v in pretrained_dict.items()} + for k in list(pretrained_dict.keys()): + if "output" in k: + print("delete key:{}".format(k)) + del pretrained_dict[k] + msg = self.swin_unet.load_state_dict(pretrained_dict,strict=False) + # print(msg) + return + pretrained_dict = pretrained_dict['model'] + print("---start load pretrained modle of swin encoder---") + + model_dict = self.swin_unet.state_dict() + full_dict = copy.deepcopy(pretrained_dict) + for k, v in pretrained_dict.items(): + if "layers." in k: + current_layer_num = 3-int(k[7:8]) + current_k = "layers_up." + str(current_layer_num) + k[8:] + full_dict.update({current_k:v}) + for k in list(full_dict.keys()): + if k in model_dict: + if full_dict[k].shape != model_dict[k].shape: + print("delete:{};shape pretrain:{};shape model:{}".format(k,v.shape,model_dict[k].shape)) + del full_dict[k] + + msg = self.swin_unet.load_state_dict(full_dict, strict=False) + # print(msg) + else: + print("none pretrain") + \ No newline at end of file diff --git a/code/sota/Swin-Unet/requirements.txt b/code/sota/Swin-Unet/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..14f46836593b279b86a2f64d92242803db73be27 --- /dev/null +++ b/code/sota/Swin-Unet/requirements.txt @@ -0,0 +1,13 @@ +torch +torchvision +numpy +tqdm +tensorboard +tensorboardX +ml-collections +medpy +SimpleITK +scipy +h5py +timm +einops diff --git a/code/sota/Swin-Unet/test.py b/code/sota/Swin-Unet/test.py new file mode 100644 index 0000000000000000000000000000000000000000..c853a9150633a40b187e6fd15618e3b9bf91ae1e --- /dev/null +++ b/code/sota/Swin-Unet/test.py @@ -0,0 +1,153 @@ +from os.path import split +import argparse +import logging +import os +import random +import sys + +import numpy as np +import torch +import torch.backends.cudnn as cudnn +from torch.utils.data import DataLoader +from tqdm import tqdm + +from config import get_config +from datasets.dataset_synapse import Synapse_dataset +from networks.vision_transformer import SwinUnet as ViT_seg +from utils import test_single_volume + +parser = argparse.ArgumentParser() +parser.add_argument('--root_path', type=str, + default='../data/Synapse/test_vol_h5', + help='root dir for validation volume data') # for acdc volume_path=root_dir +parser.add_argument('--dataset', type=str, + default='datasets', help='experiment_name') +parser.add_argument('--num_classes', type=int, + default=9, help='output channel of network') +parser.add_argument('--list_dir', type=str, + default='./lists/lists_Synapse', help='list dir') +parser.add_argument('--output_dir', type=str, help='output dir') +parser.add_argument('--max_iterations', type=int, default=30000, help='maximum epoch number to train') +parser.add_argument('--max_epochs', type=int, default=150, help='maximum epoch number to train') +parser.add_argument('--batch_size', type=int, default=24, + help='batch_size per gpu') +parser.add_argument('--img_size', type=int, default=224, help='input patch size of network input') +parser.add_argument('--is_savenii', action="store_true", help='whether to save results during inference') +parser.add_argument('--test_save_dir', type=str, default='../predictions', help='saving prediction as nii!') +parser.add_argument('--deterministic', type=int, default=1, help='whether use deterministic training') +parser.add_argument('--base_lr', type=float, default=0.01, help='segmentation network learning rate') +parser.add_argument('--seed', type=int, default=1234, help='random seed') +parser.add_argument('--cfg', type=str, required=True, metavar="FILE", help='path to config file', ) +parser.add_argument( + "--opts", + help="Modify config options by adding 'KEY VALUE' pairs. ", + default=None, + nargs='+', +) +parser.add_argument('--zip', action='store_true', help='use zipped dataset instead of folder dataset') +parser.add_argument('--cache-mode', type=str, default='part', choices=['no', 'full', 'part'], + help='no: no cache, ' + 'full: cache all data, ' + 'part: sharding the dataset into nonoverlapping pieces and only cache one piece') +parser.add_argument('--resume', help='resume from checkpoint') +parser.add_argument('--accumulation-steps', type=int, help="gradient accumulation steps") +parser.add_argument('--use-checkpoint', action='store_true', + help="whether to use gradient checkpointing to save memory") +parser.add_argument('--amp-opt-level', type=str, default='O1', choices=['O0', 'O1', 'O2'], + help='mixed precision opt level, if O0, no amp is used') +parser.add_argument('--tag', help='tag of experiment') +parser.add_argument('--eval', action='store_true', help='Perform evaluation only') +parser.add_argument('--throughput', action='store_true', help='Test throughput only') + +parser.add_argument("--n_class", default=4, type=int) +parser.add_argument("--split_name", default="test", help="Directory of the input list") + +args = parser.parse_args() + +if args.dataset == "Synapse": + args.volume_path = os.path.join(args.volume_path, "test_vol_h5") +config = get_config(args) + + +def inference(args, model, test_save_path=None): + db_test = Synapse_dataset(base_dir=args.volume_path, split=args.split_name, list_dir=args.list_dir) + testloader = DataLoader(db_test, batch_size=1, shuffle=False, num_workers=1) + logging.info("{} test iterations per epoch".format(len(testloader))) + model.eval() + metric_list = 0.0 + for i_batch, sampled_batch in tqdm(enumerate(testloader)): + # h, w = sampled_batch["image"].size()[2:] + image, label, case_name = sampled_batch["image"], sampled_batch["label"], sampled_batch['case_name'][0] + if args.dataset == "datasets": + case_name = split(case_name.split(",")[0])[-1] + metric_i = test_single_volume(image, label, model, classes=args.num_classes, + patch_size=[args.img_size, args.img_size], + test_save_path=test_save_path, case=case_name, z_spacing=args.z_spacing) + metric_list += np.array(metric_i) + logging.info('idx %d case %s mean_dice %f mean_hd95 %f' % ( + i_batch, case_name, np.mean(metric_i, axis=0)[0], np.mean(metric_i, axis=0)[1])) + metric_list = metric_list / len(db_test) + for i in range(1, args.num_classes): + logging.info('Mean class %d mean_dice %f mean_hd95 %f' % (i, metric_list[i - 1][0], metric_list[i - 1][1])) + performance = np.mean(metric_list, axis=0)[0] + mean_hd95 = np.mean(metric_list, axis=0)[1] + logging.info('Testing performance in best val model: mean_dice : %f mean_hd95 : %f' % (performance, mean_hd95)) + return "Testing Finished!" + + +if __name__ == "__main__": + if not args.deterministic: + cudnn.benchmark = True + cudnn.deterministic = False + else: + cudnn.benchmark = False + cudnn.deterministic = True + random.seed(args.seed) + np.random.seed(args.seed) + torch.manual_seed(args.seed) + torch.cuda.manual_seed(args.seed) + + dataset_name = args.dataset + dataset_config = { + args.dataset: { + 'root_path': args.root_path, + 'list_dir': f'./lists/{args.dataset}', + 'num_classes': args.n_class, + "z_spacing": 1 + }, + } + args.num_classes = dataset_config[dataset_name]['num_classes'] + args.volume_path = dataset_config[dataset_name]['root_path'] + # args.Dataset = dataset_config[dataset_name]['Dataset'] + args.list_dir = dataset_config[dataset_name]['list_dir'] + args.z_spacing = dataset_config[dataset_name]['z_spacing'] + args.is_pretrain = True + + net = ViT_seg(config, img_size=args.img_size, num_classes=args.num_classes).cuda() + + snapshot = os.path.join(args.output_dir, 'best_model.pth') + if not os.path.exists(snapshot): + snapshot = snapshot.replace('best_model', 'epoch_' + str(args.max_epochs - 1)) + msg = net.load_state_dict(torch.load(snapshot)) + print("self trained swin unet", msg) + snapshot_name = snapshot.split('/')[-1] + + log_folder = './test_log/test_log_' + os.makedirs(log_folder, exist_ok=True) + logging.basicConfig(filename=log_folder + '/' + snapshot_name + ".txt", level=logging.INFO, + format='[%(asctime)s.%(msecs)03d] %(message)s', datefmt='%H:%M:%S') + logging.getLogger().addHandler(logging.StreamHandler(sys.stdout)) + logging.info(str(args)) + logging.info(snapshot_name) + + if args.is_savenii: + args.test_save_dir = os.path.join(args.output_dir, "predictions") + test_save_path = args.test_save_dir + os.makedirs(test_save_path, exist_ok=True) + else: + test_save_path = None + inference(args, net, test_save_path) + +# python train.py --dataset Synapse --cfg $CFG --root_path $DATA_DIR --max_epochs $EPOCH_TIME --output_dir $OUT_DIR --img_size $IMG_SIZE --base_lr $LEARNING_RATE --batch_size $BATCH_SIZE +# python train.py --output_dir './model_out/datasets' --dataset datasets --img_size 224 --batch_size 32 --cfg configs/swin_tiny_patch4_window7_224_lite.yaml --root_path /media/aicvi/11111bdb-a0c7-4342-9791-36af7eb70fc0/NNUNET_OUTPUT/nnunet_preprocessed/Dataset001_mm/nnUNetPlans_2d_split +# python test.py --output_dir ./model_out/datasets --dataset datasets --cfg configs/swin_tiny_patch4_window7_224_lite.yaml --is_saveni --root_path /media/aicvi/11111bdb-a0c7-4342-9791-36af7eb70fc0/NNUNET_OUTPUT/nnunet_preprocessed/Dataset001_mm/test --max_epoch 150 --base_lr 0.05 --img_size 224 --batch_size 24 diff --git a/code/sota/Swin-Unet/test.sh b/code/sota/Swin-Unet/test.sh new file mode 100644 index 0000000000000000000000000000000000000000..84fd3ce8f5d9ad42bace0f1dbb4f76af8c9b9f95 --- /dev/null +++ b/code/sota/Swin-Unet/test.sh @@ -0,0 +1,45 @@ +#!/bin/bash +if [ $epoch_time ]; then + EPOCH_TIME=$epoch_time +else + EPOCH_TIME=150 +fi + +if [ $out_dir ]; then + OUT_DIR=$out_dir +else + OUT_DIR='./model_out' +fi + +if [ $cfg ]; then + CFG=$cfg +else + CFG='configs/swin_tiny_patch4_window7_224_lite.yaml' +fi + +if [ $data_dir ]; then + DATA_DIR=$data_dir +else + DATA_DIR='datasets/Synapse' +fi + +if [ $learning_rate ]; then + LEARNING_RATE=$learning_rate +else + LEARNING_RATE=0.05 +fi + +if [ $img_size ]; then + IMG_SIZE=$img_size +else + IMG_SIZE=224 +fi + +if [ $batch_size ]; then + BATCH_SIZE=$batch_size +else + BATCH_SIZE=24 +fi + +echo "start test model" +python test.py --dataset Synapse --cfg $CFG --root_path $DATA_DIR --max_epochs $EPOCH_TIME --output_dir $OUT_DIR --img_size $IMG_SIZE --base_lr $LEARNING_RATE --batch_size $BATCH_SIZE diff --git a/code/sota/Swin-Unet/train.py b/code/sota/Swin-Unet/train.py new file mode 100644 index 0000000000000000000000000000000000000000..4aae48d3cf3daec09b0cde67f0cf8850489eca4f --- /dev/null +++ b/code/sota/Swin-Unet/train.py @@ -0,0 +1,104 @@ +import argparse +import os +import random +import numpy as np +import torch +import torch.backends.cudnn as cudnn +from networks.vision_transformer import SwinUnet as ViT_seg +from trainer import trainer_synapse +from config import get_config + +parser = argparse.ArgumentParser() +parser.add_argument('--root_path', type=str, + default='../data/Synapse/train_npz', help='root dir for data') +parser.add_argument('--dataset', type=str, + default='Synapse', help='experiment_name') +parser.add_argument('--list_dir', type=str, + default='./lists/lists_Synapse', help='list dir') +parser.add_argument('--num_classes', type=int, + default=9, help='output channel of network') +parser.add_argument('--output_dir', type=str, help='output dir') +parser.add_argument('--max_iterations', type=int, + default=30000, help='maximum epoch number to train') +parser.add_argument('--max_epochs', type=int, + default=150, help='maximum epoch number to train') +parser.add_argument('--batch_size', type=int, + default=24, help='batch_size per gpu') +parser.add_argument('--n_gpu', type=int, default=1, help='total gpu') +parser.add_argument('--deterministic', type=int, default=1, + help='whether use deterministic training') +parser.add_argument('--base_lr', type=float, default=0.01, + help='segmentation network learning rate') +parser.add_argument('--img_size', type=int, + default=224, help='input patch size of network input') +parser.add_argument('--seed', type=int, + default=1234, help='random seed') +parser.add_argument('--cfg', type=str, required=True, metavar="FILE", help='path to config file', ) +parser.add_argument( + "--opts", + help="Modify config options by adding 'KEY VALUE' pairs. ", + default=None, + nargs='+', +) +parser.add_argument('--zip', action='store_true', help='use zipped dataset instead of folder dataset') +parser.add_argument('--cache-mode', type=str, default='part', choices=['no', 'full', 'part'], + help='no: no cache, ' + 'full: cache all data, ' + 'part: sharding the dataset into nonoverlapping pieces and only cache one piece') +parser.add_argument('--resume', help='resume from checkpoint') +parser.add_argument('--accumulation-steps', type=int, help="gradient accumulation steps") +parser.add_argument('--use-checkpoint', action='store_true', + help="whether to use gradient checkpointing to save memory") +parser.add_argument('--amp-opt-level', type=str, default='O1', choices=['O0', 'O1', 'O2'], + help='mixed precision opt level, if O0, no amp is used') +parser.add_argument('--tag', help='tag of experiment') +parser.add_argument('--eval', action='store_true', help='Perform evaluation only') +parser.add_argument('--throughput', action='store_true', help='Test throughput only') +# parser.add_argument("--dataset_name", default="datasets") +parser.add_argument("--n_class", default=4, type=int) +parser.add_argument("--num_workers", default=8, type=int) +parser.add_argument("--eval_interval", default=1, type=int) + +args = parser.parse_args() +if args.dataset == "Synapse": + args.root_path = os.path.join(args.root_path, "train_npz") +config = get_config(args) + +if __name__ == "__main__": + if not args.deterministic: + cudnn.benchmark = True + cudnn.deterministic = False + else: + cudnn.benchmark = False + cudnn.deterministic = True + + random.seed(args.seed) + np.random.seed(args.seed) + torch.manual_seed(args.seed) + torch.cuda.manual_seed(args.seed) + + dataset_name = args.dataset + dataset_config = { + args.dataset: { + 'root_path': args.root_path, + 'list_dir': f'./lists/{args.dataset}', + 'num_classes': args.n_class, + }, + } + + if args.batch_size != 24 and args.batch_size % 6 == 0: + args.base_lr *= args.batch_size / 24 + args.num_classes = dataset_config[dataset_name]['num_classes'] + args.root_path = dataset_config[dataset_name]['root_path'] + args.list_dir = dataset_config[dataset_name]['list_dir'] + + if not os.path.exists(args.output_dir): + os.makedirs(args.output_dir) + net = ViT_seg(config, img_size=args.img_size, num_classes=args.num_classes).cuda() + net.load_from(config) + + # trainer = {'Synapse': trainer_synapse} + trainer_synapse(args, net, args.output_dir) + + +# python train.py --output_dir ./model_out/datasets --dataset datasets --img_size 224 --batch_size 32 --cfg configs/swin_tiny_patch4_window7_224_lite.yaml --root_path /media/aicvi/11111bdb-a0c7-4342-9791-36af7eb70fc0/NNUNET_OUTPUT/nnunet_preprocessed/Dataset001_mm/nnUNetPlans_2d_split \ No newline at end of file diff --git a/code/sota/Swin-Unet/train.sh b/code/sota/Swin-Unet/train.sh new file mode 100644 index 0000000000000000000000000000000000000000..ba9c808bab4f10b41f08140cef3564745acc7000 --- /dev/null +++ b/code/sota/Swin-Unet/train.sh @@ -0,0 +1,45 @@ +#!/bin/bash +if [ $epoch_time ]; then + EPOCH_TIME=$epoch_time +else + EPOCH_TIME=150 +fi + +if [ $out_dir ]; then + OUT_DIR=$out_dir +else + OUT_DIR='./model_out' +fi + +if [ $cfg ]; then + CFG=$cfg +else + CFG='configs/swin_tiny_patch4_window7_224_lite.yaml' +fi + +if [ $data_dir ]; then + DATA_DIR=$data_dir +else + DATA_DIR='datasets/Synapse' +fi + +if [ $learning_rate ]; then + LEARNING_RATE=$learning_rate +else + LEARNING_RATE=0.05 +fi + +if [ $img_size ]; then + IMG_SIZE=$img_size +else + IMG_SIZE=224 +fi + +if [ $batch_size ]; then + BATCH_SIZE=$batch_size +else + BATCH_SIZE=24 +fi + +echo "start train model" +python train.py --dataset Synapse --cfg $CFG --root_path $DATA_DIR --max_epochs $EPOCH_TIME --output_dir $OUT_DIR --img_size $IMG_SIZE --base_lr $LEARNING_RATE --batch_size $BATCH_SIZE diff --git a/code/sota/Swin-Unet/trainer.py b/code/sota/Swin-Unet/trainer.py new file mode 100644 index 0000000000000000000000000000000000000000..b93c211ea159c7c3f621b4504320e2a8be5c07aa --- /dev/null +++ b/code/sota/Swin-Unet/trainer.py @@ -0,0 +1,129 @@ +import logging +import os +import random +import sys + +import torch +import torch.nn as nn +import torch.optim as optim +from tensorboardX import SummaryWriter +from torch.nn.modules.loss import CrossEntropyLoss +from torch.utils.data import DataLoader +from torchvision import transforms +from tqdm import tqdm + +from utils import DiceLoss + + +def trainer_synapse(args, model, snapshot_path): + from datasets.dataset_synapse import Synapse_dataset, RandomGenerator + logging.basicConfig(filename=snapshot_path + "/log.txt", level=logging.INFO, + format='[%(asctime)s.%(msecs)03d] %(message)s', datefmt='%H:%M:%S') + logging.getLogger().addHandler(logging.StreamHandler(sys.stdout)) + logging.info(str(args)) + base_lr = args.base_lr + num_classes = args.num_classes + batch_size = args.batch_size * args.n_gpu + # max_iterations = args.max_iterations + db_train = Synapse_dataset(base_dir=args.root_path, list_dir=args.list_dir, split="train", + transform=transforms.Compose( + [RandomGenerator(output_size=[args.img_size, args.img_size])])) + db_val = Synapse_dataset(base_dir=args.root_path, list_dir=args.list_dir, split="val", + transform=transforms.Compose( + [RandomGenerator(output_size=[args.img_size, args.img_size])])) + print("The length of train set is: {}".format(len(db_train))) + + def worker_init_fn(worker_id): + random.seed(args.seed + worker_id) + + train_loader = DataLoader(db_train, batch_size=batch_size, shuffle=True, num_workers=args.num_workers, + pin_memory=True, + worker_init_fn=worker_init_fn) + val_loader = DataLoader(db_train, batch_size=batch_size, shuffle=False, num_workers=args.num_workers, + pin_memory=True, + worker_init_fn=worker_init_fn) + if args.n_gpu > 1: + model = nn.DataParallel(model) + model.train() + ce_loss = CrossEntropyLoss() + dice_loss = DiceLoss(num_classes) + optimizer = optim.SGD(model.parameters(), lr=base_lr, momentum=0.9, weight_decay=0.0001) + writer = SummaryWriter(snapshot_path + '/log') + iter_num = 0 + max_epoch = args.max_epochs + max_iterations = args.max_epochs * len(train_loader) # max_epoch = max_iterations // len(trainloader) + 1 + logging.info("{} iterations per epoch. {} max iterations ".format(len(train_loader), max_iterations)) + iterator = tqdm(range(max_epoch), ncols=70) + best_loss = 10e10 + for epoch_num in iterator: + model.train() + batch_dice_loss = 0 + batch_ce_loss = 0 + for i_batch, sampled_batch in tqdm(enumerate(train_loader), desc=f"Train: {epoch_num}", total=len(train_loader), + leave=False): + image_batch, label_batch = sampled_batch['image'], sampled_batch['label'] + image_batch, label_batch = image_batch.cuda(), label_batch.cuda() + outputs = model(image_batch) + loss_ce = ce_loss(outputs, label_batch[:].long()) + loss_dice = dice_loss(outputs, label_batch, softmax=True) + loss = 0.4 * loss_ce + 0.6 * loss_dice + optimizer.zero_grad() + loss.backward() + optimizer.step() + lr_ = base_lr * (1.0 - iter_num / max_iterations) ** 0.9 + for param_group in optimizer.param_groups: + param_group['lr'] = lr_ + + iter_num = iter_num + 1 + writer.add_scalar('info/lr', lr_, iter_num) + writer.add_scalar('info/total_loss', loss, iter_num) + writer.add_scalar('info/loss_ce', loss_ce, iter_num) + + # logging.info('Train: iteration : %d/%d, lr : %f, loss : %f, loss_ce: %f, loss_dice: %f' % ( + # iter_num, epoch_num, lr_, loss.item(), loss_ce.item(), loss_dice.item())) + batch_dice_loss += loss_dice.item() + batch_ce_loss += loss_ce.item() + if iter_num % 20 == 0: + image = image_batch[1, 0:1, :, :] + image = (image - image.min()) / (image.max() - image.min()) + writer.add_image('train/Image', image, iter_num) + outputs = torch.argmax(torch.softmax(outputs, dim=1), dim=1, keepdim=True) + writer.add_image('train/Prediction', outputs[1, ...] * 50, iter_num) + labs = label_batch[1, ...].unsqueeze(0) * 50 + writer.add_image('train/GroundTruth', labs, iter_num) + batch_ce_loss /= len(train_loader) + batch_dice_loss /= len(train_loader) + batch_loss = 0.4 * batch_ce_loss + 0.6 * batch_dice_loss + logging.info('Train epoch: %d : loss : %f, loss_ce: %f, loss_dice: %f' % ( + epoch_num, batch_loss, batch_ce_loss, batch_dice_loss)) + if (epoch_num + 1) % args.eval_interval == 0: + model.eval() + batch_dice_loss = 0 + batch_ce_loss = 0 + with torch.no_grad(): + for i_batch, sampled_batch in tqdm(enumerate(val_loader), desc=f"Val: {epoch_num}", + total=len(val_loader), leave=False): + image_batch, label_batch = sampled_batch['image'], sampled_batch['label'] + image_batch, label_batch = image_batch.cuda(), label_batch.cuda() + outputs = model(image_batch) + loss_ce = ce_loss(outputs, label_batch[:].long()) + loss_dice = dice_loss(outputs, label_batch, softmax=True) + batch_dice_loss += loss_dice.item() + batch_ce_loss += loss_ce.item() + + batch_ce_loss /= len(val_loader) + batch_dice_loss /= len(val_loader) + batch_loss = 0.4 * batch_ce_loss + 0.6 * batch_dice_loss + logging.info('Val epoch: %d : loss : %f, loss_ce: %f, loss_dice: %f' % ( + epoch_num, batch_loss, batch_ce_loss, batch_dice_loss)) + if batch_loss < best_loss: + save_mode_path = os.path.join(snapshot_path, 'best_model.pth') + torch.save(model.state_dict(), save_mode_path) + best_loss = batch_loss + else: + save_mode_path = os.path.join(snapshot_path, 'last_model.pth') + torch.save(model.state_dict(), save_mode_path) + logging.info("save model to {}".format(save_mode_path)) + + writer.close() + return "Training Finished!" diff --git a/code/sota/Swin-Unet/utils.py b/code/sota/Swin-Unet/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..1774abbecf278de08091a8a12cc32ef88c625622 --- /dev/null +++ b/code/sota/Swin-Unet/utils.py @@ -0,0 +1,102 @@ +import numpy as np +import torch +from medpy import metric +from scipy.ndimage import zoom +import torch.nn as nn +import SimpleITK as sitk + + +class DiceLoss(nn.Module): + def __init__(self, n_classes): + super(DiceLoss, self).__init__() + self.n_classes = n_classes + + def _one_hot_encoder(self, input_tensor): + tensor_list = [] + for i in range(self.n_classes): + temp_prob = input_tensor == i # * torch.ones_like(input_tensor) + tensor_list.append(temp_prob.unsqueeze(1)) + output_tensor = torch.cat(tensor_list, dim=1) + return output_tensor.float() + + def _dice_loss(self, score, target): + target = target.float() + smooth = 1e-5 + intersect = torch.sum(score * target) + y_sum = torch.sum(target * target) + z_sum = torch.sum(score * score) + loss = (2 * intersect + smooth) / (z_sum + y_sum + smooth) + loss = 1 - loss + return loss + + def forward(self, inputs, target, weight=None, softmax=False): + if softmax: + inputs = torch.softmax(inputs, dim=1) + target = self._one_hot_encoder(target) + if weight is None: + weight = [1] * self.n_classes + assert inputs.size() == target.size(), 'predict {} & target {} shape do not match'.format(inputs.size(), target.size()) + class_wise_dice = [] + loss = 0.0 + for i in range(0, self.n_classes): + dice = self._dice_loss(inputs[:, i], target[:, i]) + class_wise_dice.append(1.0 - dice.item()) + loss += dice * weight[i] + return loss / self.n_classes + + +def calculate_metric_percase(pred, gt): + pred[pred > 0] = 1 + gt[gt > 0] = 1 + if pred.sum() > 0 and gt.sum()>0: + dice = metric.binary.dc(pred, gt) + hd95 = metric.binary.hd95(pred, gt) + return dice, hd95 + elif pred.sum() > 0 and gt.sum()==0: + return 1, 0 + else: + return 0, 0 + + +def test_single_volume(image, label, net, classes, patch_size=[256, 256], test_save_path=None, case=None, z_spacing=1): + image, label = image.squeeze(0).cpu().detach().numpy().squeeze(0), label.squeeze(0).cpu().detach().numpy().squeeze(0) + if len(image.shape) == 3: + prediction = np.zeros_like(label) + for ind in range(image.shape[0]): + slice = image[ind, :, :] + x, y = slice.shape[0], slice.shape[1] + if x != patch_size[0] or y != patch_size[1]: + slice = zoom(slice, (patch_size[0] / x, patch_size[1] / y), order=3) # previous using 0 + input = torch.from_numpy(slice).unsqueeze(0).unsqueeze(0).float().cuda() + net.eval() + with torch.no_grad(): + outputs = net(input) + out = torch.argmax(torch.softmax(outputs, dim=1), dim=1).squeeze(0) + out = out.cpu().detach().numpy() + if x != patch_size[0] or y != patch_size[1]: + pred = zoom(out, (x / patch_size[0], y / patch_size[1]), order=0) + else: + pred = out + prediction[ind] = pred + else: + input = torch.from_numpy(image).unsqueeze( + 0).unsqueeze(0).float().cuda() + net.eval() + with torch.no_grad(): + out = torch.argmax(torch.softmax(net(input), dim=1), dim=1).squeeze(0) + prediction = out.cpu().detach().numpy() + metric_list = [] + for i in range(1, classes): + metric_list.append(calculate_metric_percase(prediction == i, label == i)) + + if test_save_path is not None: + img_itk = sitk.GetImageFromArray(image.astype(np.float32)) + prd_itk = sitk.GetImageFromArray(prediction.astype(np.float32)) + lab_itk = sitk.GetImageFromArray(label.astype(np.float32)) + img_itk.SetSpacing((1, 1, z_spacing)) + prd_itk.SetSpacing((1, 1, z_spacing)) + lab_itk.SetSpacing((1, 1, z_spacing)) + sitk.WriteImage(prd_itk, test_save_path + '/'+case + "_pred.nii.gz") + sitk.WriteImage(img_itk, test_save_path + '/'+ case + "_img.nii.gz") + sitk.WriteImage(lab_itk, test_save_path + '/'+ case + "_gt.nii.gz") + return metric_list \ No newline at end of file diff --git a/code/sota/TransUNet/LICENSE b/code/sota/TransUNet/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/code/sota/TransUNet/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/code/sota/TransUNet/README.md b/code/sota/TransUNet/README.md new file mode 100644 index 0000000000000000000000000000000000000000..067feae204eaf21f1a769a8a88239d58d25e3261 --- /dev/null +++ b/code/sota/TransUNet/README.md @@ -0,0 +1,71 @@ +# TransUNet +This repo holds code for [TransUNet: Transformers Make Strong Encoders for Medical Image Segmentation](https://arxiv.org/pdf/2102.04306.pdf) + +## 📰 News +- [7/26/2024] TransUNet, which supports both 2D and 3D data and incorporates a Transformer encoder and decoder, has been featured in the journal Medical Image Analysis ([link](https://www.sciencedirect.com/science/article/pii/S1361841524002056)). +```bibtex +@article{chen2024transunet, + title={TransUNet: Rethinking the U-Net architecture design for medical image segmentation through the lens of transformers}, + author={Chen, Jieneng and Mei, Jieru and Li, Xianhang and Lu, Yongyi and Yu, Qihang and Wei, Qingyue and Luo, Xiangde and Xie, Yutong and Adeli, Ehsan and Wang, Yan and others}, + journal={Medical Image Analysis}, + pages={103280}, + year={2024}, + publisher={Elsevier} +} +``` + +- [10/15/2023] 🔥 3D version of TransUNet is out! Our 3D TransUNet surpasses nn-UNet with 88.11% Dice score on the BTCV dataset and outperforms the top-1 solution in the BraTs 2021 challenge and secure the second place in BraTs 2023 challenge. Please take a look at the [code](https://github.com/Beckschen/3D-TransUNet/tree/main) and [paper](https://arxiv.org/abs/2310.07781). + + +## Usage + +### 1. Download Google pre-trained ViT models +* [Get models in this link](https://console.cloud.google.com/storage/vit_models/): R50-ViT-B_16, ViT-B_16, ViT-L_16... +```bash +wget https://storage.googleapis.com/vit_models/imagenet21k/{MODEL_NAME}.npz && +mkdir ../model/vit_checkpoint/imagenet21k && +mv {MODEL_NAME}.npz ../model/vit_checkpoint/imagenet21k/{MODEL_NAME}.npz +``` + +[Update 2026/02] The official ViT weights appear to have expired. +You can still download a copy from the [project folder](https://drive.google.com/drive/folders/1ACJEoTp-uqfFJ73qS3eUObQh52nGuzCd?usp=sharing) (same to BTCV preprocessed data). After extraction, find the file at: +`../model/vit_checkpoint/imagenet21k/R50+ViT-B_16.npz` + +### 2. Prepare data (All data are available!) + +All data are available so no need to send emails for data. Please use the [BTCV preprocessed data](https://drive.google.com/drive/folders/1ACJEoTp-uqfFJ73qS3eUObQh52nGuzCd?usp=sharing) and [ACDC data](https://drive.google.com/drive/folders/1KQcrci7aKsYZi1hQoZ3T3QUtcy7b--n4?usp=drive_link). + +### 3. Environment + +Please prepare an environment with python=3.7, and then use the command "pip install -r requirements.txt" for the dependencies. + +### 4. Train/Test + +- Run the train script on synapse dataset. The batch size can be reduced to 12 or 6 to save memory (please also decrease the base_lr linearly), and both can reach similar performance. + +```bash +CUDA_VISIBLE_DEVICES=0 python train.py --dataset Synapse --vit_name R50-ViT-B_16 +``` + +- Run the test script on synapse dataset. It supports testing for both 2D images and 3D volumes. + +```bash +python test.py --dataset Synapse --vit_name R50-ViT-B_16 +``` + +## Reference +* [Google ViT](https://github.com/google-research/vision_transformer) +* [ViT-pytorch](https://github.com/jeonsworld/ViT-pytorch) +* [segmentation_models.pytorch](https://github.com/qubvel/segmentation_models.pytorch) + +## Citations + + +```bibtex +@article{chen2021transunet, + title={TransUNet: Transformers Make Strong Encoders for Medical Image Segmentation}, + author={Chen, Jieneng and Lu, Yongyi and Yu, Qihang and Luo, Xiangde and Adeli, Ehsan and Wang, Yan and Lu, Le and Yuille, Alan L., and Zhou, Yuyin}, + journal={arXiv preprint arXiv:2102.04306}, + year={2021} +} +``` diff --git a/code/sota/TransUNet/datasets/README.md b/code/sota/TransUNet/datasets/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c662f8e2c24f9b5a899338cfe5fcd67f43d1bba5 --- /dev/null +++ b/code/sota/TransUNet/datasets/README.md @@ -0,0 +1,29 @@ +# Data Preparing + +1. Access to the synapse multi-organ dataset: + 1. Sign up in the [official Synapse website](https://www.synapse.org/#!Synapse:syn3193805/wiki/) and download the dataset. Convert them to numpy format, clip the images within [-125, 275], normalize each 3D image to [0, 1], and extract 2D slices from 3D volume for training cases while keeping the 3D volume in h5 format for testing cases. + 2. You can also send an Email directly to jienengchen01 AT gmail.com to request the preprocessed data for reproduction. +2. The directory structure of the whole project is as follows: + +```bash +. +├── TransUNet +│   ├──datasets +│   │    └── dataset_*.py +│   ├──train.py +│   ├──test.py +│   └──... +├── model +│   └── vit_checkpoint +│   └── imagenet21k +│      ├── R50+ViT-B_16.npz +│      └── *.npz +└── data + └──Synapse + ├── test_vol_h5 + │   ├── case0001.npy.h5 + │   └── *.npy.h5 + └── train_npz + ├── case0005_slice000.npz + └── *.npz +``` diff --git a/code/sota/TransUNet/datasets/dataset_synapse.py b/code/sota/TransUNet/datasets/dataset_synapse.py new file mode 100644 index 0000000000000000000000000000000000000000..c5d0de1a99f8ca46851f51e45570d4ddc8fbff09 --- /dev/null +++ b/code/sota/TransUNet/datasets/dataset_synapse.py @@ -0,0 +1,75 @@ +import os +import random +import h5py +import numpy as np +import torch +from scipy import ndimage +from scipy.ndimage.interpolation import zoom +from torch.utils.data import Dataset + + +def random_rot_flip(image, label): + k = np.random.randint(0, 4) + image = np.rot90(image, k) + label = np.rot90(label, k) + axis = np.random.randint(0, 2) + image = np.flip(image, axis=axis).copy() + label = np.flip(label, axis=axis).copy() + return image, label + + +def random_rotate(image, label): + angle = np.random.randint(-20, 20) + image = ndimage.rotate(image, angle, order=0, reshape=False) + label = ndimage.rotate(label, angle, order=0, reshape=False) + return image, label + + +class RandomGenerator(object): + def __init__(self, output_size): + self.output_size = output_size + + def __call__(self, sample): + image, label = sample['image'], sample['label'] + + if random.random() > 0.5: + image, label = random_rot_flip(image, label) + elif random.random() > 0.5: + image, label = random_rotate(image, label) + x, y = image.shape + if x != self.output_size[0] or y != self.output_size[1]: + image = zoom(image, (self.output_size[0] / x, self.output_size[1] / y), order=3) # why not 3? + label = zoom(label, (self.output_size[0] / x, self.output_size[1] / y), order=0) + image = torch.from_numpy(image.astype(np.float32)).unsqueeze(0) + label = torch.from_numpy(label.astype(np.float32)) + sample = {'image': image, 'label': label.long()} + return sample + + +class Synapse_dataset(Dataset): + def __init__(self, base_dir, list_dir, split, transform=None): + self.transform = transform # using transform in torch! + self.split = split + self.sample_list = open(os.path.join(list_dir, self.split+'.txt')).readlines() + self.data_dir = base_dir + + def __len__(self): + return len(self.sample_list) + + def __getitem__(self, idx): + if self.split == "train": + slice_name = self.sample_list[idx].strip('\n') + data_path = os.path.join(self.data_dir, slice_name+'.npz') + data = np.load(data_path) + image, label = data['image'], data['label'] + else: + vol_name = self.sample_list[idx].strip('\n') + filepath = self.data_dir + "/{}.npy.h5".format(vol_name) + data = h5py.File(filepath) + image, label = data['image'][:], data['label'][:] + + sample = {'image': image, 'label': label} + if self.transform: + sample = self.transform(sample) + sample['case_name'] = self.sample_list[idx].strip('\n') + return sample diff --git a/code/sota/TransUNet/lists/lists_Synapse/all.lst b/code/sota/TransUNet/lists/lists_Synapse/all.lst new file mode 100644 index 0000000000000000000000000000000000000000..6ef047d4b8be2ea61d1621620e420a6f3c974ec2 --- /dev/null +++ b/code/sota/TransUNet/lists/lists_Synapse/all.lst @@ -0,0 +1,30 @@ +case0031.npy.h5 +case0007.npy.h5 +case0009.npy.h5 +case0005.npy.h5 +case0026.npy.h5 +case0039.npy.h5 +case0024.npy.h5 +case0034.npy.h5 +case0033.npy.h5 +case0030.npy.h5 +case0023.npy.h5 +case0040.npy.h5 +case0010.npy.h5 +case0021.npy.h5 +case0006.npy.h5 +case0027.npy.h5 +case0028.npy.h5 +case0037.npy.h5 +case0008.npy.h5 +case0022.npy.h5 +case0038.npy.h5 +case0036.npy.h5 +case0032.npy.h5 +case0002.npy.h5 +case0029.npy.h5 +case0003.npy.h5 +case0001.npy.h5 +case0004.npy.h5 +case0025.npy.h5 +case0035.npy.h5 diff --git a/code/sota/TransUNet/lists/lists_Synapse/test_vol.txt b/code/sota/TransUNet/lists/lists_Synapse/test_vol.txt new file mode 100644 index 0000000000000000000000000000000000000000..1c4abd53044eed5457fd1f7e0cca1c99e7222593 --- /dev/null +++ b/code/sota/TransUNet/lists/lists_Synapse/test_vol.txt @@ -0,0 +1,12 @@ +case0008 +case0022 +case0038 +case0036 +case0032 +case0002 +case0029 +case0003 +case0001 +case0004 +case0025 +case0035 diff --git a/code/sota/TransUNet/lists/lists_Synapse/train.txt b/code/sota/TransUNet/lists/lists_Synapse/train.txt new file mode 100644 index 0000000000000000000000000000000000000000..e58616844994a95407d1f664b79cd4e4533d41b8 --- /dev/null +++ b/code/sota/TransUNet/lists/lists_Synapse/train.txt @@ -0,0 +1,2211 @@ +case0031_slice000 +case0031_slice001 +case0031_slice002 +case0031_slice003 +case0031_slice004 +case0031_slice005 +case0031_slice006 +case0031_slice007 +case0031_slice008 +case0031_slice009 +case0031_slice010 +case0031_slice011 +case0031_slice012 +case0031_slice013 +case0031_slice014 +case0031_slice015 +case0031_slice016 +case0031_slice017 +case0031_slice018 +case0031_slice019 +case0031_slice020 +case0031_slice021 +case0031_slice022 +case0031_slice023 +case0031_slice024 +case0031_slice025 +case0031_slice026 +case0031_slice027 +case0031_slice028 +case0031_slice029 +case0031_slice030 +case0031_slice031 +case0031_slice032 +case0031_slice033 +case0031_slice034 +case0031_slice035 +case0031_slice036 +case0031_slice037 +case0031_slice038 +case0031_slice039 +case0031_slice040 +case0031_slice041 +case0031_slice042 +case0031_slice043 +case0031_slice044 +case0031_slice045 +case0031_slice046 +case0031_slice047 +case0031_slice048 +case0031_slice049 +case0031_slice050 +case0031_slice051 +case0031_slice052 +case0031_slice053 +case0031_slice054 +case0031_slice055 +case0031_slice056 +case0031_slice057 +case0031_slice058 +case0031_slice059 +case0031_slice060 +case0031_slice061 +case0031_slice062 +case0031_slice063 +case0031_slice064 +case0031_slice065 +case0031_slice066 +case0031_slice067 +case0031_slice068 +case0031_slice069 +case0031_slice070 +case0031_slice071 +case0031_slice072 +case0031_slice073 +case0031_slice074 +case0031_slice075 +case0031_slice076 +case0031_slice077 +case0031_slice078 +case0031_slice079 +case0031_slice080 +case0031_slice081 +case0031_slice082 +case0031_slice083 +case0031_slice084 +case0031_slice085 +case0031_slice086 +case0031_slice087 +case0031_slice088 +case0031_slice089 +case0031_slice090 +case0031_slice091 +case0031_slice092 +case0007_slice000 +case0007_slice001 +case0007_slice002 +case0007_slice003 +case0007_slice004 +case0007_slice005 +case0007_slice006 +case0007_slice007 +case0007_slice008 +case0007_slice009 +case0007_slice010 +case0007_slice011 +case0007_slice012 +case0007_slice013 +case0007_slice014 +case0007_slice015 +case0007_slice016 +case0007_slice017 +case0007_slice018 +case0007_slice019 +case0007_slice020 +case0007_slice021 +case0007_slice022 +case0007_slice023 +case0007_slice024 +case0007_slice025 +case0007_slice026 +case0007_slice027 +case0007_slice028 +case0007_slice029 +case0007_slice030 +case0007_slice031 +case0007_slice032 +case0007_slice033 +case0007_slice034 +case0007_slice035 +case0007_slice036 +case0007_slice037 +case0007_slice038 +case0007_slice039 +case0007_slice040 +case0007_slice041 +case0007_slice042 +case0007_slice043 +case0007_slice044 +case0007_slice045 +case0007_slice046 +case0007_slice047 +case0007_slice048 +case0007_slice049 +case0007_slice050 +case0007_slice051 +case0007_slice052 +case0007_slice053 +case0007_slice054 +case0007_slice055 +case0007_slice056 +case0007_slice057 +case0007_slice058 +case0007_slice059 +case0007_slice060 +case0007_slice061 +case0007_slice062 +case0007_slice063 +case0007_slice064 +case0007_slice065 +case0007_slice066 +case0007_slice067 +case0007_slice068 +case0007_slice069 +case0007_slice070 +case0007_slice071 +case0007_slice072 +case0007_slice073 +case0007_slice074 +case0007_slice075 +case0007_slice076 +case0007_slice077 +case0007_slice078 +case0007_slice079 +case0007_slice080 +case0007_slice081 +case0007_slice082 +case0007_slice083 +case0007_slice084 +case0007_slice085 +case0007_slice086 +case0007_slice087 +case0007_slice088 +case0007_slice089 +case0007_slice090 +case0007_slice091 +case0007_slice092 +case0007_slice093 +case0007_slice094 +case0007_slice095 +case0007_slice096 +case0007_slice097 +case0007_slice098 +case0007_slice099 +case0007_slice100 +case0007_slice101 +case0007_slice102 +case0007_slice103 +case0007_slice104 +case0007_slice105 +case0007_slice106 +case0007_slice107 +case0007_slice108 +case0007_slice109 +case0007_slice110 +case0007_slice111 +case0007_slice112 +case0007_slice113 +case0007_slice114 +case0007_slice115 +case0007_slice116 +case0007_slice117 +case0007_slice118 +case0007_slice119 +case0007_slice120 +case0007_slice121 +case0007_slice122 +case0007_slice123 +case0007_slice124 +case0007_slice125 +case0007_slice126 +case0007_slice127 +case0007_slice128 +case0007_slice129 +case0007_slice130 +case0007_slice131 +case0007_slice132 +case0007_slice133 +case0007_slice134 +case0007_slice135 +case0007_slice136 +case0007_slice137 +case0007_slice138 +case0007_slice139 +case0007_slice140 +case0007_slice141 +case0007_slice142 +case0007_slice143 +case0007_slice144 +case0007_slice145 +case0007_slice146 +case0007_slice147 +case0007_slice148 +case0007_slice149 +case0007_slice150 +case0007_slice151 +case0007_slice152 +case0007_slice153 +case0007_slice154 +case0007_slice155 +case0007_slice156 +case0007_slice157 +case0007_slice158 +case0007_slice159 +case0007_slice160 +case0007_slice161 +case0007_slice162 +case0009_slice000 +case0009_slice001 +case0009_slice002 +case0009_slice003 +case0009_slice004 +case0009_slice005 +case0009_slice006 +case0009_slice007 +case0009_slice008 +case0009_slice009 +case0009_slice010 +case0009_slice011 +case0009_slice012 +case0009_slice013 +case0009_slice014 +case0009_slice015 +case0009_slice016 +case0009_slice017 +case0009_slice018 +case0009_slice019 +case0009_slice020 +case0009_slice021 +case0009_slice022 +case0009_slice023 +case0009_slice024 +case0009_slice025 +case0009_slice026 +case0009_slice027 +case0009_slice028 +case0009_slice029 +case0009_slice030 +case0009_slice031 +case0009_slice032 +case0009_slice033 +case0009_slice034 +case0009_slice035 +case0009_slice036 +case0009_slice037 +case0009_slice038 +case0009_slice039 +case0009_slice040 +case0009_slice041 +case0009_slice042 +case0009_slice043 +case0009_slice044 +case0009_slice045 +case0009_slice046 +case0009_slice047 +case0009_slice048 +case0009_slice049 +case0009_slice050 +case0009_slice051 +case0009_slice052 +case0009_slice053 +case0009_slice054 +case0009_slice055 +case0009_slice056 +case0009_slice057 +case0009_slice058 +case0009_slice059 +case0009_slice060 +case0009_slice061 +case0009_slice062 +case0009_slice063 +case0009_slice064 +case0009_slice065 +case0009_slice066 +case0009_slice067 +case0009_slice068 +case0009_slice069 +case0009_slice070 +case0009_slice071 +case0009_slice072 +case0009_slice073 +case0009_slice074 +case0009_slice075 +case0009_slice076 +case0009_slice077 +case0009_slice078 +case0009_slice079 +case0009_slice080 +case0009_slice081 +case0009_slice082 +case0009_slice083 +case0009_slice084 +case0009_slice085 +case0009_slice086 +case0009_slice087 +case0009_slice088 +case0009_slice089 +case0009_slice090 +case0009_slice091 +case0009_slice092 +case0009_slice093 +case0009_slice094 +case0009_slice095 +case0009_slice096 +case0009_slice097 +case0009_slice098 +case0009_slice099 +case0009_slice100 +case0009_slice101 +case0009_slice102 +case0009_slice103 +case0009_slice104 +case0009_slice105 +case0009_slice106 +case0009_slice107 +case0009_slice108 +case0009_slice109 +case0009_slice110 +case0009_slice111 +case0009_slice112 +case0009_slice113 +case0009_slice114 +case0009_slice115 +case0009_slice116 +case0009_slice117 +case0009_slice118 +case0009_slice119 +case0009_slice120 +case0009_slice121 +case0009_slice122 +case0009_slice123 +case0009_slice124 +case0009_slice125 +case0009_slice126 +case0009_slice127 +case0009_slice128 +case0009_slice129 +case0009_slice130 +case0009_slice131 +case0009_slice132 +case0009_slice133 +case0009_slice134 +case0009_slice135 +case0009_slice136 +case0009_slice137 +case0009_slice138 +case0009_slice139 +case0009_slice140 +case0009_slice141 +case0009_slice142 +case0009_slice143 +case0009_slice144 +case0009_slice145 +case0009_slice146 +case0009_slice147 +case0009_slice148 +case0005_slice000 +case0005_slice001 +case0005_slice002 +case0005_slice003 +case0005_slice004 +case0005_slice005 +case0005_slice006 +case0005_slice007 +case0005_slice008 +case0005_slice009 +case0005_slice010 +case0005_slice011 +case0005_slice012 +case0005_slice013 +case0005_slice014 +case0005_slice015 +case0005_slice016 +case0005_slice017 +case0005_slice018 +case0005_slice019 +case0005_slice020 +case0005_slice021 +case0005_slice022 +case0005_slice023 +case0005_slice024 +case0005_slice025 +case0005_slice026 +case0005_slice027 +case0005_slice028 +case0005_slice029 +case0005_slice030 +case0005_slice031 +case0005_slice032 +case0005_slice033 +case0005_slice034 +case0005_slice035 +case0005_slice036 +case0005_slice037 +case0005_slice038 +case0005_slice039 +case0005_slice040 +case0005_slice041 +case0005_slice042 +case0005_slice043 +case0005_slice044 +case0005_slice045 +case0005_slice046 +case0005_slice047 +case0005_slice048 +case0005_slice049 +case0005_slice050 +case0005_slice051 +case0005_slice052 +case0005_slice053 +case0005_slice054 +case0005_slice055 +case0005_slice056 +case0005_slice057 +case0005_slice058 +case0005_slice059 +case0005_slice060 +case0005_slice061 +case0005_slice062 +case0005_slice063 +case0005_slice064 +case0005_slice065 +case0005_slice066 +case0005_slice067 +case0005_slice068 +case0005_slice069 +case0005_slice070 +case0005_slice071 +case0005_slice072 +case0005_slice073 +case0005_slice074 +case0005_slice075 +case0005_slice076 +case0005_slice077 +case0005_slice078 +case0005_slice079 +case0005_slice080 +case0005_slice081 +case0005_slice082 +case0005_slice083 +case0005_slice084 +case0005_slice085 +case0005_slice086 +case0005_slice087 +case0005_slice088 +case0005_slice089 +case0005_slice090 +case0005_slice091 +case0005_slice092 +case0005_slice093 +case0005_slice094 +case0005_slice095 +case0005_slice096 +case0005_slice097 +case0005_slice098 +case0005_slice099 +case0005_slice100 +case0005_slice101 +case0005_slice102 +case0005_slice103 +case0005_slice104 +case0005_slice105 +case0005_slice106 +case0005_slice107 +case0005_slice108 +case0005_slice109 +case0005_slice110 +case0005_slice111 +case0005_slice112 +case0005_slice113 +case0005_slice114 +case0005_slice115 +case0005_slice116 +case0026_slice000 +case0026_slice001 +case0026_slice002 +case0026_slice003 +case0026_slice004 +case0026_slice005 +case0026_slice006 +case0026_slice007 +case0026_slice008 +case0026_slice009 +case0026_slice010 +case0026_slice011 +case0026_slice012 +case0026_slice013 +case0026_slice014 +case0026_slice015 +case0026_slice016 +case0026_slice017 +case0026_slice018 +case0026_slice019 +case0026_slice020 +case0026_slice021 +case0026_slice022 +case0026_slice023 +case0026_slice024 +case0026_slice025 +case0026_slice026 +case0026_slice027 +case0026_slice028 +case0026_slice029 +case0026_slice030 +case0026_slice031 +case0026_slice032 +case0026_slice033 +case0026_slice034 +case0026_slice035 +case0026_slice036 +case0026_slice037 +case0026_slice038 +case0026_slice039 +case0026_slice040 +case0026_slice041 +case0026_slice042 +case0026_slice043 +case0026_slice044 +case0026_slice045 +case0026_slice046 +case0026_slice047 +case0026_slice048 +case0026_slice049 +case0026_slice050 +case0026_slice051 +case0026_slice052 +case0026_slice053 +case0026_slice054 +case0026_slice055 +case0026_slice056 +case0026_slice057 +case0026_slice058 +case0026_slice059 +case0026_slice060 +case0026_slice061 +case0026_slice062 +case0026_slice063 +case0026_slice064 +case0026_slice065 +case0026_slice066 +case0026_slice067 +case0026_slice068 +case0026_slice069 +case0026_slice070 +case0026_slice071 +case0026_slice072 +case0026_slice073 +case0026_slice074 +case0026_slice075 +case0026_slice076 +case0026_slice077 +case0026_slice078 +case0026_slice079 +case0026_slice080 +case0026_slice081 +case0026_slice082 +case0026_slice083 +case0026_slice084 +case0026_slice085 +case0026_slice086 +case0026_slice087 +case0026_slice088 +case0026_slice089 +case0026_slice090 +case0026_slice091 +case0026_slice092 +case0026_slice093 +case0026_slice094 +case0026_slice095 +case0026_slice096 +case0026_slice097 +case0026_slice098 +case0026_slice099 +case0026_slice100 +case0026_slice101 +case0026_slice102 +case0026_slice103 +case0026_slice104 +case0026_slice105 +case0026_slice106 +case0026_slice107 +case0026_slice108 +case0026_slice109 +case0026_slice110 +case0026_slice111 +case0026_slice112 +case0026_slice113 +case0026_slice114 +case0026_slice115 +case0026_slice116 +case0026_slice117 +case0026_slice118 +case0026_slice119 +case0026_slice120 +case0026_slice121 +case0026_slice122 +case0026_slice123 +case0026_slice124 +case0026_slice125 +case0026_slice126 +case0026_slice127 +case0026_slice128 +case0026_slice129 +case0026_slice130 +case0039_slice000 +case0039_slice001 +case0039_slice002 +case0039_slice003 +case0039_slice004 +case0039_slice005 +case0039_slice006 +case0039_slice007 +case0039_slice008 +case0039_slice009 +case0039_slice010 +case0039_slice011 +case0039_slice012 +case0039_slice013 +case0039_slice014 +case0039_slice015 +case0039_slice016 +case0039_slice017 +case0039_slice018 +case0039_slice019 +case0039_slice020 +case0039_slice021 +case0039_slice022 +case0039_slice023 +case0039_slice024 +case0039_slice025 +case0039_slice026 +case0039_slice027 +case0039_slice028 +case0039_slice029 +case0039_slice030 +case0039_slice031 +case0039_slice032 +case0039_slice033 +case0039_slice034 +case0039_slice035 +case0039_slice036 +case0039_slice037 +case0039_slice038 +case0039_slice039 +case0039_slice040 +case0039_slice041 +case0039_slice042 +case0039_slice043 +case0039_slice044 +case0039_slice045 +case0039_slice046 +case0039_slice047 +case0039_slice048 +case0039_slice049 +case0039_slice050 +case0039_slice051 +case0039_slice052 +case0039_slice053 +case0039_slice054 +case0039_slice055 +case0039_slice056 +case0039_slice057 +case0039_slice058 +case0039_slice059 +case0039_slice060 +case0039_slice061 +case0039_slice062 +case0039_slice063 +case0039_slice064 +case0039_slice065 +case0039_slice066 +case0039_slice067 +case0039_slice068 +case0039_slice069 +case0039_slice070 +case0039_slice071 +case0039_slice072 +case0039_slice073 +case0039_slice074 +case0039_slice075 +case0039_slice076 +case0039_slice077 +case0039_slice078 +case0039_slice079 +case0039_slice080 +case0039_slice081 +case0039_slice082 +case0039_slice083 +case0039_slice084 +case0039_slice085 +case0039_slice086 +case0039_slice087 +case0039_slice088 +case0039_slice089 +case0024_slice000 +case0024_slice001 +case0024_slice002 +case0024_slice003 +case0024_slice004 +case0024_slice005 +case0024_slice006 +case0024_slice007 +case0024_slice008 +case0024_slice009 +case0024_slice010 +case0024_slice011 +case0024_slice012 +case0024_slice013 +case0024_slice014 +case0024_slice015 +case0024_slice016 +case0024_slice017 +case0024_slice018 +case0024_slice019 +case0024_slice020 +case0024_slice021 +case0024_slice022 +case0024_slice023 +case0024_slice024 +case0024_slice025 +case0024_slice026 +case0024_slice027 +case0024_slice028 +case0024_slice029 +case0024_slice030 +case0024_slice031 +case0024_slice032 +case0024_slice033 +case0024_slice034 +case0024_slice035 +case0024_slice036 +case0024_slice037 +case0024_slice038 +case0024_slice039 +case0024_slice040 +case0024_slice041 +case0024_slice042 +case0024_slice043 +case0024_slice044 +case0024_slice045 +case0024_slice046 +case0024_slice047 +case0024_slice048 +case0024_slice049 +case0024_slice050 +case0024_slice051 +case0024_slice052 +case0024_slice053 +case0024_slice054 +case0024_slice055 +case0024_slice056 +case0024_slice057 +case0024_slice058 +case0024_slice059 +case0024_slice060 +case0024_slice061 +case0024_slice062 +case0024_slice063 +case0024_slice064 +case0024_slice065 +case0024_slice066 +case0024_slice067 +case0024_slice068 +case0024_slice069 +case0024_slice070 +case0024_slice071 +case0024_slice072 +case0024_slice073 +case0024_slice074 +case0024_slice075 +case0024_slice076 +case0024_slice077 +case0024_slice078 +case0024_slice079 +case0024_slice080 +case0024_slice081 +case0024_slice082 +case0024_slice083 +case0024_slice084 +case0024_slice085 +case0024_slice086 +case0024_slice087 +case0024_slice088 +case0024_slice089 +case0024_slice090 +case0024_slice091 +case0024_slice092 +case0024_slice093 +case0024_slice094 +case0024_slice095 +case0024_slice096 +case0024_slice097 +case0024_slice098 +case0024_slice099 +case0024_slice100 +case0024_slice101 +case0024_slice102 +case0024_slice103 +case0024_slice104 +case0024_slice105 +case0024_slice106 +case0024_slice107 +case0024_slice108 +case0024_slice109 +case0024_slice110 +case0024_slice111 +case0024_slice112 +case0024_slice113 +case0024_slice114 +case0024_slice115 +case0024_slice116 +case0024_slice117 +case0024_slice118 +case0024_slice119 +case0024_slice120 +case0024_slice121 +case0024_slice122 +case0024_slice123 +case0034_slice000 +case0034_slice001 +case0034_slice002 +case0034_slice003 +case0034_slice004 +case0034_slice005 +case0034_slice006 +case0034_slice007 +case0034_slice008 +case0034_slice009 +case0034_slice010 +case0034_slice011 +case0034_slice012 +case0034_slice013 +case0034_slice014 +case0034_slice015 +case0034_slice016 +case0034_slice017 +case0034_slice018 +case0034_slice019 +case0034_slice020 +case0034_slice021 +case0034_slice022 +case0034_slice023 +case0034_slice024 +case0034_slice025 +case0034_slice026 +case0034_slice027 +case0034_slice028 +case0034_slice029 +case0034_slice030 +case0034_slice031 +case0034_slice032 +case0034_slice033 +case0034_slice034 +case0034_slice035 +case0034_slice036 +case0034_slice037 +case0034_slice038 +case0034_slice039 +case0034_slice040 +case0034_slice041 +case0034_slice042 +case0034_slice043 +case0034_slice044 +case0034_slice045 +case0034_slice046 +case0034_slice047 +case0034_slice048 +case0034_slice049 +case0034_slice050 +case0034_slice051 +case0034_slice052 +case0034_slice053 +case0034_slice054 +case0034_slice055 +case0034_slice056 +case0034_slice057 +case0034_slice058 +case0034_slice059 +case0034_slice060 +case0034_slice061 +case0034_slice062 +case0034_slice063 +case0034_slice064 +case0034_slice065 +case0034_slice066 +case0034_slice067 +case0034_slice068 +case0034_slice069 +case0034_slice070 +case0034_slice071 +case0034_slice072 +case0034_slice073 +case0034_slice074 +case0034_slice075 +case0034_slice076 +case0034_slice077 +case0034_slice078 +case0034_slice079 +case0034_slice080 +case0034_slice081 +case0034_slice082 +case0034_slice083 +case0034_slice084 +case0034_slice085 +case0034_slice086 +case0034_slice087 +case0034_slice088 +case0034_slice089 +case0034_slice090 +case0034_slice091 +case0034_slice092 +case0034_slice093 +case0034_slice094 +case0034_slice095 +case0034_slice096 +case0034_slice097 +case0033_slice000 +case0033_slice001 +case0033_slice002 +case0033_slice003 +case0033_slice004 +case0033_slice005 +case0033_slice006 +case0033_slice007 +case0033_slice008 +case0033_slice009 +case0033_slice010 +case0033_slice011 +case0033_slice012 +case0033_slice013 +case0033_slice014 +case0033_slice015 +case0033_slice016 +case0033_slice017 +case0033_slice018 +case0033_slice019 +case0033_slice020 +case0033_slice021 +case0033_slice022 +case0033_slice023 +case0033_slice024 +case0033_slice025 +case0033_slice026 +case0033_slice027 +case0033_slice028 +case0033_slice029 +case0033_slice030 +case0033_slice031 +case0033_slice032 +case0033_slice033 +case0033_slice034 +case0033_slice035 +case0033_slice036 +case0033_slice037 +case0033_slice038 +case0033_slice039 +case0033_slice040 +case0033_slice041 +case0033_slice042 +case0033_slice043 +case0033_slice044 +case0033_slice045 +case0033_slice046 +case0033_slice047 +case0033_slice048 +case0033_slice049 +case0033_slice050 +case0033_slice051 +case0033_slice052 +case0033_slice053 +case0033_slice054 +case0033_slice055 +case0033_slice056 +case0033_slice057 +case0033_slice058 +case0033_slice059 +case0033_slice060 +case0033_slice061 +case0033_slice062 +case0033_slice063 +case0033_slice064 +case0033_slice065 +case0033_slice066 +case0033_slice067 +case0033_slice068 +case0033_slice069 +case0033_slice070 +case0033_slice071 +case0033_slice072 +case0033_slice073 +case0033_slice074 +case0033_slice075 +case0033_slice076 +case0033_slice077 +case0033_slice078 +case0033_slice079 +case0033_slice080 +case0033_slice081 +case0033_slice082 +case0033_slice083 +case0033_slice084 +case0033_slice085 +case0033_slice086 +case0033_slice087 +case0033_slice088 +case0033_slice089 +case0033_slice090 +case0033_slice091 +case0033_slice092 +case0033_slice093 +case0033_slice094 +case0033_slice095 +case0033_slice096 +case0033_slice097 +case0033_slice098 +case0033_slice099 +case0033_slice100 +case0033_slice101 +case0033_slice102 +case0033_slice103 +case0030_slice000 +case0030_slice001 +case0030_slice002 +case0030_slice003 +case0030_slice004 +case0030_slice005 +case0030_slice006 +case0030_slice007 +case0030_slice008 +case0030_slice009 +case0030_slice010 +case0030_slice011 +case0030_slice012 +case0030_slice013 +case0030_slice014 +case0030_slice015 +case0030_slice016 +case0030_slice017 +case0030_slice018 +case0030_slice019 +case0030_slice020 +case0030_slice021 +case0030_slice022 +case0030_slice023 +case0030_slice024 +case0030_slice025 +case0030_slice026 +case0030_slice027 +case0030_slice028 +case0030_slice029 +case0030_slice030 +case0030_slice031 +case0030_slice032 +case0030_slice033 +case0030_slice034 +case0030_slice035 +case0030_slice036 +case0030_slice037 +case0030_slice038 +case0030_slice039 +case0030_slice040 +case0030_slice041 +case0030_slice042 +case0030_slice043 +case0030_slice044 +case0030_slice045 +case0030_slice046 +case0030_slice047 +case0030_slice048 +case0030_slice049 +case0030_slice050 +case0030_slice051 +case0030_slice052 +case0030_slice053 +case0030_slice054 +case0030_slice055 +case0030_slice056 +case0030_slice057 +case0030_slice058 +case0030_slice059 +case0030_slice060 +case0030_slice061 +case0030_slice062 +case0030_slice063 +case0030_slice064 +case0030_slice065 +case0030_slice066 +case0030_slice067 +case0030_slice068 +case0030_slice069 +case0030_slice070 +case0030_slice071 +case0030_slice072 +case0030_slice073 +case0030_slice074 +case0030_slice075 +case0030_slice076 +case0030_slice077 +case0030_slice078 +case0030_slice079 +case0030_slice080 +case0030_slice081 +case0030_slice082 +case0030_slice083 +case0030_slice084 +case0030_slice085 +case0030_slice086 +case0030_slice087 +case0030_slice088 +case0030_slice089 +case0030_slice090 +case0030_slice091 +case0030_slice092 +case0030_slice093 +case0030_slice094 +case0030_slice095 +case0030_slice096 +case0030_slice097 +case0030_slice098 +case0030_slice099 +case0030_slice100 +case0030_slice101 +case0030_slice102 +case0030_slice103 +case0030_slice104 +case0030_slice105 +case0030_slice106 +case0030_slice107 +case0030_slice108 +case0030_slice109 +case0030_slice110 +case0030_slice111 +case0030_slice112 +case0030_slice113 +case0030_slice114 +case0030_slice115 +case0030_slice116 +case0030_slice117 +case0030_slice118 +case0030_slice119 +case0030_slice120 +case0030_slice121 +case0030_slice122 +case0030_slice123 +case0030_slice124 +case0030_slice125 +case0030_slice126 +case0030_slice127 +case0030_slice128 +case0030_slice129 +case0030_slice130 +case0030_slice131 +case0030_slice132 +case0030_slice133 +case0030_slice134 +case0030_slice135 +case0030_slice136 +case0030_slice137 +case0030_slice138 +case0030_slice139 +case0030_slice140 +case0030_slice141 +case0030_slice142 +case0030_slice143 +case0030_slice144 +case0030_slice145 +case0030_slice146 +case0030_slice147 +case0030_slice148 +case0030_slice149 +case0030_slice150 +case0030_slice151 +case0030_slice152 +case0023_slice000 +case0023_slice001 +case0023_slice002 +case0023_slice003 +case0023_slice004 +case0023_slice005 +case0023_slice006 +case0023_slice007 +case0023_slice008 +case0023_slice009 +case0023_slice010 +case0023_slice011 +case0023_slice012 +case0023_slice013 +case0023_slice014 +case0023_slice015 +case0023_slice016 +case0023_slice017 +case0023_slice018 +case0023_slice019 +case0023_slice020 +case0023_slice021 +case0023_slice022 +case0023_slice023 +case0023_slice024 +case0023_slice025 +case0023_slice026 +case0023_slice027 +case0023_slice028 +case0023_slice029 +case0023_slice030 +case0023_slice031 +case0023_slice032 +case0023_slice033 +case0023_slice034 +case0023_slice035 +case0023_slice036 +case0023_slice037 +case0023_slice038 +case0023_slice039 +case0023_slice040 +case0023_slice041 +case0023_slice042 +case0023_slice043 +case0023_slice044 +case0023_slice045 +case0023_slice046 +case0023_slice047 +case0023_slice048 +case0023_slice049 +case0023_slice050 +case0023_slice051 +case0023_slice052 +case0023_slice053 +case0023_slice054 +case0023_slice055 +case0023_slice056 +case0023_slice057 +case0023_slice058 +case0023_slice059 +case0023_slice060 +case0023_slice061 +case0023_slice062 +case0023_slice063 +case0023_slice064 +case0023_slice065 +case0023_slice066 +case0023_slice067 +case0023_slice068 +case0023_slice069 +case0023_slice070 +case0023_slice071 +case0023_slice072 +case0023_slice073 +case0023_slice074 +case0023_slice075 +case0023_slice076 +case0023_slice077 +case0023_slice078 +case0023_slice079 +case0023_slice080 +case0023_slice081 +case0023_slice082 +case0023_slice083 +case0023_slice084 +case0023_slice085 +case0023_slice086 +case0023_slice087 +case0023_slice088 +case0023_slice089 +case0023_slice090 +case0023_slice091 +case0023_slice092 +case0023_slice093 +case0023_slice094 +case0023_slice095 +case0040_slice000 +case0040_slice001 +case0040_slice002 +case0040_slice003 +case0040_slice004 +case0040_slice005 +case0040_slice006 +case0040_slice007 +case0040_slice008 +case0040_slice009 +case0040_slice010 +case0040_slice011 +case0040_slice012 +case0040_slice013 +case0040_slice014 +case0040_slice015 +case0040_slice016 +case0040_slice017 +case0040_slice018 +case0040_slice019 +case0040_slice020 +case0040_slice021 +case0040_slice022 +case0040_slice023 +case0040_slice024 +case0040_slice025 +case0040_slice026 +case0040_slice027 +case0040_slice028 +case0040_slice029 +case0040_slice030 +case0040_slice031 +case0040_slice032 +case0040_slice033 +case0040_slice034 +case0040_slice035 +case0040_slice036 +case0040_slice037 +case0040_slice038 +case0040_slice039 +case0040_slice040 +case0040_slice041 +case0040_slice042 +case0040_slice043 +case0040_slice044 +case0040_slice045 +case0040_slice046 +case0040_slice047 +case0040_slice048 +case0040_slice049 +case0040_slice050 +case0040_slice051 +case0040_slice052 +case0040_slice053 +case0040_slice054 +case0040_slice055 +case0040_slice056 +case0040_slice057 +case0040_slice058 +case0040_slice059 +case0040_slice060 +case0040_slice061 +case0040_slice062 +case0040_slice063 +case0040_slice064 +case0040_slice065 +case0040_slice066 +case0040_slice067 +case0040_slice068 +case0040_slice069 +case0040_slice070 +case0040_slice071 +case0040_slice072 +case0040_slice073 +case0040_slice074 +case0040_slice075 +case0040_slice076 +case0040_slice077 +case0040_slice078 +case0040_slice079 +case0040_slice080 +case0040_slice081 +case0040_slice082 +case0040_slice083 +case0040_slice084 +case0040_slice085 +case0040_slice086 +case0040_slice087 +case0040_slice088 +case0040_slice089 +case0040_slice090 +case0040_slice091 +case0040_slice092 +case0040_slice093 +case0040_slice094 +case0040_slice095 +case0040_slice096 +case0040_slice097 +case0040_slice098 +case0040_slice099 +case0040_slice100 +case0040_slice101 +case0040_slice102 +case0040_slice103 +case0040_slice104 +case0040_slice105 +case0040_slice106 +case0040_slice107 +case0040_slice108 +case0040_slice109 +case0040_slice110 +case0040_slice111 +case0040_slice112 +case0040_slice113 +case0040_slice114 +case0040_slice115 +case0040_slice116 +case0040_slice117 +case0040_slice118 +case0040_slice119 +case0040_slice120 +case0040_slice121 +case0040_slice122 +case0040_slice123 +case0040_slice124 +case0040_slice125 +case0040_slice126 +case0040_slice127 +case0040_slice128 +case0040_slice129 +case0040_slice130 +case0040_slice131 +case0040_slice132 +case0040_slice133 +case0040_slice134 +case0040_slice135 +case0040_slice136 +case0040_slice137 +case0040_slice138 +case0040_slice139 +case0040_slice140 +case0040_slice141 +case0040_slice142 +case0040_slice143 +case0040_slice144 +case0040_slice145 +case0040_slice146 +case0040_slice147 +case0040_slice148 +case0040_slice149 +case0040_slice150 +case0040_slice151 +case0040_slice152 +case0040_slice153 +case0040_slice154 +case0040_slice155 +case0040_slice156 +case0040_slice157 +case0040_slice158 +case0040_slice159 +case0040_slice160 +case0040_slice161 +case0040_slice162 +case0040_slice163 +case0040_slice164 +case0040_slice165 +case0040_slice166 +case0040_slice167 +case0040_slice168 +case0040_slice169 +case0040_slice170 +case0040_slice171 +case0040_slice172 +case0040_slice173 +case0040_slice174 +case0040_slice175 +case0040_slice176 +case0040_slice177 +case0040_slice178 +case0040_slice179 +case0040_slice180 +case0040_slice181 +case0040_slice182 +case0040_slice183 +case0040_slice184 +case0040_slice185 +case0040_slice186 +case0040_slice187 +case0040_slice188 +case0040_slice189 +case0040_slice190 +case0040_slice191 +case0040_slice192 +case0040_slice193 +case0040_slice194 +case0010_slice000 +case0010_slice001 +case0010_slice002 +case0010_slice003 +case0010_slice004 +case0010_slice005 +case0010_slice006 +case0010_slice007 +case0010_slice008 +case0010_slice009 +case0010_slice010 +case0010_slice011 +case0010_slice012 +case0010_slice013 +case0010_slice014 +case0010_slice015 +case0010_slice016 +case0010_slice017 +case0010_slice018 +case0010_slice019 +case0010_slice020 +case0010_slice021 +case0010_slice022 +case0010_slice023 +case0010_slice024 +case0010_slice025 +case0010_slice026 +case0010_slice027 +case0010_slice028 +case0010_slice029 +case0010_slice030 +case0010_slice031 +case0010_slice032 +case0010_slice033 +case0010_slice034 +case0010_slice035 +case0010_slice036 +case0010_slice037 +case0010_slice038 +case0010_slice039 +case0010_slice040 +case0010_slice041 +case0010_slice042 +case0010_slice043 +case0010_slice044 +case0010_slice045 +case0010_slice046 +case0010_slice047 +case0010_slice048 +case0010_slice049 +case0010_slice050 +case0010_slice051 +case0010_slice052 +case0010_slice053 +case0010_slice054 +case0010_slice055 +case0010_slice056 +case0010_slice057 +case0010_slice058 +case0010_slice059 +case0010_slice060 +case0010_slice061 +case0010_slice062 +case0010_slice063 +case0010_slice064 +case0010_slice065 +case0010_slice066 +case0010_slice067 +case0010_slice068 +case0010_slice069 +case0010_slice070 +case0010_slice071 +case0010_slice072 +case0010_slice073 +case0010_slice074 +case0010_slice075 +case0010_slice076 +case0010_slice077 +case0010_slice078 +case0010_slice079 +case0010_slice080 +case0010_slice081 +case0010_slice082 +case0010_slice083 +case0010_slice084 +case0010_slice085 +case0010_slice086 +case0010_slice087 +case0010_slice088 +case0010_slice089 +case0010_slice090 +case0010_slice091 +case0010_slice092 +case0010_slice093 +case0010_slice094 +case0010_slice095 +case0010_slice096 +case0010_slice097 +case0010_slice098 +case0010_slice099 +case0010_slice100 +case0010_slice101 +case0010_slice102 +case0010_slice103 +case0010_slice104 +case0010_slice105 +case0010_slice106 +case0010_slice107 +case0010_slice108 +case0010_slice109 +case0010_slice110 +case0010_slice111 +case0010_slice112 +case0010_slice113 +case0010_slice114 +case0010_slice115 +case0010_slice116 +case0010_slice117 +case0010_slice118 +case0010_slice119 +case0010_slice120 +case0010_slice121 +case0010_slice122 +case0010_slice123 +case0010_slice124 +case0010_slice125 +case0010_slice126 +case0010_slice127 +case0010_slice128 +case0010_slice129 +case0010_slice130 +case0010_slice131 +case0010_slice132 +case0010_slice133 +case0010_slice134 +case0010_slice135 +case0010_slice136 +case0010_slice137 +case0010_slice138 +case0010_slice139 +case0010_slice140 +case0010_slice141 +case0010_slice142 +case0010_slice143 +case0010_slice144 +case0010_slice145 +case0010_slice146 +case0010_slice147 +case0021_slice000 +case0021_slice001 +case0021_slice002 +case0021_slice003 +case0021_slice004 +case0021_slice005 +case0021_slice006 +case0021_slice007 +case0021_slice008 +case0021_slice009 +case0021_slice010 +case0021_slice011 +case0021_slice012 +case0021_slice013 +case0021_slice014 +case0021_slice015 +case0021_slice016 +case0021_slice017 +case0021_slice018 +case0021_slice019 +case0021_slice020 +case0021_slice021 +case0021_slice022 +case0021_slice023 +case0021_slice024 +case0021_slice025 +case0021_slice026 +case0021_slice027 +case0021_slice028 +case0021_slice029 +case0021_slice030 +case0021_slice031 +case0021_slice032 +case0021_slice033 +case0021_slice034 +case0021_slice035 +case0021_slice036 +case0021_slice037 +case0021_slice038 +case0021_slice039 +case0021_slice040 +case0021_slice041 +case0021_slice042 +case0021_slice043 +case0021_slice044 +case0021_slice045 +case0021_slice046 +case0021_slice047 +case0021_slice048 +case0021_slice049 +case0021_slice050 +case0021_slice051 +case0021_slice052 +case0021_slice053 +case0021_slice054 +case0021_slice055 +case0021_slice056 +case0021_slice057 +case0021_slice058 +case0021_slice059 +case0021_slice060 +case0021_slice061 +case0021_slice062 +case0021_slice063 +case0021_slice064 +case0021_slice065 +case0021_slice066 +case0021_slice067 +case0021_slice068 +case0021_slice069 +case0021_slice070 +case0021_slice071 +case0021_slice072 +case0021_slice073 +case0021_slice074 +case0021_slice075 +case0021_slice076 +case0021_slice077 +case0021_slice078 +case0021_slice079 +case0021_slice080 +case0021_slice081 +case0021_slice082 +case0021_slice083 +case0021_slice084 +case0021_slice085 +case0021_slice086 +case0021_slice087 +case0021_slice088 +case0021_slice089 +case0021_slice090 +case0021_slice091 +case0021_slice092 +case0021_slice093 +case0021_slice094 +case0021_slice095 +case0021_slice096 +case0021_slice097 +case0021_slice098 +case0021_slice099 +case0021_slice100 +case0021_slice101 +case0021_slice102 +case0021_slice103 +case0021_slice104 +case0021_slice105 +case0021_slice106 +case0021_slice107 +case0021_slice108 +case0021_slice109 +case0021_slice110 +case0021_slice111 +case0021_slice112 +case0021_slice113 +case0021_slice114 +case0021_slice115 +case0021_slice116 +case0021_slice117 +case0021_slice118 +case0021_slice119 +case0021_slice120 +case0021_slice121 +case0021_slice122 +case0021_slice123 +case0021_slice124 +case0021_slice125 +case0021_slice126 +case0021_slice127 +case0021_slice128 +case0021_slice129 +case0021_slice130 +case0021_slice131 +case0021_slice132 +case0021_slice133 +case0021_slice134 +case0021_slice135 +case0021_slice136 +case0021_slice137 +case0021_slice138 +case0021_slice139 +case0021_slice140 +case0021_slice141 +case0021_slice142 +case0006_slice000 +case0006_slice001 +case0006_slice002 +case0006_slice003 +case0006_slice004 +case0006_slice005 +case0006_slice006 +case0006_slice007 +case0006_slice008 +case0006_slice009 +case0006_slice010 +case0006_slice011 +case0006_slice012 +case0006_slice013 +case0006_slice014 +case0006_slice015 +case0006_slice016 +case0006_slice017 +case0006_slice018 +case0006_slice019 +case0006_slice020 +case0006_slice021 +case0006_slice022 +case0006_slice023 +case0006_slice024 +case0006_slice025 +case0006_slice026 +case0006_slice027 +case0006_slice028 +case0006_slice029 +case0006_slice030 +case0006_slice031 +case0006_slice032 +case0006_slice033 +case0006_slice034 +case0006_slice035 +case0006_slice036 +case0006_slice037 +case0006_slice038 +case0006_slice039 +case0006_slice040 +case0006_slice041 +case0006_slice042 +case0006_slice043 +case0006_slice044 +case0006_slice045 +case0006_slice046 +case0006_slice047 +case0006_slice048 +case0006_slice049 +case0006_slice050 +case0006_slice051 +case0006_slice052 +case0006_slice053 +case0006_slice054 +case0006_slice055 +case0006_slice056 +case0006_slice057 +case0006_slice058 +case0006_slice059 +case0006_slice060 +case0006_slice061 +case0006_slice062 +case0006_slice063 +case0006_slice064 +case0006_slice065 +case0006_slice066 +case0006_slice067 +case0006_slice068 +case0006_slice069 +case0006_slice070 +case0006_slice071 +case0006_slice072 +case0006_slice073 +case0006_slice074 +case0006_slice075 +case0006_slice076 +case0006_slice077 +case0006_slice078 +case0006_slice079 +case0006_slice080 +case0006_slice081 +case0006_slice082 +case0006_slice083 +case0006_slice084 +case0006_slice085 +case0006_slice086 +case0006_slice087 +case0006_slice088 +case0006_slice089 +case0006_slice090 +case0006_slice091 +case0006_slice092 +case0006_slice093 +case0006_slice094 +case0006_slice095 +case0006_slice096 +case0006_slice097 +case0006_slice098 +case0006_slice099 +case0006_slice100 +case0006_slice101 +case0006_slice102 +case0006_slice103 +case0006_slice104 +case0006_slice105 +case0006_slice106 +case0006_slice107 +case0006_slice108 +case0006_slice109 +case0006_slice110 +case0006_slice111 +case0006_slice112 +case0006_slice113 +case0006_slice114 +case0006_slice115 +case0006_slice116 +case0006_slice117 +case0006_slice118 +case0006_slice119 +case0006_slice120 +case0006_slice121 +case0006_slice122 +case0006_slice123 +case0006_slice124 +case0006_slice125 +case0006_slice126 +case0006_slice127 +case0006_slice128 +case0006_slice129 +case0006_slice130 +case0027_slice000 +case0027_slice001 +case0027_slice002 +case0027_slice003 +case0027_slice004 +case0027_slice005 +case0027_slice006 +case0027_slice007 +case0027_slice008 +case0027_slice009 +case0027_slice010 +case0027_slice011 +case0027_slice012 +case0027_slice013 +case0027_slice014 +case0027_slice015 +case0027_slice016 +case0027_slice017 +case0027_slice018 +case0027_slice019 +case0027_slice020 +case0027_slice021 +case0027_slice022 +case0027_slice023 +case0027_slice024 +case0027_slice025 +case0027_slice026 +case0027_slice027 +case0027_slice028 +case0027_slice029 +case0027_slice030 +case0027_slice031 +case0027_slice032 +case0027_slice033 +case0027_slice034 +case0027_slice035 +case0027_slice036 +case0027_slice037 +case0027_slice038 +case0027_slice039 +case0027_slice040 +case0027_slice041 +case0027_slice042 +case0027_slice043 +case0027_slice044 +case0027_slice045 +case0027_slice046 +case0027_slice047 +case0027_slice048 +case0027_slice049 +case0027_slice050 +case0027_slice051 +case0027_slice052 +case0027_slice053 +case0027_slice054 +case0027_slice055 +case0027_slice056 +case0027_slice057 +case0027_slice058 +case0027_slice059 +case0027_slice060 +case0027_slice061 +case0027_slice062 +case0027_slice063 +case0027_slice064 +case0027_slice065 +case0027_slice066 +case0027_slice067 +case0027_slice068 +case0027_slice069 +case0027_slice070 +case0027_slice071 +case0027_slice072 +case0027_slice073 +case0027_slice074 +case0027_slice075 +case0027_slice076 +case0027_slice077 +case0027_slice078 +case0027_slice079 +case0027_slice080 +case0027_slice081 +case0027_slice082 +case0027_slice083 +case0027_slice084 +case0027_slice085 +case0027_slice086 +case0027_slice087 +case0028_slice000 +case0028_slice001 +case0028_slice002 +case0028_slice003 +case0028_slice004 +case0028_slice005 +case0028_slice006 +case0028_slice007 +case0028_slice008 +case0028_slice009 +case0028_slice010 +case0028_slice011 +case0028_slice012 +case0028_slice013 +case0028_slice014 +case0028_slice015 +case0028_slice016 +case0028_slice017 +case0028_slice018 +case0028_slice019 +case0028_slice020 +case0028_slice021 +case0028_slice022 +case0028_slice023 +case0028_slice024 +case0028_slice025 +case0028_slice026 +case0028_slice027 +case0028_slice028 +case0028_slice029 +case0028_slice030 +case0028_slice031 +case0028_slice032 +case0028_slice033 +case0028_slice034 +case0028_slice035 +case0028_slice036 +case0028_slice037 +case0028_slice038 +case0028_slice039 +case0028_slice040 +case0028_slice041 +case0028_slice042 +case0028_slice043 +case0028_slice044 +case0028_slice045 +case0028_slice046 +case0028_slice047 +case0028_slice048 +case0028_slice049 +case0028_slice050 +case0028_slice051 +case0028_slice052 +case0028_slice053 +case0028_slice054 +case0028_slice055 +case0028_slice056 +case0028_slice057 +case0028_slice058 +case0028_slice059 +case0028_slice060 +case0028_slice061 +case0028_slice062 +case0028_slice063 +case0028_slice064 +case0028_slice065 +case0028_slice066 +case0028_slice067 +case0028_slice068 +case0028_slice069 +case0028_slice070 +case0028_slice071 +case0028_slice072 +case0028_slice073 +case0028_slice074 +case0028_slice075 +case0028_slice076 +case0028_slice077 +case0028_slice078 +case0028_slice079 +case0028_slice080 +case0028_slice081 +case0028_slice082 +case0028_slice083 +case0028_slice084 +case0028_slice085 +case0028_slice086 +case0028_slice087 +case0028_slice088 +case0037_slice000 +case0037_slice001 +case0037_slice002 +case0037_slice003 +case0037_slice004 +case0037_slice005 +case0037_slice006 +case0037_slice007 +case0037_slice008 +case0037_slice009 +case0037_slice010 +case0037_slice011 +case0037_slice012 +case0037_slice013 +case0037_slice014 +case0037_slice015 +case0037_slice016 +case0037_slice017 +case0037_slice018 +case0037_slice019 +case0037_slice020 +case0037_slice021 +case0037_slice022 +case0037_slice023 +case0037_slice024 +case0037_slice025 +case0037_slice026 +case0037_slice027 +case0037_slice028 +case0037_slice029 +case0037_slice030 +case0037_slice031 +case0037_slice032 +case0037_slice033 +case0037_slice034 +case0037_slice035 +case0037_slice036 +case0037_slice037 +case0037_slice038 +case0037_slice039 +case0037_slice040 +case0037_slice041 +case0037_slice042 +case0037_slice043 +case0037_slice044 +case0037_slice045 +case0037_slice046 +case0037_slice047 +case0037_slice048 +case0037_slice049 +case0037_slice050 +case0037_slice051 +case0037_slice052 +case0037_slice053 +case0037_slice054 +case0037_slice055 +case0037_slice056 +case0037_slice057 +case0037_slice058 +case0037_slice059 +case0037_slice060 +case0037_slice061 +case0037_slice062 +case0037_slice063 +case0037_slice064 +case0037_slice065 +case0037_slice066 +case0037_slice067 +case0037_slice068 +case0037_slice069 +case0037_slice070 +case0037_slice071 +case0037_slice072 +case0037_slice073 +case0037_slice074 +case0037_slice075 +case0037_slice076 +case0037_slice077 +case0037_slice078 +case0037_slice079 +case0037_slice080 +case0037_slice081 +case0037_slice082 +case0037_slice083 +case0037_slice084 +case0037_slice085 +case0037_slice086 +case0037_slice087 +case0037_slice088 +case0037_slice089 +case0037_slice090 +case0037_slice091 +case0037_slice092 +case0037_slice093 +case0037_slice094 +case0037_slice095 +case0037_slice096 +case0037_slice097 +case0037_slice098 diff --git a/code/sota/TransUNet/networks/vit_seg_configs.py b/code/sota/TransUNet/networks/vit_seg_configs.py new file mode 100644 index 0000000000000000000000000000000000000000..1bc4c784cd439720493cb17f333b683ae0494032 --- /dev/null +++ b/code/sota/TransUNet/networks/vit_seg_configs.py @@ -0,0 +1,130 @@ +import ml_collections + +def get_b16_config(): + """Returns the ViT-B/16 configuration.""" + config = ml_collections.ConfigDict() + config.patches = ml_collections.ConfigDict({'size': (16, 16)}) + config.hidden_size = 768 + config.transformer = ml_collections.ConfigDict() + config.transformer.mlp_dim = 3072 + config.transformer.num_heads = 12 + config.transformer.num_layers = 12 + config.transformer.attention_dropout_rate = 0.0 + config.transformer.dropout_rate = 0.1 + + config.classifier = 'seg' + config.representation_size = None + config.resnet_pretrained_path = None + config.pretrained_path = '../model/vit_checkpoint/imagenet21k/ViT-B_16.npz' + config.patch_size = 16 + + config.decoder_channels = (256, 128, 64, 16) + config.n_classes = 2 + config.activation = 'softmax' + return config + + +def get_testing(): + """Returns a minimal configuration for testing.""" + config = ml_collections.ConfigDict() + config.patches = ml_collections.ConfigDict({'size': (16, 16)}) + config.hidden_size = 1 + config.transformer = ml_collections.ConfigDict() + config.transformer.mlp_dim = 1 + config.transformer.num_heads = 1 + config.transformer.num_layers = 1 + config.transformer.attention_dropout_rate = 0.0 + config.transformer.dropout_rate = 0.1 + config.classifier = 'token' + config.representation_size = None + return config + +def get_r50_b16_config(): + """Returns the Resnet50 + ViT-B/16 configuration.""" + config = get_b16_config() + config.patches.grid = (16, 16) + config.resnet = ml_collections.ConfigDict() + config.resnet.num_layers = (3, 4, 9) + config.resnet.width_factor = 1 + + config.classifier = 'seg' + config.pretrained_path = '../model/vit_checkpoint/imagenet21k/R50+ViT-B_16.npz' + config.decoder_channels = (256, 128, 64, 16) + config.skip_channels = [512, 256, 64, 16] + config.n_classes = 2 + config.n_skip = 3 + config.activation = 'softmax' + + return config + + +def get_b32_config(): + """Returns the ViT-B/32 configuration.""" + config = get_b16_config() + config.patches.size = (32, 32) + config.pretrained_path = '../model/vit_checkpoint/imagenet21k/ViT-B_32.npz' + return config + + +def get_l16_config(): + """Returns the ViT-L/16 configuration.""" + config = ml_collections.ConfigDict() + config.patches = ml_collections.ConfigDict({'size': (16, 16)}) + config.hidden_size = 1024 + config.transformer = ml_collections.ConfigDict() + config.transformer.mlp_dim = 4096 + config.transformer.num_heads = 16 + config.transformer.num_layers = 24 + config.transformer.attention_dropout_rate = 0.0 + config.transformer.dropout_rate = 0.1 + config.representation_size = None + + # custom + config.classifier = 'seg' + config.resnet_pretrained_path = None + config.pretrained_path = '../model/vit_checkpoint/imagenet21k/ViT-L_16.npz' + config.decoder_channels = (256, 128, 64, 16) + config.n_classes = 2 + config.activation = 'softmax' + return config + + +def get_r50_l16_config(): + """Returns the Resnet50 + ViT-L/16 configuration. customized """ + config = get_l16_config() + config.patches.grid = (16, 16) + config.resnet = ml_collections.ConfigDict() + config.resnet.num_layers = (3, 4, 9) + config.resnet.width_factor = 1 + + config.classifier = 'seg' + config.resnet_pretrained_path = '../model/vit_checkpoint/imagenet21k/R50+ViT-B_16.npz' + config.decoder_channels = (256, 128, 64, 16) + config.skip_channels = [512, 256, 64, 16] + config.n_classes = 2 + config.activation = 'softmax' + return config + + +def get_l32_config(): + """Returns the ViT-L/32 configuration.""" + config = get_l16_config() + config.patches.size = (32, 32) + return config + + +def get_h14_config(): + """Returns the ViT-L/16 configuration.""" + config = ml_collections.ConfigDict() + config.patches = ml_collections.ConfigDict({'size': (14, 14)}) + config.hidden_size = 1280 + config.transformer = ml_collections.ConfigDict() + config.transformer.mlp_dim = 5120 + config.transformer.num_heads = 16 + config.transformer.num_layers = 32 + config.transformer.attention_dropout_rate = 0.0 + config.transformer.dropout_rate = 0.1 + config.classifier = 'token' + config.representation_size = None + + return config diff --git a/code/sota/TransUNet/networks/vit_seg_modeling.py b/code/sota/TransUNet/networks/vit_seg_modeling.py new file mode 100644 index 0000000000000000000000000000000000000000..8346d9f166fea32cc7007b95b3878687400a7734 --- /dev/null +++ b/code/sota/TransUNet/networks/vit_seg_modeling.py @@ -0,0 +1,453 @@ +# coding=utf-8 +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import copy +import logging +import math + +from os.path import join as pjoin + +import torch +import torch.nn as nn +import numpy as np + +from torch.nn import CrossEntropyLoss, Dropout, Softmax, Linear, Conv2d, LayerNorm +from torch.nn.modules.utils import _pair +from scipy import ndimage +from . import vit_seg_configs as configs +from .vit_seg_modeling_resnet_skip import ResNetV2 + + +logger = logging.getLogger(__name__) + + +ATTENTION_Q = "MultiHeadDotProductAttention_1/query" +ATTENTION_K = "MultiHeadDotProductAttention_1/key" +ATTENTION_V = "MultiHeadDotProductAttention_1/value" +ATTENTION_OUT = "MultiHeadDotProductAttention_1/out" +FC_0 = "MlpBlock_3/Dense_0" +FC_1 = "MlpBlock_3/Dense_1" +ATTENTION_NORM = "LayerNorm_0" +MLP_NORM = "LayerNorm_2" + + +def np2th(weights, conv=False): + """Possibly convert HWIO to OIHW.""" + if conv: + weights = weights.transpose([3, 2, 0, 1]) + return torch.from_numpy(weights) + + +def swish(x): + return x * torch.sigmoid(x) + + +ACT2FN = {"gelu": torch.nn.functional.gelu, "relu": torch.nn.functional.relu, "swish": swish} + + +class Attention(nn.Module): + def __init__(self, config, vis): + super(Attention, self).__init__() + self.vis = vis + self.num_attention_heads = config.transformer["num_heads"] + self.attention_head_size = int(config.hidden_size / self.num_attention_heads) + self.all_head_size = self.num_attention_heads * self.attention_head_size + + self.query = Linear(config.hidden_size, self.all_head_size) + self.key = Linear(config.hidden_size, self.all_head_size) + self.value = Linear(config.hidden_size, self.all_head_size) + + self.out = Linear(config.hidden_size, config.hidden_size) + self.attn_dropout = Dropout(config.transformer["attention_dropout_rate"]) + self.proj_dropout = Dropout(config.transformer["attention_dropout_rate"]) + + self.softmax = Softmax(dim=-1) + + def transpose_for_scores(self, x): + new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size) + x = x.view(*new_x_shape) + return x.permute(0, 2, 1, 3) + + def forward(self, hidden_states): + mixed_query_layer = self.query(hidden_states) + mixed_key_layer = self.key(hidden_states) + mixed_value_layer = self.value(hidden_states) + + query_layer = self.transpose_for_scores(mixed_query_layer) + key_layer = self.transpose_for_scores(mixed_key_layer) + value_layer = self.transpose_for_scores(mixed_value_layer) + + attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2)) + attention_scores = attention_scores / math.sqrt(self.attention_head_size) + attention_probs = self.softmax(attention_scores) + weights = attention_probs if self.vis else None + attention_probs = self.attn_dropout(attention_probs) + + context_layer = torch.matmul(attention_probs, value_layer) + context_layer = context_layer.permute(0, 2, 1, 3).contiguous() + new_context_layer_shape = context_layer.size()[:-2] + (self.all_head_size,) + context_layer = context_layer.view(*new_context_layer_shape) + attention_output = self.out(context_layer) + attention_output = self.proj_dropout(attention_output) + return attention_output, weights + + +class Mlp(nn.Module): + def __init__(self, config): + super(Mlp, self).__init__() + self.fc1 = Linear(config.hidden_size, config.transformer["mlp_dim"]) + self.fc2 = Linear(config.transformer["mlp_dim"], config.hidden_size) + self.act_fn = ACT2FN["gelu"] + self.dropout = Dropout(config.transformer["dropout_rate"]) + + self._init_weights() + + def _init_weights(self): + nn.init.xavier_uniform_(self.fc1.weight) + nn.init.xavier_uniform_(self.fc2.weight) + nn.init.normal_(self.fc1.bias, std=1e-6) + nn.init.normal_(self.fc2.bias, std=1e-6) + + def forward(self, x): + x = self.fc1(x) + x = self.act_fn(x) + x = self.dropout(x) + x = self.fc2(x) + x = self.dropout(x) + return x + + +class Embeddings(nn.Module): + """Construct the embeddings from patch, position embeddings. + """ + def __init__(self, config, img_size, in_channels=3): + super(Embeddings, self).__init__() + self.hybrid = None + self.config = config + img_size = _pair(img_size) + + if config.patches.get("grid") is not None: # ResNet + grid_size = config.patches["grid"] + patch_size = (img_size[0] // 16 // grid_size[0], img_size[1] // 16 // grid_size[1]) + patch_size_real = (patch_size[0] * 16, patch_size[1] * 16) + n_patches = (img_size[0] // patch_size_real[0]) * (img_size[1] // patch_size_real[1]) + self.hybrid = True + else: + patch_size = _pair(config.patches["size"]) + n_patches = (img_size[0] // patch_size[0]) * (img_size[1] // patch_size[1]) + self.hybrid = False + + if self.hybrid: + self.hybrid_model = ResNetV2(block_units=config.resnet.num_layers, width_factor=config.resnet.width_factor) + in_channels = self.hybrid_model.width * 16 + self.patch_embeddings = Conv2d(in_channels=in_channels, + out_channels=config.hidden_size, + kernel_size=patch_size, + stride=patch_size) + self.position_embeddings = nn.Parameter(torch.zeros(1, n_patches, config.hidden_size)) + + self.dropout = Dropout(config.transformer["dropout_rate"]) + + + def forward(self, x): + if self.hybrid: + x, features = self.hybrid_model(x) + else: + features = None + x = self.patch_embeddings(x) # (B, hidden. n_patches^(1/2), n_patches^(1/2)) + x = x.flatten(2) + x = x.transpose(-1, -2) # (B, n_patches, hidden) + + embeddings = x + self.position_embeddings + embeddings = self.dropout(embeddings) + return embeddings, features + + +class Block(nn.Module): + def __init__(self, config, vis): + super(Block, self).__init__() + self.hidden_size = config.hidden_size + self.attention_norm = LayerNorm(config.hidden_size, eps=1e-6) + self.ffn_norm = LayerNorm(config.hidden_size, eps=1e-6) + self.ffn = Mlp(config) + self.attn = Attention(config, vis) + + def forward(self, x): + h = x + x = self.attention_norm(x) + x, weights = self.attn(x) + x = x + h + + h = x + x = self.ffn_norm(x) + x = self.ffn(x) + x = x + h + return x, weights + + def load_from(self, weights, n_block): + ROOT = f"Transformer/encoderblock_{n_block}" + with torch.no_grad(): + query_weight = np2th(weights[pjoin(ROOT, ATTENTION_Q, "kernel")]).view(self.hidden_size, self.hidden_size).t() + key_weight = np2th(weights[pjoin(ROOT, ATTENTION_K, "kernel")]).view(self.hidden_size, self.hidden_size).t() + value_weight = np2th(weights[pjoin(ROOT, ATTENTION_V, "kernel")]).view(self.hidden_size, self.hidden_size).t() + out_weight = np2th(weights[pjoin(ROOT, ATTENTION_OUT, "kernel")]).view(self.hidden_size, self.hidden_size).t() + + query_bias = np2th(weights[pjoin(ROOT, ATTENTION_Q, "bias")]).view(-1) + key_bias = np2th(weights[pjoin(ROOT, ATTENTION_K, "bias")]).view(-1) + value_bias = np2th(weights[pjoin(ROOT, ATTENTION_V, "bias")]).view(-1) + out_bias = np2th(weights[pjoin(ROOT, ATTENTION_OUT, "bias")]).view(-1) + + self.attn.query.weight.copy_(query_weight) + self.attn.key.weight.copy_(key_weight) + self.attn.value.weight.copy_(value_weight) + self.attn.out.weight.copy_(out_weight) + self.attn.query.bias.copy_(query_bias) + self.attn.key.bias.copy_(key_bias) + self.attn.value.bias.copy_(value_bias) + self.attn.out.bias.copy_(out_bias) + + mlp_weight_0 = np2th(weights[pjoin(ROOT, FC_0, "kernel")]).t() + mlp_weight_1 = np2th(weights[pjoin(ROOT, FC_1, "kernel")]).t() + mlp_bias_0 = np2th(weights[pjoin(ROOT, FC_0, "bias")]).t() + mlp_bias_1 = np2th(weights[pjoin(ROOT, FC_1, "bias")]).t() + + self.ffn.fc1.weight.copy_(mlp_weight_0) + self.ffn.fc2.weight.copy_(mlp_weight_1) + self.ffn.fc1.bias.copy_(mlp_bias_0) + self.ffn.fc2.bias.copy_(mlp_bias_1) + + self.attention_norm.weight.copy_(np2th(weights[pjoin(ROOT, ATTENTION_NORM, "scale")])) + self.attention_norm.bias.copy_(np2th(weights[pjoin(ROOT, ATTENTION_NORM, "bias")])) + self.ffn_norm.weight.copy_(np2th(weights[pjoin(ROOT, MLP_NORM, "scale")])) + self.ffn_norm.bias.copy_(np2th(weights[pjoin(ROOT, MLP_NORM, "bias")])) + + +class Encoder(nn.Module): + def __init__(self, config, vis): + super(Encoder, self).__init__() + self.vis = vis + self.layer = nn.ModuleList() + self.encoder_norm = LayerNorm(config.hidden_size, eps=1e-6) + for _ in range(config.transformer["num_layers"]): + layer = Block(config, vis) + self.layer.append(copy.deepcopy(layer)) + + def forward(self, hidden_states): + attn_weights = [] + for layer_block in self.layer: + hidden_states, weights = layer_block(hidden_states) + if self.vis: + attn_weights.append(weights) + encoded = self.encoder_norm(hidden_states) + return encoded, attn_weights + + +class Transformer(nn.Module): + def __init__(self, config, img_size, vis): + super(Transformer, self).__init__() + self.embeddings = Embeddings(config, img_size=img_size) + self.encoder = Encoder(config, vis) + + def forward(self, input_ids): + embedding_output, features = self.embeddings(input_ids) + encoded, attn_weights = self.encoder(embedding_output) # (B, n_patch, hidden) + return encoded, attn_weights, features + + +class Conv2dReLU(nn.Sequential): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + padding=0, + stride=1, + use_batchnorm=True, + ): + conv = nn.Conv2d( + in_channels, + out_channels, + kernel_size, + stride=stride, + padding=padding, + bias=not (use_batchnorm), + ) + relu = nn.ReLU(inplace=True) + + bn = nn.BatchNorm2d(out_channels) + + super(Conv2dReLU, self).__init__(conv, bn, relu) + + +class DecoderBlock(nn.Module): + def __init__( + self, + in_channels, + out_channels, + skip_channels=0, + use_batchnorm=True, + ): + super().__init__() + self.conv1 = Conv2dReLU( + in_channels + skip_channels, + out_channels, + kernel_size=3, + padding=1, + use_batchnorm=use_batchnorm, + ) + self.conv2 = Conv2dReLU( + out_channels, + out_channels, + kernel_size=3, + padding=1, + use_batchnorm=use_batchnorm, + ) + self.up = nn.UpsamplingBilinear2d(scale_factor=2) + + def forward(self, x, skip=None): + x = self.up(x) + if skip is not None: + x = torch.cat([x, skip], dim=1) + x = self.conv1(x) + x = self.conv2(x) + return x + + +class SegmentationHead(nn.Sequential): + + def __init__(self, in_channels, out_channels, kernel_size=3, upsampling=1): + conv2d = nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, padding=kernel_size // 2) + upsampling = nn.UpsamplingBilinear2d(scale_factor=upsampling) if upsampling > 1 else nn.Identity() + super().__init__(conv2d, upsampling) + + +class DecoderCup(nn.Module): + def __init__(self, config): + super().__init__() + self.config = config + head_channels = 512 + self.conv_more = Conv2dReLU( + config.hidden_size, + head_channels, + kernel_size=3, + padding=1, + use_batchnorm=True, + ) + decoder_channels = config.decoder_channels + in_channels = [head_channels] + list(decoder_channels[:-1]) + out_channels = decoder_channels + + if self.config.n_skip != 0: + skip_channels = self.config.skip_channels + for i in range(4-self.config.n_skip): # re-select the skip channels according to n_skip + skip_channels[3-i]=0 + + else: + skip_channels=[0,0,0,0] + + blocks = [ + DecoderBlock(in_ch, out_ch, sk_ch) for in_ch, out_ch, sk_ch in zip(in_channels, out_channels, skip_channels) + ] + self.blocks = nn.ModuleList(blocks) + + def forward(self, hidden_states, features=None): + B, n_patch, hidden = hidden_states.size() # reshape from (B, n_patch, hidden) to (B, h, w, hidden) + h, w = int(np.sqrt(n_patch)), int(np.sqrt(n_patch)) + x = hidden_states.permute(0, 2, 1) + x = x.contiguous().view(B, hidden, h, w) + x = self.conv_more(x) + for i, decoder_block in enumerate(self.blocks): + if features is not None: + skip = features[i] if (i < self.config.n_skip) else None + else: + skip = None + x = decoder_block(x, skip=skip) + return x + + +class VisionTransformer(nn.Module): + def __init__(self, config, img_size=224, num_classes=21843, zero_head=False, vis=False): + super(VisionTransformer, self).__init__() + self.num_classes = num_classes + self.zero_head = zero_head + self.classifier = config.classifier + self.transformer = Transformer(config, img_size, vis) + self.decoder = DecoderCup(config) + self.segmentation_head = SegmentationHead( + in_channels=config['decoder_channels'][-1], + out_channels=config['n_classes'], + kernel_size=3, + ) + self.config = config + + def forward(self, x): + if x.size()[1] == 1: + x = x.repeat(1,3,1,1) + x, attn_weights, features = self.transformer(x) # (B, n_patch, hidden) + x = self.decoder(x, features) + logits = self.segmentation_head(x) + return logits + + def load_from(self, weights): + with torch.no_grad(): + + res_weight = weights + self.transformer.embeddings.patch_embeddings.weight.copy_(np2th(weights["embedding/kernel"], conv=True)) + self.transformer.embeddings.patch_embeddings.bias.copy_(np2th(weights["embedding/bias"])) + + self.transformer.encoder.encoder_norm.weight.copy_(np2th(weights["Transformer/encoder_norm/scale"])) + self.transformer.encoder.encoder_norm.bias.copy_(np2th(weights["Transformer/encoder_norm/bias"])) + + posemb = np2th(weights["Transformer/posembed_input/pos_embedding"]) + + posemb_new = self.transformer.embeddings.position_embeddings + if posemb.size() == posemb_new.size(): + self.transformer.embeddings.position_embeddings.copy_(posemb) + elif posemb.size()[1]-1 == posemb_new.size()[1]: + posemb = posemb[:, 1:] + self.transformer.embeddings.position_embeddings.copy_(posemb) + else: + logger.info("load_pretrained: resized variant: %s to %s" % (posemb.size(), posemb_new.size())) + ntok_new = posemb_new.size(1) + if self.classifier == "seg": + _, posemb_grid = posemb[:, :1], posemb[0, 1:] + gs_old = int(np.sqrt(len(posemb_grid))) + gs_new = int(np.sqrt(ntok_new)) + print('load_pretrained: grid-size from %s to %s' % (gs_old, gs_new)) + posemb_grid = posemb_grid.reshape(gs_old, gs_old, -1) + zoom = (gs_new / gs_old, gs_new / gs_old, 1) + posemb_grid = ndimage.zoom(posemb_grid, zoom, order=1) # th2np + posemb_grid = posemb_grid.reshape(1, gs_new * gs_new, -1) + posemb = posemb_grid + self.transformer.embeddings.position_embeddings.copy_(np2th(posemb)) + + # Encoder whole + for bname, block in self.transformer.encoder.named_children(): + for uname, unit in block.named_children(): + unit.load_from(weights, n_block=uname) + + if self.transformer.embeddings.hybrid: + self.transformer.embeddings.hybrid_model.root.conv.weight.copy_(np2th(res_weight["conv_root/kernel"], conv=True)) + gn_weight = np2th(res_weight["gn_root/scale"]).view(-1) + gn_bias = np2th(res_weight["gn_root/bias"]).view(-1) + self.transformer.embeddings.hybrid_model.root.gn.weight.copy_(gn_weight) + self.transformer.embeddings.hybrid_model.root.gn.bias.copy_(gn_bias) + + for bname, block in self.transformer.embeddings.hybrid_model.body.named_children(): + for uname, unit in block.named_children(): + unit.load_from(res_weight, n_block=bname, n_unit=uname) + +CONFIGS = { + 'ViT-B_16': configs.get_b16_config(), + 'ViT-B_32': configs.get_b32_config(), + 'ViT-L_16': configs.get_l16_config(), + 'ViT-L_32': configs.get_l32_config(), + 'ViT-H_14': configs.get_h14_config(), + 'R50-ViT-B_16': configs.get_r50_b16_config(), + 'R50-ViT-L_16': configs.get_r50_l16_config(), + 'testing': configs.get_testing(), +} + + diff --git a/code/sota/TransUNet/networks/vit_seg_modeling_resnet_skip.py b/code/sota/TransUNet/networks/vit_seg_modeling_resnet_skip.py new file mode 100644 index 0000000000000000000000000000000000000000..9753d52fbe8275e77cc18870c1e9f9564d8cc008 --- /dev/null +++ b/code/sota/TransUNet/networks/vit_seg_modeling_resnet_skip.py @@ -0,0 +1,160 @@ +import math + +from os.path import join as pjoin +from collections import OrderedDict + +import torch +import torch.nn as nn +import torch.nn.functional as F + + +def np2th(weights, conv=False): + """Possibly convert HWIO to OIHW.""" + if conv: + weights = weights.transpose([3, 2, 0, 1]) + return torch.from_numpy(weights) + + +class StdConv2d(nn.Conv2d): + + def forward(self, x): + w = self.weight + v, m = torch.var_mean(w, dim=[1, 2, 3], keepdim=True, unbiased=False) + w = (w - m) / torch.sqrt(v + 1e-5) + return F.conv2d(x, w, self.bias, self.stride, self.padding, + self.dilation, self.groups) + + +def conv3x3(cin, cout, stride=1, groups=1, bias=False): + return StdConv2d(cin, cout, kernel_size=3, stride=stride, + padding=1, bias=bias, groups=groups) + + +def conv1x1(cin, cout, stride=1, bias=False): + return StdConv2d(cin, cout, kernel_size=1, stride=stride, + padding=0, bias=bias) + + +class PreActBottleneck(nn.Module): + """Pre-activation (v2) bottleneck block. + """ + + def __init__(self, cin, cout=None, cmid=None, stride=1): + super().__init__() + cout = cout or cin + cmid = cmid or cout//4 + + self.gn1 = nn.GroupNorm(32, cmid, eps=1e-6) + self.conv1 = conv1x1(cin, cmid, bias=False) + self.gn2 = nn.GroupNorm(32, cmid, eps=1e-6) + self.conv2 = conv3x3(cmid, cmid, stride, bias=False) # Original code has it on conv1!! + self.gn3 = nn.GroupNorm(32, cout, eps=1e-6) + self.conv3 = conv1x1(cmid, cout, bias=False) + self.relu = nn.ReLU(inplace=True) + + if (stride != 1 or cin != cout): + # Projection also with pre-activation according to paper. + self.downsample = conv1x1(cin, cout, stride, bias=False) + self.gn_proj = nn.GroupNorm(cout, cout) + + def forward(self, x): + + # Residual branch + residual = x + if hasattr(self, 'downsample'): + residual = self.downsample(x) + residual = self.gn_proj(residual) + + # Unit's branch + y = self.relu(self.gn1(self.conv1(x))) + y = self.relu(self.gn2(self.conv2(y))) + y = self.gn3(self.conv3(y)) + + y = self.relu(residual + y) + return y + + def load_from(self, weights, n_block, n_unit): + conv1_weight = np2th(weights[pjoin(n_block, n_unit, "conv1/kernel")], conv=True) + conv2_weight = np2th(weights[pjoin(n_block, n_unit, "conv2/kernel")], conv=True) + conv3_weight = np2th(weights[pjoin(n_block, n_unit, "conv3/kernel")], conv=True) + + gn1_weight = np2th(weights[pjoin(n_block, n_unit, "gn1/scale")]) + gn1_bias = np2th(weights[pjoin(n_block, n_unit, "gn1/bias")]) + + gn2_weight = np2th(weights[pjoin(n_block, n_unit, "gn2/scale")]) + gn2_bias = np2th(weights[pjoin(n_block, n_unit, "gn2/bias")]) + + gn3_weight = np2th(weights[pjoin(n_block, n_unit, "gn3/scale")]) + gn3_bias = np2th(weights[pjoin(n_block, n_unit, "gn3/bias")]) + + self.conv1.weight.copy_(conv1_weight) + self.conv2.weight.copy_(conv2_weight) + self.conv3.weight.copy_(conv3_weight) + + self.gn1.weight.copy_(gn1_weight.view(-1)) + self.gn1.bias.copy_(gn1_bias.view(-1)) + + self.gn2.weight.copy_(gn2_weight.view(-1)) + self.gn2.bias.copy_(gn2_bias.view(-1)) + + self.gn3.weight.copy_(gn3_weight.view(-1)) + self.gn3.bias.copy_(gn3_bias.view(-1)) + + if hasattr(self, 'downsample'): + proj_conv_weight = np2th(weights[pjoin(n_block, n_unit, "conv_proj/kernel")], conv=True) + proj_gn_weight = np2th(weights[pjoin(n_block, n_unit, "gn_proj/scale")]) + proj_gn_bias = np2th(weights[pjoin(n_block, n_unit, "gn_proj/bias")]) + + self.downsample.weight.copy_(proj_conv_weight) + self.gn_proj.weight.copy_(proj_gn_weight.view(-1)) + self.gn_proj.bias.copy_(proj_gn_bias.view(-1)) + +class ResNetV2(nn.Module): + """Implementation of Pre-activation (v2) ResNet mode.""" + + def __init__(self, block_units, width_factor): + super().__init__() + width = int(64 * width_factor) + self.width = width + + self.root = nn.Sequential(OrderedDict([ + ('conv', StdConv2d(3, width, kernel_size=7, stride=2, bias=False, padding=3)), + ('gn', nn.GroupNorm(32, width, eps=1e-6)), + ('relu', nn.ReLU(inplace=True)), + # ('pool', nn.MaxPool2d(kernel_size=3, stride=2, padding=0)) + ])) + + self.body = nn.Sequential(OrderedDict([ + ('block1', nn.Sequential(OrderedDict( + [('unit1', PreActBottleneck(cin=width, cout=width*4, cmid=width))] + + [(f'unit{i:d}', PreActBottleneck(cin=width*4, cout=width*4, cmid=width)) for i in range(2, block_units[0] + 1)], + ))), + ('block2', nn.Sequential(OrderedDict( + [('unit1', PreActBottleneck(cin=width*4, cout=width*8, cmid=width*2, stride=2))] + + [(f'unit{i:d}', PreActBottleneck(cin=width*8, cout=width*8, cmid=width*2)) for i in range(2, block_units[1] + 1)], + ))), + ('block3', nn.Sequential(OrderedDict( + [('unit1', PreActBottleneck(cin=width*8, cout=width*16, cmid=width*4, stride=2))] + + [(f'unit{i:d}', PreActBottleneck(cin=width*16, cout=width*16, cmid=width*4)) for i in range(2, block_units[2] + 1)], + ))), + ])) + + def forward(self, x): + features = [] + b, c, in_size, _ = x.size() + x = self.root(x) + features.append(x) + x = nn.MaxPool2d(kernel_size=3, stride=2, padding=0)(x) + for i in range(len(self.body)-1): + x = self.body[i](x) + right_size = int(in_size / 4 / (i+1)) + if x.size()[2] != right_size: + pad = right_size - x.size()[2] + assert pad < 3 and pad > 0, "x {} should {}".format(x.size(), right_size) + feat = torch.zeros((b, x.size()[1], right_size, right_size), device=x.device) + feat[:, :, 0:x.size()[2], 0:x.size()[3]] = x[:] + else: + feat = x + features.append(feat) + x = self.body[-1](x) + return x, features[::-1] diff --git a/code/sota/TransUNet/requirements.txt b/code/sota/TransUNet/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..4abfe422e0bd10ed594596292121fb6eac4d4581 --- /dev/null +++ b/code/sota/TransUNet/requirements.txt @@ -0,0 +1,11 @@ +torch==1.4.0 +torchvision==0.5.0 +numpy +tqdm +tensorboard +tensorboardX +ml-collections +medpy +SimpleITK +scipy +h5py diff --git a/code/sota/TransUNet/test.py b/code/sota/TransUNet/test.py new file mode 100644 index 0000000000000000000000000000000000000000..35a48027e952822f29b7f439f85007ee81d9b92e --- /dev/null +++ b/code/sota/TransUNet/test.py @@ -0,0 +1,140 @@ +import argparse +import logging +import os +import random +import sys +import numpy as np +import torch +import torch.backends.cudnn as cudnn +import torch.nn as nn +from torch.utils.data import DataLoader +from tqdm import tqdm +from datasets.dataset_synapse import Synapse_dataset +from utils import test_single_volume +from networks.vit_seg_modeling import VisionTransformer as ViT_seg +from networks.vit_seg_modeling import CONFIGS as CONFIGS_ViT_seg + +parser = argparse.ArgumentParser() +parser.add_argument('--volume_path', type=str, + default='../data/Synapse/test_vol_h5', help='root dir for validation volume data') # for acdc volume_path=root_dir +parser.add_argument('--dataset', type=str, + default='Synapse', help='experiment_name') +parser.add_argument('--num_classes', type=int, + default=4, help='output channel of network') +parser.add_argument('--list_dir', type=str, + default='./lists/lists_Synapse', help='list dir') + +parser.add_argument('--max_iterations', type=int,default=20000, help='maximum epoch number to train') +parser.add_argument('--max_epochs', type=int, default=30, help='maximum epoch number to train') +parser.add_argument('--batch_size', type=int, default=24, + help='batch_size per gpu') +parser.add_argument('--img_size', type=int, default=224, help='input patch size of network input') +parser.add_argument('--is_savenii', action="store_true", help='whether to save results during inference') + +parser.add_argument('--n_skip', type=int, default=3, help='using number of skip-connect, default is num') +parser.add_argument('--vit_name', type=str, default='ViT-B_16', help='select one vit model') + +parser.add_argument('--test_save_dir', type=str, default='../predictions', help='saving prediction as nii!') +parser.add_argument('--deterministic', type=int, default=1, help='whether use deterministic training') +parser.add_argument('--base_lr', type=float, default=0.01, help='segmentation network learning rate') +parser.add_argument('--seed', type=int, default=1234, help='random seed') +parser.add_argument('--vit_patches_size', type=int, default=16, help='vit_patches_size, default is 16') +args = parser.parse_args() + + +def inference(args, model, test_save_path=None): + db_test = args.Dataset(base_dir=args.volume_path, split="test_vol", list_dir=args.list_dir) + testloader = DataLoader(db_test, batch_size=1, shuffle=False, num_workers=1) + logging.info("{} test iterations per epoch".format(len(testloader))) + model.eval() + metric_list = 0.0 + for i_batch, sampled_batch in tqdm(enumerate(testloader)): + h, w = sampled_batch["image"].size()[2:] + image, label, case_name = sampled_batch["image"], sampled_batch["label"], sampled_batch['case_name'][0] + metric_i = test_single_volume(image, label, model, classes=args.num_classes, patch_size=[args.img_size, args.img_size], + test_save_path=test_save_path, case=case_name, z_spacing=args.z_spacing) + metric_list += np.array(metric_i) + logging.info('idx %d case %s mean_dice %f mean_hd95 %f' % (i_batch, case_name, np.mean(metric_i, axis=0)[0], np.mean(metric_i, axis=0)[1])) + metric_list = metric_list / len(db_test) + for i in range(1, args.num_classes): + logging.info('Mean class %d mean_dice %f mean_hd95 %f' % (i, metric_list[i-1][0], metric_list[i-1][1])) + performance = np.mean(metric_list, axis=0)[0] + mean_hd95 = np.mean(metric_list, axis=0)[1] + logging.info('Testing performance in best val model: mean_dice : %f mean_hd95 : %f' % (performance, mean_hd95)) + return "Testing Finished!" + + +if __name__ == "__main__": + + if not args.deterministic: + cudnn.benchmark = True + cudnn.deterministic = False + else: + cudnn.benchmark = False + cudnn.deterministic = True + random.seed(args.seed) + np.random.seed(args.seed) + torch.manual_seed(args.seed) + torch.cuda.manual_seed(args.seed) + + dataset_config = { + 'Synapse': { + 'Dataset': Synapse_dataset, + 'volume_path': '../data/Synapse/test_vol_h5', + 'list_dir': './lists/lists_Synapse', + 'num_classes': 9, + 'z_spacing': 1, + }, + } + dataset_name = args.dataset + args.num_classes = dataset_config[dataset_name]['num_classes'] + args.volume_path = dataset_config[dataset_name]['volume_path'] + args.Dataset = dataset_config[dataset_name]['Dataset'] + args.list_dir = dataset_config[dataset_name]['list_dir'] + args.z_spacing = dataset_config[dataset_name]['z_spacing'] + args.is_pretrain = True + + # name the same snapshot defined in train script! + args.exp = 'TU_' + dataset_name + str(args.img_size) + snapshot_path = "../model/{}/{}".format(args.exp, 'TU') + snapshot_path = snapshot_path + '_pretrain' if args.is_pretrain else snapshot_path + snapshot_path += '_' + args.vit_name + snapshot_path = snapshot_path + '_skip' + str(args.n_skip) + snapshot_path = snapshot_path + '_vitpatch' + str(args.vit_patches_size) if args.vit_patches_size!=16 else snapshot_path + snapshot_path = snapshot_path + '_epo' + str(args.max_epochs) if args.max_epochs != 30 else snapshot_path + if dataset_name == 'ACDC': # using max_epoch instead of iteration to control training duration + snapshot_path = snapshot_path + '_' + str(args.max_iterations)[0:2] + 'k' if args.max_iterations != 30000 else snapshot_path + snapshot_path = snapshot_path+'_bs'+str(args.batch_size) + snapshot_path = snapshot_path + '_lr' + str(args.base_lr) if args.base_lr != 0.01 else snapshot_path + snapshot_path = snapshot_path + '_'+str(args.img_size) + snapshot_path = snapshot_path + '_s'+str(args.seed) if args.seed!=1234 else snapshot_path + + config_vit = CONFIGS_ViT_seg[args.vit_name] + config_vit.n_classes = args.num_classes + config_vit.n_skip = args.n_skip + config_vit.patches.size = (args.vit_patches_size, args.vit_patches_size) + if args.vit_name.find('R50') !=-1: + config_vit.patches.grid = (int(args.img_size/args.vit_patches_size), int(args.img_size/args.vit_patches_size)) + net = ViT_seg(config_vit, img_size=args.img_size, num_classes=config_vit.n_classes).cuda() + + snapshot = os.path.join(snapshot_path, 'best_model.pth') + if not os.path.exists(snapshot): snapshot = snapshot.replace('best_model', 'epoch_'+str(args.max_epochs-1)) + net.load_state_dict(torch.load(snapshot)) + snapshot_name = snapshot_path.split('/')[-1] + + log_folder = './test_log/test_log_' + args.exp + os.makedirs(log_folder, exist_ok=True) + logging.basicConfig(filename=log_folder + '/'+snapshot_name+".txt", level=logging.INFO, format='[%(asctime)s.%(msecs)03d] %(message)s', datefmt='%H:%M:%S') + logging.getLogger().addHandler(logging.StreamHandler(sys.stdout)) + logging.info(str(args)) + logging.info(snapshot_name) + + if args.is_savenii: + args.test_save_dir = '../predictions' + test_save_path = os.path.join(args.test_save_dir, args.exp, snapshot_name) + os.makedirs(test_save_path, exist_ok=True) + else: + test_save_path = None + inference(args, net, test_save_path) + + diff --git a/code/sota/TransUNet/train.py b/code/sota/TransUNet/train.py new file mode 100644 index 0000000000000000000000000000000000000000..438dc76b9d2a00f2abe4aacca9d7279dcad4685d --- /dev/null +++ b/code/sota/TransUNet/train.py @@ -0,0 +1,93 @@ +import argparse +import logging +import os +import random +import numpy as np +import torch +import torch.backends.cudnn as cudnn +from networks.vit_seg_modeling import VisionTransformer as ViT_seg +from networks.vit_seg_modeling import CONFIGS as CONFIGS_ViT_seg +from trainer import trainer_synapse + +parser = argparse.ArgumentParser() +parser.add_argument('--root_path', type=str, + default='../data/Synapse/train_npz', help='root dir for data') +parser.add_argument('--dataset', type=str, + default='Synapse', help='experiment_name') +parser.add_argument('--list_dir', type=str, + default='./lists/lists_Synapse', help='list dir') +parser.add_argument('--num_classes', type=int, + default=9, help='output channel of network') +parser.add_argument('--max_iterations', type=int, + default=30000, help='maximum epoch number to train') +parser.add_argument('--max_epochs', type=int, + default=150, help='maximum epoch number to train') +parser.add_argument('--batch_size', type=int, + default=24, help='batch_size per gpu') +parser.add_argument('--n_gpu', type=int, default=1, help='total gpu') +parser.add_argument('--deterministic', type=int, default=1, + help='whether use deterministic training') +parser.add_argument('--base_lr', type=float, default=0.01, + help='segmentation network learning rate') +parser.add_argument('--img_size', type=int, + default=224, help='input patch size of network input') +parser.add_argument('--seed', type=int, + default=1234, help='random seed') +parser.add_argument('--n_skip', type=int, + default=3, help='using number of skip-connect, default is num') +parser.add_argument('--vit_name', type=str, + default='R50-ViT-B_16', help='select one vit model') +parser.add_argument('--vit_patches_size', type=int, + default=16, help='vit_patches_size, default is 16') +args = parser.parse_args() + + +if __name__ == "__main__": + if not args.deterministic: + cudnn.benchmark = True + cudnn.deterministic = False + else: + cudnn.benchmark = False + cudnn.deterministic = True + + random.seed(args.seed) + np.random.seed(args.seed) + torch.manual_seed(args.seed) + torch.cuda.manual_seed(args.seed) + dataset_name = args.dataset + dataset_config = { + 'Synapse': { + 'root_path': '../data/Synapse/train_npz', + 'list_dir': './lists/lists_Synapse', + 'num_classes': 9, + }, + } + args.num_classes = dataset_config[dataset_name]['num_classes'] + args.root_path = dataset_config[dataset_name]['root_path'] + args.list_dir = dataset_config[dataset_name]['list_dir'] + args.is_pretrain = True + args.exp = 'TU_' + dataset_name + str(args.img_size) + snapshot_path = "../model/{}/{}".format(args.exp, 'TU') + snapshot_path = snapshot_path + '_pretrain' if args.is_pretrain else snapshot_path + snapshot_path += '_' + args.vit_name + snapshot_path = snapshot_path + '_skip' + str(args.n_skip) + snapshot_path = snapshot_path + '_vitpatch' + str(args.vit_patches_size) if args.vit_patches_size!=16 else snapshot_path + snapshot_path = snapshot_path+'_'+str(args.max_iterations)[0:2]+'k' if args.max_iterations != 30000 else snapshot_path + snapshot_path = snapshot_path + '_epo' +str(args.max_epochs) if args.max_epochs != 30 else snapshot_path + snapshot_path = snapshot_path+'_bs'+str(args.batch_size) + snapshot_path = snapshot_path + '_lr' + str(args.base_lr) if args.base_lr != 0.01 else snapshot_path + snapshot_path = snapshot_path + '_'+str(args.img_size) + snapshot_path = snapshot_path + '_s'+str(args.seed) if args.seed!=1234 else snapshot_path + + if not os.path.exists(snapshot_path): + os.makedirs(snapshot_path) + config_vit = CONFIGS_ViT_seg[args.vit_name] + config_vit.n_classes = args.num_classes + config_vit.n_skip = args.n_skip + if args.vit_name.find('R50') != -1: + config_vit.patches.grid = (int(args.img_size / args.vit_patches_size), int(args.img_size / args.vit_patches_size)) + net = ViT_seg(config_vit, img_size=args.img_size, num_classes=config_vit.n_classes).cuda() + net.load_from(weights=np.load(config_vit.pretrained_path)) + + trainer = {'Synapse': trainer_synapse,} + trainer[dataset_name](args, net, snapshot_path) \ No newline at end of file diff --git a/code/sota/TransUNet/trainer.py b/code/sota/TransUNet/trainer.py new file mode 100644 index 0000000000000000000000000000000000000000..2445e10ce2ef85789041532ec47d6f9674016070 --- /dev/null +++ b/code/sota/TransUNet/trainer.py @@ -0,0 +1,96 @@ +import argparse +import logging +import os +import random +import sys +import time +import numpy as np +import torch +import torch.nn as nn +import torch.optim as optim +from tensorboardX import SummaryWriter +from torch.nn.modules.loss import CrossEntropyLoss +from torch.utils.data import DataLoader +from tqdm import tqdm +from utils import DiceLoss +from torchvision import transforms + +def trainer_synapse(args, model, snapshot_path): + from datasets.dataset_synapse import Synapse_dataset, RandomGenerator + logging.basicConfig(filename=snapshot_path + "/log.txt", level=logging.INFO, + format='[%(asctime)s.%(msecs)03d] %(message)s', datefmt='%H:%M:%S') + logging.getLogger().addHandler(logging.StreamHandler(sys.stdout)) + logging.info(str(args)) + base_lr = args.base_lr + num_classes = args.num_classes + batch_size = args.batch_size * args.n_gpu + # max_iterations = args.max_iterations + db_train = Synapse_dataset(base_dir=args.root_path, list_dir=args.list_dir, split="train", + transform=transforms.Compose( + [RandomGenerator(output_size=[args.img_size, args.img_size])])) + print("The length of train set is: {}".format(len(db_train))) + + def worker_init_fn(worker_id): + random.seed(args.seed + worker_id) + + trainloader = DataLoader(db_train, batch_size=batch_size, shuffle=True, num_workers=8, pin_memory=True, + worker_init_fn=worker_init_fn) + if args.n_gpu > 1: + model = nn.DataParallel(model) + model.train() + ce_loss = CrossEntropyLoss() + dice_loss = DiceLoss(num_classes) + optimizer = optim.SGD(model.parameters(), lr=base_lr, momentum=0.9, weight_decay=0.0001) + writer = SummaryWriter(snapshot_path + '/log') + iter_num = 0 + max_epoch = args.max_epochs + max_iterations = args.max_epochs * len(trainloader) # max_epoch = max_iterations // len(trainloader) + 1 + logging.info("{} iterations per epoch. {} max iterations ".format(len(trainloader), max_iterations)) + best_performance = 0.0 + iterator = tqdm(range(max_epoch), ncols=70) + for epoch_num in iterator: + for i_batch, sampled_batch in enumerate(trainloader): + image_batch, label_batch = sampled_batch['image'], sampled_batch['label'] + image_batch, label_batch = image_batch.cuda(), label_batch.cuda() + outputs = model(image_batch) + loss_ce = ce_loss(outputs, label_batch[:].long()) + loss_dice = dice_loss(outputs, label_batch, softmax=True) + loss = 0.5 * loss_ce + 0.5 * loss_dice + optimizer.zero_grad() + loss.backward() + optimizer.step() + lr_ = base_lr * (1.0 - iter_num / max_iterations) ** 0.9 + for param_group in optimizer.param_groups: + param_group['lr'] = lr_ + + iter_num = iter_num + 1 + writer.add_scalar('info/lr', lr_, iter_num) + writer.add_scalar('info/total_loss', loss, iter_num) + writer.add_scalar('info/loss_ce', loss_ce, iter_num) + + logging.info('iteration %d : loss : %f, loss_ce: %f' % (iter_num, loss.item(), loss_ce.item())) + + if iter_num % 20 == 0: + image = image_batch[1, 0:1, :, :] + image = (image - image.min()) / (image.max() - image.min()) + writer.add_image('train/Image', image, iter_num) + outputs = torch.argmax(torch.softmax(outputs, dim=1), dim=1, keepdim=True) + writer.add_image('train/Prediction', outputs[1, ...] * 50, iter_num) + labs = label_batch[1, ...].unsqueeze(0) * 50 + writer.add_image('train/GroundTruth', labs, iter_num) + + save_interval = 50 # int(max_epoch/6) + if epoch_num > int(max_epoch / 2) and (epoch_num + 1) % save_interval == 0: + save_mode_path = os.path.join(snapshot_path, 'epoch_' + str(epoch_num) + '.pth') + torch.save(model.state_dict(), save_mode_path) + logging.info("save model to {}".format(save_mode_path)) + + if epoch_num >= max_epoch - 1: + save_mode_path = os.path.join(snapshot_path, 'epoch_' + str(epoch_num) + '.pth') + torch.save(model.state_dict(), save_mode_path) + logging.info("save model to {}".format(save_mode_path)) + iterator.close() + break + + writer.close() + return "Training Finished!" \ No newline at end of file diff --git a/code/sota/TransUNet/utils.py b/code/sota/TransUNet/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..0e3a1bf9ad7058f506faff0a8ae4f6056514575c --- /dev/null +++ b/code/sota/TransUNet/utils.py @@ -0,0 +1,102 @@ +import numpy as np +import torch +from medpy import metric +from scipy.ndimage import zoom +import torch.nn as nn +import SimpleITK as sitk + + +class DiceLoss(nn.Module): + def __init__(self, n_classes): + super(DiceLoss, self).__init__() + self.n_classes = n_classes + + def _one_hot_encoder(self, input_tensor): + tensor_list = [] + for i in range(self.n_classes): + temp_prob = input_tensor == i # * torch.ones_like(input_tensor) + tensor_list.append(temp_prob.unsqueeze(1)) + output_tensor = torch.cat(tensor_list, dim=1) + return output_tensor.float() + + def _dice_loss(self, score, target): + target = target.float() + smooth = 1e-5 + intersect = torch.sum(score * target) + y_sum = torch.sum(target * target) + z_sum = torch.sum(score * score) + loss = (2 * intersect + smooth) / (z_sum + y_sum + smooth) + loss = 1 - loss + return loss + + def forward(self, inputs, target, weight=None, softmax=False): + if softmax: + inputs = torch.softmax(inputs, dim=1) + target = self._one_hot_encoder(target) + if weight is None: + weight = [1] * self.n_classes + assert inputs.size() == target.size(), 'predict {} & target {} shape do not match'.format(inputs.size(), target.size()) + class_wise_dice = [] + loss = 0.0 + for i in range(0, self.n_classes): + dice = self._dice_loss(inputs[:, i], target[:, i]) + class_wise_dice.append(1.0 - dice.item()) + loss += dice * weight[i] + return loss / self.n_classes + + +def calculate_metric_percase(pred, gt): + pred[pred > 0] = 1 + gt[gt > 0] = 1 + if pred.sum() > 0 and gt.sum()>0: + dice = metric.binary.dc(pred, gt) + hd95 = metric.binary.hd95(pred, gt) + return dice, hd95 + elif pred.sum() > 0 and gt.sum()==0: + return 1, 0 + else: + return 0, 0 + + +def test_single_volume(image, label, net, classes, patch_size=[256, 256], test_save_path=None, case=None, z_spacing=1): + image, label = image.squeeze(0).cpu().detach().numpy(), label.squeeze(0).cpu().detach().numpy() + if len(image.shape) == 3: + prediction = np.zeros_like(label) + for ind in range(image.shape[0]): + slice = image[ind, :, :] + x, y = slice.shape[0], slice.shape[1] + if x != patch_size[0] or y != patch_size[1]: + slice = zoom(slice, (patch_size[0] / x, patch_size[1] / y), order=3) # previous using 0 + input = torch.from_numpy(slice).unsqueeze(0).unsqueeze(0).float().cuda() + net.eval() + with torch.no_grad(): + outputs = net(input) + out = torch.argmax(torch.softmax(outputs, dim=1), dim=1).squeeze(0) + out = out.cpu().detach().numpy() + if x != patch_size[0] or y != patch_size[1]: + pred = zoom(out, (x / patch_size[0], y / patch_size[1]), order=0) + else: + pred = out + prediction[ind] = pred + else: + input = torch.from_numpy(image).unsqueeze( + 0).unsqueeze(0).float().cuda() + net.eval() + with torch.no_grad(): + out = torch.argmax(torch.softmax(net(input), dim=1), dim=1).squeeze(0) + prediction = out.cpu().detach().numpy() + metric_list = [] + for i in range(1, classes): + metric_list.append(calculate_metric_percase(prediction == i, label == i)) + + if test_save_path is not None: + img_itk = sitk.GetImageFromArray(image.astype(np.float32)) + prd_itk = sitk.GetImageFromArray(prediction.astype(np.float32)) + lab_itk = sitk.GetImageFromArray(label.astype(np.float32)) + img_itk.SetSpacing((1, 1, z_spacing)) + prd_itk.SetSpacing((1, 1, z_spacing)) + lab_itk.SetSpacing((1, 1, z_spacing)) + sitk.WriteImage(prd_itk, test_save_path + '/'+case + "_pred.nii.gz") + sitk.WriteImage(img_itk, test_save_path + '/'+ case + "_img.nii.gz") + sitk.WriteImage(lab_itk, test_save_path + '/'+ case + "_gt.nii.gz") + return metric_list \ No newline at end of file