dbouget commited on
Commit
9d26f07
·
1 Parent(s): a53b581

Overall update to match Raidionics v1.3

Browse files
Files changed (5) hide show
  1. .github/workflows/deploy.yml +1 -1
  2. Dockerfile +4 -12
  3. requirements.txt +2 -2
  4. src/gui.py +115 -90
  5. src/inference.py +37 -19
.github/workflows/deploy.yml CHANGED
@@ -10,7 +10,7 @@ jobs:
10
  sync-to-hub:
11
  runs-on: ubuntu-latest
12
  steps:
13
- - uses: actions/checkout@v3
14
  with:
15
  fetch-depth: 0
16
  lfs: true
 
10
  sync-to-hub:
11
  runs-on: ubuntu-latest
12
  steps:
13
+ - uses: actions/checkout@v4
14
  with:
15
  fetch-depth: 0
16
  lfs: true
Dockerfile CHANGED
@@ -1,6 +1,6 @@
1
  # read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
  # you will also find guides on how best to write your Dockerfile
3
- FROM python:3.8-slim
4
 
5
  # set language, format and stuff
6
  ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
@@ -30,7 +30,7 @@ COPY ./requirements.txt /code/requirements.txt
30
  RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
31
 
32
  # resolve issue with tf==2.4 and gradio dependency collision issue
33
- RUN pip install --force-reinstall typing_extensions==4.7.1
34
 
35
  # Install wget
36
  RUN apt install wget -y && \
@@ -54,16 +54,8 @@ COPY --chown=user . $HOME/app
54
 
55
  # Download pretrained models
56
  RUN mkdir -p resources/models/
57
- RUN wget "https://github.com/raidionics/Raidionics-models/releases/download/1.2.0/Raidionics-MRI_Brain-ONNX-v12.zip" && \
58
- unzip "Raidionics-MRI_Brain-ONNX-v12.zip" && mv MRI_Brain/ resources/models/MRI_Brain/
59
- RUN wget "https://github.com/raidionics/Raidionics-models/releases/download/1.2.0/Raidionics-MRI_GBM-ONNX-v12.zip" && \
60
- unzip "Raidionics-MRI_GBM-ONNX-v12.zip" && mv MRI_GBM/ resources/models/MRI_GBM/
61
- RUN wget "https://github.com/raidionics/Raidionics-models/releases/download/1.2.0/Raidionics-MRI_LGGlioma-ONNX-v12.zip" && \
62
- unzip "Raidionics-MRI_LGGlioma-ONNX-v12.zip" && mv MRI_LGGlioma/ resources/models/MRI_LGGlioma/
63
- RUN wget "https://github.com/raidionics/Raidionics-models/releases/download/1.2.0/Raidionics-MRI_Meningioma-ONNX-v12.zip" && \
64
- unzip "Raidionics-MRI_Meningioma-ONNX-v12.zip" && mv MRI_Meningioma/ resources/models/MRI_Meningioma/
65
- RUN wget "https://github.com/raidionics/Raidionics-models/releases/download/1.2.0/Raidionics-MRI_Metastasis-ONNX-v12.zip" && \
66
- unzip "Raidionics-MRI_Metastasis-ONNX-v12.zip" && mv MRI_Metastasis/ resources/models/MRI_Metastasis/
67
 
68
  RUN rm -r *.zip
69
 
 
1
  # read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
  # you will also find guides on how best to write your Dockerfile
3
+ FROM python:3.10-slim
4
 
5
  # set language, format and stuff
6
  ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
 
30
  RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
31
 
32
  # resolve issue with tf==2.4 and gradio dependency collision issue
33
+ # RUN pip install --force-reinstall typing_extensions==4.7.1
34
 
35
  # Install wget
36
  RUN apt install wget -y && \
 
54
 
55
  # Download pretrained models
56
  RUN mkdir -p resources/models/
57
+ RUN wget "https://github.com/raidionics/Raidionics-models/releases/download/v1.3.0-rc/Raidionics_HF_Neuro_Resources-v13.zip" && \
58
+ unzip "Raidionics_HF_Neuro_Resources-v13.zip" -d resources/models/
 
 
 
 
 
 
 
 
59
 
60
  RUN rm -r *.zip
61
 
requirements.txt CHANGED
@@ -1,2 +1,2 @@
1
- raidionicsrads@git+https://github.com/andreped/raidionics_rads_lib
2
- gradio==3.44.4
 
1
+ raidionicsrads
2
+ gradio
src/gui.py CHANGED
@@ -1,6 +1,9 @@
1
  import os
2
 
3
  import gradio as gr
 
 
 
4
 
5
  from .inference import run_model
6
  from .utils import load_pred_volume_to_numpy
@@ -15,50 +18,47 @@ class WebUI:
15
  cwd: str = "/home/user/app/",
16
  share: int = 1,
17
  ):
 
 
 
 
 
 
 
 
18
  # global states
19
  self.images = []
20
  self.pred_images = []
21
-
22
- # @TODO: This should be dynamically set based on chosen volume size
23
- self.nb_slider_items = 512
24
 
25
  self.model_name = model_name
26
  self.cwd = cwd
27
  self.share = share
28
 
29
- self.class_name = "meningioma" # default
30
  self.class_names = {
31
- "meningioma": "MRI_Meningioma",
32
- "lower-grade-glioma": "MRI_LGGlioma",
33
- "metastasis": "MRI_Metastasis",
34
- "glioblastoma": "MRI_GBM",
35
  "brain": "MRI_Brain",
36
  }
37
 
38
  self.result_names = {
39
- "meningioma": "Tumor",
40
- "lower-grade-glioma": "Tumor",
41
- "metastasis": "Tumor",
42
- "glioblastoma": "Tumor",
43
  "brain": "Brain",
44
  }
45
 
46
- # define widgets not to be rendered immediately, but later on
47
- self.slider = gr.Slider(
48
- minimum=1,
49
- maximum=self.nb_slider_items,
50
- value=1,
51
- step=1,
52
- label="Which 2D slice to show",
53
- interactive=True,
54
- )
55
-
56
  self.volume_renderer = gr.Model3D(
57
  clear_color=[0.0, 0.0, 0.0, 0.0],
58
  label="3D Model",
59
  visible=True,
60
  elem_id="model-3d",
61
- ).style(height=512)
 
62
 
63
  def set_class_name(self, value):
64
  print("Changed task to:", value)
@@ -70,35 +70,97 @@ class WebUI:
70
  def upload_file(self, file):
71
  return file.name
72
 
73
- def process(self, mesh_file_name):
74
  path = mesh_file_name.name
75
  run_model(
76
  path,
77
  model_path=os.path.join(self.cwd, "resources/models/"),
78
  task=self.class_names[self.class_name],
79
  name=self.result_names[self.class_name],
 
80
  )
81
  nifti_to_glb("prediction.nii.gz")
82
 
83
  self.images = load_to_numpy(path)
84
- # @TODO. Dynamic update of the slider does not seem to work like this
85
- # self.nb_slider_items = len(self.images)
86
- # self.slider.update(value=int(self.nb_slider_items/2), maximum=self.nb_slider_items)
87
 
88
  self.pred_images = load_pred_volume_to_numpy("./prediction.nii.gz")
89
- return "./prediction.obj"
 
 
 
 
 
 
 
 
 
90
 
91
  def get_img_pred_pair(self, k):
92
- k = int(k) - 1
93
- # @TODO. Will the duplicate the last slice to fill up, since slider not adjustable right now
94
- if k >= len(self.images):
95
- k = len(self.images) - 1
96
- out = [gr.AnnotatedImage.update(visible=False)] * self.nb_slider_items
97
- out[k] = gr.AnnotatedImage.update(
98
- self.combine_ct_and_seg(self.images[k], self.pred_images[k]),
99
- visible=True,
100
- )
101
- return out
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
  def run(self):
104
  css = """
@@ -114,66 +176,29 @@ class WebUI:
114
  }
115
  """
116
  with gr.Blocks(css=css) as demo:
117
- with gr.Row():
118
- file_output = gr.File(file_count="single", elem_id="upload")
119
- file_output.upload(self.upload_file, file_output, file_output)
120
-
121
- model_selector = gr.Dropdown(
122
- list(self.class_names.keys()),
123
- label="Segmentation task",
124
- info="Select the preoperative segmentation model to run",
125
- multiselect=False,
126
- size="sm",
127
- )
128
- model_selector.input(
129
- fn=lambda x: self.set_class_name(x),
130
- inputs=model_selector,
131
- outputs=None,
132
- )
133
-
134
- run_btn = gr.Button("Run segmentation").style(
135
- full_width=False, size="lg"
136
- )
137
- run_btn.click(
138
- fn=lambda x: self.process(x),
139
- inputs=file_output,
140
- outputs=self.volume_renderer,
141
- )
142
-
143
  with gr.Row():
144
  gr.Examples(
145
  examples=[
146
  os.path.join(self.cwd, "t1gd.nii.gz"),
147
  ],
148
- inputs=file_output,
149
- outputs=file_output,
150
  fn=self.upload_file,
151
  cache_examples=True,
152
  )
153
-
154
- with gr.Row():
155
- with gr.Box():
156
- with gr.Column():
157
- image_boxes = []
158
- for i in range(self.nb_slider_items):
159
- visibility = True if i == 1 else False
160
- t = gr.AnnotatedImage(
161
- visible=visibility, elem_id="model-2d"
162
- ).style(
163
- color_map={self.class_name: "#ffae00"},
164
- height=512,
165
- width=512,
166
- )
167
- image_boxes.append(t)
168
-
169
- self.slider.input(
170
- self.get_img_pred_pair, self.slider, image_boxes
171
- )
172
-
173
- self.slider.render()
174
-
175
- with gr.Box():
176
- self.volume_renderer.render()
177
 
178
  # sharing app publicly -> share=True:
179
  # https://gradio.app/sharing-your-app/
 
1
  import os
2
 
3
  import gradio as gr
4
+ from PIL import Image
5
+ import logging
6
+ from zipfile import ZipFile
7
 
8
  from .inference import run_model
9
  from .utils import load_pred_volume_to_numpy
 
18
  cwd: str = "/home/user/app/",
19
  share: int = 1,
20
  ):
21
+ self.file_output = None
22
+ self.model_selector = None
23
+ self.stripped_cb = None
24
+ self.registered_cb = None
25
+ self.run_btn = None
26
+ self.slider = None
27
+ self.download_file = None
28
+
29
  # global states
30
  self.images = []
31
  self.pred_images = []
32
+ self.image_boxes = []
 
 
33
 
34
  self.model_name = model_name
35
  self.cwd = cwd
36
  self.share = share
37
 
38
+ self.class_name = "tumorcore" # default
39
  self.class_names = {
40
+ "tumorcore": "MRI_TumorCore",
41
+ "NETC": "MRI_Necrosis",
42
+ "residual-tumor": "MRI_TumorCE_Postop",
43
+ "cavity": "MRI_Cavity",
44
  "brain": "MRI_Brain",
45
  }
46
 
47
  self.result_names = {
48
+ "tumorcore": "Tumor",
49
+ "NETC": "NETC",
50
+ "residual-tumor": "Tumor",
51
+ "cavity": "Cavity",
52
  "brain": "Brain",
53
  }
54
 
 
 
 
 
 
 
 
 
 
 
55
  self.volume_renderer = gr.Model3D(
56
  clear_color=[0.0, 0.0, 0.0, 0.0],
57
  label="3D Model",
58
  visible=True,
59
  elem_id="model-3d",
60
+ height=512,
61
+ )
62
 
63
  def set_class_name(self, value):
64
  print("Changed task to:", value)
 
70
  def upload_file(self, file):
71
  return file.name
72
 
73
+ def process(self, mesh_file_name, stripped_inputs_status:bool=False):
74
  path = mesh_file_name.name
75
  run_model(
76
  path,
77
  model_path=os.path.join(self.cwd, "resources/models/"),
78
  task=self.class_names[self.class_name],
79
  name=self.result_names[self.class_name],
80
+ stripped_inputs_status=stripped_inputs_status,
81
  )
82
  nifti_to_glb("prediction.nii.gz")
83
 
84
  self.images = load_to_numpy(path)
 
 
 
85
 
86
  self.pred_images = load_pred_volume_to_numpy("./prediction.nii.gz")
87
+ slider = gr.Slider(
88
+ minimum=0,
89
+ maximum=len(self.images) - 1,
90
+ value=int(len(self.images) / 2),
91
+ step=1,
92
+ label="Which 2D slice to show",
93
+ interactive=True,
94
+ )
95
+
96
+ return "./prediction.obj", slider
97
 
98
  def get_img_pred_pair(self, k):
99
+ img = self.images[k]
100
+ img_pil = Image.fromarray(img)
101
+ seg_list = []
102
+ seg_list.append((self.pred_images[k], self.class_name))
103
+ return img_pil, seg_list
104
+
105
+ def setup_interface_inputs(self):
106
+ with gr.Row():
107
+ with gr.Column():
108
+ self.file_output = gr.File(file_count="single", elem_id="upload")
109
+
110
+ with gr.Column():
111
+ self.model_selector = gr.Dropdown(
112
+ list(self.class_names.keys()),
113
+ label="Segmentation task",
114
+ info="Select the segmentation model to run",
115
+ multiselect=False,
116
+ # size="sm",
117
+ )
118
+
119
+ with gr.Column():
120
+ with gr.Row():
121
+ self.stripped_cb = gr.Checkbox(label="Stripped inputs")
122
+ self.registered_cb = gr.Checkbox(label="Co-registered inputs")
123
+ with gr.Row():
124
+ self.run_btn = gr.Button("Run segmentation", scale=1)
125
+
126
+ def setup_interface_outputs(self):
127
+ with gr.Row():
128
+ with gr.Group():
129
+ with gr.Column():
130
+ t = gr.AnnotatedImage(
131
+ visible=True,
132
+ elem_id="model-2d",
133
+ color_map={self.class_name: "#ffae00"},
134
+ height=512,
135
+ width=512,
136
+ )
137
+
138
+ self.slider = gr.Slider(
139
+ minimum=0,
140
+ maximum=1,
141
+ value=0,
142
+ step=1,
143
+ label="Which 2D slice to show",
144
+ interactive=True,
145
+ )
146
+
147
+ self.slider.change(fn=self.get_img_pred_pair, inputs=self.slider, outputs=t)
148
+
149
+ with gr.Group():
150
+ self.volume_renderer.render()
151
+ self.download_btn = gr.DownloadButton(label="Download results", visible=False)
152
+ self.download_file = gr.File(label="Download Zip", interactive=True, visible=False)
153
+
154
+ def package_results(self):
155
+ """Generates text files and zips them."""
156
+ output_dir = "temp_output"
157
+ os.makedirs(output_dir, exist_ok=True)
158
+
159
+ zip_filename = os.path.join(output_dir, "generated_files.zip")
160
+ with ZipFile(zip_filename, 'w') as zf:
161
+ zf.write("./prediction.nii.gz")
162
+
163
+ return zip_filename
164
 
165
  def run(self):
166
  css = """
 
176
  }
177
  """
178
  with gr.Blocks(css=css) as demo:
179
+ # Define the interface components first
180
+ self.setup_interface_inputs()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  with gr.Row():
182
  gr.Examples(
183
  examples=[
184
  os.path.join(self.cwd, "t1gd.nii.gz"),
185
  ],
186
+ inputs=self.file_output,
187
+ outputs=self.file_output,
188
  fn=self.upload_file,
189
  cache_examples=True,
190
  )
191
+ self.setup_interface_outputs()
192
+
193
+ # Define the signals/slots
194
+ self.file_output.upload(self.upload_file, self.file_output, self.file_output)
195
+ self.model_selector.input(fn=lambda x: self.set_class_name(x), inputs=self.model_selector, outputs=None)
196
+ self.run_btn.click(fn=self.process, inputs=[self.file_output, self.stripped_cb],
197
+ outputs=[self.volume_renderer, self.slider]).then(fn=lambda:
198
+ gr.DownloadButton(visible=True), inputs=None, outputs=self.download_btn)
199
+ self.download_btn.click(fn=self.package_results, inputs=[], outputs=self.download_file).then(fn=lambda
200
+ file_path: gr.File(label="Download Zip", visible=True, value=file_path), inputs=self.download_file,
201
+ outputs=self.download_file)
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
  # sharing app publicly -> share=True:
204
  # https://gradio.app/sharing-your-app/
src/inference.py CHANGED
@@ -2,14 +2,17 @@ import configparser
2
  import logging
3
  import os
4
  import shutil
 
 
5
 
6
 
7
  def run_model(
8
  input_path: str,
9
  model_path: str,
10
  verbose: str = "info",
11
- task: str = "MRI_Meningioma",
12
  name: str = "Tumor",
 
13
  ):
14
  logging.basicConfig()
15
  logging.getLogger().setLevel(logging.WARNING)
@@ -55,38 +58,53 @@ def run_model(
55
  rads_config.set("System", "input_folder", patient_directory)
56
  rads_config.set("System", "output_folder", output_path)
57
  rads_config.set("System", "model_folder", model_path)
58
- rads_config.set(
59
- "System",
60
- "pipeline_filename",
61
- os.path.join(model_path, task, "pipeline.json"),
62
- )
63
  rads_config.add_section("Runtime")
64
  rads_config.set(
65
  "Runtime", "reconstruction_method", "thresholding"
66
  ) # thresholding, probabilities
67
  rads_config.set("Runtime", "reconstruction_order", "resample_first")
68
  rads_config.set("Runtime", "use_preprocessed_data", "False")
 
69
 
70
  with open("rads_config.ini", "w") as f:
71
  rads_config.write(f)
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  # finally, run inference
74
  from raidionicsrads.compute import run_rads
75
-
76
  run_rads(config_filename="rads_config.ini")
77
 
78
- # rename and move final result
79
- os.rename(
80
- "./result/prediction-"
81
- + splits[0]
82
- + "/T0/"
83
- + splits[0]
84
- + "-t1gd_annotation-"
85
- + name
86
- + ".nii.gz",
87
- "./prediction.nii.gz",
88
- )
89
-
90
  except Exception as e:
91
  print(e)
92
 
 
2
  import logging
3
  import os
4
  import shutil
5
+ import json
6
+ import fnmatch
7
 
8
 
9
  def run_model(
10
  input_path: str,
11
  model_path: str,
12
  verbose: str = "info",
13
+ task: str = "MRI_TumorCore",
14
  name: str = "Tumor",
15
+ stripped_inputs_status: bool = False,
16
  ):
17
  logging.basicConfig()
18
  logging.getLogger().setLevel(logging.WARNING)
 
58
  rads_config.set("System", "input_folder", patient_directory)
59
  rads_config.set("System", "output_folder", output_path)
60
  rads_config.set("System", "model_folder", model_path)
61
+ rads_config.set('System', 'pipeline_filename', os.path.join(output_path,
62
+ 'test_pipeline.json'))
 
 
 
63
  rads_config.add_section("Runtime")
64
  rads_config.set(
65
  "Runtime", "reconstruction_method", "thresholding"
66
  ) # thresholding, probabilities
67
  rads_config.set("Runtime", "reconstruction_order", "resample_first")
68
  rads_config.set("Runtime", "use_preprocessed_data", "False")
69
+ rads_config.set('Runtime', 'use_stripped_data', 'True' if stripped_inputs_status else 'False')
70
 
71
  with open("rads_config.ini", "w") as f:
72
  rads_config.write(f)
73
 
74
+ pip = {}
75
+ step_index = 1
76
+ pip_num = str(step_index)
77
+ pip[pip_num] = {}
78
+ pip[pip_num]["task"] = "Classification"
79
+ pip[pip_num]["inputs"] = {} # Empty input means running it on all existing data for the patient
80
+ pip[pip_num]["target"] = ["MRSequence"]
81
+ pip[pip_num]["model"] = "MRI_SequenceClassifier"
82
+ pip[pip_num]["description"] = "Classification of the MRI sequence type for all input scans."
83
+
84
+ step_index = step_index + 1
85
+ pip_num = str(step_index)
86
+ pip[pip_num] = {}
87
+ pip[pip_num]["task"] = 'Model selection'
88
+ pip[pip_num]["model"] = task
89
+ pip[pip_num]["timestamp"] = 0
90
+ pip[pip_num]["format"] = "thresholding"
91
+ pip[pip_num]["description"] = f"Identifying the best {task} segmentation model for existing inputs"
92
+
93
+ with open(os.path.join(output_path, 'test_pipeline.json'), 'w', newline='\n') as outfile:
94
+ json.dump(pip, outfile, indent=4, sort_keys=True)
95
+
96
  # finally, run inference
97
  from raidionicsrads.compute import run_rads
 
98
  run_rads(config_filename="rads_config.ini")
99
 
100
+ logging.info(f"Looking for the following pattern: {task}")
101
+ patterns = ["*_" + task + '.*', "*_" + name + '.*']
102
+ existing_files = os.listdir(os.path.join(output_path, "T0"))
103
+ logging.info(f"Existing files: {existing_files}")
104
+ fileName = str(os.path.join(output_path, "T0",
105
+ [x for x in existing_files if
106
+ any(fnmatch.fnmatch(x, pattern) for pattern in patterns)][0]))
107
+ os.rename(src=fileName, dst="./prediction.nii.gz")
 
 
 
 
108
  except Exception as e:
109
  print(e)
110