mosamatic2 2.0.2__py3-none-any.whl → 2.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mosamatic2 might be problematic. Click here for more details.
- mosamatic2/app.py +4 -0
- mosamatic2/cli.py +34 -0
- mosamatic2/commands/__init__.py +0 -0
- mosamatic2/commands/calculatescores.py +73 -0
- mosamatic2/commands/createpngsfromsegmentations.py +65 -0
- mosamatic2/commands/dicom2nifti.py +46 -0
- mosamatic2/commands/rescaledicomimages.py +54 -0
- mosamatic2/commands/segmentmusclefatl3tensorflow.py +55 -0
- mosamatic2/constants.py +6 -3
- mosamatic2/core/data/__init__.py +5 -0
- mosamatic2/core/data/dicomimage.py +18 -0
- mosamatic2/core/data/dicomimageseries.py +26 -0
- mosamatic2/core/data/dixonseries.py +22 -0
- mosamatic2/core/data/filedata.py +26 -0
- mosamatic2/core/data/multidicomimage.py +30 -0
- mosamatic2/core/managers/logmanager.py +0 -2
- mosamatic2/core/pipelines/__init__.py +1 -0
- mosamatic2/core/pipelines/defaultpipeline.py +79 -0
- mosamatic2/core/pipelines/pipeline.py +14 -0
- mosamatic2/core/tasks/__init__.py +5 -0
- mosamatic2/core/tasks/calculatescorestask/__init__.py +0 -0
- mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py +149 -0
- mosamatic2/core/tasks/createpngsfromsegmentationstask/__init__.py +0 -0
- mosamatic2/core/tasks/createpngsfromsegmentationstask/createpngsfromsegmentationstask.py +52 -0
- mosamatic2/core/tasks/dicom2niftitask/__init__.py +0 -0
- mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py +24 -0
- mosamatic2/core/tasks/rescaledicomimagestask/__init__.py +0 -0
- mosamatic2/core/tasks/rescaledicomimagestask/rescaledicomimagestask.py +64 -0
- mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/__init__.py +0 -0
- mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/paramloader.py +39 -0
- mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorflowtask.py +121 -0
- mosamatic2/core/tasks/task.py +50 -0
- mosamatic2/core/utils.py +316 -0
- mosamatic2/server.py +112 -1
- mosamatic2/ui/mainwindow.py +150 -1
- mosamatic2/ui/resources/VERSION +1 -1
- mosamatic2/ui/widgets/dialogs/__init__.py +0 -0
- mosamatic2/ui/widgets/dialogs/dialog.py +16 -0
- mosamatic2/ui/widgets/dialogs/helpdialog.py +9 -0
- mosamatic2/ui/widgets/panels/__init__.py +0 -0
- mosamatic2/ui/widgets/panels/defaultpanel.py +31 -0
- mosamatic2/ui/widgets/panels/logpanel.py +65 -0
- mosamatic2/ui/widgets/panels/mainpanel.py +82 -0
- mosamatic2/ui/widgets/panels/pipelines/__init__.py +0 -0
- mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py +299 -0
- mosamatic2/ui/widgets/panels/stackedpanel.py +22 -0
- mosamatic2/ui/widgets/panels/taskpanel.py +6 -0
- mosamatic2/ui/widgets/panels/tasks/__init__.py +0 -0
- mosamatic2/ui/widgets/panels/tasks/calculatescorestaskpanel.py +215 -0
- mosamatic2/ui/widgets/panels/tasks/createpngsfromsegmentationstaskpanel.py +186 -0
- mosamatic2/ui/widgets/panels/tasks/dicom2niftitaskpanel.py +183 -0
- mosamatic2/ui/widgets/panels/tasks/rescaledicomimagestaskpanel.py +184 -0
- mosamatic2/ui/widgets/panels/tasks/segmentmusclefatl3tensorflowtaskpanel.py +216 -0
- mosamatic2/ui/widgets/panels/tasks/selectslicefromscantaskpanel.py +184 -0
- mosamatic2/ui/worker.py +29 -0
- {mosamatic2-2.0.2.dist-info → mosamatic2-2.0.4.dist-info}/METADATA +6 -2
- mosamatic2-2.0.4.dist-info/RECORD +74 -0
- {mosamatic2-2.0.2.dist-info → mosamatic2-2.0.4.dist-info}/entry_points.txt +1 -0
- mosamatic2-2.0.2.dist-info/RECORD +0 -26
- {mosamatic2-2.0.2.dist-info → mosamatic2-2.0.4.dist-info}/WHEEL +0 -0
mosamatic2/core/utils.py
CHANGED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import os
|
|
3
|
+
import click
|
|
4
|
+
import textwrap
|
|
5
|
+
import math
|
|
6
|
+
import pendulum
|
|
7
|
+
import numpy as np
|
|
8
|
+
import struct
|
|
9
|
+
import binascii
|
|
10
|
+
import pydicom
|
|
11
|
+
import warnings
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from pydicom.uid import (
|
|
14
|
+
ExplicitVRLittleEndian, ImplicitVRLittleEndian, ExplicitVRBigEndian
|
|
15
|
+
)
|
|
16
|
+
from PIL import Image
|
|
17
|
+
|
|
18
|
+
warnings.filterwarnings("ignore", message="Invalid value for VR UI:", category=UserWarning)
|
|
19
|
+
|
|
20
|
+
MUSCLE, VAT, SAT = 1, 5, 7
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_name_with_timestamp(prefix: str='') -> str:
|
|
24
|
+
tz = pendulum.local_timezone()
|
|
25
|
+
timestamp = pendulum.now(tz).strftime('%Y%m%d%H%M%S%f')[:17]
|
|
26
|
+
if prefix != '' and not prefix.endswith('-'):
|
|
27
|
+
prefix = prefix + '-'
|
|
28
|
+
name = f'{prefix}{timestamp}'
|
|
29
|
+
return name
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def show_doc_command(cli_group: click.Group) -> click.Command:
|
|
33
|
+
@click.command(name="showdoc")
|
|
34
|
+
@click.argument("command_name", required=False)
|
|
35
|
+
def show_doc(command_name):
|
|
36
|
+
commands = cli_group.commands
|
|
37
|
+
if command_name:
|
|
38
|
+
cmd = commands.get(command_name)
|
|
39
|
+
if cmd and hasattr(cmd, 'callback') and cmd.callback.__doc__:
|
|
40
|
+
print()
|
|
41
|
+
print(textwrap.dedent(cmd.callback.__doc__).strip())
|
|
42
|
+
else:
|
|
43
|
+
click.echo(f'No docstring found for command: {command_name}')
|
|
44
|
+
else:
|
|
45
|
+
click.echo('Available commands with docstrings:')
|
|
46
|
+
for name, cmd in commands.items():
|
|
47
|
+
if hasattr(cmd, 'callback') and cmd.callback.__doc__:
|
|
48
|
+
click.echo(f" {name}")
|
|
49
|
+
click.echo('\nUse: `mosamatic show-doc <command>` to view a commands docstring')
|
|
50
|
+
return show_doc
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def home_dir():
|
|
54
|
+
return Path.home()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def mosamatic_dir():
|
|
58
|
+
d = os.path.join(home_dir(), '.mosamatic2')
|
|
59
|
+
os.makedirs(d, exist_ok=True)
|
|
60
|
+
return d
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def mosamatic_data_dir():
|
|
64
|
+
data_dir = os.path.join(mosamatic_dir(), 'data')
|
|
65
|
+
os.makedirs(data_dir, exist_ok=True)
|
|
66
|
+
return data_dir
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def mosamatic_output_dir():
|
|
70
|
+
output_dir = os.path.join(mosamatic_data_dir(), 'output')
|
|
71
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
72
|
+
return output_dir
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def current_time_in_milliseconds():
|
|
76
|
+
return int(round(time.time() * 1000))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def current_time_in_seconds() -> int:
|
|
80
|
+
return int(round(current_time_in_milliseconds() / 1000.0))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def elapsed_time_in_milliseconds(start_time_in_milliseconds):
|
|
84
|
+
return current_time_in_milliseconds() - start_time_in_milliseconds
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def elapsed_time_in_seconds(start_time_in_seconds):
|
|
88
|
+
return current_time_in_seconds() - start_time_in_seconds
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def duration(seconds):
|
|
92
|
+
h = int(math.floor(seconds/3600.0))
|
|
93
|
+
remainder = seconds - h * 3600
|
|
94
|
+
m = int(math.floor(remainder/60.0))
|
|
95
|
+
remainder = remainder - m * 60
|
|
96
|
+
s = int(math.floor(remainder))
|
|
97
|
+
return '{} hours, {} minutes, {} seconds'.format(h, m, s)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def is_dicom(f):
|
|
101
|
+
try:
|
|
102
|
+
pydicom.dcmread(f, stop_before_pixels=True)
|
|
103
|
+
return True
|
|
104
|
+
except pydicom.errors.InvalidDicomError:
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def load_dicom(f, stop_before_pixels=False):
|
|
109
|
+
if is_dicom(f):
|
|
110
|
+
return pydicom.dcmread(f, stop_before_pixels=stop_before_pixels)
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def is_jpeg2000_compressed(p):
|
|
115
|
+
return p.file_meta.TransferSyntaxUID not in [ExplicitVRLittleEndian, ImplicitVRLittleEndian, ExplicitVRBigEndian]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def is_numpy_array(value):
|
|
119
|
+
return isinstance(value, np.array)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def load_numpy_array(f):
|
|
123
|
+
try:
|
|
124
|
+
return np.load(f)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def get_pixels_from_tag_file(tag_file_path):
|
|
130
|
+
f = open(tag_file_path, 'rb')
|
|
131
|
+
f.seek(0)
|
|
132
|
+
byte = f.read(1)
|
|
133
|
+
# Make sure to check the byte-value in Python 3!!
|
|
134
|
+
while byte != b'':
|
|
135
|
+
byte_hex = binascii.hexlify(byte)
|
|
136
|
+
if byte_hex == b'0c':
|
|
137
|
+
break
|
|
138
|
+
byte = f.read(1)
|
|
139
|
+
values = []
|
|
140
|
+
f.read(1)
|
|
141
|
+
while byte != b'':
|
|
142
|
+
v = struct.unpack('b', byte)
|
|
143
|
+
values.append(v)
|
|
144
|
+
byte = f.read(1)
|
|
145
|
+
values = np.asarray(values)
|
|
146
|
+
values = values.astype(np.uint16)
|
|
147
|
+
return values
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def get_rescale_params(p):
|
|
151
|
+
rescale_slope = getattr(p, 'RescaleSlope', None)
|
|
152
|
+
rescale_intercept = getattr(p, 'RescaleIntercept', None)
|
|
153
|
+
if rescale_slope is not None and rescale_intercept is not None:
|
|
154
|
+
return rescale_slope, rescale_intercept
|
|
155
|
+
# Try Enhanced DICOM structure
|
|
156
|
+
if 'SharedFunctionalGroupsSequence' in p:
|
|
157
|
+
fg = p.SharedFunctionalGroupsSequence[0]
|
|
158
|
+
if 'PixelValueTransformationSequence' in fg:
|
|
159
|
+
pvt = fg.PixelValueTransformationSequence[0]
|
|
160
|
+
rescale_slope = pvt.get('RescaleSlope', 1)
|
|
161
|
+
rescale_intercept = pvt.get('RescaleIntercept', 0)
|
|
162
|
+
return rescale_slope, rescale_intercept
|
|
163
|
+
return 1, 0
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def get_pixels_from_dicom_object(p, normalize=True):
|
|
167
|
+
pixels = p.pixel_array
|
|
168
|
+
if not normalize:
|
|
169
|
+
return pixels
|
|
170
|
+
if normalize is True: # Map pixel values back to original HU values
|
|
171
|
+
rescale_slope, rescale_intercept = get_rescale_params(p)
|
|
172
|
+
return rescale_slope * pixels + rescale_intercept
|
|
173
|
+
if isinstance(normalize, int):
|
|
174
|
+
return (pixels + np.min(pixels)) / (np.max(pixels) - np.min(pixels)) * normalize
|
|
175
|
+
if isinstance(normalize, list):
|
|
176
|
+
return (pixels + np.min(pixels)) / (np.max(pixels) - np.min(pixels)) * normalize[1] + normalize[0]
|
|
177
|
+
return pixels
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def convert_labels_to_157(label_image: np.array) -> np.array:
|
|
181
|
+
label_image157 = np.copy(label_image)
|
|
182
|
+
label_image157[label_image157 == 1] = 1
|
|
183
|
+
label_image157[label_image157 == 2] = 5
|
|
184
|
+
label_image157[label_image157 == 3] = 7
|
|
185
|
+
return label_image157
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def normalize_between(img: np.array, min_bound: int, max_bound: int) -> np.array:
|
|
189
|
+
img = (img - min_bound) / (max_bound - min_bound)
|
|
190
|
+
img[img > 1] = 0
|
|
191
|
+
img[img < 0] = 0
|
|
192
|
+
c = (img - np.min(img))
|
|
193
|
+
d = (np.max(img) - np.min(img))
|
|
194
|
+
img = np.divide(c, d, np.zeros_like(c), where=d != 0)
|
|
195
|
+
return img
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def apply_window_center_and_width(image: np.array, center: int, width: int) -> np.array:
|
|
199
|
+
image_min = center - width // 2
|
|
200
|
+
image_max = center + width // 2
|
|
201
|
+
windowed_image = np.clip(image, image_min, image_max)
|
|
202
|
+
windowed_image = ((windowed_image - image_min) / (image_max - image_min)) * 255.0
|
|
203
|
+
return windowed_image.astype(np.uint8)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def calculate_area(labels: np.array, label, pixel_spacing) -> float:
|
|
207
|
+
mask = np.copy(labels)
|
|
208
|
+
mask[mask != label] = 0
|
|
209
|
+
mask[mask == label] = 1
|
|
210
|
+
area = np.sum(mask) * (pixel_spacing[0] * pixel_spacing[1]) / 100.0
|
|
211
|
+
return area
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def calculate_index(area: float, height: float) -> float:
|
|
215
|
+
return area / (height * height)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def calculate_mean_radiation_attenuation(image: np.array, labels: np.array, label: int) -> float:
|
|
219
|
+
mask = np.copy(labels)
|
|
220
|
+
mask[mask != label] = 0
|
|
221
|
+
mask[mask == label] = 1
|
|
222
|
+
subtracted = image * mask
|
|
223
|
+
mask_sum = np.sum(mask)
|
|
224
|
+
if mask_sum > 0.0:
|
|
225
|
+
mean_radiation_attenuation = np.sum(subtracted) / np.sum(mask)
|
|
226
|
+
else:
|
|
227
|
+
# print('Sum of mask pixels is zero, return zero radiation attenuation')
|
|
228
|
+
mean_radiation_attenuation = 0.0
|
|
229
|
+
return mean_radiation_attenuation
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def calculate_dice_score(ground_truth: np.array, prediction: np.array, label: int) -> float:
|
|
233
|
+
numerator = prediction[ground_truth == label]
|
|
234
|
+
numerator[numerator != label] = 0
|
|
235
|
+
n = ground_truth[prediction == label]
|
|
236
|
+
n[n != label] = 0
|
|
237
|
+
if np.sum(numerator) != np.sum(n):
|
|
238
|
+
raise RuntimeError('Mismatch in Dice score calculation!')
|
|
239
|
+
denominator = (np.sum(prediction[prediction == label]) + np.sum(ground_truth[ground_truth == label]))
|
|
240
|
+
dice_score = np.sum(numerator) * 2.0 / denominator
|
|
241
|
+
return dice_score
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def convert_dicom_to_numpy_array(dicom_file_path: str, window_level: int=50, window_width: int=400, normalize=True) -> np.array:
|
|
245
|
+
p = pydicom.dcmread(dicom_file_path)
|
|
246
|
+
pixels = p.pixel_array
|
|
247
|
+
pixels = pixels.reshape(p.Rows, p.Columns)
|
|
248
|
+
if normalize:
|
|
249
|
+
b = p.RescaleIntercept
|
|
250
|
+
m = p.RescaleSlope
|
|
251
|
+
pixels = m * pixels + b
|
|
252
|
+
pixels = apply_window_center_and_width(pixels, window_level, window_width)
|
|
253
|
+
return pixels
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class ColorMap:
|
|
257
|
+
def __init__(self, name: str) -> None:
|
|
258
|
+
self._name = name
|
|
259
|
+
self._values = []
|
|
260
|
+
|
|
261
|
+
def name(self) -> str:
|
|
262
|
+
return self._name
|
|
263
|
+
|
|
264
|
+
def values(self):
|
|
265
|
+
return self._values
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class GrayScaleColorMap(ColorMap):
|
|
269
|
+
def __init__(self) -> None:
|
|
270
|
+
super(GrayScaleColorMap, self).__init__(name='GrayScaleColorMap')
|
|
271
|
+
# Implement your own gray scale map or let NumPy do this more efficiently?
|
|
272
|
+
pass
|
|
273
|
+
|
|
274
|
+
class AlbertaColorMap(ColorMap):
|
|
275
|
+
def __init__(self) -> None:
|
|
276
|
+
super(AlbertaColorMap, self).__init__(name='AlbertaColorMap')
|
|
277
|
+
for i in range(256):
|
|
278
|
+
if i == 1: # muscle
|
|
279
|
+
self.values().append([255, 0, 0])
|
|
280
|
+
elif i == 2: # inter-muscular adipose tissue
|
|
281
|
+
self.values().append([0, 255, 0])
|
|
282
|
+
elif i == 5: # visceral adipose tissue
|
|
283
|
+
self.values().append([255, 255, 0])
|
|
284
|
+
elif i == 7: # subcutaneous adipose tissue
|
|
285
|
+
self.values().append([0, 255, 255])
|
|
286
|
+
elif i == 12: # unknown
|
|
287
|
+
self.values().append([0, 0, 255])
|
|
288
|
+
else:
|
|
289
|
+
self.values().append([0, 0, 0])
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def apply_color_map(pixels: np.array, color_map: ColorMap) -> np.array:
|
|
293
|
+
pixels_new = np.zeros((*pixels.shape, 3), dtype=np.uint8)
|
|
294
|
+
np.take(color_map.values(), pixels, axis=0, out=pixels_new)
|
|
295
|
+
return pixels_new
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def convert_numpy_array_to_png_image(
|
|
299
|
+
numpy_array_file_path_or_object: str, output_dir_path: str, color_map: ColorMap=None, png_file_name: str=None, fig_width: int=10, fig_height: int=10) -> str:
|
|
300
|
+
if isinstance(numpy_array_file_path_or_object, str):
|
|
301
|
+
numpy_array = np.load(numpy_array_file_path_or_object)
|
|
302
|
+
else:
|
|
303
|
+
numpy_array = numpy_array_file_path_or_object
|
|
304
|
+
if not png_file_name:
|
|
305
|
+
raise RuntimeError('PNG file name required for NumPy array object')
|
|
306
|
+
if color_map:
|
|
307
|
+
numpy_array = apply_color_map(pixels=numpy_array, color_map=color_map)
|
|
308
|
+
image = Image.fromarray(numpy_array)
|
|
309
|
+
if not png_file_name:
|
|
310
|
+
numpy_array_file_name = os.path.split(numpy_array_file_path_or_object)[1]
|
|
311
|
+
png_file_name = numpy_array_file_name + '.png'
|
|
312
|
+
elif not png_file_name.endswith('.png'):
|
|
313
|
+
png_file_name += '.png'
|
|
314
|
+
png_file_path = os.path.join(output_dir_path, png_file_name)
|
|
315
|
+
image.save(png_file_path)
|
|
316
|
+
return png_file_path
|
mosamatic2/server.py
CHANGED
|
@@ -1,2 +1,113 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import mosamatic2.constants as constants
|
|
3
|
+
from flask import Flask, request
|
|
4
|
+
from mosamatic2.core.tasks import RescaleDicomImagesTask
|
|
5
|
+
from mosamatic2.core.tasks import SegmentMuscleFatL3TensorFlowTask
|
|
6
|
+
from mosamatic2.core.tasks import CalculateScoresTask
|
|
7
|
+
from mosamatic2.core.tasks import CreatePngsFromSegmentationsTask
|
|
8
|
+
from mosamatic2.core.tasks import Dicom2NiftiTask
|
|
9
|
+
|
|
10
|
+
app = Flask(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.route('/test')
|
|
14
|
+
def run_tests():
|
|
15
|
+
return 'PASSED'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.route('/rescaledicomimages')
|
|
19
|
+
def run_rescaledicomimages():
|
|
20
|
+
images = request.args.get('images')
|
|
21
|
+
target_size = request.args.get('target_size', default=512, type=int)
|
|
22
|
+
output = request.args.get('output')
|
|
23
|
+
overwrite = request.args.get('overwrite', default=True, type=bool)
|
|
24
|
+
task = RescaleDicomImagesTask(
|
|
25
|
+
inputs={'images': images},
|
|
26
|
+
params={'target_size': target_size},
|
|
27
|
+
output=output,
|
|
28
|
+
overwrite=overwrite,
|
|
29
|
+
)
|
|
30
|
+
task.run()
|
|
31
|
+
return 'PASSED'
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.route('/segmentmusclefatl3tensorflow')
|
|
35
|
+
def run_segmentmusclefatl3tensorflow():
|
|
36
|
+
images = request.args.get('images')
|
|
37
|
+
model_files = request.args.get('model_files')
|
|
38
|
+
output = request.args.get('output')
|
|
39
|
+
overwrite = request.args.get('overwrite', default=True, type=bool)
|
|
40
|
+
task = SegmentMuscleFatL3TensorFlowTask(
|
|
41
|
+
inputs={
|
|
42
|
+
'images': images,
|
|
43
|
+
'model_files': model_files,
|
|
44
|
+
},
|
|
45
|
+
params={'model_version': 1.0},
|
|
46
|
+
output=output,
|
|
47
|
+
overwrite=overwrite,
|
|
48
|
+
)
|
|
49
|
+
task.run()
|
|
50
|
+
return 'PASSED'
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.route('/calculatescores')
|
|
54
|
+
def run_calculatescores():
|
|
55
|
+
images = request.args.get('images')
|
|
56
|
+
segmentations = request.args.get('segmentations')
|
|
57
|
+
file_type = request.args.get('file_type', default='npy', type=str)
|
|
58
|
+
output = request.args.get('output')
|
|
59
|
+
overwrite = request.args.get('overwrite', default=True, type=bool)
|
|
60
|
+
task = CalculateScoresTask(
|
|
61
|
+
inputs={
|
|
62
|
+
'images': images,
|
|
63
|
+
'segmentations': segmentations,
|
|
64
|
+
},
|
|
65
|
+
params={'file_type': file_type},
|
|
66
|
+
output=output,
|
|
67
|
+
overwrite=overwrite,
|
|
68
|
+
)
|
|
69
|
+
task.run()
|
|
70
|
+
return 'PASSED'
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@app.route('/createpngsfromsegmentations')
|
|
74
|
+
def run_createpngsfromsegmentations():
|
|
75
|
+
segmentations = request.args.get('segmentations')
|
|
76
|
+
fig_width = request.args.get('fig_width', default=10, type=int)
|
|
77
|
+
fig_height = request.args.get('fig_height', default=10, type=int)
|
|
78
|
+
output = request.args.get('output')
|
|
79
|
+
overwrite = request.args.get('overwrite', default=True, type=bool)
|
|
80
|
+
task = CreatePngsFromSegmentationsTask(
|
|
81
|
+
inputs={'segmentations': segmentations},
|
|
82
|
+
params={
|
|
83
|
+
'fig_width': fig_width,
|
|
84
|
+
'fig_height': fig_height,
|
|
85
|
+
},
|
|
86
|
+
output=output,
|
|
87
|
+
overwrite=overwrite,
|
|
88
|
+
)
|
|
89
|
+
task.run()
|
|
90
|
+
return 'PASSED'
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@app.route('/dicom2nifti')
|
|
94
|
+
def run_dicom2nifti():
|
|
95
|
+
images = request.args.get('images')
|
|
96
|
+
output = request.args.get('output')
|
|
97
|
+
overwrite = request.args.get('overwrite', default=True, type=bool)
|
|
98
|
+
task = Dicom2NiftiTask(
|
|
99
|
+
inputs={'images': images},
|
|
100
|
+
params=None,
|
|
101
|
+
output=output,
|
|
102
|
+
overwrite=overwrite,
|
|
103
|
+
)
|
|
104
|
+
task.run()
|
|
105
|
+
return 'PASSED'
|
|
106
|
+
|
|
107
|
+
|
|
1
108
|
def main():
|
|
2
|
-
|
|
109
|
+
parser = argparse.ArgumentParser()
|
|
110
|
+
parser.add_argument('--port', type=int, default=constants.MOSAMATIC2_SERVER_PORT)
|
|
111
|
+
parser.add_argument('--debug', type=bool, default=constants.MOSAMATIC2_SERVER_DEBUG)
|
|
112
|
+
args = parser.parse_args()
|
|
113
|
+
app.run(host='0.0.0.0', port=args.port, debug=args.debug)
|
mosamatic2/ui/mainwindow.py
CHANGED
|
@@ -12,6 +12,14 @@ from PySide6.QtCore import Qt, QByteArray
|
|
|
12
12
|
from mosamatic2.ui.utils import version, resource_path, is_macos
|
|
13
13
|
from mosamatic2.core.managers.logmanager import LogManager
|
|
14
14
|
from mosamatic2.ui.settings import Settings
|
|
15
|
+
from mosamatic2.ui.widgets.panels.mainpanel import MainPanel
|
|
16
|
+
from mosamatic2.ui.widgets.panels.logpanel import LogPanel
|
|
17
|
+
from mosamatic2.ui.widgets.panels.tasks.rescaledicomimagestaskpanel import RescaleDicomImagesTaskPanel
|
|
18
|
+
from mosamatic2.ui.widgets.panels.tasks.segmentmusclefatl3tensorflowtaskpanel import SegmentMuscleFatL3TensorFlowTaskPanel
|
|
19
|
+
from mosamatic2.ui.widgets.panels.tasks.createpngsfromsegmentationstaskpanel import CreatePngsFromSegmentationsTaskPanel
|
|
20
|
+
from mosamatic2.ui.widgets.panels.tasks.calculatescorestaskpanel import CalculateScoresTaskPanel
|
|
21
|
+
from mosamatic2.ui.widgets.panels.tasks.dicom2niftitaskpanel import Dicom2NiftiTaskPanel
|
|
22
|
+
from mosamatic2.ui.widgets.panels.pipelines.defaultpipelinepanel import DefaultPipelinePanel
|
|
15
23
|
|
|
16
24
|
LOG = LogManager()
|
|
17
25
|
|
|
@@ -20,14 +28,68 @@ class MainWindow(QMainWindow):
|
|
|
20
28
|
def __init__(self):
|
|
21
29
|
super(MainWindow, self).__init__()
|
|
22
30
|
self._settings = None
|
|
31
|
+
self._main_panel = None
|
|
32
|
+
self._log_panel = None
|
|
33
|
+
self._decompress_dicom_files_task_panel = None
|
|
34
|
+
self._rescale_dicom_images_task_panel = None
|
|
35
|
+
self._segment_muscle_fat_l3_tensorflow_task_panel = None
|
|
36
|
+
self._create_pngs_from_segmentations_task_panel = None
|
|
37
|
+
self._calculate_scores_task_panel = None
|
|
38
|
+
self._dicom2nifti_task_panel = None
|
|
39
|
+
self._default_pipeline_panel = None
|
|
23
40
|
self.init_window()
|
|
24
41
|
|
|
25
42
|
def init_window(self):
|
|
26
43
|
self.setWindowTitle(f'{constants.MOSAMATIC2_WINDOW_TITLE} {version()}')
|
|
27
44
|
icon_file_name = constants.MOSAMATIC2_APP_ICON_FILE_NAME_MAC if is_macos() else constants.MOSAMATIC2_APP_ICON_FILE_NAME_WIN
|
|
28
|
-
|
|
45
|
+
icon_path = resource_path(os.path.join(constants.MOSAMATIC2_ICONS_DIR_PATH, icon_file_name))
|
|
46
|
+
self.setWindowIcon(QIcon(icon_path))
|
|
29
47
|
if not self.load_geometry_and_state():
|
|
30
48
|
self.set_default_size_and_position()
|
|
49
|
+
self.init_menus()
|
|
50
|
+
self.init_status_bar()
|
|
51
|
+
self.addDockWidget(Qt.DockWidgetArea.TopDockWidgetArea, self.main_panel())
|
|
52
|
+
self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.log_panel())
|
|
53
|
+
|
|
54
|
+
def init_menus(self):
|
|
55
|
+
self.init_app_menu()
|
|
56
|
+
self.init_tasks_menu()
|
|
57
|
+
self.init_pipelines_menu()
|
|
58
|
+
if is_macos():
|
|
59
|
+
self.menuBar().setNativeMenuBar(False)
|
|
60
|
+
|
|
61
|
+
def init_app_menu(self):
|
|
62
|
+
exit_action = QAction('Exit', self)
|
|
63
|
+
exit_action.triggered.connect(self.close)
|
|
64
|
+
app_menu = self.menuBar().addMenu('Application')
|
|
65
|
+
app_menu.addAction(exit_action)
|
|
66
|
+
|
|
67
|
+
def init_tasks_menu(self):
|
|
68
|
+
rescale_dicom_images_task_action = QAction('RescaleDicomImagesTask', self)
|
|
69
|
+
rescale_dicom_images_task_action.triggered.connect(self.handle_rescale_dicom_images_task_action)
|
|
70
|
+
segment_muscle_fat_l3_tensorflow_task_action = QAction('SegmentMuscleAndFatTask (TensorFlow)', self)
|
|
71
|
+
segment_muscle_fat_l3_tensorflow_task_action.triggered.connect(self.handle_segment_muscle_fat_l3_tensorflow_task_action)
|
|
72
|
+
calculate_scores_task_action = QAction('CalculateScoresTask', self)
|
|
73
|
+
calculate_scores_task_action.triggered.connect(self.handle_calculate_scores_task_action)
|
|
74
|
+
create_pngs_from_segmentations_task_action = QAction('CreatePngsFromSegmentationsTask', self)
|
|
75
|
+
create_pngs_from_segmentations_task_action.triggered.connect(self.handle_create_pngs_from_segmentations_task_action)
|
|
76
|
+
dicom2nifti_task_action = QAction('Dicom2NiftiTask', self)
|
|
77
|
+
dicom2nifti_task_action.triggered.connect(self.handle_dicom2nifti_task_action)
|
|
78
|
+
tasks_menu = self.menuBar().addMenu('Tasks')
|
|
79
|
+
tasks_menu.addAction(rescale_dicom_images_task_action)
|
|
80
|
+
tasks_menu.addAction(segment_muscle_fat_l3_tensorflow_task_action)
|
|
81
|
+
tasks_menu.addAction(calculate_scores_task_action)
|
|
82
|
+
tasks_menu.addAction(create_pngs_from_segmentations_task_action)
|
|
83
|
+
tasks_menu.addAction(dicom2nifti_task_action)
|
|
84
|
+
|
|
85
|
+
def init_pipelines_menu(self):
|
|
86
|
+
default_pipeline_action = QAction('DefaultPipeline', self)
|
|
87
|
+
default_pipeline_action.triggered.connect(self.handle_default_pipeline_action)
|
|
88
|
+
pipelines_menu = self.menuBar().addMenu('Pipelines')
|
|
89
|
+
pipelines_menu.addAction(default_pipeline_action)
|
|
90
|
+
|
|
91
|
+
def init_status_bar(self):
|
|
92
|
+
self.set_status('Ready')
|
|
31
93
|
|
|
32
94
|
# GET
|
|
33
95
|
|
|
@@ -36,8 +98,95 @@ class MainWindow(QMainWindow):
|
|
|
36
98
|
self._settings = Settings()
|
|
37
99
|
return self._settings
|
|
38
100
|
|
|
101
|
+
def main_panel(self):
|
|
102
|
+
if not self._main_panel:
|
|
103
|
+
self._main_panel = MainPanel(self)
|
|
104
|
+
self._main_panel.add_panel(self.rescale_dicom_images_task_panel(), 'rescaledicomimagestaskpanel')
|
|
105
|
+
self._main_panel.add_panel(self.segment_muscle_fat_l3_tensorflow_task_panel(), 'segmentmusclefatl3tensorflowtaskpanel')
|
|
106
|
+
self._main_panel.add_panel(self.create_pngs_from_segmentations_task_panel(), 'createpngsfromsegmentationstaskpanel')
|
|
107
|
+
self._main_panel.add_panel(self.calculate_scores_task_panel(), 'calculatescorestaskpanel')
|
|
108
|
+
self._main_panel.add_panel(self.dicom2nifti_task_panel(), 'dicom2niftitaskpanel')
|
|
109
|
+
self._main_panel.add_panel(self.default_pipeline_panel(), 'defaultpipelinepanel')
|
|
110
|
+
self._main_panel.select_panel('defaultpipelinepanel')
|
|
111
|
+
return self._main_panel
|
|
112
|
+
|
|
113
|
+
def log_panel(self):
|
|
114
|
+
if not self._log_panel:
|
|
115
|
+
self._log_panel = LogPanel()
|
|
116
|
+
LOG.add_listener(self._log_panel)
|
|
117
|
+
return self._log_panel
|
|
118
|
+
|
|
39
119
|
# MISCELLANEOUS
|
|
40
120
|
|
|
121
|
+
def rescale_dicom_images_task_panel(self):
|
|
122
|
+
if not self._rescale_dicom_images_task_panel:
|
|
123
|
+
self._rescale_dicom_images_task_panel = RescaleDicomImagesTaskPanel()
|
|
124
|
+
return self._rescale_dicom_images_task_panel
|
|
125
|
+
|
|
126
|
+
def segment_muscle_fat_l3_tensorflow_task_panel(self):
|
|
127
|
+
if not self._segment_muscle_fat_l3_tensorflow_task_panel:
|
|
128
|
+
self._segment_muscle_fat_l3_tensorflow_task_panel = SegmentMuscleFatL3TensorFlowTaskPanel()
|
|
129
|
+
return self._segment_muscle_fat_l3_tensorflow_task_panel
|
|
130
|
+
|
|
131
|
+
def create_pngs_from_segmentations_task_panel(self):
|
|
132
|
+
if not self._create_pngs_from_segmentations_task_panel:
|
|
133
|
+
self._create_pngs_from_segmentations_task_panel = CreatePngsFromSegmentationsTaskPanel()
|
|
134
|
+
return self._create_pngs_from_segmentations_task_panel
|
|
135
|
+
|
|
136
|
+
def calculate_scores_task_panel(self):
|
|
137
|
+
if not self._calculate_scores_task_panel:
|
|
138
|
+
self._calculate_scores_task_panel = CalculateScoresTaskPanel()
|
|
139
|
+
return self._calculate_scores_task_panel
|
|
140
|
+
|
|
141
|
+
def dicom2nifti_task_panel(self):
|
|
142
|
+
if not self._dicom2nifti_task_panel:
|
|
143
|
+
self._dicom2nifti_task_panel = Dicom2NiftiTaskPanel()
|
|
144
|
+
return self._dicom2nifti_task_panel
|
|
145
|
+
|
|
146
|
+
def default_pipeline_panel(self):
|
|
147
|
+
if not self._default_pipeline_panel:
|
|
148
|
+
self._default_pipeline_panel = DefaultPipelinePanel()
|
|
149
|
+
return self._default_pipeline_panel
|
|
150
|
+
|
|
151
|
+
# SETTERS
|
|
152
|
+
|
|
153
|
+
def set_status(self, message):
|
|
154
|
+
self.statusBar().showMessage(message)
|
|
155
|
+
|
|
156
|
+
# EVENT HANDLERS
|
|
157
|
+
|
|
158
|
+
def handle_rescale_dicom_images_task_action(self):
|
|
159
|
+
self.main_panel().select_panel('rescaledicomimagestaskpanel')
|
|
160
|
+
|
|
161
|
+
def handle_segment_muscle_fat_l3_tensorflow_task_action(self):
|
|
162
|
+
self.main_panel().select_panel('segmentmusclefatl3tensorflowtaskpanel')
|
|
163
|
+
|
|
164
|
+
def handle_create_pngs_from_segmentations_task_action(self):
|
|
165
|
+
self.main_panel().select_panel('createpngsfromsegmentationstaskpanel')
|
|
166
|
+
|
|
167
|
+
def handle_calculate_scores_task_action(self):
|
|
168
|
+
self.main_panel().select_panel('calculatescorestaskpanel')
|
|
169
|
+
|
|
170
|
+
def handle_dicom2nifti_task_action(self):
|
|
171
|
+
self.main_panel().select_panel('dicom2niftitaskpanel')
|
|
172
|
+
|
|
173
|
+
def handle_default_pipeline_action(self):
|
|
174
|
+
self.main_panel().select_panel('defaultpipelinepanel')
|
|
175
|
+
|
|
176
|
+
def showEvent(self, event):
|
|
177
|
+
return super().showEvent(event)
|
|
178
|
+
|
|
179
|
+
def closeEvent(self, event):
|
|
180
|
+
self.save_geometry_and_state()
|
|
181
|
+
# Save inputs and parameters of relevant panels
|
|
182
|
+
self.rescale_dicom_images_task_panel().save_inputs_and_parameters()
|
|
183
|
+
self.segment_muscle_fat_l3_tensorflow_task_panel().save_inputs_and_parameters()
|
|
184
|
+
self.create_pngs_from_segmentations_task_panel().save_inputs_and_parameters()
|
|
185
|
+
self.calculate_scores_task_panel().save_inputs_and_parameters()
|
|
186
|
+
self.dicom2nifti_task_panel().save_inputs_and_parameters()
|
|
187
|
+
self.default_pipeline_panel().save_inputs_and_parameters()
|
|
188
|
+
return super().closeEvent(event)
|
|
189
|
+
|
|
41
190
|
def load_geometry_and_state(self):
|
|
42
191
|
geometry = self.settings().get(constants.MOSAMATIC2_WINDOW_GEOMETRY_KEY)
|
|
43
192
|
state = self.settings().get(constants.MOSAMATIC2_WINDOW_STATE_KEY)
|
mosamatic2/ui/resources/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.0.
|
|
1
|
+
2.0.4
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from PySide6.QtWidgets import (
|
|
2
|
+
QDialog,
|
|
3
|
+
)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Dialog(QDialog):
|
|
7
|
+
def __init__(self, parent=None):
|
|
8
|
+
super(Dialog, self).__init__(parent)
|
|
9
|
+
self.setFixedWidth(400)
|
|
10
|
+
|
|
11
|
+
def clear(self):
|
|
12
|
+
raise NotImplementedError()
|
|
13
|
+
|
|
14
|
+
def showEvent(self, arg__1):
|
|
15
|
+
self.clear()
|
|
16
|
+
return super().showEvent(arg__1)
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QWidget, QPushButton
|
|
2
|
+
|
|
3
|
+
from mosamatic2.ui.widgets.dialogs.helpdialog import HelpDialog
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DefaultPanel(QWidget):
|
|
7
|
+
def __init__(self):
|
|
8
|
+
super(DefaultPanel, self).__init__()
|
|
9
|
+
self._title = None
|
|
10
|
+
self._help_dialog = None
|
|
11
|
+
self._show_help_button = None
|
|
12
|
+
|
|
13
|
+
def title(self):
|
|
14
|
+
return self._title
|
|
15
|
+
|
|
16
|
+
def set_title(self, title):
|
|
17
|
+
self._title = title
|
|
18
|
+
|
|
19
|
+
def help_dialog(self):
|
|
20
|
+
if not self._help_dialog:
|
|
21
|
+
self._help_dialog = HelpDialog()
|
|
22
|
+
return self._help_dialog
|
|
23
|
+
|
|
24
|
+
def show_help_button(self):
|
|
25
|
+
if not self._show_help_button:
|
|
26
|
+
self._show_help_button = QPushButton('Help')
|
|
27
|
+
self._show_help_button.clicked.connect(self.handle_show_help_button)
|
|
28
|
+
return self._show_help_button
|
|
29
|
+
|
|
30
|
+
def handle_show_help_button(self):
|
|
31
|
+
self.help_dialog().show()
|