panseg 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- panseg/artifacts.py +109 -0
- panseg/cli.py +176 -0
- panseg/constants.py +9 -0
- panseg/datasets.py +162 -0
- panseg/infer.py +298 -0
- panseg/metrics.py +105 -0
- panseg/models/__init__.py +0 -0
- panseg/models/common.py +81 -0
- panseg/models/monai_dints.py +217 -0
- panseg/models/mrsegmentator.py +86 -0
- panseg/models/nnunet.py +374 -0
- panseg/models/totalseg.py +84 -0
- panseg/pansegai.py +81 -0
- panseg/pipeline.py +178 -0
- panseg/utils.py +157 -0
- panseg/viewer.py +32 -0
- panseg-0.1.0.dist-info/METADATA +244 -0
- panseg-0.1.0.dist-info/RECORD +20 -0
- panseg-0.1.0.dist-info/WHEEL +4 -0
- panseg-0.1.0.dist-info/entry_points.txt +2 -0
panseg/artifacts.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Load timestamped .eval-artifacts dumps produced by panseg infer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
11
|
+
DEFAULT_ARTIFACTS_DIR = REPO_ROOT / ".eval-artifacts"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def list_eval_runs(root: Path = DEFAULT_ARTIFACTS_DIR) -> list[Path]:
|
|
15
|
+
if not root.is_dir():
|
|
16
|
+
return []
|
|
17
|
+
runs = [p for p in root.iterdir() if p.is_dir() and (p / "manifest.json").is_file()]
|
|
18
|
+
return sorted(runs, key=lambda p: p.name, reverse=True)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_manifest(run_dir: Path) -> dict:
|
|
22
|
+
path = run_dir / "manifest.json"
|
|
23
|
+
if not path.is_file():
|
|
24
|
+
raise FileNotFoundError(f"No manifest.json in {run_dir}")
|
|
25
|
+
return json.loads(path.read_text())
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def as_3d(data: np.ndarray) -> np.ndarray:
|
|
29
|
+
while data.ndim > 3:
|
|
30
|
+
data = data[..., 0]
|
|
31
|
+
if data.ndim != 3:
|
|
32
|
+
raise ValueError(f"Expected a 3D volume, got shape {data.shape}")
|
|
33
|
+
return data
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def load_nifti_3d(path: Path) -> np.ndarray:
|
|
37
|
+
import nibabel as nib
|
|
38
|
+
|
|
39
|
+
image = nib.as_closest_canonical(nib.load(path))
|
|
40
|
+
return as_3d(np.asarray(image.dataobj))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_eval_artifact(run_dir: Path) -> dict:
|
|
44
|
+
run_dir = Path(run_dir)
|
|
45
|
+
manifest = load_manifest(run_dir)
|
|
46
|
+
image_name = manifest.get("image")
|
|
47
|
+
if not image_name:
|
|
48
|
+
raise FileNotFoundError(f"{run_dir} manifest is missing image")
|
|
49
|
+
image_path = run_dir / image_name
|
|
50
|
+
if not image_path.is_file():
|
|
51
|
+
raise FileNotFoundError(f"Image not found: {image_path}")
|
|
52
|
+
|
|
53
|
+
gt_name = manifest.get("ground_truth")
|
|
54
|
+
gt_path = run_dir / gt_name if gt_name else None
|
|
55
|
+
if gt_path is not None and not gt_path.is_file():
|
|
56
|
+
gt_path = None
|
|
57
|
+
|
|
58
|
+
masks: list[dict] = []
|
|
59
|
+
for entry in manifest.get("models", []):
|
|
60
|
+
if entry.get("status") != "ok" or not entry.get("mask"):
|
|
61
|
+
continue
|
|
62
|
+
mask_path = run_dir / entry["mask"]
|
|
63
|
+
if mask_path.is_file():
|
|
64
|
+
masks.append({"name": entry.get("name", mask_path.stem), "path": mask_path})
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
"run_dir": run_dir,
|
|
68
|
+
"manifest": manifest,
|
|
69
|
+
"image_path": image_path,
|
|
70
|
+
"ground_truth_path": gt_path,
|
|
71
|
+
"masks": masks,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def window_slice(volume: np.ndarray, index: int) -> np.ndarray:
|
|
76
|
+
sl = volume[:, :, index].astype(np.float32)
|
|
77
|
+
lo, hi = np.percentile(sl, [0.5, 99.5])
|
|
78
|
+
if hi <= lo:
|
|
79
|
+
hi = lo + 1.0
|
|
80
|
+
scaled = np.clip((sl - lo) / (hi - lo), 0.0, 1.0)
|
|
81
|
+
return scaled.T
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def overlay_mask(base: np.ndarray, mask: np.ndarray, color: tuple[float, float, float], alpha: float = 0.45) -> np.ndarray:
|
|
85
|
+
rgb = np.stack([base, base, base], axis=-1)
|
|
86
|
+
return overlay_on_rgb(rgb, mask, color, alpha)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def overlay_on_rgb(
|
|
90
|
+
rgb: np.ndarray,
|
|
91
|
+
mask: np.ndarray,
|
|
92
|
+
color: tuple[float, float, float],
|
|
93
|
+
alpha: float = 0.45,
|
|
94
|
+
) -> np.ndarray:
|
|
95
|
+
out = np.array(rgb, dtype=np.float32, copy=True)
|
|
96
|
+
hit = mask.T > 0
|
|
97
|
+
tint = np.array(color, dtype=np.float32)
|
|
98
|
+
out[hit] = (1.0 - alpha) * out[hit] + alpha * tint
|
|
99
|
+
return np.clip(out, 0.0, 1.0)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def dice_score(pred: np.ndarray, target: np.ndarray) -> float:
|
|
103
|
+
pred_b = pred > 0
|
|
104
|
+
target_b = target > 0
|
|
105
|
+
intersection = np.logical_and(pred_b, target_b).sum()
|
|
106
|
+
denom = pred_b.sum() + target_b.sum()
|
|
107
|
+
if denom == 0:
|
|
108
|
+
return float("nan")
|
|
109
|
+
return float(2.0 * intersection / denom)
|
panseg/cli.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from panseg.infer import (
|
|
8
|
+
DEFAULT_PREDICTION_ARTIFACTS_DIR,
|
|
9
|
+
run_hf_inference,
|
|
10
|
+
)
|
|
11
|
+
from panseg.viewer import launch_viewer
|
|
12
|
+
from panseg.pipeline import PansegLightningModule
|
|
13
|
+
from panseg.utils import DATASET_PARQUETS, DATASETS, MODELS, build_model, download_dataset
|
|
14
|
+
|
|
15
|
+
app = typer.Typer()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.callback(invoke_without_command=True)
|
|
19
|
+
def main(ctx: typer.Context) -> None:
|
|
20
|
+
if ctx.invoked_subcommand is None:
|
|
21
|
+
print("Hello, welcome to panseg!")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command()
|
|
25
|
+
def download(
|
|
26
|
+
dataset: str = typer.Option(
|
|
27
|
+
...,
|
|
28
|
+
"--dataset",
|
|
29
|
+
"-d",
|
|
30
|
+
help="Dataset name, e.g. medical-decathlon-task07",
|
|
31
|
+
),
|
|
32
|
+
output_dir: Path = typer.Option(
|
|
33
|
+
...,
|
|
34
|
+
"--output-dir",
|
|
35
|
+
"-o",
|
|
36
|
+
help="Directory to download the dataset into",
|
|
37
|
+
),
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Download a registered Kaggle dataset."""
|
|
40
|
+
try:
|
|
41
|
+
download_dataset(dataset, output_dir)
|
|
42
|
+
except ValueError as exc:
|
|
43
|
+
raise typer.BadParameter(str(exc), param_hint="--dataset") from exc
|
|
44
|
+
print(f"Downloaded '{dataset}' to {output_dir}")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.command("eval")
|
|
48
|
+
def evaluate(
|
|
49
|
+
parquet: Path | None = typer.Option(
|
|
50
|
+
None,
|
|
51
|
+
"--parquet",
|
|
52
|
+
help="Dataset manifest parquet (defaults to the dataset's parquet)",
|
|
53
|
+
file_okay=True,
|
|
54
|
+
dir_okay=False,
|
|
55
|
+
path_type=Path,
|
|
56
|
+
),
|
|
57
|
+
modality: Literal["ct", "mri"] = typer.Option(
|
|
58
|
+
...,
|
|
59
|
+
"--modality",
|
|
60
|
+
"-m",
|
|
61
|
+
help="Imaging modality",
|
|
62
|
+
),
|
|
63
|
+
model: str = typer.Option(
|
|
64
|
+
...,
|
|
65
|
+
"--model",
|
|
66
|
+
help="Model name, e.g. totalseg, dints, mrseg, or nnunet",
|
|
67
|
+
),
|
|
68
|
+
dataset: str = typer.Option(
|
|
69
|
+
...,
|
|
70
|
+
"--dataset",
|
|
71
|
+
help="Dataset name, e.g. msd-pancreas, amos-pancreas, amos-ct-pancreas, panseg-t1-pancreas, or panseg-t2-pancreas",
|
|
72
|
+
),
|
|
73
|
+
checkpoint: Path | None = typer.Option(
|
|
74
|
+
None,
|
|
75
|
+
"--checkpoint",
|
|
76
|
+
help="nnU-Net checkpoint.pth or trainer folder (required for --model nnunet)",
|
|
77
|
+
exists=True,
|
|
78
|
+
file_okay=True,
|
|
79
|
+
dir_okay=True,
|
|
80
|
+
path_type=Path,
|
|
81
|
+
),
|
|
82
|
+
output: Path | None = typer.Option(
|
|
83
|
+
None,
|
|
84
|
+
"--output",
|
|
85
|
+
help="Write one row of metrics per evaluated case to this CSV",
|
|
86
|
+
file_okay=True,
|
|
87
|
+
dir_okay=False,
|
|
88
|
+
path_type=Path,
|
|
89
|
+
),
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Run evaluation on the test split of a parquet manifest."""
|
|
92
|
+
if model not in MODELS:
|
|
93
|
+
known = ", ".join(sorted(MODELS))
|
|
94
|
+
raise typer.BadParameter(
|
|
95
|
+
f"Unknown model '{model}'. Known models: {known}",
|
|
96
|
+
param_hint="--model",
|
|
97
|
+
)
|
|
98
|
+
if dataset not in DATASETS:
|
|
99
|
+
known = ", ".join(sorted(DATASETS))
|
|
100
|
+
raise typer.BadParameter(
|
|
101
|
+
f"Unknown dataset '{dataset}'. Known datasets: {known}",
|
|
102
|
+
param_hint="--dataset",
|
|
103
|
+
)
|
|
104
|
+
try:
|
|
105
|
+
parquet_path = parquet or DATASET_PARQUETS[dataset]
|
|
106
|
+
module = PansegLightningModule(
|
|
107
|
+
build_model(model, modality, checkpoint_path=checkpoint)
|
|
108
|
+
)
|
|
109
|
+
module.run_eval(parquet_path, dataset, output_csv=output)
|
|
110
|
+
except ValueError as exc:
|
|
111
|
+
raise typer.BadParameter(str(exc)) from exc
|
|
112
|
+
except FileNotFoundError as exc:
|
|
113
|
+
raise typer.BadParameter(str(exc), param_hint="--parquet") from exc
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@app.command("infer")
|
|
117
|
+
def infer(
|
|
118
|
+
input_path: Path = typer.Option(
|
|
119
|
+
...,
|
|
120
|
+
"--input",
|
|
121
|
+
"-i",
|
|
122
|
+
help="Path to a NIfTI volume (.nii / .nii.gz)",
|
|
123
|
+
exists=True,
|
|
124
|
+
file_okay=True,
|
|
125
|
+
dir_okay=False,
|
|
126
|
+
path_type=Path,
|
|
127
|
+
),
|
|
128
|
+
checkpoint: str | None = typer.Option(
|
|
129
|
+
None,
|
|
130
|
+
"--checkpoint",
|
|
131
|
+
help=(
|
|
132
|
+
"Optional Hugging Face file path in "
|
|
133
|
+
"dillondesilva/comp3888-capstone-panseg-models. "
|
|
134
|
+
"Defaults to the CT or MRI annealing checkpoint for the resolved modality."
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
modality: Literal["ct", "mri"] | None = typer.Option(
|
|
138
|
+
None,
|
|
139
|
+
"--modality",
|
|
140
|
+
"-m",
|
|
141
|
+
help="Override the intensity-based CT/MRI routing",
|
|
142
|
+
),
|
|
143
|
+
accelerator: Literal["auto", "cpu", "cuda", "xpu", "mps"] = typer.Option(
|
|
144
|
+
"auto",
|
|
145
|
+
"--accelerator",
|
|
146
|
+
"-a",
|
|
147
|
+
help="Device used to run the checkpoint",
|
|
148
|
+
),
|
|
149
|
+
artifacts_dir: Path = typer.Option(
|
|
150
|
+
DEFAULT_PREDICTION_ARTIFACTS_DIR,
|
|
151
|
+
"--artifacts-dir",
|
|
152
|
+
help="Root directory for timestamped prediction dumps",
|
|
153
|
+
file_okay=False,
|
|
154
|
+
dir_okay=True,
|
|
155
|
+
path_type=Path,
|
|
156
|
+
),
|
|
157
|
+
) -> None:
|
|
158
|
+
"""Download an HF nnU-Net checkpoint, segment one volume, and write a prediction artifact."""
|
|
159
|
+
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
|
|
160
|
+
try:
|
|
161
|
+
run_dir = run_hf_inference(
|
|
162
|
+
input_path,
|
|
163
|
+
modality=modality,
|
|
164
|
+
checkpoint=checkpoint,
|
|
165
|
+
accelerator=accelerator,
|
|
166
|
+
artifacts_root=artifacts_dir,
|
|
167
|
+
)
|
|
168
|
+
except (FileNotFoundError, ValueError, ImportError, RuntimeError) as exc:
|
|
169
|
+
raise typer.BadParameter(str(exc)) from exc
|
|
170
|
+
print(f"saved prediction artifact: {run_dir}")
|
|
171
|
+
if typer.confirm("Launch napari on the prediction artifact?", default=True):
|
|
172
|
+
launch_viewer(run_dir)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
if __name__ == "__main__":
|
|
176
|
+
app()
|
panseg/constants.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Shared panseg constants."""
|
|
2
|
+
|
|
3
|
+
# Air on the Hounsfield scale, and a bright-voxel floor that CT clears and typical MRI does not.
|
|
4
|
+
CT_AIR_HU = -1000.0
|
|
5
|
+
CT_P99_MIN_HU = 400.0
|
|
6
|
+
|
|
7
|
+
HF_MODELS_REPO = "dillondesilva/comp3888-capstone-panseg-models"
|
|
8
|
+
DEFAULT_HF_CT_CHECKPOINT = "ct/ct_final_ckpt_c2b_annealing_aggressive.pth"
|
|
9
|
+
DEFAULT_HF_MRI_CHECKPOINT = "mri/mri_final_ckpt_c2b_annealing_aggressive.pth"
|
panseg/datasets.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from collections.abc import Sequence
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import nibabel as nib
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import torch
|
|
8
|
+
from torch.utils.data import Dataset
|
|
9
|
+
|
|
10
|
+
_REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
11
|
+
DEFAULT_PARQUET = _REPO_ROOT / "dataset_generation" / "output" / "ct_pancreas.parquet"
|
|
12
|
+
MRI_PARQUET = _REPO_ROOT / "dataset_generation" / "output" / "mri_pancreas.parquet"
|
|
13
|
+
_REQUIRED_COLUMNS = {
|
|
14
|
+
"dataset",
|
|
15
|
+
"case_id",
|
|
16
|
+
"image_path",
|
|
17
|
+
"label_path",
|
|
18
|
+
"label_value",
|
|
19
|
+
"split",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def resolve_volume_path(rel: str, repo_root: Path) -> Path:
|
|
24
|
+
path = Path(repo_root) / rel
|
|
25
|
+
if path.is_file():
|
|
26
|
+
return path
|
|
27
|
+
if rel.startswith("datasets/"):
|
|
28
|
+
alt = Path(repo_root) / rel.removeprefix("datasets/")
|
|
29
|
+
if alt.is_file():
|
|
30
|
+
return alt
|
|
31
|
+
return path
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ParquetPancreasDataset(Dataset):
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
parquet_path: Path = DEFAULT_PARQUET,
|
|
38
|
+
source_dataset: str | Sequence[str] = "msd",
|
|
39
|
+
split: str | None = "test",
|
|
40
|
+
repo_root: Path = _REPO_ROOT,
|
|
41
|
+
) -> None:
|
|
42
|
+
parquet_path = Path(parquet_path)
|
|
43
|
+
if not parquet_path.is_file():
|
|
44
|
+
raise FileNotFoundError(f"Parquet not found: {parquet_path}")
|
|
45
|
+
|
|
46
|
+
df = pd.read_parquet(parquet_path)
|
|
47
|
+
missing = _REQUIRED_COLUMNS - set(df.columns)
|
|
48
|
+
if missing:
|
|
49
|
+
raise ValueError(f"Parquet is missing columns: {sorted(missing)}")
|
|
50
|
+
|
|
51
|
+
if isinstance(source_dataset, str):
|
|
52
|
+
sources = (source_dataset,)
|
|
53
|
+
else:
|
|
54
|
+
sources = tuple(source_dataset)
|
|
55
|
+
df = df[df["dataset"].isin(sources)]
|
|
56
|
+
if split is not None:
|
|
57
|
+
df = df[df["split"] == split]
|
|
58
|
+
if df.empty:
|
|
59
|
+
qualifier = f" with split={split!r}" if split is not None else ""
|
|
60
|
+
label = ", ".join(sources)
|
|
61
|
+
raise FileNotFoundError(
|
|
62
|
+
f"No '{label}' rows{qualifier} in {parquet_path}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
self.cases: list[tuple[str, Path, Path, tuple[int, ...]]] = []
|
|
66
|
+
for row in df.itertuples(index=False):
|
|
67
|
+
image_path = resolve_volume_path(row.image_path, repo_root)
|
|
68
|
+
label_path = resolve_volume_path(row.label_path, repo_root)
|
|
69
|
+
if not image_path.is_file():
|
|
70
|
+
raise FileNotFoundError(f"Image not found: {image_path}")
|
|
71
|
+
if not label_path.is_file():
|
|
72
|
+
raise FileNotFoundError(f"Label not found: {label_path}")
|
|
73
|
+
extra = json.loads(getattr(row, "label_extra_values", None) or "[]")
|
|
74
|
+
values = (int(row.label_value), *(int(v) for v in extra))
|
|
75
|
+
self.cases.append((row.case_id, image_path, label_path, values))
|
|
76
|
+
|
|
77
|
+
def __len__(self) -> int:
|
|
78
|
+
return len(self.cases)
|
|
79
|
+
|
|
80
|
+
def __getitem__(self, index: int) -> dict[str, torch.Tensor | str]:
|
|
81
|
+
name, image_path, label_path, label_values = self.cases[index]
|
|
82
|
+
image = nib.load(image_path)
|
|
83
|
+
volume = torch.from_numpy(image.get_fdata().astype("float32"))
|
|
84
|
+
label = torch.from_numpy(nib.load(label_path).get_fdata())
|
|
85
|
+
mask = torch.isin(label, torch.tensor(label_values, dtype=label.dtype)).to(dtype=torch.float32)
|
|
86
|
+
affine = torch.tensor(image.affine, dtype=torch.float32)
|
|
87
|
+
return {"volume": volume, "mask": mask, "affine": affine, "name": name}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class MSDPancreasDataset(ParquetPancreasDataset):
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
parquet_path: Path = DEFAULT_PARQUET,
|
|
94
|
+
split: str | None = "test",
|
|
95
|
+
repo_root: Path = _REPO_ROOT,
|
|
96
|
+
) -> None:
|
|
97
|
+
super().__init__(
|
|
98
|
+
parquet_path=parquet_path,
|
|
99
|
+
source_dataset="msd",
|
|
100
|
+
split=split,
|
|
101
|
+
repo_root=repo_root,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class AMOSPancreasDataset(ParquetPancreasDataset):
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
parquet_path: Path = MRI_PARQUET,
|
|
109
|
+
split: str | None = "test",
|
|
110
|
+
repo_root: Path = _REPO_ROOT,
|
|
111
|
+
) -> None:
|
|
112
|
+
super().__init__(
|
|
113
|
+
parquet_path=parquet_path,
|
|
114
|
+
source_dataset="amos",
|
|
115
|
+
split=split,
|
|
116
|
+
repo_root=repo_root,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class AMOSCTPancreasDataset(ParquetPancreasDataset):
|
|
121
|
+
def __init__(
|
|
122
|
+
self,
|
|
123
|
+
parquet_path: Path = DEFAULT_PARQUET,
|
|
124
|
+
split: str | None = "test",
|
|
125
|
+
repo_root: Path = _REPO_ROOT,
|
|
126
|
+
) -> None:
|
|
127
|
+
super().__init__(
|
|
128
|
+
parquet_path=parquet_path,
|
|
129
|
+
source_dataset="amos",
|
|
130
|
+
split=split,
|
|
131
|
+
repo_root=repo_root,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class PanSegT1Dataset(ParquetPancreasDataset):
|
|
136
|
+
def __init__(
|
|
137
|
+
self,
|
|
138
|
+
parquet_path: Path = MRI_PARQUET,
|
|
139
|
+
split: str | None = "test",
|
|
140
|
+
repo_root: Path = _REPO_ROOT,
|
|
141
|
+
) -> None:
|
|
142
|
+
super().__init__(
|
|
143
|
+
parquet_path=parquet_path,
|
|
144
|
+
source_dataset="panseg_t1",
|
|
145
|
+
split=split,
|
|
146
|
+
repo_root=repo_root,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class PanSegT2Dataset(ParquetPancreasDataset):
|
|
151
|
+
def __init__(
|
|
152
|
+
self,
|
|
153
|
+
parquet_path: Path = MRI_PARQUET,
|
|
154
|
+
split: str | None = "test",
|
|
155
|
+
repo_root: Path = _REPO_ROOT,
|
|
156
|
+
) -> None:
|
|
157
|
+
super().__init__(
|
|
158
|
+
parquet_path=parquet_path,
|
|
159
|
+
source_dataset="panseg_t2",
|
|
160
|
+
split=split,
|
|
161
|
+
repo_root=repo_root,
|
|
162
|
+
)
|