From 9fe668d5680ba3e2a8ea7a300390487254c8d8bf Mon Sep 17 00:00:00 2001 From: Nicolas Kaenzig Date: Thu, 10 Apr 2025 13:39:58 +0200 Subject: [PATCH 01/15] started btcv config & add collator --- .../radiology/online/segmentation/btcv.yaml | 188 ++++++++++++++++++ src/eva/core/models/modules/typings.py | 4 +- src/eva/vision/data/dataloaders/__init__.py | 5 + .../data/dataloaders/collate_fn/__init__.py | 5 + .../data/dataloaders/collate_fn/collection.py | 22 ++ .../models/modules/semantic_segmentation.py | 2 +- 6 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 configs/vision/radiology/online/segmentation/btcv.yaml create mode 100644 src/eva/vision/data/dataloaders/__init__.py create mode 100644 src/eva/vision/data/dataloaders/collate_fn/__init__.py create mode 100644 src/eva/vision/data/dataloaders/collate_fn/collection.py diff --git a/configs/vision/radiology/online/segmentation/btcv.yaml b/configs/vision/radiology/online/segmentation/btcv.yaml new file mode 100644 index 000000000..573ec258d --- /dev/null +++ b/configs/vision/radiology/online/segmentation/btcv.yaml @@ -0,0 +1,188 @@ +--- +trainer: + class_path: eva.Trainer + init_args: + accelerator: cpu # TODO: remove + n_runs: &N_RUNS ${oc.env:N_RUNS, 5} + default_root_dir: &OUTPUT_ROOT ${oc.env:OUTPUT_ROOT, logs/${oc.env:MODEL_NAME, radiology/voco_b}/btcv} + max_steps: &MAX_STEPS ${oc.env:MAX_STEPS, 500000} + checkpoint_type: ${oc.env:CHECKPOINT_TYPE, best} + num_sanity_val_steps: 0 + callbacks: + - class_path: eva.callbacks.ConfigurationLogger + - class_path: lightning.pytorch.callbacks.TQDMProgressBar + init_args: + refresh_rate: ${oc.env:TQDM_REFRESH_RATE, 1} + - class_path: eva.vision.callbacks.SemanticSegmentationLogger + init_args: + log_every_n_epochs: 1 + mean: &NORMALIZE_MEAN ${oc.env:NORMALIZE_MEAN, [0.485, 0.456, 0.406]} + std: &NORMALIZE_STD ${oc.env:NORMALIZE_STD, [0.229, 0.224, 0.225]} + - class_path: lightning.pytorch.callbacks.ModelCheckpoint + init_args: + filename: best + save_last: ${oc.env:SAVE_LAST, false} + save_top_k: 1 + monitor: &MONITOR_METRIC ${oc.env:MONITOR_METRIC, 'val/MonaiDiceScore'} + mode: &MONITOR_METRIC_MODE ${oc.env:MONITOR_METRIC_MODE, max} + - class_path: lightning.pytorch.callbacks.EarlyStopping + init_args: + min_delta: 0 + patience: ${oc.env:PATIENCE, 100} + monitor: *MONITOR_METRIC + mode: *MONITOR_METRIC_MODE + logger: + - class_path: lightning.pytorch.loggers.TensorBoardLogger + init_args: + save_dir: *OUTPUT_ROOT + name: "" +model: + class_path: eva.vision.models.modules.SemanticSegmentationModule + init_args: + encoder: + class_path: eva.vision.models.ModelFromRegistry + init_args: + model_name: ${oc.env:MODEL_NAME, radiology/voco_b} + model_kwargs: + out_indices: ${oc.env:OUT_INDICES, 6} + decoder: + class_path: eva.vision.models.networks.decoders.segmentation.SwinUNETRDecoder + init_args: + feature_size: ${oc.env:IN_FEATURES, 48} + out_channels: &NUM_CLASSES 14 + inferer: + class_path: monai.inferers.SlidingWindowInferer + init_args: + roi_size: &ROI_SIZE ${oc.env:ROI_SIZE, [96, 96, 96]} + sw_batch_size: ${oc.env:SW_BATCH_SIZE, 2} + # sw_batch_size: ${oc.env:SW_BATCH_SIZE, 8} + overlap: ${oc.env:SW_OVERLAP, 0.75} + criterion: + class_path: eva.core.losses.CrossEntropyLoss + init_args: + weight: [0.05, 0.1, 1.5] + lr_multiplier_encoder: 0.0 + optimizer: + class_path: torch.optim.AdamW + init_args: + lr: ${oc.env:LR_VALUE, 0.002} + lr_scheduler: + class_path: torch.optim.lr_scheduler.PolynomialLR + init_args: + total_iters: *MAX_STEPS + power: 0.9 + postprocess: + predictions_transforms: + - class_path: torch.argmax + init_args: + dim: 1 + metrics: + common: + - class_path: eva.metrics.AverageLoss + evaluation: + - class_path: eva.vision.metrics.defaults.MulticlassSegmentationMetrics + init_args: + num_classes: *NUM_CLASSES + - class_path: torchmetrics.ClasswiseWrapper + init_args: + metric: + class_path: eva.vision.metrics.MonaiDiceScore + init_args: + include_background: true + num_classes: *NUM_CLASSES + reduction: none + labels: + - "0_background" + - "1_spleen" + - "2_right_kidney" + - "3_left_kidney" + - "4_gallbladder" + - "5_esophagus" + - "6_liver" + - "7_stomach" + - "8_aorta" + - "9_inferior_vena_cava" + - "10_portal_and_splenic_vein" + - "11_pancreas" + - "12_right_adrenal_gland" + - "13_left_adrenal_gland" +data: + class_path: eva.DataModule + init_args: + datasets: + train: + class_path: eva.vision.datasets.BTCV + init_args: &DATASET_ARGS + root: ${oc.env:DATA_ROOT, ./data/btcv} + split: train + transforms: + class_path: torchvision.transforms.v2.Compose + init_args: + transforms: + - class_path: eva.vision.data.transforms.EnsureChannelFirst + init_args: + channel_dim: 1 + - class_path: eva.vision.data.transforms.Spacing + init_args: + pixdim: [1.5, 1.5, 1.5] + - class_path: eva.vision.data.transforms.ScaleIntensityRange + init_args: + input_range: + - ${oc.env:SCALE_INTENSITY_MIN, -175.0} + - ${oc.env:SCALE_INTENSITY_MAX, 250.0} + output_range: [0.0, 1.0] + - class_path: eva.vision.data.transforms.CropForeground + - class_path: eva.vision.data.transforms.SpatialPad + init_args: + spatial_size: *ROI_SIZE + - class_path: eva.vision.data.transforms.RandCropByPosNegLabel + init_args: + spatial_size: *ROI_SIZE + num_samples: ${oc.env:SAMPLE_BATCH_SIZE, 4} + pos: ${oc.env:RAND_CROP_POS_WEIGHT, 1} + neg: ${oc.env:RAND_CROP_NEG_WEIGHT, 1} + - class_path: eva.vision.data.transforms.RandFlip + init_args: + spatial_axes: [0, 1, 2] + - class_path: eva.vision.data.transforms.RandRotate90 + init_args: + spatial_axes: [1, 2] + - class_path: eva.vision.data.transforms.RandScaleIntensity + init_args: + factors: 0.1 + prob: 0.1 + - class_path: eva.vision.data.transforms.RandShiftIntensity + init_args: + offsets: 0.1 + prob: 0.1 + val: + class_path: eva.vision.datasets.BTCV + init_args: + <<: *DATASET_ARGS + split: val + transforms: + class_path: torchvision.transforms.v2.Compose + init_args: + transforms: + - class_path: eva.vision.data.transforms.EnsureChannelFirst + init_args: + channel_dim: 1 + - class_path: eva.vision.data.transforms.Spacing + init_args: + pixdim: [1.5, 1.5, 1.5] + - class_path: eva.vision.data.transforms.ScaleIntensityRange + init_args: + input_range: + - ${oc.env:SCALE_INTENSITY_MIN, -175.0} + - ${oc.env:SCALE_INTENSITY_MAX, 250.0} + output_range: [0.0, 1.0] + - class_path: eva.vision.data.transforms.CropForeground + dataloaders: + train: + batch_size: &BATCH_SIZE ${oc.env:BATCH_SIZE, 2} + num_workers: &N_DATA_WORKERS ${oc.env:N_DATA_WORKERS, 8} + shuffle: true + collate_fn: eva.vision.data.dataloaders.collate_fn.collection_collate + val: + batch_size: *BATCH_SIZE + num_workers: *N_DATA_WORKERS diff --git a/src/eva/core/models/modules/typings.py b/src/eva/core/models/modules/typings.py index a999a7a3d..fbac48cc7 100644 --- a/src/eva/core/models/modules/typings.py +++ b/src/eva/core/models/modules/typings.py @@ -1,6 +1,6 @@ """Type annotations for model modules.""" -from typing import Any, Dict, NamedTuple +from typing import Any, Dict, NamedTuple, List import lightning.pytorch as pl import torch @@ -13,7 +13,7 @@ class INPUT_BATCH(NamedTuple): """The default input batch data scheme.""" - data: torch.Tensor + data: torch.Tensor | List[torch.Tensor] """The data batch.""" targets: torch.Tensor | None = None diff --git a/src/eva/vision/data/dataloaders/__init__.py b/src/eva/vision/data/dataloaders/__init__.py new file mode 100644 index 000000000..7367b69f7 --- /dev/null +++ b/src/eva/vision/data/dataloaders/__init__.py @@ -0,0 +1,5 @@ +"""Dataloader related utilities and functions.""" + +from eva.vision.data.dataloaders import collate_fn + +__all__ = ["collate_fn"] diff --git a/src/eva/vision/data/dataloaders/collate_fn/__init__.py b/src/eva/vision/data/dataloaders/collate_fn/__init__.py new file mode 100644 index 000000000..da01ce9a5 --- /dev/null +++ b/src/eva/vision/data/dataloaders/collate_fn/__init__.py @@ -0,0 +1,5 @@ +"""Dataloader collate API.""" + +from eva.vision.data.dataloaders.collate_fn.collection import collection_collate + +__all__ = ["collection_collate"] diff --git a/src/eva/vision/data/dataloaders/collate_fn/collection.py b/src/eva/vision/data/dataloaders/collate_fn/collection.py new file mode 100644 index 000000000..2a356bd08 --- /dev/null +++ b/src/eva/vision/data/dataloaders/collate_fn/collection.py @@ -0,0 +1,22 @@ +"""Data only collate filter function.""" + +from typing import Any, List + +import torch + +from eva.core.models.modules.typings import INPUT_BATCH + + +def collection_collate(batch: List[List[INPUT_BATCH]]) -> Any: + """Collate function for stacking a collection of data samples. + + Args: + batch: The batch to be collated. + + Returns: + The collated batch. + """ + tensors, targets, metadata = zip(*batch) + batch_tensors = torch.cat(list(map(torch.stack, tensors))) + batch_targets = torch.cat(list(map(torch.stack, targets))) + return batch_tensors, batch_targets, metadata diff --git a/src/eva/vision/models/modules/semantic_segmentation.py b/src/eva/vision/models/modules/semantic_segmentation.py index b9596f12e..f20af6ec8 100644 --- a/src/eva/vision/models/modules/semantic_segmentation.py +++ b/src/eva/vision/models/modules/semantic_segmentation.py @@ -25,7 +25,7 @@ class SemanticSegmentationModule(module.ModelModule): def __init__( self, - decoder: decoders.Decoder, + decoder: decoders.Decoder | nn.Module, criterion: Callable[..., torch.Tensor], encoder: Dict[str, Any] | Callable[[torch.Tensor], List[torch.Tensor]] | None = None, lr_multiplier_encoder: float = 0.0, From a6c3dabbcb4d01977ceee047ceefd6aac298131d Mon Sep 17 00:00:00 2001 From: Nicolas Kaenzig Date: Thu, 10 Apr 2025 14:52:25 +0200 Subject: [PATCH 02/15] fixes in btcv.yaml --- .../radiology/online/segmentation/btcv.yaml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/configs/vision/radiology/online/segmentation/btcv.yaml b/configs/vision/radiology/online/segmentation/btcv.yaml index 573ec258d..cd4999ac6 100644 --- a/configs/vision/radiology/online/segmentation/btcv.yaml +++ b/configs/vision/radiology/online/segmentation/btcv.yaml @@ -2,7 +2,6 @@ trainer: class_path: eva.Trainer init_args: - accelerator: cpu # TODO: remove n_runs: &N_RUNS ${oc.env:N_RUNS, 5} default_root_dir: &OUTPUT_ROOT ${oc.env:OUTPUT_ROOT, logs/${oc.env:MODEL_NAME, radiology/voco_b}/btcv} max_steps: &MAX_STEPS ${oc.env:MAX_STEPS, 500000} @@ -13,11 +12,6 @@ trainer: - class_path: lightning.pytorch.callbacks.TQDMProgressBar init_args: refresh_rate: ${oc.env:TQDM_REFRESH_RATE, 1} - - class_path: eva.vision.callbacks.SemanticSegmentationLogger - init_args: - log_every_n_epochs: 1 - mean: &NORMALIZE_MEAN ${oc.env:NORMALIZE_MEAN, [0.485, 0.456, 0.406]} - std: &NORMALIZE_STD ${oc.env:NORMALIZE_STD, [0.229, 0.224, 0.225]} - class_path: lightning.pytorch.callbacks.ModelCheckpoint init_args: filename: best @@ -58,9 +52,11 @@ model: # sw_batch_size: ${oc.env:SW_BATCH_SIZE, 8} overlap: ${oc.env:SW_OVERLAP, 0.75} criterion: - class_path: eva.core.losses.CrossEntropyLoss + class_path: monai.losses.DiceCELoss init_args: - weight: [0.05, 0.1, 1.5] + include_background: false + to_onehot_y: true + softmax: true lr_multiplier_encoder: 0.0 optimizer: class_path: torch.optim.AdamW @@ -179,10 +175,10 @@ data: - class_path: eva.vision.data.transforms.CropForeground dataloaders: train: - batch_size: &BATCH_SIZE ${oc.env:BATCH_SIZE, 2} + batch_size: ${oc.env:BATCH_SIZE, 2} num_workers: &N_DATA_WORKERS ${oc.env:N_DATA_WORKERS, 8} shuffle: true collate_fn: eva.vision.data.dataloaders.collate_fn.collection_collate val: - batch_size: *BATCH_SIZE + batch_size: 1 num_workers: *N_DATA_WORKERS From 8552187466ffb7a13f71441c8534370987d24910 Mon Sep 17 00:00:00 2001 From: Nicolas Kaenzig Date: Thu, 10 Apr 2025 15:31:35 +0200 Subject: [PATCH 03/15] fix in download logic --- src/eva/core/models/modules/typings.py | 2 +- src/eva/core/models/wrappers/_utils.py | 2 +- src/eva/vision/data/dataloaders/collate_fn/collection.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/eva/core/models/modules/typings.py b/src/eva/core/models/modules/typings.py index fbac48cc7..3e7a64985 100644 --- a/src/eva/core/models/modules/typings.py +++ b/src/eva/core/models/modules/typings.py @@ -1,6 +1,6 @@ """Type annotations for model modules.""" -from typing import Any, Dict, NamedTuple, List +from typing import Any, Dict, List, NamedTuple import lightning.pytorch as pl import torch diff --git a/src/eva/core/models/wrappers/_utils.py b/src/eva/core/models/wrappers/_utils.py index d2ebd79cb..cb1d9d946 100644 --- a/src/eva/core/models/wrappers/_utils.py +++ b/src/eva/core/models/wrappers/_utils.py @@ -63,7 +63,7 @@ def load_state_dict_from_url( os.makedirs(model_dir, exist_ok=True) cached_file = os.path.join(model_dir, filename or os.path.basename(url)) - if force or not _check_integrity(cached_file, md5): + if force or not os.path.exists(cached_file) or not _check_integrity(cached_file, md5): sys.stderr.write(f"Downloading: '{url}' to {cached_file}\n") _download_url_to_file(url, cached_file, progress=progress) if md5 is None or not _check_integrity(cached_file, md5): diff --git a/src/eva/vision/data/dataloaders/collate_fn/collection.py b/src/eva/vision/data/dataloaders/collate_fn/collection.py index 2a356bd08..9a7d7efa4 100644 --- a/src/eva/vision/data/dataloaders/collate_fn/collection.py +++ b/src/eva/vision/data/dataloaders/collate_fn/collection.py @@ -16,7 +16,7 @@ def collection_collate(batch: List[List[INPUT_BATCH]]) -> Any: Returns: The collated batch. """ - tensors, targets, metadata = zip(*batch) + tensors, targets, metadata = zip(*batch, strict=False) batch_tensors = torch.cat(list(map(torch.stack, tensors))) batch_targets = torch.cat(list(map(torch.stack, targets))) return batch_tensors, batch_targets, metadata From 8febd13631cab6f9e8689c8e53512ac1744528da Mon Sep 17 00:00:00 2001 From: Nicolas Kaenzig Date: Thu, 10 Apr 2025 15:52:57 +0200 Subject: [PATCH 04/15] add AsDiscrete --- src/eva/core/models/transforms/__init__.py | 3 +- src/eva/core/models/transforms/as_discrete.py | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/eva/core/models/transforms/as_discrete.py diff --git a/src/eva/core/models/transforms/__init__.py b/src/eva/core/models/transforms/__init__.py index db20ffa94..c4f70ea8b 100644 --- a/src/eva/core/models/transforms/__init__.py +++ b/src/eva/core/models/transforms/__init__.py @@ -2,5 +2,6 @@ from eva.core.models.transforms.extract_cls_features import ExtractCLSFeatures from eva.core.models.transforms.extract_patch_features import ExtractPatchFeatures +from eva.core.models.transforms.as_discrete import AsDiscrete -__all__ = ["ExtractCLSFeatures", "ExtractPatchFeatures"] +__all__ = ["AsDiscrete", "ExtractCLSFeatures", "ExtractPatchFeatures"] diff --git a/src/eva/core/models/transforms/as_discrete.py b/src/eva/core/models/transforms/as_discrete.py new file mode 100644 index 000000000..c293f6227 --- /dev/null +++ b/src/eva/core/models/transforms/as_discrete.py @@ -0,0 +1,40 @@ +"""Defines the AsDiscrete transformation.""" + +import torch +from monai.networks.utils import one_hot + + +class AsDiscrete: + def __init__( + self, + argmax: bool = False, + to_onehot: int | bool | None = None, + threshold: float | None = None, + ) -> None: + """Convert the input tensor/array into discrete values. + + Args: + argmax: Whether to execute argmax function on input data before transform. + to_onehot: if not None, convert input data into the one-hot format with + specified number of classes. If bool, it will try to infer the number + of classes. + threshold: If not None, threshold the float values to int number 0 or 1 + with specified threshold. + """ + super().__init__() + + self._argmax = argmax + self._to_onehot = to_onehot + self._threshold = threshold + + def __call__(self, tensor: torch.Tensor) -> torch.Tensor: + if self._argmax: + tensor = torch.argmax(tensor, dim=1, keepdim=True) + + if self._to_onehot is not None: + tensor = one_hot(tensor, num_classes=self._to_onehot, dim=1, dtype=torch.long) + + if self._threshold is not None: + tensor = tensor >= self._threshold + + return tensor From fce46c09ed531d8b6e8c11c9a96daafe5d2225e2 Mon Sep 17 00:00:00 2001 From: Nicolas Kaenzig Date: Fri, 11 Apr 2025 09:34:35 +0200 Subject: [PATCH 05/15] changed max steps to max epochs & added cosine scheduler --- .../radiology/online/segmentation/btcv.yaml | 43 +++++++++---------- src/eva/core/models/transforms/__init__.py | 2 +- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/configs/vision/radiology/online/segmentation/btcv.yaml b/configs/vision/radiology/online/segmentation/btcv.yaml index cd4999ac6..f834b910b 100644 --- a/configs/vision/radiology/online/segmentation/btcv.yaml +++ b/configs/vision/radiology/online/segmentation/btcv.yaml @@ -4,32 +4,25 @@ trainer: init_args: n_runs: &N_RUNS ${oc.env:N_RUNS, 5} default_root_dir: &OUTPUT_ROOT ${oc.env:OUTPUT_ROOT, logs/${oc.env:MODEL_NAME, radiology/voco_b}/btcv} - max_steps: &MAX_STEPS ${oc.env:MAX_STEPS, 500000} + max_epochs: &MAX_EPOCHS ${oc.env:MAX_EPOCHS, 500} checkpoint_type: ${oc.env:CHECKPOINT_TYPE, best} + check_val_every_n_epoch: ${oc.env:CHECK_VAL_EVERY_N_EPOCHS, 50} num_sanity_val_steps: 0 + log_every_n_steps: ${oc.env:LOG_EVERY_N_STEPS, 100} callbacks: - class_path: eva.callbacks.ConfigurationLogger - class_path: lightning.pytorch.callbacks.TQDMProgressBar init_args: refresh_rate: ${oc.env:TQDM_REFRESH_RATE, 1} - - class_path: lightning.pytorch.callbacks.ModelCheckpoint - init_args: - filename: best - save_last: ${oc.env:SAVE_LAST, false} - save_top_k: 1 - monitor: &MONITOR_METRIC ${oc.env:MONITOR_METRIC, 'val/MonaiDiceScore'} - mode: &MONITOR_METRIC_MODE ${oc.env:MONITOR_METRIC_MODE, max} - - class_path: lightning.pytorch.callbacks.EarlyStopping - init_args: - min_delta: 0 - patience: ${oc.env:PATIENCE, 100} - monitor: *MONITOR_METRIC - mode: *MONITOR_METRIC_MODE logger: - class_path: lightning.pytorch.loggers.TensorBoardLogger init_args: save_dir: *OUTPUT_ROOT name: "" + # - class_path: lightning.pytorch.loggers.WandbLogger + # init_args: + # project: ${oc.env:WANDB_PROJECT, radiology} + # name: ${oc.env:WANDB_RUN_NAME, btcv-${oc.env:MODEL_NAME, radiology/voco_b}} model: class_path: eva.vision.models.modules.SemanticSegmentationModule init_args: @@ -48,8 +41,7 @@ model: class_path: monai.inferers.SlidingWindowInferer init_args: roi_size: &ROI_SIZE ${oc.env:ROI_SIZE, [96, 96, 96]} - sw_batch_size: ${oc.env:SW_BATCH_SIZE, 2} - # sw_batch_size: ${oc.env:SW_BATCH_SIZE, 8} + sw_batch_size: ${oc.env:SW_BATCH_SIZE, 8} overlap: ${oc.env:SW_OVERLAP, 0.75} criterion: class_path: monai.losses.DiceCELoss @@ -61,17 +53,24 @@ model: optimizer: class_path: torch.optim.AdamW init_args: - lr: ${oc.env:LR_VALUE, 0.002} + lr: ${oc.env:LR_VALUE, 0.001} + betas: [0.9, 0.999] + weight_decay: ${oc.env:WEIGHT_DECAY, 0.01} lr_scheduler: - class_path: torch.optim.lr_scheduler.PolynomialLR + class_path: torch.optim.lr_scheduler.CosineAnnealingLR init_args: - total_iters: *MAX_STEPS - power: 0.9 + T_max: *MAX_EPOCHS + eta_min: ${oc.env:LR_VALUE_END, 0.0001} postprocess: predictions_transforms: - - class_path: torch.argmax + - class_path: eva.core.models.transforms.AsDiscrete + init_args: + argmax: true + to_onehot: *NUM_CLASSES + targets_transforms: + - class_path: eva.core.models.transforms.AsDiscrete init_args: - dim: 1 + to_onehot: *NUM_CLASSES metrics: common: - class_path: eva.metrics.AverageLoss diff --git a/src/eva/core/models/transforms/__init__.py b/src/eva/core/models/transforms/__init__.py index c4f70ea8b..a8f5c940f 100644 --- a/src/eva/core/models/transforms/__init__.py +++ b/src/eva/core/models/transforms/__init__.py @@ -1,7 +1,7 @@ """Model outputs transforms API.""" +from eva.core.models.transforms.as_discrete import AsDiscrete from eva.core.models.transforms.extract_cls_features import ExtractCLSFeatures from eva.core.models.transforms.extract_patch_features import ExtractPatchFeatures -from eva.core.models.transforms.as_discrete import AsDiscrete __all__ = ["AsDiscrete", "ExtractCLSFeatures", "ExtractPatchFeatures"] From dbb4805ff01bec3f12d6718a2a713ebbb50eaa95 Mon Sep 17 00:00:00 2001 From: Nicolas Kaenzig Date: Fri, 11 Apr 2025 11:12:07 +0200 Subject: [PATCH 06/15] replace metrics collection by macro dice --- configs/vision/radiology/online/segmentation/btcv.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/configs/vision/radiology/online/segmentation/btcv.yaml b/configs/vision/radiology/online/segmentation/btcv.yaml index f834b910b..f78a72c97 100644 --- a/configs/vision/radiology/online/segmentation/btcv.yaml +++ b/configs/vision/radiology/online/segmentation/btcv.yaml @@ -75,9 +75,12 @@ model: common: - class_path: eva.metrics.AverageLoss evaluation: - - class_path: eva.vision.metrics.defaults.MulticlassSegmentationMetrics + - class_path: torchmetrics.segmentation.DiceScore init_args: num_classes: *NUM_CLASSES + include_background: false + average: macro + input_format: one-hot - class_path: torchmetrics.ClasswiseWrapper init_args: metric: @@ -86,6 +89,7 @@ model: include_background: true num_classes: *NUM_CLASSES reduction: none + prefix: DiceScore_ labels: - "0_background" - "1_spleen" From f4ab85ec5040099d15b3b45aef5127101ac6f026 Mon Sep 17 00:00:00 2001 From: Nicolas Kaenzig Date: Fri, 11 Apr 2025 14:00:10 +0200 Subject: [PATCH 07/15] add input_format to MonaiDiceScore --- src/eva/vision/metrics/segmentation/monai_dice.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/eva/vision/metrics/segmentation/monai_dice.py b/src/eva/vision/metrics/segmentation/monai_dice.py index 9e7f55ac9..d780895bc 100644 --- a/src/eva/vision/metrics/segmentation/monai_dice.py +++ b/src/eva/vision/metrics/segmentation/monai_dice.py @@ -1,5 +1,7 @@ """Wrapper for dice score metric from MONAI.""" +from typing import Literal + from monai.metrics.meandice import DiceMetric from typing_extensions import override @@ -14,6 +16,7 @@ def __init__( self, num_classes: int, include_background: bool = True, + input_format: Literal["one-hot", "index"] = "index", reduction: str = "mean", ignore_index: int | None = None, **kwargs, @@ -24,6 +27,8 @@ def __init__( num_classes: The number of classes in the dataset. include_background: Whether to include the background class in the computation. reduction: The method to reduce the dice score. Options are `"mean"`, `"sum"`, `"none"`. + input_format: Choose between "one-hot" for one-hot encoded tensors or "index" + for index tensors. ignore_index: Integer specifying a target class to ignore. If given, this class index does not contribute to the returned score. kwargs: Additional keyword arguments for instantiating monai's `DiceMetric` class. @@ -40,11 +45,13 @@ def __init__( self.reduction = reduction self.orig_num_classes = num_classes self.ignore_index = ignore_index + self.input_format = input_format @override def update(self, preds, target): - preds = _utils.index_to_one_hot(preds, num_classes=self.orig_num_classes) - target = _utils.index_to_one_hot(target, num_classes=self.orig_num_classes) + if self.input_format == "index": + preds = _utils.index_to_one_hot(preds, num_classes=self.orig_num_classes) + target = _utils.index_to_one_hot(target, num_classes=self.orig_num_classes) if self.ignore_index is not None: preds, target = _utils.apply_ignore_index(preds, target, self.ignore_index) return super().update(preds, target) From 3375658684205df44eb0fd81b22e2135326ff2ec Mon Sep 17 00:00:00 2001 From: Nicolas Kaenzig Date: Fri, 11 Apr 2025 14:00:36 +0200 Subject: [PATCH 08/15] remove total segmentator test assets --- .../Totalsegmentator_dataset_v201/meta.csv | 3 --- .../s0011/ct.nii.gz | Bin 1790 -> 0 bytes .../s0011/segmentations/aorta_small.nii.gz | Bin 153 -> 0 bytes .../s0011/segmentations/brain_small.nii.gz | Bin 152 -> 0 bytes .../s0011/segmentations/colon_small.nii.gz | Bin 152 -> 0 bytes .../segmentations/semantic_labels/masks.nii.gz | Bin 79 -> 0 bytes .../s0461/ct.nii.gz | Bin 1872 -> 0 bytes .../s0461/segmentations/aorta_small.nii.gz | Bin 159 -> 0 bytes .../s0461/segmentations/brain_small.nii.gz | Bin 159 -> 0 bytes .../s0461/segmentations/colon_small.nii.gz | Bin 159 -> 0 bytes .../segmentations/semantic_labels/masks.nii.gz | Bin 79 -> 0 bytes .../s0762/ct.nii.gz | Bin 1401 -> 0 bytes .../s0762/segmentations/aorta_small.nii.gz | Bin 157 -> 0 bytes .../s0762/segmentations/brain_small.nii.gz | Bin 157 -> 0 bytes .../s0762/segmentations/colon_small.nii.gz | Bin 157 -> 0 bytes .../segmentations/semantic_labels/masks.nii.gz | Bin 79 -> 0 bytes 16 files changed, 3 deletions(-) delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/meta.csv delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/ct.nii.gz delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/segmentations/aorta_small.nii.gz delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/segmentations/brain_small.nii.gz delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/segmentations/colon_small.nii.gz delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/segmentations/semantic_labels/masks.nii.gz delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0461/ct.nii.gz delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0461/segmentations/aorta_small.nii.gz delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0461/segmentations/brain_small.nii.gz delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0461/segmentations/colon_small.nii.gz delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0461/segmentations/semantic_labels/masks.nii.gz delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0762/ct.nii.gz delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0762/segmentations/aorta_small.nii.gz delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0762/segmentations/brain_small.nii.gz delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0762/segmentations/colon_small.nii.gz delete mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0762/segmentations/semantic_labels/masks.nii.gz diff --git a/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/meta.csv b/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/meta.csv deleted file mode 100644 index 557084d84..000000000 --- a/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/meta.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81f0de47859c10a45bfd02550b4d37e5c3c9c954b44657d0f340e665ce8b2cc5 -size 399 diff --git a/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/ct.nii.gz b/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/ct.nii.gz deleted file mode 100644 index 9eac531ccaaeda77e17a1e211954b121a1d9c4c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1790 zcmVA%fuY5lDA|F?SXnb6gfkNyrXzWi4RkFWpE z=SZK6{PJI97ae!cva|lPpHE)6_phM*vb`+I590-wU#Io>yM)=3gvFoWgBZ@AIBhRj z|B$e^NI3Z;VfAXl(2L>xsefuOSfW2bZ-RXIW7^Mg~lCr-us03N?edirUR573{0qhIh(oPazcugHHz z{%i1vzYM$bo8)KvYr-D+?$Mj#cS+p~>fPb5CExZc**D1d;0eEL{BFKadJFb5a03=# zgjZwi)i2qffUfP+5_wL%3?7EpQ`|oC1A6jvM!l*xlb(6?&@;a^`ByRNEjfQ6&cOOv zU;Fbx(vQ^fV4tY-3B4Zck~*KzmtXSkh`uO)mdFpFJX^5Vz9CKrZs>FWD&;vwuDx*v z$Rjw!@0@i2+b@!zocftxpE%}M^4!LslfM1i;zu5y@N+Y7SQpeWr+;hsQj*t#eoVlz zyu!}@2KKl3&B@FBI{dEKkD&hbBcE#a=lCh`AHl%Bd?~4WIZJhzp*I9a;E_5NpC-L< zFNX8yK%6POnREUIjPE5q`P7&1?Ay2EQPLaJzdd^Lzojo3eH?O*68R2)&acRNL%o&j zr^1i??%?|ne+%#k%D^iMnLS$0Bj9e+MsW_C4R8wf>85+v{qFu;6`V z|0~{m4d;|cqR-LJyf~-vXVkGrUN!x84mR-5K5W=8u$zE^dUUR_>w{zLd>)*W@_7w^ z%v;`8<_9|W1MmI%vs9PJd~B$Xed<%k@J7-*`g~Cj=ZpL(@5k#Zhftu`$8W@sJj>vx zb7jN%<-h!~|2O@@kNw@a#=azf`*E9(`pwZlVK4v8v*Z0>|KyQ8++sJO4pVTBTs`kk zo-4rDK6<{t$HeQ?rxJ8-S%(OZ55%jSFX%V;4e)tE{deq7LGK~1<)M7p;WsDF0lPiu zJgiwa=(W_pMScREhja33C+&qNf%)cr@&GU7Nee%#NAaS6ZAP-d5fPV&#`=1$am*6_RfVB>mhbI^|oK;w?V#eo{?+t5GcNYdN%L&6i||JtH|Wd95$hQkh%*H%@>%j89U#}ieh{+M zuMK(3+4uWVOC7>X@zU1;ygJ^RkM`NVw4C4XKYQ|7(%%lO;Jf~I)JvXC(VK(5CtKF? z>BM?U|6I$L4f`u_2bQ4v=9eQkzV%hl=hi+2&O4xvQ+T_@kLRDscl6}FJT=~m=eDNa z1%0XENnpQ1F0a8MBfPGue}-TAqMh&Y9{VdW!{3Jd=B`2QJJ!Cp zo#XP;d3&HfJ9yT#z26L|xBRnz&OzVneh*mDmlnPZh~w`FJI>|%SRNJV z2ka-HeKzj$iag1y!_NNd_jW$=vBhr(&Or5N=7Il)dQ|WtC;uUKYtT7Tz}LyGbDGxx z{Qxft`q3k3I*4*9+=xf9CMr^H%tAzMatbcdHfk-k@jyZ+Ju>o#O}Whv@e}`R%+OBRAe2 zyNbDXV1Eq1N9u#tOP+cT^}Tl%_#N;Zo43!)2EO|{(24%7iPM3Ne7#EbmIn>{#`j*C zd7r>v0R5gZhCdVJH+(|w_rRL^i#L46&U^Kmc~c;Fp33u*dx3GB-&^8E-aGBXbiN$< g9)Bz25vTqbJ>=@?w}qz_dg1x@A26>WDqtA^0Br{Nxc~qF diff --git a/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/segmentations/aorta_small.nii.gz b/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/segmentations/aorta_small.nii.gz deleted file mode 100644 index 7134a2121e3375b4c1b50334f2401e9fca7b3fd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 153 zcmb2|=3oE;mj7=rY~*Y(5NLh4T4qr_N8++&O*!l~r5aGM0RuxUufprP!_e|He+;wfwNn8?D#Av(EMW-@tKg p``WLEOpE8=ikb7>P;6>`fMNp!6Nds;;y^wNUy<;Ihy+On1_0JrK-T~O diff --git a/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/segmentations/brain_small.nii.gz b/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/segmentations/brain_small.nii.gz deleted file mode 100644 index fc003094f50897389965b83703b22c6b1ae4d1bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 152 zcmb2|=3oE;mj7=rY~*Y(5NLh4TrH8Kb8rOG*1aCptP*%k2qh^ z`IZOQRjWd07VS4uy}oJsoEV;EZc*~G&(ihZTHd~W-*;tY)vVvKse3&azyBbge$*y! mz0SOy``dKpY&R8|$~!~Afq{_)JMqAriT!WgEwKbi1_l5kg*ra~ diff --git a/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/segmentations/colon_small.nii.gz b/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/segmentations/colon_small.nii.gz deleted file mode 100644 index fc003094f50897389965b83703b22c6b1ae4d1bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 152 zcmb2|=3oE;mj7=rY~*Y(5NLh4TrH8Kb8rOG*1aCptP*%k2qh^ z`IZOQRjWd07VS4uy}oJsoEV;EZc*~G&(ihZTHd~W-*;tY)vVvKse3&azyBbge$*y! mz0SOy``dKpY&R8|$~!~Afq{_)JMqAriT!WgEwKbi1_l5kg*ra~ diff --git a/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/segmentations/semantic_labels/masks.nii.gz b/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/segmentations/semantic_labels/masks.nii.gz deleted file mode 100644 index 05b28f407391fec935fe6d1f2ac66f82753a0bbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79 zcmb2|=3oE;mjB&}DGFQ$#vBZa#>~nhi?WiE9x!m5+%PtHa9Dbd;-?k6*!zyR9M76y ie}Qph5)V)1t+p4l+CzI-QG_B35Hbo9Mm(~RhfEMR&f7pp zPAU#UfW^}U0?W&o@Q_POG%j7byyXfSmo8npbm`J1@tN;;a$V7Ju5$m(?Ci|!?0;tW z+?&VjKRw5-`rzld9pmTdq+p>`#M5T!k$+qH(SOr3E+3u!|4EM>2^}84@Q;0X_U}`t z{(9Bd;lriA{O@*c51t1$C6>R^X}5o<+5YdNr=ESNSp1PnA>YFqavPj~?RFF70XPLm zVDD$Qt033G`R_;PPwu-sXT12+W&6%?grC}PF0a8J_LYI_CePz6s{&55Xy@JQ_cGycza~U!#-zS z@tWH$B;$Vx1~9(wc8U2zaQ>6)?P9R#355od{Mw z4e)9z#qcZGc75u&Y{au$F2{G!pL4r-aMVuY<2&f9;1eKz1NnuwT`xYb;7eWgf)^F? zZ^6$Vc!>S&3>jb-iT>Rqm#fu^1Gq3{>CeiIi=;bFv?t$Sm zx04^OYt8tQbp`UQf#O30S^S?fZvZM!@%a#a;{BZ>7hn~C5p2M-6>=Axi!YZwe|sU= z_R)K$KBaZq?NpB2nNVW9;j;3Z5kGWYpWth};IjHteA8un#!-9{??>`;iSZC@`(*Gf z!m9|ca^5d%xt(24TXPftEO)sDKMY)vKWXf&6#;hM$3cD87vGpTGjlsY9mx(GT~q z#Xe8(c|P*jgnt=0rr+D=Znwry_XphcS!PaPtQ-!=Tz$wz!ryaD#wZ}qbkdfCap<`3{!0TnkgPyI#bvEG}4@s{!_yncyu zbU)7(c3t>mH-c@SLi54+Mk@8YU*ZdWHOlS)yB9-gs=gcPYe|0JbGvxK?Sjs6{EA0Q z>~&u1d>yiH^4DV@^xlZ46Z8rGNnb}l0%bRXuXFT0;&;9La7oWr%^{+Z{6gNZOfK&QN zM9wvi-xXL_pFaq;eUy*lkFm?R=U6?JcFJRp-v)gls!wvCo$SRQa$Y6%LHMZicZq$Q z{C9RQdtBWw8=SB4Vkq-T^ihMSQTI{$e4x)8{N45GTSxusUL16f!EeH^-h(QAu?rUJ zqvRE++f=**hv>(g#~FQ9d|qMK2E$#?Z{ByTGk>i(}yNnyy0(Fly z>b^lQe#xKsq5eA5{(~J*_f+w(hQ5Md`Kuw%!1W$Bk@ddl9PQ$-N}X1mb4I_iC#f5D zBd|ps4ZhpSVB1IeG_Y&yylbX%+(K}KKMlPPue(0Mmn44i9W=pCJdI~l=|}fd@w9W_ z^>*I<*$Hru*8#r;>t295|CgNmgMPB@6RBT-r#11GdPTh-*md!zemy2HeTVD2s(?>b zzH7AaM*Rx>i9gFvJRkY1GOl{-eKGQ{;a_}SX&s>S1H~b(_Awx@S*LhEMK5{7{3-jS z_ekT4JI8JaFXTu4twr5(&Y^TUY|C%)wNzqVbtQhAKB#+YJmYpf;^=)DbH6h6RSTuR z6@H@b1+Tb1?A<>D{cYZJ|KYmZ8U0z`>BhXK`tB9COMEwp*G69*u%0DYfmb16R7z^>@;6`*~`E9C%zo^y*_r@_wSJSLalS9 z{`Ium#o#z6uHFm9H{B!QMM1t(eP8H&JxzSh%N)C+@A}<2d=eSn<#$}JtM9^d^+BEQ z5x+Wrbxv-Gvths6cr*Yi{#T~fz3yxi3nzv6SmzrMqp z?2GnOd9R7v<(^vYd%T7Gf$GEJh4{E;T>ChH?{(sfH&yJW;E+C|JXII*J}NGLy5JJM z)=?$@>|@Wnf?Wl>u$M;$0doQ`2 z_@wp8@A!jMy3zOg?(deI@4LQv5<8vK^xFvT`X=!t>l_yv=i@FOar9jluX>ztJN*mc K%(@p~82|uIngv7v diff --git a/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0461/segmentations/aorta_small.nii.gz b/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0461/segmentations/aorta_small.nii.gz deleted file mode 100644 index 3133fe556f4ba462af71fb7ba24832108f949a7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159 zcmb2|=3oE;mj7=r+~;*L5IFGgFw?!>2cA|9T>fbrn2c{67IG61yBw$bU9|U3#<0L-~gh upLcz&FFt$q#j49;?Z2!amj_24PWp_43 diff --git a/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0461/segmentations/brain_small.nii.gz b/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0461/segmentations/brain_small.nii.gz deleted file mode 100644 index 3133fe556f4ba462af71fb7ba24832108f949a7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159 zcmb2|=3oE;mj7=r+~;*L5IFGgFw?!>2cA|9T>fbrn2c{67IG61yBw$bU9|U3#<0L-~gh upLcz&FFt$q#j49;?Z2!amj_24PWp_43 diff --git a/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0461/segmentations/colon_small.nii.gz b/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0461/segmentations/colon_small.nii.gz deleted file mode 100644 index 3133fe556f4ba462af71fb7ba24832108f949a7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159 zcmb2|=3oE;mj7=r+~;*L5IFGgFw?!>2cA|9T>fbrn2c{67IG61yBw$bU9|U3#<0L-~gh upLcz&FFt$q#j49;?Z2!amj_24PWp_43 diff --git a/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0461/segmentations/semantic_labels/masks.nii.gz b/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0461/segmentations/semantic_labels/masks.nii.gz deleted file mode 100644 index 05b28f407391fec935fe6d1f2ac66f82753a0bbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79 zcmb2|=3oE;mjB&}DGFQ$#vBZa#>~nhi?WiE9x!m5+%PtHa9Dbd;-?k6*!zyR9M76y ie}Qph5)V)1t+p4l+WIv75T>}&v1k_TwoRG1bu-6JOptix`z*#!4z6& zkFMc~IgPVU5#;ZPyM1_`$G$mnNBs6*u`k41&wJ^yo8MRm4)6^1&uHh|&OihoC59?WT7 z&lMA|y+_;UY(1F6mb%Aaz6JkYJMZ8=_>R6`hu`;0$gi-EQ0<-^iM!z62RgPc`?a8J z=sw=iCz!zkZg8c}0iD1BUZH<3r2FE1uFRRi0eWvG_ts!HPrvVR!GDD*ETH#jorJnA z{+@X&w0qK_Cs;$@`$1l~U(Vxvv321MI6G_vDH;U+VW>2jWIJL+d@zp3nFU zyZwz@u{W@Q_O;K=d}!ST?fuQ1?+o{_gWg|{UZMA+-}~3TBQD~-CUgTgSilzAXJgJ7 zdst`2euN|RbErgHUu}K45a%9wo({WxHuMUwoU27&pmSDe{r2~ra?W?>bI!f+{fu+3 z_S7@ZeB<25g}NKmenLmo>Cq+3p#AKlKH^W{1bdhT^jvrB&ad|S@x=MnE%DCl_W}2J z#2>>ts9$O4oc7DGkJuBqgDE`047wK+`VMDU!GwLN(e^p(hvxhFsJ`J}nR7*N&~qg|VFe%TU5&Qy0lmQtns46|=dgZ<_MLoZ=QTbf&U^Jf`oQje zm)PC!8hwKkJOtW%@Vy)SXQ1aH3?key*&p=!Y7g$5@%lH|)=bO3j9J~8? zKwGaR?hdlj zwEv%!p`B-AZtI@7PwQNWcdixP5%)m%@QmI4yju^tC+=OwzFhH7un)MR6FAT>QU^5u zg57!TqkqKjJ~`i=`u6LCInB?=x9)_V;T;-hJ>%VPN`aCSekX diff --git a/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0762/segmentations/aorta_small.nii.gz b/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0762/segmentations/aorta_small.nii.gz deleted file mode 100644 index 7b3f97302f378a2cdbe697aa8cf1c9f051f2eac6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmb2|=3oE;mj7=r?B;Dy5NLSlob+BU%unH>=^7oKxd+c3N|kABIr?O(#@xA`Kl?xI zV0QYUrn=tA`|)hCsnL}uJ33p}if`^fI}u=t^GXH^slo`R!Fb sAI#=^7oKxd+c3N|kABIr?O(#@xA`Kl?xI zV0QYUrn=tA`|)hCsnL}uJ33p}if`^fI}u=t^GXH^slo`R!Fb sAI#=^7oKxd+c3N|kABIr?O(#@xA`Kl?xI zV0QYUrn=tA`|)hCsnL}uJ33p}if`^fI}u=t^GXH^slo`R!Fb sAI#~nhi?WiE9x!m5+%PtHa9Dbd;-?k6*!zyR9M76y ie}Qph5)V)1t+p4l+ Date: Fri, 11 Apr 2025 14:04:43 +0200 Subject: [PATCH 09/15] add missing file --- .../Totalsegmentator_dataset_v201/s0011/ct.nii.gz | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/ct.nii.gz diff --git a/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/ct.nii.gz b/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/ct.nii.gz new file mode 100644 index 000000000..6f578d1f4 --- /dev/null +++ b/tests/eva/assets/vision/datasets/total_segmentator/Totalsegmentator_dataset_v201/s0011/ct.nii.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6767fb3a0e550f3b2e807ad79791a29b9115449f5d26ba656bc29522a19e2ca1 +size 1790 From 2c21855910c49357ace55b71d196f170a7513cfa Mon Sep 17 00:00:00 2001 From: Nicolas Kaenzig Date: Fri, 11 Apr 2025 14:18:17 +0200 Subject: [PATCH 10/15] fix config --- configs/vision/radiology/online/segmentation/btcv.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/configs/vision/radiology/online/segmentation/btcv.yaml b/configs/vision/radiology/online/segmentation/btcv.yaml index f78a72c97..c57d1b8a4 100644 --- a/configs/vision/radiology/online/segmentation/btcv.yaml +++ b/configs/vision/radiology/online/segmentation/btcv.yaml @@ -88,6 +88,7 @@ model: init_args: include_background: true num_classes: *NUM_CLASSES + input_format: one-hot reduction: none prefix: DiceScore_ labels: From 67a18b18a18315a23896775cd3375ee8ae30bf7d Mon Sep 17 00:00:00 2001 From: Nicolas Kaenzig Date: Fri, 11 Apr 2025 14:55:56 +0200 Subject: [PATCH 11/15] fix pos weight --- configs/vision/radiology/online/segmentation/btcv.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/vision/radiology/online/segmentation/btcv.yaml b/configs/vision/radiology/online/segmentation/btcv.yaml index c57d1b8a4..07416b166 100644 --- a/configs/vision/radiology/online/segmentation/btcv.yaml +++ b/configs/vision/radiology/online/segmentation/btcv.yaml @@ -139,7 +139,7 @@ data: init_args: spatial_size: *ROI_SIZE num_samples: ${oc.env:SAMPLE_BATCH_SIZE, 4} - pos: ${oc.env:RAND_CROP_POS_WEIGHT, 1} + pos: ${oc.env:RAND_CROP_POS_WEIGHT, 9} neg: ${oc.env:RAND_CROP_NEG_WEIGHT, 1} - class_path: eva.vision.data.transforms.RandFlip init_args: From 7698583a1f19677751373ccf5e3284927c34327e Mon Sep 17 00:00:00 2001 From: Nicolas Kaenzig Date: Mon, 14 Apr 2025 09:35:56 +0200 Subject: [PATCH 12/15] fixes in AsDiscrete --- src/eva/core/models/transforms/as_discrete.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/eva/core/models/transforms/as_discrete.py b/src/eva/core/models/transforms/as_discrete.py index c293f6227..a00d3728f 100644 --- a/src/eva/core/models/transforms/as_discrete.py +++ b/src/eva/core/models/transforms/as_discrete.py @@ -1,10 +1,11 @@ """Defines the AsDiscrete transformation.""" import torch -from monai.networks.utils import one_hot class AsDiscrete: + """Convert the logits tensor to discrete values.""" + def __init__( self, argmax: bool = False, @@ -28,13 +29,29 @@ def __init__( self._threshold = threshold def __call__(self, tensor: torch.Tensor) -> torch.Tensor: + """Call method for the transformation.""" if self._argmax: tensor = torch.argmax(tensor, dim=1, keepdim=True) if self._to_onehot is not None: - tensor = one_hot(tensor, num_classes=self._to_onehot, dim=1, dtype=torch.long) + tensor = _one_hot(tensor, num_classes=self._to_onehot, dim=1, dtype=torch.long) if self._threshold is not None: tensor = tensor >= self._threshold return tensor + + +def _one_hot( + tensor: torch.Tensor, num_classes: int, dtype: torch.dtype = torch.float, dim: int = 1 +) -> torch.Tensor: + """Convert input tensor into one-hot format (implementation taken from MONAI).""" + shape = list(tensor.shape) + if shape[dim] != 1: + raise AssertionError(f"Input tensor must have 1 channel at dim {dim}.") + + shape[dim] = num_classes + o = torch.zeros(size=shape, dtype=dtype, device=tensor.device) + tensor = o.scatter_(dim=dim, index=tensor.long(), value=1) + + return tensor From 4c9be3a32ca9176d924239549df2793b59dc0460 Mon Sep 17 00:00:00 2001 From: Nicolas Kaenzig Date: Mon, 14 Apr 2025 09:55:03 +0200 Subject: [PATCH 13/15] fix pyright --- src/eva/vision/models/modules/semantic_segmentation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/eva/vision/models/modules/semantic_segmentation.py b/src/eva/vision/models/modules/semantic_segmentation.py index f20af6ec8..ae0fec57d 100644 --- a/src/eva/vision/models/modules/semantic_segmentation.py +++ b/src/eva/vision/models/modules/semantic_segmentation.py @@ -133,7 +133,9 @@ def test_step(self, batch: INPUT_TENSOR_BATCH, *args: Any, **kwargs: Any) -> STE return self._batch_step(batch) @override - def predict_step(self, batch: INPUT_BATCH, *args: Any, **kwargs: Any) -> torch.Tensor: + def predict_step( + self, batch: INPUT_BATCH, *args: Any, **kwargs: Any + ) -> torch.Tensor | List[torch.Tensor]: tensor = INPUT_BATCH(*batch).data return self.encoder(tensor) if isinstance(self.encoder, nn.Module) else tensor From 8352abefe4dd0df7916b776f6e4e3fd5cc0d4e29 Mon Sep 17 00:00:00 2001 From: Nicolas Kaenzig Date: Mon, 14 Apr 2025 10:04:40 +0200 Subject: [PATCH 14/15] pyright fix --- configs/vision/radiology/online/segmentation/btcv.yaml | 4 ++++ src/eva/core/models/modules/head.py | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/configs/vision/radiology/online/segmentation/btcv.yaml b/configs/vision/radiology/online/segmentation/btcv.yaml index 07416b166..ac22d63be 100644 --- a/configs/vision/radiology/online/segmentation/btcv.yaml +++ b/configs/vision/radiology/online/segmentation/btcv.yaml @@ -115,6 +115,10 @@ data: init_args: &DATASET_ARGS root: ${oc.env:DATA_ROOT, ./data/btcv} split: train + download: ${oc.env:DOWNLOAD_DATA, false} + # Set `download: true` to download the dataset automatically + # The BTCV dataset is distributed under the CC BY 4.0 license + # (https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode) transforms: class_path: torchvision.transforms.v2.Compose init_args: diff --git a/src/eva/core/models/modules/head.py b/src/eva/core/models/modules/head.py index 4d3732ced..56a4b3b3c 100644 --- a/src/eva/core/models/modules/head.py +++ b/src/eva/core/models/modules/head.py @@ -1,6 +1,6 @@ """Neural Network Head Module.""" -from typing import Any, Callable, Dict +from typing import Any, Callable, Dict, List import torch from lightning.pytorch.cli import LRSchedulerCallable, OptimizerCallable @@ -108,7 +108,9 @@ def test_step(self, batch: INPUT_BATCH, *args: Any, **kwargs: Any) -> STEP_OUTPU return self._batch_step(batch) @override - def predict_step(self, batch: INPUT_BATCH, *args: Any, **kwargs: Any) -> torch.Tensor: + def predict_step( + self, batch: INPUT_BATCH, *args: Any, **kwargs: Any + ) -> torch.Tensor | List[torch.Tensor]: tensor = INPUT_BATCH(*batch).data return tensor if self.backbone is None else self.backbone(tensor) From 7978bd6c0e8b019e3ea7b229657b3c5b8e8154aa Mon Sep 17 00:00:00 2001 From: Nicolas Kaenzig Date: Mon, 14 Apr 2025 10:15:56 +0200 Subject: [PATCH 15/15] add docs --- docs/datasets/btcv.md | 59 ++++++++++++++++++++++++++++++++++++++++++ docs/datasets/index.md | 3 ++- docs/index.md | 5 ++-- mkdocs.yml | 1 + 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 docs/datasets/btcv.md diff --git a/docs/datasets/btcv.md b/docs/datasets/btcv.md new file mode 100644 index 000000000..556402fa1 --- /dev/null +++ b/docs/datasets/btcv.md @@ -0,0 +1,59 @@ +# Beyond the Cranial Vault (BTCV) Abdomen dataset. + +The BTCV dataset comprises abdominal CT scans acquired at the Vanderbilt University Medical Center from metastatic liver cancer patients or post-operative ventral hernia patients. + +The annotations cover segmentations of the spleen, right and left kidney, gallbladder, esophagus, liver, stomach, aorta, inferior vena cava, portal vein and splenic vein, pancreas, right adrenal gland, left adrenal gland are included in this data set. Images were manually labeled by two experienced undergraduate students, and verified by a radiologist. + + +## Raw data + +### Key stats + +| | | +|-----------------------|-----------------------------------------------------------| +| **Modality** | Vision (radiology, CT scans) | +| **Task** | Segmentation (14 classes) | +| **Image dimension** | 512 x 512 x ~140 (number of slices) | +| **Files format** | `.nii` ("NIFTI") images | +| **Number of scans** | 30 | +| **Splits in use** | train (80%) / val (20%) | + + +### Splits + +While the full dataset contains 90 CT scans, we use the train/val split from MONAI which uses a subset of 30 CT scans (https://github.com/Luffy03/Large-Scale-Medical/blob/main/Downstream/monai/BTCV/dataset/dataset_0.json): + +| Splits | Train | Validation | +|----------------|------------------|-------------------| +| #Scans | 24 (80%) | 6 (20%) | + + +### Organization + +The training data are organized as follows: + +``` +imagesTr +├── img0001.nii.gz +├── img0002.nii.gz +└── ... + +labelsTr +├── label0001.nii.gz +├── label0002.nii.gz +└── ... +``` + +## Download + +The `BTCV` dataset class supports downloading the data during runtime by setting the init argument `download=True`. + +## Relevant links + +* [zenodo download source](https://zenodo.org/records/1169361) +* [huggingface dataset](https://huggingface.co/datasets/Luffy503/VoCo_Downstream/blob/main/BTCV.zip) + + +## License + +[CC BY 4.0](https://creativecommons.org/licenses/by/4.0) diff --git a/docs/datasets/index.md b/docs/datasets/index.md index 97cb52f01..dcf47ba49 100644 --- a/docs/datasets/index.md +++ b/docs/datasets/index.md @@ -34,7 +34,8 @@ | Dataset | #Images | Image Size | Task | Download provided |---|---|---|---|---| +| [BTCV](btcv.md) | 30 | 512 x 512 x ~140 \* | Semantic Segmentation (14 classes) | Yes | | [TotalSegmentator](total_segmentator.md) | 1228 | ~300 x ~300 x ~350 \* | Semantic Segmentation (117 classes) | Yes | -| [LiTS](lits.md) | 131 (58638) | ~300 x ~300 x ~350 \* | Semantic Segmentation (2 classes) | No | +| [LiTS](lits.md) | 131 (58638) | ~300 x ~300 x ~350 \* | Semantic Segmentation (3 classes) | No | \* 3D images of varying sizes diff --git a/docs/index.md b/docs/index.md index 8b482fe6f..941e754dd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -61,8 +61,9 @@ Supported datasets & tasks include: *Radiology datasets* -- **[TotalSegmentator](datasets/total_segmentator.md)**: radiology/CT-scan for segmentation of anatomical structures -- **[LiTS](datasets/lits.md)**: radiology/CT-scan for segmentation of liver and tumor +- **[BTCV](datasets/btcv.md)**: Segmentation of abdominal organs (CT scans). +- **[TotalSegmentator](datasets/total_segmentator.md)**: Segmentation of anatomical structures (CT scans). +- **[LiTS](datasets/lits.md)**: Segmentation of liver and tumor (CT scans). To evaluate FMs, *eva* provides support for different model-formats, including models trained with PyTorch, models available on HuggingFace and ONNX-models. For other formats custom wrappers can be implemented. diff --git a/mkdocs.yml b/mkdocs.yml index 7458bcea3..700b82d56 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -99,6 +99,7 @@ nav: - PANDA: datasets/panda.md - PANDASmall: datasets/panda_small.md - Radiology: + - BTCV: datasets/btcv.md - TotalSegmentator: datasets/total_segmentator.md - LiTS: datasets/lits.md - Reference API: