andreped commited on
Commit
883f80d
1 Parent(s): 9e16f2c

Fixed local demo launch

Browse files
Files changed (7) hide show
  1. .dockerignore +6 -0
  2. .gitignore +7 -0
  3. demo/app.py +28 -5
  4. demo/requirements.txt +2 -1
  5. demo/src/convert.py +0 -24
  6. demo/src/gui.py +105 -40
  7. demo/src/utils.py +27 -0
.dockerignore CHANGED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ venv/
2
+ *.nii
3
+ *.nii.gz
4
+ *.pyc
5
+ *.egg-info
6
+ *.csv
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ venv/
2
+ *.nii
3
+ *.nii.gz
4
+ *.pyc
5
+ *.egg-info
6
+ *.csv
7
+ *.ini
demo/app.py CHANGED
@@ -1,16 +1,39 @@
 
 
 
1
  from src.gui import WebUI
2
 
3
 
4
  def main():
5
- print("Launching demo...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
- # cwd = "/Users/andreped/workspace/AeroPath/" # local testing -> macOS
8
- cwd = "/home/user/app/" # production -> docker
9
 
10
- class_name = "airways"
 
 
 
 
 
 
11
 
12
  # initialize and run app
13
- app = WebUI(class_name=class_name, cwd=cwd)
 
14
  app.run()
15
 
16
 
 
1
+ import os
2
+ from argparse import ArgumentParser
3
+
4
  from src.gui import WebUI
5
 
6
 
7
  def main():
8
+ parser = ArgumentParser()
9
+ parser.add_argument(
10
+ "--cwd",
11
+ type=str,
12
+ default="/home/user/app/",
13
+ help="Set current working directory (path to app.py).",
14
+ )
15
+ parser.add_argument(
16
+ "--share",
17
+ type=int,
18
+ default=1,
19
+ help="Whether to enable the app to be accessible online"
20
+ "-> setups a public link which requires internet access.",
21
+ )
22
+ args = parser.parse_args()
23
 
24
+ print("Current working directory:", args.cwd)
 
25
 
26
+ if not os.path.exists(args.cwd):
27
+ raise ValueError("Chosen 'cwd' is not a valid path!")
28
+ if args.share not in [0, 1]:
29
+ raise ValueError(
30
+ "The 'share' argument can only be set to 0 or 1, but was:",
31
+ args.share,
32
+ )
33
 
34
  # initialize and run app
35
+ print("Launching demo...")
36
+ app = WebUI(cwd=args.cwd, share=args.share)
37
  app.run()
38
 
39
 
demo/requirements.txt CHANGED
@@ -1,2 +1,3 @@
1
- raidionicsrads @ git+https://github.com/andreped/raidionics_rads_lib
2
  gradio==3.44.4
 
 
1
+ raidionicsrads@git+https://github.com/andreped/raidionics_rads_lib.git
2
  gradio==3.44.4
3
+ pandas==2.0.0
demo/src/convert.py DELETED
@@ -1,24 +0,0 @@
1
- import nibabel as nib
2
- from nibabel.processing import resample_to_output
3
- from skimage.measure import marching_cubes
4
-
5
-
6
- def nifti_to_glb(path, output="prediction.obj"):
7
- # load NIFTI into numpy array
8
- image = nib.load(path)
9
- resampled = resample_to_output(image, [1, 1, 1], order=1)
10
- data = resampled.get_fdata().astype("uint8")
11
-
12
- # extract surface
13
- verts, faces, normals, values = marching_cubes(data, 0)
14
- faces += 1
15
-
16
- with open(output, 'w') as thefile:
17
- for item in verts:
18
- thefile.write("v {0} {1} {2}\n".format(item[0],item[1],item[2]))
19
-
20
- for item in normals:
21
- thefile.write("vn {0} {1} {2}\n".format(item[0],item[1],item[2]))
22
-
23
- for item in faces:
24
- thefile.write("f {0}//{0} {1}//{1} {2}//{2}\n".format(item[0],item[1],item[2]))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
demo/src/gui.py CHANGED
@@ -1,11 +1,20 @@
 
 
1
  import gradio as gr
2
- from .utils import load_ct_to_numpy, load_pred_volume_to_numpy
3
  from .compute import run_model
4
- from .convert import nifti_to_glb
 
 
5
 
6
 
7
  class WebUI:
8
- def __init__(self, class_name:str = None, cwd:str = None):
 
 
 
 
 
9
  # global states
10
  self.images = []
11
  self.pred_images = []
@@ -13,11 +22,27 @@ class WebUI:
13
  # @TODO: This should be dynamically set based on chosen volume size
14
  self.nb_slider_items = 300
15
 
16
- self.class_name = class_name
17
  self.cwd = cwd
 
 
 
 
 
 
 
 
 
 
18
 
19
  # define widgets not to be rendered immediantly, but later on
20
- self.slider = gr.Slider(1, self.nb_slider_items, value=1, step=1, label="Which 2D slice to show")
 
 
 
 
 
 
21
  self.volume_renderer = gr.Model3D(
22
  clear_color=[0.0, 0.0, 0.0, 0.0],
23
  label="3D Model",
@@ -25,29 +50,41 @@ class WebUI:
25
  elem_id="model-3d",
26
  ).style(height=512)
27
 
 
 
 
 
28
  def combine_ct_and_seg(self, img, pred):
29
  return (img, [(pred, self.class_name)])
30
-
31
  def upload_file(self, file):
32
  return file.name
33
-
34
- def load_mesh(self, mesh_file_name):
35
  path = mesh_file_name.name
36
- run_model(path)
37
- nifti_to_glb("./prediction.nii.gz")
 
 
 
 
 
 
38
  self.images = load_ct_to_numpy(path)
39
  self.pred_images = load_pred_volume_to_numpy("./prediction.nii.gz")
40
- self.slider = self.slider.update(value=2)
41
  return "./prediction.obj"
42
-
43
  def get_img_pred_pair(self, k):
44
  k = int(k) - 1
45
  out = [gr.AnnotatedImage.update(visible=False)] * self.nb_slider_items
46
- out[k] = gr.AnnotatedImage.update(self.combine_ct_and_seg(self.images[k], self.pred_images[k]), visible=True)
 
 
 
47
  return out
48
 
49
  def run(self):
50
- css="""
51
  #model-3d {
52
  height: 512px;
53
  }
@@ -55,49 +92,77 @@ class WebUI:
55
  height: 512px;
56
  margin: auto;
57
  }
 
 
 
58
  """
59
  with gr.Blocks(css=css) as demo:
60
-
61
  with gr.Row():
62
- file_output = gr.File(
63
- file_types=[".nii", ".nii.nz"],
64
- file_count="single"
65
- ).style(full_width=False, size="sm")
66
  file_output.upload(self.upload_file, file_output, file_output)
67
 
68
- run_btn = gr.Button("Run analysis").style(full_width=False, size="sm")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  run_btn.click(
70
- fn=lambda x: self.load_mesh(x),
71
  inputs=file_output,
72
- outputs=self.volume_renderer
73
  )
74
-
75
  with gr.Row():
76
  gr.Examples(
77
- examples=[self.cwd + "test_thorax_CT.nii.gz"],
 
 
78
  inputs=file_output,
79
  outputs=file_output,
80
  fn=self.upload_file,
81
  cache_examples=True,
82
  )
83
-
84
  with gr.Row():
85
  with gr.Box():
86
- image_boxes = []
87
- for i in range(self.nb_slider_items):
88
- visibility = True if i == 1 else False
89
- t = gr.AnnotatedImage(visible=visibility, elem_id="model-2d")\
90
- .style(color_map={self.class_name: "#ffae00"}, height=512, width=512)
91
- image_boxes.append(t)
92
-
93
- self.slider.change(self.get_img_pred_pair, self.slider, image_boxes)
94
-
 
 
 
 
 
 
 
 
 
 
95
  with gr.Box():
96
  self.volume_renderer.render()
97
-
98
- with gr.Row():
99
- self.slider.render()
100
 
101
- # sharing app publicly -> share=True: https://gradio.app/sharing-your-app/
102
- # inference times > 60 seconds -> need queue(): https://github.com/tloen/alpaca-lora/issues/60#issuecomment-1510006062
103
- demo.queue().launch(server_name="0.0.0.0", server_port=7860, share=False)
 
 
 
 
 
1
+ import os
2
+
3
  import gradio as gr
4
+
5
  from .compute import run_model
6
+ from .utils import load_ct_to_numpy
7
+ from .utils import load_pred_volume_to_numpy
8
+ from .utils import nifti_to_glb
9
 
10
 
11
  class WebUI:
12
+ def __init__(
13
+ self,
14
+ model_name: str = None,
15
+ cwd: str = "/home/user/app/",
16
+ share: int = 1,
17
+ ):
18
  # global states
19
  self.images = []
20
  self.pred_images = []
 
22
  # @TODO: This should be dynamically set based on chosen volume size
23
  self.nb_slider_items = 300
24
 
25
+ self.model_name = model_name
26
  self.cwd = cwd
27
+ self.share = share
28
+
29
+ self.class_name = "airways" # default
30
+ self.class_names = {
31
+ "airways": "CT_Airways",
32
+ }
33
+
34
+ self.result_names = {
35
+ "airways": "Airway",
36
+ }
37
 
38
  # define widgets not to be rendered immediantly, but later on
39
+ self.slider = gr.Slider(
40
+ 1,
41
+ self.nb_slider_items,
42
+ value=1,
43
+ step=1,
44
+ label="Which 2D slice to show",
45
+ )
46
  self.volume_renderer = gr.Model3D(
47
  clear_color=[0.0, 0.0, 0.0, 0.0],
48
  label="3D Model",
 
50
  elem_id="model-3d",
51
  ).style(height=512)
52
 
53
+ def set_class_name(self, value):
54
+ print("Changed task to:", value)
55
+ self.class_name = value
56
+
57
  def combine_ct_and_seg(self, img, pred):
58
  return (img, [(pred, self.class_name)])
59
+
60
  def upload_file(self, file):
61
  return file.name
62
+
63
+ def process(self, mesh_file_name):
64
  path = mesh_file_name.name
65
+ run_model(
66
+ path,
67
+ model_path=os.path.join(self.cwd, "resources/models/"),
68
+ task=self.class_names[self.class_name],
69
+ name=self.result_names[self.class_name],
70
+ )
71
+ nifti_to_glb("prediction.nii.gz")
72
+
73
  self.images = load_ct_to_numpy(path)
74
  self.pred_images = load_pred_volume_to_numpy("./prediction.nii.gz")
 
75
  return "./prediction.obj"
76
+
77
  def get_img_pred_pair(self, k):
78
  k = int(k) - 1
79
  out = [gr.AnnotatedImage.update(visible=False)] * self.nb_slider_items
80
+ out[k] = gr.AnnotatedImage.update(
81
+ self.combine_ct_and_seg(self.images[k], self.pred_images[k]),
82
+ visible=True,
83
+ )
84
  return out
85
 
86
  def run(self):
87
+ css = """
88
  #model-3d {
89
  height: 512px;
90
  }
 
92
  height: 512px;
93
  margin: auto;
94
  }
95
+ #upload {
96
+ height: 120px;
97
+ }
98
  """
99
  with gr.Blocks(css=css) as demo:
 
100
  with gr.Row():
101
+ file_output = gr.File(file_count="single", elem_id="upload")
 
 
 
102
  file_output.upload(self.upload_file, file_output, file_output)
103
 
104
+ model_selector = gr.Dropdown(
105
+ list(self.class_names.keys()),
106
+ label="Task",
107
+ info="Which task to perform - one model for"
108
+ "each brain tumor type and brain extraction",
109
+ multiselect=False,
110
+ size="sm",
111
+ )
112
+ model_selector.input(
113
+ fn=lambda x: self.set_class_name(x),
114
+ inputs=model_selector,
115
+ outputs=None,
116
+ )
117
+
118
+ run_btn = gr.Button("Run analysis").style(
119
+ full_width=False, size="lg"
120
+ )
121
  run_btn.click(
122
+ fn=lambda x: self.process(x),
123
  inputs=file_output,
124
+ outputs=self.volume_renderer,
125
  )
126
+
127
  with gr.Row():
128
  gr.Examples(
129
+ examples=[
130
+ os.path.join(self.cwd, "test_thorax_CT.nii.gz"),
131
+ ],
132
  inputs=file_output,
133
  outputs=file_output,
134
  fn=self.upload_file,
135
  cache_examples=True,
136
  )
137
+
138
  with gr.Row():
139
  with gr.Box():
140
+ with gr.Column():
141
+ image_boxes = []
142
+ for i in range(self.nb_slider_items):
143
+ visibility = True if i == 1 else False
144
+ t = gr.AnnotatedImage(
145
+ visible=visibility, elem_id="model-2d"
146
+ ).style(
147
+ color_map={self.class_name: "#ffae00"},
148
+ height=512,
149
+ width=512,
150
+ )
151
+ image_boxes.append(t)
152
+
153
+ self.slider.input(
154
+ self.get_img_pred_pair, self.slider, image_boxes
155
+ )
156
+
157
+ self.slider.render()
158
+
159
  with gr.Box():
160
  self.volume_renderer.render()
 
 
 
161
 
162
+ # sharing app publicly -> share=True:
163
+ # https://gradio.app/sharing-your-app/
164
+ # inference times > 60 seconds -> need queue():
165
+ # https://github.com/tloen/alpaca-lora/issues/60#issuecomment-1510006062
166
+ demo.queue().launch(
167
+ server_name="0.0.0.0", server_port=7860, share=self.share
168
+ )
demo/src/utils.py CHANGED
@@ -1,5 +1,7 @@
1
  import nibabel as nib
2
  import numpy as np
 
 
3
 
4
 
5
  def load_ct_to_numpy(data_path):
@@ -36,3 +38,28 @@ def load_pred_volume_to_numpy(data_path):
36
 
37
  print(data.shape)
38
  return [data[..., i] for i in range(data.shape[-1])]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import nibabel as nib
2
  import numpy as np
3
+ from nibabel.processing import resample_to_output
4
+ from skimage.measure import marching_cubes
5
 
6
 
7
  def load_ct_to_numpy(data_path):
 
38
 
39
  print(data.shape)
40
  return [data[..., i] for i in range(data.shape[-1])]
41
+
42
+
43
+ def nifti_to_glb(path, output="prediction.obj"):
44
+ # load NIFTI into numpy array
45
+ image = nib.load(path)
46
+ resampled = resample_to_output(image, [1, 1, 1], order=1)
47
+ data = resampled.get_fdata().astype("uint8")
48
+
49
+ # extract surface
50
+ verts, faces, normals, values = marching_cubes(data, 0)
51
+ faces += 1
52
+
53
+ with open(output, "w") as thefile:
54
+ for item in verts:
55
+ thefile.write("v {0} {1} {2}\n".format(item[0], item[1], item[2]))
56
+
57
+ for item in normals:
58
+ thefile.write("vn {0} {1} {2}\n".format(item[0], item[1], item[2]))
59
+
60
+ for item in faces:
61
+ thefile.write(
62
+ "f {0}//{0} {1}//{1} {2}//{2}\n".format(
63
+ item[0], item[1], item[2]
64
+ )
65
+ )