Spaces:
Runtime error
Runtime error
first commit
Browse files- app.py +165 -0
- input_examples/ct1.nii.gz +3 -0
- input_examples/ct2.nii.gz +3 -0
- input_examples/ct3.nii.gz +3 -0
- output_examples/ct1_seg.nii.gz +3 -0
- output_examples/ct2_seg.nii.gz +3 -0
- output_examples/ct3_seg.nii.gz +3 -0
- requirements.txt +3 -0
- segmap.py +120 -0
app.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import spaces
|
| 2 |
+
import tempfile
|
| 3 |
+
import os
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
import SimpleITK as sitk
|
| 6 |
+
import numpy as np
|
| 7 |
+
import nibabel as nib
|
| 8 |
+
from totalsegmentator.python_api import totalsegmentator
|
| 9 |
+
import gradio as gr
|
| 10 |
+
from segmap import seg_map
|
| 11 |
+
import logging
|
| 12 |
+
|
| 13 |
+
# Logging configuration
|
| 14 |
+
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
| 15 |
+
logger = logging.getLogger(__name__)
|
| 16 |
+
|
| 17 |
+
sample_files = ["ct1.nii.gz", "ct2.nii.gz", "ct3.nii.gz"]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def map_labels(seg_array):
|
| 21 |
+
labels = []
|
| 22 |
+
count = 0
|
| 23 |
+
logger.debug("unique segs:")
|
| 24 |
+
logger.debug(str(len(np.unique(seg_array))))
|
| 25 |
+
for seg_class in np.unique(seg_array):
|
| 26 |
+
if seg_class == 0:
|
| 27 |
+
continue
|
| 28 |
+
labels.append((seg_array == seg_class, seg_map[seg_class]))
|
| 29 |
+
count += 1
|
| 30 |
+
|
| 31 |
+
return labels
|
| 32 |
+
|
| 33 |
+
def sitk_to_numpy(img_sitk, norm=False):
|
| 34 |
+
img_sitk = sitk.DICOMOrient(img_sitk, "LPS")
|
| 35 |
+
img_np = sitk.GetArrayFromImage(img_sitk)
|
| 36 |
+
if norm:
|
| 37 |
+
min_val, max_val = np.min(img_np), np.max(img_np)
|
| 38 |
+
img_np = ((img_np - min_val) / (max_val - min_val)).clip(0, 1) * 255
|
| 39 |
+
img_np = img_np.astype(np.uint8)
|
| 40 |
+
return img_np
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def load_image(path, norm=False):
|
| 44 |
+
img_sitk = sitk.ReadImage(path)
|
| 45 |
+
return sitk_to_numpy(img_sitk, norm)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def show_img_seg(img_np, seg_np=None, slice_idx=50):
|
| 49 |
+
if img_np is None or (isinstance(img_np, list) and len(img_np) == 0):
|
| 50 |
+
return None
|
| 51 |
+
if isinstance(img_np, list):
|
| 52 |
+
img_np = img_np[-1]
|
| 53 |
+
slice_pos = int(slice_idx * (img_np.shape[0] / 100))
|
| 54 |
+
img_slice = img_np[slice_pos, :, :]
|
| 55 |
+
|
| 56 |
+
if seg_np is None or (isinstance(seg_np, list) and len(seg_np) == 0):
|
| 57 |
+
seg_np = []
|
| 58 |
+
else:
|
| 59 |
+
if isinstance(seg_np, list):
|
| 60 |
+
seg_np = seg_np[-1]
|
| 61 |
+
seg_np = map_labels(seg_np[slice_pos, :, :])
|
| 62 |
+
|
| 63 |
+
return img_slice, seg_np
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def load_img_to_state(path, img_state, seg_state):
|
| 67 |
+
img_state.clear()
|
| 68 |
+
seg_state.clear()
|
| 69 |
+
|
| 70 |
+
if path:
|
| 71 |
+
img_np = load_image(path, norm=True)
|
| 72 |
+
img_state.append(img_np)
|
| 73 |
+
return None, img_state, seg_state
|
| 74 |
+
else:
|
| 75 |
+
return None, img_state, seg_state
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def save_seg(seg, path):
|
| 79 |
+
if Path(path).name in sample_files:
|
| 80 |
+
path = os.path.join("output_examples", f"{Path(Path(path).stem).stem}_seg.nii.gz")
|
| 81 |
+
else:
|
| 82 |
+
sitk.WriteImage(seg, path)
|
| 83 |
+
|
| 84 |
+
return path
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
@spaces.GPU(duration=150)
|
| 88 |
+
def run_inference(path):
|
| 89 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
| 90 |
+
input_nib = nib.load(path)
|
| 91 |
+
output_nib = totalsegmentator(input_nib, fast=True)
|
| 92 |
+
output_path = os.path.join(temp_dir, "totalseg_output.nii.gz")
|
| 93 |
+
nib.save(output_nib, output_path)
|
| 94 |
+
seg_sitk = sitk.ReadImage(output_path)
|
| 95 |
+
return seg_sitk
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def inference_wrapper(input_file, img_state, seg_state, slice_slider=50):
|
| 99 |
+
file_name = Path(input_file).name
|
| 100 |
+
|
| 101 |
+
if file_name in sample_files:
|
| 102 |
+
seg_sitk = sitk.ReadImage(os.path.join("output_examples", f"{Path(Path(file_name).stem).stem}_seg.nii.gz"))
|
| 103 |
+
else:
|
| 104 |
+
seg_sitk = run_inference(input_file.name)
|
| 105 |
+
|
| 106 |
+
seg_path = save_seg(seg_sitk, input_file.name)
|
| 107 |
+
seg_state.append(sitk_to_numpy(seg_sitk))
|
| 108 |
+
|
| 109 |
+
if not img_state:
|
| 110 |
+
img_sitk = sitk.ReadImage(input_file.name)
|
| 111 |
+
img_state.append(sitk_to_numpy(img_sitk))
|
| 112 |
+
|
| 113 |
+
return show_img_seg(img_state[-1], seg_state[-1], slice_slider), seg_state, seg_path
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
with gr.Blocks(title="TotalSegmentator") as interface:
|
| 117 |
+
|
| 118 |
+
gr.Markdown("# TotalSegmentator: Segmentation of 117 Classes in CT and MR Images")
|
| 119 |
+
gr.Markdown("""
|
| 120 |
+
- **GitHub:** https://github.com/wasserth/TotalSegmentator
|
| 121 |
+
- **Please Note:** This tool is intended for research purposes only and can segment 117 classes in CT/MRI images
|
| 122 |
+
- Supports both CT and MR imaging modalities
|
| 123 |
+
- Credit: adapted from `DiGuaQiu/MRSegmentator-Gradio`
|
| 124 |
+
""")
|
| 125 |
+
|
| 126 |
+
img_state = gr.State([])
|
| 127 |
+
seg_state = gr.State([])
|
| 128 |
+
|
| 129 |
+
with gr.Accordion(label='Upload CT Scan (nifti file) then click on Generate Segmentation to run TotalSegmentator', open=True):
|
| 130 |
+
with gr.Row():
|
| 131 |
+
with gr.Column():
|
| 132 |
+
|
| 133 |
+
file_input = gr.File(
|
| 134 |
+
type="filepath", label="Upload a CT or MR Image (.nii/.nii.gz)", file_types=[".gz", ".nii.gz"]
|
| 135 |
+
)
|
| 136 |
+
gr.Examples(["input_examples/" + example for example in sample_files], file_input)
|
| 137 |
+
|
| 138 |
+
with gr.Row():
|
| 139 |
+
infer_button = gr.Button("Generate Segmentations", variant="primary")
|
| 140 |
+
clear_button = gr.ClearButton()
|
| 141 |
+
|
| 142 |
+
with gr.Column():
|
| 143 |
+
slice_slider = gr.Slider(1, 100, value=50, step=2, label="Select (relative) Slice")
|
| 144 |
+
img_viewer = gr.AnnotatedImage(label="Image Viewer")
|
| 145 |
+
download_seg = gr.File(label="Download Segmentation", interactive=False)
|
| 146 |
+
|
| 147 |
+
file_input.change(
|
| 148 |
+
load_img_to_state,
|
| 149 |
+
inputs=[file_input, img_state, seg_state],
|
| 150 |
+
outputs=[img_viewer, img_state, seg_state],
|
| 151 |
+
)
|
| 152 |
+
slice_slider.change(show_img_seg, inputs=[img_state, seg_state, slice_slider], outputs=[img_viewer])
|
| 153 |
+
|
| 154 |
+
infer_button.click(
|
| 155 |
+
inference_wrapper,
|
| 156 |
+
inputs=[file_input, img_state, seg_state, slice_slider],
|
| 157 |
+
outputs=[img_viewer, seg_state, download_seg],
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
clear_button.add([file_input, img_viewer, img_state, seg_state, download_seg])
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
if __name__ == "__main__":
|
| 164 |
+
interface.queue()
|
| 165 |
+
interface.launch(debug=True)
|
input_examples/ct1.nii.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b3d562e8465ad99c783626094236b1067a6795aac04b6e39039bfc411d2e0506
|
| 3 |
+
size 9856205
|
input_examples/ct2.nii.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:980664b91abff172ecda4cf75bade34e4916281ee2509e3b93e3cf8bc326709e
|
| 3 |
+
size 7895923
|
input_examples/ct3.nii.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b4d47d8b8261bc239484f8dfdc1706b8e95a7b284e6e9a10cbc3bd4c41cbf359
|
| 3 |
+
size 8035692
|
output_examples/ct1_seg.nii.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:271468351f58881d9192b9d7620f236d8f5f807484c4db947ccdb60606ea26cb
|
| 3 |
+
size 142167
|
output_examples/ct2_seg.nii.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0f062135c10c78ccf01a6ce13660002239b4be9f781001f1fca41f25f4691473
|
| 3 |
+
size 61747
|
output_examples/ct3_seg.nii.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:65a95c55016e865e59c89b76da30f38ff84769598f1b5e47d681714ea7d9e12a
|
| 3 |
+
size 53422
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
TotalSegmentator
|
| 2 |
+
SimpleITK
|
| 3 |
+
spaces
|
segmap.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
seg_map = [
|
| 2 |
+
"background",
|
| 3 |
+
"spleen",
|
| 4 |
+
"right_kidney",
|
| 5 |
+
"left_kidney",
|
| 6 |
+
"gallbladder",
|
| 7 |
+
"liver",
|
| 8 |
+
"stomach",
|
| 9 |
+
"pancreas",
|
| 10 |
+
"right_adrenal_gland",
|
| 11 |
+
"left_adrenal_gland",
|
| 12 |
+
"left_lung",
|
| 13 |
+
"right_lung",
|
| 14 |
+
"heart",
|
| 15 |
+
"aorta",
|
| 16 |
+
"inferior_vena_cava",
|
| 17 |
+
"portal_vein_and_splenic_vein",
|
| 18 |
+
"left_iliac_artery",
|
| 19 |
+
"right_iliac_artery",
|
| 20 |
+
"left_iliac_vein",
|
| 21 |
+
"right_iliac_vein",
|
| 22 |
+
"esophagus",
|
| 23 |
+
"small_bowel",
|
| 24 |
+
"duodenum",
|
| 25 |
+
"colon",
|
| 26 |
+
"urinary_bladder",
|
| 27 |
+
"spine",
|
| 28 |
+
"sacrum",
|
| 29 |
+
"left_hip",
|
| 30 |
+
"right_hip",
|
| 31 |
+
"left_femur",
|
| 32 |
+
"right_femur",
|
| 33 |
+
"left_autochthonous_muscle",
|
| 34 |
+
"right_autochthonous_muscle",
|
| 35 |
+
"left_iliopsoas_muscle",
|
| 36 |
+
"right_iliopsoas_muscle",
|
| 37 |
+
"left_gluteus_maximus",
|
| 38 |
+
"right_gluteus_maximus",
|
| 39 |
+
"left_gluteus_medius",
|
| 40 |
+
"right_gluteus_medius",
|
| 41 |
+
"left_gluteus_minimus",
|
| 42 |
+
"right_gluteus_minimus",
|
| 43 |
+
"trachea",
|
| 44 |
+
"thyroid_gland",
|
| 45 |
+
"prostate",
|
| 46 |
+
"kidney_cyst_left",
|
| 47 |
+
"kidney_cyst_right",
|
| 48 |
+
"vertebrae_S1",
|
| 49 |
+
"vertebrae_L5",
|
| 50 |
+
"vertebrae_L4",
|
| 51 |
+
"vertebrae_L3",
|
| 52 |
+
"vertebrae_L2",
|
| 53 |
+
"vertebrae_L1",
|
| 54 |
+
"vertebrae_T12",
|
| 55 |
+
"vertebrae_T11",
|
| 56 |
+
"vertebrae_T10",
|
| 57 |
+
"vertebrae_T9",
|
| 58 |
+
"vertebrae_T8",
|
| 59 |
+
"vertebrae_T7",
|
| 60 |
+
"vertebrae_T6",
|
| 61 |
+
"vertebrae_T5",
|
| 62 |
+
"vertebrae_T4",
|
| 63 |
+
"vertebrae_T3",
|
| 64 |
+
"vertebrae_T2",
|
| 65 |
+
"vertebrae_T1",
|
| 66 |
+
"vertebrae_C7",
|
| 67 |
+
"vertebrae_C6",
|
| 68 |
+
"vertebrae_C5",
|
| 69 |
+
"vertebrae_C4",
|
| 70 |
+
"vertebrae_C3",
|
| 71 |
+
"vertebrae_C2",
|
| 72 |
+
"vertebrae_C1",
|
| 73 |
+
"pulmonary_vein",
|
| 74 |
+
"brachiocephalic_trunk",
|
| 75 |
+
"subclavian_artery_right",
|
| 76 |
+
"subclavian_artery_left",
|
| 77 |
+
"common_carotid_artery_right",
|
| 78 |
+
"common_carotid_artery_left",
|
| 79 |
+
"brachiocephalic_vein_left",
|
| 80 |
+
"brachiocephalic_vein_right",
|
| 81 |
+
"atrial_appendage_left",
|
| 82 |
+
"superior_vena_cava",
|
| 83 |
+
"humerus_left",
|
| 84 |
+
"humerus_right",
|
| 85 |
+
"scapula_left",
|
| 86 |
+
"scapula_right",
|
| 87 |
+
"clavicula_left",
|
| 88 |
+
"clavicula_right",
|
| 89 |
+
"spinal_cord",
|
| 90 |
+
"brain",
|
| 91 |
+
"skull",
|
| 92 |
+
"rib_left_1",
|
| 93 |
+
"rib_left_2",
|
| 94 |
+
"rib_left_3",
|
| 95 |
+
"rib_left_4",
|
| 96 |
+
"rib_left_5",
|
| 97 |
+
"rib_left_6",
|
| 98 |
+
"rib_left_7",
|
| 99 |
+
"rib_left_8",
|
| 100 |
+
"rib_left_9",
|
| 101 |
+
"rib_left_10",
|
| 102 |
+
"rib_left_11",
|
| 103 |
+
"rib_left_12",
|
| 104 |
+
"rib_right_1",
|
| 105 |
+
"rib_right_2",
|
| 106 |
+
"rib_right_3",
|
| 107 |
+
"rib_right_4",
|
| 108 |
+
"rib_right_5",
|
| 109 |
+
"rib_right_6",
|
| 110 |
+
"rib_right_7",
|
| 111 |
+
"rib_right_8",
|
| 112 |
+
"rib_right_9",
|
| 113 |
+
"rib_right_10",
|
| 114 |
+
"rib_right_11",
|
| 115 |
+
"rib_right_12",
|
| 116 |
+
"sternum",
|
| 117 |
+
"costal_cartilages",
|
| 118 |
+
"114","115","116","117","118","119","120","121","122","123",
|
| 119 |
+
"124","125","126","127","128","129","130","131","132","133"
|
| 120 |
+
]
|