andreped commited on
Commit
2b2fc13
2 Parent(s): 19716d6 87f4ea6

Merge pull request #20 from andreped/dev

Browse files

Added linting + filesize workflows; linting checks; removed old demo

.github/workflows/filesize.yml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Check file size
2
+ on: # or directly `on: [push]` to run the action on every push on any branch
3
+ pull_request:
4
+ branches: [ main ]
5
+
6
+ # to run this workflow manually from the Actions tab
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ check-filesize:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Check large files
14
+ uses: ActionsDesk/lfs-warning@v2.0
15
+ with:
16
+ filesizelimit: 10485760 # this is 10MB so we can sync to HF Spaces
.github/workflows/linting.yml ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Linting
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - '*'
7
+ pull_request:
8
+ branches:
9
+ - '*'
10
+ workflow_dispatch:
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-20.04
15
+ steps:
16
+ - uses: actions/checkout@v1
17
+ - name: Set up Python 3.7
18
+ uses: actions/setup-python@v2
19
+ with:
20
+ python-version: 3.7
21
+
22
+ - name: Install lint dependencies
23
+ run: pip install wheel setuptools black==22.3.0 isort==5.10.1 flake8==4.0.1
24
+
25
+ - name: Lint the code
26
+ run: sh shell/lint.sh
AeroPath/__init__.py DELETED
File without changes
AeroPath/gui.py DELETED
@@ -1,171 +0,0 @@
1
- import os
2
-
3
- import gradio as gr
4
-
5
- from .inference 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 = []
21
-
22
- # @TODO: This should be dynamically set based on chosen volume size
23
- self.nb_slider_items = 415
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
- "lungs": "CT_Lungs",
33
- }
34
-
35
- self.result_names = {
36
- "airways": "Airways",
37
- "lungs": "Lungs",
38
- }
39
-
40
- # define widgets not to be rendered immediately, but later on
41
- self.slider = gr.Slider(
42
- 1,
43
- self.nb_slider_items,
44
- value=1,
45
- step=1,
46
- label="Which 2D slice to show",
47
- )
48
- self.volume_renderer = gr.Model3D(
49
- clear_color=[0.0, 0.0, 0.0, 0.0],
50
- label="3D Model",
51
- visible=True,
52
- elem_id="model-3d",
53
- ).style(height=512)
54
-
55
- def set_class_name(self, value):
56
- print("Changed task to:", value)
57
- self.class_name = value
58
-
59
- def combine_ct_and_seg(self, img, pred):
60
- return (img, [(pred, self.class_name)])
61
-
62
- def upload_file(self, file):
63
- return file.name
64
-
65
- def process(self, mesh_file_name):
66
- path = mesh_file_name.name
67
- run_model(
68
- path,
69
- model_path=os.path.join(self.cwd, "resources/models/"),
70
- task=self.class_names[self.class_name],
71
- name=self.result_names[self.class_name],
72
- )
73
- nifti_to_glb("prediction.nii.gz")
74
-
75
- self.images = load_ct_to_numpy(path)
76
- self.pred_images = load_pred_volume_to_numpy("./prediction.nii.gz")
77
- return "./prediction.obj"
78
-
79
- def get_img_pred_pair(self, k):
80
- k = int(k) - 1
81
- out = [gr.AnnotatedImage.update(visible=False)] * self.nb_slider_items
82
- out[k] = gr.AnnotatedImage.update(
83
- self.combine_ct_and_seg(self.images[k], self.pred_images[k]),
84
- visible=True,
85
- )
86
- return out
87
-
88
- def run(self):
89
- css = """
90
- #model-3d {
91
- height: 512px;
92
- }
93
- #model-2d {
94
- height: 512px;
95
- margin: auto;
96
- }
97
- #upload {
98
- height: 120px;
99
- }
100
- """
101
- with gr.Blocks(css=css) as demo:
102
- with gr.Row():
103
- file_output = gr.File(file_count="single", elem_id="upload")
104
- file_output.upload(self.upload_file, file_output, file_output)
105
-
106
- model_selector = gr.Dropdown(
107
- list(self.class_names.keys()),
108
- label="Task",
109
- info="Which task to perform - one model for"
110
- "airways and lungs extraction",
111
- multiselect=False,
112
- size="sm",
113
- )
114
- model_selector.input(
115
- fn=lambda x: self.set_class_name(x),
116
- inputs=model_selector,
117
- outputs=None,
118
- )
119
-
120
- run_btn = gr.Button("Run analysis").style(
121
- full_width=False, size="lg"
122
- )
123
- run_btn.click(
124
- fn=lambda x: self.process(x),
125
- inputs=file_output,
126
- outputs=self.volume_renderer,
127
- )
128
-
129
- with gr.Row():
130
- gr.Examples(
131
- examples=[
132
- os.path.join(self.cwd, "test_thorax_CT_ds.nii"),
133
- os.path.join(self.cwd, "test_thorax_CT_ds.nii"),
134
- ],
135
- inputs=file_output,
136
- outputs=file_output,
137
- fn=self.upload_file,
138
- cache_examples=True,
139
- )
140
-
141
- with gr.Row():
142
- with gr.Box():
143
- with gr.Column():
144
- image_boxes = []
145
- for i in range(self.nb_slider_items):
146
- visibility = True if i == 1 else False
147
- t = gr.AnnotatedImage(
148
- visible=visibility, elem_id="model-2d"
149
- ).style(
150
- color_map={self.class_name: "#ffae00"},
151
- height=512,
152
- width=512,
153
- )
154
- image_boxes.append(t)
155
-
156
- self.slider.input(
157
- self.get_img_pred_pair, self.slider, image_boxes
158
- )
159
-
160
- self.slider.render()
161
-
162
- with gr.Box():
163
- self.volume_renderer.render()
164
-
165
- # sharing app publicly -> share=True:
166
- # https://gradio.app/sharing-your-app/
167
- # inference times > 60 seconds -> need queue():
168
- # https://github.com/tloen/alpaca-lora/issues/60#issuecomment-1510006062
169
- demo.queue().launch(
170
- server_name="0.0.0.0", server_port=7860, share=self.share
171
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
AeroPath/inference.py DELETED
@@ -1,103 +0,0 @@
1
- import configparser
2
- import logging
3
- import os
4
- import shutil
5
- import traceback
6
-
7
-
8
- def run_model(
9
- input_path: str,
10
- model_path: str,
11
- verbose: str = "info",
12
- task: str = "CT_Airways",
13
- name: str = "Airways",
14
- ):
15
- logging.basicConfig()
16
- logging.getLogger().setLevel(logging.WARNING)
17
-
18
- if verbose == "debug":
19
- logging.getLogger().setLevel(logging.DEBUG)
20
- elif verbose == "info":
21
- logging.getLogger().setLevel(logging.INFO)
22
- elif verbose == "error":
23
- logging.getLogger().setLevel(logging.ERROR)
24
- else:
25
- raise ValueError("Unsupported verbose value provided:", verbose)
26
-
27
- # delete patient/result folder if they exist
28
- if os.path.exists("./patient/"):
29
- shutil.rmtree("./patient/")
30
- if os.path.exists("./result/"):
31
- shutil.rmtree("./result/")
32
-
33
- patient_directory = ''
34
- output_path = ''
35
- try:
36
- # setup temporary patient directory
37
- filename = input_path.split("/")[-1]
38
- splits = filename.split(".")
39
- extension = ".".join(splits[1:])
40
- patient_directory = "./patient/"
41
- os.makedirs(patient_directory + "T0/", exist_ok=True)
42
- shutil.copy(
43
- input_path,
44
- patient_directory + "T0/" + splits[0] + "-t1gd." + extension,
45
- )
46
-
47
- # define output directory to save results
48
- output_path = "./result/prediction-" + splits[0] + "/"
49
- os.makedirs(output_path, exist_ok=True)
50
-
51
- # Setting up the configuration file
52
- rads_config = configparser.ConfigParser()
53
- rads_config.add_section("Default")
54
- rads_config.set("Default", "task", "mediastinum_diagnosis")
55
- rads_config.set("Default", "caller", "")
56
- rads_config.add_section("System")
57
- rads_config.set("System", "gpu_id", "-1")
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(
62
- "System",
63
- "pipeline_filename",
64
- os.path.join(model_path, task, "pipeline.json"),
65
- )
66
- rads_config.add_section("Runtime")
67
- rads_config.set(
68
- "Runtime", "reconstruction_method", "thresholding"
69
- ) # thresholding, probabilities
70
- rads_config.set("Runtime", "reconstruction_order", "resample_first")
71
- rads_config.set("Runtime", "use_preprocessed_data", "False")
72
-
73
- with open("rads_config.ini", "w") as f:
74
- rads_config.write(f)
75
-
76
- # finally, run inference
77
- from raidionicsrads.compute import run_rads
78
-
79
- run_rads(config_filename="rads_config.ini")
80
-
81
- # rename and move final result
82
- os.rename(
83
- "./result/prediction-"
84
- + splits[0]
85
- + "/T0/"
86
- + splits[0]
87
- + "-t1gd_annotation-"
88
- + name
89
- + ".nii.gz",
90
- "./prediction.nii.gz",
91
- )
92
- # Clean-up
93
- if os.path.exists(patient_directory):
94
- shutil.rmtree(patient_directory)
95
- if os.path.exists(output_path):
96
- shutil.rmtree(output_path)
97
- except Exception as e:
98
- print(traceback.format_exc())
99
- # Clean-up
100
- if os.path.exists(patient_directory):
101
- shutil.rmtree(patient_directory)
102
- if os.path.exists(output_path):
103
- shutil.rmtree(output_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
AeroPath/utils.py DELETED
@@ -1,67 +0,0 @@
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):
8
- if type(data_path) != str:
9
- data_path = data_path.name
10
-
11
- image = nib.load(data_path)
12
- resampled = resample_to_output(image, None, order=0)
13
- data = resampled.get_fdata()
14
-
15
- data = np.rot90(data, k=1, axes=(0, 1))
16
-
17
- data[data < -1024] = -1024
18
- data[data > 1024] = 1024
19
-
20
- data = data - np.amin(data)
21
- data = data / np.amax(data) * 255
22
- data = data.astype("uint8")
23
-
24
- print(data.shape)
25
- return [data[..., i] for i in range(data.shape[-1])]
26
-
27
-
28
- def load_pred_volume_to_numpy(data_path):
29
- if type(data_path) != str:
30
- data_path = data_path.name
31
-
32
- image = nib.load(data_path)
33
- resampled = resample_to_output(image, None, order=0)
34
- data = resampled.get_fdata()
35
-
36
- data = np.rot90(data, k=1, axes=(0, 1))
37
-
38
- data[data > 0] = 1
39
- data = data.astype("uint8")
40
-
41
- print(data.shape)
42
- return [data[..., i] for i in range(data.shape[-1])]
43
-
44
-
45
- def nifti_to_glb(path, output="prediction.obj"):
46
- # load NIFTI into numpy array
47
- image = nib.load(path)
48
- resampled = resample_to_output(image, [1, 1, 1], order=1)
49
- data = resampled.get_fdata().astype("uint8")
50
-
51
- # extract surface
52
- verts, faces, normals, values = marching_cubes(data, 0)
53
- faces += 1
54
-
55
- with open(output, "w") as thefile:
56
- for item in verts:
57
- thefile.write("v {0} {1} {2}\n".format(item[0], item[1], item[2]))
58
-
59
- for item in normals:
60
- thefile.write("vn {0} {1} {2}\n".format(item[0], item[1], item[2]))
61
-
62
- for item in faces:
63
- thefile.write(
64
- "f {0}//{0} {1}//{1} {2}//{2}\n".format(
65
- item[0], item[1], item[2]
66
- )
67
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py DELETED
@@ -1,41 +0,0 @@
1
- import os
2
- from argparse import ArgumentParser
3
-
4
- from AeroPath.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
-
40
- if __name__ == "__main__":
41
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
setup.cfg CHANGED
@@ -4,11 +4,11 @@ description-file = README.md
4
  [isort]
5
  force_single_line=True
6
  known_first_party=aeropath
7
- line_length=80
8
  profile=black
9
 
10
  [flake8]
11
  # imported but unused in __init__.py, that's ok.
12
  per-file-ignores=*__init__.py:F401
13
  ignore=E203,W503,W605,F632,E266,E731,E712,E741
14
- max-line-length=80
 
4
  [isort]
5
  force_single_line=True
6
  known_first_party=aeropath
7
+ line_length=160
8
  profile=black
9
 
10
  [flake8]
11
  # imported but unused in __init__.py, that's ok.
12
  per-file-ignores=*__init__.py:F401
13
  ignore=E203,W503,W605,F632,E266,E731,E712,E741
14
+ max-line-length=160
shell/format.sh ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ #!/bin/bash
2
+ isort --sl demo/src/ demo/app.py
3
+ black --line-length 80 demo/src/ demo/app.py
4
+ flake8 demo/src/ demo/app.py
shell/lint.sh ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ isort --check --sl -c demo/src/ demo/app.py
3
+ if ! [ $? -eq 0 ]
4
+ then
5
+ echo "Please run \"sh shell/format.sh\" to format the code."
6
+ exit 1
7
+ fi
8
+ echo "no issues with isort"
9
+ flake8 demo/src/ demo/app.py
10
+ if ! [ $? -eq 0 ]
11
+ then
12
+ echo "Please fix the code style issue."
13
+ exit 1
14
+ fi
15
+ echo "no issues with flake8"
16
+ black --check --line-length 80 demo/src/ demo/app.py
17
+ if ! [ $? -eq 0 ]
18
+ then
19
+ echo "Please run \"sh shell/format.sh\" to format the code."
20
+ exit 1
21
+ fi
22
+ echo "no issues with black"
23
+ echo "linting success!"