isaac commited on
Commit ·
d8a2ed8
1
Parent(s): 978cbc0
Accept NRRD/MetaImage volumes via SimpleITK normalize-to-NIfTI
Browse files
app.py
CHANGED
|
@@ -32,6 +32,7 @@ os.environ.setdefault("CONFIGS_ROOT", CKPT_DIR)
|
|
| 32 |
|
| 33 |
import sys
|
| 34 |
sys.path.insert(0, os.path.join(ROOT, "RADAR_inference")) # vendored absolute imports (dynamic_network_architectures) resolve from here
|
|
|
|
| 35 |
import pandas as pd # noqa: E402
|
| 36 |
import spaces # noqa: E402
|
| 37 |
import gradio as gr # noqa: E402
|
|
@@ -77,22 +78,17 @@ def _english_name(column: str) -> str:
|
|
| 77 |
def diagnose(ct_file):
|
| 78 |
"""Run RADAR on one uploaded contrast-enhanced abdominal CT volume."""
|
| 79 |
path = ct_file if isinstance(ct_file, str) else ct_file.name
|
| 80 |
-
if
|
| 81 |
-
raise gr.Error("
|
| 82 |
-
try:
|
| 83 |
-
import nibabel as nib
|
| 84 |
-
|
| 85 |
-
nib.load(path)
|
| 86 |
-
except Exception:
|
| 87 |
-
raise gr.Error("upload a .nii or .nii.gz CT volume")
|
| 88 |
-
|
| 89 |
tmpdir = tempfile.mkdtemp(prefix="radar_case_")
|
| 90 |
outdir = tempfile.mkdtemp(prefix="radar_out_")
|
| 91 |
try:
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
| 96 |
try:
|
| 97 |
evaluate(pad_func, model, tmpdir, outdir, "space")
|
| 98 |
except (OSError, ValueError, RuntimeError) as exc:
|
|
@@ -119,8 +115,8 @@ demo = gr.Interface(
|
|
| 119 |
fn=diagnose,
|
| 120 |
inputs=gr.File(
|
| 121 |
# ".gz" not ".nii.gz": Gradio matches Path.suffix ("gz"), so the compound form never validates.
|
| 122 |
-
# diagnose() still enforces
|
| 123 |
-
label="Abdominal CT (
|
| 124 |
),
|
| 125 |
outputs=[gr.Label(label="Top findings", num_top_classes=10), gr.Dataframe(label="All finding scores")],
|
| 126 |
title="RADAR Abdominal CT",
|
|
|
|
| 32 |
|
| 33 |
import sys
|
| 34 |
sys.path.insert(0, os.path.join(ROOT, "RADAR_inference")) # vendored absolute imports (dynamic_network_architectures) resolve from here
|
| 35 |
+
from intake import _to_nifti # noqa: E402
|
| 36 |
import pandas as pd # noqa: E402
|
| 37 |
import spaces # noqa: E402
|
| 38 |
import gradio as gr # noqa: E402
|
|
|
|
| 78 |
def diagnose(ct_file):
|
| 79 |
"""Run RADAR on one uploaded contrast-enhanced abdominal CT volume."""
|
| 80 |
path = ct_file if isinstance(ct_file, str) else ct_file.name
|
| 81 |
+
if path.lower().endswith(".dcm"):
|
| 82 |
+
raise gr.Error("a single .dcm is one slice, not a volume: upload the full series")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
tmpdir = tempfile.mkdtemp(prefix="radar_case_")
|
| 84 |
outdir = tempfile.mkdtemp(prefix="radar_out_")
|
| 85 |
try:
|
| 86 |
+
src = _to_nifti(path, tmpdir)
|
| 87 |
+
fname = os.path.basename(src)
|
| 88 |
+
if src != os.path.join(tmpdir, fname):
|
| 89 |
+
if not fname.endswith((".nii", ".nii.gz")):
|
| 90 |
+
fname += ".nii.gz"
|
| 91 |
+
shutil.copy(src, os.path.join(tmpdir, fname))
|
| 92 |
try:
|
| 93 |
evaluate(pad_func, model, tmpdir, outdir, "space")
|
| 94 |
except (OSError, ValueError, RuntimeError) as exc:
|
|
|
|
| 115 |
fn=diagnose,
|
| 116 |
inputs=gr.File(
|
| 117 |
# ".gz" not ".nii.gz": Gradio matches Path.suffix ("gz"), so the compound form never validates.
|
| 118 |
+
# diagnose() still enforces volume suffix + parseable content server-side.
|
| 119 |
+
label="Abdominal CT (NIfTI / NRRD / MetaImage)", file_types=[".nii", ".gz", ".nrrd", ".nhdr", ".mha", ".mhd"]
|
| 120 |
),
|
| 121 |
outputs=[gr.Label(label="Top findings", num_top_classes=10), gr.Dataframe(label="All finding scores")],
|
| 122 |
title="RADAR Abdominal CT",
|
intake.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Upload intake: normalize NIfTI / NRRD / MetaImage / DICOM series to .nii.gz.
|
| 2 |
+
|
| 3 |
+
Downstream (RADAR_inference DataFolder/evaluate) only ever sees one .nii.gz,
|
| 4 |
+
so the vendored upstream chain stays untouched. No new dependencies:
|
| 5 |
+
SimpleITK and nibabel are already required.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
import gradio as gr
|
| 11 |
+
|
| 12 |
+
VOLUME_SUFFIXES = (".nii", ".nii.gz", ".nrrd", ".nhdr", ".mha", ".mhd")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _to_nifti(path: str, workdir: str) -> str:
|
| 16 |
+
"""Normalize one single-file volume to .nii.gz via SimpleITK."""
|
| 17 |
+
import SimpleITK as sitk
|
| 18 |
+
|
| 19 |
+
low = path.lower()
|
| 20 |
+
if low.endswith((".nii", ".nii.gz")):
|
| 21 |
+
import nibabel as nib
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
nib.load(path)
|
| 25 |
+
except Exception:
|
| 26 |
+
raise gr.Error("upload a .nii or .nii.gz CT volume")
|
| 27 |
+
return path
|
| 28 |
+
if not low.endswith((".nrrd", ".nhdr", ".mha", ".mhd")):
|
| 29 |
+
raise gr.Error("upload a .nii.gz, .nrrd/.nhdr, .mha/.mhd volume, or a DICOM series")
|
| 30 |
+
try:
|
| 31 |
+
img = sitk.ReadImage(path)
|
| 32 |
+
except Exception:
|
| 33 |
+
raise gr.Error(f"could not read {os.path.basename(path)} as an image volume")
|
| 34 |
+
if img.GetDimension() != 3 or min(img.GetSize()) < 1:
|
| 35 |
+
raise gr.Error(f"{os.path.basename(path)} is not a 3D volume")
|
| 36 |
+
out = os.path.join(workdir, "case.nii.gz")
|
| 37 |
+
sitk.WriteImage(img, out)
|
| 38 |
+
return out
|