andreped commited on
Commit
4e18454
1 Parent(s): 6faad95

Reworked app + 2D + 3D viewer

Browse files
Dockerfile CHANGED
@@ -22,6 +22,8 @@ WORKDIR /code
22
  RUN apt-get update -y
23
  RUN apt install git --fix-missing -y
24
 
 
 
25
  # install dependencies
26
  COPY ./demo/requirements.txt /code/demo/requirements.txt
27
  RUN python3.7 -m pip install --no-cache-dir --upgrade -r /code/demo/requirements.txt
@@ -32,8 +34,6 @@ RUN python3.7 -m pip install --force-reinstall typing_extensions==4.0.0
32
  # Install wget
33
  RUN apt install wget -y
34
 
35
- RUN ls -la
36
-
37
  # Set up a new user named "user" with user ID 1000
38
  RUN useradd -m -u 1000 user
39
 
 
22
  RUN apt-get update -y
23
  RUN apt install git --fix-missing -y
24
 
25
+ RUN ls -la
26
+
27
  # install dependencies
28
  COPY ./demo/requirements.txt /code/demo/requirements.txt
29
  RUN python3.7 -m pip install --no-cache-dir --upgrade -r /code/demo/requirements.txt
 
34
  # Install wget
35
  RUN apt install wget -y
36
 
 
 
37
  # Set up a new user named "user" with user ID 1000
38
  RUN useradd -m -u 1000 user
39
 
demo/app.py CHANGED
@@ -1,178 +1,16 @@
1
- import gradio as gr
2
- import subprocess as sp
3
- from skimage.measure import marching_cubes
4
- import nibabel as nib
5
- from nibabel.processing import resample_to_output
6
- import numpy as np
7
- import random
8
 
9
 
10
- def nifti_to_glb(path):
11
- # load NIFTI into numpy array
12
- image = nib.load(path)
13
- resampled = resample_to_output(image, [1, 1, 1], order=1)
14
- data = resampled.get_fdata().astype("uint8")
15
-
16
- # extract surface
17
- verts, faces, normals, values = marching_cubes(data, 0)
18
- faces += 1
19
-
20
- with open('prediction.obj', 'w') as thefile:
21
- for item in verts:
22
- thefile.write("v {0} {1} {2}\n".format(item[0],item[1],item[2]))
23
-
24
- for item in normals:
25
- thefile.write("vn {0} {1} {2}\n".format(item[0],item[1],item[2]))
26
-
27
- for item in faces:
28
- thefile.write("f {0}//{0} {1}//{1} {2}//{2}\n".format(item[0],item[1],item[2]))
29
-
30
-
31
- def run_model(input_path):
32
- from livermask.utils.run import run_analysis
33
-
34
- run_analysis(cpu=True, extension='.nii', path=input_path, output='prediction', verbose=True, vessels=False, name="/home/user/app/model.h5", mp_enabled=False)
35
-
36
-
37
- def load_mesh(mesh_file_name):
38
- path = mesh_file_name.name
39
- run_model(path)
40
- nifti_to_glb("prediction-livermask.nii")
41
- return "./prediction.obj"
42
-
43
-
44
- def setup_gallery(data_path, pred_path):
45
- image = nib.load(data_path)
46
- resampled = resample_to_output(image, [1, 1, 1], order=1)
47
- data = resampled.get_fdata().astype("uint8")
48
-
49
- image = nib.load(pred_path)
50
- resampled = resample_to_output(image, [1, 1, 1], order=0)
51
- pred = resampled.get_fdata().astype("uint8")
52
-
53
-
54
- def load_ct_to_numpy(data_path):
55
- if type(data_path) != str:
56
- data_path = data_path.name
57
-
58
- image = nib.load(data_path)
59
- data = image.get_fdata()
60
-
61
- data = np.rot90(data, k=1, axes=(0, 1))
62
-
63
- data[data < -150] = -150
64
- data[data > 250] = 250
65
-
66
- data = data - np.amin(data)
67
- data = data / np.amax(data) * 255
68
- data = data.astype("uint8")
69
-
70
- print(data.shape)
71
- return [data[..., i] for i in range(data.shape[-1])]
72
-
73
-
74
- def upload_file(file):
75
- return file.name
76
-
77
- #def select_section(evt: gr.SelectData):
78
- # return section_labels[evt.index]
79
-
80
-
81
- if __name__ == "__main__":
82
  print("Launching demo...")
83
- with gr.Blocks() as demo:
84
- """
85
- with gr.Blocks() as demo:
86
- with gr.Row():
87
- text1 = gr.Textbox(label="t1")
88
- slider2 = gr.Textbox(label="slide")
89
- drop3 = gr.Dropdown(["a", "b", "c"], label="d3")
90
- with gr.Row():
91
- with gr.Column(scale=1, min_width=600):
92
- text1 = gr.Textbox(label="prompt 1")
93
- text2 = gr.Textbox(label="prompt 2")
94
- inbtw = gr.Button("Between")
95
- text4 = gr.Textbox(label="prompt 1")
96
- text5 = gr.Textbox(label="prompt 2")
97
- with gr.Column(scale=2, min_width=600):
98
- img1 = gr.Image("images/cheetah.jpg")
99
- btn = gr.Button("Go").style(full_width=True)
100
-
101
- greeter_1 = gr.Interface(lambda name: f"Hello {name}!", inputs="textbox", outputs=gr.Textbox(label="Greeter 1"))
102
- greeter_2 = gr.Interface(lambda name: f"Greetings {name}!", inputs="textbox", outputs=gr.Textbox(label="Greeter 2"))
103
- demo = gr.Parallel(greeter_1, greeter_2)
104
-
105
- volume_renderer = gr.Interface(
106
- fn=load_mesh,
107
- inputs=gr.UploadButton(label="Click to Upload a File", file_types=[".nii", ".nii.nz"], file_count="single"),
108
- outputs=gr.Model3D(clear_color=[0.0, 0.0, 0.0, 0.0], label="3D Model"),
109
- title="livermask: Automatic Liver Parenchyma segmentation in CT",
110
- description="Using pretrained deep learning model trained on the LiTS17 dataset",
111
- )
112
- """
113
-
114
- with gr.Row():
115
- # file_output = gr.File()
116
- upload_button = gr.UploadButton(label="Click to Upload a File", file_types=[".nii", ".nii.nz"], file_count="single")
117
- # upload_button.upload(upload_file, upload_button, file_output)
118
-
119
- #select_btn = gr.Button("Run analysis")
120
- #select_btn.click(fn=upload_file, inputs=upload_button, outputs=output, api_name="Analysis")
121
-
122
- #upload_button.click(section, [img_input, num_boxes, num_segments], img_output)
123
-
124
- #print("file output:", file_output)
125
 
126
- images = load_ct_to_numpy("./test-volume.nii")
 
127
 
128
- def variable_outputs(k):
129
- k = int(k) - 1
130
- out = [gr.AnnotatedImage.update(visible=False)] * len(images)
131
- out[k] = gr.AnnotatedImage.update(visible=True)
132
- return out
133
-
134
- def section(img, num_segments):
135
- sections = []
136
- for b in range(num_segments):
137
- x = random.randint(0, img.shape[1])
138
- y = random.randint(0, img.shape[0])
139
- r = random.randint(0, min(x, y, img.shape[1] - x, img.shape[0] - y))
140
- mask = np.zeros(img.shape[:2])
141
- for i in range(img.shape[0]):
142
- for j in range(img.shape[1]):
143
- dist_square = (i - y) ** 2 + (j - x) ** 2
144
- if dist_square < r**2:
145
- mask[i, j] = round((r**2 - dist_square) / r**2 * 4) / 4
146
- sections.append((mask, "parenchyma"))
147
- return (img, sections)
148
-
149
- with gr.Row():
150
- s = gr.Slider(1, len(images), value=1, step=1, label="Which 2D slice to show")
151
-
152
- with gr.Row():
153
- with gr.Box():
154
- images_boxes = []
155
- for i, image in enumerate(images):
156
- visibility = True if i == 1 else False # only first slide visible - change slide through slider
157
- t = gr.AnnotatedImage(value=section(image, 1), visible=visibility).style(color_map={"parenchyma": "#ffae00"}, width=image.shape[1])
158
- images_boxes.append(t)
159
 
160
- s.change(variable_outputs, s, images_boxes)
161
 
162
-
163
- #upload_button.upload(upload_file, upload_button, file_output)
164
-
165
- #section_btn.click(section, [images[40], num_boxes, num_segments], img_output)
166
- #ct_images.upload(section, [images[40], num_boxes, num_segments], img_output)
167
-
168
- #demo = gr.Interface(
169
- # fn=load_ct_to_numpy,
170
- # inputs=gr.UploadButton(label="Click to Upload a File", file_types=[".nii", ".nii.nz"], file_count="single"),
171
- # outputs=gr.Gallery(label="CT slices").style(columns=[4], rows=[4], object_fit="contain", height="auto"),
172
- # title="livermask: Automatic Liver Parenchyma segmentation in CT",
173
- # description="Using pretrained deep learning model trained on the LiTS17 dataset",
174
- #)
175
-
176
- # sharing app publicly -> share=True: https://gradio.app/sharing-your-app/
177
- # inference times > 60 seconds -> need queue(): https://github.com/tloen/alpaca-lora/issues/60#issuecomment-1510006062
178
- demo.queue().launch(server_name="0.0.0.0", server_port=7860, share=True)
 
1
+ from src.gui import WebUI
 
 
 
 
 
 
2
 
3
 
4
+ def main():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  print("Launching demo...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
+ model_name = "/home/user/app/model.h5" # "/Users/andreped/workspace/livermask/model.h5"
8
+ class_name = "parenchyma"
9
 
10
+ # initialize and run app
11
+ app = WebUI(model_name=model_name, class_name=class_name)
12
+ app.run()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
 
14
 
15
+ if __name__ == "__main__":
16
+ main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
demo/src/__init__.py ADDED
File without changes
demo/src/compute.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+
2
+
3
+ def run_model(input_path, model_name="/home/user/app/model.h5"):
4
+ from livermask.utils.run import run_analysis
5
+ run_analysis(cpu=True, extension='.nii', path=input_path, output='prediction', verbose=True, vessels=False, name=model_name, mp_enabled=False)
6
+
demo/src/convert.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, model_name, class_name):
9
+ # global states
10
+ self.images = []
11
+ self.pred_images = []
12
+
13
+ self.nb_slider_items = 100
14
+
15
+ self.model_name = model_name
16
+ self.class_name = class_name
17
+
18
+ # define widgets not to be rendered immediantly, but later on
19
+ self.slider = gr.Slider(1, self.nb_slider_items, value=1, step=1, label="Which 2D slice to show")
20
+ self.volume_renderer = gr.Model3D(
21
+ clear_color=[0.0, 0.0, 0.0, 0.0],
22
+ label="3D Model",
23
+ visible=True
24
+ ).style(height=512)
25
+
26
+ def combine_ct_and_seg(self, img, pred):
27
+ return (img, [(pred, self.class_name)])
28
+
29
+ def upload_file(self, file):
30
+ return file.name
31
+
32
+ def load_mesh(self, mesh_file_name, model_name="/home/user/app/model.h5"):
33
+ path = mesh_file_name.name
34
+ run_model(path, model_name)
35
+ nifti_to_glb("prediction-livermask.nii")
36
+ self.images = load_ct_to_numpy("./files/test_ct.nii")
37
+ self.pred_images = load_pred_volume_to_numpy("./prediction-livermask.nii")
38
+ self.slider = self.slider.update(value=2)
39
+ return "./prediction.obj"
40
+
41
+ def get_img_pred_pair(self, k):
42
+ k = int(k) - 1
43
+ out = [gr.AnnotatedImage.update(visible=False)] * self.nb_slider_items
44
+ out[k] = gr.AnnotatedImage.update(self.combine_ct_and_seg(self.images[k], self.pred_images[k]), visible=True)
45
+ return out
46
+
47
+ def run(self):
48
+ with gr.Blocks() as demo:
49
+
50
+ with gr.Row().style(equal_height=True):
51
+ file_output = gr.File(file_types=[".nii", ".nii.nz"], file_count="single").style(full_width=False, size="sm")
52
+ file_output.upload(self.upload_file, file_output, file_output)
53
+
54
+ run_btn = gr.Button("Run analysis").style(full_width=False, size="sm")
55
+ run_btn.click(fn=lambda x: self.load_mesh(x, model_name=self.model_name), inputs=file_output, outputs=self.volume_renderer)
56
+
57
+ with gr.Row().style(equal_height=True):
58
+ with gr.Box():
59
+ image_boxes = []
60
+ for i in range(self.nb_slider_items):
61
+ visibility = True if i == 1 else False
62
+ t = gr.AnnotatedImage(visible=visibility)\
63
+ .style(color_map={self.class_name: "#ffae00"}, height=512, width=512)
64
+ image_boxes.append(t)
65
+
66
+ self.slider.change(self.get_img_pred_pair, self.slider, image_boxes)
67
+
68
+ with gr.Box():
69
+ self.volume_renderer.render()
70
+
71
+ with gr.Row():
72
+ self.slider.render()
73
+
74
+ # sharing app publicly -> share=True: https://gradio.app/sharing-your-app/
75
+ # inference times > 60 seconds -> need queue(): https://github.com/tloen/alpaca-lora/issues/60#issuecomment-1510006062
76
+ demo.queue().launch(server_name="0.0.0.0", server_port=7860, share=True)
demo/src/utils.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import nibabel as nib
2
+ import numpy as np
3
+
4
+
5
+ def load_ct_to_numpy(data_path):
6
+ if type(data_path) != str:
7
+ data_path = data_path.name
8
+
9
+ image = nib.load(data_path)
10
+ data = image.get_fdata()
11
+
12
+ data = np.rot90(data, k=1, axes=(0, 1))
13
+
14
+ data[data < -150] = -150
15
+ data[data > 250] = 250
16
+
17
+ data = data - np.amin(data)
18
+ data = data / np.amax(data) * 255
19
+ data = data.astype("uint8")
20
+
21
+ print(data.shape)
22
+ return [data[..., i] for i in range(data.shape[-1])]
23
+
24
+
25
+ def load_pred_volume_to_numpy(data_path):
26
+ if type(data_path) != str:
27
+ data_path = data_path.name
28
+
29
+ image = nib.load(data_path)
30
+ data = image.get_fdata()
31
+
32
+ data = np.rot90(data, k=1, axes=(0, 1))
33
+
34
+ data[data > 0] = 1
35
+ data = data.astype("uint8")
36
+
37
+ print(data.shape)
38
+ return [data[..., i] for i in range(data.shape[-1])]