From bb76a5cd1c9e19a01b8972da767d846df245fff3 Mon Sep 17 00:00:00 2001 From: Sathiesh Kaliyugarasan Date: Fri, 2 Oct 2026 14:19:34 +0200 Subject: [PATCH 1/5] Move VS training launchers into scripts --- .../notebooks/01_five_fold_cross_validation.ipynb | 2 +- research/vestibular_schwannoma/scripts/__init__.py | 1 + .../{ => scripts}/merge_parallel_folds.py | 2 +- .../vestibular_schwannoma/{ => scripts}/train_5fold.py | 10 +++++----- .../tests/workflow/test_merge_parallel_folds.py | 2 +- .../tests/workflow/test_train_5fold.py | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) create mode 100644 research/vestibular_schwannoma/scripts/__init__.py rename research/vestibular_schwannoma/{ => scripts}/merge_parallel_folds.py (97%) rename research/vestibular_schwannoma/{ => scripts}/train_5fold.py (97%) diff --git a/research/vestibular_schwannoma/notebooks/01_five_fold_cross_validation.ipynb b/research/vestibular_schwannoma/notebooks/01_five_fold_cross_validation.ipynb index 36ac684..749da74 100644 --- a/research/vestibular_schwannoma/notebooks/01_five_fold_cross_validation.ipynb +++ b/research/vestibular_schwannoma/notebooks/01_five_fold_cross_validation.ipynb @@ -85,7 +85,7 @@ "\n", "`training_seed` initializes training randomness before each independent run while retaining cuDNN performance optimizations. In an all-data run, every case remains in training and the first case by stable `case_id` order is duplicated only for fastai's validation phase. Its metric is an internal monitor, not held-out evaluation.\n", "\n", - "Every independently launched job must use a distinct, previously nonexistent `RESULTS_ROOT`. Re-run this configuration cell before starting another sweep. After parallel fold subsets finish (or one is interrupted after completing some folds), use `merge_parallel_folds.py` to validate and combine their `completed_run_ids.json` registries. The merger rejects different dataset, split, preprocessing, model, loss, or training contracts. A partial `completed_run_ids.json` registry cannot be used directly for inference." + "Every independently launched job must use a distinct, previously nonexistent `RESULTS_ROOT`. Re-run this configuration cell before starting another sweep. After parallel fold subsets finish (or one is interrupted after completing some folds), use `scripts/merge_parallel_folds.py` to validate and combine their `completed_run_ids.json` registries. The merger rejects different dataset, split, preprocessing, model, loss, or training contracts. A partial `completed_run_ids.json` registry cannot be used directly for inference." ] }, { diff --git a/research/vestibular_schwannoma/scripts/__init__.py b/research/vestibular_schwannoma/scripts/__init__.py new file mode 100644 index 0000000..33b299a --- /dev/null +++ b/research/vestibular_schwannoma/scripts/__init__.py @@ -0,0 +1 @@ +"""Command-line entry points for vestibular schwannoma research workflows.""" diff --git a/research/vestibular_schwannoma/merge_parallel_folds.py b/research/vestibular_schwannoma/scripts/merge_parallel_folds.py similarity index 97% rename from research/vestibular_schwannoma/merge_parallel_folds.py rename to research/vestibular_schwannoma/scripts/merge_parallel_folds.py index 11a533b..5d8931b 100644 --- a/research/vestibular_schwannoma/merge_parallel_folds.py +++ b/research/vestibular_schwannoma/scripts/merge_parallel_folds.py @@ -8,7 +8,7 @@ from pathlib import Path -PROJECT_ROOT = Path(__file__).resolve().parent +PROJECT_ROOT = Path(__file__).resolve().parents[1] RESEARCH_ROOT = PROJECT_ROOT.parent if str(RESEARCH_ROOT) not in sys.path: sys.path.insert(0, str(RESEARCH_ROOT)) diff --git a/research/vestibular_schwannoma/train_5fold.py b/research/vestibular_schwannoma/scripts/train_5fold.py similarity index 97% rename from research/vestibular_schwannoma/train_5fold.py rename to research/vestibular_schwannoma/scripts/train_5fold.py index d72758c..1627dd8 100644 --- a/research/vestibular_schwannoma/train_5fold.py +++ b/research/vestibular_schwannoma/scripts/train_5fold.py @@ -16,16 +16,16 @@ ------------------------------------------------ Inspect all options or run a short single-fold check:: - python train_5fold.py --help - python train_5fold.py --models unet --folds 1 --epochs 5 --no-compile + python scripts/train_5fold.py --help + python scripts/train_5fold.py --models unet --folds 1 --epochs 5 --no-compile Run one model across all five folds:: - python train_5fold.py --models unet + python scripts/train_5fold.py --models unet Run the default five folds and skip an optional model if it is not installed:: - python train_5fold.py --skip-unavailable + python scripts/train_5fold.py --skip-unavailable The defaults request three models, folds 1-5, and 500 epochs. Models and folds run sequentially so only one model occupies GPU memory at a time. Each held-out fold is @@ -64,7 +64,7 @@ from fastMONAI.vision_all import MedDataset, MedMask, ZNormalization, preprocess_dataset -PROJECT_ROOT = Path(__file__).resolve().parent +PROJECT_ROOT = Path(__file__).resolve().parents[1] RESEARCH_ROOT = PROJECT_ROOT.parent if str(RESEARCH_ROOT) not in sys.path: sys.path.insert(0, str(RESEARCH_ROOT)) diff --git a/research/vestibular_schwannoma/tests/workflow/test_merge_parallel_folds.py b/research/vestibular_schwannoma/tests/workflow/test_merge_parallel_folds.py index 9ae6348..f37df1b 100644 --- a/research/vestibular_schwannoma/tests/workflow/test_merge_parallel_folds.py +++ b/research/vestibular_schwannoma/tests/workflow/test_merge_parallel_folds.py @@ -5,7 +5,7 @@ from pathlib import Path from unittest.mock import patch -from vestibular_schwannoma import merge_parallel_folds +from vestibular_schwannoma.scripts import merge_parallel_folds class MergeParallelFoldsCliTests(unittest.TestCase): diff --git a/research/vestibular_schwannoma/tests/workflow/test_train_5fold.py b/research/vestibular_schwannoma/tests/workflow/test_train_5fold.py index 4b107f8..a7d4c48 100644 --- a/research/vestibular_schwannoma/tests/workflow/test_train_5fold.py +++ b/research/vestibular_schwannoma/tests/workflow/test_train_5fold.py @@ -1,6 +1,6 @@ import unittest -from vestibular_schwannoma import train_5fold +from vestibular_schwannoma.scripts import train_5fold class FiveFoldLauncherTests(unittest.TestCase): From ee185c228bb41310b80c3f0ef5a7419befdf4696 Mon Sep 17 00:00:00 2001 From: Sathiesh Kaliyugarasan Date: Fri, 2 Oct 2026 14:19:34 +0200 Subject: [PATCH 2/5] Add reproducible VS data preparation and replacement masks --- research/vestibular_schwannoma/.gitignore | 12 +- research/vestibular_schwannoma/README.md | 52 +- research/vestibular_schwannoma/data/README.md | 260 +- .../data/corrected_labels.json | 34 + .../data/reference_dataset.json | 28639 ++++++++++++++++ .../data/releases/LICENSE.txt | 11 + .../data/releases/README.md | 24 + .../data/releases/vs_corrected_labels_v1.zip | Bin 0 -> 573221 bytes .../data/requirements.txt | 9 + .../scripts/prepare_data.py | 350 + .../tests/workflow/test_data_conversion.py | 113 + .../tests/workflow/test_data_download.py | 461 + .../tests/workflow/test_data_idc.py | 154 + .../tests/workflow/test_data_preparation.py | 312 + .../workflow/data_conversion.py | 177 + .../workflow/data_download.py | 596 + .../workflow/data_idc.py | 129 + .../workflow/data_preparation.py | 666 + .../workflow/data_sources.py | 134 + 19 files changed, 32093 insertions(+), 40 deletions(-) create mode 100644 research/vestibular_schwannoma/data/corrected_labels.json create mode 100644 research/vestibular_schwannoma/data/reference_dataset.json create mode 100644 research/vestibular_schwannoma/data/releases/LICENSE.txt create mode 100644 research/vestibular_schwannoma/data/releases/README.md create mode 100644 research/vestibular_schwannoma/data/releases/vs_corrected_labels_v1.zip create mode 100644 research/vestibular_schwannoma/data/requirements.txt create mode 100644 research/vestibular_schwannoma/scripts/prepare_data.py create mode 100644 research/vestibular_schwannoma/tests/workflow/test_data_conversion.py create mode 100644 research/vestibular_schwannoma/tests/workflow/test_data_download.py create mode 100644 research/vestibular_schwannoma/tests/workflow/test_data_idc.py create mode 100644 research/vestibular_schwannoma/tests/workflow/test_data_preparation.py create mode 100644 research/vestibular_schwannoma/workflow/data_conversion.py create mode 100644 research/vestibular_schwannoma/workflow/data_download.py create mode 100644 research/vestibular_schwannoma/workflow/data_idc.py create mode 100644 research/vestibular_schwannoma/workflow/data_preparation.py create mode 100644 research/vestibular_schwannoma/workflow/data_sources.py diff --git a/research/vestibular_schwannoma/.gitignore b/research/vestibular_schwannoma/.gitignore index 6b606c4..a6f9f10 100644 --- a/research/vestibular_schwannoma/.gitignore +++ b/research/vestibular_schwannoma/.gitignore @@ -22,11 +22,18 @@ coverage.xml htmlcov/ # Private/local medical data. Keep only documentation, the reviewed research -# index, and synthetic examples. +# index, replacement-mask release, and synthetic examples. data/** !data/ !data/README.md +!data/releases/ +!data/releases/README.md +!data/releases/LICENSE.txt +!data/releases/vs_corrected_labels_v1.zip !data/ml_dataset.csv +!data/reference_dataset.json +!data/corrected_labels.json +!data/requirements.txt !data/*.example.csv raw_data/ nii_data/ @@ -83,3 +90,6 @@ tmp*/ # Private project planning docs/roadmap.html docs/component_postprocessing_review.html + +# Public, persistent mapping of deployed model hashes to REDCap instances +!deployment/pacs/redcap_model_instances.json diff --git a/research/vestibular_schwannoma/README.md b/research/vestibular_schwannoma/README.md index f4c8655..b0ea454 100644 --- a/research/vestibular_schwannoma/README.md +++ b/research/vestibular_schwannoma/README.md @@ -5,11 +5,12 @@ with five-fold cross-validation, optional all-data training, inference, and PACS ## Contents -- `train_5fold.py`: command-line five-fold training and evaluation. -- `merge_parallel_folds.py`: validate and combine parallel fold subsets for inference. -- `notebooks/`: cross-validation and inference workflows. +- `scripts/train_5fold.py`: command-line five-fold training and evaluation. +- `scripts/merge_parallel_folds.py`: validate and combine parallel fold subsets for inference. +- `notebooks/`: educational walkthroughs of cross-validation and inference. - `workflow/`: shared configuration, training, evaluation, and artifact handling. - `data/ml_dataset.csv`: public case index and fixed fold assignments. +- `scripts/prepare_data.py`: download, prepare, and verify the exact study dataset. - `deployment/pacs/`: Safetensors bundle builder and ROR/PACS container. - `tests/`: workflow and deployment tests. @@ -21,23 +22,27 @@ Use fastMONAI 0.10.1 or a matching development checkout. From the fastMONAI repo pip install -e '.[dev]' ``` -Images are not included. The CSV expects prepared data under `../nii_data/`; see -[data/README.md](data/README.md). UNet and DynUNet use MONAI. The training notebook provides +Download and prepare the 344-case dataset with `scripts/prepare_data.py`; see +[data/README.md](data/README.md) for scripted acquisition, corrected labels, and +reference verification. The default prepared layout is `../nii_data/`, matching +the shared CSV. UNet and DynUNet use MONAI. The training notebook provides setup instructions for optional SegMamba support. ## Training -Use the CLI for unattended training: +Use the CLI for unattended training. Run the commands below from +`research/vestibular_schwannoma/`; relative data and output paths resolve from that +project directory: ```bash -python train_5fold.py --models unet --folds 1 --epochs 5 --no-compile -python train_5fold.py --models unet # One model, all five folds -python train_5fold.py --models dynunet_small dynunet_xs -python train_5fold.py --skip-unavailable +python scripts/train_5fold.py --models unet --folds 1 --epochs 5 --no-compile +python scripts/train_5fold.py --models unet # One model, all five folds +python scripts/train_5fold.py --models dynunet_small dynunet_xs +python scripts/train_5fold.py --skip-unavailable ``` The default is three models, five folds, and 500 epochs. Models and folds run sequentially -within a launcher. Run `python train_5fold.py --help` for all options. +within a launcher. Run `python scripts/train_5fold.py --help` for all options. DynUNet variants use the same architecture, deep supervision, patch size, and training settings. They differ only in channel widths: the existing `dynunet` uses @@ -47,11 +52,11 @@ settings. They differ only in channel widths: the existing `dynunet` uses For one process per GPU, assign one visible GPU and a new `--results-root` to each process: ```bash -CUDA_VISIBLE_DEVICES=0 python train_5fold.py --models unet --folds 1 --results-root cv_results/unet_fold_1 & -CUDA_VISIBLE_DEVICES=1 python train_5fold.py --models unet --folds 2 --results-root cv_results/unet_fold_2 & -CUDA_VISIBLE_DEVICES=2 python train_5fold.py --models unet --folds 3 --results-root cv_results/unet_fold_3 & -CUDA_VISIBLE_DEVICES=3 python train_5fold.py --models unet --folds 4 --results-root cv_results/unet_fold_4 & -CUDA_VISIBLE_DEVICES=4 python train_5fold.py --models unet --folds 5 --results-root cv_results/unet_fold_5 & +CUDA_VISIBLE_DEVICES=0 python scripts/train_5fold.py --models unet --folds 1 --results-root cv_results/unet_fold_1 & +CUDA_VISIBLE_DEVICES=1 python scripts/train_5fold.py --models unet --folds 2 --results-root cv_results/unet_fold_2 & +CUDA_VISIBLE_DEVICES=2 python scripts/train_5fold.py --models unet --folds 3 --results-root cv_results/unet_fold_3 & +CUDA_VISIBLE_DEVICES=3 python scripts/train_5fold.py --models unet --folds 4 --results-root cv_results/unet_fold_4 & +CUDA_VISIBLE_DEVICES=4 python scripts/train_5fold.py --models unet --folds 5 --results-root cv_results/unet_fold_5 & wait ``` @@ -75,7 +80,7 @@ inference-ready. Merge disjoint registries into a new results root; the merger r 1-5, a matching training contract, distinct run IDs, no overlaps, and a new output root: ```bash -python merge_parallel_folds.py \ +python scripts/merge_parallel_folds.py \ cv_results/unet_fold_1/completed_run_ids.json \ cv_results/unet_fold_2/completed_run_ids.json \ cv_results/unet_fold_3/completed_run_ids.json \ @@ -89,12 +94,12 @@ Training and merging fail if their output directory already exists; they never o weights or manifests. For example, replace an existing `cv_results/unet_fold_1` like this: ```bash -python train_5fold.py \ +python scripts/train_5fold.py \ --models unet \ --folds 1 \ --results-root cv_results/unet_fold_1_retrained_20260902 -python merge_parallel_folds.py \ +python scripts/merge_parallel_folds.py \ cv_results/unet_fold_1_retrained_20260902/completed_run_ids.json \ cv_results/unet_fold_2/completed_run_ids.json \ cv_results/unet_fold_3/completed_run_ids.json \ @@ -108,11 +113,10 @@ Contracts must match. The original directory and MLflow run remain unchanged. ## Inference and artifacts -- Notebook 01 uses fixed folds and can train an all-data model. Its duplicated validation case - remains in training and is only an internal fastai monitor. -- Notebook 02 loads declared models from - `cv_results//inference_run_ids.json` and enforces their preprocessing and output - contracts. +- Cross-validation uses fixed folds. Optional all-data training keeps every case in training; + its duplicated validation case is only an internal fastai monitor. +- Inference loads declared models from `cv_results//inference_run_ids.json` and + enforces their preprocessing and output contracts. - Evaluation and inference use TTA by default and preserve every predicted region. Predictions require clinical review. - Fold checkpoints live under `//fold_/checkpoints/`. They support diff --git a/research/vestibular_schwannoma/data/README.md b/research/vestibular_schwannoma/data/README.md index 54122cc..9c3f5fa 100644 --- a/research/vestibular_schwannoma/data/README.md +++ b/research/vestibular_schwannoma/data/README.md @@ -1,20 +1,250 @@ -# Data layout +# Reproduce the study dataset -Images and masks are not included. Prepare the source datasets in the layout referenced -by `ml_dataset.csv`; a reproducible preprocessing workflow is still in development. +The shared `ml_dataset.csv` defines **344 native-space CE-T1 scans and tumour +masks**, their order, volumes, quartile labels, and fixed cross-validation folds. +`reference_dataset.json` fingerprints the current study data. Preparation must +match those voxel values and geometry before publishing an output directory. -Paths resolve from the `vestibular_schwannoma/` directory. The current index expects data -under `../nii_data/`. +| Source | Public cases | Study cases | Selection | +| --- | ---: | ---: | --- | +| Queen Square / TCIA Vestibular-Schwannoma-SEG, version 2 | 242 | 239 | Exclude `vs_gk_8`, `vs_gk_73`, and `vs_gk_131` | +| crossMoDA 2022 / Tilburg | 105 | 105 | `training_source`, `center == Tilburg`, CE-T1 only | -## Columns +`vs_gk_73` is a meningioma, as recorded in the historical combined-data notebook. +Cases 8 and 131 were removed following radiologist review in fastMONAI commit +`d8bd2de`; the specific findings are not recorded there. The eight other gaps in +Queen Square numbering are already absent from the public source. crossMoDA +London cases overlap Queen Square and are omitted. Target/validation images, +T2 images, cochlea labels, backup masks, HUS data, and other collections are +not included in the prepared study directory. -| Column | Role | -| --- | --- | -| `case_id` | Required pseudonymous identifier used in evaluation output. | -| `t1_img_path` | Required relative path to the CE-T1w image. | -| `t1_seg_path` | Required relative path to the reference mask. | -| `fold` | Required fixed cross-validation fold. | -| `volume_mm3` | Optional tumor volume derived from the reference mask. | -| `quartile_label` | Optional zero-based volume-quartile label. | +## Setup -Only `case_id`, both paths, and `fold` are required. +Run these commands from `research/vestibular_schwannoma/`. Python 3.10+ and a CPU +are sufficient; preparation does not import fastMONAI or PyTorch or require a GPU. + +```bash +pip install -r data/requirements.txt +python scripts/prepare_data.py --help +``` + +Allow roughly 80–100 GB free for downloads, extraction, and prepared copies. +Downloaded sources remain untouched. Existing output directories are refused; +use a new `--output` directory for another run. + +## 1. Download by script + +The default DICOM downloader is the pip package +[`idc-index`](https://github.com/ImagingDataCommons/idc-index), installed by +`data/requirements.txt`. The same TCIA collection is available in +[NCI Imaging Data Commons (IDC)](https://portal.imaging.datacommons.cancer.gov/collections/vestibular_schwannoma_seg/). +No Data Retriever installation, browser download, account, or cloud credentials +are needed. The tested package version is pinned to `0.12.5` with IDC v24 metadata. + +```bash +python scripts/prepare_data.py download +``` + +This downloads and checksum-verifies the approximately 7.8 GB +[crossMoDA 2022 training archive](https://zenodo.org/records/6504722), extracts it, +downloads the official TCIA manifest and support files, and downloads the **239 +Queen Square T1 MR series** from IDC (29,400 DICOM instances, approximately 15.5 GB). +The shared CSV selects cases; T2, radiation-therapy series, and the three excluded +cases are omitted from IDC downloads. Missing or ambiguous T1 series abort before +transferring data. Re-running resumes/reuses cached downloads. IDC's sync compares +existing files with remote sizes; preparation then checks exact voxel hashes and +geometry. `queen_square/idc_manifest.json` records the CSV checksum, IDC and package +versions, selected series UIDs, expected instance counts, and completion status. + +Sources can be acquired separately: + +```bash +python scripts/prepare_data.py download --dataset tilburg +python scripts/prepare_data.py download --dataset queen-square +``` + +Expected layout: + +```text +data/raw/ +├── queen_square/ +│ ├── assets/ # manifest, contours, matrices, mapping +│ ├── idc_manifest.json # selected T1 series and download provenance +│ └── dicom/VS-SEG-001//MR_/... +├── crossmoda2022/ +│ ├── crossmoda2022_training.zip +│ └── crossmoda2022_training/ +│ ├── infos_source_training.csv +│ ├── training_source/ +│ └── training_target/ # downloaded but not used +└── corrected_labels/ # extracted from the bundled 17-mask ZIP +``` + +The 17 replacement masks are included in this repository as +[`releases/vs_corrected_labels_v1.zip`](releases/vs_corrected_labels_v1.zip). +`download` checks and extracts this archive automatically; no separate hosting +account or download is needed. Use `download --dataset labels` to extract just +the masks. An alternative HTTPS location can be supplied with +`--corrected-label-url URL`; the archive must match `corrected_labels.json`. + +## Alternatives: TCIA Data Retriever CLI or graphical app + +To use the current [TCIA Data Retriever](https://github.com/TCIA/data-retriever) +binary instead of IDC: + +```bash +python scripts/prepare_data.py download --dataset queen-square \ + --tcia-backend retriever --retriever /path/to/TCIA_Data_Retriever +``` + +This invokes `--cli`, `-i`, `-o`, `--skip-existing`, +`--directory-mode descriptive`, and `--accept-data-policy` to download the full +collection manifest. Providing `--retriever` also selects this backend when +`--tcia-backend` is omitted. The older Java NBIA Data Retriever uses different CLI +flags and is not interchangeable with this executable. + +Download DICOM using the collection manifest and Data Retriever from the +[TCIA collection page](https://www.cancerimagingarchive.net/collection/vestibular-schwannoma-seg/). +Classic and descriptive directory names are supported. Download support files: + +```bash +python scripts/prepare_data.py download --dataset queen-square --assets-only +``` + +Supply the DICOM collection with `prepare --queen-square +/path/to/Vestibular-Schwannoma-SEG`. DICOM metadata selects the T1 MR series; +radiation-therapy files are not used as MR images. + +## 2. Prepare and verify + +For a fresh checkout without an existing `../nii_data/` directory: + +```bash +python scripts/prepare_data.py prepare +python scripts/prepare_data.py verify +``` + +Default output is `../nii_data/`, matching the shared CSV and training notebook. +Alternate output writes its own CSV with updated paths and unchanged case order, +folds, volumes, and quartiles: + +```bash +python scripts/prepare_data.py prepare --output data/prepared +python scripts/prepare_data.py verify --data-root data/prepared +python scripts/train_5fold.py --data-csv data/prepared/ml_dataset.csv --models unet +``` + +CLI paths resolve relative to this project, even when invoked from elsewhere. +The bundled masks are used automatically if they have not already been extracted. +`--workers` controls CPU parallelism; default 2. To reuse the existing VS_Seg data: + +```bash +python scripts/prepare_data.py prepare \ + --from-existing /home/sathiesh/ml_projects/VS_Seg \ + --output data/prepared +``` + +The shortcut uses that checkout's `nii_data/queen_square_data`, `crossmoda_data`, +and `errors_fixed_nl`. Individual `--queen-square-nifti`, `--crossmoda`, and +`--corrected-labels` options accept equivalent data elsewhere. + +Preparation selects only shared CSV cases, converts native Queen Square T1 DICOM, +applies 17 NL masks, retains Tilburg tumour label 1, and removes only these +verified six-neighbour components: + +| Case | Kept voxels | Removed voxels | +| --- | ---: | ---: | +| `vs_gk_12` | 6685 | 79 | +| `vs_gk_13` | 2927 | 10 | +| `vs_gk_53` | 2756 | 52 | +| `vs_gk_86` | 3336 | 55 | +| `vs_gk_87` | 4144 | 17 | +| `vs_gk_250` | 24946 | 1 | +| `crossmoda2022_etz_38` | 3616 | 1 | +| `crossmoda2022_etz_67` | 3568 | 1 | + +Unexpected signatures fail. Other masks retain every tumour region. The final +inventory is **344 images + 344 masks**, a manifest, and a generated CSV. Each +pair must match reference voxel fingerprints, geometry, and CSV volume. Missing +corrections or mismatched data abort and remove staged partial output. + +Queen Square rasterization preserves the historical SimpleITK conversion, +rounded contour coordinates, tumour-name selection, and polygon filling used +in this study. It is not a general-purpose converter. Substituting Slicer or +changing rasterization requires a new dataset version and reference. Acquisition +does not register or resample; training preprocessing remains separate. + +Fingerprints hash C-order uint16 images and uint8 integer masks. Compression and +mask storage dtype do not affect them; affine and spacing are checked separately. +The historical encoding decodes label 1 as `0.9999999997671694` in 103 Tilburg +masks. Integer rounding within `1e-6` accounts for this encoding; actual fractional +labels are rejected. + +## Sharing the 17 replacement masks + +Correction cases: **4, 5, 29, 35, 43, 44, 45, 47, 55, 56, 61, 63, 71, 74, 76, +96, 119**. These are new NL masks drawn for scans whose original tumour +annotations contained errors. + +Contributors: **Njål Lura** and **Satheshkumar Kaliyugarassan**. +The release uses **CC BY 4.0** and asks readers to cite the accompanying study +paper when available. + +The [`releases/`](releases/) folder contains one ZIP, +a [short README](releases/README.md), and a [license notice](releases/LICENSE.txt). +These small masks are versioned with the preparation code on GitHub. Model +weights are distributed separately on Hugging Face. Use a tagged repository +release to identify the code, case index, and masks used for a study. + +Maintainer packaging command: + +```bash +python scripts/prepare_data.py bundle-labels --data-root ../nii_data +``` + +The ZIP contains 17 masks, a TCIA case mapping, provenance manifest, README, +and license. Fresh NIfTI headers omit annotation-editor text and extensions +while preserving geometry and tumour voxels. Existing bundles and asset metadata +are refused. If the archive changes, update its checksum and size in +`corrected_labels.json`. + +Original Queen Square data are +[CC BY 4.0](https://wiki.cancerimagingarchive.net/pages/viewpage.action?pageId=70229053); +crossMoDA data are [CC BY-NC-SA 4.0](https://zenodo.org/records/6504722). Their +licenses are separate from the Apache-2.0 software license. This release contains +new tumour masks for Queen Square images. + +## Maintaining the reference + +The reference was frozen from the current dataset. Do not regenerate it to make +mismatched preparation pass. Fingerprint an explicitly reviewed future version +using a new path: + +```bash +python scripts/prepare_data.py freeze-reference \ + --data-root /path/to/reviewed/data --output data/reference_dataset_v2.json +``` + +Audit indexed pairs in mixed legacy storage with `verify --data-root /path/to/data +--allow-extra-files`. Normal preparation/verification enforces exact inventory. + +Download helpers were adapted from `VS_Seg/preprocess_data`; conversion reproduces +`VS_Seg/preprocessing/simple_data_conversion.py` (Apache-2.0). +Native preparation from the original local TCIA/crossMoDA downloads was checked +against all 344 reference pairs, including the 17-label ZIP and all eight +component cleanups. Standalone verification also checked the exact 688-file +inventory and the generated CSV's unchanged folds and metadata. + +The pip-installed IDC route (`idc-index==0.12.5`, IDC v24) was tested with live +downloads of cases 1, 4, and 12: 360 DICOM instances, approximately 190 MB. All +three converted images and final masks matched the frozen reference's voxel +hashes, geometry, and CSV volumes, including case 4's human correction and case +12's 79-voxel cleanup. Removing a downloaded test slice and rerunning restored +it with the same SHA-256. IDC metadata covered all 239 study T1 series; the full +15.5 GB cohort was not redownloaded for this check. + +```bash +python -m pytest -q tests/workflow/test_data_preparation.py \ + tests/workflow/test_data_download.py tests/workflow/test_data_conversion.py \ + tests/workflow/test_data_idc.py +``` diff --git a/research/vestibular_schwannoma/data/corrected_labels.json b/research/vestibular_schwannoma/data/corrected_labels.json new file mode 100644 index 0000000..74aba9f --- /dev/null +++ b/research/vestibular_schwannoma/data/corrected_labels.json @@ -0,0 +1,34 @@ +{ + "cases": [ + "vs_gk_4", + "vs_gk_5", + "vs_gk_29", + "vs_gk_35", + "vs_gk_43", + "vs_gk_44", + "vs_gk_45", + "vs_gk_47", + "vs_gk_55", + "vs_gk_56", + "vs_gk_61", + "vs_gk_63", + "vs_gk_71", + "vs_gk_74", + "vs_gk_76", + "vs_gk_96", + "vs_gk_119" + ], + "contributors": [ + "Nj\u00e5l Lura", + "Satheshkumar Kaliyugarassan" + ], + "doi": null, + "filename": "vs_corrected_labels_v1.zip", + "release_license": "CC BY 4.0", + "release_license_url": "https://creativecommons.org/licenses/by/4.0/", + "schema": "vs-corrected-label-asset-v1", + "sha256": "dcfe4b0c645b69c4ede4b34fd6942731f35c90488ac63364555a218dd6ee58ba", + "size_bytes": 573221, + "url": null, + "version": 1 +} diff --git a/research/vestibular_schwannoma/data/reference_dataset.json b/research/vestibular_schwannoma/data/reference_dataset.json new file mode 100644 index 0000000..f1d9dd0 --- /dev/null +++ b/research/vestibular_schwannoma/data/reference_dataset.json @@ -0,0 +1,28639 @@ +{ + "canonical_voxels": { + "image": "little-endian uint16, C order", + "mask": "integer labels (quantization tolerance 1e-6), uint8, C order" + }, + "cases": [ + { + "case_id": "vs_gk_193", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.6737289428711 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -82.5914077758789 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "5e472db7283340d3259e77e3e29f0bb3a6188e67c99e0d163deb655feb570090" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.6737289428711 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -82.5914077758789 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 17290, + "volume_mm3": 4362.997055053711, + "voxel_sha256": "c4fa0006cb33692e634355522966c4ac1569bcf8a4b2c24a044c307b4ed3ac6e" + } + }, + { + "case_id": "vs_gk_115", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.42625427246094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.4017333984375 + ], + [ + 0.0, + 0.0, + 1.5, + -76.82969665527344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "87ebb332a19658d6ff047b4e977ed5fecebeca06a2718ac7130bd50993a05fd7" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.42625427246094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.4017333984375 + ], + [ + 0.0, + 0.0, + 1.5, + -76.82969665527344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5909, + "volume_mm3": 1491.090202331543, + "voxel_sha256": "a4660e0fe62898bbf83ea7f79adb9d69f16fe45f51f2b5717d35557407402180" + } + }, + { + "case_id": "vs_gk_43", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.22018432617188 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 143.9906768798828 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "af4234f5dbe9bd6f93a8abebdbd14a61dcc2126ac22cc818a60b5b562a177544" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.22018432617188 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 143.9906768798828 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1304, + "volume_mm3": 329.05426025390625, + "voxel_sha256": "ba97b8ff3337ab81494930c2f1c329031557d102849526277ffad2d9281294b6" + } + }, + { + "case_id": "vs_gk_213", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.71170806884766 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.63078308105469 + ], + [ + 0.0, + 0.0, + 1.5, + -75.33110809326172 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "566f48039fa2ab74b15c6ccd550690be9f0f6433a23eadb2dabd0b4ba582b5c7" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.71170806884766 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.63078308105469 + ], + [ + 0.0, + 0.0, + 1.5, + -75.33110809326172 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1937, + "volume_mm3": 488.7868881225586, + "voxel_sha256": "99cc0196e800c529ae47bf506fdfe6cc3cea1f0b2fd8e6d66fe58235f01479ff" + } + }, + { + "case_id": "vs_gk_87", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.6319580078125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -88.03934478759766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "fc7ace05edfa4c6277a52981494a6bdf41f6756b4722615f6c0a944860d32fee" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.6319580078125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -88.03934478759766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 4144, + "volume_mm3": 1045.7061767578125, + "voxel_sha256": "d4763aa4551d13f2b41311cace2c397e6d821c344a339bbe25125a56a70cd693" + } + }, + { + "case_id": "vs_gk_53", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.09684753417969 + ], + [ + 0.0, + 0.0, + 1.0, + -55.28692626953125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "166599c0474b2204ddcb58b07aca64942c7f235b4e3d6915c765fbdc8537f45e" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.09684753417969 + ], + [ + 0.0, + 0.0, + 1.0, + -55.28692626953125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 2756, + "volume_mm3": 463.63677978515625, + "voxel_sha256": "03bce86d1b1145c2401d067f82f6f989f9f4832f17ed34a8841b8bf56b76c108" + } + }, + { + "case_id": "vs_gk_183", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 117.9417495727539 + ], + [ + 0.0, + 0.0, + 1.5, + -88.43867492675781 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "978685dce9eb597c80501f60a2d93d208f253123484d5329a83d16ef7ec4d915" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 117.9417495727539 + ], + [ + 0.0, + 0.0, + 1.5, + -88.43867492675781 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1651, + "volume_mm3": 416.6170120239258, + "voxel_sha256": "6153b0c4d30170b081200ac587c274383a4e762737af7283390b6e67b882c0c0" + } + }, + { + "case_id": "vs_gk_201", + "image": { + "affine": [ + [ + -0.41015625, + 2.0085350483506348e-12, + 0.0, + 106.61001586914062 + ], + [ + -2.0085350483506348e-12, + -0.41015625, + 0.0, + 113.22930145263672 + ], + [ + 0.0, + 0.0, + 1.5, + -87.63998413085938 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "aec447299ac2997425db3741ae52a983c3f1371fd5c561a76973a338a9fac273" + }, + "mask": { + "affine": [ + [ + -0.41015625, + 2.0085350483506348e-12, + 0.0, + 106.61001586914062 + ], + [ + -2.0085350483506348e-12, + -0.41015625, + 0.0, + 113.22930145263672 + ], + [ + 0.0, + 0.0, + 1.5, + -87.63998413085938 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3747, + "volume_mm3": 945.5263137817383, + "voxel_sha256": "d6d6927f8429a025357cdcf4c6cfec11df226f282cf687ae3fd4433fd57cdb42" + } + }, + { + "case_id": "vs_gk_148", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.17689514160156 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 108.3755874633789 + ], + [ + 0.0, + 0.0, + 1.5, + -81.95758819580078 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "da96a9bb0e8315b51e96d567bb641c0067d6959bc6ea60272c53070a8cadf61a" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.17689514160156 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 108.3755874633789 + ], + [ + 0.0, + 0.0, + 1.5, + -81.95758819580078 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1828, + "volume_mm3": 461.2815856933594, + "voxel_sha256": "b73e9c6b003bd3930fed2e23b2acf1ef1842d4248caf06294ba245307c86ad19" + } + }, + { + "case_id": "vs_gk_5", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.6053237915039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.09684753417969 + ], + [ + 0.0, + 0.0, + 1.5, + -83.80205535888672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "702aa38a1374dc46f975bedfbca8e931410cea599852d402a9837fa4ececa51f" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.6053237915039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.09684753417969 + ], + [ + 0.0, + 0.0, + 1.5, + -83.80205535888672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 8228, + "volume_mm3": 2076.2718200683594, + "voxel_sha256": "6c4c2c6cc24489fc471445c5f695197070246ec4d9fe4b4978c4e284862d5359" + } + }, + { + "case_id": "vs_gk_109", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.28974914550781 + ], + [ + 0.0, + 0.0, + 1.5, + -80.64090728759766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "58b147752e41c926f5bd08491b6c588acd8d30bf0c81b3b0426c4623d6486618" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.28974914550781 + ], + [ + 0.0, + 0.0, + 1.5, + -80.64090728759766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 18294, + "volume_mm3": 4616.348648071289, + "voxel_sha256": "89acc9fbe890c87b65450369420c11ef4e3b649eb978e910b53a34d7325ced5c" + } + }, + { + "case_id": "vs_gk_161", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.33877563476562 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 135.3874053955078 + ], + [ + 0.0, + 0.0, + 1.5, + -97.34674072265625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "1e00fc1f6bbf3c2d40129fe08f3a8ae4a7d8812423f36d6c14b5eee3dac5a380" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.33877563476562 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 135.3874053955078 + ], + [ + 0.0, + 0.0, + 1.5, + -97.34674072265625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 4374, + "volume_mm3": 1103.744888305664, + "voxel_sha256": "0a7226384095d5f5b12ebf034d40ace07b469abd22dc0e304cf1c4fcb0a6766c" + } + }, + { + "case_id": "vs_gk_129", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.55847930908203 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.4823455810547 + ], + [ + 0.0, + 0.0, + 1.5, + -78.43858337402344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "276ba45cf11254497e9b73fa72f12eb5b786ace6e1fe47386019d34f05d3d6e5" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.55847930908203 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.4823455810547 + ], + [ + 0.0, + 0.0, + 1.5, + -78.43858337402344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3379, + "volume_mm3": 852.6643753051758, + "voxel_sha256": "7078c110f455b5b80bd564e08fde59da80ebe40ad752717ee6d4dc22e2f59ecd" + } + }, + { + "case_id": "vs_gk_78", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.6319580078125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.88619995117188 + ], + [ + 0.0, + 0.0, + 1.5, + -97.72457885742188 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "ce4b15ba8781d706f66637b04f4a997fd887a27b7874d5515c0546fa40e44346" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.6319580078125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.88619995117188 + ], + [ + 0.0, + 0.0, + 1.5, + -97.72457885742188 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 15947, + "volume_mm3": 4024.101448059082, + "voxel_sha256": "5c6cac2ab1aa8f69f87eb2dc7700b776d745cc938af0e55bc05882edf8b15eaf" + } + }, + { + "case_id": "vs_gk_199", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.31472778320312 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.64567565917969 + ], + [ + 0.0, + 0.0, + 1.5, + -79.3058090209961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "830a0939b433297f225fc3f9d1a6c5a082598fef37568616cf1b0c18e64ecd5e" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.31472778320312 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.64567565917969 + ], + [ + 0.0, + 0.0, + 1.5, + -79.3058090209961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 15550, + "volume_mm3": 3923.921585083008, + "voxel_sha256": "d555e9769b5bb16805dfe1573c8f1ecfc0938a40a3fc85d2045fe059b64edfe2" + } + }, + { + "case_id": "vs_gk_164", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.0266342163086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 134.3731231689453 + ], + [ + 0.0, + 0.0, + 1.5, + -90.91181182861328 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "3ee802c08d83fafc08583a45a69ac177b9ab41311e7189e97c7bc7bbb1a4168b" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.0266342163086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 134.3731231689453 + ], + [ + 0.0, + 0.0, + 1.5, + -90.91181182861328 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 19154, + "volume_mm3": 4833.36296081543, + "voxel_sha256": "28db1b51979ea5ba94ddc7606a6ec0797ae83b679b7b9bf766729ffa8f469911" + } + }, + { + "case_id": "vs_gk_91", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.91283416748047 + ], + [ + 0.0, + 0.0, + 1.5, + -89.8553237915039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "f9d099e342e97409c9916b08da8f655a588af2c8ca93d9e26e232703a3703b83" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.91283416748047 + ], + [ + 0.0, + 0.0, + 1.5, + -89.8553237915039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 6930, + "volume_mm3": 1748.7316131591797, + "voxel_sha256": "2cbbc114263c17d09c3093ea47c7e942fa3a1cee1b01ec9b36db7c3420563986" + } + }, + { + "case_id": "vs_gk_18", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.81597900390625 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.36077880859375 + ], + [ + 0.0, + 0.0, + 1.0, + -61.94552230834961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "57ec88d50805ca5b5362531c3e13cc57ba259cab755dfdaaad46ecd9416f1b08" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.81597900390625 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.36077880859375 + ], + [ + 0.0, + 0.0, + 1.0, + -61.94552230834961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 295, + "volume_mm3": 49.62730407714844, + "voxel_sha256": "cdb486fe5bc406cddeb7cb53f13c0841538bd36cdd34395781fd552d466c4407" + } + }, + { + "case_id": "vs_gk_61", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.3946762084961 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 143.25665283203125 + ], + [ + 0.0, + 0.0, + 1.5, + -76.53813171386719 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "472e5cb853e62ac436b05a2c08ee1cc793bee226f58184835ceba89a10c552bd" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.3946762084961 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 143.25665283203125 + ], + [ + 0.0, + 0.0, + 1.5, + -76.53813171386719 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2832, + "volume_mm3": 714.6331787109375, + "voxel_sha256": "ceef62edfdba0ddd296fc8824f7e7ef3c682e9485abb1e61c4b19acfe49a4971" + } + }, + { + "case_id": "crossmoda2022_etz_82", + "image": { + "affine": [ + [ + -0.8203023659930848, + 0.0, + -0.007455996765890988, + 104.25017547607422 + ], + [ + 0.0, + -0.8203125, + 0.0, + 111.88443756103516 + ], + [ + -0.004077499527548826, + 0.0, + 1.4999809924132306, + -44.0452880859375 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999995231628418 + ], + "voxel_sha256": "f9482c4095c84fc5e60d166e23fd68492abc59d77afe062ee6c8828a584795e6" + }, + "mask": { + "affine": [ + [ + -0.8203023076057434, + -0.0002430947351967916, + -0.0074559967033565044, + 104.25017547607422 + ], + [ + 0.0002430947351967916, + -0.8203124403953552, + 1.1047769703509402e-06, + 111.88443756103516 + ], + [ + -0.004077499266713858, + -6.041751134944207e-07, + 1.4999810457229614, + -44.0452880859375 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999995231628418 + ], + "tumour_voxels": 9822, + "volume_mm3": 9914.018149687035, + "voxel_sha256": "6b01938377a92437784590a8927061ea92ddf0009e0074ce22d642b380c31c38" + } + }, + { + "case_id": "vs_gk_177", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.26207733154297 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 117.9251708984375 + ], + [ + 0.0, + 0.0, + 1.5, + -81.87078094482422 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "67edadb77726ff8e0e1b0c013ec5965bc44fef9b973134eeaf9eac43c665a14c" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.26207733154297 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 117.9251708984375 + ], + [ + 0.0, + 0.0, + 1.5, + -81.87078094482422 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 13896, + "volume_mm3": 3506.5475463867188, + "voxel_sha256": "d59794a3ede7ef2d80e54c1711e0f73037c308461f9db1f3008768dd86aecc4a" + } + }, + { + "case_id": "vs_gk_125", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.21065521240234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.7225341796875 + ], + [ + 0.0, + 0.0, + 1.5, + -71.10273742675781 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "001e5f9ba00b445dd7726a067ed92977a2c76f574d2bd48599a38ae69e4a9d61" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.21065521240234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.7225341796875 + ], + [ + 0.0, + 0.0, + 1.5, + -71.10273742675781 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 10328, + "volume_mm3": 2606.1904907226562, + "voxel_sha256": "c65a271209039fb0ce9d32fb8760d61ceb9521bd571c792646d954ebd5ddcf4e" + } + }, + { + "case_id": "vs_gk_221", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.82393646240234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.22119903564453 + ], + [ + 0.0, + 0.0, + 1.5, + -76.42599487304688 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "236fbfe4c5017170221dfc31a2ae6c07b6e4b52e40b462628f86b8423613ad25" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.82393646240234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.22119903564453 + ], + [ + 0.0, + 0.0, + 1.5, + -76.42599487304688 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2469, + "volume_mm3": 623.0329513549805, + "voxel_sha256": "cac6e971fa3de751af1973e2552923a27dc17c027e63374fdb473d9c9d568bc9" + } + }, + { + "case_id": "vs_gk_71", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.78934478759766 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.49152374267578 + ], + [ + 0.0, + 0.0, + 1.5, + -71.69551849365234 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "03a7cf07f4ce65eae905a87bce377f94aaf4d90d356f0f9467c66164437f6021" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.78934478759766 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.49152374267578 + ], + [ + 0.0, + 0.0, + 1.5, + -71.69551849365234 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 8299, + "volume_mm3": 2094.188117980957, + "voxel_sha256": "b4a7e2168df3ebb3842c341b0d4e84e0a56c7a039661e94c7e5f67b93dceb5e1" + } + }, + { + "case_id": "vs_gk_33", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.18402099609375 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 137.20338439941406 + ], + [ + 0.0, + 0.0, + 1.5, + -88.03934478759766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "723622376523ce5c8afb0508d3cca7dcbe20d910b8fef59e7e4310be5287f6ff" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.18402099609375 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 137.20338439941406 + ], + [ + 0.0, + 0.0, + 1.5, + -88.03934478759766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 4821, + "volume_mm3": 1216.541862487793, + "voxel_sha256": "e95ea827d3be75b80784c5471fa5850fa9d32810a0e2dc063073e98ac52de471" + } + }, + { + "case_id": "crossmoda2022_etz_86", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.02671813964844 + ], + [ + 0.0, + -0.8203125, + 0.0, + 128.0367431640625 + ], + [ + 0.0, + 0.0, + 1.5, + -56.54960632324219 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "167be052679fbda4c9dfba23ed3b34527d80ccd9174dae6c16a2597b6cd74dae" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.02671813964844 + ], + [ + 0.0, + -0.8203125, + 0.0, + 128.0367431640625 + ], + [ + 0.0, + 0.0, + 1.5, + -56.54960632324219 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 2681, + "volume_mm3": 2706.1180114746094, + "voxel_sha256": "14947eed6585c679bd7cf822a91b9f1b21e3f0c7418d15e69ba238dcf14065a6" + } + }, + { + "case_id": "vs_gk_218", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.84261322021484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.1234893798828 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "2c15fd790b5aa046fb44a664826148b152e607057d3b0fa747e4c6be66e6f543" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.84261322021484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.1234893798828 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 11862, + "volume_mm3": 2993.283462524414, + "voxel_sha256": "3f576f56560feaa4814f3f9c0052898f0f4e0d3d51da7591f1f0da200d6be96c" + } + }, + { + "case_id": "vs_gk_86", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 101.3680419921875 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 136.5980682373047 + ], + [ + 0.0, + 0.0, + 1.0, + -64.36682891845703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "a211ddf7ffbf0fd3b36b9b4d9c25a11738226aed3dc09379a41cb8f2024d0c54" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 101.3680419921875 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 136.5980682373047 + ], + [ + 0.0, + 0.0, + 1.0, + -64.36682891845703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 3336, + "volume_mm3": 561.2091064453125, + "voxel_sha256": "ffc0664eb036f84f5073273f9f5806d091b8187de4cf3e4b062abc1c30631939" + } + }, + { + "case_id": "vs_gk_25", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.18402099609375 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -92.2766342163086 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "d7b8c1555e7bd933fbb8a9578beb287cb29815605d44d4c8b7ac4c0e50b6f067" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.18402099609375 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -92.2766342163086 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3956, + "volume_mm3": 998.2658386230469, + "voxel_sha256": "7e791ce484ef050cac0cd6b870e7f7c3a876aa648a2837f2b8c7d61c78941664" + } + }, + { + "case_id": "crossmoda2022_etz_56", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.5463638305664 + ], + [ + 0.0, + -0.8203125, + 0.0, + 122.85071563720703 + ], + [ + 0.0, + 0.0, + 1.5, + -53.706520080566406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "8895a49615269ee6eafaa6216aa28d35f9d09509fa5e4ac2c9b4a9bee737af07" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.5463638305664 + ], + [ + 0.0, + -0.8203125, + 0.0, + 122.85071563720703 + ], + [ + 0.0, + 0.0, + 1.5, + -53.706520080566406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 559, + "volume_mm3": 564.2372131347656, + "voxel_sha256": "364e364218ed53305f5437ec662a3400e518dde0dd092b8a9a97cb8cfd9f8721" + } + }, + { + "case_id": "vs_gk_171", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.50996398925781 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 121.55801391601562 + ], + [ + 0.0, + 0.0, + 1.5, + -78.07473754882812 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "681e0dcd3d2f480884a2187ee651e9db968c9558f739ee1c93b7db3197489bba" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.50996398925781 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 121.55801391601562 + ], + [ + 0.0, + 0.0, + 1.5, + -78.07473754882812 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 6015, + "volume_mm3": 1517.838478088379, + "voxel_sha256": "29619ea9140327482466bad2294872a7119281de29a40d053dfa2cb91692aac7" + } + }, + { + "case_id": "crossmoda2022_etz_51", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 98.93766784667969 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.80723571777344 + ], + [ + 0.0, + 0.0, + 1.5, + -52.40217590332031 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "199ba0e107b2803c2b4adda44a9fd4225101b0eaeef7c26907af0165936d5386" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 98.93766784667969 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.80723571777344 + ], + [ + 0.0, + 0.0, + 1.5, + -52.40217590332031 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 1169, + "volume_mm3": 1179.9522399902344, + "voxel_sha256": "f451eb3a07677770177634a0f670f569297b2d1d9f051ac812f9ee627684885f" + } + }, + { + "case_id": "vs_gk_95", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.64067077636719 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.2772216796875 + ], + [ + 0.0, + 0.0, + 1.5, + -80.41435241699219 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "a747ef2e6b0af90041c40fb487a3bcfe1a9156423a1aa21f4568cbc3e5517d37" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.64067077636719 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.2772216796875 + ], + [ + 0.0, + 0.0, + 1.5, + -80.41435241699219 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5781, + "volume_mm3": 1458.790397644043, + "voxel_sha256": "b29cb8ef835c467ef784931dbe0d4351201df61d784f3a2d5bfa933561f9597c" + } + }, + { + "case_id": "crossmoda2022_etz_104", + "image": { + "affine": [ + [ + -0.8203099574903563, + -0.002042375680369798, + 0.0, + 103.80521392822266 + ], + [ + 0.002042375680369798, + -0.8203099574903563, + 0.0, + 115.79468536376953 + ], + [ + 0.0, + 0.0, + 1.5, + -51.8602180480957 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "415f0d91509b4e3a04508f5612f991b81728bc776b1cb64f5d67f7ba0dd4c153" + }, + "mask": { + "affine": [ + [ + -0.8203099370002747, + -0.002042375737801194, + 0.0, + 103.80521392822266 + ], + [ + 0.002042375737801194, + -0.8203099370002747, + 0.0, + 115.79468536376953 + ], + [ + 0.0, + 0.0, + 1.5, + -51.8602180480957 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 717, + "volume_mm3": 723.7174987792969, + "voxel_sha256": "31b476a1fd1c1c98c7495af84a7a05d8694a43d5cd993bfdb46f58c30eeb6a5b" + } + }, + { + "case_id": "vs_gk_238", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.84261322021484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.88619995117188 + ], + [ + 0.0, + 0.0, + 1.5, + -86.82868957519531 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "49d89da5e4123e1582752fc58acb4e9fe93b45866cbc264918c8efc791fdf182" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.84261322021484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.88619995117188 + ], + [ + 0.0, + 0.0, + 1.5, + -86.82868957519531 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 4687, + "volume_mm3": 1182.7280044555664, + "voxel_sha256": "411c60ab39ef7d256de84189747c092b5b8b9d6945b916ec4c344e7fb03ef629" + } + }, + { + "case_id": "vs_gk_248", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.44599151611328 + ], + [ + 0.0, + 0.0, + 1.5, + -90.46065521240234 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "049b1dc74b036a9501c151b7603974081ef4d9ef448b44bda79221cd305e89fd" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.44599151611328 + ], + [ + 0.0, + 0.0, + 1.5, + -90.46065521240234 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 11185, + "volume_mm3": 2822.4477767944336, + "voxel_sha256": "cbd1cf2c8336e02825f4f2ec4caa22ea087391ad561fa24909490438c0f0d885" + } + }, + { + "case_id": "vs_gk_136", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.21932983398438 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.27467346191406 + ], + [ + 0.0, + 0.0, + 1.0, + -72.47393798828125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "50f3a55948a0fbf644767575bc7a1b39a73e0099749fb80116c965247c934c3e" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.21932983398438 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.27467346191406 + ], + [ + 0.0, + 0.0, + 1.0, + -72.47393798828125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 1707, + "volume_mm3": 287.1654510498047, + "voxel_sha256": "ed41327194e521840a5c831fd57aab034ceddbcb0110ecc6c734ba816d8f8ec1" + } + }, + { + "case_id": "vs_gk_187", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.25382995605469 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 113.89274597167969 + ], + [ + 0.0, + 0.0, + 1.5, + -90.87691497802734 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "fa5bc5c451e8f223484a2bd214cbcb93e94050acea650605a6d6cd31e0e99aaf" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.25382995605469 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 113.89274597167969 + ], + [ + 0.0, + 0.0, + 1.5, + -90.87691497802734 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 16410, + "volume_mm3": 4140.935897827148, + "voxel_sha256": "ff9f605691790118862ab2e0f0b2786c5166b9c0e91da9a7885601a385018ea0" + } + }, + { + "case_id": "vs_gk_19", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.6053237915039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.5447998046875 + ], + [ + 0.0, + 0.0, + 1.5, + -89.8553237915039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "eb1086d9f1fa20b1f2285c54105d56810108a11b33fa4d98138bf324173b55ec" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.6053237915039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.5447998046875 + ], + [ + 0.0, + 0.0, + 1.5, + -89.8553237915039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 27058, + "volume_mm3": 6827.875900268555, + "voxel_sha256": "bff1ee269414056c29a84066098ade160bf535fd0bee56a613f25bac94ed37fc" + } + }, + { + "case_id": "vs_gk_185", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.84219360351562 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 114.04306030273438 + ], + [ + 0.0, + 0.0, + 1.5, + -84.19683837890625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "9c1b224aee4052a5403addad9fdec81f52d9a3523d9e79a226cc6de896ffbdba" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.84219360351562 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 114.04306030273438 + ], + [ + 0.0, + 0.0, + 1.5, + -84.19683837890625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2824, + "volume_mm3": 712.6144409179688, + "voxel_sha256": "7d3e92b38895dfa2bdfb8b924397525d306664ffdc925bc1e69434e9311c37fa" + } + }, + { + "case_id": "vs_gk_123", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 113.79226684570312 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.53305053710938 + ], + [ + 0.0, + 0.0, + 1.5, + -84.12117767333984 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "dceb54a15fb1f94035c2076eef612a59f2baa08f21ce8b03cdba6770b736268b" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 113.79226684570312 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.53305053710938 + ], + [ + 0.0, + 0.0, + 1.5, + -84.12117767333984 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5232, + "volume_mm3": 1320.2545166015625, + "voxel_sha256": "6cfcba9e5b778518a6d12dbe04ab5a810756f9ec9aef58ee0b5feb63d6e10315" + } + }, + { + "case_id": "crossmoda2022_etz_76", + "image": { + "affine": [ + [ + -0.8203099574903563, + -0.002042375680369798, + 0.0, + 104.32624816894531 + ], + [ + 0.002042375680369798, + -0.8203099574903563, + 0.0, + 112.14738464355469 + ], + [ + 0.0, + 0.0, + 1.5, + -52.902313232421875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "8f9253e38c2aa292bf648057730131af895bb6dcfb8e7605a4c75dec304a277f" + }, + "mask": { + "affine": [ + [ + -0.8203099370002747, + -0.002042375737801194, + 0.0, + 104.32624816894531 + ], + [ + 0.002042375737801194, + -0.8203099370002747, + 0.0, + 112.14738464355469 + ], + [ + 0.0, + 0.0, + 1.5, + -52.902313232421875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 8238, + "volume_mm3": 8315.180969238281, + "voxel_sha256": "b9bdd87f97ac812c94f37009ea96674a6bcf1b294710dd94658021b97015eb48" + } + }, + { + "case_id": "vs_gk_103", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.60392761230469 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.77244567871094 + ], + [ + 0.0, + 0.0, + 1.5, + -83.94384765625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "ee2d99d7c35e7ef0b9a3a02bdafba4bff11d21d2ea01e7fe18be713c0d49858c" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.60392761230469 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.77244567871094 + ], + [ + 0.0, + 0.0, + 1.5, + -83.94384765625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2319, + "volume_mm3": 585.1816177368164, + "voxel_sha256": "374f3dc9af37fd8305255e501f28c22683c275d1e0b7487905bbf70d88fe995a" + } + }, + { + "case_id": "vs_gk_56", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.7288055419922 + ], + [ + 0.0, + 0.0, + 1.5, + -92.2766342163086 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "809cec8ff394873c1b61f0f0248737974794a4938b002267772ac80e29c92cc5" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.7288055419922 + ], + [ + 0.0, + 0.0, + 1.5, + -92.2766342163086 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 11070, + "volume_mm3": 2793.428421020508, + "voxel_sha256": "72aaad366bdfe62f1b75b447a31cd46a785b282518de47b24b22c4486785f902" + } + }, + { + "case_id": "crossmoda2022_etz_70", + "image": { + "affine": [ + [ + -0.8203024746580195, + 0.0, + 0.007415894062437737, + 104.40687561035156 + ], + [ + 0.0, + -0.8203125, + 0.0, + 115.53172302246094 + ], + [ + 0.004055579635427581, + 0.0, + 1.4999770188406194, + -101.87576293945312 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999953508377075 + ], + "voxel_sha256": "18839968224ffdd6799fe4dac2b511cb48767a5bb985b57ca9d656f39d121139" + }, + "mask": { + "affine": [ + [ + -0.8203024864196777, + 0.0, + 0.007415893953293562, + 104.40687561035156 + ], + [ + 0.0, + -0.8203125, + 0.0, + 115.53172302246094 + ], + [ + 0.004055579658597708, + 0.0, + 1.4999769926071167, + -101.87576293945312 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999953508377075 + ], + "tumour_voxels": 452, + "volume_mm3": 456.2333271380339, + "voxel_sha256": "ce633b0c5cad1dd40c14f73b16a108d497c9cf48e2886c66ddecc59ae8c5bb13" + } + }, + { + "case_id": "crossmoda2022_etz_78", + "image": { + "affine": [ + [ + -0.820305516731866, + 0.0, + 0.006189339450938122, + 105.30687713623047 + ], + [ + 0.0, + -0.8203125, + 0.0, + 132.8507080078125 + ], + [ + 0.003384800661227335, + 0.0, + 1.4999847272216429, + -65.87683868408203 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999974966049194 + ], + "voxel_sha256": "2b08c347386a093cb5594949424b933646371bf9c3a9e47ba808cb7b64f9e0dd" + }, + "mask": { + "affine": [ + [ + -0.8203054666519165, + -0.0003072029212489724, + 0.006189339328557253, + 105.30687713623047 + ], + [ + 0.0003072029212489724, + -0.8203124403953552, + -1.158943177870242e-06, + 132.8507080078125 + ], + [ + 0.0033848006278276443, + 6.33798151739029e-07, + 1.4999847412109375, + -65.87683868408203 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999974966049194 + ], + "tumour_voxels": 1483, + "volume_mm3": 1496.8915752748217, + "voxel_sha256": "7f5b9e300c8453d1b1be7b3f7cddb40eef6e7085b4deae3b4f170f28d4543650" + } + }, + { + "case_id": "crossmoda2022_etz_97", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 104.06879425048828 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.17902374267578 + ], + [ + 0.0, + 0.0, + 1.5, + -49.25501251220703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "903b5e60ce1c3fd9927ab48cb1dc1af083b4f17e82190fc5568b3aa68f476099" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 104.06879425048828 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.17902374267578 + ], + [ + 0.0, + 0.0, + 1.5, + -49.25501251220703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 6142, + "volume_mm3": 6199.543762207031, + "voxel_sha256": "351abbb1b7312178d0ed76ea5139e1a8644ebbf8479c80483ce03207b01544ad" + } + }, + { + "case_id": "vs_gk_172", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.6053237915039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.49152374267578 + ], + [ + 0.0, + 0.0, + 1.5, + -83.19673156738281 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "b256e6707c315a3f25ec602d2635fd247de0fea8aa29649b0ae80e8bc1535e66" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.6053237915039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.49152374267578 + ], + [ + 0.0, + 0.0, + 1.5, + -83.19673156738281 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 12925, + "volume_mm3": 3261.5232467651367, + "voxel_sha256": "9260b90dd66552d654147b86c329d54ebc9ac0f8bfcef09487e5649ec2063595" + } + }, + { + "case_id": "vs_gk_154", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.28086853027344 + ], + [ + 0.0, + 0.0, + 1.5, + -90.98819732666016 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "33b9c8b0c285f4f3c83489e0bf9282bf53974b64c60ef3f33d48bcc70788f5b7" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.28086853027344 + ], + [ + 0.0, + 0.0, + 1.5, + -90.98819732666016 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 7786, + "volume_mm3": 1964.736557006836, + "voxel_sha256": "a6376cf231354564506b1f365d18eaea935d289c65032a42f5a37619f5b28d3a" + } + }, + { + "case_id": "crossmoda2022_etz_102", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.98463439941406 + ], + [ + 0.0, + -0.8203125, + 0.0, + 123.34735870361328 + ], + [ + 0.0, + 0.0, + 1.5, + -55.50751495361328 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "8aae19195f3c747b25c617e5bc076813407e8cb9a06f4675fe41320ec7366dab" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.98463439941406 + ], + [ + 0.0, + -0.8203125, + 0.0, + 123.34735870361328 + ], + [ + 0.0, + 0.0, + 1.5, + -55.50751495361328 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 1311, + "volume_mm3": 1323.2826232910156, + "voxel_sha256": "0f9657e46eb3b9ff445a72ec8e7b2f3bc989b9a080e1f003069663f40ab6080e" + } + }, + { + "case_id": "vs_gk_50", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 112.26392364501953 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 140.83534240722656 + ], + [ + 0.0, + 0.0, + 1.5, + -91.06597900390625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "0adcb9e8c3caca0ea2f0e383cd203e4817ebdfd5e2518e5b6308d6f9707ad81d" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 112.26392364501953 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 140.83534240722656 + ], + [ + 0.0, + 0.0, + 1.5, + -91.06597900390625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 17909, + "volume_mm3": 4519.196891784668, + "voxel_sha256": "7659b8f3f9d2565f024634c6ee0ea6306a012833e803eafa8531ec29cd9bd1a9" + } + }, + { + "case_id": "vs_gk_24", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -89.8553237915039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "e9836d35a9f0787828011bebe72808fd6a748fea0a8d9d12f77ef084cee227b1" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -89.8553237915039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 14494, + "volume_mm3": 3657.448196411133, + "voxel_sha256": "7463d804bf01779a414669f99a50ce6180b022f5182319aa19cc7f7407276ec1" + } + }, + { + "case_id": "crossmoda2022_etz_49", + "image": { + "affine": [ + [ + -0.8203009622786714, + 0.0, + -0.007955629875124727, + 104.26223754882812 + ], + [ + 0.0, + -0.8203125, + 0.0, + 123.34736633300781 + ], + [ + -0.004350740274445406, + 0.0, + 1.4999771143382348, + -51.82599639892578 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999982118606567 + ], + "voxel_sha256": "56c359c9fb53c3c27ee2e920157ddf69cd781f596e1862b829b4ffc7152c7877" + }, + "mask": { + "affine": [ + [ + -0.8203009366989136, + -4.6976314479252324e-05, + -0.00795563030987978, + 104.26223754882812 + ], + [ + 4.6976314479252324e-05, + -0.8203125, + 2.2779659047955647e-07, + 123.34736633300781 + ], + [ + -0.00435074046254158, + -1.245764167379093e-07, + 1.4999771118164062, + -51.82599639892578 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999982118606567 + ], + "tumour_voxels": 2509, + "volume_mm3": 2532.5035422962173, + "voxel_sha256": "92154337198a09746a9da2bc1773b4112145c086e262c7122451d86b3a3787c2" + } + }, + { + "case_id": "vs_gk_100", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.20332336425781 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 117.86100006103516 + ], + [ + 0.0, + 0.0, + 1.5, + -75.77489471435547 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "7ee202f8b505203c9ef493116d6513c4899284dc786b5f519eebe9e453d30852" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.20332336425781 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 117.86100006103516 + ], + [ + 0.0, + 0.0, + 1.5, + -75.77489471435547 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5843, + "volume_mm3": 1474.4356155395508, + "voxel_sha256": "47c5f57de91e5dec984ba219505c20fd56e940cb7331784111e88a095b189f5a" + } + }, + { + "case_id": "crossmoda2022_etz_46", + "image": { + "affine": [ + [ + -0.820312499999912, + 0.0, + 6.948603612499565e-07, + 100.42147064208984 + ], + [ + 0.0, + -0.8203125, + 0.0, + 123.34735870361328 + ], + [ + 3.8000176005856994e-07, + 0.0, + 1.499999999999839, + -58.112770080566406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "397535f5f4eed23386295d22ed5a835b4a983c5452ac611fd2dbef058c6b8672" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 6.948603754608484e-07, + 100.42147064208984 + ], + [ + 0.0, + -0.8203125, + 0.0, + 123.34735870361328 + ], + [ + 3.800017509547615e-07, + 0.0, + 1.5, + -58.112770080566406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 1890, + "volume_mm3": 1907.7072143554688, + "voxel_sha256": "0d2029e07e7c5ae44cdeb45bffbc515724cfe9b703ea3522b46787eef7e531eb" + } + }, + { + "case_id": "vs_gk_241", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.58020782470703 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 121.02953338623047 + ], + [ + 0.0, + 0.0, + 1.5, + -109.86093139648438 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "9f777e5a1e614a1d124efcc6746ce243bf5ef8933e825c0a711f1e78fefe019e" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.58020782470703 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 121.02953338623047 + ], + [ + 0.0, + 0.0, + 1.5, + -109.86093139648438 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 16959, + "volume_mm3": 4279.471778869629, + "voxel_sha256": "d7c537e09a61c16e5b644484931a39b59c808c55251c0ada952f176b88bdea50" + } + }, + { + "case_id": "vs_gk_44", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.87525939941406 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 149.97918701171875 + ], + [ + 0.0, + 0.0, + 1.5, + -83.67826843261719 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "89b3376f8d9bea1c56acf9adee51cb1d9072f2f66e1cc4a81fac4947e72c0822" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.87525939941406 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 149.97918701171875 + ], + [ + 0.0, + 0.0, + 1.5, + -83.67826843261719 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 6432, + "volume_mm3": 1623.065185546875, + "voxel_sha256": "862ede319dcb0a0aa60f0a76817de53cb9b4b85b037abbf10e936ceb51e76fa1" + } + }, + { + "case_id": "crossmoda2022_etz_66", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 105.11090087890625 + ], + [ + 0.0, + -0.8203125, + 0.0, + 116.57382202148438 + ], + [ + 0.0, + 0.0, + 1.5, + -49.2550048828125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "9f4f241eca635c3108a78e069ebd54c477d2a8c68898a3f8f34bcaee3097c867" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 105.11090087890625 + ], + [ + 0.0, + -0.8203125, + 0.0, + 116.57382202148438 + ], + [ + 0.0, + 0.0, + 1.5, + -49.2550048828125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 4556, + "volume_mm3": 4598.6846923828125, + "voxel_sha256": "3547defdc6d41f6e064beebeb6fd8eec6e4567b27c5635cfad81579f653982ce" + } + }, + { + "case_id": "vs_gk_166", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 113.06169128417969 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 137.8192138671875 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "900da2a7d7d0f03f5e6627b033fe53f187fa8d5279aa672f33d43fbe6effbe75" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 113.06169128417969 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 137.8192138671875 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 8932, + "volume_mm3": 2253.9207458496094, + "voxel_sha256": "3291caf8df4bef70a4dc620d7d163bd7036a2ca60c03b96a01fb26239df70b23" + } + }, + { + "case_id": "crossmoda2022_etz_100", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 102.41593170166016 + ], + [ + 0.0, + -0.8203125, + 0.0, + 121.11157989501953 + ], + [ + 0.0, + 0.0, + 1.5, + -32.83695602416992 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "755353acd2decb4843fa9c009630f12d6b83d17761c700246ef23d7abbae1d57" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 102.41593170166016 + ], + [ + 0.0, + -0.8203125, + 0.0, + 121.11157989501953 + ], + [ + 0.0, + 0.0, + 1.5, + -32.83695602416992 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 370, + "volume_mm3": 373.46649169921875, + "voxel_sha256": "9573c49df2f3cc1443571155da5b4a5e4c0af517adda95aa2316cbbe4c2b878e" + } + }, + { + "case_id": "crossmoda2022_etz_41", + "image": { + "affine": [ + [ + -0.8203124999855472, + 0.0, + 8.9041432146712e-06, + 104.5896224975586 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.67679595947266 + ], + [ + 4.869453320523312e-06, + 0.0, + 1.499999999973572, + -41.53322982788086 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "a7b271be34e107625d148cb2a229bee633114d42fc818e6dfde2d1d42df68388" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 8.904143214749638e-06, + 104.5896224975586 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.67679595947266 + ], + [ + 4.869453277933644e-06, + 0.0, + 1.5, + -41.53322982788086 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 6534, + "volume_mm3": 6595.216369628906, + "voxel_sha256": "b967c602ccf0f803f1ec5f93950b285910a86e57c125cd86edee16cbfc49adff" + } + }, + { + "case_id": "vs_gk_250", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.0623321533203 + ], + [ + 0.0, + 0.0, + 1.5, + -86.47453308105469 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "4cc9ca516241e598d416efaab192f0cb07b099e68e2cff094a79ec5525d70337" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.0623321533203 + ], + [ + 0.0, + 0.0, + 1.5, + -86.47453308105469 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 24946, + "volume_mm3": 6294.929122924805, + "voxel_sha256": "d2b18ca5f93bce4bcae56fdfa0714a26acedf535c0938346d575a63fa346a019" + } + }, + { + "case_id": "crossmoda2022_etz_59", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.98462677001953 + ], + [ + 0.0, + -0.8203125, + 0.0, + 115.53173065185547 + ], + [ + 0.0, + 0.0, + 1.5, + -58.63376235961914 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "a9ef0b97c053e3a6cd06335c26e94a36f5466a489706af359d9211f37bd1f6b0" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.98462677001953 + ], + [ + 0.0, + -0.8203125, + 0.0, + 115.53173065185547 + ], + [ + 0.0, + 0.0, + 1.5, + -58.63376235961914 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 7169, + "volume_mm3": 7236.165618896484, + "voxel_sha256": "a446811799c0c13ddbdc4ec8a338f437360e0869eb2e0d79944cc2b1f012366f" + } + }, + { + "case_id": "vs_gk_116", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.41062927246094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.32613372802734 + ], + [ + 0.0, + 0.0, + 1.5, + -83.68720245361328 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "a342ed519bb71444a1e2781c5fa1911fa29be9efb9713725e0876a9ca29e1365" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.41062927246094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.32613372802734 + ], + [ + 0.0, + 0.0, + 1.5, + -83.68720245361328 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3920, + "volume_mm3": 989.1815185546875, + "voxel_sha256": "3d6642b2d42634ebfad415420fc35bc800b3fe3bbb9297b5a6d858cc401e1760" + } + }, + { + "case_id": "vs_gk_157", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.53441619873047 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.59575653076172 + ], + [ + 0.0, + 0.0, + 1.5, + -79.10356903076172 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "68875cb89b6c36bd1fec6dc21be9704dcb0d7b803820f82ee70293bbb28d3bf6" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.53441619873047 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.59575653076172 + ], + [ + 0.0, + 0.0, + 1.5, + -79.10356903076172 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 27629, + "volume_mm3": 6971.963310241699, + "voxel_sha256": "e1820ef24b0627633ec95f120ecece01f1bde82808a178a8c76fc374d6db5392" + } + }, + { + "case_id": "vs_gk_46", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.0266342163086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.5447998046875 + ], + [ + 0.0, + 0.0, + 1.5, + -88.6446762084961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "dd666bc104d15cdc00f7036555898c52b28daeb20861d0624c0c8573ad209446" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.0266342163086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.5447998046875 + ], + [ + 0.0, + 0.0, + 1.5, + -88.6446762084961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5676, + "volume_mm3": 1432.2944641113281, + "voxel_sha256": "906d35fb3945a2b229f740828d677a1b05db1a48618b44f21dbd61e73e595469" + } + }, + { + "case_id": "crossmoda2022_etz_16", + "image": { + "affine": [ + [ + -0.8203028795414105, + 0.0, + 0.0072646181587121715, + 103.88959503173828 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.17902374267578 + ], + [ + 0.003972841844342949, + 0.0, + 1.4999809778095956, + -38.8192024230957 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999985694885254 + ], + "voxel_sha256": "038d101369fd3edd5d287db4f0e55bb1919cb6a355fb420892540f58d802d818" + }, + "mask": { + "affine": [ + [ + -0.8203029036521912, + 0.0, + 0.007264618296176195, + 103.88959503173828 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.17902374267578 + ], + [ + 0.003972841892391443, + 0.0, + 1.4999809265136719, + -38.8192024230957 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999985694885254 + ], + "tumour_voxels": 1453, + "volume_mm3": 1466.6116079206404, + "voxel_sha256": "3f379332cf3e1e96a8c2fc1af9d6c50c0a5138860a6b297e4e0863733a1b849d" + } + }, + { + "case_id": "vs_gk_146", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.97461700439453 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.8785400390625 + ], + [ + 0.0, + 0.0, + 1.0, + -73.65231323242188 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "0d7c5e0fd97484a86a887483c8c22835f839ef362ba1cbc5e39a3f2e697872f5" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.97461700439453 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.8785400390625 + ], + [ + 0.0, + 0.0, + 1.0, + -73.65231323242188 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 5141, + "volume_mm3": 864.8609161376953, + "voxel_sha256": "af641584c4ae67dd0dd3c65fe78e00180eff6fbdf5242881608af98c08287748" + } + }, + { + "case_id": "vs_gk_152", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 111.05326843261719 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.28086853027344 + ], + [ + 0.0, + 0.0, + 1.5, + -93.48728942871094 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "a3038eef1acaa46ae77cc4c79a1ba2cd1b2543977754f08ebd3f2b70f72928c6" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 111.05326843261719 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.28086853027344 + ], + [ + 0.0, + 0.0, + 1.5, + -93.48728942871094 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 4079, + "volume_mm3": 1029.3039321899414, + "voxel_sha256": "2af65b2cc8679ff3b8316b6c9438ea93564e0715abd981f913be9c2851861889" + } + }, + { + "case_id": "vs_gk_107", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.61420440673828 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.1323699951172 + ], + [ + 0.0, + 0.0, + 1.5, + -87.63579559326172 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "3e63a1f22b2dedeaa8a875bda175d6ecb741f47b919435a30dab584fc9128e04" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.61420440673828 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.1323699951172 + ], + [ + 0.0, + 0.0, + 1.5, + -87.63579559326172 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 14511, + "volume_mm3": 3661.7380142211914, + "voxel_sha256": "e0630b136d411d4af47dddb857e87fe5ec80499575a7dd00fc415067023d437e" + } + }, + { + "case_id": "vs_gk_246", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.45914459228516 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.85684204101562 + ], + [ + 0.0, + 0.0, + 1.5, + -75.77489471435547 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "5e701f9813cca6929c4ddd5ba7c8f3d9c86d03e642907ce829ecdd4b4fb66398" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.45914459228516 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.85684204101562 + ], + [ + 0.0, + 0.0, + 1.5, + -75.77489471435547 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 23604, + "volume_mm3": 5956.285858154297, + "voxel_sha256": "a873710d808b624b2acfc018f98ebc491c525ecb7ae0d152f0625ce05a9c4a13" + } + }, + { + "case_id": "vs_gk_211", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.35291290283203 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.90641021728516 + ], + [ + 0.0, + 0.0, + 1.5, + -87.50883483886719 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "832f54994be8e27791f2ee434d160debd9847cbcb5c81cea85e0069fe941b0f0" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.35291290283203 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.90641021728516 + ], + [ + 0.0, + 0.0, + 1.5, + -87.50883483886719 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 4999, + "volume_mm3": 1261.4587783813477, + "voxel_sha256": "893724bbb8d1cc60c52cba23864a3eff1d8499c617255a9502d1ae468235dcfa" + } + }, + { + "case_id": "crossmoda2022_etz_33", + "image": { + "affine": [ + [ + -0.8203021553171116, + 0.0, + -0.0075331199712578, + 101.64684295654297 + ], + [ + 0.0, + -0.8203125, + 0.0, + 109.27922058105469 + ], + [ + -0.004119664834833701, + 0.0, + 1.499984779449806, + -59.67125701904297 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.500003695487976 + ], + "voxel_sha256": "e1bd7d70df0984b156c39c8fcc6a856bb8f1f12ebdf254cf8a452b85cfa46af5" + }, + "mask": { + "affine": [ + [ + -0.8203021287918091, + -0.0001852660352597013, + -0.0075331199914216995, + 101.64684295654297 + ], + [ + 0.0001852660352597013, + -0.8203125, + 8.506758604198694e-07, + 109.27922058105469 + ], + [ + -0.0041196648962795734, + -4.652122242987389e-07, + 1.4999847412109375, + -59.67125701904297 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.500003695487976 + ], + "tumour_voxels": 230, + "volume_mm3": 232.15541814170138, + "voxel_sha256": "eda320cc81a175d49049a6fa65953eba43f3758071377e0606694c2a8d3ae1ac" + } + }, + { + "case_id": "crossmoda2022_etz_42", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 99.37940216064453 + ], + [ + 0.0, + -0.8203125, + 0.0, + 131.16299438476562 + ], + [ + 0.0, + 0.0, + 1.5, + -49.77605438232422 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "d4251254376621ab1b652f17353e0f91c916aa0491b207960fdb79f59c8c215a" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 99.37940216064453 + ], + [ + 0.0, + -0.8203125, + 0.0, + 131.16299438476562 + ], + [ + 0.0, + 0.0, + 1.5, + -49.77605438232422 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 1274, + "volume_mm3": 1285.9359741210938, + "voxel_sha256": "42efd71f72e19a5dc5e584a512ea090e0ec5290c3824d15992f75e598683c17f" + } + }, + { + "case_id": "vs_gk_184", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.77139282226562 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 118.59600830078125 + ], + [ + 0.0, + 0.0, + 1.5, + -83.64869689941406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "0a9ae509f7e32de96915ad114aab73792c50ba0f01c7bac4259f7c338de06537" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.77139282226562 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 118.59600830078125 + ], + [ + 0.0, + 0.0, + 1.5, + -83.64869689941406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1967, + "volume_mm3": 496.3571548461914, + "voxel_sha256": "f73066d8a3343c0f02431df86ca715f82e8dc7815d449590060db06f6091a3ab" + } + }, + { + "case_id": "vs_gk_70", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 101.9733657836914 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -94.69794464111328 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "7c07ee25a997b802e57f3cf20b752d097083e62c5f5fc0508d374a955fc8cb03" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 101.9733657836914 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -94.69794464111328 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 10042, + "volume_mm3": 2534.0206146240234, + "voxel_sha256": "098b2e951a2af68c4864f21ec5596498fd985447ad9b99fcf17d9e4846fcbc62" + } + }, + { + "case_id": "vs_gk_215", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.42753601074219 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.197509765625 + ], + [ + 0.0, + 0.0, + 1.5, + -71.53986358642578 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "9992ac83df57e9ff6c2f95941e8982da14647ea1aea0351a0fa7a80fc778f074" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.42753601074219 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.197509765625 + ], + [ + 0.0, + 0.0, + 1.5, + -71.53986358642578 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3330, + "volume_mm3": 840.2996063232422, + "voxel_sha256": "40be6147d2e0642212ff8078ccdd9b58a9879e118bf39f90264534c51cd01460" + } + }, + { + "case_id": "crossmoda2022_etz_63", + "image": { + "affine": [ + [ + -0.8203054592541998, + 0.0, + -0.006214774460459368, + 103.0020751953125 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.67679595947266 + ], + [ + -0.003398701811913642, + 0.0, + 1.4999884367843241, + -60.664207458496094 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.500001311302185 + ], + "voxel_sha256": "c5a1c4aeb4105c3ea3adcfff3cd80576e83828b8ae230ea55607f2b1ed6c1e0a" + }, + "mask": { + "affine": [ + [ + -0.8203054666519165, + -8.621870620117988e-06, + -0.006214774679392576, + 103.0020751953125 + ], + [ + 8.621870620117988e-06, + -0.8203125, + 3.2660246063187515e-08, + 120.67679595947266 + ], + [ + -0.0033987017814069986, + -1.786105485734879e-08, + 1.4999884366989136, + -60.664207458496094 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.500001311302185 + ], + "tumour_voxels": 936, + "volume_mm3": 944.770113028062, + "voxel_sha256": "cd3e264323c083b319a34cdd28d550fee16bfb87a4290dfe694ee7c44b8851c4" + } + }, + { + "case_id": "vs_gk_204", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.0266342163086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.1234893798828 + ], + [ + 0.0, + 0.0, + 1.5, + -81.38075256347656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "09f62c9ac8fb9aa2183cdcb374fd4a887328f9f33c9576ee3bdd227e4db957ce" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.0266342163086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.1234893798828 + ], + [ + 0.0, + 0.0, + 1.5, + -81.38075256347656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3500, + "volume_mm3": 883.1977844238281, + "voxel_sha256": "e4fc52fdd7b4540d0be3c108306b0bff43767f0867e1979c7de8800e1c7775dd" + } + }, + { + "case_id": "crossmoda2022_etz_32", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 102.50568389892578 + ], + [ + 0.0, + -0.8203125, + 0.0, + 115.01067352294922 + ], + [ + 0.0, + 0.0, + 1.5, + -46.128753662109375 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "71511d2ce22fe70c8e2808c49adbae47c7afd1727c2d5828f31f076e776f80a0" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 102.50568389892578 + ], + [ + 0.0, + -0.8203125, + 0.0, + 115.01067352294922 + ], + [ + 0.0, + 0.0, + 1.5, + -46.128753662109375 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 933, + "volume_mm3": 941.7411804199219, + "voxel_sha256": "349baea8fc54f4d5ae1e37ea6ab9ead742f9a497d601029c54b8475c849ceef4" + } + }, + { + "case_id": "vs_gk_21", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.88304901123047 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.55397033691406 + ], + [ + 0.0, + 0.0, + 1.5, + -93.57456970214844 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "4a5c2400c617896d933111509417ea13ac4384f1a2fb2c8b80ce45ee20007ac3" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.88304901123047 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.55397033691406 + ], + [ + 0.0, + 0.0, + 1.5, + -93.57456970214844 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 16896, + "volume_mm3": 4263.57421875, + "voxel_sha256": "0844cf905362649267a77db284fe6cd3bea72317bb7fb35ad3190845fa517673" + } + }, + { + "case_id": "crossmoda2022_etz_35", + "image": { + "affine": [ + [ + -0.8203053192075486, + 0.0, + -0.006276279528177481, + 102.71965789794922 + ], + [ + 0.0, + -0.8203125, + 0.0, + 127.38835144042969 + ], + [ + -0.003432336820862966, + 0.0, + 1.4999884191152872, + -58.6969108581543 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000015497207642 + ], + "voxel_sha256": "6d3ec3085aaa907df96536062064d992410299000fb98f09da464c47961e07f0" + }, + "mask": { + "affine": [ + [ + -0.8203052878379822, + -0.0003019591385964304, + -0.006276279222220182, + 102.71965789794922 + ], + [ + 0.0003019591385964304, + -0.8203124403953552, + 1.1551624083949719e-06, + 127.38835144042969 + ], + [ + -0.0034323367290198803, + -6.317287670754013e-07, + 1.4999884366989136, + -58.6969108581543 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000015497207642 + ], + "tumour_voxels": 2640, + "volume_mm3": 2664.73663978104, + "voxel_sha256": "5ffa9155c0624da6c0a2b1be18c25e310cf5677ba5f86ba694bab3be533f0ecb" + } + }, + { + "case_id": "vs_gk_83", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 101.9733657836914 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.09684753417969 + ], + [ + 0.0, + 0.0, + 1.5, + -83.80205535888672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "1d595b661911a1aa46bfcda50cb13b5a674fc7263fc1aa1caa4ec644adb09ad9" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 101.9733657836914 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.09684753417969 + ], + [ + 0.0, + 0.0, + 1.5, + -83.80205535888672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 16111, + "volume_mm3": 4065.4855728149414, + "voxel_sha256": "d52a5167fd42068ee542a4f7943d0f8b5266fc609916b8e93c4b09d0aeed6004" + } + }, + { + "case_id": "crossmoda2022_etz_37", + "image": { + "affine": [ + [ + -0.8203124988863953, + 0.0, + -7.81595608183747e-05, + 105.11280059814453 + ], + [ + 0.0, + -0.8203125, + 0.0, + 123.86840057373047 + ], + [ + -4.274350982254866e-05, + 0.0, + 1.4999999979636944, + -62.796653747558594 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "14a43fa1ec7ef25b0346720a902cf11f493506cd427350b9d87acb435fd27119" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + -7.815955905243754e-05, + 105.11280059814453 + ], + [ + 0.0, + -0.8203125, + 0.0, + 123.86840057373047 + ], + [ + -4.274351158528589e-05, + 0.0, + 1.5, + -62.796653747558594 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 1286, + "volume_mm3": 1298.0484008789062, + "voxel_sha256": "8cc2f887ed4e114fd6fdd5097041d61ef498edb6e340a6c74c973c44a9b5fdeb" + } + }, + { + "case_id": "crossmoda2022_etz_60", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.98463439941406 + ], + [ + 0.0, + -0.8203125, + 0.0, + 121.78423309326172 + ], + [ + 0.0, + 0.0, + 1.5, + -63.84416961669922 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "f95012d4d1250e750c032b857452feca9de09fffa0db505073c8ddba7662ddcd" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.98463439941406 + ], + [ + 0.0, + -0.8203125, + 0.0, + 121.78423309326172 + ], + [ + 0.0, + 0.0, + 1.5, + -63.84416961669922 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 180, + "volume_mm3": 181.6864013671875, + "voxel_sha256": "f35f2d7ccd38d5898221c05492bbc1c06f7cc550aee39291d5dd9afdbf608f0a" + } + }, + { + "case_id": "vs_gk_181", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.5379867553711 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 117.31365966796875 + ], + [ + 0.0, + 0.0, + 1.5, + -82.88162994384766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "3f8b94642551ace1dc97fef67b86ad2834a8053ad2ffd1a0ccddc3e97793e5ba" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.5379867553711 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 117.31365966796875 + ], + [ + 0.0, + 0.0, + 1.5, + -82.88162994384766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 10154, + "volume_mm3": 2562.282943725586, + "voxel_sha256": "51818c6af7245f35c314bcef720ae8f8db42d87ed4b279353905b36d50382569" + } + }, + { + "case_id": "crossmoda2022_etz_7", + "image": { + "affine": [ + [ + -0.8203038923323104, + 0.0, + -0.006871607373338476, + 104.7571029663086 + ], + [ + 0.0, + -0.8203125, + 0.0, + 126.90327453613281 + ], + [ + -0.0037579090876873524, + 0.0, + 1.4999847370969508, + -40.15108871459961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000004768371582 + ], + "voxel_sha256": "f8a9ae3dc913bce3a4d46f7cbb9d5248790c2bd1b8de87695ee1c17e3869e0c2" + }, + "mask": { + "affine": [ + [ + -0.8203039169311523, + 0.0, + -0.006871607154607773, + 104.7571029663086 + ], + [ + 0.0, + -0.8203125, + 0.0, + 126.90327453613281 + ], + [ + -0.0037579091731458902, + 0.0, + 1.4999847412109375, + -40.15108871459961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000004768371582 + ], + "tumour_voxels": 7688, + "volume_mm3": 7760.030543018365, + "voxel_sha256": "48dad7bac8e6e92509af1807d85fb886ce50f043916c8c729162c22b0d370bd0" + } + }, + { + "case_id": "vs_gk_223", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.42823791503906 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 121.61552429199219 + ], + [ + 0.0, + 0.0, + 1.5, + -103.31478118896484 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "e7fa0bc0ed963b1c4abdaba0f7da5130347feb3db9299ace2e9375a53504d622" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.42823791503906 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 121.61552429199219 + ], + [ + 0.0, + 0.0, + 1.5, + -103.31478118896484 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 9199, + "volume_mm3": 2321.2961196899414, + "voxel_sha256": "baa875682a6df8829c014241b9f2a8fb6baf88c4b1959d2c36865ef105036d88" + } + }, + { + "case_id": "crossmoda2022_etz_6", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.53495025634766 + ], + [ + 0.0, + -0.8203125, + 0.0, + 121.3285140991211 + ], + [ + 0.0, + 0.0, + 1.5, + -71.410888671875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "e165bb92d7238fa5924c87f802f718ea4d59bd30e393c726b61913ab93f2883b" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.53495025634766 + ], + [ + 0.0, + -0.8203125, + 0.0, + 121.3285140991211 + ], + [ + 0.0, + 0.0, + 1.5, + -71.410888671875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 7827, + "volume_mm3": 7900.330352783203, + "voxel_sha256": "5ace99e7c6c56eada4b02e0fb15733c765d2b0eb8b20ce687e9aa4376d37d6a7" + } + }, + { + "case_id": "vs_gk_99", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.55511474609375 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 113.97238159179688 + ], + [ + 0.0, + 0.0, + 1.5, + -92.13976287841797 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "3ec34b1030b3e905757e47f363e52d46aec0526b142df5ada734a9ab1dccc323" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.55511474609375 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 113.97238159179688 + ], + [ + 0.0, + 0.0, + 1.5, + -92.13976287841797 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 12375, + "volume_mm3": 3122.735023498535, + "voxel_sha256": "456ab166106de63da576fd7381f3e21b1bc0a8ff941adda46f8cc9a3a1b511c4" + } + }, + { + "case_id": "vs_gk_106", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.15545654296875 + ], + [ + 0.0, + 0.0, + 1.5, + -72.2372817993164 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "0a5491870fb9c24848183787164c2720ea787a37ae6d4c4c8cc2e304dd91a18d" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.15545654296875 + ], + [ + 0.0, + 0.0, + 1.5, + -72.2372817993164 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 4095, + "volume_mm3": 1033.341407775879, + "voxel_sha256": "9b5e53f9d12984f3c2d48995a5e175a2d4b42b93cbee3563a63e0bbc1570a3f8" + } + }, + { + "case_id": "crossmoda2022_etz_62", + "image": { + "affine": [ + [ + -0.8202894219077594, + -0.0061532074955932044, + 0.0, + 102.76580047607422 + ], + [ + 0.0061532074955932044, + -0.8202894219077594, + 0.0, + 115.26573181152344 + ], + [ + 0.0, + 0.0, + 1.5, + -52.381256103515625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "0d3c26803ca5fca071ffc2930c3834760a4f11bee736934df63234c1930a397c" + }, + "mask": { + "affine": [ + [ + -0.8202894330024719, + -0.0061532072722911835, + 0.0, + 102.76580047607422 + ], + [ + 0.0061532072722911835, + -0.8202894330024719, + 0.0, + 115.26573181152344 + ], + [ + 0.0, + 0.0, + 1.5, + -52.381256103515625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 3471, + "volume_mm3": 3503.5194396972656, + "voxel_sha256": "3139ec5a8ee069ba1ed7a9eef3ef52ae056af2eb5f9a340b038b8a6f7b5cc720" + } + }, + { + "case_id": "vs_gk_110", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.15318298339844 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 109.914794921875 + ], + [ + 0.0, + 0.0, + 1.5, + -89.64252471923828 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "8f06e186b522363be9dc942c92685088d3b70fd4217357964307f6ece936e245" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.15318298339844 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 109.914794921875 + ], + [ + 0.0, + 0.0, + 1.5, + -89.64252471923828 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 19634, + "volume_mm3": 4954.487228393555, + "voxel_sha256": "ed02fdd483984ed3befbe449dbac7f14cde1d4fa013208254dd0a73609c9cae4" + } + }, + { + "case_id": "vs_gk_229", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.81597900390625 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.88619995117188 + ], + [ + 0.0, + 0.0, + 1.5, + -81.38075256347656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "1fdffeef4372690e653f5613f4920daeac67bc8b457e284f5d13fb2ac8c88a4f" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.81597900390625 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.88619995117188 + ], + [ + 0.0, + 0.0, + 1.5, + -81.38075256347656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1194, + "volume_mm3": 301.29661560058594, + "voxel_sha256": "c8a9617f88d5e95a76ec7097c103925e46041066ffb3c734983d24ca0a64580e" + } + }, + { + "case_id": "vs_gk_121", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.3946762084961 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.85956573486328 + ], + [ + 0.0, + 0.0, + 1.5, + -88.03934478759766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "68ef6e6b934b4251bb08093f5a9b146fab9c233b32840127e610d7e702771bde" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.3946762084961 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.85956573486328 + ], + [ + 0.0, + 0.0, + 1.5, + -88.03934478759766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5761, + "volume_mm3": 1453.743553161621, + "voxel_sha256": "bb08a3f2d67abe5d6375c8e387b3fccc1695e96c88f9df38207b9a1579167394" + } + }, + { + "case_id": "crossmoda2022_etz_87", + "image": { + "affine": [ + [ + -0.8203124989692522, + 0.0, + 7.519566311025844e-05, + 104.06695556640625 + ], + [ + 0.0, + -0.8203125, + 0.0, + 129.59986877441406 + ], + [ + 4.112262826342258e-05, + 0.0, + 1.4999999981152041, + -54.47066879272461 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "8a6c45ea58d50e347dea6259c55f683c4b6314b311ab2723b26802b075ce12bb" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 7.519566133851185e-05, + 104.06695556640625 + ], + [ + 0.0, + -0.8203125, + 0.0, + 129.59986877441406 + ], + [ + 4.112262831768021e-05, + 0.0, + 1.5, + -54.47066879272461 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 676, + "volume_mm3": 682.3333740234375, + "voxel_sha256": "c07ece994729ed077524fcfb407c4cf91ea5fcbbd041d8ba67a76615dbd61180" + } + }, + { + "case_id": "vs_gk_15", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.21065521240234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.15011596679688 + ], + [ + 0.0, + 0.0, + 1.5, + -87.43402099609375 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "3c15f7ee9e31fed51b62293778f25e71ded0b59e162ee97761377123896924ae" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.21065521240234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.15011596679688 + ], + [ + 0.0, + 0.0, + 1.5, + -87.43402099609375 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5129, + "volume_mm3": 1294.2632675170898, + "voxel_sha256": "a589b6f933b1819311a555f6b3ee95efb4071c632c4db1fc6ef20bc913c7581e" + } + }, + { + "case_id": "vs_gk_149", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.84440612792969 + ], + [ + 0.0, + 0.0, + 1.5, + -71.73236846923828 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "935eb35cf0dd0514fecbc190bb0d9fd34e1b3312b326984cf2c1ba6b6be61981" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.84440612792969 + ], + [ + 0.0, + 0.0, + 1.5, + -71.73236846923828 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 24511, + "volume_mm3": 6185.160255432129, + "voxel_sha256": "fac710a515098b91cdf68b72267e78c5d87f3881358f9b3b1bf347b34c3778eb" + } + }, + { + "case_id": "vs_gk_212", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.15550994873047 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.52947998046875 + ], + [ + 0.0, + 0.0, + 1.5, + -99.38386535644531 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "a275216d9befe593f6369cfb5058a3c1f1d8529db8e9f09fd789941a641bc892" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.15550994873047 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.52947998046875 + ], + [ + 0.0, + 0.0, + 1.5, + -99.38386535644531 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 10627, + "volume_mm3": 2681.6408157348633, + "voxel_sha256": "528a2729ca255ac3ee1eb8cfbb58f3df6816e0bc04510f9a6a255243c5367709" + } + }, + { + "case_id": "vs_gk_92", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.6053237915039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.34093475341797 + ], + [ + 0.0, + 0.0, + 1.5, + -111.2724380493164 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "347911fd247f5ab937d2210c2111bd306f4b241dee91e30c34e95bfa3ac7edaa" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.6053237915039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.34093475341797 + ], + [ + 0.0, + 0.0, + 1.5, + -111.2724380493164 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5091, + "volume_mm3": 1284.6742630004883, + "voxel_sha256": "a214037916409faea29af79a0ef41891549166925d8bb7a5e9100d91551d26a5" + } + }, + { + "case_id": "vs_gk_102", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.84261322021484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.30750274658203 + ], + [ + 0.0, + 0.0, + 1.5, + -85.6180419921875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "95cc1a45fcfc8ffdac68ef6a76cfbd4941651aaf575b52a004cbb9f6eac6043d" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.84261322021484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.30750274658203 + ], + [ + 0.0, + 0.0, + 1.5, + -85.6180419921875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5064, + "volume_mm3": 1277.8610229492188, + "voxel_sha256": "246e5470a0229c21ae85f935655342fba33391f1a5b108b89fa29dd5751c0ae2" + } + }, + { + "case_id": "vs_gk_210", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.0266342163086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.7554473876953 + ], + [ + 0.0, + 0.0, + 1.5, + -85.01271057128906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "b21213fdab41a9bccd6b4e20e098368320a0a69e276f38d9bb04287f067bf20d" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.0266342163086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.7554473876953 + ], + [ + 0.0, + 0.0, + 1.5, + -85.01271057128906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 21656, + "volume_mm3": 5464.723205566406, + "voxel_sha256": "e3d4ec927e6c75ada2a8d69a22d4440c632616c7050885bc419a9ac868146d5f" + } + }, + { + "case_id": "vs_gk_122", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.58126068115234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 127.51815795898438 + ], + [ + 0.0, + 0.0, + 1.5, + -78.97179412841797 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "f6a013877c9222048cb98db4694caceb85f7b01cbbcc647f92b3e2c06b3f4089" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.58126068115234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 127.51815795898438 + ], + [ + 0.0, + 0.0, + 1.5, + -78.97179412841797 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3732, + "volume_mm3": 941.7411804199219, + "voxel_sha256": "f5507c232c8345bd6b4626f377e9c5a1215be50c30f151c201fc6af41c7d4726" + } + }, + { + "case_id": "vs_gk_128", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.2300262451172 + ], + [ + 0.0, + 0.0, + 1.5, + -105.71488952636719 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "ee9beb7b8516eeac11340f9886ebfbe522239396a326083dcaaa21188dbd2ae0" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.2300262451172 + ], + [ + 0.0, + 0.0, + 1.5, + -105.71488952636719 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2027, + "volume_mm3": 511.49768829345703, + "voxel_sha256": "a969375cdf19c5e879efb363fee66faf2b35bd0b3a1aaab16c277baa58bab82f" + } + }, + { + "case_id": "crossmoda2022_etz_4", + "image": { + "affine": [ + [ + -0.820292551191628, + -0.003950385186440643, + -0.007566525029868017, + 105.27645874023438 + ], + [ + 0.003950334915425223, + -0.8203029879947584, + 3.644216189920018e-05, + 118.67404174804688 + ], + [ + -0.0041379910386849465, + 1.9506273828177467e-09, + 1.4999810345463787, + -45.07966995239258 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000001192092896 + ], + "voxel_sha256": "6ca347180d4585b51d3ae1d1f42563344c697ebb67bd91f3cc41d137746e6552" + }, + "mask": { + "affine": [ + [ + -0.8202925324440002, + -0.0039503853768110275, + -0.007566525135189295, + 105.27645874023438 + ], + [ + 0.003950335085391998, + -0.8203029632568359, + 3.644216121756472e-05, + 118.67404174804688 + ], + [ + -0.0041379909962415695, + 1.9506274373526367e-09, + 1.4999810457229614, + -45.07966995239258 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000001192092896 + ], + "tumour_voxels": 1097, + "volume_mm3": 1107.277767441883, + "voxel_sha256": "ca785a514478193cdd8dee889de8bb2e45e261b8ccb5ef44c1bd0abb2d755530" + } + }, + { + "case_id": "vs_gk_159", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.57868957519531 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.28086853027344 + ], + [ + 0.0, + 0.0, + 1.5, + -85.01271057128906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "76c023aac58135542f8b14ef31dc537af137c6c181d1170652da8bc6eeca9f0b" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.57868957519531 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.28086853027344 + ], + [ + 0.0, + 0.0, + 1.5, + -85.01271057128906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 9759, + "volume_mm3": 2462.607765197754, + "voxel_sha256": "8392693a20ccfb03dbd54b588d07012acaf81d3706095f80032115ca1c447ab5" + } + }, + { + "case_id": "vs_gk_242", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.87409210205078 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.6440658569336 + ], + [ + 0.0, + 0.0, + 1.5, + -107.0708236694336 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "3ac07a943c2bea59798ab76b302abac9a4648bb54cd4f652d76a62b104c113f0" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.87409210205078 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.6440658569336 + ], + [ + 0.0, + 0.0, + 1.5, + -107.0708236694336 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2610, + "volume_mm3": 658.6132049560547, + "voxel_sha256": "2dab25db38de2481a292cd4e967f9fc47b06635cd8dc89f8a5f9be898d8603e3" + } + }, + { + "case_id": "crossmoda2022_etz_77", + "image": { + "affine": [ + [ + -0.8202723654548063, + 0.0, + -0.014837840373443104, + 103.3851089477539 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.0948486328125 + ], + [ + -0.008114439440082747, + 0.0, + 1.4999274455415599, + -57.07637023925781 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000008344650269 + ], + "voxel_sha256": "1c2b0c596fd977b75a56412689f1f215d4d550d50b69cd05be33b11dad1c4297" + }, + "mask": { + "affine": [ + [ + -0.8202723860740662, + 0.0, + -0.01483784057199955, + 103.3851089477539 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.0948486328125 + ], + [ + -0.008114439435303211, + 0.0, + 1.4999274015426636, + -57.07637023925781 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000008344650269 + ], + "tumour_voxels": 321, + "volume_mm3": 324.00759602005564, + "voxel_sha256": "b76c0c0dc7b843ded7d87ded6352190982a73a839114702340773bb636142c87" + } + }, + { + "case_id": "crossmoda2022_etz_68", + "image": { + "affine": [ + [ + -0.8202692475678003, + 0.0, + -0.015403373924535923, + 103.39856719970703 + ], + [ + 0.0, + -0.8203125, + 0.0, + 124.91047668457031 + ], + [ + -0.008423725470641004, + 0.0, + 1.4999199562142311, + -37.23714065551758 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999990463256836 + ], + "voxel_sha256": "d618e815fe823785e2c9dbf0199e5bcbfe5a549e17c70436425b097382153922" + }, + "mask": { + "affine": [ + [ + -0.8202692270278931, + 0.0, + -0.01540337409824133, + 103.39856719970703 + ], + [ + 0.0, + -0.8203125, + 0.0, + 124.91047668457031 + ], + [ + -0.00842372514307499, + 0.0, + 1.4999200105667114, + -37.23714065551758 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999990463256836 + ], + "tumour_voxels": 218, + "volume_mm3": 220.04227953439113, + "voxel_sha256": "bd72cc12a3e5c80d679ed53bc873a3883df2cc3dfb133076673f362da84f156d" + } + }, + { + "case_id": "vs_gk_224", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.4602279663086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 115.55672454833984 + ], + [ + 0.0, + 0.0, + 1.5, + -78.37903594970703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "a5e4d5623c079d8c848ed3866039da614a184f06971242306128d4918079d7d4" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.4602279663086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 115.55672454833984 + ], + [ + 0.0, + 0.0, + 1.5, + -78.37903594970703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2133, + "volume_mm3": 538.245964050293, + "voxel_sha256": "a739ac4c7eb1cc54d058bc803cf96f1ffe95528f85b4fc45721809420b7946b4" + } + }, + { + "case_id": "vs_gk_236", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.9446792602539 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.75752258300781 + ], + [ + 0.0, + 0.0, + 1.5, + -79.67979431152344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "2aa5a6ba0fce7dc5898e3ec3d61df852075413de72e8a563655f13de7b7476be" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.9446792602539 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.75752258300781 + ], + [ + 0.0, + 0.0, + 1.5, + -79.67979431152344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2740, + "volume_mm3": 691.4176940917969, + "voxel_sha256": "5b63a11d37046b7ad3b22ec2c55a1465949dc8af0d353c4c558fdc886ba0a27b" + } + }, + { + "case_id": "vs_gk_10", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.3946762084961 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 127.51815795898438 + ], + [ + 0.0, + 0.0, + 1.5, + -74.11682891845703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "1a0ab668a75868647a530979f631e14106f9f6ccb164febff7793ce0e8c61825" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.3946762084961 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 127.51815795898438 + ], + [ + 0.0, + 0.0, + 1.5, + -74.11682891845703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 31406, + "volume_mm3": 7925.05989074707, + "voxel_sha256": "0ed0747b2fd774c582a159c4bb50e22dc1fad4ea3b2b4caae05a9249f75631e9" + } + }, + { + "case_id": "vs_gk_36", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.59764862060547 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 145.84719848632812 + ], + [ + 0.0, + 0.0, + 1.5, + -81.90797424316406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "2c6159836290f233acb1bc97395118e2207d2205856ef15ed8860e8b365a1b5e" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.59764862060547 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 145.84719848632812 + ], + [ + 0.0, + 0.0, + 1.5, + -81.90797424316406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 13069, + "volume_mm3": 3297.860527038574, + "voxel_sha256": "9ba5d08ca12ced6b5e5cdfcfdb3935a3e1eda2d578d2c5dd6d96229fc188b563" + } + }, + { + "case_id": "vs_gk_153", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.4527359008789 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.32574462890625 + ], + [ + 0.0, + 0.0, + 1.5, + -110.4985580444336 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "49ed9ae58a41a37289d8ae2befaa4e2b0d6afe73dea31f2d9d9c4f8868defd59" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.4527359008789 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.32574462890625 + ], + [ + 0.0, + 0.0, + 1.5, + -110.4985580444336 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2841, + "volume_mm3": 716.9042587280273, + "voxel_sha256": "11548255d56175c2ffe25f54c408e9bce4dbb4c0be70b278e5fb2cb5ea767b16" + } + }, + { + "case_id": "vs_gk_249", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 112.64562225341797 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 113.20623016357422 + ], + [ + 0.0, + 0.0, + 1.5, + -69.20867156982422 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "2f2de50076b81ba028da7a3ac16dcd139d11acd2f1bc849fac7a91fe4c0b2f08" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 112.64562225341797 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 113.20623016357422 + ], + [ + 0.0, + 0.0, + 1.5, + -69.20867156982422 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 7612, + "volume_mm3": 1920.8290100097656, + "voxel_sha256": "7abbffe6ba9a67044aa04562e98c18d482f779a5e63daf428286d7ba6cb7dfd8" + } + }, + { + "case_id": "vs_gk_150", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.34420776367188 + ], + [ + 0.0, + 0.0, + 1.5, + -102.69263458251953 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "d5b2d369686d2057dd12767d20d8fbb60903a6064dd57f7b07b4a6e44214dc90" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.34420776367188 + ], + [ + 0.0, + 0.0, + 1.5, + -102.69263458251953 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 11652, + "volume_mm3": 2940.2915954589844, + "voxel_sha256": "093f94736309fd01329c03d460bdf6801e7785d78777c28d1e089c5637204331" + } + }, + { + "case_id": "vs_gk_75", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.21065521240234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 134.1767578125 + ], + [ + 0.0, + 0.0, + 1.5, + -86.82868957519531 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "2edf5cb2b51d49843e555c212a68d329f24769e123f3eeaf3cd10c4211acfc19" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.21065521240234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 134.1767578125 + ], + [ + 0.0, + 0.0, + 1.5, + -86.82868957519531 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 10724, + "volume_mm3": 2706.1180114746094, + "voxel_sha256": "d46490267d0be48e32daaad5d9c347f5f510b722d9d244b978cad5287c89aeba" + } + }, + { + "case_id": "crossmoda2022_etz_95", + "image": { + "affine": [ + [ + -0.8202862229437894, + 0.0, + -0.012006075085248286, + 103.63501739501953 + ], + [ + 0.0, + -0.8203125, + 0.0, + 124.51969909667969 + ], + [ + -0.006565828573907233, + 0.0, + 1.499950520060135, + -47.7449951171875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999985694885254 + ], + "voxel_sha256": "4f5e0aced70136942e94d347625c6ba2b1bcc45c119e53ceda7d3cc381d58c0f" + }, + "mask": { + "affine": [ + [ + -0.8202860951423645, + -0.0004543611139524728, + -0.01200607419013977, + 103.63501739501953 + ], + [ + 0.0004543611139524728, + -0.8203123807907104, + 3.325062607473228e-06, + 124.51969909667969 + ], + [ + -0.006565827876329422, + -1.818395389818761e-06, + 1.4999505281448364, + -47.7449951171875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999985694885254 + ], + "tumour_voxels": 1418, + "volume_mm3": 1431.283730235009, + "voxel_sha256": "a5f585d1ae3713a1664c761293e7ec6fa47f1ed1788b7645f6e63d846536ee6b" + } + }, + { + "case_id": "vs_gk_82", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.90887451171875 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 136.8341522216797 + ], + [ + 0.0, + 0.0, + 1.5, + -88.34112548828125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "55b570423f4a7a950639a4ba27d35fd2b16abeba89ac8b1b5caa8896e295b57b" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.90887451171875 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 136.8341522216797 + ], + [ + 0.0, + 0.0, + 1.5, + -88.34112548828125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 26978, + "volume_mm3": 6807.688522338867, + "voxel_sha256": "4aedbdd596bdbae46f0e250e80edf89622d959af7b423594c4a90952a54fc935" + } + }, + { + "case_id": "crossmoda2022_etz_22", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 105.85081481933594 + ], + [ + 0.0, + -0.8203125, + 0.0, + 124.50308990478516 + ], + [ + 0.0, + 0.0, + 1.5, + -65.01087188720703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "56206953f7683bab4cb21c20adf9b98ae7c25b147e3f853d51ee9c78b0b69527" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 105.85081481933594 + ], + [ + 0.0, + -0.8203125, + 0.0, + 124.50308990478516 + ], + [ + 0.0, + 0.0, + 1.5, + -65.01087188720703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 6221, + "volume_mm3": 6279.283905029297, + "voxel_sha256": "ef87c0e79357229198511c4b1864ea55bd8d0e6158ba6193cfb635b2a98660ce" + } + }, + { + "case_id": "vs_gk_20", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.9356689453125 + ], + [ + 0.0, + 0.0, + 1.0, + -75.8680419921875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "00921b83c2ea4116e77313f918142c90d9a29205aab44aa3b5bf45ac6518ea69" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.9356689453125 + ], + [ + 0.0, + 0.0, + 1.0, + -75.8680419921875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 2428, + "volume_mm3": 408.45794677734375, + "voxel_sha256": "879edd07e73932896fc9aee47c4340b962c7b9c05efedbade7d753fb935fa595" + } + }, + { + "case_id": "crossmoda2022_etz_69", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.5463638305664 + ], + [ + 0.0, + -0.8201751197424295, + 0.027451143075566856, + 123.46495819091797 + ], + [ + 0.0, + 0.015012348641747735, + 1.499748313628856, + -43.87529754638672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999995231628418 + ], + "voxel_sha256": "f88e429b4512938b380528d6d2931d6225b2d4b4b235e44f2ff83bb91000a4b1" + }, + "mask": { + "affine": [ + [ + -0.8203123807907104, + -0.0004209628386888653, + 7.044192443572683e-06, + 101.5463638305664 + ], + [ + 0.0004209628386888653, + -0.8201749920845032, + 0.02745114080607891, + 123.46495819091797 + ], + [ + -3.852293957606889e-06, + 0.0150123480707407, + 1.4997483491897583, + -43.87529754638672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999995231628418 + ], + "tumour_voxels": 5758, + "volume_mm3": 5811.944258389121, + "voxel_sha256": "7d2ab6940aad872284b45c88517cd45d83a3e6530627e2c6f42b9eb1c6cb4f2d" + } + }, + { + "case_id": "crossmoda2022_etz_90", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.5463638305664 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.19853973388672 + ], + [ + 0.0, + 0.0, + 1.5, + -59.35869598388672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "ae4610267b7c2fc21961936a7c2868535a90b7ae5ab496ddfc047f5bae967e92" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.5463638305664 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.19853973388672 + ], + [ + 0.0, + 0.0, + 1.5, + -59.35869598388672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 1031, + "volume_mm3": 1040.6593322753906, + "voxel_sha256": "31fabcb4cbc492b9e066c85c585a7db0ee72df26569d2302b7502ff45fe8da5e" + } + }, + { + "case_id": "vs_gk_233", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.6319580078125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.49152374267578 + ], + [ + 0.0, + 0.0, + 1.5, + -83.80205535888672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "0dc2dc3f8234f5a466fa49b9a2b5e3360175a1f1ef56e29205e5e996d988e381" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.6319580078125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.49152374267578 + ], + [ + 0.0, + 0.0, + 1.5, + -83.80205535888672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 31500, + "volume_mm3": 7948.780059814453, + "voxel_sha256": "6a1bbbc71bd8baebd608c6c52dfb59234efb23b9526a0e8fe6cd5da90c6cebd6" + } + }, + { + "case_id": "crossmoda2022_etz_0", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 104.06879425048828 + ], + [ + 0.0, + -0.8203125, + 0.0, + 121.78422546386719 + ], + [ + 0.0, + 0.0, + 1.5, + -50.29709243774414 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "bba2c9db973094d8a3b5c998948eab5f2c7a11e0f6eb0af583255f140f1e67c9" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 104.06879425048828 + ], + [ + 0.0, + -0.8203125, + 0.0, + 121.78422546386719 + ], + [ + 0.0, + 0.0, + 1.5, + -50.29709243774414 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 2306, + "volume_mm3": 2327.6046752929688, + "voxel_sha256": "c3f414c106c8ebdafc808f8d195984ca74bd9ff0fcab9ebfd3dd98e9011cc7b6" + } + }, + { + "case_id": "vs_gk_165", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.85208892822266 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.4372329711914 + ], + [ + 0.0, + 0.0, + 1.5, + -86.39791107177734 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "50ee82ada01a157e0822f8b745b37537f764154b77336f0b3b930f3e64fa772f" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.85208892822266 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.4372329711914 + ], + [ + 0.0, + 0.0, + 1.5, + -86.39791107177734 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2332, + "volume_mm3": 588.4620666503906, + "voxel_sha256": "0dcd34fb6507b01e91c7036593dd2cb05dd5ecb82c797163a9b36207253f4603" + } + }, + { + "case_id": "crossmoda2022_etz_54", + "image": { + "affine": [ + [ + -0.820310700853995, + 0.0, + -0.0031415971215714694, + 103.92166900634766 + ], + [ + 0.0, + -0.8203125, + 0.0, + 116.4109878540039 + ], + [ + -0.001718057239308505, + 0.0, + 1.499999928776778, + -73.11787414550781 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000032186508179 + ], + "voxel_sha256": "e64064c6bd6dacf8e9a8b5fc4db50673ecea94837072e15695e99015a3ea4c43" + }, + "mask": { + "affine": [ + [ + -0.8203107118606567, + 0.0, + -0.003141597146168351, + 103.92166900634766 + ], + [ + 0.0, + -0.8203125, + 0.0, + 116.4109878540039 + ], + [ + -0.0017180572031065822, + 0.0, + 1.4999998807907104, + -73.11787414550781 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000032186508179 + ], + "tumour_voxels": 603, + "volume_mm3": 608.6507506000999, + "voxel_sha256": "4cd79a9abdda1894074677f7f04b937a4cd8ca39e42cfd22692bd4cc19e07d23" + } + }, + { + "case_id": "vs_gk_190", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.84261322021484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 117.22760009765625 + ], + [ + 0.0, + 0.0, + 1.5, + -74.11682891845703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "68b2784a07d78fd28722ccc0f311e54ae3d48fd9711e4a578a4803db02799e90" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.84261322021484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 117.22760009765625 + ], + [ + 0.0, + 0.0, + 1.5, + -74.11682891845703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1735, + "volume_mm3": 437.81375885009766, + "voxel_sha256": "dc8ea00f9d8508cf20eee25efd3e286b59764a946683f0e50d7d4ad20c4f6a46" + } + }, + { + "case_id": "vs_gk_155", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 127.14743041992188 + ], + [ + 0.0, + 0.0, + 1.0, + -76.94207763671875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "11abef0b17849afca286347ce9a4c12b0708e48a81682d064d6d6fc88decd331" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 127.14743041992188 + ], + [ + 0.0, + 0.0, + 1.0, + -76.94207763671875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 1636, + "volume_mm3": 275.22125244140625, + "voxel_sha256": "86b35c09b9ea8aecd083790a8fc67c66e892277770f0fd918198dc135c19cf0a" + } + }, + { + "case_id": "vs_gk_47", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.79148864746094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 127.70220184326172 + ], + [ + 0.0, + 0.0, + 1.5, + -75.99894714355469 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "38e12952e40706fefd9bd99e10ec6f7568bf5a1864dcc7859262a3108affcd7e" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.79148864746094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 127.70220184326172 + ], + [ + 0.0, + 0.0, + 1.5, + -75.99894714355469 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 18205, + "volume_mm3": 4593.890190124512, + "voxel_sha256": "8cf1cda62a1d63ad331a2560d4aeedbb0d199b366b95327aa997fb3ad3ee329d" + } + }, + { + "case_id": "crossmoda2022_etz_79", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 98.85838317871094 + ], + [ + 0.0, + -0.8203125, + 0.0, + 124.38944244384766 + ], + [ + 0.0, + 0.0, + 1.5, + -52.90229797363281 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "a4fe76e611115ebb992e4b73a2079a6c3ebd09d5fc3ef63837fd59f7d5ed339f" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 98.85838317871094 + ], + [ + 0.0, + -0.8203125, + 0.0, + 124.38944244384766 + ], + [ + 0.0, + 0.0, + 1.5, + -52.90229797363281 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 537, + "volume_mm3": 542.0310974121094, + "voxel_sha256": "d512a5671b7e2733353bbcdbbf1150d75fb75e50ab70b5332dd369b3c32989c4" + } + }, + { + "case_id": "vs_gk_57", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.57868957519531 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 142.65133666992188 + ], + [ + 0.0, + 0.0, + 1.5, + -80.77542114257812 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "63072b8d4dc05e89e6f52c147e16277538f23aaaff5fe104cf9c6d48e149d75b" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.57868957519531 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 142.65133666992188 + ], + [ + 0.0, + 0.0, + 1.5, + -80.77542114257812 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3202, + "volume_mm3": 807.9998016357422, + "voxel_sha256": "cfae3aff3c8e69c6f6e67350bdfcd426c19aba7f47f1e93cd1ea44a148db8ed9" + } + }, + { + "case_id": "vs_gk_194", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.33434295654297 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.88619995117188 + ], + [ + 0.0, + 0.0, + 1.5, + -81.38075256347656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "55c80439abd954ac865bb8c0a0d072ec3b793f79161736bdea14cfc6f00b622b" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.33434295654297 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.88619995117188 + ], + [ + 0.0, + 0.0, + 1.5, + -81.38075256347656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 22565, + "volume_mm3": 5694.1022872924805, + "voxel_sha256": "594b9a2dee31befc9f8cd7c6cf5b671b2188a7c9edabe1c3c5800f5c34548e13" + } + }, + { + "case_id": "vs_gk_176", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 114.52495574951172 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.7311248779297 + ], + [ + 0.0, + 0.0, + 1.5, + -79.72504425048828 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "0d53e641ff145a033c4b037ab5793f3987140910f4eb3ac39866cf38ca00acd1" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 114.52495574951172 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.7311248779297 + ], + [ + 0.0, + 0.0, + 1.5, + -79.72504425048828 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3965, + "volume_mm3": 1000.5369186401367, + "voxel_sha256": "f0976f0a3071348324b7faee4535da5e9cafb47f066e6468b4d37b97a677d656" + } + }, + { + "case_id": "vs_gk_140", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.01251220703125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 113.43054962158203 + ], + [ + 0.0, + 0.0, + 1.5, + -62.0810432434082 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "a92dd91cf97c048b48b402ed7595b008caf2dab54f93d6651724ec271f24faf6" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.01251220703125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 113.43054962158203 + ], + [ + 0.0, + 0.0, + 1.5, + -62.0810432434082 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 27387, + "volume_mm3": 6910.8964920043945, + "voxel_sha256": "dde1fc909fa9d53b12f512906d8db2ef0016dc3815495c684c5d1da512909879" + } + }, + { + "case_id": "vs_gk_237", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.75191497802734 + ], + [ + 0.0, + 0.0, + 1.5, + -83.47046661376953 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "8f8245652ac811a5d0f3304a5f7d2976c0c1b18ea5fac18146c378bc729d4261" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.75191497802734 + ], + [ + 0.0, + 0.0, + 1.5, + -83.47046661376953 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5512, + "volume_mm3": 1390.9103393554688, + "voxel_sha256": "852184463b91e48a5661d857f6924a950ccdc2e18e180f43ceb24a722cda7e42" + } + }, + { + "case_id": "vs_gk_31", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.33413696289062 + ], + [ + 0.0, + 0.0, + 1.5, + -85.6180419921875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "d04c09b693ea28ab8ef248eecf827a0294983a6794f6adc2f9f35d56c643caac" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.33413696289062 + ], + [ + 0.0, + 0.0, + 1.5, + -85.6180419921875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1395, + "volume_mm3": 352.0174026489258, + "voxel_sha256": "0e03e7d0ac02ac3776366869bb64d9e2382e4fc67a6e4b59945e025b6dd97315" + } + }, + { + "case_id": "crossmoda2022_etz_98", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.02672576904297 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.61589050292969 + ], + [ + 0.0, + 0.0, + 1.5, + -93.54358673095703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "1be15ef0c11a17ef767ffb256ed0e2f00ca38a44f443242858242c3acb25baee" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.02672576904297 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.61589050292969 + ], + [ + 0.0, + 0.0, + 1.5, + -93.54358673095703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 1035, + "volume_mm3": 1044.6968078613281, + "voxel_sha256": "b333d8db844b033851589c1e3dd15f78a985f1ff8cbfb09f3dd49b768f9a23a5" + } + }, + { + "case_id": "vs_gk_203", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.7460708618164 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.06169128417969 + ], + [ + 0.0, + 0.0, + 1.5, + -79.64661407470703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "e556493d68139e1112f9ee698b42e4440d1ab3464e5465b677842f70e2814e10" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.7460708618164 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.06169128417969 + ], + [ + 0.0, + 0.0, + 1.5, + -79.64661407470703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 8240, + "volume_mm3": 2079.2999267578125, + "voxel_sha256": "bf212dbcce7ba52b7280d8227ccafd3f054afc16664938729cf5d8ffd0afc9d5" + } + }, + { + "case_id": "vs_gk_23", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 134.78207397460938 + ], + [ + 0.0, + 0.0, + 1.0, + -79.5 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "486fc546ab0aef580df2aa698093524c5abf0b64f27cbb274545f96762f90518" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 134.78207397460938 + ], + [ + 0.0, + 0.0, + 1.0, + -79.5 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 3535, + "volume_mm3": 594.6865081787109, + "voxel_sha256": "4aedea931a8ef6c963592e99dbb3d227003a778ee5662c41d94ab951d98e9fbd" + } + }, + { + "case_id": "vs_gk_98", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.20851135253906 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.34050750732422 + ], + [ + 0.0, + 0.0, + 1.5, + -82.62447357177734 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "2f751b45d523a7e2fd13aa8fe6cb09a7e99326edde79d4e55921e371aede06aa" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.20851135253906 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.34050750732422 + ], + [ + 0.0, + 0.0, + 1.5, + -82.62447357177734 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 8587, + "volume_mm3": 2166.862678527832, + "voxel_sha256": "287aea1c6645b267e22f28c3ae849bbabaa0898e86557313f8d70ea907b52cfe" + } + }, + { + "case_id": "crossmoda2022_etz_9", + "image": { + "affine": [ + [ + -0.8202506337004853, + 0.003907990124550939, + -0.016979479398259476, + 104.0700454711914 + ], + [ + -0.005387965553682642, + -0.809033697246036, + 0.24768929253243108, + 136.19212341308594 + ], + [ + -0.008512661005502511, + 0.13550572677878114, + 1.479312679314491, + -54.09610366821289 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000014305114746 + ], + "voxel_sha256": "c9841987f49fdc628236df63db5ab0452e995d6022251551794680876bed307e" + }, + "mask": { + "affine": [ + [ + -0.8202506303787231, + 0.003907990176230669, + -0.016979480162262917, + 104.0700454711914 + ], + [ + -0.005387965589761734, + -0.8090336918830872, + 0.24768929183483124, + 136.19212341308594 + ], + [ + -0.00851266086101532, + 0.13550572097301483, + 1.4793126583099365, + -54.09610366821289 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000014305114746 + ], + "tumour_voxels": 1057, + "volume_mm3": 1066.9039410619007, + "voxel_sha256": "5f29933352f2a3c7db771d9dd36a48996864aa5814c097adf4b87208c6f4fbc1" + } + }, + { + "case_id": "vs_gk_11", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.50450134277344 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.76590728759766 + ], + [ + 0.0, + 0.0, + 1.5, + -78.71849060058594 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "5773e17887a8c43138021acaf96a2afe1e34bc5583aca686893178231cf81393" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.50450134277344 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.76590728759766 + ], + [ + 0.0, + 0.0, + 1.5, + -78.71849060058594 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 24666, + "volume_mm3": 6224.273300170898, + "voxel_sha256": "9c4acea0e98d9820aa0743cae9baa4daa52ab1b590fce509b1c50557e1c8ffd0" + } + }, + { + "case_id": "crossmoda2022_etz_21", + "image": { + "affine": [ + [ + -0.820298473639919, + -0.002768206681967913, + 0.007163903620961227, + 102.88615417480469 + ], + [ + 0.0033629832218886433, + -0.8096003304152727, + 0.24154178635837814, + 136.19546508789062 + ], + [ + 0.0034208396802921184, + 0.13210692517615116, + 1.480407941899602, + -57.58076477050781 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000004768371582 + ], + "voxel_sha256": "650cfc763ebb89c79f329c70c3cfac31d85e03db29baf2647a4c2119025480c9" + }, + "mask": { + "affine": [ + [ + -0.8202984929084778, + -0.0027682066429406404, + 0.007163903675973415, + 102.88615417480469 + ], + [ + 0.0033629832323640585, + -0.8096003532409668, + 0.241541787981987, + 136.19546508789062 + ], + [ + 0.003420839784666896, + 0.1321069300174713, + 1.480407953262329, + -57.58076477050781 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000004768371582 + ], + "tumour_voxels": 430, + "volume_mm3": 434.0287634622655, + "voxel_sha256": "f23d9898d43b4b36fd0c4f492109f2bddf52aba84cc575facd072a8916116429" + } + }, + { + "case_id": "crossmoda2022_etz_40", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.9846420288086 + ], + [ + 0.0, + -0.8203125, + 0.0, + 113.44754791259766 + ], + [ + 0.0, + 0.0, + 1.5, + -64.88627624511719 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "abe0a2d881419f464f670bbef73de312e7aef2586445e3bcb9bb3afed48ee9a7" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.9846420288086 + ], + [ + 0.0, + -0.8203125, + 0.0, + 113.44754791259766 + ], + [ + 0.0, + 0.0, + 1.5, + -64.88627624511719 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 1088, + "volume_mm3": 1098.193359375, + "voxel_sha256": "d71c8f4d697ea6f258df6187572ee4fe205f7cecb135c37caefef9907c5b1c81" + } + }, + { + "case_id": "vs_gk_9", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.0266342163086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 136.5980682373047 + ], + [ + 0.0, + 0.0, + 1.5, + -77.14346313476562 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "ef7643615cde3108f2a71b92eb4dbecccfe9e9d57e6dc5ed48f0ceb590f44206" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.0266342163086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 136.5980682373047 + ], + [ + 0.0, + 0.0, + 1.5, + -77.14346313476562 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 14208, + "volume_mm3": 3585.2783203125, + "voxel_sha256": "15429c6013f35734098681b5f5ee30bd0041ea0e9ff10d0ef73580f875e8fac0" + } + }, + { + "case_id": "crossmoda2022_etz_67", + "image": { + "affine": [ + [ + -0.8203053129458596, + -1.3341021700521454e-06, + 0.006279001447172077, + 105.30533599853516 + ], + [ + -1.3341021700521454e-06, + -0.820312252356311, + -0.0011655442261594557, + 113.74845123291016 + ], + [ + 0.0034338327369733326, + -0.0006374077078735391, + 1.4999847362090415, + -59.699310302734375 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999983310699463 + ], + "voxel_sha256": "1c280feda8f00f2eeb5c9be05bce1b4816411ec257b21df5b2f9dee78838aca6" + }, + "mask": { + "affine": [ + [ + -0.8203052878379822, + -1.3341021940505016e-06, + 0.0062790014781057835, + 105.30533599853516 + ], + [ + -1.3341021940505016e-06, + -0.8203122615814209, + -0.0011655442649498582, + 113.74845123291016 + ], + [ + 0.003433832898736, + -0.0006374077056534588, + 1.4999847412109375, + -59.699310302734375 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999983310699463 + ], + "tumour_voxels": 3568, + "volume_mm3": 3601.424215635052, + "voxel_sha256": "819246ff1840ffa10e917c72d5c79e15b0d0cce3bd56beaec18fd2956efa98da" + } + }, + { + "case_id": "vs_gk_188", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.07498168945312 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.14938354492188 + ], + [ + 0.0, + 0.0, + 1.5, + -84.43746185302734 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "750cf09923d1fbb08d53d5d554d0a3589a4d25c98b3cc1c71ce2473c982bdeab" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.07498168945312 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.14938354492188 + ], + [ + 0.0, + 0.0, + 1.5, + -84.43746185302734 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 12353, + "volume_mm3": 3117.183494567871, + "voxel_sha256": "d26afe8ee1c2399bd1f4642035294cb9baae1b50b7c0195983de690a2870a6f4" + } + }, + { + "case_id": "vs_gk_225", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.2340316772461 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 115.18397521972656 + ], + [ + 0.0, + 0.0, + 1.5, + -85.9181137084961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "218cd7421c49236b9cc2f022d9199818118e99eed7357cc15f7dd6676c5669dc" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.2340316772461 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 115.18397521972656 + ], + [ + 0.0, + 0.0, + 1.5, + -85.9181137084961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 22181, + "volume_mm3": 5597.2028732299805, + "voxel_sha256": "1e80230f2c8ddb818ab5b7dc59056e227a5bb81e4f9c6a407872b16b6325312e" + } + }, + { + "case_id": "vs_gk_158", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.90729522705078 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.91283416748047 + ], + [ + 0.0, + 0.0, + 1.5, + -83.15310668945312 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "e4f8c04f316917568d3275ded6e661310493dbddbeae6f1a72d0277400315e5f" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.90729522705078 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.91283416748047 + ], + [ + 0.0, + 0.0, + 1.5, + -83.15310668945312 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 13410, + "volume_mm3": 3383.909225463867, + "voxel_sha256": "6b89cb17e9562094e70ae09eac241684068d54fad250ca61440f3336b04c8d4f" + } + }, + { + "case_id": "vs_gk_226", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 112.69922637939453 + ], + [ + 0.0, + 0.0, + 1.5, + -85.8628158569336 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "5f727454ebc42e18b25b71586e2e86470917fe6fef36ec971956906c9a80f219" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 112.69922637939453 + ], + [ + 0.0, + 0.0, + 1.5, + -85.8628158569336 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 7759, + "volume_mm3": 1957.9233169555664, + "voxel_sha256": "f9d94a81ca5e5eb9ccc099533507714df972fb9e4dd93134342207e6205fbf17" + } + }, + { + "case_id": "vs_gk_118", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.76714324951172 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 139.79251098632812 + ], + [ + 0.0, + 0.0, + 1.5, + -77.7427978515625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "4ceab1e3547435a26836d86358cbbf89a8244bceeda440bd8d8025b5c5f8683d" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.76714324951172 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 139.79251098632812 + ], + [ + 0.0, + 0.0, + 1.5, + -77.7427978515625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3044, + "volume_mm3": 768.1297302246094, + "voxel_sha256": "aba95ba60f94da1dd1a930732449b36dfd64edd561e1093a51947941943f066c" + } + }, + { + "case_id": "vs_gk_3", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.4994125366211 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 135.8495330810547 + ], + [ + 0.0, + 0.0, + 1.5, + -90.08313751220703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "3f13ea68c1bbc6850e66d233052b556848a24b17c8277a6eb8c05855c29224d6" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.4994125366211 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 135.8495330810547 + ], + [ + 0.0, + 0.0, + 1.5, + -90.08313751220703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 12729, + "volume_mm3": 3212.0641708374023, + "voxel_sha256": "d93346e761fb949c8fb39731ca8afe1a1b129e595dde86ebeb6c81a92ce797e8" + } + }, + { + "case_id": "vs_gk_239", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 117.73889923095703 + ], + [ + 0.0, + 0.0, + 1.5, + -84.23499298095703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "7de88b9e0857f2ea209387cdf420161fdff995c12af35e73a076e354d53a752e" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 117.73889923095703 + ], + [ + 0.0, + 0.0, + 1.5, + -84.23499298095703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2051, + "volume_mm3": 517.5539016723633, + "voxel_sha256": "fe93d476acc96ffae08ba07cc88f04d52f15f37e3b30e2ba1759c71698d5dd48" + } + }, + { + "case_id": "vs_gk_88", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.25423431396484 + ], + [ + 0.0, + 0.0, + 1.5, + -91.06597900390625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "44d601c8a4c4b52f5a3dd4b5cef008a74a48da5dc4e7ad88551affd5ba605632" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.25423431396484 + ], + [ + 0.0, + 0.0, + 1.5, + -91.06597900390625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 13737, + "volume_mm3": 3466.425132751465, + "voxel_sha256": "d65d9795b61c2035a41d35315965c03a9d86c74c52b115df5aa4f9f5b1181e43" + } + }, + { + "case_id": "vs_gk_119", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.69841766357422 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.81260681152344 + ], + [ + 0.0, + 0.0, + 1.5, + -75.93569946289062 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "9d8e2da0e530d946c6879d8bcd844a57c4b0023faf99fff025e9158d2dc2242e" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.69841766357422 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.81260681152344 + ], + [ + 0.0, + 0.0, + 1.5, + -75.93569946289062 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 20541, + "volume_mm3": 5183.361625671387, + "voxel_sha256": "622d91a256ffb0b01b61f5111b85bf122c14b249620acc161410b509ded3db66" + } + }, + { + "case_id": "vs_gk_144", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 140.3158416748047 + ], + [ + 0.0, + 0.0, + 1.5, + -79.38292694091797 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "57997c00630691d7fd5598b21aee431f0e17a106fa2e68f52071c5c48911c837" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 140.3158416748047 + ], + [ + 0.0, + 0.0, + 1.5, + -79.38292694091797 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5320, + "volume_mm3": 1342.4606323242188, + "voxel_sha256": "e95ec09cc32fd168465d423e9106a05584c1fe5814933c441957edb8e503d204" + } + }, + { + "case_id": "crossmoda2022_etz_61", + "image": { + "affine": [ + [ + -0.8203051789905654, + 0.0, + -0.006337245828860075, + 102.57025909423828 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.6333236694336 + ], + [ + -0.003465685719505133, + 0.0, + 1.4999847056797093, + -70.2208023071289 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999980926513672 + ], + "voxel_sha256": "5dd631819fc5e61de122ddfef149bbb5ece868cd1e5cbfc920ca6fa9151ea5c8" + }, + "mask": { + "affine": [ + [ + -0.8203050494194031, + -0.0004266202449798584, + -0.006337245460599661, + 102.57025909423828 + ], + [ + 0.0004266202449798584, + -0.8203123807907104, + 1.6479143596370704e-06, + 117.6333236694336 + ], + [ + -0.0034656855277717113, + -9.012043165057548e-07, + 1.4999847412109375, + -70.2208023071289 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999980926513672 + ], + "tumour_voxels": 736, + "volume_mm3": 742.8945631720126, + "voxel_sha256": "c132af728ddc6306281fe87d3550a77302204b6f68df91b336a523a3e4906fff" + } + }, + { + "case_id": "crossmoda2022_etz_58", + "image": { + "affine": [ + [ + -0.820305573864082, + 0.0, + -0.006163984323506142, + 104.30519104003906 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.52446746826172 + ], + [ + -0.003370926247945934, + 0.0, + 1.4999885271485802, + -50.764122009277344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000011920928955 + ], + "voxel_sha256": "7ab485cd97fce64c522316667cdef0065a674b525318bb5b3e52109433880dff" + }, + "mask": { + "affine": [ + [ + -0.8203054666519165, + -0.00043371206265874207, + -0.0061639840714633465, + 104.30519104003906 + ], + [ + 0.00043371206265874207, + -0.8203123807907104, + 1.6295044815706206e-06, + 120.52446746826172 + ], + [ + -0.0033709260169416666, + -8.911345048545627e-07, + 1.4999885559082031, + -50.764122009277344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000011920928955 + ], + "tumour_voxels": 1200, + "volume_mm3": 1211.2436383904424, + "voxel_sha256": "b06e3298fbaef2de1e207700469b2c57e2006361f880aadedc3aff042ac4add5" + } + }, + { + "case_id": "vs_gk_58", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.56925201416016 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.1834945678711 + ], + [ + 0.0, + 0.0, + 1.5, + -82.18836212158203 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "f554e6901e62b4f2a752604ba30076712b4fb9eac5f036d32457897712d2868b" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.56925201416016 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.1834945678711 + ], + [ + 0.0, + 0.0, + 1.5, + -82.18836212158203 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 10986, + "volume_mm3": 2772.231674194336, + "voxel_sha256": "b674d5151118bbfaab81aff8c0117424d99e3a37e853df40a01ce0d2e2245912" + } + }, + { + "case_id": "vs_gk_189", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 111.14586639404297 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 111.05601501464844 + ], + [ + 0.0, + 0.0, + 1.5, + -69.93441009521484 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "c2d5452d55e6f4271a6d6117cc4426ab5224a18b94092d9af298cd510e7d911c" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 111.14586639404297 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 111.05601501464844 + ], + [ + 0.0, + 0.0, + 1.5, + -69.93441009521484 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 4249, + "volume_mm3": 1072.2021102905273, + "voxel_sha256": "69ede84e1b2b407d21c404dfbdcac2f263db8326503f34c4ef37b30db810b3c4" + } + }, + { + "case_id": "crossmoda2022_etz_36", + "image": { + "affine": [ + [ + -0.8202723860225101, + 0.0, + -0.014834038013786613, + 101.82191467285156 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.22109985351562 + ], + [ + -0.008112360025802403, + 0.0, + 1.4999274831510963, + -60.723934173583984 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000008344650269 + ], + "voxel_sha256": "11930b40e6a29a6755f93d4c6a135568e0137fb27b8f268e8ddd45f9400f239f" + }, + "mask": { + "affine": [ + [ + -0.8202723860740662, + 0.0, + -0.014834037981927395, + 101.82191467285156 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.22109985351562 + ], + [ + -0.008112359791994095, + 0.0, + 1.4999275207519531, + -60.723934173583984 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000008344650269 + ], + "tumour_voxels": 1541, + "volume_mm3": 1555.4383347878684, + "voxel_sha256": "7b3283a8e7ee30376b375a415dc4badf27b1557b3c676a047cc4801936cd483f" + } + }, + { + "case_id": "vs_gk_147", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.37308502197266 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.58694458007812 + ], + [ + 0.0, + 0.0, + 1.5, + -116.90756225585938 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "fbd8e8503eef7d084fcc0b9f064783976a060f2be4b4812c85c7850f3b6a031b" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.37308502197266 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.58694458007812 + ], + [ + 0.0, + 0.0, + 1.5, + -116.90756225585938 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 7862, + "volume_mm3": 1983.914566040039, + "voxel_sha256": "3bce5ba33cfa78f96e1f66216e23275e2126c81270a55b71014beb734544479a" + } + }, + { + "case_id": "vs_gk_230", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.63019561767578 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.40721893310547 + ], + [ + 0.0, + 0.0, + 1.0, + -73.13848114013672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "a870aec35aafef3c11b5fd5684afbabfcfd7a0621374a775618ee78801b559df" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.63019561767578 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.40721893310547 + ], + [ + 0.0, + 0.0, + 1.0, + -73.13848114013672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 1185, + "volume_mm3": 199.35035705566406, + "voxel_sha256": "cbfe0165493f38febddd1ba564723f6cc4372d12a6e4eeda24bed58b0e8ff549" + } + }, + { + "case_id": "crossmoda2022_etz_43", + "image": { + "affine": [ + [ + -0.8202839052624258, + 0.0, + -0.012524360344263533, + 105.32782745361328 + ], + [ + 0.0, + -0.8203125, + 0.0, + 126.76375579833984 + ], + [ + -0.006849264462244822, + 0.0, + 1.4999466396336572, + -54.13628387451172 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.499998927116394 + ], + "voxel_sha256": "620f7c97fe8d02c7773a19e2a1dab4951d1a2b056228d90cc119047561b3fd9e" + }, + "mask": { + "affine": [ + [ + -0.8202838897705078, + 0.0, + -0.012524360790848732, + 105.32782745361328 + ], + [ + 0.0, + -0.8203125, + 0.0, + 126.76375579833984 + ], + [ + -0.00684926426038146, + 0.0, + 1.4999465942382812, + -54.13628387451172 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.499998927116394 + ], + "tumour_voxels": 1303, + "volume_mm3": 1315.2067314093074, + "voxel_sha256": "7c7058a07f60a28b656b16ffbe6da3b10d70389d2f315c11b48a8ea956e68ec8" + } + }, + { + "case_id": "vs_gk_85", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.44794464111328 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 134.78207397460938 + ], + [ + 0.0, + 0.0, + 1.0, + -69.81476593017578 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "87607fea3ae8b75f73ee527075c5e7fde04013b62174ad647e536c5a9332c640" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.44794464111328 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 134.78207397460938 + ], + [ + 0.0, + 0.0, + 1.0, + -69.81476593017578 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 1039, + "volume_mm3": 174.78904724121094, + "voxel_sha256": "aafe088023ad5a180bea5010237cc7e0902e0e7b86715d93ff6a77a768ddcb4f" + } + }, + { + "case_id": "vs_gk_34", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.81597900390625 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.0702133178711 + ], + [ + 0.0, + 0.0, + 1.5, + -88.6446762084961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "d631f49558c2f7a46dbd0dc38795d5f945e59ab878f48b6ecc5f5a180a81e382" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.81597900390625 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.0702133178711 + ], + [ + 0.0, + 0.0, + 1.5, + -88.6446762084961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1971, + "volume_mm3": 497.3665237426758, + "voxel_sha256": "b4aa6639c97d7dcdeb0244e318f267eccea16ff205e59b27b04b763e05a1ecd0" + } + }, + { + "case_id": "crossmoda2022_etz_30", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 100.94255828857422 + ], + [ + 0.0, + -0.8203125, + 0.0, + 116.05276489257812 + ], + [ + 0.0, + 0.0, + 1.5, + -60.71794128417969 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "35df60b7e53f0836ae8eca4aa5e68e66b79ac889f7b785d2aac34d4bf7ad6072" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 100.94255828857422 + ], + [ + 0.0, + -0.8203125, + 0.0, + 116.05276489257812 + ], + [ + 0.0, + 0.0, + 1.5, + -60.71794128417969 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 259, + "volume_mm3": 261.4265441894531, + "voxel_sha256": "69c09b276ce02a36450f79101cdda3481aac28956ae017006f6722f60624865f" + } + }, + { + "case_id": "vs_gk_49", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.67375183105469 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.33413696289062 + ], + [ + 0.0, + 0.0, + 1.5, + -77.00660705566406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "2efeee205042733e247300c39ec9e8761016cd5a98ab34d06c9a58eaea587c31" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.67375183105469 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.33413696289062 + ], + [ + 0.0, + 0.0, + 1.5, + -77.00660705566406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 7736, + "volume_mm3": 1952.1194458007812, + "voxel_sha256": "69d796be12188b34a668f2268f19bd885263a2d505b4ff7eb819d14fa500830c" + } + }, + { + "case_id": "vs_gk_191", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.41620635986328 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.13274383544922 + ], + [ + 0.0, + 0.0, + 1.5, + -95.4181137084961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "8c60ac5fad366aeaf81858b00cd6cff44c6eb01f537436886bca97939df4ebdf" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.41620635986328 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.13274383544922 + ], + [ + 0.0, + 0.0, + 1.5, + -95.4181137084961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2622, + "volume_mm3": 661.6413116455078, + "voxel_sha256": "8dd391d0c929a9fa77b506d21512f8514ef722bd24aa32e43be616e1c6b980e3" + } + }, + { + "case_id": "vs_gk_77", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.78934478759766 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.15011596679688 + ], + [ + 0.0, + 0.0, + 1.5, + -89.8553237915039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "8a5a16ee14122a8bcb693dcdc752d83031a79f5c2f27cc4ced264679453da0cf" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.78934478759766 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.15011596679688 + ], + [ + 0.0, + 0.0, + 1.5, + -89.8553237915039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 791, + "volume_mm3": 199.60269927978516, + "voxel_sha256": "39a9596ecbb346ddd1278bad2da3ad582230da507be137e97fd9549af5772345" + } + }, + { + "case_id": "vs_gk_143", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.6319580078125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.85956573486328 + ], + [ + 0.0, + 0.0, + 1.5, + -65.03692626953125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "15a307724ae9dcda691eda0c4669195428e5d472902c06a089f0bf7af29a4a2a" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.6319580078125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.85956573486328 + ], + [ + 0.0, + 0.0, + 1.5, + -65.03692626953125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 6254, + "volume_mm3": 1578.1482696533203, + "voxel_sha256": "7c255606d80c81252f3a734b49f85d4ab7655ade1968592f8e2e973936bb19d7" + } + }, + { + "case_id": "vs_gk_54", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.65462493896484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 140.6253662109375 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "d8608636bda3c6736f37b4ca31b21c3e8dc9da8550fd253e55e17e38237845c9" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.65462493896484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 140.6253662109375 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 6310, + "volume_mm3": 1592.2794342041016, + "voxel_sha256": "e1a24590e56eccbbb0b4c8c281015c88b331eb0a89737890242d1027c75cb0e6" + } + }, + { + "case_id": "crossmoda2022_etz_85", + "image": { + "affine": [ + [ + -0.8203100511627286, + 0.0, + -0.003665189539860544, + 101.5530776977539 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.61589813232422 + ], + [ + -0.0020043995738390384, + 0.0, + 1.4999962373797344, + -53.688724517822266 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000007152557373 + ], + "voxel_sha256": "d123f7b4e9af1cff032dc2520e9bfaffad95a4c8c450c7664539ec6cf36623a8" + }, + "mask": { + "affine": [ + [ + -0.8203099370002747, + -0.00039202196057885885, + -0.0036651892587542534, + 101.5530776977539 + ], + [ + 0.00039202196057885885, + -0.8203123807907104, + 8.757863270147936e-07, + 117.61589813232422 + ], + [ + -0.0020043994300067425, + -4.789454237652535e-07, + 1.4999961853027344, + -53.688724517822266 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000007152557373 + ], + "tumour_voxels": 246, + "volume_mm3": 248.3048669360869, + "voxel_sha256": "79ad66c68281808bb03f7bcef34a15d05f936b0e9c7c082cb36a0c5b5d4137d9" + } + }, + { + "case_id": "vs_gk_72", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.57868957519531 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.5447998046875 + ], + [ + 0.0, + 0.0, + 1.5, + -81.38075256347656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "f543c45f05014aca4d118195863039c49122c2807659e00462f5abe2784fde3d" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.57868957519531 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.5447998046875 + ], + [ + 0.0, + 0.0, + 1.5, + -81.38075256347656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 14060, + "volume_mm3": 3547.931671142578, + "voxel_sha256": "70ff1d79a308706edbfa917a9ade7bf1e1a8edf9f6507e0f1d4ee5ac51e345f1" + } + }, + { + "case_id": "vs_gk_175", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.4529800415039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 127.51815795898438 + ], + [ + 0.0, + 0.0, + 1.5, + -93.7029800415039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "d6169377702b9fb53b2ac0f47290e7502cb84d2022a53c3e924257472650104d" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.4529800415039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 127.51815795898438 + ], + [ + 0.0, + 0.0, + 1.5, + -93.7029800415039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2666, + "volume_mm3": 672.7443695068359, + "voxel_sha256": "3e51b078060bbad320dd37a4c3773d6b925734d5cb646ea1bdd3940ec24ac8a0" + } + }, + { + "case_id": "vs_gk_65", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.1068344116211 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.84588623046875 + ], + [ + 0.0, + 0.0, + 1.5, + -79.21637725830078 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "e87c6b268ce3e8d88eb14bf9c377218f86ecbaafea1d657d1b7c01bbaa741631" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.1068344116211 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.84588623046875 + ], + [ + 0.0, + 0.0, + 1.5, + -79.21637725830078 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1722, + "volume_mm3": 434.53330993652344, + "voxel_sha256": "9ecf7d54becedcfacd6b2dc0f1f4541dc2944a02233168f5a1880cd56201ea38" + } + }, + { + "case_id": "vs_gk_137", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.02540588378906 + ], + [ + 0.0, + 0.0, + 1.5, + -87.53295135498047 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "9cc6b7d4c01ff5863f5e1984b666ca21e414787f7b0fceea3ad64e6bdec872d9" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.02540588378906 + ], + [ + 0.0, + 0.0, + 1.5, + -87.53295135498047 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 7676, + "volume_mm3": 1936.9789123535156, + "voxel_sha256": "ad557119b6d9688a8625bb1709cdc30c00f18df2e79d7aa3b5d14a0c0eaa48b6" + } + }, + { + "case_id": "vs_gk_200", + "image": { + "affine": [ + [ + -0.41015625, + 2.0085350483506348e-12, + 0.0, + 107.23505401611328 + ], + [ + -2.0085350483506348e-12, + -0.41015625, + 0.0, + 114.85285949707031 + ], + [ + 0.0, + 0.0, + 1.5, + -84.03487396240234 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "ffb785036112a5759f7a760892c5ff9463e118048d13d102813413457387ce1b" + }, + "mask": { + "affine": [ + [ + -0.41015625, + 2.0085350483506348e-12, + 0.0, + 107.23505401611328 + ], + [ + -2.0085350483506348e-12, + -0.41015625, + 0.0, + 114.85285949707031 + ], + [ + 0.0, + 0.0, + 1.5, + -84.03487396240234 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2481, + "volume_mm3": 626.0610580444336, + "voxel_sha256": "ae39a3fd544ba7f832ba673eef2729e64ac9fca969074ff83abc8e5144610a9d" + } + }, + { + "case_id": "vs_gk_120", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.78462982177734 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.5373764038086 + ], + [ + 0.0, + 0.0, + 1.5, + -84.54224395751953 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "4bbac3d72806747e3703b360185ddfb5d699065e2d9b35f0ae3058af3628da3e" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.78462982177734 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.5373764038086 + ], + [ + 0.0, + 0.0, + 1.5, + -84.54224395751953 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 12820, + "volume_mm3": 3235.027313232422, + "voxel_sha256": "57924b6267bf89074ccc2bb9c4aa0540c2bdc50cc12e6ea5a19a9b978820cf03" + } + }, + { + "case_id": "crossmoda2022_etz_23", + "image": { + "affine": [ + [ + -0.8203012325747281, + 0.0, + 0.007861886835802148, + 102.7502212524414 + ], + [ + 0.0, + -0.8203125, + 0.0, + 108.42950439453125 + ], + [ + 0.0042994758554704875, + 0.0, + 1.4999771317626835, + -64.7237777709961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999977350234985 + ], + "voxel_sha256": "977af6e8c237d9f7ff59519dcb4f6ced7e704e04e1f3eb4ec61ae258745d4b11" + }, + "mask": { + "affine": [ + [ + -0.8203011751174927, + -0.0003532599948812276, + 0.00786188617348671, + 102.7502212524414 + ], + [ + 0.0003532599948812276, + -0.8203124403953552, + -1.692836121947039e-06, + 108.42950439453125 + ], + [ + 0.004299475811421871, + 9.257711326426943e-07, + 1.4999771118164062, + -64.7237777709961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999977350234985 + ], + "tumour_voxels": 3666, + "volume_mm3": 3700.3407870466617, + "voxel_sha256": "fa2a2245462eb8dd20a6b5da3729b8fb49d1cabac68df25eb1a36c2c6358412d" + } + }, + { + "case_id": "crossmoda2022_etz_80", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 102.41593170166016 + ], + [ + 0.0, + -0.8203125, + 0.0, + 129.37245178222656 + ], + [ + 0.0, + 0.0, + 1.5, + -52.83695602416992 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "7858f5187f943d1842b160d812a6bec4789c9bfc6da56060fa8fc6e33c55734c" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 102.41593170166016 + ], + [ + 0.0, + -0.8203125, + 0.0, + 129.37245178222656 + ], + [ + 0.0, + 0.0, + 1.5, + -52.83695602416992 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 6237, + "volume_mm3": 6295.433807373047, + "voxel_sha256": "a04234cb8ac5177ce004416d2a5f553be9e10b6d227162a71769f72c8ecdcf7d" + } + }, + { + "case_id": "vs_gk_104", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.27666473388672 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.98907470703125 + ], + [ + 0.0, + 0.0, + 1.5, + -87.38380432128906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "29ee00727328e2f5cbbc401a0061b5637339a796143b43e08013106bc1c56b9a" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.27666473388672 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.98907470703125 + ], + [ + 0.0, + 0.0, + 1.5, + -87.38380432128906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 23019, + "volume_mm3": 5808.665657043457, + "voxel_sha256": "cf76e92c3111edcead63d76b9dfc26d46ffebfd74a4351a7a018ec6c1c7e1e54" + } + }, + { + "case_id": "crossmoda2022_etz_20", + "image": { + "affine": [ + [ + -0.8203054033524364, + 0.0, + -0.006239383074094017, + 106.91571807861328 + ], + [ + 0.0, + -0.8203125, + 0.0, + 109.80723571777344 + ], + [ + -0.003412167499789927, + 0.0, + 1.499984877524378, + -90.22762298583984 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.499997854232788 + ], + "voxel_sha256": "a8779fb8f674be6d59548dc842ef0f7c605118eba493cd446d73b531b688b82a" + }, + "mask": { + "affine": [ + [ + -0.8203054070472717, + 0.0, + -0.006239383015781641, + 106.91571807861328 + ], + [ + 0.0, + -0.8203125, + 0.0, + 109.80723571777344 + ], + [ + -0.00341216754168272, + 0.0, + 1.499984860420227, + -90.22762298583984 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.499997854232788 + ], + "tumour_voxels": 2543, + "volume_mm3": 2566.8214318870014, + "voxel_sha256": "1a137e1a5a06c2e68719047085aa4f196cfa4be77c4abd60459cfe8ef7ed16c3" + } + }, + { + "case_id": "vs_gk_29", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.81597900390625 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.15011596679688 + ], + [ + 0.0, + 0.0, + 1.0, + -76.4733657836914 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "7e5d485ce755c2569aeff608f8e360ccc3ea74c767aade698eb96821db4e07c5" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.81597900390625 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.15011596679688 + ], + [ + 0.0, + 0.0, + 1.0, + -76.4733657836914 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 19912, + "volume_mm3": 3349.7589111328125, + "voxel_sha256": "698c3982e5da7bccec540172709334edf54676664a5f83d941e4b1f6b6002cce" + } + }, + { + "case_id": "crossmoda2022_etz_15", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 107.1950454711914 + ], + [ + 0.0, + -0.8203125, + 0.0, + 113.96859741210938 + ], + [ + 0.0, + 0.0, + 1.5, + -110.73795318603516 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "f9ad71176efd5d5d322fcedd50f7548f8db270fcf53a4882446be4f31446398e" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 107.1950454711914 + ], + [ + 0.0, + -0.8203125, + 0.0, + 113.96859741210938 + ], + [ + 0.0, + 0.0, + 1.5, + -110.73795318603516 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 2202, + "volume_mm3": 2222.6303100585938, + "voxel_sha256": "1bb5af27d403b371fe342481e256b1c4e695ba7096ea0ccca22a3fe5be58bc8a" + } + }, + { + "case_id": "vs_gk_37", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.11822509765625 + ], + [ + 0.0, + 0.0, + 1.5, + -83.00984954833984 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "c1052486c13c23705dd731886250ff221bc7159a719846752cc6b1a5988333c3" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.11822509765625 + ], + [ + 0.0, + 0.0, + 1.5, + -83.00984954833984 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 8514, + "volume_mm3": 2148.441696166992, + "voxel_sha256": "b01a40c81ad05f0f82dc8b4bc41fc0b964ff6de1d50554cc3ef9b464916cf707" + } + }, + { + "case_id": "vs_gk_244", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.80587768554688 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.7234115600586 + ], + [ + 0.0, + 0.0, + 1.5, + -75.11957550048828 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "6ca2fa63b8cbac10702e0941574016494fb659ec64ddcde37ae13b170b3ce6e7" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.80587768554688 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.7234115600586 + ], + [ + 0.0, + 0.0, + 1.5, + -75.11957550048828 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3043, + "volume_mm3": 767.8773880004883, + "voxel_sha256": "bba020d9d0c0dd26a120c5e91ffe55595cb7d896937c847858fa0e1585e19312" + } + }, + { + "case_id": "vs_gk_151", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.44794464111328 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 117.22760009765625 + ], + [ + 0.0, + 0.0, + 1.5, + -129.80690002441406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "2893375488aa3cf721fd2b2ec91a7b9f0b2e7336f82d3dcd76872331865f90b5" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.44794464111328 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 117.22760009765625 + ], + [ + 0.0, + 0.0, + 1.5, + -129.80690002441406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 6071, + "volume_mm3": 1531.9696426391602, + "voxel_sha256": "cea590f8349505797b61d2aaa529da1e1bf54dbff4612c6cda47d58ff5431edc" + } + }, + { + "case_id": "vs_gk_96", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.63854217529297 + ], + [ + 0.0, + 0.0, + 1.5, + -69.20079040527344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "e9170cc0d407bdbf7608929a2d6f0ca65dfe1fe236027c18a96ea7c131507db3" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.63854217529297 + ], + [ + 0.0, + 0.0, + 1.5, + -69.20079040527344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2265, + "volume_mm3": 571.5551376342773, + "voxel_sha256": "663672c74ee05bca92b95f8ce293a5539541ca31c1db38921807fbe125bd1c5e" + } + }, + { + "case_id": "crossmoda2022_etz_93", + "image": { + "affine": [ + [ + -0.8202727834400679, + 0.0, + -0.014760405436091147, + 102.8947982788086 + ], + [ + 0.0, + -0.8203125, + 0.0, + 114.88855743408203 + ], + [ + -0.008072075553001726, + 0.0, + 1.499931309149357, + -72.76600646972656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000039339065552 + ], + "voxel_sha256": "4fe7c99c86b13e80b84b24c33773e8eeb76a3cf84a3c19df642612d1fbddf171" + }, + "mask": { + "affine": [ + [ + -0.8202728033065796, + 0.0, + -0.014760405756533146, + 102.8947982788086 + ], + [ + 0.0, + -0.8203125, + 0.0, + 114.88855743408203 + ], + [ + -0.008072075434029102, + 0.0, + 1.4999313354492188, + -72.76600646972656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000039339065552 + ], + "tumour_voxels": 1255, + "volume_mm3": 1266.7612872928657, + "voxel_sha256": "d895dbb16cb6da6e7c8250503ee90dbf1bcc8b9e8cb9628b0aab01e2a750644f" + } + }, + { + "case_id": "vs_gk_81", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.9565658569336 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.34442138671875 + ], + [ + 0.0, + 0.0, + 1.5, + -81.59747314453125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "b49288806ec938d9dd4f75d93c4b865a4c27125020032ddaffb3907e6366f188" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.9565658569336 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.34442138671875 + ], + [ + 0.0, + 0.0, + 1.5, + -81.59747314453125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 39749, + "volume_mm3": 10030.351066589355, + "voxel_sha256": "107ed65186869369cf581f002ffa438bd30f990b78d0e75992fe83e7f086d7eb" + } + }, + { + "case_id": "vs_gk_167", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.56895446777344 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.52032470703125 + ], + [ + 0.0, + 0.0, + 1.5, + -84.68104553222656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "14d92132a4448f0ede331d018658e328e029466874148a758b3d4a71d6db0224" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.56895446777344 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.52032470703125 + ], + [ + 0.0, + 0.0, + 1.5, + -84.68104553222656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 8898, + "volume_mm3": 2245.341110229492, + "voxel_sha256": "67502c6645112293c74331d5ebaeb8320d52a7ecad5795a384a23826ed9c3f39" + } + }, + { + "case_id": "vs_gk_133", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.73577880859375 + ], + [ + 0.0, + 0.0, + 1.5, + -78.37995910644531 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "965fe1414d9ede8fb240d773173ef93a22d5e9f1b528f4eefc2fc654f6c098aa" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.73577880859375 + ], + [ + 0.0, + 0.0, + 1.5, + -78.37995910644531 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 4839, + "volume_mm3": 1221.0840225219727, + "voxel_sha256": "1680cf35f42432918e1f1db3171e6d2f997bbf2dd011bcab62fe2a7adf4fc901" + } + }, + { + "case_id": "vs_gk_220", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.26239013671875 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 121.80896759033203 + ], + [ + 0.0, + 0.0, + 1.5, + -88.43440246582031 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "8151b9e77352b91c9574decfbcd78182a6be36ac979f5352d2230730ea28b5bf" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.26239013671875 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 121.80896759033203 + ], + [ + 0.0, + 0.0, + 1.5, + -88.43440246582031 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 21586, + "volume_mm3": 5447.05924987793, + "voxel_sha256": "188dab3fff61bf5d9bbd407b85806d882c3e62497edf5a8128540a815b890b52" + } + }, + { + "case_id": "vs_gk_195", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.07540893554688 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.4427490234375 + ], + [ + 0.0, + 0.0, + 1.5, + -82.33197784423828 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "5aa09e5745c7dc36358e7a94106d145c2701bb1978813dbc0c224ff8491cbc37" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.07540893554688 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.4427490234375 + ], + [ + 0.0, + 0.0, + 1.5, + -82.33197784423828 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 597, + "volume_mm3": 150.64830780029297, + "voxel_sha256": "183ded0993be6a75ceeab6ad28a57bc768dab4d67f4e4fc9bd41e06c0da09b77" + } + }, + { + "case_id": "vs_gk_163", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.64067077636719 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.57179260253906 + ], + [ + 0.0, + 0.0, + 1.5, + -88.5703353881836 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "9d03baf23078b0e48f70f55d7954d1dc9c5ab56a9cf96c1cb590b1345914a0ff" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.64067077636719 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.57179260253906 + ], + [ + 0.0, + 0.0, + 1.5, + -88.5703353881836 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1080, + "volume_mm3": 272.52960205078125, + "voxel_sha256": "80ade829991098e717c19a6c50d8bd8a6fa62219bdaa6aea3df179bc199a1892" + } + }, + { + "case_id": "crossmoda2022_etz_18", + "image": { + "affine": [ + [ + -0.8203101530679513, + -0.001962251228643349, + 0.0, + 101.71292877197266 + ], + [ + 0.001962251228643349, + -0.8203101530679513, + 0.0, + 123.09745025634766 + ], + [ + 0.0, + 0.0, + 1.5, + -61.23899841308594 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "6f421bd8042711b8922d69cdc38a2d6c6c63c79198d7ce3e97ddf1e6f2c34b1b" + }, + "mask": { + "affine": [ + [ + -0.8203101754188538, + -0.0019622512627393007, + 0.0, + 101.71292877197266 + ], + [ + 0.0019622512627393007, + -0.8203101754188538, + 0.0, + 123.09745025634766 + ], + [ + 0.0, + 0.0, + 1.5, + -61.23899841308594 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 586, + "volume_mm3": 591.4901733398438, + "voxel_sha256": "6bd5d8e30da9a1f32f95632b6b9d983e11a49242a17e8c94f8a882816f989b6e" + } + }, + { + "case_id": "crossmoda2022_etz_94", + "image": { + "affine": [ + [ + -0.8203055165884341, + 0.0, + -0.006189418261524208, + 103.87102508544922 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.24201965332031 + ], + [ + -0.0033848354217455453, + 0.0, + 1.4999884224158837, + -44.14423751831055 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000011920928955 + ], + "voxel_sha256": "a98124390d7aa17ca3a2ac903b87029ae9ac20a9b9c062231818856762b31e81" + }, + "mask": { + "affine": [ + [ + -0.8203054666519165, + -0.00030681968200951815, + -0.006189418025314808, + 103.87102508544922 + ], + [ + 0.00030681968200951815, + -0.8203124403953552, + 1.15751220164384e-06, + 120.24201965332031 + ], + [ + -0.0033848353195935488, + -6.330139399324253e-07, + 1.4999884366989136, + -44.14423751831055 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000011920928955 + ], + "tumour_voxels": 373, + "volume_mm3": 376.49489759969583, + "voxel_sha256": "225003f85ba91c64f29338e0ee728b277970d13d664c518a9ff7e259d7437222" + } + }, + { + "case_id": "crossmoda2022_etz_89", + "image": { + "affine": [ + [ + -0.8203099574903563, + -0.002042375680369798, + 0.0, + 102.24335479736328 + ], + [ + 0.002042375680369798, + -0.8203099574903563, + 0.0, + 124.65113830566406 + ], + [ + 0.0, + 0.0, + 1.5, + -53.94438934326172 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "94c6bb1d99cbc4dbfbded2cd3c44458f9c920229166e120f9d543d209b1de4e4" + }, + "mask": { + "affine": [ + [ + -0.8203099370002747, + -0.002042375737801194, + 0.0, + 102.24335479736328 + ], + [ + 0.002042375737801194, + -0.8203099370002747, + 0.0, + 124.65113830566406 + ], + [ + 0.0, + 0.0, + 1.5, + -53.94438934326172 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 3202, + "volume_mm3": 3231.9992065429688, + "voxel_sha256": "6b9ed17f4d37a7c0704146186f6ef01a25875a270ede426c31a8af1fefaf80d5" + } + }, + { + "case_id": "vs_gk_192", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 116.8388442993164 + ], + [ + 0.0, + 0.0, + 1.5, + -83.7967300415039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "8beec459faef1bec352634173f8fa112d0f6bab960201438395bddc7f929d457" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 116.8388442993164 + ], + [ + 0.0, + 0.0, + 1.5, + -83.7967300415039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 4685, + "volume_mm3": 1182.2233200073242, + "voxel_sha256": "b368ba30dd02b963c19208f7e0916f1c2859d7878393027c43d2bfb3c4a1c9bf" + } + }, + { + "case_id": "vs_gk_59", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.44037628173828 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 141.55030822753906 + ], + [ + 0.0, + 0.0, + 1.5, + -88.43653869628906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "389ef5dbc3cfd2c55dc7047ea73e8c1cac356250bb541bb32e20e0f17981f867" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.44037628173828 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 141.55030822753906 + ], + [ + 0.0, + 0.0, + 1.5, + -88.43653869628906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 9134, + "volume_mm3": 2304.8938751220703, + "voxel_sha256": "d038a455ead8441589f0913e5d60b7bdffe2406b297f9c1a7b863feaa8adfc00" + } + }, + { + "case_id": "vs_gk_26", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.42131042480469 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 140.2300262451172 + ], + [ + 0.0, + 0.0, + 1.5, + -89.8553237915039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "3a6946d2c7fe68540ffcb7a053626adc0cb14bec2e1718faa437c042e24f6589" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.42131042480469 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 140.2300262451172 + ], + [ + 0.0, + 0.0, + 1.5, + -89.8553237915039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 9143, + "volume_mm3": 2307.16495513916, + "voxel_sha256": "53e67e42b382a2466c33444193bda7afc796dfece222850c16b7f4151fbd7fba" + } + }, + { + "case_id": "vs_gk_245", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.84261322021484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.30750274658203 + ], + [ + 0.0, + 0.0, + 1.5, + -77.74878692626953 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "be3e1fa980cda11cb9e4fbac33b89fdbd31ebbaad1183d4865a3f04a79a0efe8" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.84261322021484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.30750274658203 + ], + [ + 0.0, + 0.0, + 1.5, + -77.74878692626953 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 14447, + "volume_mm3": 3645.5881118774414, + "voxel_sha256": "78b0a55a862dd61dceab7466465dfff280fc2ca12b6584f388499839606badd8" + } + }, + { + "case_id": "vs_gk_13", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.78934478759766 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.64891052246094 + ], + [ + 0.0, + 0.0, + 1.5, + -85.01271057128906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "4c0d8d36fb94341c2fc10bdc33e38f082af29128c125ce9f5962666bd651e780" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.78934478759766 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.64891052246094 + ], + [ + 0.0, + 0.0, + 1.5, + -85.01271057128906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2927, + "volume_mm3": 738.6056900024414, + "voxel_sha256": "81fadc50e4b07db9207678a311001e859ba179b6ed1578127da05d5633d8aa88" + } + }, + { + "case_id": "crossmoda2022_etz_19", + "image": { + "affine": [ + [ + -0.8202902042135882, + -0.00604526044594854, + -0.00033395151691647867, + 100.67525482177734 + ], + [ + 0.006045260296377259, + -0.8202902245439663, + 2.4590009641868396e-06, + 125.18021392822266 + ], + [ + -0.00018263468673298787, + -1.1529620372761647e-09, + 1.4999999628234455, + -60.69464111328125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "9c8643b1608f9513f72d6921380b5d9a955e141b887972213b469b284e1dd743" + }, + "mask": { + "affine": [ + [ + -0.820290207862854, + -0.006045260466635227, + -0.00033395152422599494, + 100.67525482177734 + ], + [ + 0.006045260466635227, + -0.820290207862854, + 2.4590010525571415e-06, + 125.18021392822266 + ], + [ + -0.00018263468518853188, + -1.1529620591588241e-09, + 1.5, + -60.69464111328125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 11348, + "volume_mm3": 11454.318237304688, + "voxel_sha256": "ebd2a6ff3d16160e34e380927f7e074b4ee8f5ab80bb6c97af2c62feadbb6305" + } + }, + { + "case_id": "vs_gk_234", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.67822265625 + ], + [ + 0.0, + 0.0, + 1.5, + -81.89025115966797 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "9b6f5c707fe313237d49ed8c3dd0465cd2630c467f6e84dd04c7fa2a38d230dd" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.67822265625 + ], + [ + 0.0, + 0.0, + 1.5, + -81.89025115966797 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 43546, + "volume_mm3": 10988.494491577148, + "voxel_sha256": "1a0652f8f565626c1ca9f6cfff31cfc315c23a268e594a938dadcbb6ef1707b1" + } + }, + { + "case_id": "vs_gk_169", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.82393646240234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.0476531982422 + ], + [ + 0.0, + 0.0, + 1.5, + -102.66371154785156 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "28ab9a777bc1413ceecb7a59e1e49fa708af8c1bf7723454e87484a3155caa53" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.82393646240234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.0476531982422 + ], + [ + 0.0, + 0.0, + 1.5, + -102.66371154785156 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3386, + "volume_mm3": 854.4307708740234, + "voxel_sha256": "73fa6c6e0b35a298817fc9820837c099ae1d0b62d4f3a84247bc88bff5978282" + } + }, + { + "case_id": "vs_gk_67", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 101.9733657836914 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.5447998046875 + ], + [ + 0.0, + 0.0, + 1.5, + -92.2766342163086 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "844041986922928b13dc93e91f3658b5c3400c813f1cd1f8289eb5b1716399d7" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 101.9733657836914 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.5447998046875 + ], + [ + 0.0, + 0.0, + 1.5, + -92.2766342163086 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 10733, + "volume_mm3": 2708.389091491699, + "voxel_sha256": "e3be0fbcdfcfa94e52f78d787c99db487337e77de99b64f63a003c2140cd69bb" + } + }, + { + "case_id": "crossmoda2022_etz_24", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 104.06879425048828 + ], + [ + 0.0, + -0.8203125, + 0.0, + 122.82632446289062 + ], + [ + 0.0, + 0.0, + 1.5, + -104.14353942871094 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "4a2c936a8ff83bb74e8c1f702386a751ad4decefc6c5401f416b0121aa94b7cd" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 104.06879425048828 + ], + [ + 0.0, + -0.8203125, + 0.0, + 122.82632446289062 + ], + [ + 0.0, + 0.0, + 1.5, + -104.14353942871094 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 3581, + "volume_mm3": 3614.550018310547, + "voxel_sha256": "c3edb2e6d2d0fa2a06180705ce00c86eeaaeca5d8af09fdd9eb4426df92e73db" + } + }, + { + "case_id": "crossmoda2022_etz_27", + "image": { + "affine": [ + [ + -0.82027115391801, + 0.0, + -0.015060108244168433, + 101.3062515258789 + ], + [ + 0.0, + -0.8203125, + 0.0, + 118.65798950195312 + ], + [ + -0.008236000623260744, + 0.0, + 1.4999236805161034, + -58.10286331176758 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999992847442627 + ], + "voxel_sha256": "2a3a050cb558f6d53d1f7b59fc9e9e42bc2f7f852d89f6502f87bb36a3286e1c" + }, + "mask": { + "affine": [ + [ + -0.8202710151672363, + -0.00043650585575960577, + -0.015060107223689556, + 101.3062515258789 + ], + [ + 0.00043650585575960577, + -0.8203123807907104, + 4.007004008599324e-06, + 118.65798950195312 + ], + [ + -0.008236000314354897, + -2.1913315322308335e-06, + 1.4999237060546875, + -58.10286331176758 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999992847442627 + ], + "tumour_voxels": 1029, + "volume_mm3": 1038.6400992199924, + "voxel_sha256": "c6e75f12b49607d2cae15978c1818b5ab9c0f9e7808cfa2a86a7fab414e04580" + } + }, + { + "case_id": "crossmoda2022_etz_3", + "image": { + "affine": [ + [ + -0.8203009609439399, + -0.004013172119973312, + 0.0030737290310357372, + 100.85750579833984 + ], + [ + 0.00401316367594343, + -0.820302683224787, + -1.5053779503663684e-05, + 109.28650665283203 + ], + [ + 0.0016809663913378607, + -8.834733942668415e-09, + 1.4999962546034038, + -63.016334533691406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999994039535522 + ], + "voxel_sha256": "d5077790a2d57e49e07fcc94977e4f21216d689ffe5fd283d5a8f01e54917e92" + }, + "mask": { + "affine": [ + [ + -0.8203009366989136, + -0.004013172350823879, + 0.003073729109019041, + 100.85750579833984 + ], + [ + 0.00401316350325942, + -0.8203026652336121, + -1.5053779861773364e-05, + 109.28650665283203 + ], + [ + 0.0016809663502499461, + -8.834733833396058e-09, + 1.499996304512024, + -63.016334533691406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999994039535522 + ], + "tumour_voxels": 420, + "volume_mm3": 423.93476806682884, + "voxel_sha256": "b924a65bfd3bd3356d413ff8c59b091c1faa4510c7f2487f62ef1b93b397b242" + } + }, + { + "case_id": "vs_gk_60", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.84261322021484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.0, + -63.76150131225586 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "007a42b0ec23de27a21c3c678f6f2d2c92ce935f367ce8f62369d9e231f90828" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.84261322021484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.0, + -63.76150131225586 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 1521, + "volume_mm3": 255.87501525878906, + "voxel_sha256": "d7e4c8e29132845fd0c5570f35813dfe9b01ac17feb5ede397ba03ae63551167" + } + }, + { + "case_id": "vs_gk_35", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.23728942871094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 137.8087158203125 + ], + [ + 0.0, + 0.0, + 1.5, + -88.03934478759766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "d59e74c394376e77a74afca3cf5a5707050f856a60df2096ca9a8b2eafde11a6" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.23728942871094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 137.8087158203125 + ], + [ + 0.0, + 0.0, + 1.5, + -88.03934478759766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 7260, + "volume_mm3": 1832.0045471191406, + "voxel_sha256": "ee8e57bd2870b6152106ee993dfa1cea0c94f3e6594ce05f089a46096c0fa964" + } + }, + { + "case_id": "vs_gk_198", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.52058410644531 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 113.2614974975586 + ], + [ + 0.0, + 0.0, + 1.5, + -87.59705352783203 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "6fddc52b990ca3274f4f88d6a934efaf3e69c92ee085b8dcba4f9d1025a13983" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.52058410644531 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 113.2614974975586 + ], + [ + 0.0, + 0.0, + 1.5, + -87.59705352783203 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5460, + "volume_mm3": 1377.7885437011719, + "voxel_sha256": "f9171201cad52543bf6bcb40be3b622a880fb4fff7e67649c50d94cec2445781" + } + }, + { + "case_id": "vs_gk_93", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.1366958618164 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 147.1584014892578 + ], + [ + 0.0, + 0.0, + 1.0, + -81.22661590576172 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "ceafe6345009273035cb74c125f80d7156fbe270e521d8e302253e64ba99c271" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.1366958618164 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 147.1584014892578 + ], + [ + 0.0, + 0.0, + 1.0, + -81.22661590576172 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 724, + "volume_mm3": 121.79718017578125, + "voxel_sha256": "cfaf84309912191100622242255c018f6a0853d340579e5833408235037efa8d" + } + }, + { + "case_id": "vs_gk_135", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.0266342163086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.67554473876953 + ], + [ + 0.0, + 0.0, + 1.5, + -77.14346313476562 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "964ade56a7bf9e7c80fe3ce956164f711dde2cb396af04c1d785fbce0bc1ba72" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.0266342163086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.67554473876953 + ], + [ + 0.0, + 0.0, + 1.5, + -77.14346313476562 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 4278, + "volume_mm3": 1079.520034790039, + "voxel_sha256": "60e564803275ea6a7873593095b3b32786c667aa92aba1e56957b5813e67ad09" + } + }, + { + "case_id": "vs_gk_170", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.3964614868164 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.8813247680664 + ], + [ + 0.0, + 0.0, + 1.5, + -88.40088653564453 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "4651fa692d4a4cef48090ee320b59d2c6e4a9579a90e9372bbd8159b3c06dc5c" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.3964614868164 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.8813247680664 + ], + [ + 0.0, + 0.0, + 1.5, + -88.40088653564453 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1013, + "volume_mm3": 255.62267303466797, + "voxel_sha256": "f10b98296b778a498c7babe5099878091405d0c90f89cceeff8e3bf35162090e" + } + }, + { + "case_id": "vs_gk_69", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.60465240478516 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.0700454711914 + ], + [ + 0.0, + 0.0, + 1.5, + -95.52904510498047 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "72e6d51e3f6c705fbc54fbab367662609fbd9d47fe9aefe88651e87c050f7010" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.60465240478516 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 124.0700454711914 + ], + [ + 0.0, + 0.0, + 1.5, + -95.52904510498047 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 8768, + "volume_mm3": 2212.53662109375, + "voxel_sha256": "135a9c2e8fd4767a3b1ff374dfa1b4af190cf9eb7bb6932c2afbd184e41fdbbe" + } + }, + { + "case_id": "vs_gk_142", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.26383209228516 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 116.62227630615234 + ], + [ + 0.0, + 0.0, + 1.5, + -85.6180419921875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "e3a675d9d56aa1b49d7103b07df23dde8f5840e423cd614b8ad7794bbeec0ee5" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.26383209228516 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 116.62227630615234 + ], + [ + 0.0, + 0.0, + 1.5, + -85.6180419921875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 39569, + "volume_mm3": 9984.929466247559, + "voxel_sha256": "6c2f90d0d1d5e361f72b400e56ac5f896a4c0b8f7949ca84d094ca41905f789a" + } + }, + { + "case_id": "vs_gk_52", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 133.57142639160156 + ], + [ + 0.0, + 0.0, + 1.5, + -72.30084991455078 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "9af2c2c791ebfa406f05c6dcdfc6de820a4612225cabe1dafd5a736af9c1d108" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 133.57142639160156 + ], + [ + 0.0, + 0.0, + 1.5, + -72.30084991455078 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 10093, + "volume_mm3": 2546.890068054199, + "voxel_sha256": "1af53a016f17f0ba63aa4df55d3c9624527024fbc515f0f6846d2ee813ab0dd4" + } + }, + { + "case_id": "vs_gk_222", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.00810241699219 + ], + [ + 0.0, + 0.0, + 1.5, + -81.92311096191406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "d7b2bcad620ffca57657342aece99b8eb39e5668b680b34dc2de0642becb5283" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.00810241699219 + ], + [ + 0.0, + 0.0, + 1.5, + -81.92311096191406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1375, + "volume_mm3": 346.9705581665039, + "voxel_sha256": "50fe7a6878f982012ff97c5a31e4af56faedbfc70cd80021ce629c71f25fb494" + } + }, + { + "case_id": "vs_gk_41", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.56134796142578 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 148.01083374023438 + ], + [ + 0.0, + 0.0, + 1.5, + -84.56595611572266 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "5f8d74ce39e195be1da25164e7ef655277237cb3e35be738345bc9004533440b" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.56134796142578 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 148.01083374023438 + ], + [ + 0.0, + 0.0, + 1.5, + -84.56595611572266 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2141, + "volume_mm3": 540.2647018432617, + "voxel_sha256": "89ce4ba077471510bbf5d3a250fb321d2870f01b78e73c74d73f0b3eb6d206d5" + } + }, + { + "case_id": "crossmoda2022_etz_92", + "image": { + "affine": [ + [ + -0.8203023742394803, + 0.0, + -0.007452962564706921, + 105.29219818115234 + ], + [ + 0.0, + -0.8203125, + 0.0, + 124.38945007324219 + ], + [ + -0.004075840198248803, + 0.0, + 1.4999810074923488, + -53.424259185791016 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999995231628418 + ], + "voxel_sha256": "606801863d07c261c83f7fc09ecf9e6bcf2148723e3b3e74703aef1a186fdbbe" + }, + "mask": { + "affine": [ + [ + -0.8203023076057434, + -0.00026948892627842724, + -0.007452962454408407, + 105.29219818115234 + ], + [ + 0.00026948892627842724, + -0.8203124403953552, + 1.224230686602823e-06, + 124.38945007324219 + ], + [ + -0.00407584011554718, + -6.695013325952459e-07, + 1.4999810457229614, + -53.424259185791016 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999995231628418 + ], + "tumour_voxels": 3711, + "volume_mm3": 3745.7667841059447, + "voxel_sha256": "3497b3893fda2f3c12b509ffead2935bdc3da777203658c25edf389d9470ca87" + } + }, + { + "case_id": "vs_gk_79", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.07591247558594 + ], + [ + 0.0, + 0.0, + 1.0, + -76.76535034179688 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "7cfe5068ae22e85321470f8112c31605996b703d5fbb28ed44e04404b655a00d" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.07591247558594 + ], + [ + 0.0, + 0.0, + 1.0, + -76.76535034179688 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 1724, + "volume_mm3": 290.02532958984375, + "voxel_sha256": "5fa448ba498e393e0d7255b1b4cef8b04bc21a2167caf48132f1ad7e1632be92" + } + }, + { + "case_id": "vs_gk_28", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.21065521240234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 115.89588165283203 + ], + [ + 0.0, + 0.0, + 1.5, + -82.4703369140625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "fd09e4918a3afdf01f68ea291c989ced6903b2c873666303baff32f09d3a2fb3" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.21065521240234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 115.89588165283203 + ], + [ + 0.0, + 0.0, + 1.5, + -82.4703369140625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1814, + "volume_mm3": 457.74879455566406, + "voxel_sha256": "ad99d519fe3dd5bcb86a616b1bd91d7ab2ac5f03f6e3d6dbb4bb188eb22c1d77" + } + }, + { + "case_id": "vs_gk_48", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.60502624511719 + ], + [ + 0.0, + 0.0, + 1.5, + -77.77120971679688 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "1da74374764c8bb2d51b7a13930998447de3eccc9e84a3dc22644d46c2eac365" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.60502624511719 + ], + [ + 0.0, + 0.0, + 1.5, + -77.77120971679688 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 8041, + "volume_mm3": 2029.0838241577148, + "voxel_sha256": "50e32336fdd056d3489135869dada5261e4561d745e878e73373f124c61ccf43" + } + }, + { + "case_id": "vs_gk_162", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.95065307617188 + ], + [ + 0.0, + 0.0, + 1.5, + -84.26464080810547 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "c39f979bd98695f33b93f9a3ff9eef6c526641535e7c0ed197c0d8755b8f913c" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.95065307617188 + ], + [ + 0.0, + 0.0, + 1.5, + -84.26464080810547 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2475, + "volume_mm3": 624.547004699707, + "voxel_sha256": "50105af10e1a7edd7f2cf8150eb08a7b4a2d2d027caf9d28e33694627fd21031" + } + }, + { + "case_id": "vs_gk_141", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.78934478759766 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 138.14382934570312 + ], + [ + 0.0, + 0.0, + 1.0, + -66.18280792236328 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "b5ba2bedb99eceefe6cb7af1b039efe836785bc118c1c57c75d2805a8e48ac07" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.78934478759766 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 138.14382934570312 + ], + [ + 0.0, + 0.0, + 1.0, + -66.18280792236328 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 462, + "volume_mm3": 77.72140502929688, + "voxel_sha256": "f2b1cb31e5335d4f53deddd2d336c38cc8065ded84a1643666e7d12c86c11984" + } + }, + { + "case_id": "vs_gk_228", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.44794464111328 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.12039947509766 + ], + [ + 0.0, + 0.0, + 1.5, + -83.28190612792969 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "01b85cce9214addf44cdd152f8c3d33cb66176e6f38ffced9f7f8eeeb410d682" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.44794464111328 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.12039947509766 + ], + [ + 0.0, + 0.0, + 1.5, + -83.28190612792969 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 28138, + "volume_mm3": 7100.405502319336, + "voxel_sha256": "69841c92a166ad6d7e04cd81bac47cb658cd620624db00d6923e7caeb0ca6e60" + } + }, + { + "case_id": "vs_gk_17", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -89.8553237915039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "ad1b3cd5d0e8a20580d228f3f949ffa07b1d9c12e25ecedabea41a20b1ca876c" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -89.8553237915039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 23440, + "volume_mm3": 5914.9017333984375, + "voxel_sha256": "3a9f561f131d7d4f04f666e51680e903e58a9744daf52837a84a6018bc3b4603" + } + }, + { + "case_id": "crossmoda2022_etz_11", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 100.94254302978516 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.0948486328125 + ], + [ + 0.0, + 0.0, + 1.5, + -37.27104568481445 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "8310d78fc24da7e06e6409c03ff294dfea74e5ea83ccb2ef80f2b5648e4cc587" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 100.94254302978516 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.0948486328125 + ], + [ + 0.0, + 0.0, + 1.5, + -37.27104568481445 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 5513, + "volume_mm3": 5564.650726318359, + "voxel_sha256": "f2db357e31bb65c90fddf4389b8f878d1d14db7f5531f42f67fae6f8fdf42eab" + } + }, + { + "case_id": "vs_gk_232", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.23728942871094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.88619995117188 + ], + [ + 0.0, + 0.0, + 1.5, + -97.11924743652344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "dc8058027b3d12305ef64498e163a999d4bad4cbe030ffb5ff4455905516ceb9" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.23728942871094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.88619995117188 + ], + [ + 0.0, + 0.0, + 1.5, + -97.11924743652344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 4551, + "volume_mm3": 1148.4094619750977, + "voxel_sha256": "69ab88a851dad1a4674c05b6bbd7fe61830039d6dd5b95d0b3865ac70bb48815" + } + }, + { + "case_id": "vs_gk_139", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.68961334228516 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 118.29412841796875 + ], + [ + 0.0, + 0.0, + 1.5, + -81.87078094482422 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "82e33baee6952e433a1c9555fb2278b9abcebc1eb639ade449e284b5d190776f" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.68961334228516 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 118.29412841796875 + ], + [ + 0.0, + 0.0, + 1.5, + -81.87078094482422 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2529, + "volume_mm3": 638.1734848022461, + "voxel_sha256": "2a2f2171b17f266cb5ae13463b0b8bca814929727ad48373ae230e4af0e1ad67" + } + }, + { + "case_id": "vs_gk_247", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.87937927246094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.68866729736328 + ], + [ + 0.0, + 0.0, + 1.5, + -73.29167938232422 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "36499180719507962806dfcf7c894ed749f993b0d3b564744ac17ab6d7778bcc" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 110.87937927246094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.68866729736328 + ], + [ + 0.0, + 0.0, + 1.5, + -73.29167938232422 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5053, + "volume_mm3": 1275.0852584838867, + "voxel_sha256": "977be59034897246bcb150770ac450dfacaaed5aaab5af51ad4c22dd8c8c6847" + } + }, + { + "case_id": "vs_gk_132", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.67596435546875 + ], + [ + 0.0, + 0.0, + 1.5, + -87.56561279296875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "12ab1da0d94875fe009be6ca72baa898d9821ee1a7ae9a8e3cae62151acf373b" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.67596435546875 + ], + [ + 0.0, + 0.0, + 1.5, + -87.56561279296875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1700, + "volume_mm3": 428.9817810058594, + "voxel_sha256": "fd957c343e5f9d4b66aa24a41777613aeeb65093ed7985d41f29754f303e3ebe" + } + }, + { + "case_id": "vs_gk_111", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.50086212158203 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 116.44932556152344 + ], + [ + 0.0, + 0.0, + 1.0, + -74.24699401855469 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "276e58075e0b87049f5ec46f6e7ee1aad1703571f89b6786bc122309a418c31f" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.50086212158203 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 116.44932556152344 + ], + [ + 0.0, + 0.0, + 1.0, + -74.24699401855469 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 576, + "volume_mm3": 96.8994140625, + "voxel_sha256": "de26c98b2c04c5b79245272335d68d216d83a5a0733e3021d3ebb0b0fa6fdc07" + } + }, + { + "case_id": "crossmoda2022_etz_99", + "image": { + "affine": [ + [ + -0.8203055968326162, + 0.0, + 0.0061537548510765105, + 102.87506866455078 + ], + [ + 0.0, + -0.8203125, + 0.0, + 122.3052749633789 + ], + [ + 0.0033653322771092474, + 0.0, + 1.4999884499399327, + -43.95235061645508 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.500001072883606 + ], + "voxel_sha256": "22db06e3044efe8cdb91c5180cd47207c3db8a59533010d14b7e470b1f25f3ee" + }, + "mask": { + "affine": [ + [ + -0.820305585861206, + 0.0, + 0.006153754889965057, + 102.87506866455078 + ], + [ + 0.0, + -0.8203125, + 0.0, + 122.3052749633789 + ], + [ + 0.0033653322607278824, + 0.0, + 1.4999884366989136, + -43.95235061645508 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.500001072883606 + ], + "tumour_voxels": 3877, + "volume_mm3": 3913.326010696801, + "voxel_sha256": "2cd7a2d975dade0b61b5c64f8fa83834dec8b45ae4b9c65164ea2a1449b35dee" + } + }, + { + "case_id": "crossmoda2022_etz_39", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.02672576904297 + ], + [ + 0.0, + -0.8203125, + 0.0, + 123.868408203125 + ], + [ + 0.0, + 0.0, + 1.5, + -84.68588256835938 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "b62d9357a6f4e757b834cbc9043e54568c22c72432813b2377a173759c411418" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.02672576904297 + ], + [ + 0.0, + -0.8203125, + 0.0, + 123.868408203125 + ], + [ + 0.0, + 0.0, + 1.5, + -84.68588256835938 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 6393, + "volume_mm3": 6452.895355224609, + "voxel_sha256": "2d957ca341ef70110c95e9560a8d2133ec82ce87d3c6e1c3facd3b3161e03c3a" + } + }, + { + "case_id": "vs_gk_84", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.42810821533203 + ], + [ + 0.0, + 0.0, + 1.5, + -75.6765365600586 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "d715cf573a5f2a3842b667092b89d4262c7572db7d8f7efef4a4eec1871119e4" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.42810821533203 + ], + [ + 0.0, + 0.0, + 1.5, + -75.6765365600586 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2281, + "volume_mm3": 575.5926132202148, + "voxel_sha256": "a34325fa3f162d75e41d1abad5e85d448b589c63d5c7f7bb5a1a1a1561373372" + } + }, + { + "case_id": "crossmoda2022_etz_71", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.5463638305664 + ], + [ + 0.0, + -0.8203125, + 0.0, + 124.15505981445312 + ], + [ + 0.0, + 0.0, + 1.5, + -93.7065200805664 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "a128809189baf6aa8c513e18757c5a810f68e04187b9bc2a1ba705a72f692b71" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.5463638305664 + ], + [ + 0.0, + -0.8203125, + 0.0, + 124.15505981445312 + ], + [ + 0.0, + 0.0, + 1.5, + -93.7065200805664 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 4988, + "volume_mm3": 5034.7320556640625, + "voxel_sha256": "794296ff33bc974fb4e179aa59cab8e162eb27dff5485a74c0bb5ac25037dc21" + } + }, + { + "case_id": "vs_gk_4", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.6053237915039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 140.83534240722656 + ], + [ + 0.0, + 0.0, + 1.5, + -69.8795394897461 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "74b2139096e80069022056ac1c93cd6f9a86321437a5c0c5ef8700616387c9d8" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.6053237915039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 140.83534240722656 + ], + [ + 0.0, + 0.0, + 1.5, + -69.8795394897461 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3034, + "volume_mm3": 765.6063079833984, + "voxel_sha256": "d744c4decca714bc04bf1612c6d327185d380f4e4e69d74b7e5bb075bb593f96" + } + }, + { + "case_id": "vs_gk_76", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 101.9733657836914 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 127.51815795898438 + ], + [ + 0.0, + 0.0, + 1.5, + -79.56476593017578 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "699bd7aaaac708d6ba6031cb4c40698e407538ae86d4f709584f8f953a752ac7" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 101.9733657836914 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 127.51815795898438 + ], + [ + 0.0, + 0.0, + 1.5, + -79.56476593017578 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 29837, + "volume_mm3": 7529.134941101074, + "voxel_sha256": "2bfbca514dfa1694e0583d6c822a27c349e9a0dfdadfc75b048928069806380a" + } + }, + { + "case_id": "vs_gk_174", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.93141174316406 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.70369720458984 + ], + [ + 0.0, + 0.0, + 1.5, + -73.33694458007812 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "27b7b899ddfa7b80879f17bb54326289b799949cac45e6b12cdcc659379c5370" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.93141174316406 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.70369720458984 + ], + [ + 0.0, + 0.0, + 1.5, + -73.33694458007812 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1199, + "volume_mm3": 302.5583267211914, + "voxel_sha256": "58be49230bf531472c3bed6360fb7594a85eafcc8f091abd07f8a014f99492b1" + } + }, + { + "case_id": "vs_gk_16", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.81597900390625 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 140.2300262451172 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "be910251cf71bd4444038203ece9d52687ddab2e176dd0254f2b078ab740a280" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.81597900390625 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 140.2300262451172 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3445, + "volume_mm3": 869.318962097168, + "voxel_sha256": "fb3e67b276680a9c27535c8f49625685f3cf598659294b5607328f58c5426bad" + } + }, + { + "case_id": "crossmoda2022_etz_101", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 102.41608428955078 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.67664337158203 + ], + [ + 0.0, + 0.0, + 1.5, + -60.66304397583008 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "c353b8ec39892b84580dd63a9717f46e5f72329a76e91717d0b50ae88e6b572e" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 102.41608428955078 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.67664337158203 + ], + [ + 0.0, + 0.0, + 1.5, + -60.66304397583008 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 322, + "volume_mm3": 325.01678466796875, + "voxel_sha256": "adfe673f3211d7fe7f7ff05dffa338a072475e3cf23339ce290df987424fd954" + } + }, + { + "case_id": "vs_gk_64", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.57868957519531 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 143.8619842529297 + ], + [ + 0.0, + 0.0, + 1.5, + -68.06356048583984 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "89c6f9821f8a445558908fc9277b91fcdd3309588017b55712fa4d78ee33dbeb" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.57868957519531 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 143.8619842529297 + ], + [ + 0.0, + 0.0, + 1.5, + -68.06356048583984 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 9804, + "volume_mm3": 2473.963165283203, + "voxel_sha256": "5b63fc6d166c2918f3e7c7b7e180c15b49ae059ac5d75190fe13fdf2e1182538" + } + }, + { + "case_id": "vs_gk_243", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.2926254272461 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.15721893310547 + ], + [ + 0.0, + 0.0, + 1.5, + -90.10852813720703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "9123bbd6e16575c021122a3ebffea6c0bde67cc516079547782602b41baef017" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.2926254272461 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.15721893310547 + ], + [ + 0.0, + 0.0, + 1.5, + -90.10852813720703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2973, + "volume_mm3": 750.2134323120117, + "voxel_sha256": "f16d7c38ca2552b4f65631f5e0e9c20936c7709eef76b931d8a650498f968e1a" + } + }, + { + "case_id": "vs_gk_173", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 111.05326843261719 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 134.78207397460938 + ], + [ + 0.0, + 0.0, + 1.5, + -88.6446762084961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "9f45f7df6ba3fa6737c4a17c62734536046acaefff70c2325b9d2041089134bb" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 111.05326843261719 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 134.78207397460938 + ], + [ + 0.0, + 0.0, + 1.5, + -88.6446762084961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 27232, + "volume_mm3": 6871.783447265625, + "voxel_sha256": "79d6923819f138cde4bdfcfb017a41550b9a82994071cb104b72cc12fca98383" + } + }, + { + "case_id": "vs_gk_180", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.82109069824219 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.30615234375 + ], + [ + 0.0, + 0.0, + 1.5, + -74.7867202758789 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "5d8757459858f15ac8681a60d89021956605dc56d1554e50c7e654c9aeda2a5c" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.82109069824219 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.30615234375 + ], + [ + 0.0, + 0.0, + 1.5, + -74.7867202758789 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2142, + "volume_mm3": 540.5170440673828, + "voxel_sha256": "a53bfb44e8f916798536d0289e784471fe62009985ae94b81140b52f4051166b" + } + }, + { + "case_id": "vs_gk_112", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 101.486083984375 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.96324157714844 + ], + [ + 0.0, + 0.0, + 1.0, + -63.14604568481445 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "9baf47655499e7a1ca1b0176865ee665a5f9d6b4d46602bc1bd9e4a236c148b2" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 101.486083984375 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.96324157714844 + ], + [ + 0.0, + 0.0, + 1.0, + -63.14604568481445 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 590, + "volume_mm3": 99.25460815429688, + "voxel_sha256": "afd87ef95e7c9b0a6edf41de1edff06dbbf2c20ae43ec3322c3179e99d392dda" + } + }, + { + "case_id": "crossmoda2022_etz_83", + "image": { + "affine": [ + [ + -0.8203021611327688, + 0.0, + 0.007530983019583028, + 104.4040298461914 + ], + [ + 0.0, + -0.8203125, + 0.0, + 128.5577850341797 + ], + [ + 0.004118506666143971, + 0.0, + 1.49998097543499, + -62.80570983886719 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999998807907104 + ], + "voxel_sha256": "18bf413db7a159a6debaca9fcf6aced0ec8db7d6d79bbb83878cb6c613f7f496" + }, + "mask": { + "affine": [ + [ + -0.8203021287918091, + -0.0002094392984872684, + 0.007530983071774244, + 104.4040298461914 + ], + [ + 0.0002094392984872684, + -0.8203125, + -9.613980864742189e-07, + 128.5577850341797 + ], + [ + 0.004118506796658039, + 5.257646193967958e-07, + 1.4999809265136719, + -62.80570983886719 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999998807907104 + ], + "tumour_voxels": 698, + "volume_mm3": 704.5394337543257, + "voxel_sha256": "ccec65d420c562599b97a9cb24f2da99212b08e8f8098c035a565ee45bda2ff8" + } + }, + { + "case_id": "crossmoda2022_etz_38", + "image": { + "affine": [ + [ + -0.820302369036517, + 0.0, + -0.007454877100956614, + 103.72911834716797 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.61589813232422 + ], + [ + -0.00407688721059319, + 0.0, + 1.4999809979783618, + -53.94516372680664 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999995231628418 + ], + "voxel_sha256": "d6c0439418460f2f749147d9adb0b678dc2fc578340ffb0b0a2b66195119b625" + }, + "mask": { + "affine": [ + [ + -0.8203023076057434, + -0.0002531564387027174, + -0.007454876322299242, + 103.72911834716797 + ], + [ + 0.0002531564387027174, + -0.8203124403953552, + 1.1503310588523163e-06, + 117.61589813232422 + ], + [ + -0.004076886922121048, + -6.290874239311961e-07, + 1.4999810457229614, + -53.94516372680664 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999995231628418 + ], + "tumour_voxels": 3616, + "volume_mm3": 3649.8767694225535, + "voxel_sha256": "2fa2347a0b344de512a70ea058135a1afc16d0c1b31174d6f677093227989733" + } + }, + { + "case_id": "vs_gk_138", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.42131042480469 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.33413696289062 + ], + [ + 0.0, + 0.0, + 1.5, + -94.69794464111328 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "80aad96c856f17f04eb1d3644c1d4f6ca1d9ed1ab4b1dcc84599ed36232e6e5f" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.42131042480469 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.33413696289062 + ], + [ + 0.0, + 0.0, + 1.5, + -94.69794464111328 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 16302, + "volume_mm3": 4113.68293762207, + "voxel_sha256": "653bafedc0f756c71d0669452ca2be4424026e6ddeba1cbff17ef040ffac2c11" + } + }, + { + "case_id": "vs_gk_202", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.6319580078125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -97.11924743652344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "d2382f856bc3b3299e63a027cdcccea1ddc404a0a1c4c1aae9fa391b94c419db" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.6319580078125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -97.11924743652344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 12523, + "volume_mm3": 3160.081672668457, + "voxel_sha256": "c89f6dee587db88ef4a8c0405bde623ae54b997e06f15a596f27539e08945b95" + } + }, + { + "case_id": "crossmoda2022_etz_14", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.98462677001953 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.74214935302734 + ], + [ + 0.0, + 0.0, + 1.5, + -56.02855682373047 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "7afe39b927c733bfc8b2e1b405ffa8f473c6800721d02915fe260b03eca498e1" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.98462677001953 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.74214935302734 + ], + [ + 0.0, + 0.0, + 1.5, + -56.02855682373047 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 1568, + "volume_mm3": 1582.6904296875, + "voxel_sha256": "95dfa6911bc57302075451eef0808897b0b4ee2755f78afc1194ad7608ddcfb6" + } + }, + { + "case_id": "vs_gk_182", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.6319580078125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.0702133178711 + ], + [ + 0.0, + 0.0, + 1.5, + -88.6446762084961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "23e8653c1d31d4879d10d064e487b91843dc7ec7ab4a1bc5953daf246503b46f" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.6319580078125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.0702133178711 + ], + [ + 0.0, + 0.0, + 1.5, + -88.6446762084961 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 781, + "volume_mm3": 197.07927703857422, + "voxel_sha256": "8b400c1563348648c194f997ac56cc7ae1380e9e151a9d04532bcaa38ca667a1" + } + }, + { + "case_id": "crossmoda2022_etz_28", + "image": { + "affine": [ + [ + -0.8203027572469125, + 0.0, + -0.007310664867376952, + 103.20458984375 + ], + [ + 0.0, + -0.8203125, + 0.0, + 124.91048431396484 + ], + [ + -0.003998013176942424, + 0.0, + 1.4999846880454166, + -52.39218521118164 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000025033950806 + ], + "voxel_sha256": "98e48c436c07c3517a190d24466f887ff7fcfd665667ed7853cd31a0376a8d68" + }, + "mask": { + "affine": [ + [ + -0.8203027248382568, + -0.00024343037512153387, + -0.007310664746910334, + 103.20458984375 + ], + [ + 0.00024343037512153387, + -0.8203124403953552, + 1.0847380735867773e-06, + 124.91048431396484 + ], + [ + -0.003998013213276863, + -5.932151339038683e-07, + 1.4999847412109375, + -52.39218521118164 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000025033950806 + ], + "tumour_voxels": 2323, + "volume_mm3": 2344.7678597802224, + "voxel_sha256": "4d52992f15a08d854d9c08651d6bfa83b931e402adcc5eaf6144a47582c54adf" + } + }, + { + "case_id": "vs_gk_51", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.23728942871094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.67554473876953 + ], + [ + 0.0, + 0.0, + 1.5, + -79.56476593017578 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "26a304d37feafe7b1447e5be16225848aaf057efde096161268caf1b2c409c28" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.23728942871094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 122.67554473876953 + ], + [ + 0.0, + 0.0, + 1.5, + -79.56476593017578 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1809, + "volume_mm3": 456.4870834350586, + "voxel_sha256": "79bd556552f4256d1f9f39e6eb24e45682693d5b2fb76061d503ccd0c96b5583" + } + }, + { + "case_id": "crossmoda2022_etz_57", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.54774475097656 + ], + [ + 0.0, + -0.8203125, + 0.0, + 113.44756317138672 + ], + [ + 0.0, + 0.0, + 1.5, + -58.11272430419922 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "2990f1a1dc9682d18273ce53b51b49262ab7b16b9adcf1b54145df1bd9e70412" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.54774475097656 + ], + [ + 0.0, + -0.8203125, + 0.0, + 113.44756317138672 + ], + [ + 0.0, + 0.0, + 1.5, + -58.11272430419922 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 268, + "volume_mm3": 270.5108642578125, + "voxel_sha256": "06cec1d1711ae15f58cb783c18603458d71f0d8fd7c61e346851906b4dc08b7a" + } + }, + { + "case_id": "vs_gk_217", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 111.18309020996094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.9825210571289 + ], + [ + 0.0, + 0.0, + 1.5, + -81.5211410522461 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "0e4d3f5e1fb22a33c0a01fcaedff0a307cc1140320d4aa4b6b281eda08f761f7" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 111.18309020996094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.9825210571289 + ], + [ + 0.0, + 0.0, + 1.5, + -81.5211410522461 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 21349, + "volume_mm3": 5387.2541427612305, + "voxel_sha256": "4c9ac8e3658be3f34f9af671dec2b01818fe7c9377b8b49631f75e3236e47226" + } + }, + { + "case_id": "vs_gk_179", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.88067626953125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.39112854003906 + ], + [ + 0.0, + 0.0, + 1.5, + -70.60107421875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "fd766b9849b62ff801e55010f9bd38196e0155654435127456716c423d7edea1" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.88067626953125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.39112854003906 + ], + [ + 0.0, + 0.0, + 1.5, + -70.60107421875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 10469, + "volume_mm3": 2641.7707443237305, + "voxel_sha256": "c90e5bad74bb4624c57a195ce38112946e553102d4523a87399d6480d3279276" + } + }, + { + "case_id": "vs_gk_214", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.2348861694336 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.29517364501953 + ], + [ + 0.0, + 0.0, + 1.5, + -80.17106628417969 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "15b2fca91899db7fa63a8009220f3d8aa87381c40e235c786072ef4d8d58424a" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.2348861694336 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.29517364501953 + ], + [ + 0.0, + 0.0, + 1.5, + -80.17106628417969 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3786, + "volume_mm3": 955.3676605224609, + "voxel_sha256": "47ead586baf521f4af9e18346a59ff5af5381a4ebfc6ddbdb925b9e94c3313e1" + } + }, + { + "case_id": "crossmoda2022_etz_29", + "image": { + "affine": [ + [ + -0.8203054586005541, + 0.0, + -0.006215063428712267, + 103.00208282470703 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.80723571777344 + ], + [ + -0.0033988595711719432, + 0.0, + 1.4999885547973515, + -41.96854019165039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000014305114746 + ], + "voxel_sha256": "a7e391ee17f96b32686f21257c463256ae774ef24102016778d81972f73bbe85" + }, + "mask": { + "affine": [ + [ + -0.8203054666519165, + 0.0, + -0.006215063389390707, + 103.00208282470703 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.80723571777344 + ], + [ + -0.003398859640583396, + 0.0, + 1.4999885559082031, + -41.96854019165039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000014305114746 + ], + "tumour_voxels": 315, + "volume_mm3": 317.9515056144737, + "voxel_sha256": "72fe48992348732860987b592a26151d904d5fd84fd0cbe018da74f7fc3044a0" + } + }, + { + "case_id": "crossmoda2022_etz_64", + "image": { + "affine": [ + [ + -0.8202992283257461, + -0.0026672859547707, + 0.007001134050142828, + 101.47525024414062 + ], + [ + 0.003288363443107392, + -0.8079844575983459, + 0.2590057329989233, + 131.6494598388672 + ], + [ + 0.003310638956695493, + 0.1416566254059169, + 1.4774549380856177, + -65.858642578125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000020265579224 + ], + "voxel_sha256": "77615eb15998a45a3acd74a574c04481e37f8795e371635a07a74ca8a5961230" + }, + "mask": { + "affine": [ + [ + -0.8202992081642151, + -0.002667285967618227, + 0.007001134101301432, + 101.47525024414062 + ], + [ + 0.00328836333937943, + -0.807984471321106, + 0.25900572538375854, + 131.6494598388672 + ], + [ + 0.0033106389455497265, + 0.14165662229061127, + 1.4774549007415771, + -65.858642578125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000020265579224 + ], + "tumour_voxels": 2571, + "volume_mm3": 2595.090938924659, + "voxel_sha256": "4f1c01082b22ba6a7a585e1e67e81d843cdd455517d6b6b73fd962ad2b559d26" + } + }, + { + "case_id": "vs_gk_74", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 136.77243041992188 + ], + [ + 0.0, + 0.0, + 1.5, + -82.71757507324219 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "6dddb00e49f8ceb6a9f3dbda431e50c81ef3b2c59374430f1a556deac1a31ca2" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 136.77243041992188 + ], + [ + 0.0, + 0.0, + 1.5, + -82.71757507324219 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 6648, + "volume_mm3": 1677.5711059570312, + "voxel_sha256": "a2d476ed8773c61cdea5cc54471a554cfb6d21d142b242d7a27095c015e6e51f" + } + }, + { + "case_id": "crossmoda2022_etz_13", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 100.94255065917969 + ], + [ + 0.0, + -0.8203125, + 0.0, + 105.63191986083984 + ], + [ + 0.0, + 0.0, + 1.5, + -41.960426330566406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "ec809bbbe1b365f27ab12f22432174e797a62ce67d7fdddfe7a7b640afe51d00" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 100.94255065917969 + ], + [ + 0.0, + -0.8203125, + 0.0, + 105.63191986083984 + ], + [ + 0.0, + 0.0, + 1.5, + -41.960426330566406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 2671, + "volume_mm3": 2696.0243225097656, + "voxel_sha256": "af0e898a5ab877a7425b8208724e73a3be2b8ec3281881735704cabdf6c0e190" + } + }, + { + "case_id": "crossmoda2022_etz_47", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 105.63192749023438 + ], + [ + 0.0, + -0.8203125, + 0.0, + 128.5577850341797 + ], + [ + 0.0, + 0.0, + 1.5, + 19.001495361328125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "cb7d5069843803a4ab42fc9220a9d06508416a6dee1d65e7de827ca9bcf79a3b" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 105.63192749023438 + ], + [ + 0.0, + -0.8203125, + 0.0, + 128.5577850341797 + ], + [ + 0.0, + 0.0, + 1.5, + 19.001495361328125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 1358, + "volume_mm3": 1370.7229614257812, + "voxel_sha256": "b2fb4094260897fc5c6d03ba435fea04be59dee0af8ea2711945f000fd6fa722" + } + }, + { + "case_id": "vs_gk_45", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.6053237915039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.09684753417969 + ], + [ + 0.0, + 0.0, + 1.5, + -85.01271057128906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "b2dbaa0aa2096234e004ec7603b3f9b025b171aaf47bc057be470acfce481cab" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.6053237915039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.09684753417969 + ], + [ + 0.0, + 0.0, + 1.5, + -85.01271057128906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 8768, + "volume_mm3": 2212.53662109375, + "voxel_sha256": "ddea2b8bc9de2ef771c5f542e22bee7f086ca416fe301d4b237d2b8b08878e89" + } + }, + { + "case_id": "vs_gk_108", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 136.99435424804688 + ], + [ + 0.0, + 0.0, + 1.5, + -81.44607543945312 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "d3d991567dcf4440ae3d5540163cb83fcb9566bbc77d8ac00c8c1285ea3bfd58" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 136.99435424804688 + ], + [ + 0.0, + 0.0, + 1.5, + -81.44607543945312 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 29684, + "volume_mm3": 7490.526580810547, + "voxel_sha256": "322e6b7e047610557d5b7030276db3f53897a8ce90b18cc85cda8927c4818a68" + } + }, + { + "case_id": "vs_gk_117", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.48536682128906 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.30486297607422 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "bf262c71c55ba62a52c39aab9fff6af550b473868e48b326dc61384363bcfa6a" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.48536682128906 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.30486297607422 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3959, + "volume_mm3": 999.0228652954102, + "voxel_sha256": "05d7a959d187c8c7d7bb5c0a070cb816e221e6bb47bf19b5cf98ffa358829b66" + } + }, + { + "case_id": "vs_gk_42", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 134.23623657226562 + ], + [ + 0.0, + 0.0, + 1.5, + -91.04148864746094 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "10ee9ecf3955113bfee6396e0880def31a4a49bca709997ddcf18e25ea08ec42" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 134.23623657226562 + ], + [ + 0.0, + 0.0, + 1.5, + -91.04148864746094 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1760, + "volume_mm3": 444.122314453125, + "voxel_sha256": "4f92fff41c7bcd8d04fa47c8ce8d6bc706bf3e2f0e36bb4427aa45502baf2678" + } + }, + { + "case_id": "crossmoda2022_etz_26", + "image": { + "affine": [ + [ + -0.8202491295797519, + 0.0, + -0.01864450138210819, + 102.42985534667969 + ], + [ + 0.0, + -0.8203125, + 0.0, + 121.5463638305664 + ], + [ + -0.010196228710132945, + 0.0, + 1.4998816194584284, + -62.403602600097656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999974966049194 + ], + "voxel_sha256": "99048b8705472d2144e0c1238b6f26761c28e954cd0bc8169a79444d48e30ae8" + }, + "mask": { + "affine": [ + [ + -0.8202491402626038, + 0.0, + -0.01864450052380562, + 102.42985534667969 + ], + [ + 0.0, + -0.8203125, + 0.0, + 121.5463638305664 + ], + [ + -0.010196228511631489, + 0.0, + 1.499881625175476, + -62.403602600097656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999974966049194 + ], + "tumour_voxels": 908, + "volume_mm3": 916.5054284218058, + "voxel_sha256": "6571afae8694f4a23e08f231c1dc51ead7493fd75138f143c1f6ac0a064465a2" + } + }, + { + "case_id": "crossmoda2022_etz_52", + "image": { + "affine": [ + [ + -0.8203124999999077, + 0.0, + 7.112950015652641e-07, + 100.94254302978516 + ], + [ + 0.0, + -0.8203125, + 0.0, + 110.84234619140625 + ], + [ + 3.889894539810038e-07, + 0.0, + 1.4999999999998312, + -48.21297836303711 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "a765d470b8b19c605a98410cafecc67b1b33849528bf7a6dfa5ed5d0803c327d" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 7.112950015653041e-07, + 100.94254302978516 + ], + [ + 0.0, + -0.8203125, + 0.0, + 110.84234619140625 + ], + [ + 3.889894628628099e-07, + 0.0, + 1.5, + -48.21297836303711 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 5061, + "volume_mm3": 5108.415985107422, + "voxel_sha256": "c9ba41e67d1a257450d4807aa7a00ace71e8ad12fd5edad83f57c88bf7c95bc3" + } + }, + { + "case_id": "crossmoda2022_etz_55", + "image": { + "affine": [ + [ + -0.8201291057741626, + 0.0, + -0.03171648444701744, + 106.21308135986328 + ], + [ + 0.0, + -0.8203125, + 0.0, + 128.5028839111328 + ], + [ + -0.017344956567322562, + 0.0, + 1.4996642930105537, + -49.31290054321289 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999996423721313 + ], + "voxel_sha256": "bdb0f1feb38856259b7e3e5c2faf529d1b33fc9151171c55f1ce71b038237961" + }, + "mask": { + "affine": [ + [ + -0.8201290369033813, + -0.0002936699893325567, + -0.0317164845764637, + 106.21308135986328 + ], + [ + 0.0002936699893325567, + -0.8203124403953552, + 5.6778490034048446e-06, + 128.5028839111328 + ], + [ + -0.01734495535492897, + -3.1050742563820677e-06, + 1.499664306640625, + -49.31290054321289 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999996423721313 + ], + "tumour_voxels": 3168, + "volume_mm3": 3197.6799016760197, + "voxel_sha256": "6196ab0f25a792c6d9ca5b0a0b15bf8035918854f987be2ff5e414be66db8dd7" + } + }, + { + "case_id": "vs_gk_7", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.18402099609375 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.15011596679688 + ], + [ + 0.0, + 0.0, + 1.5, + -97.11924743652344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "809d8a912d89c4253fdbd9616eaab63f7f2b6ebc37e63c565321f4a878d23799" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.18402099609375 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.15011596679688 + ], + [ + 0.0, + 0.0, + 1.5, + -97.11924743652344 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 21908, + "volume_mm3": 5528.313446044922, + "voxel_sha256": "5304c1e68884646d17af61ff7a9929be2ede3e6c4119b5a0e4e31a0f632db2ad" + } + }, + { + "case_id": "vs_gk_113", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 112.1705551147461 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.322265625 + ], + [ + 0.0, + 0.0, + 1.5, + -71.75776672363281 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "8eb7a66636e5ff3dfd7f091d873179e929473a4b9a38492696d6774603b8016e" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 112.1705551147461 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 130.322265625 + ], + [ + 0.0, + 0.0, + 1.5, + -71.75776672363281 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2404, + "volume_mm3": 606.6307067871094, + "voxel_sha256": "fa745d7fff6b2572bdb492522f29eacd94d6f62d0deb8b6472246bfbc8b0e4bb" + } + }, + { + "case_id": "crossmoda2022_etz_1", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 104.58982849121094 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.61589813232422 + ], + [ + 0.0, + 0.0, + 1.5, + -49.25501251220703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "532132e3ca2271f63afc7ef9c0e1477d7b68d59cb84582291ea2ccccf1fb9289" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 104.58982849121094 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.61589813232422 + ], + [ + 0.0, + 0.0, + 1.5, + -49.25501251220703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 354, + "volume_mm3": 357.31658935546875, + "voxel_sha256": "c73d0a710e6802d3e52bedc0e28d52fbfea457541f95bc4badebab0250f7b163" + } + }, + { + "case_id": "vs_gk_231", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.53193664550781 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 109.63773345947266 + ], + [ + 0.0, + 0.0, + 1.5, + -81.30314636230469 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "b4253745030e6f6ac7aea549390d4ee9ca3ce02291ffd5196668ab5df9b13eba" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.53193664550781 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 109.63773345947266 + ], + [ + 0.0, + 0.0, + 1.5, + -81.30314636230469 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 10563, + "volume_mm3": 2665.4909133911133, + "voxel_sha256": "5750943d581294f7e36c6803835acb5e93ce32685ce0d983558d63c89ad79285" + } + }, + { + "case_id": "vs_gk_14", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.78934478759766 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.15011596679688 + ], + [ + 0.0, + 0.0, + 1.5, + -74.11682891845703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "17f7af98463fd18bfdcc7c97b54f3b08eed3f482a9934378b767844b42470916" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 103.78934478759766 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.15011596679688 + ], + [ + 0.0, + 0.0, + 1.5, + -74.11682891845703 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5128, + "volume_mm3": 1294.0109252929688, + "voxel_sha256": "2dadc0f720f7e3fb9a8ad99c24da3c0f8b29a04ae6f50c54cb51802ab217cbec" + } + }, + { + "case_id": "crossmoda2022_etz_84", + "image": { + "affine": [ + [ + -0.8203023667107913, + 0.0, + 0.0074557131866556815, + 102.84275817871094 + ], + [ + 0.0, + -0.8203125, + 0.0, + 128.0367431640625 + ], + [ + 0.004077355138398022, + 0.0, + 1.4999770598676476, + -71.13703155517578 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999955892562866 + ], + "voxel_sha256": "38719dd887f9e0bb10a705968f3d79720187e19ade96b6b67561c2fe7feff31c" + }, + "mask": { + "affine": [ + [ + -0.8203023076057434, + -0.0002455046633258462, + 0.007455713115632534, + 102.84275817871094 + ], + [ + 0.0002455046633258462, + -0.8203124403953552, + -1.1156867003592197e-06, + 128.0367431640625 + ], + [ + 0.004077354911714792, + 6.101429903537792e-07, + 1.4999771118164062, + -71.13703155517578 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999955892562866 + ], + "tumour_voxels": 331, + "volume_mm3": 334.1001223134299, + "voxel_sha256": "2d657f124077a7b6f1ff0d969885d83dda4338c37fabfe089d4757c6aba43c1d" + } + }, + { + "case_id": "vs_gk_145", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.98389434814453 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.33660125732422 + ], + [ + 0.0, + 0.0, + 1.5, + -64.652587890625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "336594745d4d2c1ea98d489c80a66c0cda9dc6852caf19270cfe84962499872c" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.98389434814453 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.33660125732422 + ], + [ + 0.0, + 0.0, + 1.5, + -64.652587890625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 4289, + "volume_mm3": 1082.295799255371, + "voxel_sha256": "18ce641d637e78511e21a01f61f5641be9c6b5eb509e6d0d5e454fc84182460b" + } + }, + { + "case_id": "vs_gk_205", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.11863708496094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 115.41966247558594 + ], + [ + 0.0, + 0.0, + 1.5, + -74.83477783203125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "7f91be94d723441d1dd18d095cbb929e5cb35c825dbfc3fbecec3c14ecd1f382" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.11863708496094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 115.41966247558594 + ], + [ + 0.0, + 0.0, + 1.5, + -74.83477783203125 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1979, + "volume_mm3": 499.38526153564453, + "voxel_sha256": "fc2f8f97419257631fd24e27e0d419a164386696c405b1d47afc500c3a072647" + } + }, + { + "case_id": "vs_gk_235", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.23728942871094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -142.51876831054688 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "5c298f1105faf7c7d9fa38db744c9079f925f31f13b33cb780a359dcfbcfc19e" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.23728942871094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.93946838378906 + ], + [ + 0.0, + 0.0, + 1.5, + -142.51876831054688 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2285, + "volume_mm3": 576.6019821166992, + "voxel_sha256": "05cd54c3a3ba0e3dcc441bd0472320a915928138f78dcf3bc89dba353bb34ade" + } + }, + { + "case_id": "vs_gk_114", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.6319580078125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.15011596679688 + ], + [ + 0.0, + 0.0, + 1.5, + -86.2233657836914 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "5be0a2e7174ebecc459f8665ee057dee650ad8533f05c215686b946040746b31" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.6319580078125 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.15011596679688 + ], + [ + 0.0, + 0.0, + 1.5, + -86.2233657836914 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 19373, + "volume_mm3": 4888.625907897949, + "voxel_sha256": "92aaa90369293dadde3652f61a3b283e7438ff7ed0c07aa885513ae159c39840" + } + }, + { + "case_id": "vs_gk_62", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.35635375976562 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 147.0117950439453 + ], + [ + 0.0, + 0.0, + 1.5, + -74.6943359375 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "0b6a5cf68de43581cfa6eb3283ce807a82a0396e30e1e78271106a303f47efa5" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.35635375976562 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 147.0117950439453 + ], + [ + 0.0, + 0.0, + 1.5, + -74.6943359375 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 13184, + "volume_mm3": 3326.8798828125, + "voxel_sha256": "935d89d2d044b20fcc1e53edf029027c9e2d27bedb781135ca884278813e7e1e" + } + }, + { + "case_id": "vs_gk_38", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.23728942871094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.7288055419922 + ], + [ + 0.0, + 0.0, + 1.5, + -90.46065521240234 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "1d835418be6babe963c0bb7ca83a364a026d21e0a584e8eff930c97cfa9269f9" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.23728942871094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.7288055419922 + ], + [ + 0.0, + 0.0, + 1.5, + -90.46065521240234 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 26650, + "volume_mm3": 6724.920272827148, + "voxel_sha256": "0bdd12fd8514b14294a7854fabc84c0e54435828157a034e438f72772c74a84b" + } + }, + { + "case_id": "vs_gk_207", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.29460906982422 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 115.56456756591797 + ], + [ + 0.0, + 0.0, + 1.5, + -103.56535339355469 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "9be762f4a91374fcb3d638cd9655904d282b3b5430f95fa8cedce0d4b97644e4" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.29460906982422 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 115.56456756591797 + ], + [ + 0.0, + 0.0, + 1.5, + -103.56535339355469 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 15355, + "volume_mm3": 3874.7148513793945, + "voxel_sha256": "c0d51705b6663fa15fa1f0628708c368d21b0180cdcb9372d9723f9439208157" + } + }, + { + "case_id": "vs_gk_186", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 111.05326843261719 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 114.20096588134766 + ], + [ + 0.0, + 0.0, + 1.5, + -77.74878692626953 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "d3622450dad23995ce0651e79942029ce12fe9444c063645a0d202cf9b5168f4" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 111.05326843261719 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 114.20096588134766 + ], + [ + 0.0, + 0.0, + 1.5, + -77.74878692626953 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 9278, + "volume_mm3": 2341.231155395508, + "voxel_sha256": "1b7e6c0e30f929295b91a8296c38a4bb682897e3c55c828354768cdc803a64df" + } + }, + { + "case_id": "vs_gk_206", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.7554473876953 + ], + [ + 0.0, + 0.0, + 1.5, + -86.05399322509766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "a616cee34702673fbdf0ca97c173261079c4a28f96f6ce2b0daef9b7034fcc4d" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 131.7554473876953 + ], + [ + 0.0, + 0.0, + 1.5, + -86.05399322509766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 18985, + "volume_mm3": 4790.717124938965, + "voxel_sha256": "1e3b8d913631b681d225061f873db11855d1f8f2a23c8753399187e24bac577d" + } + }, + { + "case_id": "vs_gk_105", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 141.440673828125 + ], + [ + 0.0, + 0.0, + 1.5, + -88.03934478759766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "5967583509553cd46642a28f5d5778e7004d610f264673712c61db36573e221d" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 141.440673828125 + ], + [ + 0.0, + 0.0, + 1.5, + -88.03934478759766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3597, + "volume_mm3": 907.6749801635742, + "voxel_sha256": "5162b310cbf9adfa627863fd141935aab1493f47ad1ab11772077ec87ee913da" + } + }, + { + "case_id": "crossmoda2022_etz_103", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.2854995727539 + ], + [ + 0.0, + -0.8203125, + 0.0, + 118.50288391113281 + ], + [ + 0.0, + 0.0, + 1.5, + -55.44565200805664 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "221bb4e3c6529ecd68a36bd77db8340e0fede39621751a2466ff852f665cb376" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.2854995727539 + ], + [ + 0.0, + -0.8203125, + 0.0, + 118.50288391113281 + ], + [ + 0.0, + 0.0, + 1.5, + -55.44565200805664 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 8430, + "volume_mm3": 8508.979797363281, + "voxel_sha256": "86ebc97daa9c8c8c671dabecde62e0b673b30493b48469a37797242785f8e808" + } + }, + { + "case_id": "vs_gk_30", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.0266342163086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.30750274658203 + ], + [ + 0.0, + 0.0, + 1.5, + -103.17251586914062 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "f4c8328679bf0d9be8a308d3e3e08e6c20d46b734a197872e51bc4eb453024bf" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.0266342163086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.30750274658203 + ], + [ + 0.0, + 0.0, + 1.5, + -103.17251586914062 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 31478, + "volume_mm3": 7943.228530883789, + "voxel_sha256": "39a1cff0fb6ed4b19fe3466f85674968d9044ba16b1e1f2fdac3977ca61c8a8c" + } + }, + { + "case_id": "crossmoda2022_etz_44", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.54776763916016 + ], + [ + 0.0, + -0.8203125, + 0.0, + 123.86840057373047 + ], + [ + 0.0, + 0.0, + 1.5, + -34.665836334228516 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "60ddae7216e7a8770cf145dd5e6934b23e972cc34adff5bc6ea1a10155329e13" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.54776763916016 + ], + [ + 0.0, + -0.8203125, + 0.0, + 123.86840057373047 + ], + [ + 0.0, + 0.0, + 1.5, + -34.665836334228516 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 2074, + "volume_mm3": 2093.4310913085938, + "voxel_sha256": "4309b45535d36c7e4b5d5bb0fb4ce5447cddc004d8a8f30261171bb1496c3b74" + } + }, + { + "case_id": "crossmoda2022_etz_12", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 106.67399597167969 + ], + [ + 0.0, + -0.8203125, + 0.0, + 128.0367431640625 + ], + [ + 0.0, + 0.0, + 1.5, + -48.73396682739258 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "7a2c28732b7ae19ce59fb2d64d8054238993586633e2e2cbfa18bfd033f9ffe8" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 106.67399597167969 + ], + [ + 0.0, + -0.8203125, + 0.0, + 128.0367431640625 + ], + [ + 0.0, + 0.0, + 1.5, + -48.73396682739258 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 795, + "volume_mm3": 802.4482727050781, + "voxel_sha256": "e207df4cb2630a5e4e6a2d77df120523be46f808c2e1e56cc8f502d272ecc2e8" + } + }, + { + "case_id": "vs_gk_89", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.8875732421875 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.65111541748047 + ], + [ + 0.0, + 0.0, + 1.5, + -77.92059326171875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "c8761d51634344d00956f5bccab9be00c8a9e99899cdff4def6f1041d865aa45" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.8875732421875 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.65111541748047 + ], + [ + 0.0, + 0.0, + 1.5, + -77.92059326171875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 13384, + "volume_mm3": 3377.3483276367188, + "voxel_sha256": "2ab99c125bb3a9f9d30d76e2af4adec03ca28c8d63c25cb029485e8f75dc59bf" + } + }, + { + "case_id": "crossmoda2022_etz_48", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.0267105102539 + ], + [ + 0.0, + -0.8203125, + 0.0, + 122.30526733398438 + ], + [ + 0.0, + 0.0, + 1.5, + -44.04458999633789 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "b9854111afa0878e368482ccacd27dfd3e8cc624f99d78d1d7a71b75eec2fcf5" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.0267105102539 + ], + [ + 0.0, + -0.8203125, + 0.0, + 122.30526733398438 + ], + [ + 0.0, + 0.0, + 1.5, + -44.04458999633789 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 294, + "volume_mm3": 296.75445556640625, + "voxel_sha256": "c3d60fc6668d858c6ccc30b9872fce5323072dd9b92a13f518f1a44c08c2dcfe" + } + }, + { + "case_id": "vs_gk_94", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.7326889038086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.00810241699219 + ], + [ + 0.0, + 0.0, + 1.5, + -99.50764465332031 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "b43ed2a7da326b618af212cbd2cb6061cf2314c8271c3bd85d42b41d2e05fb80" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.7326889038086 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 120.00810241699219 + ], + [ + 0.0, + 0.0, + 1.5, + -99.50764465332031 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2707, + "volume_mm3": 683.0904006958008, + "voxel_sha256": "8f497146edeca7553ccb6f307cd2355bc813333dcb0dd9ebdaeed652dbce2106" + } + }, + { + "case_id": "crossmoda2022_etz_45", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 104.58984375 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.74214172363281 + ], + [ + 0.0, + 0.0, + 1.5, + -57.59168243408203 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "4e4f4703ae7774d1618fa177f561c2c7f627529e5b5409ad46fa7a2f7c1556ec" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 104.58984375 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.74214172363281 + ], + [ + 0.0, + 0.0, + 1.5, + -57.59168243408203 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 2869, + "volume_mm3": 2895.879364013672, + "voxel_sha256": "a6a3152c571cbc4216638a5dcbdfc5518704a94b996322e8b992fb9456dcad86" + } + }, + { + "case_id": "crossmoda2022_etz_96", + "image": { + "affine": [ + [ + -0.8203054207752596, + 0.0, + -0.006231734172073189, + 103.87205505371094 + ], + [ + 0.0, + -0.8203125, + 0.0, + 118.50288391113281 + ], + [ + -0.003407976375252985, + 0.0, + 1.4999884856310326, + -53.27172088623047 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000014305114746 + ], + "voxel_sha256": "c21e0e9458c7a57773c7cbceec2deeafbe840f738657072a938837480552dd58" + }, + "mask": { + "affine": [ + [ + -0.8203054070472717, + 0.0, + -0.006231734063476324, + 103.87205505371094 + ], + [ + 0.0, + -0.8203125, + 0.0, + 118.50288391113281 + ], + [ + -0.003407976357266307, + 0.0, + 1.4999884366989136, + -53.27172088623047 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000014305114746 + ], + "tumour_voxels": 1687, + "volume_mm3": 1702.8069522908481, + "voxel_sha256": "8000ed745bb466edafff488486c140db0e3437873c7b06cea2f65107f2b1b565" + } + }, + { + "case_id": "crossmoda2022_etz_88", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.98462677001953 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.17901611328125 + ], + [ + 0.0, + 0.0, + 1.5, + -58.6337776184082 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "6b93954123b57b7b8c322844644eff2d7fabab4c3c00572bc773b69766030cb9" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.98462677001953 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.17901611328125 + ], + [ + 0.0, + 0.0, + 1.5, + -58.6337776184082 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 1012, + "volume_mm3": 1021.4813232421875, + "voxel_sha256": "55c45794309792b7b66ec37f6ba3d2ff51f08ed68a9b39ab73f4e6394ca35cd8" + } + }, + { + "case_id": "crossmoda2022_etz_65", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.11157989501953 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.80723571777344 + ], + [ + 0.0, + 0.0, + 1.5, + -88.92391204833984 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "b5c68fd30dc81f81b51d5ed1bdf1bce7bd958887f2334e412a96e16a47c8c981" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.11157989501953 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.80723571777344 + ], + [ + 0.0, + 0.0, + 1.5, + -88.92391204833984 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 3504, + "volume_mm3": 3536.82861328125, + "voxel_sha256": "7a4d9ab19c565d63adc8c9651c69d609a6e340d7f8e311bbc1dbfc473b298eba" + } + }, + { + "case_id": "crossmoda2022_etz_53", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.72027587890625 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.24201965332031 + ], + [ + 0.0, + 0.0, + 1.5, + -64.14130401611328 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "4e9717d6b2b6d06b3a40b5e87dea38e1d6f088eefdc6ff1a46ae74d6cb566a95" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.72027587890625 + ], + [ + 0.0, + -0.8203125, + 0.0, + 120.24201965332031 + ], + [ + 0.0, + 0.0, + 1.5, + -64.14130401611328 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 457, + "volume_mm3": 461.2815856933594, + "voxel_sha256": "27e09be2754009fcc4203324e4aaa3ec98e70a9c3f53f1f38b47cad93d607b87" + } + }, + { + "case_id": "crossmoda2022_etz_74", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.98114776611328 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.19853973388672 + ], + [ + 0.0, + 0.0, + 1.5, + -88.92391204833984 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "bb6a85bb066364ce189340492b4c69aa7f5822cc9ece09a00254597e2b498c2e" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.98114776611328 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.19853973388672 + ], + [ + 0.0, + 0.0, + 1.5, + -88.92391204833984 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 3679, + "volume_mm3": 3713.4681701660156, + "voxel_sha256": "0ddd9ae5ce1eafa8aa87a6ae8a4083375b2246ce284f17a1b6cf3f12ab12dcdf" + } + }, + { + "case_id": "crossmoda2022_etz_8", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.46360778808594 + ], + [ + 0.0, + -0.8203125, + 0.0, + 108.75818634033203 + ], + [ + 0.0, + 0.0, + 1.5, + -37.27104568481445 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "34f09cab303d8279d2b849d67a4039845fd323c078da94a543515ab1dfe61006" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.46360778808594 + ], + [ + 0.0, + -0.8203125, + 0.0, + 108.75818634033203 + ], + [ + 0.0, + 0.0, + 1.5, + -37.27104568481445 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 516, + "volume_mm3": 520.8343505859375, + "voxel_sha256": "a887af547928668e184f3f9e10ddef10de722d7ae2c28fc6ab3911bb5422ebe9" + } + }, + { + "case_id": "vs_gk_197", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 113.47457885742188 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.70217895507812 + ], + [ + 0.0, + 0.0, + 1.5, + -83.80205535888672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "515dc36832b3513183d97a5e16005016742e8e7d68ac654baf20032604c6f02b" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 113.47457885742188 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.70217895507812 + ], + [ + 0.0, + 0.0, + 1.5, + -83.80205535888672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 7681, + "volume_mm3": 1938.240623474121, + "voxel_sha256": "851c2314aaab7ad66a25498373db7f9b1126df105b08c10536500f052bea0f55" + } + }, + { + "case_id": "vs_gk_126", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.05703735351562 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.69131469726562 + ], + [ + 0.0, + 0.0, + 1.5, + -77.11132049560547 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "3a00f6f776505c518c4c74b7fc389e7ba03e73e5a78302f78bffe55d260533cd" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.05703735351562 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.69131469726562 + ], + [ + 0.0, + 0.0, + 1.5, + -77.11132049560547 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 17838, + "volume_mm3": 4501.28059387207, + "voxel_sha256": "512fd1ac9ea5e403229ba202fe6aec6d419090c11dbc70e9323a7b29390a34b6" + } + }, + { + "case_id": "crossmoda2022_etz_50", + "image": { + "affine": [ + [ + -0.8203099574903563, + -0.002042375680369798, + 0.0, + 103.28670501708984 + ], + [ + 0.002042375680369798, + -0.8203099574903563, + 0.0, + 114.75005340576172 + ], + [ + 0.0, + 0.0, + 1.5, + -52.381256103515625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "a847702cf0bdce3ac0eeefc83b98d25dc5324c4c51b7a6186e8bf8e45369b7cc" + }, + "mask": { + "affine": [ + [ + -0.8203099370002747, + -0.002042375737801194, + 0.0, + 103.28670501708984 + ], + [ + 0.002042375737801194, + -0.8203099370002747, + 0.0, + 114.75005340576172 + ], + [ + 0.0, + 0.0, + 1.5, + -52.381256103515625 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 952, + "volume_mm3": 960.919189453125, + "voxel_sha256": "aa71d6661582bf8017aa39a8e853256c701dbbf68017db7b915ae89455b1d557" + } + }, + { + "case_id": "vs_gk_55", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 137.20338439941406 + ], + [ + 0.0, + 0.0, + 1.5, + -89.8553237915039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "e915f13a707f1c2d2319f6ca3fb98d3ce13fcc8fede46cf0023d80b2244dabbf" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 137.20338439941406 + ], + [ + 0.0, + 0.0, + 1.5, + -89.8553237915039 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5529, + "volume_mm3": 1395.2001571655273, + "voxel_sha256": "ff13ddcf63369cd075155111ab04e0abb1a1e70f29da1f461f765b0c0329bf2e" + } + }, + { + "case_id": "vs_gk_12", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.84261322021484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 138.41404724121094 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "c39e065c21154eb7511cf2954e49aeca716b0bfc4691d513c4d2d35b663197f6" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.84261322021484 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 138.41404724121094 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 6685, + "volume_mm3": 1686.9077682495117, + "voxel_sha256": "76c950c1b36ddd2be9fdfe611b78641933b2f7179b18bf7869a6bdee02108e85" + } + }, + { + "case_id": "vs_gk_2", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.07536315917969 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.8135528564453 + ], + [ + 0.0, + 0.0, + 1.5, + -78.16703796386719 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "3829738e340fcea290c194d99483849d9ab8f9dea1d5b2bc934c81bda3a236a1" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.07536315917969 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.8135528564453 + ], + [ + 0.0, + 0.0, + 1.5, + -78.16703796386719 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 4682, + "volume_mm3": 1181.466293334961, + "voxel_sha256": "339ec9e7a51e1c254e7a2c60b6878ed6c50958b381eada7b7bf15b9925e0f6aa" + } + }, + { + "case_id": "vs_gk_134", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.68315887451172 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.624267578125 + ], + [ + 0.0, + 0.0, + 1.5, + -81.67579650878906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "df28d2988d04578f10079d8649ca2587b11ad2e81cee00a092da4973315f2806" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.68315887451172 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.624267578125 + ], + [ + 0.0, + 0.0, + 1.5, + -81.67579650878906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 13542, + "volume_mm3": 3417.2183990478516, + "voxel_sha256": "8cc733934d302549c9b470dc4d6329c8d6929d3f6d112bcaa7b00d7a8fe2d690" + } + }, + { + "case_id": "vs_gk_32", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 101.3680419921875 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 135.3874053955078 + ], + [ + 0.0, + 0.0, + 1.5, + -90.46065521240234 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "4d5fee575cb41168aa1bd67daffdf15c34d837e500a330712d54d14cf253640b" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 101.3680419921875 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 135.3874053955078 + ], + [ + 0.0, + 0.0, + 1.5, + -90.46065521240234 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 14411, + "volume_mm3": 3636.503791809082, + "voxel_sha256": "040353b274bd30daa96477c77f13fc690d1a5d8ea0a511072be5f52154d9bfa6" + } + }, + { + "case_id": "vs_gk_66", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.42131042480469 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 143.25665283203125 + ], + [ + 0.0, + 0.0, + 1.5, + -74.72215270996094 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "490d1907b825e1d5e5599486f342137e2d0d7c506b3bb5e2519d3bd87eb72509" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.42131042480469 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 143.25665283203125 + ], + [ + 0.0, + 0.0, + 1.5, + -74.72215270996094 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 29617, + "volume_mm3": 7473.619651794434, + "voxel_sha256": "b721681dc402f725821087547d675d1ac7306b3c11007ae1bdadb3e46c9bc40d" + } + }, + { + "case_id": "vs_gk_27", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.6928482055664 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 138.38665771484375 + ], + [ + 0.0, + 0.0, + 1.5, + -87.57618713378906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "02fcddedec6755cfa251ddf20aad1bfc083374a224963aba067c23e9f90034ef" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.6928482055664 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 138.38665771484375 + ], + [ + 0.0, + 0.0, + 1.5, + -87.57618713378906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5420, + "volume_mm3": 1367.6948547363281, + "voxel_sha256": "fce209fcf2f149f69cae224ed934426626e550acddf827e3eb3dbcea374d9080" + } + }, + { + "case_id": "vs_gk_1", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.32681274414062 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.0744400024414 + ], + [ + 0.0, + 0.0, + 1.5, + -92.00067138671875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "f970fa80ad96e1cf9b599713d4a42a6e9d7e82a70040e8723bfc3430df96e429" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.32681274414062 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.0744400024414 + ], + [ + 0.0, + 0.0, + 1.5, + -92.00067138671875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 14540, + "volume_mm3": 3669.055938720703, + "voxel_sha256": "83b953423bcca0133bdad0e3e7316a4c28f41f10520736285ff419f930596451" + } + }, + { + "case_id": "crossmoda2022_etz_5", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.5477523803711 + ], + [ + 0.0, + -0.8203125, + 0.0, + 115.53172302246094 + ], + [ + 0.0, + 0.0, + 1.5, + -50.29709243774414 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "8d5d9ad92dc17e585820927c805eb42266d78017ad4982f3b9a7862a7bb284a2" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 103.5477523803711 + ], + [ + 0.0, + -0.8203125, + 0.0, + 115.53172302246094 + ], + [ + 0.0, + 0.0, + 1.5, + -50.29709243774414 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 330, + "volume_mm3": 333.09173583984375, + "voxel_sha256": "d7aec824fe4259b601066f5f4bac2c89ae6b58d865fbf4ee3a07b5bfff3cff90" + } + }, + { + "case_id": "crossmoda2022_etz_81", + "image": { + "affine": [ + [ + -0.8202845664297926, + 0.0, + -0.012378743585106362, + 103.15043640136719 + ], + [ + 0.0, + -0.8203125, + 0.0, + 111.98114776611328 + ], + [ + -0.006769618404092904, + 0.0, + 1.4999504711396134, + -51.10305404663086 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000015497207642 + ], + "voxel_sha256": "ba6809b77f601d2704e26c353d518886420c02146a4352a94e2d1e17e669cc26" + }, + "mask": { + "affine": [ + [ + -0.8202845454216003, + -0.00023592865909449756, + -0.012378742918372154, + 103.15043640136719 + ], + [ + 0.00023592865909449756, + -0.8203124403953552, + 1.7801448848331347e-06, + 111.98114776611328 + ], + [ + -0.006769618485122919, + -9.735157391332905e-07, + 1.4999505281448364, + -51.10305404663086 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000015497207642 + ], + "tumour_voxels": 2009, + "volume_mm3": 2027.824208075799, + "voxel_sha256": "6f96beff169b8609b7aed7f248d177038c9c3d09d05093714aa6b75069e53e94" + } + }, + { + "case_id": "vs_gk_196", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.33413696289062 + ], + [ + 0.0, + 0.0, + 1.5, + -80.77542114257812 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "6b4a0d0f3a89634894f99ae1160b7ea42fb369e40564d370b89fac402d58135c" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.0 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.33413696289062 + ], + [ + 0.0, + 0.0, + 1.5, + -80.77542114257812 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 3140, + "volume_mm3": 792.3545837402344, + "voxel_sha256": "4e7b91b0da1e3e9e7ffd607d829e092499c989287b41405da68ab86d6f599f24" + } + }, + { + "case_id": "vs_gk_101", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.21065521240234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.33413696289062 + ], + [ + 0.0, + 0.0, + 1.5, + -82.5914077758789 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "f224c887c77933f4443bc3d3129aa3c21d0de02d665b23b5fdc71d8de50937a9" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.21065521240234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 129.33413696289062 + ], + [ + 0.0, + 0.0, + 1.5, + -82.5914077758789 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 864, + "volume_mm3": 218.023681640625, + "voxel_sha256": "58f6f8915a2dcfbda4fde487abef9301167608e3bcb40362cb7788408b038bc4" + } + }, + { + "case_id": "crossmoda2022_etz_34", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 105.02462768554688 + ], + [ + 0.0, + -0.8203125, + 0.0, + 121.11157989501953 + ], + [ + 0.0, + 0.0, + 1.5, + -58.48912811279297 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "e2562c6aa6479badf7dd086453602117a5fae955d3147e1ed5fe89092a6278bd" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 105.02462768554688 + ], + [ + 0.0, + -0.8203125, + 0.0, + 121.11157989501953 + ], + [ + 0.0, + 0.0, + 1.5, + -58.48912811279297 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 63, + "volume_mm3": 63.590240478515625, + "voxel_sha256": "71c3385aef84ea0074f140a3cbec9e9973f1633abce832c7aaa257f9b4c809c6" + } + }, + { + "case_id": "crossmoda2022_etz_31", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.11157989501953 + ], + [ + 0.0, + -0.8203125, + 0.0, + 116.76375579833984 + ], + [ + 0.0, + 0.0, + 1.5, + -48.923912048339844 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "32a55e32c9cbc9fc31c00b14fc97947f22ef8159c21f337abd78ba9e0fc547d0" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 101.11157989501953 + ], + [ + 0.0, + -0.8203125, + 0.0, + 116.76375579833984 + ], + [ + 0.0, + 0.0, + 1.5, + -48.923912048339844 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 468, + "volume_mm3": 472.3846435546875, + "voxel_sha256": "69ce82dd4bff9ba9f18fc9a4c25a219800ddd4125ed24e0234febb54b189c929" + } + }, + { + "case_id": "vs_gk_63", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.3946762084961 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.36077880859375 + ], + [ + 0.0, + 0.0, + 1.5, + -81.38075256347656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "97db6133455e1091dc48e643ed8b7a6a1ca46aebf460d61dee0bf9edea399135" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.3946762084961 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.36077880859375 + ], + [ + 0.0, + 0.0, + 1.5, + -81.38075256347656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2821, + "volume_mm3": 711.8574142456055, + "voxel_sha256": "15d88c5b5934ba79093acc7ee380e5cd6fc47a7aa79fe179104085038e642e89" + } + }, + { + "case_id": "vs_gk_240", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.49169921875 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.25104522705078 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "94f483699a4c450087087e1f023bf85bd66803231402eeb4acf975d896176511" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.49169921875 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 123.25104522705078 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 12013, + "volume_mm3": 3031.387138366699, + "voxel_sha256": "5b51e45063c57a1b75deb6beeb79b2292a556bf9f6fc22c6814e48eca9f95918" + } + }, + { + "case_id": "vs_gk_6", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.85269927978516 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 133.42324829101562 + ], + [ + 0.0, + 0.0, + 1.5, + -95.69190979003906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "afaf345fff3bb13f5385e78becddf8f5bb0d1f6d5309ece68caa227cf3f21d12" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 102.85269927978516 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 133.42324829101562 + ], + [ + 0.0, + 0.0, + 1.5, + -95.69190979003906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 13807, + "volume_mm3": 3484.0890884399414, + "voxel_sha256": "b825683039febbb685b6fb4f001e7d9c34bec42ff1157c023d4ba1784cbfe297" + } + }, + { + "case_id": "crossmoda2022_etz_25", + "image": { + "affine": [ + [ + -0.820305486262913, + 0.0, + -0.0062028276569063845, + 100.82787322998047 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.37245178222656 + ], + [ + -0.0033921767665904437, + 0.0, + 1.4999847907153492, + -85.44756317138672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.499997615814209 + ], + "voxel_sha256": "d44b69e04ca226720850e66874f57bd323bd5c7c225567ebc56c05e50254aaa5" + }, + "mask": { + "affine": [ + [ + -0.8203054666519165, + -0.0002106786851072684, + -0.006202827673405409, + 100.82787322998047 + ], + [ + 0.0002106786851072684, + -0.8203125, + 7.965312533997349e-07, + 119.37245178222656 + ], + [ + -0.003392176702618599, + -4.356037379693589e-07, + 1.4999847412109375, + -85.44756317138672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.499997615814209 + ], + "tumour_voxels": 905, + "volume_mm3": 913.4773993828276, + "voxel_sha256": "6f5c3f21b3468c3a53ebbccdd98ecea17c35104c0005d0d9185bd4850c5ee484" + } + }, + { + "case_id": "vs_gk_22", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.16568756103516 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 139.18209838867188 + ], + [ + 0.0, + 0.0, + 1.0, + -68.66920471191406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "voxel_sha256": "e90dd85f37aa11a63c9cbb508f158c2c23df32a60d53be96d1c3b75db65a2222" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.16568756103516 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 139.18209838867188 + ], + [ + 0.0, + 0.0, + 1.0, + -68.66920471191406 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 160 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.0 + ], + "tumour_voxels": 2156, + "volume_mm3": 362.69989013671875, + "voxel_sha256": "c6505d8f12e4cba05c9aa0e2c848b02654e7f2cae2038a7a575518f21086ab46" + } + }, + { + "case_id": "vs_gk_80", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.45001983642578 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 142.52452087402344 + ], + [ + 0.0, + 0.0, + 1.5, + -88.02499389648438 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "bc6e5220c36fdd2fe4e3c92ff30dc28a279419d4f219b9f10d41b396f7672823" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.45001983642578 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 142.52452087402344 + ], + [ + 0.0, + 0.0, + 1.5, + -88.02499389648438 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 7005, + "volume_mm3": 1767.6572799682617, + "voxel_sha256": "9b134ed1a971cccf9e1e8677a04fb386cbde62ec4a05a9646c2e29fe6717d0b8" + } + }, + { + "case_id": "vs_gk_178", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.33028411865234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.77044677734375 + ], + [ + 0.0, + 0.0, + 1.5, + -88.47323608398438 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "b6606af34dc170e5e56d66276bd106f79009aa29bfa49c87edafbdbd12c0a3b1" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 107.33028411865234 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.77044677734375 + ], + [ + 0.0, + 0.0, + 1.5, + -88.47323608398438 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2939, + "volume_mm3": 741.6337966918945, + "voxel_sha256": "024c22e46a0aa1a228468678a39d77466ac626046fc49aea22ba84e2c483c77e" + } + }, + { + "case_id": "vs_gk_127", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.06366729736328 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.79338073730469 + ], + [ + 0.0, + 0.0, + 1.5, + -75.2049560546875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "c659ab20fcae0bfd12a9eee11dfcce3125f348409940df8d2ecfdd9f0846fc49" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.06366729736328 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 119.79338073730469 + ], + [ + 0.0, + 0.0, + 1.5, + -75.2049560546875 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 19317, + "volume_mm3": 4874.494743347168, + "voxel_sha256": "d614d2932166cfe1f223d876c76910e471c0c22f0f555e9bf05ab9bec47f8955" + } + }, + { + "case_id": "vs_gk_216", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.88804626464844 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.07603454589844 + ], + [ + 0.0, + 0.0, + 1.5, + -81.25760650634766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "72afdbd2c7f42594633f89c5b58d71d10ed04a7f8dfe23968a1d789fbdeb59fe" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.88804626464844 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 125.07603454589844 + ], + [ + 0.0, + 0.0, + 1.5, + -81.25760650634766 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2280, + "volume_mm3": 575.3402709960938, + "voxel_sha256": "82715e8d2f0c414427f543d0701af71886d3b9c50be2b9cd8eb723748001d660" + } + }, + { + "case_id": "vs_gk_90", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.6053237915039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.7288055419922 + ], + [ + 0.0, + 0.0, + 1.5, + -85.01271057128906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "0c1b191e9a37e64e9f6acc62230e78f570763db81fe030c1dd4aee6786ed211a" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 105.6053237915039 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 128.7288055419922 + ], + [ + 0.0, + 0.0, + 1.5, + -85.01271057128906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 1384, + "volume_mm3": 349.24163818359375, + "voxel_sha256": "8d66ec25d46677ba5eac949556c2b951c9b4e70bde5c8786c0bba54d2b5b6d0c" + } + }, + { + "case_id": "crossmoda2022_etz_72", + "image": { + "affine": [ + [ + -0.8203053997149535, + 0.0, + -0.0062409967821961485, + 104.30706024169922 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.37245178222656 + ], + [ + -0.0034130418603331557, + 0.0, + 1.4999884471207219, + -47.184120178222656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000014305114746 + ], + "voxel_sha256": "bb75db887d0965c3ab8f24755d2e5a0c289243ab406c7246e86bd292833f64a0" + }, + "mask": { + "affine": [ + [ + -0.8203054070472717, + 0.0, + -0.00624099699780345, + 104.30706024169922 + ], + [ + 0.0, + -0.8203125, + 0.0, + 119.37245178222656 + ], + [ + -0.0034130418207496405, + 0.0, + 1.4999884366989136, + -47.184120178222656 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000014305114746 + ], + "tumour_voxels": 694, + "volume_mm3": 700.5026822109357, + "voxel_sha256": "1847b6499eb503e45e2bf7d8235d39670d48448d671bd0eca4910485402cc099" + } + }, + { + "case_id": "crossmoda2022_etz_91", + "image": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 102.50566864013672 + ], + [ + 0.0, + -0.8203125, + 0.0, + 126.47360229492188 + ], + [ + 0.0, + 0.0, + 1.5, + -46.128761291503906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "9b4a6ad84d8a1f27caf2196b8ffab2b89d5cb9bd3776eb61b132597bd6177e1d" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + 0.0, + 102.50566864013672 + ], + [ + 0.0, + -0.8203125, + 0.0, + 126.47360229492188 + ], + [ + 0.0, + 0.0, + 1.5, + -46.128761291503906 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 3877, + "volume_mm3": 3913.323211669922, + "voxel_sha256": "68b70fb99fd637eda23f52c23ce5a4a108b55ad340a78d08be699ad0d4ab23b8" + } + }, + { + "case_id": "vs_gk_209", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.79148864746094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 114.52987670898438 + ], + [ + 0.0, + 0.0, + 1.5, + -88.35425567626953 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "03e046ca327e47d7f5ae086c6d999df0dc1610f7b623c15a0ec7e18700effcad" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 106.79148864746094 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 114.52987670898438 + ], + [ + 0.0, + 0.0, + 1.5, + -88.35425567626953 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 27610, + "volume_mm3": 6967.168807983398, + "voxel_sha256": "5b6d0aaeff53a8853b4aeb066b321d56a92598f721ad816b704d569ad787386c" + } + }, + { + "case_id": "crossmoda2022_etz_17", + "image": { + "affine": [ + [ + -0.820291221022666, + 0.0, + -0.010804118995533274, + 102.14893341064453 + ], + [ + 0.0, + -0.8203125, + 0.0, + 114.05001068115234 + ], + [ + -0.005908499758290025, + 0.0, + 1.4999618051072015, + -74.59355926513672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000007152557373 + ], + "voxel_sha256": "1371368dc52496dd932223cea58f075a62d927bb87d203942270b6c4dc9d4c16" + }, + "mask": { + "affine": [ + [ + -0.8202911615371704, + -0.00025271496269851923, + -0.01080411858856678, + 102.14893341064453 + ], + [ + 0.00025271496269851923, + -0.8203124403953552, + 1.6642425180179998e-06, + 114.05001068115234 + ], + [ + -0.005908499471843243, + -9.101322007154522e-07, + 1.4999618530273438, + -74.59355926513672 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5000007152557373 + ], + "tumour_voxels": 366, + "volume_mm3": 369.42919227076345, + "voxel_sha256": "7a3b93347168e0531bfe4efbf999aa98e130979e8ce5f15f4bd5127a27d9c950" + } + }, + { + "case_id": "crossmoda2022_etz_75", + "image": { + "affine": [ + [ + -0.820312498967125, + 0.0, + -7.527321754232169e-05, + 103.028564453125 + ], + [ + 0.0, + -0.8203125, + 0.0, + 109.27922058105469 + ], + [ + -4.116504084345717e-05, + 0.0, + 1.4999999981113143, + -41.95516586303711 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "voxel_sha256": "a0b61f7ce714f5b2c1453d9d186955b77abd3b9f8fe48146e7c4982a0d83e0e6" + }, + "mask": { + "affine": [ + [ + -0.8203125, + 0.0, + -7.527321577072144e-05, + 103.028564453125 + ], + [ + 0.0, + -0.8203125, + 0.0, + 109.27922058105469 + ], + [ + -4.1165039874613285e-05, + 0.0, + 1.5, + -41.95516586303711 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.5 + ], + "tumour_voxels": 2385, + "volume_mm3": 2407.3448181152344, + "voxel_sha256": "769468faa33343b3a7304966886c41fac507d671d947d6a43a4e0b3dc8329f05" + } + }, + { + "case_id": "crossmoda2022_etz_2", + "image": { + "affine": [ + [ + -0.8203054005509102, + 0.0, + 0.006240614004305712, + 102.69691467285156 + ], + [ + 0.0, + -0.8203125, + 0.0, + 131.11158752441406 + ], + [ + 0.003412840936941037, + 0.0, + 1.4999847531933366, + -45.0108528137207 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999977350234985 + ], + "voxel_sha256": "9363e6778913f42407c855450d0565014995c44b05559ca0dc9ac92ae307e95a" + }, + "mask": { + "affine": [ + [ + -0.8203054070472717, + 0.0, + 0.006240614224225283, + 102.69691467285156 + ], + [ + 0.0, + -0.8203125, + 0.0, + 131.11158752441406 + ], + [ + 0.003412840887904167, + 0.0, + 1.4999847412109375, + -45.0108528137207 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999977350234985 + ], + "tumour_voxels": 3415, + "volume_mm3": 3446.98957658602, + "voxel_sha256": "5008e43bad9373d9090168c7e06feafabca6e7a04e24af24bb1927e978abed5d" + } + }, + { + "case_id": "vs_gk_40", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.3946762084961 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.30750274658203 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "c68e8d7f2429cb3d177850f62abb4db99de7d5904a46d4e24b0292afd428c1da" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.3946762084961 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 126.30750274658203 + ], + [ + 0.0, + 0.0, + 1.5, + -89.25 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 7498, + "volume_mm3": 1892.061996459961, + "voxel_sha256": "82e15403409264301c27d7710af6bbf9348cedbe827e1b744608e827ba3537fd" + } + }, + { + "case_id": "vs_gk_156", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.6675796508789 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 118.18299865722656 + ], + [ + 0.0, + 0.0, + 1.5, + -82.71538543701172 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "f95e11b63db509fd4d1066347950220f83d08f8b43826d606f6f3c00f2f871c7" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 109.6675796508789 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 118.18299865722656 + ], + [ + 0.0, + 0.0, + 1.5, + -82.71538543701172 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 24987, + "volume_mm3": 6305.2751541137695, + "voxel_sha256": "c4386d466fac6e5aa98f6e3eb33840183d401bbfa99adaad0a15677eb752a146" + } + }, + { + "case_id": "crossmoda2022_etz_73", + "image": { + "affine": [ + [ + -0.8203052989379721, + 0.0, + -0.0062851159442876454, + 103.87334442138672 + ], + [ + 0.0, + -0.8203125, + 0.0, + 122.85071563720703 + ], + [ + -0.0034371776989544447, + 0.0, + 1.499984686595345, + -62.833126068115234 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.499997854232788 + ], + "voxel_sha256": "f332c92f34dec7c91eeb4a9a8e562a303421febdcaaf7ea2f387589f7d28c559" + }, + "mask": { + "affine": [ + [ + -0.8203052878379822, + -0.0002406752173556015, + -0.006285115610808134, + 103.87334442138672 + ], + [ + 0.0002406752173556015, + -0.8203124403953552, + 9.220133847520628e-07, + 122.85071563720703 + ], + [ + -0.003437177510932088, + -5.042268185206922e-07, + 1.4999847412109375, + -62.833126068115234 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.499997854232788 + ], + "tumour_voxels": 1781, + "volume_mm3": 1797.6834330282145, + "voxel_sha256": "76fcfc811b67bb6c2aa5ebff58e862374588bc87f572ec79bae8076c91cfd013" + } + }, + { + "case_id": "crossmoda2022_etz_10", + "image": { + "affine": [ + [ + -0.8202836207064984, + 0.0, + -0.012586526063698596, + 104.89453125 + ], + [ + 0.0, + -0.8203125, + 0.0, + 117.6333236694336 + ], + [ + -0.006883259176246623, + 0.0, + 1.4999465961235618, + -60.653682708740234 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999994039535522 + ], + "voxel_sha256": "331881bc9bf7a0488e0a346d323b15462c88d6d10a9f2396f67fc5a0c511a8ce" + }, + "mask": { + "affine": [ + [ + -0.8202835321426392, + -0.0003292715991847217, + -0.01258652564138174, + 104.89453125 + ], + [ + 0.0003292715991847217, + -0.8203124403953552, + 2.526146090531256e-06, + 117.6333236694336 + ], + [ + -0.006883258931338787, + -1.3814867543260334e-06, + 1.4999465942382812, + -60.653682708740234 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 256, + 256, + 50 + ], + "spacing": [ + 0.8203125, + 0.8203125, + 1.4999994039535522 + ], + "tumour_voxels": 1021, + "volume_mm3": 1030.565233800553, + "voxel_sha256": "0c78b56f9b47d2001a78d4f3653dce832414f6b2da9a810eacf0b9f9bdedc325" + } + }, + { + "case_id": "vs_gk_68", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.3946762084961 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.96609497070312 + ], + [ + 0.0, + 0.0, + 1.5, + -97.72457885742188 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "296d064728417219fca93168c5edee241aa16d16de9cf5139ca2be01055ded9f" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 104.3946762084961 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 132.96609497070312 + ], + [ + 0.0, + 0.0, + 1.5, + -97.72457885742188 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 2385, + "volume_mm3": 601.8362045288086, + "voxel_sha256": "25a1a1c964f6e736b490c8d98b250dda45d0286f5a5e7d616049280767d0be4e" + } + }, + { + "case_id": "vs_gk_124", + "image": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.92313385009766 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 134.38363647460938 + ], + [ + 0.0, + 0.0, + 1.5, + -84.67301177978516 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "voxel_sha256": "4c0d2cef6f8898d51cc095d4f60d2a0cc0ac3a606243b7bd01410e94798f8352" + }, + "mask": { + "affine": [ + [ + -0.41015625, + -8.412444352234871e-11, + 0.0, + 108.92313385009766 + ], + [ + 8.412444352234871e-11, + -0.41015625, + 0.0, + 134.38363647460938 + ], + [ + 0.0, + 0.0, + 1.5, + -84.67301177978516 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "shape": [ + 512, + 512, + 120 + ], + "spacing": [ + 0.41015625, + 0.41015625, + 1.5 + ], + "tumour_voxels": 5498, + "volume_mm3": 1387.3775482177734, + "voxel_sha256": "3c3158181752730f22a2e0b859a1d2292c666e809323a5a46f7794e079780400" + } + } + ], + "component_cleanup": { + "crossmoda2022_etz_38": [ + 3616, + 1 + ], + "crossmoda2022_etz_67": [ + 3568, + 1 + ], + "vs_gk_12": [ + 6685, + 79 + ], + "vs_gk_13": [ + 2927, + 10 + ], + "vs_gk_250": [ + 24946, + 1 + ], + "vs_gk_53": [ + 2756, + 52 + ], + "vs_gk_86": [ + 3336, + 55 + ], + "vs_gk_87": [ + 4144, + 17 + ] + }, + "conversion": "VS_Seg historical SimpleITK T1 / rounded JSON polygon rasterization v1", + "counts": { + "cases": 344, + "images": 344, + "masks": 344, + "queen_square": 239, + "tilburg": 105 + }, + "exclusions": { + "vs_gk_131": "Excluded after radiologist review; fastMONAI commit d8bd2de", + "vs_gk_73": "Meningioma; excluded in VS_Seg/00c_combined_data.ipynb", + "vs_gk_8": "Excluded after radiologist review; fastMONAI commit d8bd2de" + }, + "fold_counts": { + "1": 70, + "2": 68, + "3": 68, + "4": 69, + "5": 69 + }, + "human_corrected_cases": [ + "vs_gk_4", + "vs_gk_5", + "vs_gk_29", + "vs_gk_35", + "vs_gk_43", + "vs_gk_44", + "vs_gk_45", + "vs_gk_47", + "vs_gk_55", + "vs_gk_56", + "vs_gk_61", + "vs_gk_63", + "vs_gk_71", + "vs_gk_74", + "vs_gk_76", + "vs_gk_96", + "vs_gk_119" + ], + "index_sha256": "6ae5557a1b2bea2c4f30f5289a759d6db1b992881652458a31d5d02e82ea5cf7", + "schema": "vs-study-reference-v1", + "sources": { + "queen_square": "10.7937/TCIA.9YTJ-5Q73 (version 2)", + "tilburg": "10.5281/zenodo.6504722 (training_source, center=Tilburg)" + } +} diff --git a/research/vestibular_schwannoma/data/releases/LICENSE.txt b/research/vestibular_schwannoma/data/releases/LICENSE.txt new file mode 100644 index 0000000..56c91f2 --- /dev/null +++ b/research/vestibular_schwannoma/data/releases/LICENSE.txt @@ -0,0 +1,11 @@ +Replacement masks and release documentation: CC BY 4.0. +License: https://creativecommons.org/licenses/by/4.0/ + +This license permits redistribution and adaptation, including commercial use, +with appropriate credit, a license link, and identification of changes. +Consult the linked license for its full terms. + +Contributors: Njål Lura, Satheshkumar Kaliyugarassan. +Image source: Shapey et al. (2021), Vestibular-Schwannoma-SEG version 2, +The Cancer Imaging Archive, DOI: 10.7937/TCIA.9YTJ-5Q73 (CC BY 4.0). +These masks are new annotations for scans with errors in the original annotations. diff --git a/research/vestibular_schwannoma/data/releases/README.md b/research/vestibular_schwannoma/data/releases/README.md new file mode 100644 index 0000000..fc859c3 --- /dev/null +++ b/research/vestibular_schwannoma/data/releases/README.md @@ -0,0 +1,24 @@ +--- +license: cc-by-4.0 +--- + +# Replacement vestibular schwannoma masks + +17 newly drawn tumour masks for Queen Square scans whose original annotations +contained errors. These are the NL masks used in our study. + +**Contributors:** Njål Lura and Satheshkumar Kaliyugarassan. + +Download [vs_corrected_labels_v1.zip](vs_corrected_labels_v1.zip). It contains +binary NIfTI masks in native CE-T1 geometry (**0: background, 1: tumour**), a case +mapping, and provenance information. MRI images are obtained separately. + +**Image source:** [TCIA Vestibular-Schwannoma-SEG, version 2](https://doi.org/10.7937/TCIA.9YTJ-5Q73), +Shapey et al. (2021), [Scientific Data 8, 286](https://doi.org/10.1038/s41597-021-01064-w). + +**License:** [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). + +Preparation instructions: `research/vestibular_schwannoma/data/README.md` in +[fastMONAI](https://github.com/MMIV-ML/fastMONAI). + +Please cite our accompanying study paper when available (currently in preparation). diff --git a/research/vestibular_schwannoma/data/releases/vs_corrected_labels_v1.zip b/research/vestibular_schwannoma/data/releases/vs_corrected_labels_v1.zip new file mode 100644 index 0000000000000000000000000000000000000000..02527b8384132a0a7b69b80164308afa438decf7 GIT binary patch literal 573221 zcmeF)30xD``Zw^zuqcY4wJt>^Rx7qDh#(*e!CJLeA-1@+SOX#=l@NAB%%ZroZrGxA z0mRly>KarO6(rz>R1g&*EJ9Rv0*HZt8IsIYq3I2vXg@|!#)bN4yY`?^h|k{W#<{bn&v0{}VHvtQ^b4Ore=lzzwogDPo$VE} zDunJ8u$&(39Mf_Y3f)6OZi^?Cs~}PY(<68D|vX7rK(} z6&M)I2@Ll03iY9*gIMktI*#u3{5kyn0#>1)QJeghqlWmc@be}cL+7lZd$05g@bw9? zG@6bMILtqk9=g&8?HI7iXZiE}S8#&q#4)Z2^Y^ERq60?1PW-<1a5=#tj&!$gt{nEK z&kYOq8b^2cLXQ!$a#a}HE0{jV%ik|D%-1W}DNI=_C|2-E4>1J zBI!P%bT5BP`k09mCR%?uj=s<*BosmTdj*eo_g)#{6%fE-dyRLWF^eAV6C6VDOdN+4 zedyD@0=#{K>BM6aG}D5;S0ep!^iPun#@JCjvPIaJ1~G#i->W-d+JAbduR8I2g4Sbq1-#3HC!zi~8Yr zdsrIHd#UQ|uExn0(^hgc{=?Nnal96@iXd`wZHa{;pLkIjH^rzlNYXLnZGQfXYDI z>StJ9h>55Xx4B)1h^i13RsfxBL1@_WNV1mxOl{2dTIS=AifICUnV0t}-(XHy!18f) z>#l>hq`Q%Wi$33)?(4&0`-BEZPN4^I=&oPPruz|Ph02nEVzJS1K=okh9TJX?F))Pg zFz#0^xw^ms8#cVeFD+=)lPrGXux=(ab^|%e2}t2o)1#1<>+9nx{91ll!ei=xzQKR zQ(?<2(e!2I>NzetG=mJCx{P&A>k z0=0F(LO&A&g|))Z-^VCCgyp-6Wjk)6J23-|pD@989C~V0r&e9}u+Xq&p^}(yZ5iO_ zXX(49YnMqccA50@E|Xrh%f!jg_d%O4J=*aN*mbCe*C!d>f z_M+vc+A-N34fk>b)1CUJwf)c>*J@jScxqvKXT#4Y9(yoXFf>luqkyj<00JNY0w4ea zAOHd&00JNY0w4ea-A15gxk+C=qBQ~)%WT>L_y7SA009sH0T2KI5C8!X009sH0TAf! z0vny8%Gi~~KaXj^@I>bzx8uD_M#UN3ez$S*^+Snv6BeEA)9bZg_z?W8$Znoq?8!ab zIoR^tkD4!Kx0<(}zs2RMr{CkaGu$)|%Cag>EZVSBRg`L%BuX>M3x-=U10bBFg?X9m^Mu`5vcZB7AjztBi4fs|w3+|46JkbSYPaFBhSk_G$bqspKF~p{G!)ggJ@fmSWBH1hng1bPv(pPuX&Gv$o%}bEMBUh~a?+HDn)Q z2`6{+4oXiBH_vpFrIyc1zKMBvjPOs+DCPE9VbI|;mBGB=ol=Tp-&Pqb z2n~Su(Qsfq^x~;|53iN*f4p*#iRgd({En|ekyuTawW2-HB5ZC6jFYI_QGZ`TIuDea6Vm*lW4_z9}8Za%a!$C5pkjU{*XKbe|Q!ZntB6%o$5 zy0N*{%;m=CJlf#e&r2&+Sd84EIa*~f;YRFv6Es+^o=K09$r#sSGxO1y>633JVw9A=oGr8*8?-}TXq}HC0(3uIX zxwNOKO6}r{I5Wk>iizczjNDK=DqF7_cH_Phv=-q_&e6k)7Nx~JTqY}Ojy|9Jbrfx- zLthT1)yaxOiB0&1Lm8RCK|iK{DW!~#WMpWfRb#}|xlgX3k+nhN$G9cJ`xx9tBZqpG z=n|rnB=sy}NCjcSNc`ETAkRm{6sA2f5gTN;5i^@K?A#^I$ehG>H=a71>?EajInmWg z+J!Od5@M;WCOV1XhC7nIMcks0-RsVsL~-!r7IS+o@~{t=UfCE>Y37piIhXeBYEvm? zQ`FsZ%8Dtm6_gc@(xrH0#W7+)l}jlzBgLqXeny>=QZH?f))-9q4s}QE5Et#x(av22 zckTDYqK#OjkwZcKY)p_FggaW;nM`??rP=3pUBVx&EVzYEd+(#6!)#R%uUXLE#ETa+ zY+TW>L0y9$2HjHG_EKkQ9J_lT32chKN@IG)D*QS=ToS-N)A=5Hp-0_NUSlsLZ>HkiX+DpIqCUiXuPcW?Pyu3Ld!xKV~_ScmBb?!_Gp3EweZM6qZFN+ zh|?UJD!X0?@w)vBp+JuMTxZYlFK<%TILC@iqO2Z$#@Zq?Z~NeaW*TdMC-wH39qQBT zHW(_owD%66CAkZp;;U4cGl?_Cff@;aD*A?M-%(w#v`WHvAWkC%jHU%>wL!khnTkeH z<+Im7&&NR-8VBS6BVXUUY9^ZFws^-JVzi)Rl_xlHtj<(r z8gsw6^DMcHKy#zy2)Z9WKmY_l;BORYVYEMRAs_DsezxEI?tZx)+6Mw400JNY0w4ea zAOHd&00JNY0wA!UIGRTOzh62onK=(UKmY_l00ck)1V8`;KmY_l00ck)1iHO|7raYN zx9?+UAP9f}2!H?xfB*=900@8p2!H?x{9a(AlLL{FMWM*~Y`x##0oxz|0w4eaAOHd& z00JNY0w4eaAOHg0Qeb!c0|9XC=}w8j&Ak$TIS;)Vig|3@?FK6IGGjVqsuO)luXyVPy3rqYoQtB+PC$NoPNOTh`H&UvAxHx%DV1b+`DG3 zk+;uCJDcp&Q4vvrX}OQa&iG`wa`b7QakcfD>ZEPwbJIRm_%gT z&-ZY=t#ZVcaEoOhTO2+sd2dCim-#n?b>xx=G!q0s00ds6fPlqbyO-PRoo)Fm%&+8K zk1pv5JeI98*eQ=^;XAjhun2z?8}Dr+rY^4|GmzPsu&GKS{uqT;5?PgGz_ILJzAj{b z{-GDnS3I52*?;ul#60IsaoY{gJbt(^Mrlm1o-EdkFjFcX7yl;WUthta-MCH0cbBy> z%W_2gjbwZ@O0bPb;kJ?^i9+W;JaD(aAyD`vpnb&L3C?lcKK5oYE6lGZ9FU`U=$SmV z&AA3Mm+L6t*{oeIOH7Vq;p^2Z%y4b2lv32e=Bf7xQDQwCBU6F3dG^{cV3ZTg(ml$S z-rMjM=2@dE&y-{b?oPN^d8cV}P;^2%Q(^4s%G{p%n_1@3j3X!b0u38W9fm@iZ{88R z;ZxPEZTUoSFPSNiHpsMWZ6-F6=}Qc26jy#vgf>(TPsrehZeNqn8u;|Yb^e(c&mW$Y zG$M@+ z&l0Z@`Lpcm)Er{KYl0diXoDC3^x2c?#&^{hmF11o&);^dd{VgW=J`?w&-N4P_rF&e zB*%NBfbmI~a9AFPvh|KQk9IqWU-m>}kUUUL6sn_In@mk+PLm1kWL7m%u_z)LWwxV2 zbwuIqqIb;2Jl(L=w(@~!uy?!u5>9%VY9(!~8N;S??#Y%bPX{LdGE$*n-8zM6cO80S z?D?Uu+$?kAt!fGXU5ie$%=g<6*)2*#o2Wpm`y%$9NTNo}L?rdgx`;VejF=Uz_Y8H1 z_Ll(O@F+fJ6;raJcD|?IuPOJ>{DS@mGgPD#n*Gt zyxUVP>vPbJ-!=;YqXlcLCRKY<>pGY1ul@aE;aPA!f+l7OLR^iY(R8s^h4p)#)$=Aj zwm`fJpotFAoLV&N!YQ+9rh3*L;uPbu9_jQwbGTD?+#3?K-r)2mz1Q7oyi${$ zZg%IXeLIV#l+m5&+Co-9m4ttC3UZx;r+6TB^eqbg^cileEO^!xiL;3{Hd>L8OOt0C z#H8-7o!zTq+{JEc9h%fBPd66rDc_u&Mb`M)zTvE^Av+Yt^eS7i{wL|jIFw$4(1JSS zrV3+5BuFWnI(M<~66bb%L5_2+gr97Ngk|Wpb?se7(?Xs)8+}{KQw#4hTs0|i=)U>@ z)JJ6LlUH|$G0?R^z8=(0@k;c<5S>fczA=UP+7IMG>X*D&8h6rOnHP|=;{*wwdY z^Lp1gT@uQEzCpfJR0YYikFYHG30PQzF8xEumVy&jX(kUNUrZ_{ zjx}gfIini5^T}v2)ixcyQtUNIZA^E;Q;wn8Jn5YE#s?p=d9+W@ z*KsIRO!##*nzhl=nuH@xc*Hs|?Uo1+Mb|YFYg!3qbODaMq7kPG!cxgHMfF009sH0T2KI5C8!X z009sHfmaGZ#{Da4;0_P~0T2KI5C8!X009sH0T2KI5a^Zy??OsVx9nGF6bOI-2!H?x zfB*=900@8p2!H?xyix$(FyNIma0dv000@8p2!H?xfB*=900@8p2y{yUKa;+_h&N2s zQ}dgQ%yjFm6QQ*r00JNY0w4eaAOHd&00JNY0wC~z2<)Nom5KVER6&nXtYDuN^Q|od{QN9^*O+hZsofWE z%(phWJYfLkc8XpoYvFs+`;lCSF10X@_2Ga z73WOL?%6#fZ*+_>D_T5!%cA4UdUk|~rLhHjBuMJ zrP<0|OYd?cmL{d`67G*+z0--EDqp6klIAbrpZX0=@3meFSskD`hZz(Np?z8)cEcUi)YtlKZ|;LX&95;(7UTK7 z(nm33+=Ry`oHrfI+g{iac%)2)3GY|i3kImm+BgMbSKLlr*~W=Q8xG3KwwPFk8~3W= zo7=9}b!3({HpiHr`;=aaA>*w1$DKFI@(hoO9xk&nFb$7m;k(jQ2J?b7rXIZ*WtdQk85dn%*6Aj$kqq-jk|~C zep#jT%wey+c;-gPs*;UslPZ6UP>lC>P6;F98c`s6Arxh7!D%!&>XrxIW8n$?2vDuXq8>d8vj` zRihc0;ih&}$TsNE`Rsw&Ug<*CWkpSNp(OCsg|is_Wau7gkI#0<&0MmB__SXg>Nyl+ zU!{WM(8Qti!cr4D>;(ctrNU(OJyhDnBuvz9t_W{O%<(L|oG9qhSQW-Woj^<##0-Jf z4BDx}jyTc9irv%>O}ezw5?HO`E#?}`$(?eH^YEJxZ-vv620Jk|@_P*ZZa?Pc}?7*`_Xk*N1W76_Cln*e9GhjyfdB}A00@8p2>h!+=GgYuo?ASr52!Ck za?9DfIJ8I#h4`eSonDIMwzc`cBDq~mDVeVRjcdQ9Kc&)Y%rd5p8{gVbOkd_R;(h07 z$5nGOm-N3HYPZZLWN1`cs$uG}aTyc0+!!gHStR;t>UH+AQ@@yEQpg`AIxG9nG5*a0*4uW-HzCi_D*v4C*fqzi}x2cC^aG8L^l_az?8PvtshwVl4QM>MQk$Zkj-Bv%7h{F8;Bb z;AUY`XaA&Q5#@%~cTQ$dM-4%#+Jh@m9PuNRs=eHynM0wv$r0Qc6((Ym1(#@uDlXBr ztKxJmZdf~IE;(`ZBJ-fDQ>Zj+ZbyaUWaqfiCA;m{*&3MMqMTOhFE3!+4+&7yoKlMz zajKHG{3O1evLMHt$$Rr=-2c%LIWG!}+Hf?j*=D7@Ln$+tEnLd~A3ZZX4G4e$2!H?x z{Kof37GT$TvhfE5m3 zn`d*^%KU=Jg(^5^_hn2_OhDxeVH}q7Tf6UY!Vzb2!H?xfB*=900@8p z2!OzwE|9r7Xhr*^M*k)Ixc#PFyv3o6@K7r9i*rT%YvDZg*fwTthAZx%tgMQ8$NWui z%j-El0c&DLuJeXo*Eg1QBp8oKq})8qjWyDsV5?WXi>D{Goyodj5rY9}(UVaFsDGb5?Q^ zC%<#n8BVpt>4$Pkkzxtr%#BqlSlf`P5K;2?tJw0?dg*9>yoycY^5fcrSkHz!3_N+`J0sr@sG?_v&^$~iS3a*wdfPvPF;4787p?x*eT1ZI0a%? zwH=yjIp`MrJU+$>-x%3J)-gG4$g{sAOB|7-f_DCgtpAy(ZCOy0-EMq*UtE(-v2{e* zo@7r=amJ?DyhBaV4h18`nh&y7cJt&vi;Zp5b3}ZB#_zFQ#^-W6%`&U&(F74k%)V$Q z&Ja_V93Z#Nh2%_3%n`%}DrYoV#H)fDvX8JV_zCU*Of&zfXI&e!uc>#jjeVJK^0MZ0 zR$f`#s2jFxd~6I%?PAWEJet2*W3V&05iJgwax<431?Z0;{q^g5K6i(pAZH1ley z5hk&LFtxKqg6?I%j@xTqW}mzw;9S1vwfk(=<@HK-=ipm*-W0Fam7R3AFyw|R7TYtm z9&KQ$O3I@)V)Uc=pi;ZxphnX`0mDt>sE}3Z;Gc%wns|mw-8vILexbxU`rVLJZZESo zjv4LK0x`P}o!#eP9_3M=gXH(pwir@QS=JU4i@+R|va31>_D{_m@Vp=Z0w4eaAOHd& z00KHD@HBh)(4J(Qm14W~N8tkmKmY_l00ck)1V8`;KmY_l00cmwy9*da1=7iD0abWu z&+gt>XdeiG00@8p2!H?xfB*=900@8p2!O!Rj~cVqk=Fut;;Tmf3Lqc=0w4eaAOHd& z00JNY0w4eaAOHg0ULe`fum^E5z(ph4ez4oO78(cwAOHd&00JNY0w4eaAOHd&00JOz zclc0K;#xq@dc0u=u>l_-00JNY0w4eae@WmVbw@{G(H~>6Pj8#Pw*OiRh4`eSXLval zd(yv+#kMxOJORaGr>tK+XjO#l~UEK$C|fhF-lhd&{ibc~e$XrzKnKH`Tmv z`FLYXYb-v3|NTZ$pO0@$&oKHj6_Za`bM0cEhg0XIedw;a(9t>|Er}t^|9nqs{o&F9 z9p5cG{sz1jOCSnf$!M)mopPaf;k`=bA)zK5Mb00ck)1V8`; zKmY_l00ck)1VG>|5fE6UDSmh~({#qPdlzt5vx~{^`E!Hs?yk8ud1Now;a3;TeoNW` zhY12800JNY0w4eaAOHd&@RkTrS0!W!Y%+cm-K_UGU$&deox(MZ_{u6iW2;B{Zz6uQ zh721<0hcHsR~vaL#qIbMB7YD?I6A0hRWVV!7Kw8PiQRZ_Nt6H1!wg8)IB&e^ri@(k4bWf4Cic=FN`9Zb66G( ztd-7BAab&aWYGj_N3+w~8M)K=`pj5(uby|4e+_Rf%?^@hve{c{ic z=l06?(wtqRw<_27Sa#s)3(0FtYA^Udrt}Y3A>lKn+2NG9sHtL&nQ{hCZ7+}0^z|Hx z3N}&5!0(_EwL{>XSVmr_Hj?~Vbo2AcAzl@Rwbsh}EEGLl`_H5QIiEkb7n8m8_|mNY ztzL@$ZuYDb2d?!-kwfgDZe6sJk)0C$!DZQP(0Fv8MNZU1cgFcP_9%+^VLt#m+6XX zX^wZjLYiwOmx=qKFlSM0;v#G$f4_=cJd8&4Y+^dr2D_sKZ8Slv&5n~%NB(|6C0>nQG7N^gT@_|Xf+nAa|^Ktlk;*LdL6em zUufmEZiwOSNyG$kq2_MGwD`KC8MK}k7O?OwWeP=%16t6oSNUyLDwsRbEW2lzWDmnd zlfse9%<1IxkeZ{dQDUV#npl*f6`Hxw`A_!t+qzqeZB1UouyNMd<~)mIseu=V7@FE) zd-4t!MmyYDz*SEVxF=y$uHdRi_HB@{0td73^^eiGzr<;kiJg?hoQh^pG^bV(QROJ1 zyo{_#%~a**=5MRWpFD1X?*yA;7Rfi#J)Ab^uV^!g3!-=(;umkzc!g~O^P;%R6>fL>>jiUcQeRXJAc-k*pH3i7rVj$Q0sabo!8 z9<4bIA$P&dqDzIn6V! z_EK$G(C6X8(6k+2Yc9mJTDH6L?cxt@xz_wuz#i8N0quLjH?^vR9DmspDm@^qx2!%= zQ@$wsByZ)Ued?zj-La3LO&|aQAOHd&00JNY0w4eaAOHd&pwj}Gu})Kd3o`q5-I&1@ z_}!qLUynNd!S;Ui>Cs<@_2?lwY(oCe1f3Rw#)AL|fB*=900@8p2)qsfPeFsTV5Z_W zm&R?28O?YdPw`efCVrGx@4SAr;&5l*_wKPSa=cO<8=N;^`V3!*`4%wtt4i8TEcg!U zEA{4XxW9aJY@&IC4*r!-+58S_+mX%9W-CYXPh+<@dg~evcD~ns)VJL5RKXlx?;Ak_ zr9Hk_Qj{vtOH~?sUYx_Us!d`Hp5B-2<+|1kDVR!;{Ljfah zkc)%*OeA$fEF)g?uuD1zNe5!WSVsBd26=X@Zlw`E)mu3_dU!cR}b3??LMN(QQpJNu3>`b1a$TFN#d z0=|N_QQ4w7BVJWf6=T6D6&4JCD`vpI>mZ-bCC(;kg`am!_FrIi(Q4TtiORsVYMfYO z?bd4MvSq8vVCTb$Vok4;=qJG|eQA4T2^bcmSjtlie_-s-Ca{Sbc2HiakCF0kn7e6+ zd6*HWD!GTI=5*|-X3mP#Peaa7C@> zH&7%n`4bQNS9f`^N8#Dwbie*ohW;;8Z*nL@@>^x*Pu?do<#Ra{eM6-p@$ne3rgB(~ zy`a9L%3e@XfvP*D1I;1H@1Q$nb9w68yNss!s1qFbu<#>Q%vM!_8~U9wYBJ8)4WFuf z(jlHo2FapuZZbc%z}b!aSC9N{+Fw&%#0qC+4VRgui5{+yohi2nO0-apr&mUDX)9NM zDW%j**}|a|TsnrPiiB@P_~P0I8RK4w%3#7>CHj)T8_nE#*Dx$UX!0dZ^x6{{8uvOP zbXyaIg#_uR#Z>c|DD@mg^%8lY4(iFL)|VT~aSMKeDo9SobH`!qhU_EeLS8)fHo5)9 zV_j<~i!M(MOjYDGZ#|!1iw&IjB>j^9sG-+VQ7sqQ3$kwAlkks+pq0n3XV90QTNTEh z2j`%wuK9{b8(i}Zn#(W5v+&s`YbAUEYnXT^3xE80Y)Q5GC#a0cjCnVWBMO^G*|cuN zEhy4A(Y!4`mcc|Z$wY=XnU^fPCwfPR)+SFaiwx5^4+CU(nxoI(5n=|W^5Yr$1JXa@ zQ1u@-m}RcCkb7jJ0yoQ?Cu}v#oI2={jFEp9Re5e9I+F%Z7HeXcxKv)%F5wT z-dTGhT{HcLxlkK2e}qh%XQQr6qh93z=Ewr?oP~PRuK}t`Y2K zGpM;g^GgTGWsK}a=*#sz=*u+;`m#~uC(q77@#*m@0$rL!CaY)Hn|F*jshMGZxdC-U z97D}_u{%Dv&vq*2nxwAD<@ES4U+&>@eXDBV?G{Hd zRgbWLvIAASKQRJ6u{ere<`A|wi8-Ap`9pPy;ZCUX>ku#53Eyh5M6iqVu2ZCo@fE#~1;S#0^BphNk} z@dqpYxV^tc=hYrYeOo;;S3gECD_5d8dqzp83Nr~utD9|MN6|SnAygUoHA6eTpx!bqYgi0UujTqT#}{1a>Fzqm^Iv%6=eCKomLE?%K#cMQ27p!VvR zbpIRfxz14mt-oaGho&QVV=J_nZ@HMEq0U0foAgQ*#_UKEQ*+xb(TsB`PlS6uMrVPN zeHw!aMMuz`Vd!>uZB=V!5wV>QK+GWM$LhNSq*y82t)19W_i}}S|V|$O7aPzAt+L)cYq?FGNpjV|kgi2%jbUbAc z`u50&PaCs3frSgARakz+E*7rr+i8{=nvGqU{Hd!UqU|00@8p2!H?xfB*=9 z00@8p2!KF$7q~opXixGQfl_R@zPmRT+6Mw400JNY0w4eaAOHd&00JNY0w8eIC@PRn zUJIzgOM8L`2!H?xfB*=900@8p2!H?xfB*=9K(`n8s4;6Dc`aZkzG~#J-M+cdKo9@{ z5C8!X009sH0T2KI5C8!X0D%-k!%@V=fb|-2z^sGhdk2u$2H*<_fB*=900@A&JRQfp_YZ;d=2*-<#-=wpumPX$_*oAlKqo>8D;nN3>&A0Pk%AOHd&00JNY z0w4eaAOHd&00P}zVB;mn&$f^3`7`~~a{R7A+19gDMvmhD+S_iV+q|8eHUHD=67Xa? zC(w7_zNU8;Ph_4GJxsn(obhe!Nwx7o6mCxY^g?@~xN3AVHY{<{Kt|TAikf}Q)(jv+7}aY$BH$aIT|~4C7G_Qwo{f}<+!6vL?W(} z6U%Tz>8QkA3^#O_tV(w>m%B>wL3(7d^9KFaJMGp(h5XWO)D3yZnxY*V?n)_Nv~kkf zQ3`XM#=taKBBeN@pPM+8A==7|Q=uGRZ3%XTwI`^8S^}ZJoS`-PBWKHT{++{M)Lj+ zh8rr=vMLl-P1NG&3^(qJ=|Wew-1ZZv5?e8wmGuKrnD2U1e7b70Y_Lm7T_Os$C%&M- zbdgfQxkIKKYt2x?v8*ZvCGn~qo>aE!+(1SZ@*q1mx4oDDd%efRSqmnr3s&?w=#Tzu zW17)EEnu*(Q+{jXn9)`eVZ{zglv*51M8l%lPZzCpWk5kYK>!3m00iD(0Rao%I12v3 zFbIGE2!H?xfB*=900@8p2!H?x=$1gTp&|KhQ7#(M_Jg|BbZ9LIfB*=900@8p2!H?x zfB*=900`)$z~$jXweK#e6x*%WNdagy2!H?xfB*=900@8p2!H?xfB*>SmVi-IAf5b2 z3{`k(Pu*%dv=#(F00ck)1V8`;KmY_l00ck)1awm1qoyosFLW_La1a-D_WS4!es2Rf zG!Os*5C8!Xc!LF6`ggW*?fw`BeI;hlc?o(4W#W^Lj{l`F=t-0QuQX^EQ%a_*e@ff1 z_4LJN-%4AIi+;@bRIlIhQBODQ(qvim9DPBMbf#b2zRKeAG^gUSgBq+K3U)s0+q-EUNiUVgCl(q5} zy?cbea*Bs4H2e*AhC>4Z5C8!X009sH0T2KI5C8!X009vAQv$0`!5aqYSuE6D`!>Y&h)%)CZz7)SZOvVp0EK$VlWVmXQBRFE!o_0I2X3=QG#BNBfuRt3_2zZXU1s^4+ zW0HDvAxZ-e(mlZm-;~dlW^D-LV1wO4R$V%fJN(+02h!qPZf@0#4&;hj<1(*Z%I8od zirHMX)f|njvg{u79Tf9zTVHmM%&1|9DW{uk^EAMgKQI5KUva%|sE8e;dWrDl$ zbgC>L$c}HX-|CQmF2i`k(CSpdhC#~l59CWF_JXZZaZ<|3#%LAB+N1Jm5ORND;WvL^ zAk1naMI8l#6PR79$Pi`GYmjPJR=5)VPOSD8g4b=WE~#eR$5vsc2FDExB!MT-T66nZ zl-8sQc9dh-qM=n1@xw73ZphA&Ch_7`x+J?cEzE{%NGnRH_i!j%W6SMZ`xUmwsn%Ywi_N3; zEV(J-Uk~8XZro-x(Z$qH>ZIYtQcC|e=9F6;iawS~%$wv)T90t!31#gqFO=n}ImBJ6 zAbG8yi4MVL+IxtT@;xtKwBh#l^@vHzf4D?e(l#yLFP4SxV)GGgj)bNKNCKs$Gc}}*S5yhpCUbQA}rU`XT z@Oe+)kCIai{`P}@;KP)RJ%1`y_TkWChGf;#Q+d?i+L)cWk16BPfF~B8zaRP8^(?fQ zBBoXCswxbVOm;~?6R++qPg}TY!!v(UUl^XX8)@4Ym2=^@8q4N-ff!}GED=F-zQ_o%;QDV!chxPFfb&uuMa`WpU81K14&KmY_l z00ck)1V8`;KmY_l00cnbO%)IfiVCEY2Umrc_SD@tFladlfB*=900@8p2!H?xfB*=9 z00`)ez(X`kLEBk72MH5$EHNf}3fc#Sgd;Da$~)D1(| zHYk{P5>s=kAAdjeuXg2IxBvNURSIT0aIf@)^QN@C=)#V`W7(>Kw}Wp>dpP!O^>Dd< zMl z9i3(_S7TKJO`oKTsRPpMI8>@tr$=VH8|IN&L^9J-gcTl3`ITMAwpsCK>7g5VNlrrlsY9j2Cpho5db(;vK z-fE_*ClAePh&#UyL2Z1HSS7*n11k32RKG-&D~!T}(X32v$WgFpjlSu#Y9=b2(B+U*2N~yz>e)dp zBjAa&Y+02kmci6GDq64V&fm#uLR+siOj*3_I_2_;K6L*!D>RKHN2Xx(fW!RKG!>do z;-!@L4^-O=Hr1+)=?`Y0FyhrEXg)#bMv7fTk+es9c!jh_c=%y)FaO3zlyRQ!i#p>n zllmMa6LN_ugG|hA?eXnP){=ch=tftbzlo_~KT?O&cyU-JVrPm&&YUjIQ&RzuYJ z0&ng+V7>D}7X61PLqc6F=KV7{WO#S4c21%N|G?A8N4hk+U&v8d-(+}j^|$6SkC}LFyu)xMU+bdDkW37p#r@rH z!X^m(j|HAu!QTk!%r#(WCtrf7$TJ5tIQ2h_#~m!l2Gg(y;ZI-U~7Q;)P$DmcZ}5{6eC3t!LWse7Y* zIuu+*q*ZcIyqpF_T2|$|6Mua0rhTkFwa6l8`^aen^v6Hu*Pk4|{DbcXpZELaqXV5g zmTWBPe65H3VAkSQ9s2%fG-o^eoLekza^9GI!YZ6~R0ZmKY5L;?$c7$BE1;>mEo#PB`n=QctdCHs|C#l(_{tYz9n zRw7AJfjEO=_jn5@%!8%CW{A!ORwXPkBX_=BKAosNohG8 z%9VPSFD1+XMSuojsgcxQ%Bv)N2X#R+k}YNP)D~&^B7C_v>aWZU#r{o1si0&$C>hm? zvMX_7gWx9OLGQ)5o9j@%uplvDSP6GPjDtUiVqE*g*fT?h8BDm3+-9Xc=1^kmrR_8M zw83-na}g*{*b~bh;*vm=tCEsZpV&T=r#3`W1yQdADG_wbi4uybxrb4tI~zmMtVw)S z-9$!f5=vDJlBW{!iV4_Cotcs|6IXIicMd#|vb4$hJK@l)se&zMo*K*xT)@JgoNP7A z42(dLln&~$bb=0reP*D_CXy8iyu@%^h0Hmqo+psNWVS6KEpFPbF_^Gd6J&7vIq+z$ zbPkHeC8V>-Aj_w9ec!MF{n`#kb*gA{`R11Mio)iQ)pysN?Hn~cWF9)v?Px`*0^6Y2>#{axTCNBWl_(YF&7;MdGv(D1eoC1Ni%H^_arv~pb5WKs z>8&ks6d{Z@l*E)wc12e(xpr=5xTzg=XW|er?qFrjBa4@Fi@80`j^~G)Unw9?e2Vhs z=+$@U@MvQu-w^R{e9oucaL5($uLbdF-#XkB@h_w3P}(h-%sg;68UdDQ1gyq{+8O&9 zH1b6i-UUr`6A??KMUU(t-?0n(B_Y0Bx9T^r~w7v3Z0%k9DD}>5C8!X009sH zf&Ybo>KeRnSGWK7*w8=_009sH0T2KI5C8!X009sH0TB4TfKgN+oxJE!g_rjH{T;9k z0w4eaAOHd&00JNY0w4eaAOHd&&@BZ%YRpF9Z0 z%Ja6f{?7uv1Nu5`qOYcIZGZ1TR>`T@h?f439y}Oz?au6>m$rI(8-ToD z9K-2Xw`7bB@eeQj;h16Dw*$w$VOK8Ta6sU#77!%uYtlcyZ)?*q|Gida?u!o+$@J5*|VM*>RYkd{3k@wccEa=uxUX5t)m!Udd2xB3&EHMxEjO-yY%H z{QE6Yxtp7e{fVMHzV`xy(lO6kqT#1Z*)}q_cUjrf08ztDez=uP#{Y&>dXpa@7alQJ zXhN*;?~Wvn=IuwYsYeia+*3kWTc;>h7x5xhlyq)x(G?R^k+AdZ}GsI;t;qdq!ExnA!hS>ix_xY!pyrL&4^O^XhqX&FB_u1w@ z%6)b`AJu1COAmv#_ne~7c;`Q&UcEE>vso5FZG)TFZ~t(m{h**os||^XDih^`ei;*u ze(F+0f1;hS{ypv+I?oj~;!3RWz3dY$|99x-Z-l zDov=Zx2!%=Q!cr>gpsmuJWmz%-?SZm7X&~61V8`;KmY_l00ck)1V8`;K;SJ9c#t!E zXixGum14W~N8tkmKmY_l00ck)1V8`;KmY_l00cmwy9*d~Dt>I3KS2NUT@8HUC6j5c z@*`hf9(qW>_ll;#9&f?L3^+s(009sH0T2KI5P0(i@ZyXmtgP?zP(C#?PUD9OU2$7w zS({a>3`M5zSNS!heyF2)%PODFaIS&99j#|spM(Ae^X$|&llUcV%mM}*+mk~C%B%e3 zLUIFR%TsftqxtdJo8RGo`w^y>g>$K!+v~BOCmV&bDGsNGxD>CTcom5KZ3M~ntoFSu ze3wv#?U`CD5r3kwMd9*H6fUpPrp%M!@|9I6I-aR}5$JuJ3Kad8l#tbRZiox9fynYG zm)q+wdPV(*V$BEnN`=)OM$-(QdU_i(7HOfuS;cHsA;i`uRB>@CqV&uy_z75;oc6c( z?VJ37Ev%BwGPCoiITGJMZ{((~!3I_YXaR?}nz`Hz;rAXBStzA^obio^OI)JBPF=8; zmQpXRVG@z@a$%%~jiuIWr#WK28^I@TPOT>fvMLD6K4LC(ev<)S$FXI%H(`A$IQ;V& zo9xT_&>OAA`oE;#;7~?bb(*<+OT9XTpmU}^YGbx)%Gx*uVpkloR&mJE%qK>(y0VH> zz;M$zs9W#FxSPL@gM5=8)8VX%#6EBZ<7;Wwcj{oS(PMk{P3Q+(y%@=8NMmS73yC6>OTK&4&b}(o#9M5No?vj#28{FG#|H)k zW!;+Z>F4wMJEeGnu*uke@7AXAD)vl#qPk!vZcBVQ?aNcUS6M|B66Y5?Wm!P^wfrZY z4(iIbd{l@yy5*kX>p!|T@EIONopE{t4+{PMlG&!|f{km8XZiYOS7ctBbN<^yu>-8=4r}^P(=&6w9boxLzO*NU zp){A*e);tOwRbLXQI+W*AHvWshHm#YWwJKg%JJOhT+#&B%I%`oT54+wXXc!PBrpni zSB7*eOIO{jY{R6|GFOvI)LJj2Yh|u^DJ4`gEHM=#Q$b~%%l~%<3yLQl@4tOum21nsG1nd&cc0+GU8U4!}0eyoA5CI}U z1c(3;AOb{y2oM1xKm>@uZ=C>AMz_yq+E{vAuAOpu*Ch-;y0FW(2QR*erU!oOHAmM+ z1c(3;AOb{y2oM1xKm>>Y5g-D;c>=k|M+{!~_lbj_8J-ZkWYEc#l`+dJW5nQgpX-=s zb3hChnmb#9a^PWm<&%xgXVj%oicUH91AxAHeFU;SZR zhj%YKht<%53oQ3WU2}c#v5BoNn^v@V%~J zvM12CQ)TPdz7afuOAh#3TlaSK1R}f}kh?5mXjRUwedVrMNNyxo?kk^Bm2RuBIJ6^H zlxoc>-BITAM>dso9hflTJ@4=5+P|LYPuf3X!qUm!1?dT0`O3}-@w5D`69ze)Mp0x>^c?vy!0(Vwc z&g^gw8_I91=#$TW@?L)8ktwsh*UTLLaM4A_N6#vl;v#-z`N{d$9^G@fb#vsl?_#W*uZ`P1F*9=KKTm_+ z{a~j5#G3%0)UUFl=%oNs9Bu^vIe`&KdRUcj&Rc-QbD6LDMjkz|aO*S2e_Ji_oL-G@ z=)T$cqu>4L17L=Q}BD(o|p6yPi$> zwK5yOJ}eAGy}R_3&wDue4Nt`P!*1~ePJI!Za+@bm_(do^qA5tQBO|SDNM?(W*{6lK znfVkFrJ{64Zu4p8sK~6C)M@$GzWdq9Jn!%3N^1sAY`yjOuKw{KrGH&IDDKk{nUPcE zO)ksD2hiHxv+--Zcfp?hQr7A{C#0-bP4Q89w>xs@%b})iO7g12ooLKv^29^PhdKvG zHkgS~=b$lfSQx(`b8d5L&KAqstz2N4_wxSNb?INUe%_E{gW@*za9?&%{>){$8q(VO zr(oW8U(`dx|50SQsJ!n=9A3F{V{Y&}>~G2qeqEj0(`>U?4FDBZYW=oHGRD=YUbEA(ZX7j~_!@b4*LQ_de&e{7&!w$L#pmT<0DA_Z!_iuZwyJ9h%8{ zzX17BSD~9>Y5g-CYfCvx)B0vO)01+Sp&5Xd#$&sOF^sK>xdmn0M_fEG<1c(3;AOb{y2oM1x zKm>>Y5g-CYpg9sqioW8{<|+a${O&!|<1Vhdpa8ul0z`la5CI}U1pZ$H_I8fimp-q4 z67LyfI#eD;0&jD(qU7)--olxZc*nJ|tnHnA^x7UjMn*mF8aU{QVTTfvKmOvj7VDzt zmv@KViAxrCd+x>#|DBTh;;2DydDC-BuiDVd zE&4Ywm2bqBE?RTIzoc`&;Eqvie22dOf89TQi3kt@ zB0vO)01+SpM1Tko0U|&IhyW4z4HNiLqs4@q(&LxBaqGC!pxgUILaQ4`t?1eD!wn<99QV{SHTgns zd8d49`E_4M+3x;=}iB8QT5e zuJWyKAecn#j9Vm!hd$T9t-+Z*R=xf0f*t0PvWo~@% zyDRXz@Zn7V`0cTuq&{5P^Rs`w6bxh>e?0$i%*LV(xpPZ$V-6O5GR!rya^)C*_L9si z1M@e%l_^#(-I&t{v2M-iXKzW#`jV>`KP)YpJ$%i-SB$$naZ#FOU5I|tER+7m!iEBSm$j3<~=GI31q zhJI#u*b#kV>hUe}{t(^08P$WuGJAmgov+vZt|F!4sx1TdTPEdC&L1!5J2P9h(X)#p zTI|}C>z=m9cZKz%*VY8Cf4Z`L+@$+5{im|`7kE}DW%^fUA1LswnC$hQ81-^|@RRAK zn~J`?@<@fN+4<>sPXT@gwno*^!9l2w6e$Rog{qRnQ z^}GIcL)^RL^2W7E++7TTBzhuSy+1oXc->0W#B)6|{roiajaK`7-V@ToT(^6>&pTS$ zn(Iz)iJnpR_8Q+i?Y}d7hR-YA;?C;?`83M=~*t^Q37r5n?Gk@s%VPIkR@ug5} z(B!wrzJhl`BlG%=F@4?5r}^9d)jvAnRd3$7sM|x_2fY~hFf=d>?H3=kcCDz0e!s9N zV*XCF@+s&5JrBmDU|_!7+p8TaW}S>-xp&E?qOTXP@s(|i>Foz)h!5}Q#nB5g#r@EN zQVNUOYDfGtPPE?>p#gJr9F!kZnXD2tXy<(w#5Nz?7E5rI_x*9L4lEkKXv6S-)fP2@ z`^sl5uWIBKTVFRtK^YJ~<@>yN#w}>e^FocdBr(*8i?`l_53Jv~RZ5(>d8+l5=pJ>c zwroyJEtbF^D%6Tgulb`tJ>S$Yb3?-X{@_rr*&G9(@7Q|FM6q%`+FkQf{F+xIpu0UU z2<&wq-y0XY(ociS+CI{Zotbp&M1Tko0U|&IhyW2F0z`la5CI}U1eyy0S_Hhg)HK~E z5g-CYfCvx)B0vO)01+SpM1TkofhqzjE%ZT6fd~)*B0vO)01+SpM1Tko0U|&Ih(NO< z@YG#$o6ti7N8Kqcn$?}t%@P43Km>>Y5g-CYfCvx)B0vO)01;@O1YSJ8H_P+|#C;kp zXnje@D?q=901+SpM1TkofwN9v?O#2v8x!j%3%|HDi^;|Ec;;rsS;Lcsi)Ts}&Rk{L zmS-Dxd9SuDx+J{5q(hI~z2EQ59uqktaq+t;Q4cM@K5o}rZv5YESoG{bl6S#Gq?X*|^0$7i_=L|p`(Vy-WEj5DyW&vM zTkcnUDc%hmV>)Hd@r~Rad7(0D;J$tg>ZjC-ws*Ym&-1qFzf+7nxI1&1_lEAnPVFh^ z`_k8+#Fq8&cN&&3w|zzHge4OzTPM7|y0RiCX|y}?<)Q9Of2S`zWpVDv>;4=R{e{!Z zrL3{#uI--Q!R0;utrKqW7goKJ{rj*J+b4LZe_!zob4RY+SDafoBsHO8*!WMp^-Lh8s!eAH zz}0Nr*?|#g+}N2D7=a`4o!qbbeESDjf9%SPJ$hhcPN&$C=FsGsg5$A^4ktWSHZ_06 zk4c5nqQl=zUb8{Un%=9d^=scIO^j?c=FuX{C*x-oS?(P_sOX}oLcDx>H};G>zrY*H z3;6YUY*|8P{nk=EydQ z7D)F)Mc+kCF=Y>!U;bL}VBTew6)|ymwS7skly%Cimb_t=6`rm&byHPIflzH#S5o&6;zq+8ygxr+^>3l6+XA)(>5_R_j5t+y}dj0J@4Z#fJ$}Hu-rrr2^-^B>zt@rLqxZGz zv3}FZYnXylKSnV-0_9Q6rq7RF!+g5yWE8XRMQ_yb@z)+}w|4X3A6%9dqgHz&zSk5JaR1;FG zuUz>O>dLIJ@he}64-Q&62fs||h8hixMW{J*bP842Poq=e=?Xhme4wSj@=t+omB)&= z_PfAkUe?NLq!~JtUTF`BZE290@ql4nsP4z-+Tj{%o*)`pJ4YO^_4^YiMM-BdW z7moesDd=y*-$Z2q*Bw(rCXu?N+@ zr*X@3`p5idLyG6G6R(KLyX$@Lt=)^&iC4ZoW^Pf7i-sJKp8V$P11qxDjoM$}{#UgX z(>h_k9~!{HxJ>^6<@xyFh%5F>StFpPp+h`29o6yDdQ`{r`%oQs2lK9k>C+~p#k~kE zaGR%0g9Y?Wa;86VTWncIroR{dOn^O9-OX&!P%LKOtL{%fRb8>!p91Ju#WVV$q96e0 zX1wJ2cC6;VX>Qa@v3&z~Rz%dku?uoP>%-+;`WOA@nUtb;x=)GrzO&}y7rK4sinw~~ z(d@W&i_nqwtb`4;dpb;-f+aq0-(_7qfu3cbxh#CymfYZ2YbO*gf2aGC ze+X62f|R1gYo6)r9a3buzr&6dS<^aIw6=aTcH(6(A09c;|IGN=rkwgtk97kmN$gfDY_kuzHir zvb4$rV5-hB3rr6HETNEUJffuF*{#v%+}X9(vvYy-s;8f{wwm-=Ft%)MtE+Y{?6d9B zE&~rfR{NHJ1N%b~3J+Z=C-PbE9?B6TZ_N%2Ua3sHY)0YbuN}%BA2lG67#+cFifY|#pegpSImp}xF01+SpM1Tko0U|&I zhyW2F0z}|EC$M)tJ#}}X2UF7h5dk7V1c(3;AOb{y2oM1xKm>>Y5eOr2>G>Y5x5Wu-1hytCquq~nZaQ9)Z zAOb{y2oM1xKm>>Y5g-CYfCvx)A`nI(ujMl>OlQEPfM?X?_93?b{UQQHfCvx)B0vQG z=L8a3rTpG9EFofV#6u56wz#VL{5M)!4xd%{EEaq;H!F@Ns}4Omz00Wd_{3rHiWr}+ zC&s7g#v`mPH7UuKI4)*tRn9$2@cXUorT>X*V_Dlf**DdCG9t3=gr}ChiGMTNK5+Xb z-Pd+}Gv~*MjxQHq^vafdla1-l(T9&GxL*8tz`bq978ic%wZE|8gQ+V!zA<;f;Hwt~ zzS?llbN+d;?E>?%SFh?4vw!4jk5E~8yj%M(1`if1^X?k9X!!yElH2|g+-7j64t@VW z$44h50z`la5CI}U1c(3;AOb{y2oM1xKm^V)fgc^wSA?b$vV8IFdOS)$M1Tko0U|&I zhyW2F0z`la5CI}U1c<r&=8UZ2j@8@AxO1H8*ErE}GrRDN zelg7;qg)-_S*72tKi+on%kj&*e6wWidlTEPShZZrdcLPSa*B*(r+;&1`XjegR79V& zc_MtlP;x#qV`eCMqM4j8l;hGoQl7gV>H5qx%*`qRSryKmTy)`p;RUO9cYk)+*E^6X z`$T1H>$k`p`n1y>d8>S&z!Mi-?((*a%R;iTP^R%YzLb)Jjb@%PUntL5Rl>0XGsSqF z8oJDdR9`(J;a*3Ie z4H-dw$k)EGCP~}rY;E!8&PaSFzWGrcuz5jQ*MV&O^1k2vyK?h!`&Vzd3$KfN<#bAD z?3;O@tMIi%R@FHXA6*|eHzKdnGQwM6=EEI(v^k`nCIR# zE#ig0N6bG|Bt~sB%uMLl16n8tt(he7z@eN4&8~A+@%r(A*+;id*x7wjXFlqoYCJ3c z9+7t_CX_NB>B5^SnmP@NUJ?N!Km>>Y5g-CY;MXVcP~5Kp}U*t{)hk( zAOb{y2oM1xKm>>Y5g-CYfCvzQN3TA>Y5g-CYfCvx) zB0vO)01>$03GAF48DTmDtighNAG+Z0mTr&;5CI}U1c(3;AOb{y2oM1xKm>>Y5m0H# zMrs5^fCvx)B0vO)01+SpM1Tko0U|&IniYXJ?~-pe;{w{02bZ;@+>Is*`XK^DfCvx) zBJfKHtiAe^)JLY(PvhKb{+f-!`z#i7YmEX^)YK$HPtUMDk?u+zf2q~lQB6&CWvCfR zu2kf7&ggijwPT+Ltto2yuykvxK00}S<}Nv~+7y zsx@P%Zgr(4B_^e+$<~^}(mUP}Dm^{jqh-xC(2U?sTPTC{Dxf@HCe;(rI3y`NEZ+l;KJn8!8^fXs#rDF?D2evUxrnNJp_F z)6K$gal`QiHLa?6yge~=O-`J`{J@~l7pgADydWHoPtwe9R%N*ss?UM2Ux~#6B#?t@sh&Y8D0t9cvX>uYk#!%1zD|4 zIMvTyXZ>ps7@N%7d4UrciQ`1O*tCnNS5UnJtgS91QL)LAV&@e>RwO}W&(@h5I5ktd z?4R~)LG7466z#rFW!qVf=c$h7!T zt})2*j`Dei7iuoB<0x11$P~2X6dvzMqS=^4o{)DcfaDFl3%}N*b$+qM$f9Syd#KR}C}*Mq@>VHCW9dJ9U|ndD*GR zY)xOyfPjK@%1F{vGpYqZ|9+kO-P5Zx!w6NEP<;WXJ8^wkVXrB!L0MrZS@8>G1!uQ0 ziYN;bI&$3C7<~KYpdzj zDEp`L)H=&~DJ*1;lYdTFaEv6{%%09Mj0o|sG$t)Xr{IKWa5^!*$%^3=ATtEJ({6~0 zomC`8VmLw6Ax{)T7aW2j$xcD%WI+}=4b`VKCM^WsiT}LDa)!q9s;;t5R@3a9EQtza zl*)08pgROf!??;j6ppnUPKTsPyvXaatkzNm8j}|GCQ1uVIfJ<1l=DYkh&GwEE0V%M zv0$LE{M4`;?LcQ&x!_qF6dM-TA&QU}vfQ+bXi}%GY1`%q)ENz0ka%9SOEQmPML1h$ zs&_5tNM49FQYcLFoCW2g;gahr7p$VxR)4~^3!WDgn`D<2mSuTPW^3!e_++?xAu6&H z8VIM`HC2KhpgZjX!%CdQC@4qQ47(soBJ=^(4(&iw1Qv3GHAIKVFdRPKn81(?R$%O$ zE~pHzYYgKMQI-Ru$H76TWFc@^Nm6)@Rb{9uyj>Jo-4Fy(WhL1zG*P{P90)a^GpZN- zne+?(JW?ctHx7_tq5krxC>mLMj@ zvu5}FuLQ=gZjDHsSj(Udmlv$eb2g~yb|?!hq%kAbi0gXtLf2*8j!ukrhyKjkIhGN1 zT~T<=fIgtBj8hQ|UKgRJICaKuFtVZwA~aDaW2lN)Z3|Sp5$c#DswAqC>U3%%?-W_6 zt`4WB@hWr+!zuAP3?@xhI42`%4yVkrx*{8jrYMFX%bbkssZj_TQ7|9|nkFvL`I8|AQvVEY6%Qh?B%V`|L>8xg&K7yTfI(bHS$g1d64UUl=l7rEl468buCNG-m z;uOwgg9zu54e}e95cyv^IpI%b;f7=E5(~qtsp7GoFgWvpbL6!6)ol>?34h+n$+4Wx z&Wj@K359P|T41YgHM^(@5K8V4Pxf#-ZXQ zd=s^cCR-pL<;zw)eE2j}vaCaIK-1dI4n~lTHf5;y;J+J6sQ`qh6r5IyF_~ zR2H5E6>}G?IRVOO0#pMHlcgHmsSd~<2j;suNmUg^G92aybshGK)|h&ssxYRVqF^|6 zm>>=v^Btlr!hs5h7~EA5JR;-J7@1XIwZP($C3r|2n3IFQ%qgl|O$Tqx9Fdx6j@Zwn zUf9no^`gcX{=b|Ck!)~h@~{eikr~vKVZBysHakLR85S2NO@CoX$(heo?-LiQMq#&e zN|;?yOG2=`Vn}P*_Z@ zYM690G}yg5JR7n(L!vkZnQ=5pM4;z4&0~R}lG7X-j>xi$=Z{VSmHelnQcoh*-8m25UV~Kz%4*m{J$HWB(yMuEg z1_Lf8yP+^#V@d?3N$k9VPYDhOY#7#HSSKQ`psxsal|clLszC%P4qY%RF z%4nL#VQhC|(n~NTUD6HAZ^G}0*jl@(Tj(PCJLW7@CxWstU5cp7#>54LkRdRb8-^^MU}AlCE|js_259bc7b3D zn0JVCLbyJ>4fa~rU%jXb)~>;gU^hdy1-KFwh!?~`Nbo<%jBZCjgc-?+*a*C3=x|c9 zjDXg`@Ukw#@!7af6H(m;g3U2;$|70^vl@t_uN6kL9G^Rc8tyvYeN_@R9sBwL!aTB75vu(-EpvZ0)$6g7VFSV2N|S>ZWnPv zI5Y%R6m?aq6%o;hdU0CAIiodFySxUi5$QaNu=thy6})JZBu+qhkLj*xa`acdURm!z z{b>&w(>yGske!iuIKIdNIjvDN*#beRb+{j*wog-(Bpdvhn1qIWFoSLzkr66~c}!8) zFu0rHluk^W+MS5*#*~x_&y~|~h#JRgP*32k;511!W4`SOda&!FZfA|gOc2--y2>L0 z95bp8JVU}TiWzzZpQwUq7Q}js3gm*sIxv-@%7_C+5Qn78@MU3g7P~b%Kw~nZsZkZu znS7ekdF0b%5digzAB{N6Dg_!Diw9Eh{HKhE6ZvzgQYbczAFw?TAZW^svv#TmWL222 z_$Mu?_7vp#_?#(+M@Yj}g<1jwww+>Q@jQqCnE_X5czkELPQf`0hZ8{#f*Hw;C%VvU z@xTY7EjbRN0qTXKEAV9)nyMij8?zsHz>Jg8)3L!!Q5%yNA`f4M9S#V%PcXycu-om1 zf#*JSLlg1LAdmPAGgwqWP=y4+f&;RsNv6Vrhs|W6md((ZylAS26A#6nmYWURAj)|p zFMcJR0+Cm!b%foZ*K!JGtA6TGjZVbW>z4Hn)YmESC_3ixU=*7V6GkNUK{U*73aDocKsp{v7d4n7cMG$BG$FfkzaM3H-(K7ObQzuzew% z5b})&&=8p=YqF@~{%U&o&x;IG5QN5?iTEAT6+1dzsTpZWj*(bGr)CM9j(wh3|7NoF z!I5cdb+H}$saV%1edw@}DQcSa0W~>k?8rncq?4YmrkX`o6_%8$>0@dw_aUk}q_L#3 z=1j1PDP)6Z49p!WDxQ3VD0G-M6@m{C(*n1$jQi8<8Y0;-odomFsIsg&rs>JLxfD-w zl2cDj4=uaXv#0e>k68s<)l+hHKM_ANE!kXcWN1dl@bo*obaAHXSj=M-mJmuwaiyl) zTxp42stZf+;uzZn<#Y+%XUBBsP(4L8zdI_uGgchJvO`#@sB_gSKb=QaLGGB2wNbE; zPt{689a#p!puE)Ok)HSUuqlr{a(`$0!%~$5KmEy{zw+v$(p*)`3HAQVeb%HDH4)1Z znIl)njHHaJH~yjzPgb3J3RWbsR>c-t)AdC2WoRv<>h+n-HJMn+8d}fDYNWYRto^aH z6KXeEP3zpxIdrtS98-#FE@e_x{wO^y9m^=;hS^IeUi#1j5f@z*c~$i?Q#V@f$VlGM z`yz|Q+%BuN_|*I-%@+^e*Yln}{qC`4jLE3`Me(ET(t(i{i@9AI_Qg>s>M!^7xVv|Q z-`o6ci;6e#9dm0F_Pyg5TP&gVm{Qc?!?EU#&6z%`?pYt5_Qb)pnHGz=-5B=mjh9#~ zVJkH?G~U%Mj?4M$>m%Q?Sj^2DR@|pk>J-<&>I`*D+tJ1|`qA+gi@AlDHoi+l_{BBw z=yu)Gj;6feiX3aPm|J*hS1hViS_2Pu)-CNz?V*ml##t=p7G7H4n=cB#Hle%IOBV&)cpX%WxXDXf8Tn!1ut#+VM3hf$Kb zg_l-#vTkW-?gy4ph0lVSxrLY3vZ79D4Ve6O nORKvMpr!g}{*zl$TZOL*_|O9vN15+ljepl-@8k!q@aq2nT+W(& literal 0 HcmV?d00001 diff --git a/research/vestibular_schwannoma/data/requirements.txt b/research/vestibular_schwannoma/data/requirements.txt new file mode 100644 index 0000000..43e76b1 --- /dev/null +++ b/research/vestibular_schwannoma/data/requirements.txt @@ -0,0 +1,9 @@ +# CPU-only acquisition and study reproduction (Python >= 3.10). +# Pin the tested downloader and its bundled IDC v24 metadata. +idc-index==0.12.5 +nibabel>=5.2 +numpy>=1.24 +scipy>=1.10 +SimpleITK>=2.3 +pydicom>=2.4 +matplotlib>=3.7 diff --git a/research/vestibular_schwannoma/scripts/prepare_data.py b/research/vestibular_schwannoma/scripts/prepare_data.py new file mode 100644 index 0000000..d2d659a --- /dev/null +++ b/research/vestibular_schwannoma/scripts/prepare_data.py @@ -0,0 +1,350 @@ +#!/usr/bin/env python3 +"""Download, prepare, and verify the published 344-case VS study dataset.""" + +from __future__ import annotations + +import argparse +import json +import shutil +import sys +import zipfile +from pathlib import Path + +PROJECT_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(PROJECT_ROOT)) + +from workflow.data_download import ( # noqa: E402 + ChecksumMismatchError, + DownloadError, + acquire_crossmoda, + acquire_tcia, + download_file, + safe_extract_zip, + verify_checksum, +) +from workflow.data_sources import TCIA_VESTIBULAR_SCHWANNOMA_SEG # noqa: E402 + + +def project_path(value: str | Path) -> Path: + path = Path(value).expanduser() + return path.resolve() if path.is_absolute() else (PROJECT_ROOT / path).resolve() + + +def positive_int(value: str) -> int: + number = int(value) + if number < 1: + raise argparse.ArgumentTypeError("must be positive") + return number + + +def parser() -> argparse.ArgumentParser: + root = argparse.ArgumentParser(description=__doc__) + commands = root.add_subparsers(dest="command", required=True) + download = commands.add_parser("download", help="download original public sources") + download.add_argument( + "--dataset", choices=["all", "queen-square", "tilburg", "labels"], default="all" + ) + download.add_argument("--raw-root", default="data/raw") + download.add_argument("--index", default="data/ml_dataset.csv") + download.add_argument( + "--tcia-backend", + choices=["idc", "retriever"], + help="DICOM downloader (default: idc; --retriever selects retriever)", + ) + download.add_argument( + "--retriever", type=Path, help="current TCIA_Data_Retriever executable" + ) + download.add_argument( + "--assets-only", action="store_true", help="skip TCIA DICOM retrieval" + ) + download.add_argument( + "--corrected-label-url", + help="download the label archive using its pinned SHA-256", + ) + + for command, help_text in [ + ("prepare", "prepare and verify only the shared study cases"), + ("verify", "verify prepared data against the frozen study reference"), + ("freeze-reference", "maintainer: fingerprint the current reference dataset"), + ("bundle-labels", "maintainer: package the 17 corrected reference masks"), + ]: + sub = commands.add_parser(command, help=help_text) + sub.add_argument("--index", default="data/ml_dataset.csv") + sub.add_argument("--reference", default="data/reference_dataset.json") + if command in {"prepare", "verify", "freeze-reference"}: + sub.add_argument("--workers", type=positive_int, default=2) + if command != "prepare": + sub.add_argument("--data-root", default="../nii_data") + if command == "prepare": + sub.add_argument("--raw-root", default="data/raw") + queen = sub.add_mutually_exclusive_group() + queen.add_argument("--queen-square", help="TCIA DICOM collection root") + queen.add_argument( + "--queen-square-nifti", help="existing native Queen Square NIfTI root" + ) + sub.add_argument("--contours", help="TCIA JSON contour ZIP or directory") + sub.add_argument( + "--crossmoda", help="extracted crossMoDA training archive root" + ) + sub.add_argument( + "--corrected-labels", help="17-mask ZIP or local NL mask directory" + ) + sub.add_argument( + "--from-existing", + help="reuse a local VS_Seg checkout's data and NL masks", + ) + sub.add_argument( + "--output", + default="../nii_data", + help="new dataset root; existing outputs are refused", + ) + elif command == "verify": + sub.add_argument( + "--allow-extra-files", + action="store_true", + help="verify indexed pairs in a legacy mixed-data root", + ) + elif command == "freeze-reference": + sub.add_argument("--output", default="data/reference_dataset.json") + else: + sub.add_argument( + "--output", default="data/releases/vs_corrected_labels_v1.zip" + ) + sub.add_argument("--asset-metadata", default="data/corrected_labels.json") + return root + + +def progress(number: int, total: int, case_id: str) -> None: + if number == 1 or number % 10 == 0 or number == total: + print(f"{number}/{total}: {case_id}", file=sys.stderr, flush=True) + + +def label_asset() -> dict: + asset = json.loads((PROJECT_ROOT / "data/corrected_labels.json").read_text()) + filename = asset["filename"] + if not isinstance(filename, str) or Path(filename).name != filename: + raise ValueError("Invalid corrected-label archive filename") + if not asset.get("sha256"): + raise ValueError("Corrected-label metadata must declare a SHA-256") + return asset + + +def bundled_labels(asset: dict | None = None) -> Path: + """Locate and verify the replacement masks included in this checkout.""" + asset = label_asset() if asset is None else asset + archive = PROJECT_ROOT / "data/releases" / asset["filename"] + if not verify_checksum(archive, f"sha256:{asset['sha256']}"): + raise ChecksumMismatchError( + f"Bundled corrected-label archive differs: {archive}" + ) + return archive + + +def _download(args: argparse.Namespace) -> dict: + raw = project_path(args.raw_root) + results = {} + backend = args.tcia_backend or ("retriever" if args.retriever else "idc") + if args.retriever and backend != "retriever": + raise ValueError("--retriever requires --tcia-backend retriever") + retriever = ( + project_path(args.retriever) + if args.retriever is not None + else shutil.which("TCIA_Data_Retriever") + ) + if ( + args.dataset in {"all", "queen-square"} + and not args.assets_only + and backend == "retriever" + and retriever is None + ): + raise FileNotFoundError( + "Install TCIA_Data_Retriever from https://github.com/TCIA/data-retriever " + "and pass --retriever /path/to/TCIA_Data_Retriever. Use --assets-only " + "if downloading DICOM through the TCIA graphical app; see data/README.md." + ) + client = None + if ( + args.dataset in {"all", "queen-square"} + and not args.assets_only + and backend == "idc" + ): + from workflow.data_idc import create_idc_client, select_t1_series + + client = create_idc_client() + series = select_t1_series(client, project_path(args.index)) + print( + f"IDC: {len(series)} study T1 series, " + f"{sum(s['series_size_MB'] for s in series) / 1000:.2f} GB; " + "T2 and radiation-therapy series are omitted.", + file=sys.stderr, + flush=True, + ) + if args.dataset in {"all", "tilburg"}: + print( + "Downloading/checking crossMoDA 2022 training archive (7.8 GB)...", + file=sys.stderr, + flush=True, + ) + result = acquire_crossmoda(raw / "crossmoda2022") + results["crossmoda_training"] = str(result.data_dir) + if args.dataset in {"all", "queen-square"}: + print( + "Downloading/checking TCIA assets and DICOM...", file=sys.stderr, flush=True + ) + result = acquire_tcia( + raw / "queen_square", + retriever=retriever, + assets_only=args.assets_only or backend == "idc", + ) + data_dir = result.data_dir + if client is not None: + from workflow.data_idc import download_t1_series + + data_dir = download_t1_series( + client, series, raw / "queen_square", project_path(args.index) + ) + results["queen_square_idc_manifest"] = str( + raw / "queen_square/idc_manifest.json" + ) + results["queen_square_dicom"] = str(data_dir) if data_dir is not None else None + results["queen_square_backend"] = "assets-only" if args.assets_only else backend + results["queen_square_assets"] = [str(p) for p in result.assets] + if args.dataset in {"all", "labels"}: + asset = label_asset() + if args.corrected_label_url: + if not args.corrected_label_url.startswith("https://"): + raise ValueError("Corrected-label downloads require HTTPS") + archive = download_file( + args.corrected_label_url, + raw / asset["filename"], + checksum=f"sha256:{asset['sha256']}", + ) + else: + archive = bundled_labels(asset) + results["corrected_labels"] = str( + safe_extract_zip(archive, raw / "corrected_labels") + ) + return results + + +def run(args: argparse.Namespace) -> dict: + if args.command == "download": + return _download(args) + # CLI help and direct URL downloads do not import imaging dependencies. + from workflow.data_preparation import ( + build_label_bundle, + freeze_reference, + prepare_dataset, + verify_dataset, + write_json, + ) + + index = project_path(args.index) + reference = project_path(args.reference) + if args.command == "freeze-reference": + output = project_path(args.output) + if output.exists(): + raise FileExistsError(f"Frozen reference already exists: {output}") + value = freeze_reference( + index, project_path(args.data_root), workers=args.workers, progress=progress + ) + output.parent.mkdir(parents=True, exist_ok=True) + write_json(output, value) + return {"reference": str(output), **value["counts"]} + if args.command == "verify": + return verify_dataset( + index, + reference, + project_path(args.data_root), + workers=args.workers, + progress=progress, + allow_extra_files=args.allow_extra_files, + ) + if args.command == "bundle-labels": + metadata = project_path(args.asset_metadata) + if metadata.exists(): + raise FileExistsError(f"Label asset metadata already exists: {metadata}") + value = build_label_bundle( + index, reference, project_path(args.data_root), project_path(args.output) + ) + metadata.parent.mkdir(parents=True, exist_ok=True) + write_json(metadata, value) + return value + raw = project_path(args.raw_root) + if args.from_existing: + if any( + ( + args.queen_square, + args.queen_square_nifti, + args.contours, + args.crossmoda, + args.corrected_labels, + ) + ): + raise ValueError( + "Use --from-existing by itself, or pass the individual source options" + ) + source = project_path(args.from_existing) + queen = source / "nii_data/queen_square_data" + crossmoda = source / "crossmoda_data" + corrections = source / "errors_fixed_nl" + contours = None + converted = True + else: + queen = ( + project_path(args.queen_square_nifti or args.queen_square) + if (args.queen_square_nifti or args.queen_square) + else raw / "queen_square/dicom" + ) + crossmoda = ( + project_path(args.crossmoda) + if args.crossmoda + else raw / "crossmoda2022/crossmoda2022_training" + ) + corrections = ( + project_path(args.corrected_labels) + if args.corrected_labels + else raw / "corrected_labels" + ) + if not args.corrected_labels and not corrections.exists(): + corrections = bundled_labels() + contours = ( + project_path(args.contours) + if args.contours + else raw + / "queen_square/assets" + / TCIA_VESTIBULAR_SCHWANNOMA_SEG.asset("contours").filename + ) + converted = args.queen_square_nifti is not None + result = prepare_dataset( + index, + reference, + project_path(args.output), + queen_square=queen, + queen_square_nifti=converted, + crossmoda=crossmoda, + corrections=corrections, + contours=contours, + workers=args.workers, + progress=progress, + ) + return { + "prepared": str(project_path(args.output)), + "verified": True, + **result["counts"], + } + + +def main(argv: list[str] | None = None) -> int: + args = parser().parse_args(argv) + try: + result = run(args) + except (OSError, ValueError, KeyError, DownloadError, zipfile.BadZipFile) as error: + print(f"Data preparation failed: {error}", file=sys.stderr) + return 1 + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/research/vestibular_schwannoma/tests/workflow/test_data_conversion.py b/research/vestibular_schwannoma/tests/workflow/test_data_conversion.py new file mode 100644 index 0000000..ffc59e2 --- /dev/null +++ b/research/vestibular_schwannoma/tests/workflow/test_data_conversion.py @@ -0,0 +1,113 @@ +"""Native series selection and historical contour conversion regression checks.""" + +import json +import zipfile +from pathlib import Path + +import numpy as np +import pytest +import SimpleITK as sitk + +from workflow.data_conversion import find_t1_series, rasterize_t1, read_contours + + +def test_t1_selection_uses_dicom_metadata_not_folder_names(tmp_path, monkeypatch): + case = tmp_path / "Vestibular-Schwannoma-SEG/VS-SEG-001" + headers = {} + for folder, modality, description in [ + ("a", "RTDOSE", "t1_rt"), + ("b", "MR", "t2_ciss"), + ("c", "MR", "t1_mpr"), + ]: + file = case / folder / "one.dcm" + file.parent.mkdir(parents=True) + file.touch() + headers[str(file)] = { + "Modality": modality, + "SeriesDescription": description, + "PatientID": "VS-SEG-001", + } + monkeypatch.setattr( + "workflow.data_conversion.sitk.ImageSeriesReader.GetGDCMSeriesIDs", + lambda _: ["uid"], + ) + monkeypatch.setattr( + "workflow.data_conversion.sitk.ImageSeriesReader.GetGDCMSeriesFileNames", + lambda p, _: [str(next(Path(p).glob("*.dcm")))], + ) + monkeypatch.setattr( + "workflow.data_conversion.pydicom.dcmread", lambda p, **_: headers[p] + ) + assert find_t1_series(tmp_path, "vs_gk_1") == [str(case / "c/one.dcm")] + headers[str(case / "b/one.dcm")]["SeriesDescription"] = "t1_other" + with pytest.raises(ValueError, match="duplicated T1|expected one T1"): + find_t1_series(tmp_path, "vs_gk_1") + + +def test_wrong_patient_is_rejected(tmp_path, monkeypatch): + folder = tmp_path / "VS-SEG-001" + folder.mkdir() + file = folder / "one.dcm" + file.touch() + monkeypatch.setattr( + "workflow.data_conversion.sitk.ImageSeriesReader.GetGDCMSeriesIDs", + lambda _: ["uid"], + ) + monkeypatch.setattr( + "workflow.data_conversion.sitk.ImageSeriesReader.GetGDCMSeriesFileNames", + lambda *_: [str(file)], + ) + monkeypatch.setattr( + "workflow.data_conversion.pydicom.dcmread", + lambda *a, **k: { + "Modality": "MR", + "SeriesDescription": "t1_mpr", + "PatientID": "VS-SEG-002", + }, + ) + with pytest.raises(ValueError, match="PatientID"): + find_t1_series(tmp_path, "vs_gk_1") + + +def test_historical_polygon_fill_and_geometry(tmp_path): + image = sitk.GetImageFromArray(np.zeros((3, 6, 6), dtype=np.uint16)) + image.SetOrigin((10, 20, 30)) + image.SetSpacing((1, 1, 1.5)) + reference, output = tmp_path / "image.nii.gz", tmp_path / "mask.nii.gz" + sitk.WriteImage(image, str(reference)) + square = [[11, 21, 31.5], [14, 21, 31.5], [14, 24, 31.5], [11, 24, 31.5]] + contours = [ + {"structure_name": "TV", "LPS_contour_points": [square]}, + { + "structure_name": "Cochlea", + "LPS_contour_points": [[[10, 20, 30], [15, 20, 30], [15, 25, 30]]], + }, + ] + rasterize_t1(contours, reference, output) + result = sitk.ReadImage(str(output)) + values = sitk.GetArrayFromImage(result) + assert values[1, 2, 2] == 1 + assert values[0].sum() == values[2].sum() == 0 + assert values[1, 0, 0] == 0 + assert result.GetOrigin() == image.GetOrigin() + assert result.GetSpacing() == image.GetSpacing() + assert result.GetDirection() == image.GetDirection() + + +def test_contours_zip_and_directory_are_equivalent_and_duplicates_rejected(tmp_path): + contours = [{"structure_name": "TV", "LPS_contour_points": []}] + path = tmp_path / "contours/vs_gk_1_t1/contours.json" + path.parent.mkdir(parents=True) + path.write_text(json.dumps(contours)) + archive = tmp_path / "contours.zip" + with zipfile.ZipFile(archive, "w") as z: + z.write(path, "contours/vs_gk_1_t1/contours.json") + assert ( + read_contours(archive, "vs_gk_1") + == read_contours(tmp_path, "vs_gk_1") + == contours + ) + with zipfile.ZipFile(archive, "a") as z: + z.write(path, "other/vs_gk_1_t1/contours.json") + with pytest.raises(ValueError, match="expected one contours"): + read_contours(archive, "vs_gk_1") diff --git a/research/vestibular_schwannoma/tests/workflow/test_data_download.py b/research/vestibular_schwannoma/tests/workflow/test_data_download.py new file mode 100644 index 0000000..637dde7 --- /dev/null +++ b/research/vestibular_schwannoma/tests/workflow/test_data_download.py @@ -0,0 +1,461 @@ +from __future__ import annotations + +import hashlib +import io +import stat +import subprocess +import zipfile +from pathlib import Path +from unittest.mock import patch +from urllib.error import HTTPError + +import pytest + +from workflow import data_download as download +from workflow.data_sources import CROSSMODA_2022, TCIA_VESTIBULAR_SCHWANNOMA_SEG + + +class FakeResponse(io.BytesIO): + def __init__( + self, body: bytes, *, status: int, headers: dict[str, str] | None = None + ): + super().__init__(body) + self.status = status + self.headers = headers or {} + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + return False + + +class RecordingOpener: + def __init__(self, *responses: FakeResponse): + self.responses = list(responses) + self.requests = [] + + def __call__(self, request, *, timeout): + self.requests.append((request, timeout)) + return self.responses.pop(0) + + +def digest(data: bytes, algorithm: str = "md5") -> str: + return f"{algorithm}:{hashlib.new(algorithm, data).hexdigest()}" + + +def make_zip(path: Path, members: dict[str, bytes]) -> Path: + with zipfile.ZipFile(path, "w") as archive: + for name, body in members.items(): + archive.writestr(name, body) + return path + + +def test_source_constants_match_official_records(): + assert CROSSMODA_2022.doi == "10.5281/zenodo.6504722" + assert CROSSMODA_2022.license_name == "CC BY-NC-SA 4.0" + training = CROSSMODA_2022.asset("training") + assert training.url == ( + "https://zenodo.org/records/6504722/files/crossmoda2022_training.zip?download=1" + ) + assert training.checksum == "md5:8d68fcd44eaee6b0a4371ce344e775cd" + + tcia = TCIA_VESTIBULAR_SCHWANNOMA_SEG + assert tcia.doi == "10.7937/TCIA.9YTJ-5Q73" + assert tcia.license_name == "CC BY 4.0" + assert {asset.name for asset in tcia.assets} == { + "manifest", + "contours", + "matrices", + "modality_mapping", + } + root = "https://www.cancerimagingarchive.net/wp-content/uploads/" + assert tcia.asset("manifest").url == root + ( + "Vestibular-Schwannoma-SEG-Feb-2021-manifest.tcia" + ) + assert tcia.asset("contours").url == root + ( + "Vestibular-Schwannoma-SEG-contours-Mar-2021.zip" + ) + assert tcia.asset("matrices").url == root + ( + "Vestibular-Schwannoma-SEG_matrices-Mar-2021.zip" + ) + assert ( + tcia.asset("matrices").filename + == "Vestibular-Schwannoma-SEG_matrices-Mar-2021.zip" + ) + assert ( + tcia.asset("modality_mapping").url == root + "DirectoryNamesMappingModality.csv" + ) + + +def test_download_resumes_partial_file_when_range_is_honored(tmp_path): + destination = tmp_path / "asset.bin" + partial = tmp_path / "asset.bin.part" + partial.write_bytes(b"first-") + complete = b"first-second" + opener = RecordingOpener( + FakeResponse( + b"second", + status=206, + headers={"Content-Range": "bytes 6-11/12"}, + ) + ) + + result = download.download_file( + "https://example.invalid/asset.bin", + destination, + checksum=digest(complete), + opener=opener, + retries=0, + ) + + assert result == destination + assert destination.read_bytes() == complete + assert not partial.exists() + request, timeout = opener.requests[0] + assert request.get_header("Range") == "bytes=6-" + assert timeout == download.DEFAULT_TIMEOUT + + +def test_download_restarts_partial_file_when_server_ignores_range(tmp_path): + destination = tmp_path / "asset.bin" + partial = tmp_path / "asset.bin.part" + partial.write_bytes(b"stale-prefix") + complete = b"complete-response" + opener = RecordingOpener(FakeResponse(complete, status=200)) + + download.download_file( + "https://example.invalid/asset.bin", + destination, + checksum=digest(complete, "sha256"), + opener=opener, + retries=0, + ) + + assert destination.read_bytes() == complete + assert opener.requests[0][0].get_header("Range") == "bytes=12-" + + +def test_download_restarts_after_server_rejects_resume_range(tmp_path): + destination = tmp_path / "asset.bin" + partial = tmp_path / "asset.bin.part" + partial.write_bytes(b"uncertain-partial") + requests = [] + + def opener(request, *, timeout): + requests.append(request) + if len(requests) == 1: + raise HTTPError(request.full_url, 416, "Range Not Satisfiable", {}, None) + return FakeResponse(b"complete-response", status=200) + + download.download_file( + "https://example.invalid/asset.bin", + destination, + opener=opener, + retries=1, + ) + + assert destination.read_bytes() == b"complete-response" + assert [request.get_header("Range") for request in requests] == ["bytes=17-", None] + + +def test_download_resumes_after_truncated_full_response(tmp_path): + destination = tmp_path / "asset.bin" + opener = RecordingOpener( + FakeResponse(b"abcd", status=200, headers={"Content-Length": "8"}), + FakeResponse( + b"efgh", + status=206, + headers={"Content-Length": "4", "Content-Range": "bytes 4-7/8"}, + ), + ) + + download.download_file( + "https://example.invalid/asset.bin", + destination, + opener=opener, + retries=1, + ) + + assert destination.read_bytes() == b"abcdefgh" + assert [request.get_header("Range") for request, _ in opener.requests] == [ + None, + "bytes=4-", + ] + + +def test_download_resumes_after_truncated_partial_response(tmp_path): + destination = tmp_path / "asset.bin" + destination.with_name("asset.bin.part").write_bytes(b"ab") + opener = RecordingOpener( + FakeResponse( + b"cd", + status=206, + headers={"Content-Length": "6", "Content-Range": "bytes 2-7/8"}, + ), + FakeResponse( + b"efgh", + status=206, + headers={"Content-Length": "4", "Content-Range": "bytes 4-7/8"}, + ), + ) + + download.download_file( + "https://example.invalid/asset.bin", + destination, + opener=opener, + retries=1, + ) + + assert destination.read_bytes() == b"abcdefgh" + assert [request.get_header("Range") for request, _ in opener.requests] == [ + "bytes=2-", + "bytes=4-", + ] + + +@pytest.mark.parametrize( + "response", + [ + FakeResponse( + b"ignored", status=200, headers={"Content-Length": "not-a-number"} + ), + FakeResponse( + b"ignored", + status=206, + headers={"Content-Length": "5", "Content-Range": "bytes 2-7/8"}, + ), + ], +) +def test_invalid_length_headers_preserve_existing_partial(tmp_path, response): + destination = tmp_path / "asset.bin" + partial = destination.with_name("asset.bin.part") + partial.write_bytes(b"ab") + + with pytest.raises(download.DownloadError, match="Content-Length"): + download.download_file( + "https://example.invalid/asset.bin", + destination, + opener=RecordingOpener(response), + retries=0, + ) + + assert partial.read_bytes() == b"ab" + assert not destination.exists() + + +def test_checksum_mismatch_does_not_publish_or_keep_invalid_partial(tmp_path): + destination = tmp_path / "asset.bin" + opener = RecordingOpener(FakeResponse(b"wrong", status=200)) + + with pytest.raises(download.ChecksumMismatchError, match="Checksum mismatch"): + download.download_file( + "https://example.invalid/asset.bin", + destination, + checksum=digest(b"expected"), + opener=opener, + retries=0, + ) + + assert not destination.exists() + assert not destination.with_name("asset.bin.part").exists() + + +def test_verified_cache_is_reused_and_force_redownloads(tmp_path): + destination = tmp_path / "asset.bin" + destination.write_bytes(b"cached") + + def should_not_open(*args, **kwargs): + raise AssertionError("verified cache should not make a request") + + assert ( + download.download_file( + "https://example.invalid/asset.bin", + destination, + checksum=digest(b"cached"), + opener=should_not_open, + ) + == destination + ) + + opener = RecordingOpener(FakeResponse(b"fresh", status=200)) + download.download_file( + "https://example.invalid/asset.bin", + destination, + checksum=digest(b"fresh"), + opener=opener, + force=True, + retries=0, + ) + assert destination.read_bytes() == b"fresh" + assert opener.requests[0][0].get_header("Range") is None + + +@pytest.mark.parametrize("member", ["../escape.txt", "/absolute.txt", "C:\\escape.txt"]) +def test_safe_zip_extraction_rejects_traversal_and_absolute_paths(tmp_path, member): + archive = make_zip(tmp_path / "unsafe.zip", {member: b"unsafe"}) + destination = tmp_path / "output" + + with pytest.raises(download.UnsafeArchiveError, match="Unsafe archive path"): + download.safe_extract_zip(archive, destination) + + assert not destination.exists() + assert not (tmp_path / "escape.txt").exists() + + +def test_safe_zip_extraction_rejects_symbolic_links(tmp_path): + archive_path = tmp_path / "symlink.zip" + link = zipfile.ZipInfo("link") + link.create_system = 3 + link.external_attr = (stat.S_IFLNK | 0o777) << 16 + with zipfile.ZipFile(archive_path, "w") as archive: + archive.writestr(link, "target") + + with pytest.raises(download.UnsafeArchiveError, match="symbolic link"): + download.safe_extract_zip(archive_path, tmp_path / "output") + + +def test_safe_zip_extraction_publishes_complete_tree_and_keeps_siblings(tmp_path): + archive = make_zip( + tmp_path / "safe.zip", + {"training_source/case.nii.gz": b"image", "metadata.csv": b"case,center\n"}, + ) + sibling = tmp_path / "raw-archive.zip" + sibling.write_bytes(b"do not delete") + + destination = download.safe_extract_zip(archive, tmp_path / "output") + + assert (destination / "training_source" / "case.nii.gz").read_bytes() == b"image" + assert (destination / "metadata.csv").read_bytes() == b"case,center\n" + assert sibling.read_bytes() == b"do not delete" + assert not list(tmp_path.glob(".output.staging-*")) + + +def test_safe_zip_extraction_rejects_file_destination_even_with_force(tmp_path): + archive = make_zip(tmp_path / "safe.zip", {"case.nii.gz": b"image"}) + destination = tmp_path / "output" + destination.write_bytes(b"keep me") + + with pytest.raises(FileExistsError, match="not a directory"): + download.safe_extract_zip(archive, destination, force=True) + + assert destination.read_bytes() == b"keep me" + + +def test_safe_zip_extraction_rejects_dangling_destination_symlink(tmp_path): + archive = make_zip(tmp_path / "safe.zip", {"case.nii.gz": b"image"}) + destination = tmp_path / "output" + destination.symlink_to(tmp_path / "missing", target_is_directory=True) + + with pytest.raises(FileExistsError, match="not a directory"): + download.safe_extract_zip(archive, destination, force=True) + + assert destination.is_symlink() + + +def test_safe_zip_keeps_backup_if_publish_and_rollback_both_fail(tmp_path, monkeypatch): + archive = make_zip(tmp_path / "safe.zip", {"case.nii.gz": b"new"}) + destination = tmp_path / "output" + destination.mkdir() + (destination / "sentinel.txt").write_bytes(b"original") + real_replace = download.os.replace + + def fail_publish_and_rollback(source, target): + source = Path(source) + target = Path(target) + if source.name.startswith(".output.staging-") and target == destination: + raise OSError("publish failed") + if source.name.startswith(".output.backup-") and target == destination: + raise OSError("rollback failed") + return real_replace(source, target) + + monkeypatch.setattr(download.os, "replace", fail_publish_and_rollback) + with pytest.raises(OSError, match="rollback failed"): + download.safe_extract_zip(archive, destination, force=True) + + assert not destination.exists() + backups = list(tmp_path.glob(".output.backup-*")) + assert len(backups) == 1 + assert (backups[0] / "sentinel.txt").read_bytes() == b"original" + + +def test_crossmoda_acquisition_extracts_downloaded_archive(tmp_path, monkeypatch): + archive = make_zip( + tmp_path / "fixture.zip", {"training_source/case.nii.gz": b"data"} + ) + + def fake_download(url, destination, **kwargs): + destination = Path(destination) + destination.write_bytes(archive.read_bytes()) + return destination + + monkeypatch.setattr(download, "download_file", fake_download) + result = download.acquire_crossmoda(tmp_path / "crossmoda") + + assert result.source is CROSSMODA_2022 + assert result.assets[0].name == "crossmoda2022_training.zip" + assert (result.data_dir / "training_source" / "case.nii.gz").read_bytes() == b"data" + + +def test_tcia_retriever_uses_required_argument_list(tmp_path): + executable = tmp_path / "TCIA_Data_Retriever" + executable.write_text("#!/bin/sh\n") + executable.chmod(0o755) + manifest = tmp_path / "manifest.tcia" + manifest.write_text("manifest") + calls = [] + + def runner(command, *, check): + calls.append((command, check)) + return subprocess.CompletedProcess(command, 0) + + command = download.run_tcia_data_retriever( + manifest, + tmp_path / "dicom", + executable=executable, + runner=runner, + ) + + assert list(command) == [ + str(executable), + "--cli", + "-i", + str(manifest), + "-o", + str(tmp_path / "dicom"), + "--skip-existing", + "--directory-mode", + "descriptive", + "--accept-data-policy", + ] + assert calls == [(list(command), True)] + + +def test_tcia_retriever_absence_has_actionable_error(tmp_path): + with patch("workflow.data_download.shutil.which", return_value=None): + with pytest.raises( + download.RetrieverNotFoundError, + match="manifest/assets-only mode", + ): + download.run_tcia_data_retriever( + tmp_path / "manifest.tcia", + tmp_path / "dicom", + ) + + +def test_tcia_assets_only_does_not_resolve_or_run_retriever(tmp_path, monkeypatch): + expected_assets = tuple( + tmp_path / asset.filename for asset in TCIA_VESTIBULAR_SCHWANNOMA_SEG.assets + ) + + def fake_download_assets(*args, **kwargs): + return expected_assets + + monkeypatch.setattr(download, "download_assets", fake_download_assets) + with patch("workflow.data_download.shutil.which") as which: + result = download.acquire_tcia(tmp_path, assets_only=True) + + assert result.assets == expected_assets + assert result.data_dir is None + which.assert_not_called() diff --git a/research/vestibular_schwannoma/tests/workflow/test_data_idc.py b/research/vestibular_schwannoma/tests/workflow/test_data_idc.py new file mode 100644 index 0000000..0afde2e --- /dev/null +++ b/research/vestibular_schwannoma/tests/workflow/test_data_idc.py @@ -0,0 +1,154 @@ +"""Check cohort selection and incomplete transfers without downloading test data.""" + +import csv +import json +from pathlib import Path +from types import SimpleNamespace + +import pytest + +from scripts.prepare_data import main +from workflow import data_idc as idc +from workflow.data_download import DownloadError + + +def write_index(path, cases): + with path.open("w", newline="") as handle: + writer = csv.DictWriter(handle, fieldnames=["case_id"]) + writer.writeheader() + writer.writerows({"case_id": c} for c in cases) + return path + + +def metadata(number, description="t1_test", uid=None): + return { + "PatientID": f"VS-SEG-{number:03d}", + "StudyInstanceUID": f"1.2.{number}", + "SeriesInstanceUID": uid or f"1.3.{number}", + "SeriesDescription": description, + "instanceCount": 2, + "series_size_MB": 1.0, + } + + +class FakeClient: + def __init__(self, records, *, missing_slice=False): + self.records = records + self.missing_slice = missing_slice + self.requested = [] + + def sql_query(self, query): + return SimpleNamespace(to_dict=lambda _: self.records) + + def get_idc_version(self): + return "v24" + + def download_dicom_series(self, **kwargs): + self.requested = kwargs["seriesInstanceUID"] + assert kwargs["use_s5cmd_sync"] + for s in self.records: + if s["SeriesInstanceUID"] not in self.requested: + continue + folder = ( + Path(kwargs["downloadDir"]) + / s["PatientID"] + / s["StudyInstanceUID"] + / f"MR_{s['SeriesInstanceUID']}" + ) + folder.mkdir(parents=True, exist_ok=True) + for i in range(s["instanceCount"] - int(self.missing_slice)): + (folder / f"{i}.dcm").write_bytes(b"synthetic instance") + + +def test_selection_uses_csv_order_and_omits_t2_and_excluded_cases(tmp_path): + index = write_index( + tmp_path / "index.csv", ["vs_gk_12", "vs_gk_4", "crossmoda2022_etz_0"] + ) + client = FakeClient( + [ + metadata(4), + metadata(12), + metadata(8), + metadata(73), + metadata(131), + metadata(4, description="t2_test", uid="1.3.4.2"), + ] + ) + selected = idc.select_t1_series(client, index) + assert [s["case_id"] for s in selected] == ["vs_gk_12", "vs_gk_4"] + assert [s["SeriesInstanceUID"] for s in selected] == ["1.3.12", "1.3.4"] + + +@pytest.mark.parametrize("records", [[], [metadata(4), metadata(4, uid="1.3.4.2")]]) +def test_missing_or_ambiguous_t1_series_aborts_before_transfer(tmp_path, records): + index = write_index(tmp_path / "index.csv", ["vs_gk_4"]) + with pytest.raises(DownloadError, match="one T1 series per case"): + idc.select_t1_series(FakeClient(records), index) + + +def test_two_cases_cannot_share_a_series_uid(tmp_path): + index = write_index(tmp_path / "index.csv", ["vs_gk_4", "vs_gk_12"]) + with pytest.raises(DownloadError, match="repeated T1 SeriesInstanceUID"): + idc.select_t1_series( + FakeClient([metadata(4), metadata(12, uid="1.3.4")]), index + ) + + +def test_partial_download_remains_incomplete_and_can_resume(tmp_path, monkeypatch): + monkeypatch.setattr(idc, "version", lambda _: "0.12.5") + index = write_index(tmp_path / "index.csv", ["vs_gk_4"]) + client = FakeClient([metadata(4)], missing_slice=True) + selected = idc.select_t1_series(client, index) + destination = tmp_path / "download" + with pytest.raises(DownloadError, match="downloaded 1/2"): + idc.download_t1_series(client, selected, destination, index) + manifest = destination / "idc_manifest.json" + assert not json.loads(manifest.read_text())["download_complete"] + client.missing_slice = False + assert ( + idc.download_t1_series(client, selected, destination, index) + == destination / "dicom" + ) + completed = json.loads(manifest.read_text()) + assert completed["download_complete"] + assert completed["series"] == selected + assert completed["index_sha256"] == idc.file_checksum(index, "sha256") + + +def test_default_cli_checks_idc_dependency_before_network(monkeypatch, capsys): + def unavailable(): + raise DownloadError("Install idc-index") + + monkeypatch.setattr(idc, "create_idc_client", unavailable) + monkeypatch.setattr( + "scripts.prepare_data.acquire_tcia", + lambda *a, **k: pytest.fail("network called"), + ) + monkeypatch.setattr( + "scripts.prepare_data.acquire_crossmoda", + lambda *a, **k: pytest.fail("network called"), + ) + assert main(["download"]) == 1 + assert "Install idc-index" in capsys.readouterr().err + + +def test_assets_only_does_not_import_idc(monkeypatch, tmp_path, capsys): + monkeypatch.setattr(idc, "create_idc_client", lambda: pytest.fail("IDC imported")) + monkeypatch.setattr( + "scripts.prepare_data.acquire_tcia", + lambda *a, **k: SimpleNamespace(data_dir=None, assets=[]), + ) + assert ( + main( + [ + "download", + "--dataset", + "queen-square", + "--assets-only", + "--raw-root", + str(tmp_path), + ] + ) + == 0 + ) + assert json.loads(capsys.readouterr().out)["queen_square_backend"] == "assets-only" diff --git a/research/vestibular_schwannoma/tests/workflow/test_data_preparation.py b/research/vestibular_schwannoma/tests/workflow/test_data_preparation.py new file mode 100644 index 0000000..4763379 --- /dev/null +++ b/research/vestibular_schwannoma/tests/workflow/test_data_preparation.py @@ -0,0 +1,312 @@ +"""Data-integrity checks using synthetic public-ID fixtures, without real scans.""" + +import csv +import json +import zipfile + +import nibabel as nib +import numpy as np +import pytest + +from scripts.prepare_data import main +from scripts import prepare_data as cli +from workflow.data_preparation import ( + COMPONENT_SIGNATURES, + build_label_bundle, + case_paths, + clean_components, + describe_nifti, + freeze_reference, + integer_labels, + prepare_dataset, + verify_dataset, + write_json, +) + + +def write_nifti(path, data, *, private_text=False): + path.parent.mkdir(parents=True, exist_ok=True) + image = nib.Nifti1Image(data, np.diag([-1.0, -1.0, 1.5, 1.0])) + image.header.set_xyzt_units("mm") + if private_text: + image.header["descrip"] = b"private annotation editor information" + image.header.extensions.append( + nib.nifti1.Nifti1Extension(6, b"private extension") + ) + nib.save(image, path) + + +@pytest.fixture +def study(tmp_path): + reference_root = tmp_path / "reference" + index = tmp_path / "project/data/ml_dataset.csv" + index.parent.mkdir(parents=True) + rows = [] + for case, fold in [("vs_gk_4", "1"), ("crossmoda2022_etz_0", "2")]: + image_path, mask_path = case_paths(case) + image = np.arange(60, dtype=np.uint16).reshape(3, 4, 5) + mask = np.zeros_like(image, dtype=np.uint8) + mask[1, 1, 1:4] = 1 + write_nifti(reference_root / image_path, image) + write_nifti(reference_root / mask_path, mask, private_text=True) + rows.append( + { + "case_id": case, + "t1_img_path": "../nii_data/" + image_path.as_posix(), + "t1_seg_path": "../nii_data/" + mask_path.as_posix(), + "fold": fold, + "quartile_label": "0", + "volume_mm3": "4.5", + } + ) + with index.open("w", newline="") as handle: + writer = csv.DictWriter(handle, fieldnames=list(rows[0])) + writer.writeheader() + writer.writerows(rows) + frozen = index.parent / "reference_dataset.json" + write_json(frozen, freeze_reference(index, reference_root)) + crossmoda = tmp_path / "crossmoda" + crossmoda.mkdir() + with (crossmoda / "infos_source_training.csv").open("w", newline="") as handle: + writer = csv.writer(handle) + writer.writerow(["crossmoda_name", "center", "group"]) + writer.writerow(["crossmoda2022_etz_0", "Tilburg", "training_source"]) + writer.writerow(["crossmoda2021_ldn_1", "London", "training_source"]) + image_path, mask_path = case_paths("crossmoda2022_etz_0") + raw_image = nib.load(reference_root / image_path) + raw_mask = np.asanyarray(nib.load(reference_root / mask_path).dataobj).copy() + raw_mask[0, 0, 0] = 2 # Cochlea must never become tumour foreground. + write_nifti( + crossmoda / "training_source" / image_path.name, + np.asanyarray(raw_image.dataobj), + ) + write_nifti(crossmoda / "training_source" / mask_path.name, raw_mask) + corrections = tmp_path / "corrections" + _, relative = case_paths("vs_gk_4") + write_nifti( + corrections / relative.name, + np.asanyarray(nib.load(reference_root / relative).dataobj), + ) + return index, frozen, reference_root, crossmoda, corrections + + +def prepare(study, output): + index, frozen, root, crossmoda, corrections = study + return prepare_dataset( + index, + frozen, + output, + queen_square=root / "queen_square_data", + queen_square_nifti=True, + crossmoda=crossmoda, + corrections=corrections, + ) + + +def test_prepare_applies_overlay_removes_cochlea_preserves_sources_and_folds( + study, tmp_path +): + index, frozen, root, crossmoda, _ = study + source = root / case_paths("vs_gk_4")[1] + before = source.read_bytes() + # The prepared Queen Square input can contain an incorrect original mask. + bad = nib.load(source) + values = np.asanyarray(bad.dataobj).copy() + values[0, 0, 0] = 1 + write_nifti(source, values) + original = source.read_bytes() + output = tmp_path / "prepared" + result = prepare(study, output) + assert source.read_bytes() == original != before + assert result["counts"]["cases"] == 2 + assert result["cases"][0]["human_corrected"] + assert verify_dataset(index, frozen, output)["verified"] + _, mask = case_paths("crossmoda2022_etz_0") + assert nib.load(crossmoda / "training_source" / mask.name).get_fdata()[0, 0, 0] == 2 + assert nib.load(output / mask).get_fdata()[0, 0, 0] == 0 + assert not (output / "tilburg_data/crossmoda2021_ldn_1").exists() + + +def test_reference_checks_voxels_not_compression_or_mask_storage_dtype(study): + index, frozen, root, _, _ = study + path = root / case_paths("vs_gk_4")[1] + image = nib.load(path) + write_nifti(path, np.asanyarray(image.dataobj).astype(np.int16)) + assert verify_dataset(index, frozen, root)["verified"] + values = image.get_fdata() + values[0, 0, 0] = 1 + write_nifti(path, values.astype(np.uint8)) + with pytest.raises(ValueError, match="volume|voxels"): + verify_dataset(index, frozen, root) + + +def test_geometry_drift_is_rejected(study): + index, frozen, root, _, _ = study + path = root / case_paths("vs_gk_4")[0] + image = nib.load(path) + affine = image.affine.copy() + affine[0, 3] += 2 + nib.save(nib.Nifti1Image(np.asanyarray(image.dataobj), affine), path) + with pytest.raises(ValueError, match="geometry|affine"): + verify_dataset(index, frozen, root) + + +def test_failed_overlay_does_not_publish_partial_dataset(study, tmp_path): + *_, corrections = study + path = next(corrections.glob("*.nii.gz")) + image = nib.load(path) + values = np.asanyarray(image.dataobj).copy() + values[0, 0, 0] = 1 + write_nifti(path, values) + output = tmp_path / "prepared" + with pytest.raises(ValueError, match="corrected mask voxels"): + prepare(study, output) + assert not output.exists() + assert not list(tmp_path.glob(".prepared.staging-*")) + + +def test_existing_output_is_refused(study, tmp_path): + output = tmp_path / "prepared" + output.mkdir() + marker = output / "keep" + marker.write_text("existing data") + with pytest.raises(FileExistsError): + prepare(study, output) + assert marker.read_text() == "existing data" + + +def test_csv_changes_are_rejected(study): + index, frozen, root, _, _ = study + index.write_text(index.read_text().replace(",1,0,4.5", ",3,0,4.5")) + with pytest.raises(ValueError, match="CSV does not match"): + verify_dataset(index, frozen, root) + + +def test_exact_inventory_rejects_unindexed_cases_and_backups(study): + index, frozen, root, _, _ = study + extra = root / "queen_square_data/vs_gk_73/backup.nii.gz" + write_nifti(extra, np.ones((2, 2, 2), dtype=np.uint8)) + with pytest.raises(ValueError, match="inventory mismatch"): + verify_dataset(index, frozen, root) + assert verify_dataset(index, frozen, root, allow_extra_files=True)["verified"] + + +def test_verified_cleanup_is_idempotent_and_rejects_unexpected_blobs(): + largest, extra = COMPONENT_SIGNATURES["vs_gk_13"] + mask = np.zeros((1, 3, largest), dtype=np.uint8) + mask[0, 0, :] = 1 + mask[0, 2, :extra] = 1 + cleaned, removed = clean_components("vs_gk_13", mask) + assert removed == extra and cleaned.sum() == largest + assert clean_components("vs_gk_13", cleaned)[1] == 0 + mask[0, 2, extra] = 1 + with pytest.raises(ValueError, match="unexpected component signature"): + clean_components("vs_gk_13", mask) + assert np.array_equal(clean_components("vs_gk_1", mask)[0], mask) + + +def test_label_quantization_is_normalized_but_fractional_masks_are_rejected(): + assert np.array_equal(integer_labels(np.array([0.0, 0.9999999997671694])), [0, 1]) + with pytest.raises(ValueError, match="fractional"): + integer_labels(np.array([0.0, 0.5])) + + +def test_label_bundle_is_deterministic_and_strips_private_headers(study, tmp_path): + index, frozen, root, crossmoda, _ = study + first, second = tmp_path / "first.zip", tmp_path / "second.zip" + one = build_label_bundle(index, frozen, root, first) + two = build_label_bundle(index, frozen, root, second) + assert one["sha256"] == two["sha256"] + with zipfile.ZipFile(first) as archive: + assert archive.namelist() == [ + "LICENSE.txt", + "README.txt", + "case_mapping.csv", + "labels/vs_gk_4_seg_refT1.nii.gz", + "manifest.json", + ] + archive.extractall(tmp_path / "labels") + mask = nib.load(tmp_path / "labels/labels/vs_gk_4_seg_refT1.nii.gz") + assert not bytes(mask.header["descrip"]).strip(b"\0") + assert not mask.header.extensions + source = describe_nifti(root / case_paths("vs_gk_4")[1], "mask") + bundled = describe_nifti( + tmp_path / "labels/labels/vs_gk_4_seg_refT1.nii.gz", "mask" + ) + assert source["voxel_sha256"] == bundled["voxel_sha256"] + output = tmp_path / "from_bundle" + assert prepare_dataset( + index, + frozen, + output, + queen_square=root / "queen_square_data", + queen_square_nifti=True, + crossmoda=crossmoda, + corrections=first, + ) + + +def test_download_preflights_retriever_before_network(monkeypatch, capsys): + monkeypatch.setattr("scripts.prepare_data.shutil.which", lambda _: None) + assert main(["download", "--tcia-backend", "retriever"]) == 1 + assert "Install TCIA_Data_Retriever" in capsys.readouterr().err + + +def bundle_in_project(study, monkeypatch): + index, frozen, root, *_ = study + project = index.parent.parent + archive = project / "data/releases/vs_corrected_labels_v1.zip" + archive.parent.mkdir() + asset = build_label_bundle(index, frozen, root, archive) + write_json(project / "data/corrected_labels.json", asset) + monkeypatch.setattr(cli, "PROJECT_ROOT", project) + return archive + + +def test_labels_command_extracts_bundled_masks_without_network( + study, monkeypatch, capsys +): + archive = bundle_in_project(study, monkeypatch) + monkeypatch.setattr( + cli, "download_file", lambda *a, **k: pytest.fail("Network used") + ) + assert main(["download", "--dataset", "labels"]) == 0 + extracted = cli.PROJECT_ROOT / "data/raw/corrected_labels" + assert json.loads(capsys.readouterr().out)["corrected_labels"] == str(extracted) + with zipfile.ZipFile(archive) as bundle: + name = "labels/vs_gk_4_seg_refT1.nii.gz" + assert (extracted / name).read_bytes() == bundle.read(name) + + +def test_corrupt_bundled_archive_is_rejected_before_extraction( + study, monkeypatch, capsys +): + archive = bundle_in_project(study, monkeypatch) + archive.write_bytes(archive.read_bytes() + b"unexpected bytes") + assert main(["download", "--dataset", "labels"]) == 1 + assert "archive differs" in capsys.readouterr().err + assert not (cli.PROJECT_ROOT / "data/raw/corrected_labels").exists() + + +def test_prepare_uses_bundled_masks_without_separate_label_argument( + study, monkeypatch, tmp_path, capsys +): + bundle_in_project(study, monkeypatch) + _, _, root, crossmoda, _ = study + output = tmp_path / "from_default_bundle" + assert ( + main( + [ + "prepare", + "--queen-square-nifti", + str(root / "queen_square_data"), + "--crossmoda", + str(crossmoda), + "--output", + str(output), + ] + ) + == 0 + ) + assert json.loads(capsys.readouterr().out)["verified"] is True + assert verify_dataset(*study[:2], output)["cases"] == 2 diff --git a/research/vestibular_schwannoma/workflow/data_conversion.py b/research/vestibular_schwannoma/workflow/data_conversion.py new file mode 100644 index 0000000..7546041 --- /dev/null +++ b/research/vestibular_schwannoma/workflow/data_conversion.py @@ -0,0 +1,177 @@ +"""Reproduce the study's native T1 conversion from TCIA DICOM and JSON. + +The contour algorithm is intentionally the historical VS_Seg +``preprocessing/simple_data_conversion.py`` algorithm. Changing its rounding, +polygon filling, or coordinate calculation would change the study labels. +Every output is checked against the frozen study reference by data_preparation. +""" + +from __future__ import annotations + +import json +import zipfile +from functools import lru_cache +from pathlib import Path + +import numpy as np +import pydicom +import SimpleITK as sitk +from matplotlib.path import Path as PolygonPath + + +TUMOR_NAMES = frozenset( + [ + "AN", + "an", + "Acoustic Neuroma", + "TV", + "tv", + "Tumor", + "tumour", + "GTV", + "Vol 2y", + "Vol2016", + "Vol2y", + "Vol2015", + "Vol1y", + "Vol 3y", + "Vol 1y", + "Vol2.5y", + "Vol2017", + "Vol T2 2y", + "Vol3y", + "Vol3m", + "vol 2y", + "Vol 2.5y", + "Vol2.5", + "Vol 2.5", + "Vol20mo", + "Vol2018_CISS", + "Vol2018", + "Vol2016CISS", + "Vol2014", + "vol 1y", + "Vol16", + "Vol1", + "TvT1", + "T1+ 13", + "Rt AN", + "6m", + "1y", + ] +) + + +def find_t1_series(root: Path, case_id: str) -> list[str]: + """Find one T1 MR series in classic/descriptive TCIA or organized input.""" + + number = int(case_id.removeprefix("vs_gk_")) + candidates = [ + root / f"VS-SEG-{number:03d}", + root / "Vestibular-Schwannoma-SEG" / f"VS-SEG-{number:03d}", + root / f"{case_id}_t1", + ] + cases = [p for p in candidates if p.is_dir()] + if len(cases) != 1: + raise ValueError(f"{case_id}: expected one TCIA case folder under {root}") + folders = sorted({p.parent for p in cases[0].rglob("*.dcm")}) + series = {} + for folder in folders: + # TCIA stores each series in its own directory. Skip RT/T2 directories + # before GDCM enumeration, which warns on non-image radiation files. + first = sorted(folder.glob("*.dcm"))[0] + header = pydicom.dcmread( + str(first), + stop_before_pixels=True, + specific_tags=["Modality", "SeriesDescription"], + ) + if header.get("Modality") != "MR" or not str( + header.get("SeriesDescription", "") + ).lower().startswith("t1_"): + continue + for uid in sitk.ImageSeriesReader.GetGDCMSeriesIDs(str(folder)) or (): + files = sitk.ImageSeriesReader.GetGDCMSeriesFileNames(str(folder), uid) + if not files: + continue + header = pydicom.dcmread( + files[0], + stop_before_pixels=True, + specific_tags=["Modality", "SeriesDescription", "PatientID"], + ) + if header.get("Modality") != "MR": + continue + description = str(header.get("SeriesDescription", "")).lower() + if not description.startswith("t1_"): + continue + if str(header.get("PatientID", "")) != f"VS-SEG-{number:03d}": + raise ValueError(f"{case_id}: DICOM PatientID does not match") + if uid in series: + raise ValueError(f"{case_id}: duplicated T1 series {uid}") + series[uid] = list(files) + if len(series) != 1: + raise ValueError(f"{case_id}: expected one T1 MR series, found {len(series)}") + return next(iter(series.values())) + + +def convert_t1(root: Path, case_id: str, destination: Path) -> None: + """Convert the selected native MR series, without registration/resampling.""" + + reader = sitk.ImageSeriesReader() + reader.SetNumberOfThreads(1) + reader.SetFileNames(find_t1_series(root, case_id)) + image = reader.Execute() + sitk.WriteImage(image, str(destination)) + + +def read_contours(source: Path, case_id: str) -> list[dict]: + relative = f"{case_id}_t1/contours.json" + if source.is_file(): + with zipfile.ZipFile(source) as archive: + names = [n for n in archive.namelist() if n.endswith(relative)] + if len(names) != 1: + raise ValueError(f"{case_id}: expected one contours.json in {source}") + return json.loads(archive.read(names[0])) + candidates = [source / relative, source / "contours" / relative] + matches = [p for p in candidates if p.is_file()] + if len(matches) != 1: + raise ValueError(f"{case_id}: expected one contours.json under {source}") + return json.loads(matches[0].read_text()) + + +@lru_cache(maxsize=4) +def _slice_grid(width: int, height: int) -> np.ndarray: + x, y = np.meshgrid(np.arange(width), np.arange(height), indexing="xy") + return np.column_stack([x.ravel(), y.ravel()]) + + +def rasterize_t1(contours: list[dict], reference: Path, destination: Path) -> None: + """Apply the frozen historical rounding and polygon fill algorithm. + + The historical physical-to-index calculation uses origin and spacing. It + must not be substituted for a general direction-aware contour converter. + Its suitability here is established by voxel parity with this study only. + """ + + image = sitk.ReadImage(str(reference)) + size = image.GetSize() + origin = np.asarray(image.GetOrigin()) + spacing = np.asarray(image.GetSpacing()) + mask = np.zeros((size[2], size[1], size[0]), dtype=np.uint8) + for structure in contours: + if structure.get("structure_name") not in TUMOR_NAMES: + continue + for points in structure.get("LPS_contour_points", []): + if len(points) < 3: + continue + indices = np.round((np.asarray(points) - origin) / spacing).astype(int) + z = int(np.median(indices[:, 2])) + if 0 <= z < size[2]: + inside = ( + PolygonPath(indices[:, :2]) + .contains_points(_slice_grid(size[0], size[1])) + .reshape(size[1], size[0]) + ) + mask[z] = np.logical_or(mask[z], inside) + output = sitk.GetImageFromArray(mask) + output.CopyInformation(image) + sitk.WriteImage(output, str(destination)) diff --git a/research/vestibular_schwannoma/workflow/data_download.py b/research/vestibular_schwannoma/workflow/data_download.py new file mode 100644 index 0000000..e1e305a --- /dev/null +++ b/research/vestibular_schwannoma/workflow/data_download.py @@ -0,0 +1,596 @@ +"""Reliable, restartable acquisition of the upstream datasets.""" + +from __future__ import annotations + +import hashlib +import os +import re +import shutil +import stat +import subprocess +import tempfile +import time +import uuid +import zipfile +from collections.abc import Callable +from dataclasses import dataclass +from pathlib import Path, PurePosixPath, PureWindowsPath +from typing import BinaryIO +from urllib.error import HTTPError, URLError +from urllib.request import Request, urlopen + +from .data_sources import ( + CROSSMODA_2022, + TCIA_VESTIBULAR_SCHWANNOMA_SEG, + DatasetSource, +) + +DEFAULT_CHUNK_SIZE = 1024 * 1024 +DEFAULT_TIMEOUT = 60.0 +DEFAULT_RETRIES = 2 +TCIA_RETRIEVER_NAME = "TCIA_Data_Retriever" +_CONTENT_RANGE_RE = re.compile(r"bytes ([0-9]+)-([0-9]+)/([0-9]+)", re.IGNORECASE) + + +class DownloadError(RuntimeError): + """An upstream asset could not be downloaded or acquired.""" + + +class ChecksumMismatchError(DownloadError): + """Downloaded bytes do not match the checksum published upstream.""" + + +class UnsafeArchiveError(DownloadError): + """An archive contains an entry that cannot be extracted safely.""" + + +class RetrieverNotFoundError(DownloadError): + """The TCIA command-line data retriever is not installed.""" + + +@dataclass(frozen=True, slots=True) +class AcquisitionResult: + """Paths produced while acquiring one upstream dataset.""" + + source: DatasetSource + assets: tuple[Path, ...] + data_dir: Path | None + + +UrlOpener = Callable[..., BinaryIO] +ProcessRunner = Callable[..., subprocess.CompletedProcess[object]] + + +def _checksum_spec(expected: str) -> tuple[str, str]: + value = expected.strip().lower() + if ":" in value: + algorithm, digest = value.split(":", 1) + elif len(value) == 32: + algorithm, digest = "md5", value + elif len(value) == 64: + algorithm, digest = "sha256", value + else: + raise ValueError( + "Checksum must be prefixed with 'md5:' or 'sha256:', or have " + "the corresponding hexadecimal digest length" + ) + + if algorithm not in {"md5", "sha256"}: + raise ValueError(f"Unsupported checksum algorithm: {algorithm!r}") + expected_length = 32 if algorithm == "md5" else 64 + if len(digest) != expected_length or any( + c not in "0123456789abcdef" for c in digest + ): + raise ValueError(f"Invalid {algorithm} checksum: {digest!r}") + return algorithm, digest + + +def file_checksum( + path: str | Path, algorithm: str, *, chunk_size: int = DEFAULT_CHUNK_SIZE +) -> str: + """Calculate an MD5 or SHA-256 digest without loading the file into memory.""" + + algorithm = algorithm.lower() + if algorithm not in {"md5", "sha256"}: + raise ValueError(f"Unsupported checksum algorithm: {algorithm!r}") + try: + digest = hashlib.new(algorithm, usedforsecurity=False) + except TypeError: # pragma: no cover - compatibility with older Python builds + digest = hashlib.new(algorithm) + with Path(path).open("rb") as stream: + for chunk in iter(lambda: stream.read(chunk_size), b""): + digest.update(chunk) + return digest.hexdigest() + + +def verify_checksum(path: str | Path, expected: str) -> bool: + """Return whether *path* matches an ``md5:`` or ``sha256:`` digest.""" + + algorithm, expected_digest = _checksum_spec(expected) + return file_checksum(path, algorithm) == expected_digest + + +def _response_status(response: object) -> int | None: + status = getattr(response, "status", None) + if status is not None: + return int(status) + getcode = getattr(response, "getcode", None) + return int(getcode()) if getcode is not None and getcode() is not None else None + + +def _response_header(response: object, name: str) -> str | None: + headers = getattr(response, "headers", None) + if headers is None: + return None + value = headers.get(name) + return str(value) if value is not None else None + + +def _content_length(response: object, url: str) -> int | None: + raw_value = _response_header(response, "Content-Length") + if raw_value is None: + return None + value = raw_value.strip() + if re.fullmatch(r"[0-9]+", value) is None: + raise DownloadError( + f"Server returned an invalid Content-Length for {url!r}: {raw_value!r}" + ) + return int(value) + + +def _resume_range( + response: object, + offset: int, + content_length: int | None, + url: str, +) -> tuple[int, int] | None: + if _response_status(response) != 206: + return None + if offset <= 0: + raise DownloadError( + f"Server returned an unsolicited partial response for {url!r}" + ) + + raw_value = _response_header(response, "Content-Range") + match = ( + _CONTENT_RANGE_RE.fullmatch(raw_value.strip()) + if raw_value is not None + else None + ) + if match is None: + raise DownloadError( + f"Server returned an invalid Content-Range for {url!r}: {raw_value!r}" + ) + start, end, total = (int(value) for value in match.groups()) + if start != offset or end < start or total <= end: + raise DownloadError( + f"Server returned an inconsistent Content-Range for {url!r}: {raw_value!r}" + ) + + range_length = end - start + 1 + if content_length is not None and content_length != range_length: + raise DownloadError( + f"Content-Length does not match Content-Range for {url!r}: " + f"{content_length} != {range_length}" + ) + return range_length, total + + +def _download_once( + url: str, + partial_path: Path, + *, + timeout: float, + chunk_size: int, + opener: UrlOpener, +) -> None: + offset = partial_path.stat().st_size if partial_path.exists() else 0 + headers = {"Range": f"bytes={offset}-"} if offset else {} + request = Request(url, headers=headers) + + try: + response_context = opener(request, timeout=timeout) + except HTTPError as error: + if error.code == 416 and offset: + # Without a trustworthy remote length, a 416 response does not + # prove that the partial file is complete. Discard it so the next + # retry requests the full asset instead of publishing uncertain + # bytes (especially for assets without a published checksum). + partial_path.unlink(missing_ok=True) + raise DownloadError( + f"Server rejected the resume offset for {url!r}; restarting" + ) from error + raise + + with response_context as response: + content_length = _content_length(response, url) + resume_range = _resume_range(response, offset, content_length, url) + expected_response_bytes = ( + resume_range[0] if resume_range is not None else content_length + ) + + # A server is allowed to ignore Range and return 200. Restarting the + # partial file avoids silently appending a full response to it. + mode = "ab" if resume_range is not None else "wb" + response_bytes = 0 + try: + with partial_path.open(mode) as output: + while True: + chunk = response.read(chunk_size) + if not chunk: + break + response_bytes += len(chunk) + if ( + expected_response_bytes is not None + and response_bytes > expected_response_bytes + ): + raise DownloadError( + f"Server sent more bytes than declared for {url!r}: " + f"expected {expected_response_bytes}, " + f"received at least {response_bytes}" + ) + output.write(chunk) + except DownloadError: + if mode == "ab": + with partial_path.open("r+b") as output: + output.truncate(offset) + else: + partial_path.unlink(missing_ok=True) + raise + + if ( + expected_response_bytes is not None + and response_bytes != expected_response_bytes + ): + raise DownloadError( + f"Server response ended early for {url!r}: " + f"expected {expected_response_bytes} bytes, received {response_bytes}" + ) + if resume_range is not None: + _, total = resume_range + accumulated = partial_path.stat().st_size + if accumulated != total: + raise DownloadError( + f"Partial response for {url!r} ended at {accumulated} of {total} bytes" + ) + + +def download_file( + url: str, + destination: str | Path, + *, + checksum: str | None = None, + timeout: float = DEFAULT_TIMEOUT, + retries: int = DEFAULT_RETRIES, + force: bool = False, + chunk_size: int = DEFAULT_CHUNK_SIZE, + opener: UrlOpener | None = None, +) -> Path: + """Stream *url* to *destination* with restart and integrity support. + + In-progress bytes are stored at ``.part``. If the server + honors a Range request they are resumed; if it returns a full 200 response, + the partial file is safely overwritten. The final name appears only after + checksum verification and an atomic rename. + """ + + if retries < 0: + raise ValueError("retries must be non-negative") + if chunk_size <= 0: + raise ValueError("chunk_size must be positive") + + destination = Path(destination) + destination.parent.mkdir(parents=True, exist_ok=True) + partial_path = destination.with_name(destination.name + ".part") + + if destination.exists() and not force: + if checksum is None or verify_checksum(destination, checksum): + return destination + + if force and partial_path.exists(): + partial_path.unlink() + + open_url = opener or urlopen + last_error: BaseException | None = None + for attempt in range(retries + 1): + try: + _download_once( + url, + partial_path, + timeout=timeout, + chunk_size=chunk_size, + opener=open_url, + ) + last_error = None + break + except (DownloadError, OSError, URLError) as error: + last_error = error + if attempt == retries: + break + time.sleep(min(2**attempt, 5)) + + if last_error is not None: + raise DownloadError( + f"Failed to download {url!r} after {retries + 1} attempt(s): {last_error}" + ) from last_error + if not partial_path.exists(): + raise DownloadError(f"Download of {url!r} did not create {partial_path}") + + if checksum is not None and not verify_checksum(partial_path, checksum): + try: + algorithm, expected_digest = _checksum_spec(checksum) + actual_digest = file_checksum(partial_path, algorithm) + finally: + # A completed but invalid partial cannot be resumed meaningfully. + partial_path.unlink(missing_ok=True) + raise ChecksumMismatchError( + f"Checksum mismatch for {url!r}: expected {algorithm}:" + f"{expected_digest}, got {algorithm}:{actual_digest}" + ) + + os.replace(partial_path, destination) + return destination + + +def download_assets( + source: DatasetSource, + destination: str | Path, + *, + timeout: float = DEFAULT_TIMEOUT, + retries: int = DEFAULT_RETRIES, + force: bool = False, + opener: UrlOpener | None = None, +) -> tuple[Path, ...]: + """Download every published asset in *source* into one directory.""" + + destination = Path(destination) + return tuple( + download_file( + asset.url, + destination / asset.filename, + checksum=asset.checksum, + timeout=timeout, + retries=retries, + force=force, + opener=opener, + ) + for asset in source.assets + ) + + +def _safe_member_path(info: zipfile.ZipInfo) -> PurePosixPath: + raw_name = info.filename + if "\x00" in raw_name: + raise UnsafeArchiveError(f"Archive entry contains a NUL byte: {raw_name!r}") + + # ZIP uses POSIX separators, but treating backslashes as separators also + # protects extraction on Windows and archives produced by Windows tools. + normalized_name = raw_name.replace("\\", "/") + posix_path = PurePosixPath(normalized_name) + windows_path = PureWindowsPath(raw_name) + if ( + not normalized_name + or posix_path.is_absolute() + or windows_path.is_absolute() + or windows_path.drive + or ".." in posix_path.parts + ): + raise UnsafeArchiveError(f"Unsafe archive path: {raw_name!r}") + + mode = (info.external_attr >> 16) & 0xFFFF + file_type = stat.S_IFMT(mode) + if stat.S_ISLNK(mode): + raise UnsafeArchiveError(f"Archive entry is a symbolic link: {raw_name!r}") + if file_type not in {0, stat.S_IFREG, stat.S_IFDIR}: + raise UnsafeArchiveError(f"Archive entry is not a regular file: {raw_name!r}") + return posix_path + + +def safe_extract_zip( + archive: str | Path, + destination: str | Path, + *, + force: bool = False, +) -> Path: + """Safely extract a ZIP into an atomically published directory.""" + + archive = Path(archive) + destination = Path(destination) + destination.parent.mkdir(parents=True, exist_ok=True) + + if destination.is_symlink() or (destination.exists() and not destination.is_dir()): + raise FileExistsError( + f"Extraction destination is not a directory: {destination}" + ) + if destination.exists() and not force: + return destination + + staging = Path( + tempfile.mkdtemp(prefix=f".{destination.name}.staging-", dir=destination.parent) + ) + backup: Path | None = None + try: + with zipfile.ZipFile(archive) as zip_file: + members = [(info, _safe_member_path(info)) for info in zip_file.infolist()] + staging_root = staging.resolve() + for info, relative_path in members: + target = staging.joinpath(*relative_path.parts) + resolved_target = target.resolve() + if os.path.commonpath((staging_root, resolved_target)) != str( + staging_root + ): + raise UnsafeArchiveError(f"Unsafe archive path: {info.filename!r}") + if info.is_dir() or stat.S_ISDIR((info.external_attr >> 16) & 0xFFFF): + target.mkdir(parents=True, exist_ok=True) + continue + target.parent.mkdir(parents=True, exist_ok=True) + with ( + zip_file.open(info) as source_stream, + target.open("wb") as output_stream, + ): + shutil.copyfileobj(source_stream, output_stream) + + if destination.exists(): + backup = destination.with_name( + f".{destination.name}.backup-{uuid.uuid4().hex}" + ) + os.replace(destination, backup) + try: + os.replace(staging, destination) + except BaseException: + if backup is not None: + os.replace(backup, destination) + backup = None + raise + if backup is not None: + shutil.rmtree(backup) + backup = None + return destination + finally: + if staging.exists(): + shutil.rmtree(staging) + + +def acquire_crossmoda( + destination: str | Path, + *, + force: bool = False, + timeout: float = DEFAULT_TIMEOUT, + retries: int = DEFAULT_RETRIES, + opener: UrlOpener | None = None, +) -> AcquisitionResult: + """Download and safely extract the CrossMoDA 2022 training archive.""" + + destination = Path(destination) + destination.mkdir(parents=True, exist_ok=True) + training = CROSSMODA_2022.asset("training") + archive = download_file( + training.url, + destination / training.filename, + checksum=training.checksum, + timeout=timeout, + retries=retries, + force=force, + opener=opener, + ) + data_dir = safe_extract_zip( + archive, + destination / "crossmoda2022_training", + force=force, + ) + return AcquisitionResult(CROSSMODA_2022, (archive,), data_dir) + + +def _resolve_retriever(executable: str | Path | None) -> str: + if executable is None: + resolved = shutil.which(TCIA_RETRIEVER_NAME) + else: + candidate = Path(executable).expanduser() + resolved = ( + str(candidate) + if candidate.is_file() and os.access(candidate, os.X_OK) + else None + ) + + if resolved is None: + raise RetrieverNotFoundError( + f"Could not find the {TCIA_RETRIEVER_NAME!r} executable. Install the " + "current TCIA Data Retriever from " + "https://github.com/TCIA/data-retriever, pass its path explicitly, or run " + "in manifest/assets-only mode." + ) + return resolved + + +def run_tcia_data_retriever( + manifest: str | Path, + destination: str | Path, + *, + executable: str | Path | None = None, + runner: ProcessRunner | None = None, +) -> tuple[str, ...]: + """Invoke TCIA Data Retriever without using a shell.""" + + retriever = _resolve_retriever(executable) + manifest = Path(manifest) + destination = Path(destination) + destination.mkdir(parents=True, exist_ok=True) + command = ( + retriever, + "--cli", + "-i", + str(manifest), + "-o", + str(destination), + "--skip-existing", + "--directory-mode", + "descriptive", + "--accept-data-policy", + ) + run_process = runner or subprocess.run + try: + run_process(list(command), check=True) + except subprocess.CalledProcessError as error: + raise DownloadError( + f"TCIA Data Retriever failed with exit status {error.returncode}" + ) from error + return command + + +def acquire_tcia( + destination: str | Path, + *, + retriever: str | Path | None = None, + assets_only: bool = False, + force: bool = False, + timeout: float = DEFAULT_TIMEOUT, + retries: int = DEFAULT_RETRIES, + opener: UrlOpener | None = None, + runner: ProcessRunner | None = None, +) -> AcquisitionResult: + """Acquire TCIA's manifest/supporting assets and, optionally, DICOM data.""" + + destination = Path(destination) + assets_dir = destination / "assets" + assets = download_assets( + TCIA_VESTIBULAR_SCHWANNOMA_SEG, + assets_dir, + timeout=timeout, + retries=retries, + force=force, + opener=opener, + ) + if assets_only: + return AcquisitionResult(TCIA_VESTIBULAR_SCHWANNOMA_SEG, assets, None) + + manifest_asset = TCIA_VESTIBULAR_SCHWANNOMA_SEG.asset("manifest") + manifest_path = assets_dir / manifest_asset.filename + data_dir = destination / "dicom" + run_tcia_data_retriever( + manifest_path, + data_dir, + executable=retriever, + runner=runner, + ) + return AcquisitionResult(TCIA_VESTIBULAR_SCHWANNOMA_SEG, assets, data_dir) + + +__all__ = [ + "AcquisitionResult", + "ChecksumMismatchError", + "DEFAULT_CHUNK_SIZE", + "DEFAULT_RETRIES", + "DEFAULT_TIMEOUT", + "DownloadError", + "RetrieverNotFoundError", + "TCIA_RETRIEVER_NAME", + "UnsafeArchiveError", + "acquire_crossmoda", + "acquire_tcia", + "download_assets", + "download_file", + "file_checksum", + "run_tcia_data_retriever", + "safe_extract_zip", + "verify_checksum", +] diff --git a/research/vestibular_schwannoma/workflow/data_idc.py b/research/vestibular_schwannoma/workflow/data_idc.py new file mode 100644 index 0000000..3918d27 --- /dev/null +++ b/research/vestibular_schwannoma/workflow/data_idc.py @@ -0,0 +1,129 @@ +"""Download only the study's Queen Square T1 series from the public IDC mirror.""" + +from __future__ import annotations + +import csv +import json +import re +from collections import Counter +from importlib.metadata import version +from pathlib import Path + +from .data_download import DownloadError, file_checksum + +IDC_COLLECTION = "vestibular_schwannoma_seg" +IDC_DIRECTORY_TEMPLATE = "%PatientID/%StudyInstanceUID/%Modality_%SeriesInstanceUID" + + +def create_idc_client(): + """Import IDC only when downloading DICOM; help and other routes stay usable.""" + + try: + from idc_index import IDCClient + except ImportError as error: + raise DownloadError( + "Install the Python IDC downloader with " + "'pip install -r data/requirements.txt' (includes idc-index). " + "Data Retriever is not required." + ) from error + return IDCClient.client() + + +def select_t1_series(client, index: Path) -> list[dict]: + """Require exactly one T1 MR series for each Queen Square case in the CSV.""" + + with index.open(newline="") as handle: + rows = list(csv.DictReader(handle)) + case_ids = [r["case_id"] for r in rows if r["case_id"].startswith("vs_gk_")] + if not case_ids or len(case_ids) != len(set(case_ids)): + raise ValueError("Study CSV has no Queen Square cases or repeats a case") + if any(not re.fullmatch(r"vs_gk_[1-9][0-9]*", c) for c in case_ids): + raise ValueError("Study CSV contains an invalid Queen Square case ID") + patients = {f"VS-SEG-{int(c.removeprefix('vs_gk_')):03d}": c for c in case_ids} + metadata = client.sql_query( + "SELECT PatientID, StudyInstanceUID, SeriesInstanceUID, SeriesDescription, " + "instanceCount, series_size_MB FROM index " + f"WHERE collection_id = '{IDC_COLLECTION}' AND Modality = 'MR'" + ) + series = [ + {"case_id": patients[s["PatientID"]], **s} + for s in metadata.to_dict("records") + if s["PatientID"] in patients + and str(s["SeriesDescription"]).lower().startswith("t1_") + ] + counts = Counter(s["case_id"] for s in series) + mismatches = [f"{c}: {counts[c]} series" for c in case_ids if counts[c] != 1] + if mismatches: + raise DownloadError( + "IDC must provide one T1 series per case: " + ", ".join(mismatches) + ) + if len({s["SeriesInstanceUID"] for s in series}) != len(series): + raise DownloadError("IDC returned a repeated T1 SeriesInstanceUID") + for item in series: + if ( + any( + not re.fullmatch(r"[0-9]+(?:\.[0-9]+)+", str(item[key])) + for key in ("StudyInstanceUID", "SeriesInstanceUID") + ) + or int(item["instanceCount"]) <= 0 + ): + raise DownloadError(f"{item['case_id']}: invalid IDC series metadata") + by_case = {s["case_id"]: s for s in series} + return [by_case[c] for c in case_ids] + + +def download_t1_series( + client, series: list[dict], destination: Path, index: Path +) -> Path: + """Resume IDC downloads, check instance counts, and record the exact selection.""" + + if not series: + raise ValueError("Cannot download an empty IDC selection") + data_dir = destination / "dicom" + data_dir.mkdir(parents=True, exist_ok=True) + manifest = { + "schema": "vs-idc-download-v1", + "collection_id": IDC_COLLECTION, + "idc_version": client.get_idc_version(), + "idc_index_version": version("idc-index"), + "index_sha256": file_checksum(index, "sha256"), + "directory_template": IDC_DIRECTORY_TEMPLATE, + "source_bucket_location": "aws", + "download_complete": False, + "series": series, + } + manifest_path = destination / "idc_manifest.json" + + def write_manifest(): + temporary = manifest_path.with_suffix(".json.tmp") + temporary.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") + temporary.replace(manifest_path) + + write_manifest() + try: + client.download_dicom_series( + seriesInstanceUID=[s["SeriesInstanceUID"] for s in series], + downloadDir=str(data_dir), + dirTemplate=IDC_DIRECTORY_TEMPLATE, + use_s5cmd_sync=True, + show_progress_bar=True, + quiet=True, + ) + except (RuntimeError, OSError, ValueError) as error: + raise DownloadError(f"IDC download failed; rerun to resume: {error}") from error + for item in series: + folder = ( + data_dir + / item["PatientID"] + / item["StudyInstanceUID"] + / f"MR_{item['SeriesInstanceUID']}" + ) + count = sum(1 for _ in folder.glob("*.dcm")) + if count != int(item["instanceCount"]): + raise DownloadError( + f"{item['case_id']}: downloaded {count}/{item['instanceCount']} " + "DICOM instances; rerun to resume" + ) + manifest["download_complete"] = True + write_manifest() + return data_dir diff --git a/research/vestibular_schwannoma/workflow/data_preparation.py b/research/vestibular_schwannoma/workflow/data_preparation.py new file mode 100644 index 0000000..c9f0441 --- /dev/null +++ b/research/vestibular_schwannoma/workflow/data_preparation.py @@ -0,0 +1,666 @@ +"""Prepare and verify the exact study cohort against its public fingerprints.""" + +from __future__ import annotations + +import csv +import gzip +import hashlib +import json +import os +import re +import shutil +import tempfile +import zipfile +from collections import Counter +from concurrent.futures import ThreadPoolExecutor, as_completed +from contextlib import contextmanager +from pathlib import Path, PurePosixPath +from typing import Callable + +import nibabel as nib +import numpy as np +from scipy import ndimage + +from .data_download import file_checksum, safe_extract_zip + +SCHEMA = "vs-study-reference-v1" +PROJECT_ROOT = Path(__file__).resolve().parents[1] +HUMAN_CORRECTED_CASES = tuple( + f"vs_gk_{i}" + for i in [4, 5, 29, 35, 43, 44, 45, 47, 55, 56, 61, 63, 71, 74, 76, 96, 119] +) +LABEL_CONTRIBUTORS = ("Njål Lura", "Satheshkumar Kaliyugarassan") +LABEL_RELEASE_LICENSE = "CC BY 4.0" +LABEL_RELEASE_LICENSE_URL = "https://creativecommons.org/licenses/by/4.0/" +COMPONENT_SIGNATURES = { + "vs_gk_12": (6685, 79), + "vs_gk_13": (2927, 10), + "vs_gk_53": (2756, 52), + "vs_gk_86": (3336, 55), + "vs_gk_87": (4144, 17), + "vs_gk_250": (24946, 1), + "crossmoda2022_etz_38": (3616, 1), + "crossmoda2022_etz_67": (3568, 1), +} +EXCLUSIONS = { + "vs_gk_73": "Meningioma; excluded in VS_Seg/00c_combined_data.ipynb", + "vs_gk_8": "Excluded after radiologist review; fastMONAI commit d8bd2de", + "vs_gk_131": "Excluded after radiologist review; fastMONAI commit d8bd2de", +} +_CASE = re.compile(r"^(vs_gk_[1-9][0-9]*|crossmoda2022_etz_(0|[1-9][0-9]*))$") + + +def write_json(path: Path, value: dict) -> None: + path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n") + + +def case_paths(case_id: str) -> tuple[Path, Path]: + if not _CASE.fullmatch(case_id): + raise ValueError(f"Invalid study case ID: {case_id!r}") + if case_id.startswith("vs_gk_"): + base = Path("queen_square_data") / case_id + return base / f"{case_id}_t1_refT1.nii.gz", base / f"{case_id}_seg_refT1.nii.gz" + base = Path("tilburg_data") / case_id + return base / f"{case_id}_ceT1.nii.gz", base / f"{case_id}_Label.nii.gz" + + +def load_index(path: Path) -> list[dict[str, str]]: + with path.open(newline="") as handle: + reader = csv.DictReader(handle) + required = { + "case_id", + "t1_img_path", + "t1_seg_path", + "fold", + "volume_mm3", + "quartile_label", + } + if not required.issubset(reader.fieldnames or []): + raise ValueError(f"{path}: missing study CSV columns") + rows = list(reader) + if not rows or len({r["case_id"] for r in rows}) != len(rows): + raise ValueError("Study CSV is empty or has duplicate case IDs") + for row in rows: + paths = case_paths(row["case_id"]) + if row["case_id"] in EXCLUSIONS: + raise ValueError(f"Excluded case in study CSV: {row['case_id']}") + for field, relative in zip(("t1_img_path", "t1_seg_path"), paths): + if row[field] != str(PurePosixPath("../nii_data") / relative.as_posix()): + raise ValueError(f"{row['case_id']}: unexpected canonical CSV path") + if row["fold"] not in {"1", "2", "3", "4", "5"}: + raise ValueError(f"{row['case_id']}: invalid fold") + if row["quartile_label"] not in {"0", "1", "2", "3"}: + raise ValueError(f"{row['case_id']}: invalid quartile label") + if not np.isfinite(float(row["volume_mm3"])) or float(row["volume_mm3"]) <= 0: + raise ValueError(f"{row['case_id']}: invalid tumour volume") + return rows + + +def _values(image: nib.Nifti1Image, role: str) -> np.ndarray: + array = np.asanyarray(image.dataobj) + if array.ndim != 3: + raise ValueError(f"Expected 3D {role}, got {array.shape}") + if role == "mask": + array = integer_labels(array) + if not np.all((array == 0) | (array == 1)) or not np.any(array == 1): + raise ValueError("Tumour mask must contain non-empty binary labels {0, 1}") + dtype = np.dtype("u1") + else: + if not np.isfinite(array).all() or array.min() < 0 or array.max() > 65535: + raise ValueError("Study image values must be finite and fit uint16") + if array.dtype.kind == "f" and not np.equal(array, np.floor(array)).all(): + raise ValueError("Study images must have integral intensity values") + dtype = np.dtype(" np.ndarray: + """Recover integer labels from harmless NIfTI scaling quantization. + + 103 reference Tilburg masks decode label 1 as 0.9999999997671694. + Float32 label loading recovers 1. Reject actual fractional annotations. + """ + + if values.dtype.kind == "f": + rounded = np.rint(values) + if not np.isfinite(values).all() or not np.allclose( + values, rounded, rtol=0, atol=1e-6 + ): + raise ValueError("Mask contains fractional or non-finite labels") + return rounded + return values + + +def describe_nifti(path: Path, role: str) -> dict: + image = nib.load(path) + values = _values(image, role) + result = { + "shape": list(values.shape), + "affine": image.affine.tolist(), + "spacing": [float(v) for v in image.header.get_zooms()[:3]], + "voxel_sha256": hashlib.sha256(memoryview(values).cast("B")).hexdigest(), + } + if role == "mask": + result["tumour_voxels"] = int(np.count_nonzero(values)) + result["volume_mm3"] = result["tumour_voxels"] * float( + np.prod(np.asarray(result["spacing"], dtype=np.float64)) + ) + return result + + +def check_fingerprint(actual: dict, expected: dict, *, case_id: str, role: str) -> None: + if ( + actual["shape"] != expected["shape"] + or actual["voxel_sha256"] != expected["voxel_sha256"] + ): + raise ValueError(f"{case_id}: {role} voxels differ from the study reference") + if not np.allclose(actual["affine"], expected["affine"], rtol=0, atol=1e-5): + raise ValueError(f"{case_id}: {role} affine differs from the study reference") + if not np.allclose(actual["spacing"], expected["spacing"], rtol=0, atol=1e-6): + raise ValueError(f"{case_id}: {role} spacing differs from the study reference") + + +def _describe_pair(row: dict, root: Path) -> dict: + case_id = row["case_id"] + image_path, mask_path = case_paths(case_id) + try: + image = describe_nifti(root / image_path, "image") + mask = describe_nifti(root / mask_path, "mask") + except ValueError as error: + raise ValueError(f"{case_id}: {error}") from error + if image["shape"] != mask["shape"] or not np.allclose( + image["affine"], mask["affine"], rtol=0, atol=5e-4 + ): + raise ValueError(f"{case_id}: image/mask geometry mismatch") + if not np.isclose( + mask["volume_mm3"], float(row["volume_mm3"]), rtol=1e-6, atol=1e-5 + ): + raise ValueError(f"{case_id}: mask volume disagrees with the committed CSV") + return {"case_id": case_id, "image": image, "mask": mask} + + +def _parallel( + rows: list[dict], function: Callable, workers: int, progress: Callable | None +) -> list: + if workers < 1: + raise ValueError("workers must be positive") + results = {} + with ThreadPoolExecutor(max_workers=workers) as pool: + pending = {pool.submit(function, row): row["case_id"] for row in rows} + try: + for number, future in enumerate(as_completed(pending), 1): + results[pending[future]] = future.result() + if progress is not None: + progress(number, len(rows), pending[future]) + except BaseException: + for future in pending: + future.cancel() + raise + return [results[row["case_id"]] for row in rows] + + +def freeze_reference( + index: Path, root: Path, *, workers: int = 2, progress: Callable | None = None +) -> dict: + """Read only indexed files; no source pixels, headers, or local paths are published.""" + + rows = load_index(index) + cases = _parallel(rows, lambda row: _describe_pair(row, root), workers, progress) + ids = {row["case_id"] for row in rows} + return { + "schema": SCHEMA, + "index_sha256": file_checksum(index, "sha256"), + "canonical_voxels": { + "image": "little-endian uint16, C order", + "mask": "integer labels (quantization tolerance 1e-6), uint8, C order", + }, + "counts": { + "cases": len(rows), + "images": len(rows), + "masks": len(rows), + "queen_square": sum(c.startswith("vs_gk_") for c in ids), + "tilburg": sum(c.startswith("crossmoda2022_etz_") for c in ids), + }, + "fold_counts": dict(sorted(Counter(row["fold"] for row in rows).items())), + "human_corrected_cases": [ + case for case in HUMAN_CORRECTED_CASES if case in ids + ], + "component_cleanup": { + case: list(sizes) + for case, sizes in COMPONENT_SIGNATURES.items() + if case in ids + }, + "exclusions": EXCLUSIONS, + "sources": { + "queen_square": "10.7937/TCIA.9YTJ-5Q73 (version 2)", + "tilburg": "10.5281/zenodo.6504722 (training_source, center=Tilburg)", + }, + "conversion": "VS_Seg historical SimpleITK T1 / rounded JSON polygon rasterization v1", + "cases": cases, + } + + +def load_reference(path: Path, index: Path) -> tuple[list[dict], dict]: + rows = load_index(index) + reference = json.loads(path.read_text()) + if reference["schema"] != SCHEMA or reference["index_sha256"] != file_checksum( + index, "sha256" + ): + raise ValueError( + "Study CSV does not match the frozen reference; preserve the shared index" + ) + if [r["case_id"] for r in rows] != [c["case_id"] for c in reference["cases"]]: + raise ValueError("Reference case IDs/order do not match the shared CSV") + return rows, reference + + +def verify_dataset( + index: Path, + reference_path: Path, + root: Path, + *, + workers: int = 2, + progress: Callable | None = None, + allow_extra_files: bool = False, +) -> dict: + rows, reference = load_reference(reference_path, index) + expected = {case["case_id"]: case for case in reference["cases"]} + if not allow_extra_files: + wanted = {p.as_posix() for row in rows for p in case_paths(row["case_id"])} + present = { + p.relative_to(root).as_posix() for p in root.rglob("*.nii*") if p.is_file() + } + if present != wanted: + raise ValueError( + f"Prepared inventory mismatch: missing={sorted(wanted - present)}, extra={sorted(present - wanted)}" + ) + + def check(row: dict) -> dict: + actual = _describe_pair(row, root) + for role in ("image", "mask"): + check_fingerprint( + actual[role], + expected[row["case_id"]][role], + case_id=row["case_id"], + role=role, + ) + return actual + + _parallel(rows, check, workers, progress) + generated = root / "ml_dataset.csv" + if generated.exists(): + with generated.open(newline="") as handle: + prepared_rows = list(csv.DictReader(handle)) + if len(prepared_rows) != len(rows): + raise ValueError("Prepared CSV case count differs from the shared CSV") + for shared, prepared in zip(rows, prepared_rows): + for field in shared: + if field not in {"t1_img_path", "t1_seg_path"} and shared[ + field + ] != prepared.get(field): + raise ValueError( + f"Prepared CSV changed {shared['case_id']}:{field}" + ) + for field, relative in zip( + ("t1_img_path", "t1_seg_path"), case_paths(shared["case_id"]) + ): + target = Path(prepared[field]) + if not target.is_absolute(): + target = PROJECT_ROOT / target + if target.resolve() != (root / relative).resolve(): + raise ValueError( + f"Prepared CSV points outside the verified dataset: {shared['case_id']}" + ) + return { + "verified": True, + **reference["counts"], + "fold_counts": reference["fold_counts"], + } + + +def clean_components(case_id: str, values: np.ndarray) -> tuple[np.ndarray, int]: + """Remove only the eight verified blobs, accepting already-cleaned masks.""" + + if case_id not in COMPONENT_SIGNATURES: + return values, 0 + components, _ = ndimage.label( + values == 1, structure=ndimage.generate_binary_structure(3, 1) + ) + sizes = np.bincount(components.ravel())[1:] + actual = tuple(sorted(sizes.tolist(), reverse=True)) + expected = COMPONENT_SIGNATURES[case_id] + if actual == (expected[0],): + return values, 0 + if actual != expected: + raise ValueError( + f"{case_id}: unexpected component signature {actual}, expected {expected}" + ) + return (components == int(sizes.argmax() + 1)).astype(np.uint8), expected[1] + + +def save_binary( + source: Path, destination: Path, case_id: str, *, crossmoda: bool = False +) -> int: + image = nib.load(source) + values = integer_labels(np.asanyarray(image.dataobj)) + if not np.all(np.isin(values, [0, 1, 2] if crossmoda else [0, 1])): + raise ValueError(f"{case_id}: unsupported source mask labels") + values, removed = clean_components(case_id, (values == 1).astype(np.uint8)) + output = nib.Nifti1Image(values, image.affine, image.header.copy()) + output.set_data_dtype(np.uint8) + # Preserve source spatial metadata exactly; nibabel can otherwise recalculate qform. + for key in ( + "pixdim", + "qform_code", + "sform_code", + "quatern_b", + "quatern_c", + "quatern_d", + "qoffset_x", + "qoffset_y", + "qoffset_z", + "srow_x", + "srow_y", + "srow_z", + ): + output.header[key] = image.header[key] + nib.save(output, destination) + return removed + + +@contextmanager +def correction_directory(source: Path): + if source.is_file(): + with tempfile.TemporaryDirectory(prefix="vs-labels-") as temporary: + yield safe_extract_zip(source, Path(temporary) / "labels") + elif source.is_dir(): + yield source + else: + raise FileNotFoundError( + f"Corrected labels not found: {source}; see data/README.md" + ) + + +def corrected_paths(root: Path, reference: dict) -> dict[str, Path]: + found = {} + for case_id in reference["human_corrected_cases"]: + matches = [ + p + for p in root.rglob(f"{case_id}_*.nii*") + if p.is_file() and p.name.endswith((".nii", ".nii.gz")) + ] + if len(matches) != 1: + raise ValueError( + f"{case_id}: expected exactly one corrected mask under {root}, found {len(matches)}" + ) + expected = next( + c["mask"] for c in reference["cases"] if c["case_id"] == case_id + ) + check_fingerprint( + describe_nifti(matches[0], "mask"), + expected, + case_id=case_id, + role="corrected mask", + ) + found[case_id] = matches[0] + return found + + +def _tilburg_ids(root: Path) -> set[str]: + with (root / "infos_source_training.csv").open( + newline="", encoding="utf-8-sig" + ) as handle: + reader = csv.DictReader(handle) + if not {"crossmoda_name", "center", "group"}.issubset(reader.fieldnames or []): + raise ValueError("CrossMoDA metadata is missing name, center, or group") + selected = [ + r["crossmoda_name"] + for r in reader + if r["center"] == "Tilburg" and r["group"] == "training_source" + ] + if len(selected) != len(set(selected)): + raise ValueError("Duplicate Tilburg cases in metadata") + return set(selected) + + +def prepare_dataset( + index: Path, + reference_path: Path, + output: Path, + *, + queen_square: Path, + crossmoda: Path, + corrections: Path, + contours: Path | None = None, + queen_square_nifti: bool = False, + workers: int = 2, + progress: Callable | None = None, +) -> dict: + """Stage only indexed T1 pairs; publish after all 344 pairs match reference.""" + + rows, reference = load_reference(reference_path, index) + output = output.resolve() + if output.exists(): + raise FileExistsError( + f"Output already exists: {output}; choose a new directory" + ) + for source in (queen_square, crossmoda, corrections): + if output == source.resolve() or output.is_relative_to(source.resolve()): + raise ValueError("Prepared output must be outside source directories") + expected_tilburg = { + r["case_id"] for r in rows if r["case_id"].startswith("crossmoda") + } + if _tilburg_ids(crossmoda) != expected_tilburg: + raise ValueError("Tilburg metadata does not match the shared study case list") + if not queen_square_nifti and (contours is None or not contours.exists()): + raise FileNotFoundError( + "TCIA JSON contours are required for native DICOM preparation" + ) + output.parent.mkdir(parents=True, exist_ok=True) + expected = {c["case_id"]: c for c in reference["cases"]} + with correction_directory(corrections) as label_root: + overlays = corrected_paths(label_root, reference) + staging = Path( + tempfile.mkdtemp(prefix=f".{output.name}.staging-", dir=output.parent) + ) + try: + + def prepare(row: dict) -> dict: + case_id = row["case_id"] + image_relative, mask_relative = case_paths(case_id) + image_out, mask_out = staging / image_relative, staging / mask_relative + image_out.parent.mkdir(parents=True) + human = case_id in overlays + if case_id.startswith("crossmoda"): + source = crossmoda / "training_source" + shutil.copy2(source / image_out.name, image_out) + mask_source = source / mask_out.name + elif queen_square_nifti: + source = queen_square / case_id + shutil.copy2(source / image_out.name, image_out) + mask_source = overlays[case_id] if human else source / mask_out.name + else: + from .data_conversion import convert_t1, rasterize_t1, read_contours + + convert_t1(queen_square, case_id, image_out) + if human: + mask_source = overlays[case_id] + else: + rasterize_t1( + read_contours(contours, case_id), image_out, mask_out + ) + mask_source = mask_out + removed = save_binary( + mask_source, + mask_out, + case_id, + crossmoda=case_id.startswith("crossmoda"), + ) + actual = _describe_pair(row, staging) + for role in ("image", "mask"): + check_fingerprint( + actual[role], + expected[case_id][role], + case_id=case_id, + role=role, + ) + return { + "case_id": case_id, + "human_corrected": human, + "removed_voxels": removed, + } + + records = _parallel(rows, prepare, workers, progress) + manifest = { + "schema": "vs-prepared-dataset-v1", + "reference_sha256": file_checksum(reference_path, "sha256"), + "index_sha256": reference["index_sha256"], + "counts": reference["counts"], + "sources": reference["sources"], + "exclusions": reference["exclusions"], + "verified_against_reference": True, + "cases": records, + } + write_json(staging / "manifest.json", manifest) + with (staging / "ml_dataset.csv").open("w", newline="") as handle: + writer = csv.DictWriter(handle, fieldnames=list(rows[0])) + writer.writeheader() + for row in rows: + prepared_row = row.copy() + for key, relative in zip( + ("t1_img_path", "t1_seg_path"), case_paths(row["case_id"]) + ): + prepared_row[key] = os.path.relpath( + output / relative, PROJECT_ROOT + ) + writer.writerow(prepared_row) + if output.exists(): + raise FileExistsError(f"Output appeared during preparation: {output}") + staging.rename(output) + except BaseException: + shutil.rmtree(staging, ignore_errors=True) + raise + return manifest + + +def build_label_bundle( + index: Path, reference_path: Path, root: Path, destination: Path +) -> dict: + """Package the 17 used labels with fresh headers, deterministic bytes, and provenance.""" + + _, reference = load_reference(reference_path, index) + if destination.exists(): + raise FileExistsError(f"Label bundle already exists: {destination}") + destination.parent.mkdir(parents=True, exist_ok=True) + entries = {} + records = [] + for case_id in reference["human_corrected_cases"]: + _, relative = case_paths(case_id) + source = root / relative + actual = describe_nifti(source, "mask") + expected = next( + c["mask"] for c in reference["cases"] if c["case_id"] == case_id + ) + check_fingerprint(actual, expected, case_id=case_id, role="corrected mask") + loaded = nib.load(source) + # Fresh headers exclude free-text fields and extensions from local annotations. + image = nib.Nifti1Image(_values(loaded, "mask"), loaded.affine) + image.header.set_xyzt_units("mm") + image.set_qform(loaded.affine, code=1) + image.set_sform(loaded.affine, code=1) + payload = gzip.compress(image.to_bytes(), compresslevel=6, mtime=0) + name = f"labels/{case_id}_seg_refT1.nii.gz" + entries[name] = payload + records.append( + { + "case_id": case_id, + "tcia_patient_id": f"VS-SEG-{int(case_id.removeprefix('vs_gk_')):03d}", + "file": name, + "sha256": hashlib.sha256(payload).hexdigest(), + "reference_mask": actual, + } + ) + title = ( + f"Replacement tumour segmentation masks for {len(records)} cases " + "from Vestibular-Schwannoma-SEG" + ) + provenance = { + "schema": "vs-corrected-labels-v1", + "version": 1, + "title": title, + "source_doi": "10.7937/TCIA.9YTJ-5Q73", + "source_license": "CC BY 4.0", + "release_license": LABEL_RELEASE_LICENSE, + "release_license_url": LABEL_RELEASE_LICENSE_URL, + "annotation_set": "NL masks newly drawn for scans with errors in the original annotations", + "source_role": "MRI images", + "contributors": list(LABEL_CONTRIBUTORS), + "associated_paper": { + "status": "in preparation", + "doi": None, + "citation_location": "Dataset record and reproduction repository", + }, + "index_sha256": reference["index_sha256"], + "cases": records, + } + entries["manifest.json"] = ( + json.dumps(provenance, indent=2, sort_keys=True, ensure_ascii=False) + "\n" + ).encode() + entries["case_mapping.csv"] = ( + "case_id,tcia_patient_id,mask_file\n" + + "".join( + f"{r['case_id']},{r['tcia_patient_id']},{r['file']}\n" for r in records + ) + ).encode() + entries["LICENSE.txt"] = ( + "Replacement masks and release documentation: CC BY 4.0.\n" + f"License: {LABEL_RELEASE_LICENSE_URL}\n\n" + "This license permits redistribution and adaptation, including commercial use,\n" + "with appropriate credit, a license link, and identification of changes.\n" + "Consult the linked license for its full terms.\n\n" + f"Contributors: {', '.join(LABEL_CONTRIBUTORS)}.\n" + "Image source: Shapey et al. (2021), Vestibular-Schwannoma-SEG version 2,\n" + "The Cancer Imaging Archive, DOI: 10.7937/TCIA.9YTJ-5Q73 (CC BY 4.0).\n" + "These masks are new annotations for scans with errors in the original annotations.\n" + ).encode() + entries["README.txt"] = ( + f"{title}\nVersion 1.0\n\n" + f"{len(records)} newly drawn tumour masks for scans whose original annotations contained errors.\n" + "These are the NL masks used in our study.\n" + f"Contributors: {', '.join(LABEL_CONTRIBUTORS)}.\n" + "Labels: 0 background, 1 tumour. Native T1 geometry; no MRI images.\n" + "case_mapping.csv maps each mask to its original TCIA PatientID.\n" + "manifest.json records provenance and reference fingerprints.\n\n" + "Image source: TCIA Vestibular-Schwannoma-SEG version 2, DOI 10.7937/TCIA.9YTJ-5Q73.\n" + "Shapey et al. (2021), Scientific Data 8, 286.\n" + "Source paper DOI: 10.1038/s41597-021-01064-w.\n" + f"License: {LABEL_RELEASE_LICENSE}. See LICENSE.txt.\n\n" + "Preparation: research/vestibular_schwannoma/data/README.md in\n" + "https://github.com/MMIV-ML/fastMONAI\n\n" + "Please cite our accompanying study paper when available (in preparation).\n" + ).encode() + descriptor, temporary = tempfile.mkstemp(suffix=".zip", dir=destination.parent) + os.close(descriptor) + try: + with zipfile.ZipFile(temporary, "w", compression=zipfile.ZIP_STORED) as archive: + for name, payload in sorted(entries.items()): + info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0)) + info.external_attr = 0o100644 << 16 + archive.writestr(info, payload) + os.rename(temporary, destination) + except BaseException: + Path(temporary).unlink(missing_ok=True) + raise + return { + "schema": "vs-corrected-label-asset-v1", + "version": 1, + "filename": destination.name, + "sha256": file_checksum(destination, "sha256"), + "size_bytes": destination.stat().st_size, + "url": None, + "doi": None, + "cases": reference["human_corrected_cases"], + "contributors": list(LABEL_CONTRIBUTORS), + "release_license": LABEL_RELEASE_LICENSE, + "release_license_url": LABEL_RELEASE_LICENSE_URL, + } diff --git a/research/vestibular_schwannoma/workflow/data_sources.py b/research/vestibular_schwannoma/workflow/data_sources.py new file mode 100644 index 0000000..beae40c --- /dev/null +++ b/research/vestibular_schwannoma/workflow/data_sources.py @@ -0,0 +1,134 @@ +"""Authoritative metadata for the upstream vestibular schwannoma datasets. + +The data licenses below apply to the downloaded datasets, independently of the +license used for this software repository. +""" + +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass(frozen=True, slots=True) +class SourceAsset: + """A file published as part of an upstream dataset.""" + + name: str + filename: str + url: str + checksum: str | None = None + + +@dataclass(frozen=True, slots=True) +class DatasetSource: + """Citation, license, and download metadata for an upstream dataset.""" + + key: str + title: str + doi: str + license_name: str + license_url: str + page_url: str + assets: tuple[SourceAsset, ...] + + def asset(self, name: str) -> SourceAsset: + """Return a named asset, raising a useful error for unknown names.""" + + for asset in self.assets: + if asset.name == name: + return asset + available = ", ".join(asset.name for asset in self.assets) + raise KeyError( + f"Unknown asset {name!r} for {self.key}; choose from: {available}" + ) + + +CROSSMODA_2022 = DatasetSource( + key="crossmoda2022", + title="Cross-Modality Domain Adaptation Challenge 2022 (crossMoDA)", + doi="10.5281/zenodo.6504722", + license_name="CC BY-NC-SA 4.0", + license_url="https://creativecommons.org/licenses/by-nc-sa/4.0/", + page_url="https://zenodo.org/records/6504722", + assets=( + SourceAsset( + name="training", + filename="crossmoda2022_training.zip", + url=( + "https://zenodo.org/records/6504722/files/crossmoda2022_training.zip?download=1" + ), + checksum="md5:8d68fcd44eaee6b0a4371ce344e775cd", + ), + ), +) + + +TCIA_VESTIBULAR_SCHWANNOMA_SEG = DatasetSource( + key="vestibular_schwannoma_seg", + title=( + "Segmentation of Vestibular Schwannoma from Magnetic Resonance Imaging: " + "An Open Annotated Dataset and Baseline Algorithm" + ), + doi="10.7937/TCIA.9YTJ-5Q73", + license_name="CC BY 4.0", + license_url="https://creativecommons.org/licenses/by/4.0/", + page_url=( + "https://www.cancerimagingarchive.net/collection/vestibular-schwannoma-seg/" + ), + assets=( + SourceAsset( + name="manifest", + filename="Vestibular-Schwannoma-SEG-Feb-2021-manifest.tcia", + url=( + "https://www.cancerimagingarchive.net/wp-content/uploads/" + "Vestibular-Schwannoma-SEG-Feb-2021-manifest.tcia" + ), + ), + SourceAsset( + name="contours", + filename="Vestibular-Schwannoma-SEG-contours-Mar-2021.zip", + url=( + "https://www.cancerimagingarchive.net/wp-content/uploads/" + "Vestibular-Schwannoma-SEG-contours-Mar-2021.zip" + ), + ), + SourceAsset( + name="matrices", + filename="Vestibular-Schwannoma-SEG_matrices-Mar-2021.zip", + url=( + "https://www.cancerimagingarchive.net/wp-content/uploads/" + "Vestibular-Schwannoma-SEG_matrices-Mar-2021.zip" + ), + ), + SourceAsset( + name="modality_mapping", + filename="DirectoryNamesMappingModality.csv", + url=( + "https://www.cancerimagingarchive.net/wp-content/uploads/" + "DirectoryNamesMappingModality.csv" + ), + ), + ), +) + +# Short aliases for callers that use cohort names rather than repository names. +TCIA_VS_SEG = TCIA_VESTIBULAR_SCHWANNOMA_SEG +QUEEN_SQUARE = TCIA_VESTIBULAR_SCHWANNOMA_SEG +TILBURG = CROSSMODA_2022 + +DATASET_SOURCES = { + TCIA_VESTIBULAR_SCHWANNOMA_SEG.key: TCIA_VESTIBULAR_SCHWANNOMA_SEG, + CROSSMODA_2022.key: CROSSMODA_2022, +} + + +__all__ = [ + "CROSSMODA_2022", + "DATASET_SOURCES", + "DatasetSource", + "QUEEN_SQUARE", + "SourceAsset", + "TCIA_VESTIBULAR_SCHWANNOMA_SEG", + "TCIA_VS_SEG", + "TILBURG", +] From 4c9df0f1a60630fb0f653a8daf7bc79c47d58c0d Mon Sep 17 00:00:00 2001 From: Sathiesh Kaliyugarasan Date: Fri, 2 Oct 2026 14:19:34 +0200 Subject: [PATCH 3/5] Add parallel PACS inference and REDCap mask exports --- .../deployment/pacs/.ror/virt/Dockerfile | 3 +- .../deployment/pacs/README.md | 88 ++++- .../deployment/pacs/deployment_bundle.py | 38 +- .../deployment/pacs/entrypoint.sh | 2 +- .../deployment/pacs/pacs_inference.py | 46 ++- .../deployment/pacs/parallel_inference.py | 362 ++++++++++++++++++ .../pacs/redcap_model_instances.json | 4 + .../deployment/pacs/redcap_output.py | 310 +++++++++++++++ .../deployment/pacs/requirements.yml | 5 + .../deployment/test_deployment_bundle.py | 2 +- .../tests/deployment/test_entrypoint.py | 4 +- .../tests/deployment/test_pacs_inference.py | 16 +- .../deployment/test_parallel_inference.py | 102 +++++ .../tests/deployment/test_redcap_output.py | 270 +++++++++++++ 14 files changed, 1223 insertions(+), 29 deletions(-) create mode 100644 research/vestibular_schwannoma/deployment/pacs/parallel_inference.py create mode 100644 research/vestibular_schwannoma/deployment/pacs/redcap_model_instances.json create mode 100644 research/vestibular_schwannoma/deployment/pacs/redcap_output.py create mode 100644 research/vestibular_schwannoma/tests/deployment/test_parallel_inference.py create mode 100644 research/vestibular_schwannoma/tests/deployment/test_redcap_output.py diff --git a/research/vestibular_schwannoma/deployment/pacs/.ror/virt/Dockerfile b/research/vestibular_schwannoma/deployment/pacs/.ror/virt/Dockerfile index b7147cd..d4871a6 100644 --- a/research/vestibular_schwannoma/deployment/pacs/.ror/virt/Dockerfile +++ b/research/vestibular_schwannoma/deployment/pacs/.ror/virt/Dockerfile @@ -27,7 +27,8 @@ LABEL org.opencontainers.image.version="${VERSION}" ENV VERSION="${VERSION}" COPY deployment_hashing.py deployment_models.py deployment_bundle.py \ - dicom_output.py pacs_inference.py entrypoint.sh ./ + parallel_inference.py dicom_output.py redcap_output.py pacs_inference.py entrypoint.sh ./ +COPY redcap_model_instances.json ./ COPY model_bundles/ ./model_bundles/ # The code to run when container is started: diff --git a/research/vestibular_schwannoma/deployment/pacs/README.md b/research/vestibular_schwannoma/deployment/pacs/README.md index bb859b5..813f911 100644 --- a/research/vestibular_schwannoma/deployment/pacs/README.md +++ b/research/vestibular_schwannoma/deployment/pacs/README.md @@ -1,15 +1,19 @@ # Vestibular Schwannoma ROR/PACS Container -Build the CE-T1w vestibular schwannoma container with `unet`, `dynunet`, or both. -Multiple Safetensors members form a sequential ensemble. Eight-flip TTA is enabled by default. +Build the CE-T1w vestibular schwannoma container with `dynunet`, `unet`, or both. +DynUNet is the default model. +Multiple Safetensors members run concurrently in separate CPU workers with three +threads per model. Five-fold deployment therefore requires at least 15 available CPU +threads; the qualified target has 16. Eight-flip TTA is enabled by default. See [Research PACS deployment with ROR](../../../RESEARCH_PACS_DEPLOYMENT.md) for the general build, qualification, export, and handoff workflow. ## Prepare model bundles -Use fastMONAI 0.10.1 or the environment pinned in `requirements.yml`. Provide complete -MLflow run IDs. +Use the environment pinned in `requirements.yml` to prepare bundles. Strict model +metadata checks also depend on the PyTorch version; matching fastMONAI alone is +insufficient. Provide complete MLflow run IDs. One model trained on all data: @@ -81,7 +85,7 @@ ror trigger -cont "vs-seg:$BUILD_VERSION" -each -keep \ `ROR_CONT_OPTIONS` accepts: -- `model-type`: `unet` (default) or `dynunet`. +- `model-type`: `dynunet` (default) or `unet`. - `tta`: JSON Boolean, default `true`. Unknown keys and invalid values fail before inference. Header preflight rejects @@ -90,5 +94,77 @@ missing optional Frame of Reference metadata produce aggregated warnings. The runtime writes `mask` and intermediate `vote_map` DICOM series. Fiona's `pr2mask` creates `fused`, `fused_vote_map`, and `reports`; `vote_map` is not published. Vote-map probabilities -are `round(probability x 65535)`. Existing `mask`, `fused`, `fused_vote_map`, and `reports` +are `round(probability x 65535)`. Existing `mask`, `fused`, `fused_vote_map`, `reports`, and `redcap` directories are rejected to prevent overwrites. + +### Recoverable masks in REDCap JSON + +The final output now includes `redcap//output.json` and +`output_data_dictionary.zip`, using pr2mask's EAV row format and instrument. +`redcap` is an owned output directory and must not already exist. +The exporter reads the **written DICOM mask**, matches it to the source images by +geometry, and stores the full binary mask once in the model's permanent `pr2mask` repeat instance. +It also handles an empty measurement list using the source PatientID and +ReferringPhysicianName (`EventName:` prefix removed), matching pr2mask routing. + +Added fields: `vs_mask_json` and `vs_measurements_json` (Notes), `vs_model_type`, `vs_bundle_sha256`, +`vs_prediction_id`, `vs_deployment_version`, `vs_tta`, and `vs_created_at`. +The existing data dictionary is extended; measurement definitions and OriginID +are retained. Compression happens in memory, so the mask is JSON text rather +than a separate binary attachment. There is no direct REDCap uploader. + +`vs_mask_json` is a JSON string with schema version 1: + +- Binary voxels: C-order `(slice, row, column)`, foreground 1, little-order bits, + gzip-compressed and Base64-encoded (`packbits-gzip-base64-v1`). +- Shape, foreground count, and SHA-256 of the decoded C-order uint8 voxel bytes. +- DICOM LPS geometry: `pixel_spacing` in row/column order, + `image_orientation_patient`, and `image_positions_patient` in encoded slice + order. **All lengths are millimetres by schema convention; no units field.** + Slice order follows increasing position along the DICOM orientation normal. + Per-slice positions preserve irregular spacing; SliceThickness is not used + as a substitute for the distance between slice centres. +- Source Study/Series/Frame of Reference and ordered SOP UIDs, mask series UID, + model type, member IDs, bundle hash, deployment version and TTA. +- Stable prediction identity (hash of mask, geometry and provenance) and UTC + export timestamp. The timestamp is excluded from the prediction identity. + +Recover voxels with `redcap_output.decode_mask(json.loads(row['value']))` for the +`vs_mask_json` row. For voxel `[s, r, c]`, its LPS position is +`position[s] + r * pixel_spacing[0] * orientation[3:6] + +c * pixel_spacing[1] * orientation[0:3]`. +Use the same spatial grid as the reference before computing Dice. Distance +metrics must respect geometry; irregular grids require physical-coordinate +surface distances or a documented resampling step. + +### Stable model destinations and overwrites + +`redcap_model_instances.json` assigns each full bundle SHA-256 a permanent positive +integer. The current UNet bundle uses instance 1 and DynUNet uses instance 2. +Never renumber or reuse entries. Register each new bundle with an unused integer +before building a release; unknown bundles and duplicate allocations fail closed. +The registry is copied into the container and validated before inference. + +All fields for a model share its repeat instance. Within the same record/event: + +- The same bundle overwrites its previous result, including when TTA, deployment + version, timestamp or the predicted mask changes. +- A different bundle uses a different instance, including different weights of + the same architecture. `vs_prediction_id` remains provenance, not a routing key. + +pr2mask's per-region measurements are preserved in `vs_measurements_json`, a JSON +list of `{redcap_repeat_instance, field_name, value}` entries. The instance inside +this list is the original **region** number, not the model destination. Measurements +are no longer emitted as separate top-level region instances or scalar measurement +fields: consumers of those fields must read this list instead. This avoids collisions +and ensures that reruns with fewer regions (or an empty mask) replace the entire old +measurement list without leaving stale regions. Existing dictionary definitions +remain alongside the new fields. + +Install the extended dictionary in the receiving REDCap project and qualify a +PACS-to-REDCap round trip before use. The importer must honor the supplied numeric +repeat instance and update existing values. Live importer behavior and field limits +have not been verified. The record/event must already identify the intended case/scan; +multiple scans routed to the same record/event and model intentionally share a slot. +If earlier prototype exports were imported, reconcile those old instances before +using the permanent mapping. diff --git a/research/vestibular_schwannoma/deployment/pacs/deployment_bundle.py b/research/vestibular_schwannoma/deployment/pacs/deployment_bundle.py index f060000..a1a806a 100644 --- a/research/vestibular_schwannoma/deployment/pacs/deployment_bundle.py +++ b/research/vestibular_schwannoma/deployment/pacs/deployment_bundle.py @@ -57,7 +57,26 @@ def _read_packaged_deployment(path: Path, model_type: str) -> tuple[dict, list[s return deployment, filenames -def load_deployment(model_type: str, *, script_dir: Path = SCRIPT_DIR) -> dict: +def _metadata_output_channels(metadata: dict) -> int: + """Read and validate the declared model output-channel count.""" + arch_kwargs = metadata.get("arch_kwargs", {}) + output_channels = arch_kwargs.get("out_channels") + if output_channels is None: + output_config = metadata.get("inference_config", {}).get("output", {}) + classes = output_config.get("classes") + if isinstance(classes, list): + output_channels = len(classes) + if ( + not isinstance(output_channels, int) + or isinstance(output_channels, bool) + or output_channels < 1 + ): + raise RuntimeError("model metadata must declare positive out_channels") + return output_channels + + +def describe_deployment(model_type: str, *, script_dir: Path = SCRIPT_DIR) -> dict: + """Validate a packaged deployment without loading model weights.""" if model_type not in MODEL_CONFIGS: raise ValueError(f"unknown model type: {model_type!r}") models_dir = script_dir / "model_bundles" / model_type @@ -73,12 +92,21 @@ def load_deployment(model_type: str, *, script_dir: Path = SCRIPT_DIR) -> dict: raise FileNotFoundError(f"packaged model files not found: {missing}") first_metadata = read_safetensors_metadata(paths[0]) + deployment["model_paths"] = paths + deployment["output_channels"] = _metadata_output_channels(first_metadata) + deployment["patch_config"] = PatchConfig( + **dict(first_metadata["inference_config"]["patch_config"]) + ) + return deployment + + +def load_deployment(model_type: str, *, script_dir: Path = SCRIPT_DIR) -> dict: + """Validate a packaged deployment and load all model weights.""" + deployment = describe_deployment(model_type, script_dir=script_dir) + paths = deployment["model_paths"] models = [load_safetensors_model(path, device="cpu") for path in paths] for member, path in zip(deployment["members"], paths): - print(f" Loaded {member['member_id']}: {path.name}") + print(" Loaded {}: {}".format(member["member_id"], path.name)) deployment["predictor"] = models[0] if len(models) == 1 else models - deployment["patch_config"] = PatchConfig( - **dict(first_metadata["inference_config"]["patch_config"]) - ) return deployment diff --git a/research/vestibular_schwannoma/deployment/pacs/entrypoint.sh b/research/vestibular_schwannoma/deployment/pacs/entrypoint.sh index 378094f..0aaa435 100755 --- a/research/vestibular_schwannoma/deployment/pacs/entrypoint.sh +++ b/research/vestibular_schwannoma/deployment/pacs/entrypoint.sh @@ -28,7 +28,7 @@ if ! jq -e ' exit 2 fi -model_type=$(jq -r '.["model-type"] // "unet"' <<< "$ror_options") +model_type=$(jq -r '.["model-type"] // "dynunet"' <<< "$ror_options") if [[ ! "$model_type" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then echo "Error: invalid model-type syntax: ${model_type}" >&2 exit 2 diff --git a/research/vestibular_schwannoma/deployment/pacs/pacs_inference.py b/research/vestibular_schwannoma/deployment/pacs/pacs_inference.py index 6b02566..83b7ed1 100755 --- a/research/vestibular_schwannoma/deployment/pacs/pacs_inference.py +++ b/research/vestibular_schwannoma/deployment/pacs/pacs_inference.py @@ -12,17 +12,21 @@ from pathlib import Path import torch +from fastMONAI.vision_all import load_safetensors_model from fastMONAI.vision_patch import PatchInferenceEngine -from deployment_bundle import load_deployment +from deployment_bundle import describe_deployment as load_deployment from deployment_models import MODEL_CONFIGS from dicom_output import validate_dicom_input, write_prediction_outputs +from parallel_inference import ParallelEnsemblePatchInferenceEngine +from redcap_output import write_redcap_mask, model_repeat_instance SW_BATCH_SIZE = 1 +THREADS_PER_ENSEMBLE_MODEL = 3 DEFAULT_WORK_DIR = Path("/output_tmp") DEFAULT_PR2MASK_DIR = Path("/pr2mask") -FINAL_OUTPUT_DIRS = ("fused", "fused_vote_map", "reports", "mask") +FINAL_OUTPUT_DIRS = ("fused", "fused_vote_map", "reports", "mask", "redcap") LOG_NAME = "pacs_command.log" PR2MASK_BUNDLE_HASH_LENGTH = 32 @@ -70,6 +74,34 @@ def _deployment_label(deployment: dict) -> str: return "single model" if count == 1 else f"{count}-model ensemble" +def _create_inference_engine(deployment: dict): + model_paths = deployment.get("model_paths") + if model_paths and len(model_paths) > 1: + return ParallelEnsemblePatchInferenceEngine( + model_paths, + [member["member_id"] for member in deployment["members"]], + deployment["patch_config"], + output_channels=deployment["output_channels"], + threads_per_model=THREADS_PER_ENSEMBLE_MODEL, + sw_batch_size=SW_BATCH_SIZE, + ) + if model_paths: + predictor = load_safetensors_model(model_paths[0], device="cpu") + print( + " Loaded {}: {}".format( + deployment["members"][0]["member_id"], model_paths[0].name + ) + ) + else: + # Compatibility path for injected/test deployments using an in-memory model. + predictor = deployment["predictor"] + return PatchInferenceEngine( + predictor, + deployment["patch_config"], + sw_batch_size=SW_BATCH_SIZE, + ) + + def _required_pr2mask_tools(pr2mask_dir: Path) -> dict[str, Path]: tools = { name: pr2mask_dir / name @@ -234,6 +266,7 @@ def _run_postprocessing( stderr=subprocess.STDOUT, check=True, ) + write_redcap_mask(work_dir, input_dir, deployment, version=version, use_tta=use_tta) _copy_final_outputs(work_dir, output_dir) _publish_log(log_path, output_dir) @@ -263,16 +296,13 @@ def run_inference( print("=" * 60) deployment = load_deployment(model_type) + model_repeat_instance(deployment["bundle_sha256"]) patch_config = deployment["patch_config"] label = _deployment_label(deployment) print(f"Deployment: {label}") print(f"Patch size: {patch_config.patch_size}") - engine = PatchInferenceEngine( - deployment["predictor"], - patch_config, - sw_batch_size=SW_BATCH_SIZE, - ) + engine = _create_inference_engine(deployment) print(f"Running patch inference (TTA={'on' if use_tta else 'off'})...") segmentation, probabilities = engine.predict_mask_and_probabilities( str(input_dir), tta=use_tta @@ -316,7 +346,7 @@ def main(argv=None): ) parser.add_argument("input_dir", help="Directory containing the input DICOM series") parser.add_argument("output_dir", help="Directory for final PACS output") - parser.add_argument("--model-type", choices=tuple(MODEL_CONFIGS), default="unet") + parser.add_argument("--model-type", choices=tuple(MODEL_CONFIGS), default="dynunet") parser.add_argument( "--tta", action=argparse.BooleanOptionalAction, diff --git a/research/vestibular_schwannoma/deployment/pacs/parallel_inference.py b/research/vestibular_schwannoma/deployment/pacs/parallel_inference.py new file mode 100644 index 0000000..04a34ed --- /dev/null +++ b/research/vestibular_schwannoma/deployment/pacs/parallel_inference.py @@ -0,0 +1,362 @@ +"""CPU-parallel model-ensemble inference for the PACS deployment.""" + +from __future__ import annotations + +import itertools +import multiprocessing as mp +import os +import queue +import tempfile +import time +import traceback +from pathlib import Path +from threading import BrokenBarrierError + +import numpy as np +import torch +import torchio as tio +from fastMONAI.vision_all import load_safetensors_model +from fastMONAI.vision_patch import PatchInferenceEngine + + +THREADS_PER_MODEL = 3 +WORKER_STARTUP_TIMEOUT_SECONDS = 600 +WORKER_FORWARD_TIMEOUT_SECONDS = 900 +WORKER_SHUTDOWN_TIMEOUT_SECONDS = 30 +NO_FLIP_COMMAND = -1 +STOP_COMMAND = -2 + +# Batch tensors have shape [B, C, D, H, W], so spatial axes are 2, 3, and 4. +TTA_FLIP_AXES = ( + (), + (4,), + (3,), + (2,), + (3, 4), + (2, 4), + (2, 3), + (2, 3, 4), +) + + +def assign_worker_cpus( + available_cpus, worker_count: int, threads_per_model: int = THREADS_PER_MODEL +) -> list[tuple[int, ...]]: + """Return disjoint fixed-size CPU sets for ensemble workers.""" + if worker_count < 1: + raise ValueError("worker_count must be positive") + if threads_per_model < 1: + raise ValueError("threads_per_model must be positive") + available = tuple(sorted(set(available_cpus))) + required = worker_count * threads_per_model + if len(available) < required: + raise RuntimeError( + f"parallel {worker_count}-model ensemble requires at least {required} " + f"available CPU threads ({threads_per_model} per model), found " + f"{len(available)}" + ) + return [ + available[index : index + threads_per_model] + for index in range(0, required, threads_per_model) + ] + + +def _logits_to_probabilities(logits: torch.Tensor) -> torch.Tensor: + logits = logits.float() + if logits.shape[1] == 1: + return torch.sigmoid(logits) + return torch.softmax(logits, dim=1) + + +def _ensemble_worker( + member_id, + model_path, + cpu_set, + input_path, + input_shape, + output_path, + output_shape, + output_offset, + command, + barrier, + ready_queue, + error_queue, +): + input_array = None + output_array = None + try: + os.sched_setaffinity(0, set(cpu_set)) + thread_count = len(cpu_set) + os.environ["OMP_NUM_THREADS"] = str(thread_count) + os.environ["MKL_NUM_THREADS"] = str(thread_count) + torch.set_num_threads(thread_count) + torch.set_num_interop_threads(1) + + input_array = np.memmap( + input_path, dtype=np.float32, mode="r+", shape=tuple(input_shape) + ) + output_array = np.memmap( + output_path, + dtype=np.float32, + mode="r+", + offset=output_offset, + shape=tuple(output_shape), + ) + input_tensor = torch.from_numpy(input_array) + output_tensor = torch.from_numpy(output_array) + model = load_safetensors_model(model_path, device="cpu") + model.eval() + ready_queue.put( + { + "member_id": member_id, + "cpu_set": list(cpu_set), + "threads": torch.get_num_threads(), + } + ) + + while True: + barrier.wait(timeout=WORKER_FORWARD_TIMEOUT_SECONDS) + current_command = command.value + if current_command == STOP_COMMAND: + break + axes = ( + () + if current_command == NO_FLIP_COMMAND + else TTA_FLIP_AXES[current_command] + ) + with torch.inference_mode(): + model_input = torch.flip(input_tensor, axes) if axes else input_tensor + probabilities = _logits_to_probabilities(model(model_input)) + if axes: + probabilities = torch.flip(probabilities, axes) + if tuple(probabilities.shape) != tuple(output_tensor.shape): + raise RuntimeError( + f"{member_id} produced shape {tuple(probabilities.shape)}, " + f"expected {tuple(output_tensor.shape)}" + ) + output_tensor.copy_(probabilities) + barrier.wait(timeout=WORKER_FORWARD_TIMEOUT_SECONDS) + except BaseException as exc: + error_queue.put( + { + "member_id": member_id, + "error": f"{type(exc).__name__}: {exc}", + "traceback": traceback.format_exc(), + } + ) + try: + barrier.abort() + except BaseException: + pass + finally: + del input_array + del output_array + + +def _next_worker_error(error_queue, timeout=0): + try: + return error_queue.get(timeout=timeout) + except queue.Empty: + return None + + +def _format_worker_error(error) -> str: + if error is None: + return "parallel ensemble worker failed without an error report" + return f"parallel ensemble worker {error['member_id']} failed: {error['error']}" + + +class ParallelEnsemblePatchInferenceEngine(PatchInferenceEngine): + """Run one CPU process per model while retaining PatchInferenceEngine I/O.""" + + def __init__( + self, + model_paths, + member_ids, + config, + *, + output_channels: int, + threads_per_model: int = THREADS_PER_MODEL, + sw_batch_size: int = 1, + ): + paths = tuple(Path(path) for path in model_paths) + members = tuple(member_ids) + if len(paths) < 2: + raise ValueError("parallel ensemble requires at least two model paths") + if len(paths) != len(members): + raise ValueError("model_paths and member_ids must have equal length") + if output_channels < 1: + raise ValueError("output_channels must be positive") + if sw_batch_size != 1: + raise ValueError("parallel ensemble currently requires sw_batch_size=1") + missing = [str(path) for path in paths if not path.is_file()] + if missing: + raise FileNotFoundError( + f"parallel ensemble model files not found: {missing}" + ) + + self.model_paths = paths + self.member_ids = members + self.output_channels = output_channels + self.threads_per_model = threads_per_model + self.worker_cpu_sets = assign_worker_cpus( + os.sched_getaffinity(0), len(paths), threads_per_model + ) + + # The parent owns preprocessing, aggregation, and postprocessing. The + # identity module satisfies the base engine's inference-only setup; its + # forward method is never called. + super().__init__(torch.nn.Identity(), config, sw_batch_size=sw_batch_size) + + def _wait_for_workers(self, processes, ready_queue, error_queue): + ready = [] + deadline = time.monotonic() + WORKER_STARTUP_TIMEOUT_SECONDS + while len(ready) < len(processes): + error = _next_worker_error(error_queue) + if error is not None: + raise RuntimeError(_format_worker_error(error)) + failed = [ + process for process in processes if process.exitcode not in (None, 0) + ] + if failed: + raise RuntimeError( + f"parallel ensemble worker exited during startup with code " + f"{failed[0].exitcode}" + ) + remaining = deadline - time.monotonic() + if remaining <= 0: + raise RuntimeError("parallel ensemble workers timed out during startup") + try: + ready.append(ready_queue.get(timeout=min(0.2, remaining))) + except queue.Empty: + pass + for worker in sorted(ready, key=lambda item: item["member_id"]): + cpus = ",".join(str(cpu) for cpu in worker["cpu_set"]) + print( + f" Ready {worker['member_id']}: {worker['threads']} threads " + f"on CPUs {cpus}" + ) + + def _dispatch(self, barrier, command, command_value, error_queue): + command_value.value = command + try: + barrier.wait(timeout=WORKER_FORWARD_TIMEOUT_SECONDS) + barrier.wait(timeout=WORKER_FORWARD_TIMEOUT_SECONDS) + except BrokenBarrierError as exc: + error = _next_worker_error(error_queue, timeout=1) + raise RuntimeError(_format_worker_error(error)) from exc + + def _stop_workers(self, processes, barrier, command_value): + alive = [process for process in processes if process.is_alive()] + if alive: + command_value.value = STOP_COMMAND + try: + barrier.wait(timeout=5) + except BrokenBarrierError: + pass + for process in processes: + process.join(timeout=WORKER_SHUTDOWN_TIMEOUT_SECONDS) + for process in processes: + if process.is_alive(): + process.terminate() + process.join(timeout=WORKER_SHUTDOWN_TIMEOUT_SECONDS) + process.close() + + def _run_inference(self, prepared, tta: bool = False) -> torch.Tensor: + patch_iterator = iter(prepared.patch_loader) + try: + first_batch = next(patch_iterator) + except StopIteration as exc: + raise RuntimeError("patch sampler produced no patches") from exc + + first_input = first_batch["image"][tio.DATA] + if first_input.device.type != "cpu" or first_input.dtype != torch.float32: + first_input = first_input.to(device="cpu", dtype=torch.float32) + input_shape = tuple(first_input.shape) + output_shape = (input_shape[0], self.output_channels, *input_shape[2:]) + member_output_shape = output_shape + all_output_shape = (len(self.model_paths), *output_shape) + bytes_per_member = ( + int(np.prod(member_output_shape)) * np.dtype(np.float32).itemsize + ) + + context = mp.get_context("spawn") + command_value = context.Value("i", NO_FLIP_COMMAND) + barrier = context.Barrier(len(self.model_paths) + 1) + ready_queue = context.Queue() + error_queue = context.Queue() + + with tempfile.TemporaryDirectory(prefix="vs-parallel-ensemble-") as directory: + buffer_dir = Path(directory) + input_path = buffer_dir / "input.float32" + output_path = buffer_dir / "output.float32" + input_array = np.memmap( + input_path, dtype=np.float32, mode="w+", shape=input_shape + ) + output_array = np.memmap( + output_path, dtype=np.float32, mode="w+", shape=all_output_shape + ) + input_tensor = torch.from_numpy(input_array) + output_tensor = torch.from_numpy(output_array) + + processes = [ + context.Process( + name=f"vs-ensemble-{member_id}", + target=_ensemble_worker, + args=( + member_id, + str(model_path), + cpu_set, + str(input_path), + input_shape, + str(output_path), + member_output_shape, + index * bytes_per_member, + command_value, + barrier, + ready_queue, + error_queue, + ), + ) + for index, (member_id, model_path, cpu_set) in enumerate( + zip(self.member_ids, self.model_paths, self.worker_cpu_sets) + ) + ] + for process in processes: + process.start() + + try: + self._wait_for_workers(processes, ready_queue, error_queue) + flip_commands = range(len(TTA_FLIP_AXES)) if tta else (NO_FLIP_COMMAND,) + for patches_batch in itertools.chain((first_batch,), patch_iterator): + patch_input = patches_batch["image"][tio.DATA] + patch_input = patch_input.to(device="cpu", dtype=torch.float32) + if tuple(patch_input.shape) != input_shape: + raise RuntimeError( + f"parallel ensemble patch shape changed from {input_shape} " + f"to {tuple(patch_input.shape)}" + ) + input_tensor.copy_(patch_input) + summed_probabilities = None + for command in flip_commands: + self._dispatch(barrier, command, command_value, error_queue) + for member_index in range(len(self.model_paths)): + member_probabilities = output_tensor[member_index] + summed_probabilities = ( + member_probabilities.clone() + if summed_probabilities is None + else summed_probabilities + member_probabilities + ) + divisor = len(self.model_paths) * (len(TTA_FLIP_AXES) if tta else 1) + probabilities = summed_probabilities / divisor + prepared.aggregator.add_batch( + probabilities, patches_batch[tio.LOCATION] + ) + finally: + self._stop_workers(processes, barrier, command_value) + del input_tensor + del output_tensor + del input_array + del output_array + + return prepared.aggregator.get_output_tensor() diff --git a/research/vestibular_schwannoma/deployment/pacs/redcap_model_instances.json b/research/vestibular_schwannoma/deployment/pacs/redcap_model_instances.json new file mode 100644 index 0000000..1bcded6 --- /dev/null +++ b/research/vestibular_schwannoma/deployment/pacs/redcap_model_instances.json @@ -0,0 +1,4 @@ +{ + "4d8991eff16c90ad0eb185757df9cfccf5615cb7a5d7a9d3304c61e65ad9d172": 1, + "c5642793b0f57b49ea8f04a08d2b8c9deff1523813d87d89eec204f85846d6ec": 2 +} diff --git a/research/vestibular_schwannoma/deployment/pacs/redcap_output.py b/research/vestibular_schwannoma/deployment/pacs/redcap_output.py new file mode 100644 index 0000000..7d2e36f --- /dev/null +++ b/research/vestibular_schwannoma/deployment/pacs/redcap_output.py @@ -0,0 +1,310 @@ +"""Lossless DICOM mask transport in pr2mask REDCap EAV JSON. + +Schema v1: distances are millimetres, coordinates are DICOM LPS, and the +array axes are (slice, row, column). Per-slice positions are authoritative. +""" + +from __future__ import annotations + +import base64 +import csv +import gzip +import hashlib +import io +import json +import math +import zipfile +from datetime import datetime, timezone +from pathlib import Path + +import numpy as np +from pydicom import dcmread +from pydicom.misc import is_dicom + +ENCODING = "packbits-gzip-base64-v1" +MAX_VOXELS = 256 * 1024 * 1024 +MODEL_INSTANCES_PATH = Path(__file__).with_name("redcap_model_instances.json") + + +def model_repeat_instance(bundle_sha256): + """Explicit immutable allocation: never truncate a hash into a REDCap integer.""" + registry = json.loads(MODEL_INSTANCES_PATH.read_text()) + if ( + not isinstance(registry, dict) + or any( + len(key) != 64 or any(c not in "0123456789abcdef" for c in key) + for key in registry + ) + or any( + type(n) is not int or not 1 <= n <= 2147483647 for n in registry.values() + ) + or len(set(registry.values())) != len(registry) + ): + raise ValueError("Invalid REDCap model-instance registry") + if bundle_sha256 not in registry: + raise ValueError( + "Unregistered model bundle: allocate a permanent REDCap instance in redcap_model_instances.json" + ) + return str(registry[bundle_sha256]) + + +FIELDS = { + "vs_measurements_json": ("notes", "Complete pr2mask region measurements (JSON)"), + "vs_mask_json": ( + "notes", + "Lossless binary mask and geometry (schema v1; distances in mm)", + ), + "vs_model_type": ("text", "Model type"), + "vs_bundle_sha256": ("text", "Model bundle SHA-256"), + "vs_prediction_id": ("text", "Prediction identity SHA-256"), + "vs_deployment_version": ("text", "Deployment version"), + "vs_tta": ("text", "Test-time augmentation enabled"), + "vs_created_at": ("text", "Prediction export time (UTC)"), +} + + +def _json(value): + return json.dumps(value, separators=(",", ":"), sort_keys=True, allow_nan=False) + + +def encode_mask(mask): + array = np.asarray(mask) + if array.ndim != 3 or not 0 < array.size <= MAX_VOXELS: + raise ValueError("Invalid mask dimensions or size") + if not np.all((array == 0) | (array == 1)): + raise ValueError("Expected binary DICOM mask") + array = np.ascontiguousarray(array, dtype=np.uint8) + packed = np.packbits(array.ravel(), bitorder="little").tobytes() + return dict( + schema_version=1, + encoding=ENCODING, + shape=list(array.shape), + axis_order=["slice", "row", "column"], + flatten_order="C", + bit_order="little", + foreground_value=1, + foreground_voxels=int(array.sum()), + mask_sha256=hashlib.sha256(array.tobytes()).hexdigest(), + data=base64.b64encode(gzip.compress(packed, mtime=0)).decode("ascii"), + ) + + +def decode_mask(payload): + """Recover uint8 voxels; geometry is in payload['geometry'] (all lengths mm).""" + expected = dict( + schema_version=1, + encoding=ENCODING, + axis_order=["slice", "row", "column"], + flatten_order="C", + bit_order="little", + foreground_value=1, + ) + if any(payload.get(k) != v for k, v in expected.items()): + raise ValueError("Unsupported mask schema") + shape = payload["shape"] + if len(shape) != 3 or any(type(n) is not int or n <= 0 for n in shape): + raise ValueError("Invalid mask shape") + count = math.prod(shape) + if count > MAX_VOXELS: + raise ValueError("Mask exceeds size limit") + size = (count + 7) // 8 + # Bound both the compressed input and decompressed output. + if len(payload["data"]) > 4 * (size + 65536): + raise ValueError("Encoded mask exceeds size limit") + compressed = base64.b64decode(payload["data"], validate=True) + with gzip.GzipFile(fileobj=io.BytesIO(compressed)) as stream: + raw = stream.read(size + 1) + if len(raw) != size: + raise ValueError("Mask length mismatch") + bits = np.unpackbits(np.frombuffer(raw, dtype=np.uint8), bitorder="little") + if bits[count:].any(): + raise ValueError("Nonzero mask padding") + array = bits[:count].reshape(shape) + if hashlib.sha256(array.tobytes()).hexdigest() != payload["mask_sha256"]: + raise ValueError("Mask checksum mismatch") + if int(array.sum()) != payload["foreground_voxels"]: + raise ValueError("Foreground count mismatch") + return array + + +def _series(directory, *, pixels=False): + datasets = [ + dcmread(p, stop_before_pixels=not pixels) + for p in Path(directory).rglob("*") + if p.is_file() and is_dicom(p) + ] + datasets = [ + d for d in datasets if hasattr(d, "ImagePositionPatient") and hasattr(d, "Rows") + ] + if not datasets: + raise ValueError("No DICOM image slices found") + first = datasets[0] + orientation = np.asarray(first.ImageOrientationPatient, dtype=float) + spacing = np.asarray(first.PixelSpacing, dtype=float) + if ( + orientation.shape != (6,) + or spacing.shape != (2,) + or not np.isfinite(orientation).all() + or not np.isfinite(spacing).all() + or (spacing <= 0).any() + ): + raise ValueError("Invalid DICOM geometry") + x, y = orientation[:3], orientation[3:] + if not ( + np.isclose(np.linalg.norm(x), 1) + and np.isclose(np.linalg.norm(y), 1) + and np.isclose(np.dot(x, y), 0, atol=1e-5) + ): + raise ValueError("Invalid DICOM orientation") + for d in datasets: + if any( + str(d.get(k, "")) != str(first.get(k, "")) + for k in ( + "StudyInstanceUID", + "SeriesInstanceUID", + "FrameOfReferenceUID", + "Rows", + "Columns", + ) + ): + raise ValueError("Mixed DICOM series or dimensions") + if not ( + np.allclose(d.ImageOrientationPatient, orientation) + and np.allclose(d.PixelSpacing, spacing) + ): + raise ValueError("Inconsistent DICOM geometry") + if not np.isfinite(np.asarray(d.ImagePositionPatient, dtype=float)).all(): + raise ValueError("Invalid slice position") + normal = np.cross(x, y) + datasets.sort(key=lambda d: np.dot(d.ImagePositionPatient, normal)) + positions = np.asarray([d.ImagePositionPatient for d in datasets], dtype=float) + if len(datasets) > 1 and (np.diff(positions @ normal) <= 1e-6).any(): + raise ValueError("Duplicate slice positions") + return datasets + + +def build_mask_payload(mask_dir, input_dir, deployment, *, version, use_tta): + """Read final DICOM pixels and match them to source slices by patient geometry.""" + masks, source = _series(mask_dir, pixels=True), _series(input_dir) + if len(masks) != len(source): + raise ValueError("Source and mask slice counts differ") + for m, s in zip(masks, source): + if (m.Rows, m.Columns) != (s.Rows, s.Columns): + raise ValueError("Source and mask dimensions differ") + for key in ("ImagePositionPatient", "ImageOrientationPatient", "PixelSpacing"): + if not np.allclose(m.get(key), s.get(key), rtol=0, atol=1e-4): + raise ValueError(f"Source and mask geometry differ: {key}") + for key in ("StudyInstanceUID", "FrameOfReferenceUID"): + if str(m.get(key, "")) != str(s.get(key, "")): + raise ValueError(f"Source and mask identifiers differ: {key}") + payload = encode_mask(np.stack([d.pixel_array for d in masks])) + payload["geometry"] = { + "coordinate_system": "LPS", + "pixel_spacing": [float(x) for x in masks[0].PixelSpacing], + "image_orientation_patient": [ + float(x) for x in masks[0].ImageOrientationPatient + ], + "image_positions_patient": [ + [float(x) for x in d.ImagePositionPatient] for d in masks + ], + } + payload["source"] = { + "study_uid": str(source[0].StudyInstanceUID), + "series_uid": str(source[0].SeriesInstanceUID), + "frame_of_reference_uid": str(source[0].get("FrameOfReferenceUID", "")), + "sop_instance_uids": [str(d.SOPInstanceUID) for d in source], + } + payload["mask_series_uid"] = str(masks[0].SeriesInstanceUID) + payload["model"] = dict( + model_type=deployment["model_type"], + bundle_sha256=deployment["bundle_sha256"], + deployment_version=version, + tta=bool(use_tta), + member_ids=[m["member_id"] for m in deployment["members"]], + ) + payload["prediction_id"] = hashlib.sha256(_json(payload).encode()).hexdigest() + payload["created_at"] = datetime.now(timezone.utc).isoformat() + return payload, source[0] + + +def _extend_dictionary(path): + with zipfile.ZipFile(path) as archive: + contents = {name: archive.read(name) for name in archive.namelist()} + reader = csv.DictReader(io.StringIO(contents["instrument.csv"].decode("utf-8-sig"))) + names = reader.fieldnames + required = ["Variable / Field Name", "Form Name", "Field Type", "Field Label"] + if not names or any(k not in names for k in required): + raise ValueError("Unsupported pr2mask data dictionary") + rows = list(reader) + if any(row["Variable / Field Name"] in FIELDS for row in rows): + raise ValueError("Mask fields already present in data dictionary") + for field, (kind, label) in FIELDS.items(): + rows.append(dict(zip(required, [field, "pr2mask", kind, label]))) + buffer = io.StringIO() + writer = csv.DictWriter(buffer, fieldnames=names) + writer.writeheader() + writer.writerows(rows) + contents["instrument.csv"] = buffer.getvalue().encode() + with zipfile.ZipFile(path, "w", compression=zipfile.ZIP_DEFLATED) as archive: + for name, data in contents.items(): + archive.writestr(name, data) + + +def write_redcap_mask(work_dir, input_dir, deployment, *, version, use_tta): + """Store the complete prediction in its permanently allocated model instance.""" + instance = model_repeat_instance(deployment["bundle_sha256"]) + work_dir = Path(work_dir) + paths = list((work_dir / "redcap").glob("*/output.json")) + if len(paths) != 1: + raise ValueError("Expected exactly one pr2mask REDCap output.json") + path = paths[0] + rows = json.loads(path.read_text()) + if not isinstance(rows, list): + raise ValueError("Expected pr2mask EAV rows") + payload, source = build_mask_payload( + work_dir / "mask", input_dir, deployment, version=version, use_tta=use_tta + ) + destination = dict( + record_id=str(source.get("PatientID", "")).rstrip(), + redcap_event_name=str(source.get("ReferringPhysicianName", "")).removeprefix( + "EventName:" + ), + redcap_repeat_instrument="pr2mask", + redcap_repeat_instance="1", + ) + if rows: + destination = {k: rows[0][k] for k in destination} + destination["redcap_repeat_instance"] = "1" + if destination["redcap_repeat_instrument"] != "pr2mask": + raise ValueError("Unexpected REDCap instrument") + if any( + any( + row[k] != destination[k] + for k in ("record_id", "redcap_event_name", "redcap_repeat_instrument") + ) + for row in rows + ): + raise ValueError("Mixed REDCap destinations") + if any(row.get("field_name") in FIELDS for row in rows): + raise ValueError("Mask fields already present in output.json") + # Region instances belong inside the measurement list, not in model routing. + measurements = [ + {k: row[k] for k in ("redcap_repeat_instance", "field_name", "value")} + for row in rows + ] + destination["redcap_repeat_instance"] = instance + values = dict( + vs_measurements_json=_json(measurements), + vs_mask_json=_json(payload), + vs_model_type=deployment["model_type"], + vs_bundle_sha256=deployment["bundle_sha256"], + vs_prediction_id=payload["prediction_id"], + vs_deployment_version=version, + vs_tta=str(int(use_tta)), + vs_created_at=payload["created_at"], + ) + rows = [ + dict(destination, field_name=key, value=value) for key, value in values.items() + ] + _extend_dictionary(path.with_name("output_data_dictionary.zip")) + path.write_text(_json(rows) + "\n") diff --git a/research/vestibular_schwannoma/deployment/pacs/requirements.yml b/research/vestibular_schwannoma/deployment/pacs/requirements.yml index 25fbc24..1658852 100644 --- a/research/vestibular_schwannoma/deployment/pacs/requirements.yml +++ b/research/vestibular_schwannoma/deployment/pacs/requirements.yml @@ -17,6 +17,11 @@ dependencies: # requirements plus the built image digest are the runtime contract. - fastMONAI==0.10.1 - fastai==2.8.7 + # Keep the fastai/fastMONAI dispatch stack on the known-good versions from + # the previously qualified PACS image. plum-dispatch 2.10.0 makes the fastai + # docs decorator try to mutate a read-only Function.__doc__ during import. + - fastcore==2.2.12 + - plum-dispatch==2.9.0 - monai==1.6.0 - torchio==1.2.1 - safetensors==0.7.0 diff --git a/research/vestibular_schwannoma/tests/deployment/test_deployment_bundle.py b/research/vestibular_schwannoma/tests/deployment/test_deployment_bundle.py index 1d03066..ff2d522 100644 --- a/research/vestibular_schwannoma/tests/deployment/test_deployment_bundle.py +++ b/research/vestibular_schwannoma/tests/deployment/test_deployment_bundle.py @@ -17,7 +17,7 @@ def metadata(self, run_id="run-1", patch_config=None): return { "artifact_schema": "1", "arch_id": "monai.unet", - "arch_kwargs": {}, + "arch_kwargs": {"out_channels": 2}, "wrapper_spec": [], "artifact_role": "final", "mlflow_run": run_id, diff --git a/research/vestibular_schwannoma/tests/deployment/test_entrypoint.py b/research/vestibular_schwannoma/tests/deployment/test_entrypoint.py index c60feef..7b62d70 100644 --- a/research/vestibular_schwannoma/tests/deployment/test_entrypoint.py +++ b/research/vestibular_schwannoma/tests/deployment/test_entrypoint.py @@ -72,7 +72,7 @@ def run_entrypoint(self, options=None): def captured_args(self): return self.capture.read_text().splitlines() - def expected_args(self, model_type="unet", *, tta=True): + def expected_args(self, model_type="dynunet", *, tta=True): args = [ "run", "--no-capture-output", @@ -98,7 +98,7 @@ def test_valid_options_are_forwarded_as_arguments(self): self.expected_args("dynunet", tta=False), ) - def test_defaults_to_unet_with_tta(self): + def test_defaults_to_dynunet_with_tta(self): result = self.run_entrypoint() self.assertEqual(result.returncode, 0, result.stderr) self.assertEqual(self.captured_args(), self.expected_args()) diff --git a/research/vestibular_schwannoma/tests/deployment/test_pacs_inference.py b/research/vestibular_schwannoma/tests/deployment/test_pacs_inference.py index 1c43004..233cc29 100644 --- a/research/vestibular_schwannoma/tests/deployment/test_pacs_inference.py +++ b/research/vestibular_schwannoma/tests/deployment/test_pacs_inference.py @@ -41,7 +41,7 @@ def test_main_forwards_arguments_and_image_version(self): version="20260817T120000Z", ) - def test_main_defaults_to_tta_on(self): + def test_main_defaults_to_dynunet_with_tta_on(self): argv = ["/data/input", "/output"] with ( patch.dict(os.environ, {"VERSION": "20260817T120000Z"}), @@ -52,7 +52,7 @@ def test_main_defaults_to_tta_on(self): run_inference.assert_called_once_with( "/data/input", "/output", - "unet", + "dynunet", use_tta=True, version="20260817T120000Z", ) @@ -72,7 +72,7 @@ def test_main_allows_tta_to_be_disabled(self): run_inference.assert_called_once_with( "/data/input", "/output", - "unet", + "dynunet", use_tta=False, version="20260817T120000Z", ) @@ -83,7 +83,7 @@ def deployment(self): return { "patch_config": SimpleNamespace(patch_size=[16, 16, 16]), "model_type": "unet", - "bundle_sha256": "a" * 64, + "bundle_sha256": "4d8991eff16c90ad0eb185757df9cfccf5615cb7a5d7a9d3304c61e65ad9d172", "predictor": object(), "members": [{"member_id": "all_data"}], } @@ -396,7 +396,10 @@ def run(command, **kwargs): "bundle_sha256": "a" * 64, "members": [{"member_id": f"fold_{number}"} for number in range(5)], } - with patch.object(pacs.subprocess, "run", side_effect=run): + with ( + patch.object(pacs.subprocess, "run", side_effect=run), + patch.object(pacs, "write_redcap_mask") as export_mask, + ): pacs._run_postprocessing( input_dir, work_dir, @@ -407,6 +410,9 @@ def run(command, **kwargs): tools=tools, ) + export_mask.assert_called_once_with( + work_dir, input_dir, deployment, version="20260817T120000Z", use_tta=True + ) self.assertEqual(len(calls), 3) identity = f"20260817T120000Z_m1_b{'a' * 32}_t1" self.assertLessEqual(len(identity), 64) diff --git a/research/vestibular_schwannoma/tests/deployment/test_parallel_inference.py b/research/vestibular_schwannoma/tests/deployment/test_parallel_inference.py new file mode 100644 index 0000000..16074d2 --- /dev/null +++ b/research/vestibular_schwannoma/tests/deployment/test_parallel_inference.py @@ -0,0 +1,102 @@ +import sys +import unittest +from pathlib import Path +from types import SimpleNamespace +from unittest.mock import patch + +import torch + + +PROJECT_ROOT = Path(__file__).resolve().parents[2] +PACS_DIR = PROJECT_ROOT / "deployment" / "pacs" +sys.path.insert(0, str(PACS_DIR)) + +import pacs_inference as pacs # noqa: E402 +import parallel_inference as parallel # noqa: E402 + + +class CpuAssignmentTests(unittest.TestCase): + def test_assigns_disjoint_three_thread_worker_sets(self): + assignments = parallel.assign_worker_cpus(range(16), worker_count=5) + + self.assertEqual( + assignments, + [ + (0, 1, 2), + (3, 4, 5), + (6, 7, 8), + (9, 10, 11), + (12, 13, 14), + ], + ) + self.assertEqual(len(set().union(*map(set, assignments))), 15) + + def test_rejects_insufficient_cpu_affinity(self): + with self.assertRaisesRegex(RuntimeError, "requires at least 15"): + parallel.assign_worker_cpus(range(14), worker_count=5) + + def test_probability_activation_matches_output_channels(self): + binary = torch.tensor([[[[[0.0]]]]]) + multiclass = torch.tensor([[[[[1.0]]], [[[2.0]]]]]) + + self.assertTrue( + torch.equal( + parallel._logits_to_probabilities(binary), + torch.sigmoid(binary), + ) + ) + self.assertTrue( + torch.equal( + parallel._logits_to_probabilities(multiclass), + torch.softmax(multiclass, dim=1), + ) + ) + + +class EngineSelectionTests(unittest.TestCase): + def test_packaged_ensemble_uses_parallel_engine_with_three_threads(self): + deployment = { + "members": [{"member_id": f"fold_{index}"} for index in range(1, 6)], + "model_paths": [Path(f"fold_{index}.safetensors") for index in range(1, 6)], + "patch_config": SimpleNamespace(), + "output_channels": 2, + } + engine = object() + with patch.object( + pacs, "ParallelEnsemblePatchInferenceEngine", return_value=engine + ) as parallel_engine: + actual = pacs._create_inference_engine(deployment) + + self.assertIs(actual, engine) + parallel_engine.assert_called_once_with( + deployment["model_paths"], + [f"fold_{index}" for index in range(1, 6)], + deployment["patch_config"], + output_channels=2, + threads_per_model=3, + sw_batch_size=1, + ) + + def test_injected_in_memory_predictor_uses_standard_engine(self): + predictor = object() + deployment = { + "members": [{"member_id": "test"}], + "patch_config": SimpleNamespace(), + "predictor": predictor, + } + engine = object() + with patch.object( + pacs, "PatchInferenceEngine", return_value=engine + ) as standard: + actual = pacs._create_inference_engine(deployment) + + self.assertIs(actual, engine) + standard.assert_called_once_with( + predictor, + deployment["patch_config"], + sw_batch_size=1, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/research/vestibular_schwannoma/tests/deployment/test_redcap_output.py b/research/vestibular_schwannoma/tests/deployment/test_redcap_output.py new file mode 100644 index 0000000..716fec9 --- /dev/null +++ b/research/vestibular_schwannoma/tests/deployment/test_redcap_output.py @@ -0,0 +1,270 @@ +import base64 +import csv +import gzip +import io +import json +import sys +import tempfile +import unittest +from unittest.mock import patch +import zipfile +from pathlib import Path + +import numpy as np +from pydicom import dcmread + +PACS_DIR = Path(__file__).resolve().parents[2] / "deployment" / "pacs" +sys.path.insert(0, str(PACS_DIR)) +from redcap_output import ( # noqa: E402 + encode_mask, + decode_mask, + build_mask_payload, + write_redcap_mask, + FIELDS, + model_repeat_instance, +) +import redcap_output # noqa: E402 +from vestibular_schwannoma.tests.deployment.test_dicom_output import _write_test_image # noqa: E402 + +DEPLOYMENT = { + "model_type": "unet", + "bundle_sha256": "4d8991eff16c90ad0eb185757df9cfccf5615cb7a5d7a9d3304c61e65ad9d172", + "members": [{"member_id": "all_data"}], +} + + +def fixture(root, empty=False): + source, work = root / "source", root / "work" + source.mkdir() + (work / "mask").mkdir(parents=True) + mask = np.zeros((3, 2, 3), dtype=np.uint16) + if not empty: + mask[0, 0, 2] = mask[2, 1, 0] = 1 + # Oblique to the usual axial grid, anisotropic and irregular slice positions. + for i, offset in enumerate([0.0, 2.0, 5.0]): + kwargs = dict( + orientation=(0, 1, 0, 0, 0, 1), + pixel_spacing=(0.7, 0.9), + position=(offset, 12, 31), + ) + p = _write_test_image(source / f"{2 - i}", i, **kwargs) + d = dcmread(p) + d.PatientID = "SYNTHETIC" + d.ReferringPhysicianName = "EventName:test_arm_1" + d.save_as(p) + p = _write_test_image( + work / "mask" / f"{2 - i}", + i, + series_uid="1.2.8", + sop_uid=f"1.2.8.{i + 1}", + **kwargs, + ) + d = dcmread(p) + d.PixelData = mask[i].astype(" Date: Fri, 2 Oct 2026 14:30:50 +0200 Subject: [PATCH 4/5] Run VS research tests with pytest in CI --- .github/workflows/test.yaml | 12 ++++++------ settings.ini | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0fd8785..91506e1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -6,9 +6,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: fastai/workflows/nbdev-ci@master - - name: Test vestibular schwannoma deployment - run: >- - python -m unittest discover - -s research/vestibular_schwannoma/tests - -t research - -p 'test_*.py' + - name: Test vestibular schwannoma workflows and deployment + env: + PYTHONPATH: research:research/vestibular_schwannoma + run: | + python -m pip install pytest + python -m pytest -q research/vestibular_schwannoma/tests diff --git a/settings.ini b/settings.ini index 947ade4..c9d15e2 100644 --- a/settings.ini +++ b/settings.ini @@ -9,7 +9,7 @@ version = 0.10.1 ### OPTIONAL ### requirements = fastai>=2.8.6 monai>=1.5.2 torchio>=1.2.1 xlrd>=1.2.0 scikit-image>=0.23.2,<2 imagedata>=3.8.14 mlflow>=3.14 ipython huggingface-hub gdown plum-dispatch<2.10 safetensors>=0.7.0 -dev_requirements = ipywidgets nbdev<3 execnb<0.2 fastcore<2 tabulate quarto +dev_requirements = ipywidgets nbdev<3 execnb<0.2 fastcore<2 tabulate quarto pytest ### nbdev ### nbs_path = nbs From fd66328ee4310c8514997b674ebc1fd74859614c Mon Sep 17 00:00:00 2001 From: Sathiesh Kaliyugarasan Date: Fri, 2 Oct 2026 14:47:03 +0200 Subject: [PATCH 5/5] Simplify VS mask packaging and deployment tests --- research/vestibular_schwannoma/.gitignore | 9 +- research/vestibular_schwannoma/data/README.md | 15 +- .../data/corrected_labels.json | 34 --- .../LICENSE.txt | 0 .../{releases => corrected_labels}/README.md | 0 .../vs_corrected_labels_v1.zip | Bin .../scripts/prepare_data.py | 54 +--- .../deployment/test_deployment_bundle.py | 95 ++----- .../tests/deployment/test_entrypoint.py | 15 +- .../tests/deployment/test_pacs_inference.py | 136 +++------- .../deployment/test_parallel_inference.py | 152 ++++++----- .../deployment/test_prepare_model_bundle.py | 251 +++++++++--------- .../tests/workflow/test_data_preparation.py | 11 +- 13 files changed, 297 insertions(+), 475 deletions(-) delete mode 100644 research/vestibular_schwannoma/data/corrected_labels.json rename research/vestibular_schwannoma/data/{releases => corrected_labels}/LICENSE.txt (100%) rename research/vestibular_schwannoma/data/{releases => corrected_labels}/README.md (100%) rename research/vestibular_schwannoma/data/{releases => corrected_labels}/vs_corrected_labels_v1.zip (100%) diff --git a/research/vestibular_schwannoma/.gitignore b/research/vestibular_schwannoma/.gitignore index a6f9f10..3c7dc5c 100644 --- a/research/vestibular_schwannoma/.gitignore +++ b/research/vestibular_schwannoma/.gitignore @@ -26,13 +26,12 @@ htmlcov/ data/** !data/ !data/README.md -!data/releases/ -!data/releases/README.md -!data/releases/LICENSE.txt -!data/releases/vs_corrected_labels_v1.zip +!data/corrected_labels/ +!data/corrected_labels/README.md +!data/corrected_labels/LICENSE.txt +!data/corrected_labels/vs_corrected_labels_v1.zip !data/ml_dataset.csv !data/reference_dataset.json -!data/corrected_labels.json !data/requirements.txt !data/*.example.csv raw_data/ diff --git a/research/vestibular_schwannoma/data/README.md b/research/vestibular_schwannoma/data/README.md index 9c3f5fa..0f2f9e5 100644 --- a/research/vestibular_schwannoma/data/README.md +++ b/research/vestibular_schwannoma/data/README.md @@ -81,11 +81,11 @@ data/raw/ ``` The 17 replacement masks are included in this repository as -[`releases/vs_corrected_labels_v1.zip`](releases/vs_corrected_labels_v1.zip). +[`corrected_labels/vs_corrected_labels_v1.zip`](corrected_labels/vs_corrected_labels_v1.zip). `download` checks and extracts this archive automatically; no separate hosting account or download is needed. Use `download --dataset labels` to extract just -the masks. An alternative HTTPS location can be supplied with -`--corrected-label-url URL`; the archive must match `corrected_labels.json`. +the masks. The ZIP contains its own case mapping and provenance manifest; +preparation verifies every replacement mask against `reference_dataset.json`. ## Alternatives: TCIA Data Retriever CLI or graphical app @@ -190,8 +190,8 @@ Contributors: **Njål Lura** and **Satheshkumar Kaliyugarassan**. The release uses **CC BY 4.0** and asks readers to cite the accompanying study paper when available. -The [`releases/`](releases/) folder contains one ZIP, -a [short README](releases/README.md), and a [license notice](releases/LICENSE.txt). +The [`corrected_labels/`](corrected_labels/) folder contains one ZIP, +a [short README](corrected_labels/README.md), and a [license notice](corrected_labels/LICENSE.txt). These small masks are versioned with the preparation code on GitHub. Model weights are distributed separately on Hugging Face. Use a tagged repository release to identify the code, case index, and masks used for a study. @@ -204,9 +204,8 @@ python scripts/prepare_data.py bundle-labels --data-root ../nii_data The ZIP contains 17 masks, a TCIA case mapping, provenance manifest, README, and license. Fresh NIfTI headers omit annotation-editor text and extensions -while preserving geometry and tumour voxels. Existing bundles and asset metadata -are refused. If the archive changes, update its checksum and size in -`corrected_labels.json`. +while preserving geometry and tumour voxels. Existing bundles are refused; +package an explicitly reviewed future release under a new filename. Original Queen Square data are [CC BY 4.0](https://wiki.cancerimagingarchive.net/pages/viewpage.action?pageId=70229053); diff --git a/research/vestibular_schwannoma/data/corrected_labels.json b/research/vestibular_schwannoma/data/corrected_labels.json deleted file mode 100644 index 74aba9f..0000000 --- a/research/vestibular_schwannoma/data/corrected_labels.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "cases": [ - "vs_gk_4", - "vs_gk_5", - "vs_gk_29", - "vs_gk_35", - "vs_gk_43", - "vs_gk_44", - "vs_gk_45", - "vs_gk_47", - "vs_gk_55", - "vs_gk_56", - "vs_gk_61", - "vs_gk_63", - "vs_gk_71", - "vs_gk_74", - "vs_gk_76", - "vs_gk_96", - "vs_gk_119" - ], - "contributors": [ - "Nj\u00e5l Lura", - "Satheshkumar Kaliyugarassan" - ], - "doi": null, - "filename": "vs_corrected_labels_v1.zip", - "release_license": "CC BY 4.0", - "release_license_url": "https://creativecommons.org/licenses/by/4.0/", - "schema": "vs-corrected-label-asset-v1", - "sha256": "dcfe4b0c645b69c4ede4b34fd6942731f35c90488ac63364555a218dd6ee58ba", - "size_bytes": 573221, - "url": null, - "version": 1 -} diff --git a/research/vestibular_schwannoma/data/releases/LICENSE.txt b/research/vestibular_schwannoma/data/corrected_labels/LICENSE.txt similarity index 100% rename from research/vestibular_schwannoma/data/releases/LICENSE.txt rename to research/vestibular_schwannoma/data/corrected_labels/LICENSE.txt diff --git a/research/vestibular_schwannoma/data/releases/README.md b/research/vestibular_schwannoma/data/corrected_labels/README.md similarity index 100% rename from research/vestibular_schwannoma/data/releases/README.md rename to research/vestibular_schwannoma/data/corrected_labels/README.md diff --git a/research/vestibular_schwannoma/data/releases/vs_corrected_labels_v1.zip b/research/vestibular_schwannoma/data/corrected_labels/vs_corrected_labels_v1.zip similarity index 100% rename from research/vestibular_schwannoma/data/releases/vs_corrected_labels_v1.zip rename to research/vestibular_schwannoma/data/corrected_labels/vs_corrected_labels_v1.zip diff --git a/research/vestibular_schwannoma/scripts/prepare_data.py b/research/vestibular_schwannoma/scripts/prepare_data.py index d2d659a..3b51407 100644 --- a/research/vestibular_schwannoma/scripts/prepare_data.py +++ b/research/vestibular_schwannoma/scripts/prepare_data.py @@ -14,13 +14,10 @@ sys.path.insert(0, str(PROJECT_ROOT)) from workflow.data_download import ( # noqa: E402 - ChecksumMismatchError, DownloadError, acquire_crossmoda, acquire_tcia, - download_file, safe_extract_zip, - verify_checksum, ) from workflow.data_sources import TCIA_VESTIBULAR_SCHWANNOMA_SEG # noqa: E402 @@ -57,10 +54,6 @@ def parser() -> argparse.ArgumentParser: download.add_argument( "--assets-only", action="store_true", help="skip TCIA DICOM retrieval" ) - download.add_argument( - "--corrected-label-url", - help="download the label archive using its pinned SHA-256", - ) for command, help_text in [ ("prepare", "prepare and verify only the shared study cases"), @@ -108,9 +101,8 @@ def parser() -> argparse.ArgumentParser: sub.add_argument("--output", default="data/reference_dataset.json") else: sub.add_argument( - "--output", default="data/releases/vs_corrected_labels_v1.zip" + "--output", default="data/corrected_labels/vs_corrected_labels_v1.zip" ) - sub.add_argument("--asset-metadata", default="data/corrected_labels.json") return root @@ -119,24 +111,12 @@ def progress(number: int, total: int, case_id: str) -> None: print(f"{number}/{total}: {case_id}", file=sys.stderr, flush=True) -def label_asset() -> dict: - asset = json.loads((PROJECT_ROOT / "data/corrected_labels.json").read_text()) - filename = asset["filename"] - if not isinstance(filename, str) or Path(filename).name != filename: - raise ValueError("Invalid corrected-label archive filename") - if not asset.get("sha256"): - raise ValueError("Corrected-label metadata must declare a SHA-256") - return asset - - -def bundled_labels(asset: dict | None = None) -> Path: - """Locate and verify the replacement masks included in this checkout.""" - asset = label_asset() if asset is None else asset - archive = PROJECT_ROOT / "data/releases" / asset["filename"] - if not verify_checksum(archive, f"sha256:{asset['sha256']}"): - raise ChecksumMismatchError( - f"Bundled corrected-label archive differs: {archive}" - ) +def bundled_labels() -> Path: + """Check the ZIP's integrity; preparation verifies masks against the reference.""" + archive = PROJECT_ROOT / "data/corrected_labels/vs_corrected_labels_v1.zip" + with zipfile.ZipFile(archive) as bundle: + if bad_member := bundle.testzip(): + raise zipfile.BadZipFile(f"Corrupt label archive member: {bad_member}") return archive @@ -210,17 +190,7 @@ def _download(args: argparse.Namespace) -> dict: results["queen_square_backend"] = "assets-only" if args.assets_only else backend results["queen_square_assets"] = [str(p) for p in result.assets] if args.dataset in {"all", "labels"}: - asset = label_asset() - if args.corrected_label_url: - if not args.corrected_label_url.startswith("https://"): - raise ValueError("Corrected-label downloads require HTTPS") - archive = download_file( - args.corrected_label_url, - raw / asset["filename"], - checksum=f"sha256:{asset['sha256']}", - ) - else: - archive = bundled_labels(asset) + archive = bundled_labels() results["corrected_labels"] = str( safe_extract_zip(archive, raw / "corrected_labels") ) @@ -261,15 +231,9 @@ def run(args: argparse.Namespace) -> dict: allow_extra_files=args.allow_extra_files, ) if args.command == "bundle-labels": - metadata = project_path(args.asset_metadata) - if metadata.exists(): - raise FileExistsError(f"Label asset metadata already exists: {metadata}") - value = build_label_bundle( + return build_label_bundle( index, reference, project_path(args.data_root), project_path(args.output) ) - metadata.parent.mkdir(parents=True, exist_ok=True) - write_json(metadata, value) - return value raw = project_path(args.raw_root) if args.from_existing: if any( diff --git a/research/vestibular_schwannoma/tests/deployment/test_deployment_bundle.py b/research/vestibular_schwannoma/tests/deployment/test_deployment_bundle.py index ff2d522..261eaf6 100644 --- a/research/vestibular_schwannoma/tests/deployment/test_deployment_bundle.py +++ b/research/vestibular_schwannoma/tests/deployment/test_deployment_bundle.py @@ -3,7 +3,7 @@ import tempfile import unittest from pathlib import Path -from unittest.mock import MagicMock, call, patch +from unittest.mock import patch PROJECT_ROOT = Path(__file__).resolve().parents[2] PACS_DIR = PROJECT_ROOT / "deployment" / "pacs" @@ -108,75 +108,34 @@ def test_missing_manifest_and_declared_model_fail_before_loading(self): metadata.assert_not_called() load.assert_not_called() - def test_loads_ensemble_and_uses_first_member_patch_config(self): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - _, deployment = self.write_bundle( - root, member_ids=("fold_1", "fold_2") - ) - paths = [ - root / "model_bundles" / "unet" / f"fold_{index}.safetensors" - for index in (1, 2) - ] - models = [MagicMock(), MagicMock()] + def test_loads_single_and_ensemble_with_declared_patch_config(self): + for member_ids in (("all_data",), ("fold_1", "fold_2")): with ( - patch.object( - runtime_bundle, - "read_safetensors_metadata", - return_value=self.metadata(patch_config={"patch_size": [16, 16, 16]}), - ) as metadata, - patch.object( - runtime_bundle, - "load_safetensors_model", - side_effect=models, - ) as load, - patch.object( - runtime_bundle, "PatchConfig", return_value="patch-config" - ) as patch_config, + self.subTest(member_ids=member_ids), + tempfile.TemporaryDirectory() as directory, ): - loaded = runtime_bundle.load_deployment("unet", script_dir=root) - - self.assertEqual(loaded["members"], deployment["members"]) - self.assertNotIn("models", loaded) - self.assertEqual(loaded["predictor"], models) - self.assertEqual(loaded["patch_config"], "patch-config") - metadata.assert_called_once_with(paths[0]) - self.assertEqual( - load.call_args_list, - [call(paths[0], device="cpu"), call(paths[1], device="cpu")], - ) - patch_config.assert_called_once_with(patch_size=[16, 16, 16]) - - def test_loads_one_final_model_from_derived_filename(self): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - _, deployment = self.write_bundle(root, member_ids=("all_data",)) - path = root / "model_bundles" / "unet" / "all_data.safetensors" - model = MagicMock() - with ( - patch.object( - runtime_bundle, - "read_safetensors_metadata", - return_value=self.metadata("run-final"), - ), - patch.object( - runtime_bundle, - "load_safetensors_model", - return_value=model, - ) as load, - patch.object( - runtime_bundle, "PatchConfig", return_value="patch-config" - ), - ): - loaded = runtime_bundle.load_deployment("unet", script_dir=root) - - self.assertEqual(loaded["members"], deployment["members"]) - self.assertIs(loaded["predictor"], model) - load.assert_called_once_with(path, device="cpu") - - def test_unknown_model_type_is_rejected(self): - with self.assertRaisesRegex(ValueError, "unknown model type"): - runtime_bundle.load_deployment("unknown") + root = Path(directory) + _, declaration = self.write_bundle(root, member_ids=member_ids) + models = [object() for _ in member_ids] + with ( + patch.object( + runtime_bundle, + "read_safetensors_metadata", + return_value=self.metadata( + patch_config={"patch_size": [16, 16, 16]} + ), + ), + patch.object( + runtime_bundle, "load_safetensors_model", side_effect=models + ), + ): + loaded = runtime_bundle.load_deployment("unet", script_dir=root) + self.assertEqual(loaded["members"], declaration["members"]) + self.assertEqual( + loaded["predictor"], models[0] if len(models) == 1 else models + ) + self.assertEqual(loaded["patch_config"].patch_size, [16, 16, 16]) + self.assertEqual(loaded["output_channels"], 2) if __name__ == "__main__": diff --git a/research/vestibular_schwannoma/tests/deployment/test_entrypoint.py b/research/vestibular_schwannoma/tests/deployment/test_entrypoint.py index 7b62d70..a99f296 100644 --- a/research/vestibular_schwannoma/tests/deployment/test_entrypoint.py +++ b/research/vestibular_schwannoma/tests/deployment/test_entrypoint.py @@ -40,9 +40,7 @@ def setUp(self): fake_bin = self.root / "bin" fake_bin.mkdir() conda = fake_bin / "conda" - conda.write_text( - "#!/bin/bash\nprintf '%s\\n' \"$@\" > \"$CAPTURE_PATH\"\n" - ) + conda.write_text('#!/bin/bash\nprintf \'%s\\n\' "$@" > "$CAPTURE_PATH"\n') conda.chmod(0o755) self.fake_bin = fake_bin @@ -103,18 +101,9 @@ def test_defaults_to_dynunet_with_tta(self): self.assertEqual(result.returncode, 0, result.stderr) self.assertEqual(self.captured_args(), self.expected_args()) - def test_tta_can_be_enabled_explicitly(self): - result = self.run_entrypoint(json.dumps({"tta": True})) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual( - self.captured_args(), self.expected_args(tta=True) - ) - def test_shell_metacharacters_are_rejected_without_execution(self): marker = self.root / "injected" - result = self.run_entrypoint( - json.dumps({"model-type": f"unet;touch {marker}"}) - ) + result = self.run_entrypoint(json.dumps({"model-type": f"unet;touch {marker}"})) self.assertEqual(result.returncode, 2) self.assertFalse(marker.exists()) self.assertFalse(self.capture.exists()) diff --git a/research/vestibular_schwannoma/tests/deployment/test_pacs_inference.py b/research/vestibular_schwannoma/tests/deployment/test_pacs_inference.py index 233cc29..d3cb812 100644 --- a/research/vestibular_schwannoma/tests/deployment/test_pacs_inference.py +++ b/research/vestibular_schwannoma/tests/deployment/test_pacs_inference.py @@ -19,63 +19,26 @@ class CommandLineTests(unittest.TestCase): - def test_main_forwards_arguments_and_image_version(self): - argv = [ - "/data/input", - "/output", - "--model-type", - "unet", - "--tta", + def test_model_tta_options_and_image_version(self): + cases = [ + ([], "dynunet", True), + (["--model-type", "unet", "--tta"], "unet", True), + (["--no-tta"], "dynunet", False), ] - with ( - patch.dict(os.environ, {"VERSION": "20260817T120000Z"}), - patch.object(pacs, "run_inference") as run_inference, - ): - pacs.main(argv) - - run_inference.assert_called_once_with( - "/data/input", - "/output", - "unet", - use_tta=True, - version="20260817T120000Z", - ) - - def test_main_defaults_to_dynunet_with_tta_on(self): - argv = ["/data/input", "/output"] - with ( - patch.dict(os.environ, {"VERSION": "20260817T120000Z"}), - patch.object(pacs, "run_inference") as run_inference, - ): - pacs.main(argv) - - run_inference.assert_called_once_with( - "/data/input", - "/output", - "dynunet", - use_tta=True, - version="20260817T120000Z", - ) - - def test_main_allows_tta_to_be_disabled(self): - argv = [ - "/data/input", - "/output", - "--no-tta", - ] - with ( - patch.dict(os.environ, {"VERSION": "20260817T120000Z"}), - patch.object(pacs, "run_inference") as run_inference, - ): - pacs.main(argv) - - run_inference.assert_called_once_with( - "/data/input", - "/output", - "dynunet", - use_tta=False, - version="20260817T120000Z", - ) + for options, model_type, use_tta in cases: + with ( + self.subTest(options=options), + patch.dict(os.environ, {"VERSION": "20260817T120000Z"}), + patch.object(pacs, "run_inference") as run, + ): + pacs.main(["/data/input", "/output", *options]) + run.assert_called_once_with( + "/data/input", + "/output", + model_type, + use_tta=use_tta, + version="20260817T120000Z", + ) class PredictionOutputTests(unittest.TestCase): @@ -138,11 +101,14 @@ def test_inference_writes_probability_channel_one_then_postprocesses(self): written_mask, written_probability = write_outputs.call_args.args[:2] self.assertTrue(torch.equal(written_mask, mask)) self.assertTrue(torch.equal(written_probability, probabilities[1])) - self.assertEqual(write_outputs.call_args.args[2:], ( - input_output_dir, - work_dir, - deployment, - )) + self.assertEqual( + write_outputs.call_args.args[2:], + ( + input_output_dir, + work_dir, + deployment, + ), + ) self.assertTrue(write_outputs.call_args.kwargs["use_tta"]) postprocess.assert_called_once() self.assertTrue(unrelated_files_remain) @@ -207,7 +173,9 @@ def test_nonempty_work_directory_is_rejected_before_model_loading(self): patch.object(pacs, "_required_pr2mask_tools", return_value={}), patch.object(pacs, "load_deployment") as load, ): - with self.assertRaisesRegex(RuntimeError, "work directory must be empty"): + with self.assertRaisesRegex( + RuntimeError, "work directory must be empty" + ): pacs.run_inference( "/dicom", output, @@ -217,40 +185,6 @@ def test_nonempty_work_directory_is_rejected_before_model_loading(self): ) load.assert_not_called() - def test_dicom_preflight_runs_before_model_loading(self): - events = [] - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - with ( - patch.object(pacs, "_required_pr2mask_tools", return_value={}), - patch.object( - pacs, - "validate_dicom_input", - side_effect=lambda path: events.append(("preflight", path)), - ), - patch.object( - pacs, - "load_deployment", - side_effect=lambda model: ( - events.append(("load", model)), - self.deployment(), - )[1], - ), - patch.object(pacs, "PatchInferenceEngine", side_effect=RuntimeError("stop")), - ): - with self.assertRaisesRegex(RuntimeError, "stop"): - pacs.run_inference( - root / "input", - root / "output", - "unet", - version="release", - work_dir=root / "work", - ) - self.assertEqual( - events, - [("preflight", root / "input"), ("load", "unet")], - ) - def test_dicom_preflight_failure_prevents_model_loading(self): with tempfile.TemporaryDirectory() as directory: root = Path(directory) @@ -378,9 +312,7 @@ def test_commands_bind_identity_and_copy_only_final_outputs(self): def run(command, **kwargs): calls.append(command) - self.assertEqual( - (output_dir / pacs.LOG_NAME).read_text(), "old log" - ) + self.assertEqual((output_dir / pacs.LOG_NAME).read_text(), "old log") self.assertTrue((work_dir / pacs.LOG_NAME).is_file()) kwargs["stdout"].write(f"command {len(calls)}\n") kwargs["stdout"].flush() @@ -411,7 +343,11 @@ def run(command, **kwargs): ) export_mask.assert_called_once_with( - work_dir, input_dir, deployment, version="20260817T120000Z", use_tta=True + work_dir, + input_dir, + deployment, + version="20260817T120000Z", + use_tta=True, ) self.assertEqual(len(calls), 3) identity = f"20260817T120000Z_m1_b{'a' * 32}_t1" diff --git a/research/vestibular_schwannoma/tests/deployment/test_parallel_inference.py b/research/vestibular_schwannoma/tests/deployment/test_parallel_inference.py index 16074d2..e1d66d0 100644 --- a/research/vestibular_schwannoma/tests/deployment/test_parallel_inference.py +++ b/research/vestibular_schwannoma/tests/deployment/test_parallel_inference.py @@ -1,11 +1,22 @@ +import os import sys +import tempfile import unittest from pathlib import Path -from types import SimpleNamespace from unittest.mock import patch +import nibabel as nib +import numpy as np import torch - +from fastMONAI.vision_all import ( + PatchConfig, + PatchInferenceEngine, + make_model_spec, + make_output_spec, + patch_config_to_dict, + save_safetensors_model, +) +from monai.networks.nets import UNet PROJECT_ROOT = Path(__file__).resolve().parents[2] PACS_DIR = PROJECT_ROOT / "deployment" / "pacs" @@ -18,16 +29,8 @@ class CpuAssignmentTests(unittest.TestCase): def test_assigns_disjoint_three_thread_worker_sets(self): assignments = parallel.assign_worker_cpus(range(16), worker_count=5) - self.assertEqual( - assignments, - [ - (0, 1, 2), - (3, 4, 5), - (6, 7, 8), - (9, 10, 11), - (12, 13, 14), - ], + assignments, [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14)] ) self.assertEqual(len(set().union(*map(set, assignments))), 15) @@ -35,67 +38,78 @@ def test_rejects_insufficient_cpu_affinity(self): with self.assertRaisesRegex(RuntimeError, "requires at least 15"): parallel.assign_worker_cpus(range(14), worker_count=5) - def test_probability_activation_matches_output_channels(self): - binary = torch.tensor([[[[[0.0]]]]]) - multiclass = torch.tensor([[[[[1.0]]], [[[2.0]]]]]) - self.assertTrue( - torch.equal( - parallel._logits_to_probabilities(binary), - torch.sigmoid(binary), +class ParallelEnsembleTests(unittest.TestCase): + @unittest.skipUnless( + hasattr(os, "sched_getaffinity") and len(os.sched_getaffinity(0)) >= 2, + "Real parallel workers require Linux and two available CPUs", + ) + def test_real_workers_match_sequential_inference_with_and_without_tta(self): + previous_threads = torch.get_num_threads() + self.addCleanup(torch.set_num_threads, previous_threads) + torch.set_num_threads(1) + with tempfile.TemporaryDirectory() as directory, torch.random.fork_rng(): + torch.manual_seed(42) + root = Path(directory) + kwargs = dict( + spatial_dims=3, + in_channels=1, + out_channels=2, + channels=(2, 4), + strides=(2,), ) - ) - self.assertTrue( - torch.equal( - parallel._logits_to_probabilities(multiclass), - torch.softmax(multiclass, dim=1), + models = [UNet(**kwargs).eval() for _ in range(2)] + config = PatchConfig( + patch_size=[16, 16, 16], + patch_overlap=8, + apply_reorder=False, + normalization=None, + aggregation_mode="average", ) - ) - - -class EngineSelectionTests(unittest.TestCase): - def test_packaged_ensemble_uses_parallel_engine_with_three_threads(self): - deployment = { - "members": [{"member_id": f"fold_{index}"} for index in range(1, 6)], - "model_paths": [Path(f"fold_{index}.safetensors") for index in range(1, 6)], - "patch_config": SimpleNamespace(), - "output_channels": 2, - } - engine = object() - with patch.object( - pacs, "ParallelEnsemblePatchInferenceEngine", return_value=engine - ) as parallel_engine: - actual = pacs._create_inference_engine(deployment) - - self.assertIs(actual, engine) - parallel_engine.assert_called_once_with( - deployment["model_paths"], - [f"fold_{index}" for index in range(1, 6)], - deployment["patch_config"], - output_channels=2, - threads_per_model=3, - sw_batch_size=1, - ) - - def test_injected_in_memory_predictor_uses_standard_engine(self): - predictor = object() - deployment = { - "members": [{"member_id": "test"}], - "patch_config": SimpleNamespace(), - "predictor": predictor, - } - engine = object() - with patch.object( - pacs, "PatchInferenceEngine", return_value=engine - ) as standard: - actual = pacs._create_inference_engine(deployment) - - self.assertIs(actual, engine) - standard.assert_called_once_with( - predictor, - deployment["patch_config"], - sw_batch_size=1, - ) + metadata = dict( + config_schema="1", + workflow="patch", + patch_config=patch_config_to_dict(config, inference_only=True), + output=make_output_spec("multiclass_segmentation", classes=2), + ) + image = root / "synthetic.nii.gz" + values = ( + np.random.default_rng(42).normal(size=(18, 17, 13)).astype(np.float32) + ) + nib.save(nib.Nifti1Image(values, np.eye(4)), image) + paths = [ + save_safetensors_model( + model, + root / f"fold_{i}.safetensors", + make_model_spec("monai.unet", kwargs), + metadata, + "best", + ) + for i, model in enumerate(models) + ] + deployment = dict( + model_paths=paths, + output_channels=2, + patch_config=config, + members=[{"member_id": f"fold_{i}"} for i in range(2)], + ) + # Small CI runners use one thread per model; production allocation is + # covered by CpuAssignmentTests above. + with patch.object(pacs, "THREADS_PER_ENSEMBLE_MODEL", 1): + engine = pacs._create_inference_engine(deployment) + sequential = PatchInferenceEngine(models, config) + for use_tta in (False, True): + with self.subTest(tta=use_tta): + actual_mask, actual = engine.predict_mask_and_probabilities( + image, tta=use_tta + ) + expected_mask, expected = sequential.predict_mask_and_probabilities( + image, tta=use_tta + ) + torch.testing.assert_close(actual, expected, rtol=1e-5, atol=1e-6) + torch.testing.assert_close( + actual_mask, expected_mask, rtol=0, atol=0 + ) if __name__ == "__main__": diff --git a/research/vestibular_schwannoma/tests/deployment/test_prepare_model_bundle.py b/research/vestibular_schwannoma/tests/deployment/test_prepare_model_bundle.py index 7d18e71..b1cf5d9 100644 --- a/research/vestibular_schwannoma/tests/deployment/test_prepare_model_bundle.py +++ b/research/vestibular_schwannoma/tests/deployment/test_prepare_model_bundle.py @@ -3,7 +3,7 @@ import sys import tempfile import unittest -from contextlib import redirect_stderr, redirect_stdout +from contextlib import redirect_stdout from pathlib import Path from unittest.mock import patch @@ -25,12 +25,17 @@ class ArtifactSelectionTests(unittest.TestCase): def parse_run(self, role, *extra): - return bundle.make_parser().parse_args([ - "--model-type", "unet", - "--run", "all_data=run-id", - "--artifact-role", role, - *extra, - ]) + return bundle.make_parser().parse_args( + [ + "--model-type", + "unet", + "--run", + "all_data=run-id", + "--artifact-role", + role, + *extra, + ] + ) def test_pacs_and_research_expect_the_same_output_contract(self): self.assertEqual( @@ -59,11 +64,10 @@ def metadata( "workflow": "patch", "patch_config": ( {"keep_largest_component": False} - if patch_config is None else patch_config - ), - "output": bundle.make_output_spec( - "multiclass_segmentation", classes=2 + if patch_config is None + else patch_config ), + "output": bundle.make_output_spec("multiclass_segmentation", classes=2), }, } @@ -73,46 +77,15 @@ def local_args(self, root: Path, *member_ids: str): artifact = root / f"source-{member_id}.safetensors" artifact.write_bytes(member_id.encode()) argv.extend(["--artifact", f"{member_id}={artifact}"]) - argv.extend([ - "--artifact-role", "final", - "--out", str(root / "bundle"), - ]) - return bundle.make_parser().parse_args(argv) - - def test_role_selects_standard_artifact_path(self): - self.assertEqual( - bundle._requested_artifact_path(self.parse_run("final")), - "model/final_model.safetensors", - ) - self.assertEqual( - bundle._requested_artifact_path(self.parse_run("best")), - "model/best_model.safetensors", - ) - - def test_explicit_artifact_path_overrides_role_default(self): - args = self.parse_run( - "final", "--artifact-path", "custom/export.safetensors" - ) - self.assertEqual( - bundle._requested_artifact_path(args), - "custom/export.safetensors", - ) - - def test_artifact_role_is_required(self): - with redirect_stderr(io.StringIO()): - with self.assertRaises(SystemExit): - bundle.make_parser().parse_args([ - "--model-type", "unet", - "--run", "all_data=run-id", - ]) - - def test_uid_prefix_is_optional(self): - default_args = self.parse_run("final") - registered_args = self.parse_run( - "final", "--dicom-uid-prefix", REGISTERED_TEST_PREFIX + argv.extend( + [ + "--artifact-role", + "final", + "--out", + str(root / "bundle"), + ] ) - self.assertIsNone(default_args.dicom_uid_prefix) - self.assertEqual(registered_args.dicom_uid_prefix, REGISTERED_TEST_PREFIX) + return bundle.make_parser().parse_args(argv) def test_registered_prefix_validation(self): self.assertEqual( @@ -143,8 +116,10 @@ def test_invalid_prefix_fails_before_download_or_output_creation(self): output = Path(directory) / "bundle" args = self.parse_run( "final", - "--dicom-uid-prefix", "1.02.3", - "--out", str(output), + "--dicom-uid-prefix", + "1.02.3", + "--out", + str(output), ) with patch.object(bundle, "_download_run_artifact") as download: with self.assertRaisesRegex(ValueError, "canonical numeric"): @@ -153,9 +128,7 @@ def test_invalid_prefix_fails_before_download_or_output_creation(self): self.assertFalse(output.exists()) def test_registry_preserves_existing_models_and_unique_codes(self): - self.assertEqual( - MODEL_CONFIGS["unet"]["arch_ids"], frozenset({"monai.unet"}) - ) + self.assertEqual(MODEL_CONFIGS["unet"]["arch_ids"], frozenset({"monai.unet"})) self.assertEqual( MODEL_CONFIGS["dynunet"]["arch_ids"], frozenset({"monai.dynunet"}), @@ -163,9 +136,7 @@ def test_registry_preserves_existing_models_and_unique_codes(self): self.assertEqual(MODEL_CONFIGS["unet"]["dicom_model_code"], 1) self.assertEqual(MODEL_CONFIGS["dynunet"]["dicom_model_code"], 2) - model_codes = [ - config["dicom_model_code"] for config in MODEL_CONFIGS.values() - ] + model_codes = [config["dicom_model_code"] for config in MODEL_CONFIGS.values()] self.assertTrue(all(type(code) is int and code > 0 for code in model_codes)) self.assertEqual(len(model_codes), len(set(model_codes))) self.assertEqual( @@ -216,9 +187,7 @@ def test_requested_mlflow_run_mismatch_is_rejected_during_preparation(self): artifact.touch() args = self.parse_run("final", "--out", str(root / "bundle")) with ( - patch.object( - bundle, "_download_run_artifact", return_value=artifact - ), + patch.object(bundle, "_download_run_artifact", return_value=artifact), patch.object( bundle, "read_safetensors_metadata", @@ -268,7 +237,10 @@ def test_incompatible_ensemble_is_rejected_during_bundle_preparation(self): ), ) for second_metadata, message in incompatible: - with self.subTest(message=message), tempfile.TemporaryDirectory() as directory: + with ( + self.subTest(message=message), + tempfile.TemporaryDirectory() as directory, + ): root = Path(directory) args = self.local_args(root, "fold_1", "fold_2") with ( @@ -287,9 +259,7 @@ def test_largest_component_filter_is_rejected_before_loading(self): with tempfile.TemporaryDirectory() as directory: root = Path(directory) args = self.local_args(root, "all_data") - metadata = self.metadata( - patch_config={"keep_largest_component": True} - ) + metadata = self.metadata(patch_config={"keep_largest_component": True}) with ( patch.object( bundle, "read_safetensors_metadata", return_value=metadata @@ -321,48 +291,54 @@ def test_strict_load_failure_prevents_bundle_publication(self): bundle.build_bundle(args) self.assertFalse((root / "bundle").exists()) - def test_final_run_writes_minimal_schema_one_manifest(self): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - artifact = root / "downloaded.safetensors" - artifact.touch() - output = root / "bundle" - output.mkdir() - output.chmod(0o750) - args = self.parse_run("final", "--out", str(output)) + def test_requested_artifact_produces_the_declared_bundle(self): + cases = [ + ("final", "model/final_model.safetensors", []), + ("best", "model/best_model.safetensors", []), + ( + "final", + "custom/export.safetensors", + ["--artifact-path", "custom/export.safetensors"], + ), + ] + for role, artifact_path, extra in cases: with ( - patch.object( - bundle, - "_download_run_artifact", - return_value=artifact, - ) as download, - patch.object( - bundle, - "read_safetensors_metadata", - return_value=self.metadata(), - ), - patch.object(bundle, "load_safetensors_model") as strict_load, - redirect_stdout(io.StringIO()), + self.subTest(role=role, artifact_path=artifact_path), + tempfile.TemporaryDirectory() as directory, ): - manifest_path = bundle.build_bundle(args) - - download.assert_called_once_with( - "run-id", "model/final_model.safetensors" - ) - strict_load.assert_called_once_with(artifact.resolve(), device="cpu") - declaration = json.loads(manifest_path.read_text()) - self.assertEqual( - set(declaration), - {"schema_version", "model_type", "members", "bundle_sha256"}, - ) - self.assertEqual( - set(declaration["members"][0]), - {"member_id", "sha256"}, - ) - self.assertEqual(declaration["schema_version"], 1) - self.assertEqual(declaration["model_type"], "unet") - self.assertTrue((manifest_path.parent / "all_data.safetensors").is_file()) - self.assertEqual(manifest_path.parent.stat().st_mode & 0o777, 0o750) + root = Path(directory) + artifact = root / "downloaded.safetensors" + artifact.touch() + output = root / "bundle" + output.mkdir() + output.chmod(0o750) + args = self.parse_run(role, "--out", str(output), *extra) + with ( + patch.object( + bundle, "_download_run_artifact", return_value=artifact + ) as download, + patch.object( + bundle, + "read_safetensors_metadata", + return_value=self.metadata(role=role), + ), + patch.object(bundle, "load_safetensors_model"), + redirect_stdout(io.StringIO()), + ): + manifest_path = bundle.build_bundle(args) + download.assert_called_once_with("run-id", artifact_path) + declaration = json.loads(manifest_path.read_text()) + self.assertEqual( + set(declaration), + {"schema_version", "model_type", "members", "bundle_sha256"}, + ) + self.assertEqual( + set(declaration["members"][0]), {"member_id", "sha256"} + ) + self.assertEqual(declaration["schema_version"], 1) + self.assertEqual(declaration["model_type"], "unet") + self.assertTrue((output / "all_data.safetensors").is_file()) + self.assertEqual(output.stat().st_mode & 0o777, 0o750) def build_local_manifest( self, @@ -376,10 +352,14 @@ def build_local_manifest( artifact = root / f"source-{output_name}.safetensors" artifact.write_bytes(model_bytes) argv = [ - "--model-type", "unet", - "--artifact", f"{member_id}={artifact}", - "--artifact-role", "final", - "--out", str(root / output_name), + "--model-type", + "unet", + "--artifact", + f"{member_id}={artifact}", + "--artifact-role", + "final", + "--out", + str(root / output_name), ] if prefix is not None: argv.extend(["--dicom-uid-prefix", prefix]) @@ -435,12 +415,17 @@ def test_copy_failure_leaves_no_partial_bundle_or_staging_directory(self): artifact.write_bytes(member.encode()) sources.extend(["--artifact", f"{member}={artifact}"]) output = root / "bundle" - args = bundle.make_parser().parse_args([ - "--model-type", "unet", - *sources, - "--artifact-role", "final", - "--out", str(output), - ]) + args = bundle.make_parser().parse_args( + [ + "--model-type", + "unet", + *sources, + "--artifact-role", + "final", + "--out", + str(output), + ] + ) real_copy = bundle.shutil.copy2 copy_count = 0 @@ -476,12 +461,18 @@ def test_manifest_failure_leaves_preexisting_empty_target_empty(self): artifact.write_bytes(b"model") output = root / "bundle" output.mkdir() - args = bundle.make_parser().parse_args([ - "--model-type", "unet", - "--artifact", f"all_data={artifact}", - "--artifact-role", "final", - "--out", str(output), - ]) + args = bundle.make_parser().parse_args( + [ + "--model-type", + "unet", + "--artifact", + f"all_data={artifact}", + "--artifact-role", + "final", + "--out", + str(output), + ] + ) with ( patch.object( bundle, @@ -503,12 +494,18 @@ def test_metadata_role_mismatch_is_rejected(self): root = Path(directory) artifact = root / "model.safetensors" artifact.touch() - args = bundle.make_parser().parse_args([ - "--model-type", "unet", - "--artifact", f"all_data={artifact}", - "--artifact-role", "final", - "--out", str(root / "bundle"), - ]) + args = bundle.make_parser().parse_args( + [ + "--model-type", + "unet", + "--artifact", + f"all_data={artifact}", + "--artifact-role", + "final", + "--out", + str(root / "bundle"), + ] + ) with patch.object( bundle, "read_safetensors_metadata", diff --git a/research/vestibular_schwannoma/tests/workflow/test_data_preparation.py b/research/vestibular_schwannoma/tests/workflow/test_data_preparation.py index 4763379..67881f6 100644 --- a/research/vestibular_schwannoma/tests/workflow/test_data_preparation.py +++ b/research/vestibular_schwannoma/tests/workflow/test_data_preparation.py @@ -255,10 +255,9 @@ def test_download_preflights_retriever_before_network(monkeypatch, capsys): def bundle_in_project(study, monkeypatch): index, frozen, root, *_ = study project = index.parent.parent - archive = project / "data/releases/vs_corrected_labels_v1.zip" + archive = project / "data/corrected_labels/vs_corrected_labels_v1.zip" archive.parent.mkdir() - asset = build_label_bundle(index, frozen, root, archive) - write_json(project / "data/corrected_labels.json", asset) + build_label_bundle(index, frozen, root, archive) monkeypatch.setattr(cli, "PROJECT_ROOT", project) return archive @@ -268,7 +267,7 @@ def test_labels_command_extracts_bundled_masks_without_network( ): archive = bundle_in_project(study, monkeypatch) monkeypatch.setattr( - cli, "download_file", lambda *a, **k: pytest.fail("Network used") + "workflow.data_download.urlopen", lambda *a, **k: pytest.fail("Network used") ) assert main(["download", "--dataset", "labels"]) == 0 extracted = cli.PROJECT_ROOT / "data/raw/corrected_labels" @@ -282,9 +281,9 @@ def test_corrupt_bundled_archive_is_rejected_before_extraction( study, monkeypatch, capsys ): archive = bundle_in_project(study, monkeypatch) - archive.write_bytes(archive.read_bytes() + b"unexpected bytes") + archive.write_bytes(archive.read_bytes()[:-22]) assert main(["download", "--dataset", "labels"]) == 1 - assert "archive differs" in capsys.readouterr().err + assert "not a zip file" in capsys.readouterr().err assert not (cli.PROJECT_ROOT / "data/raw/corrected_labels").exists()