mosamatic2 2.0.1__py3-none-any.whl → 2.0.3__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.

Files changed (72) hide show
  1. mosamatic2/app.py +31 -1
  2. mosamatic2/cli.py +32 -0
  3. mosamatic2/commands/__init__.py +0 -0
  4. mosamatic2/commands/calculatescores.py +73 -0
  5. mosamatic2/commands/createpngsfromsegmentations.py +65 -0
  6. mosamatic2/commands/rescaledicomimages.py +54 -0
  7. mosamatic2/commands/segmentmusclefatl3tensorflow.py +55 -0
  8. mosamatic2/constants.py +27 -0
  9. mosamatic2/core/__init__.py +0 -0
  10. mosamatic2/core/data/__init__.py +5 -0
  11. mosamatic2/core/data/dicomimage.py +18 -0
  12. mosamatic2/core/data/dicomimageseries.py +26 -0
  13. mosamatic2/core/data/dixonseries.py +22 -0
  14. mosamatic2/core/data/filedata.py +26 -0
  15. mosamatic2/core/data/multidicomimage.py +30 -0
  16. mosamatic2/core/managers/__init__.py +0 -0
  17. mosamatic2/core/managers/logmanager.py +45 -0
  18. mosamatic2/core/managers/logmanagerlistener.py +3 -0
  19. mosamatic2/core/pipelines/__init__.py +1 -0
  20. mosamatic2/core/pipelines/defaultpipeline.py +79 -0
  21. mosamatic2/core/pipelines/pipeline.py +14 -0
  22. mosamatic2/core/singleton.py +9 -0
  23. mosamatic2/core/tasks/__init__.py +4 -0
  24. mosamatic2/core/tasks/calculatescorestask/__init__.py +0 -0
  25. mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py +149 -0
  26. mosamatic2/core/tasks/createpngsfromsegmentationstask/__init__.py +0 -0
  27. mosamatic2/core/tasks/createpngsfromsegmentationstask/createpngsfromsegmentationstask.py +52 -0
  28. mosamatic2/core/tasks/rescaledicomimagestask/__init__.py +0 -0
  29. mosamatic2/core/tasks/rescaledicomimagestask/rescaledicomimagestask.py +64 -0
  30. mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/__init__.py +0 -0
  31. mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/paramloader.py +39 -0
  32. mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorflowtask.py +121 -0
  33. mosamatic2/core/tasks/task.py +49 -0
  34. mosamatic2/core/utils.py +316 -0
  35. mosamatic2/server.py +98 -0
  36. mosamatic2/ui/__init__.py +0 -0
  37. mosamatic2/ui/mainwindow.py +196 -0
  38. mosamatic2/ui/resources/VERSION +1 -0
  39. mosamatic2/ui/resources/icons/mosamatic2.icns +0 -0
  40. mosamatic2/ui/resources/icons/mosamatic2.ico +0 -0
  41. mosamatic2/ui/resources/icons/spinner.gif +0 -0
  42. mosamatic2/ui/resources/images/body-composition.jpg +0 -0
  43. mosamatic2/ui/settings.py +62 -0
  44. mosamatic2/ui/utils.py +36 -0
  45. mosamatic2/ui/widgets/__init__.py +0 -0
  46. mosamatic2/ui/widgets/dialogs/__init__.py +0 -0
  47. mosamatic2/ui/widgets/dialogs/dialog.py +16 -0
  48. mosamatic2/ui/widgets/dialogs/helpdialog.py +9 -0
  49. mosamatic2/ui/widgets/panels/__init__.py +0 -0
  50. mosamatic2/ui/widgets/panels/defaultpanel.py +31 -0
  51. mosamatic2/ui/widgets/panels/logpanel.py +65 -0
  52. mosamatic2/ui/widgets/panels/mainpanel.py +82 -0
  53. mosamatic2/ui/widgets/panels/pipelines/__init__.py +0 -0
  54. mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py +299 -0
  55. mosamatic2/ui/widgets/panels/stackedpanel.py +22 -0
  56. mosamatic2/ui/widgets/panels/taskpanel.py +6 -0
  57. mosamatic2/ui/widgets/panels/tasks/__init__.py +0 -0
  58. mosamatic2/ui/widgets/panels/tasks/calculatescorestaskpanel.py +215 -0
  59. mosamatic2/ui/widgets/panels/tasks/createpngsfromsegmentationstaskpanel.py +186 -0
  60. mosamatic2/ui/widgets/panels/tasks/rescaledicomimagestaskpanel.py +184 -0
  61. mosamatic2/ui/widgets/panels/tasks/segmentmusclefatl3tensorflowtaskpanel.py +216 -0
  62. mosamatic2/ui/widgets/panels/tasks/selectslicefromscantaskpanel.py +184 -0
  63. mosamatic2/ui/widgets/splashscreen.py +101 -0
  64. mosamatic2/ui/worker.py +29 -0
  65. mosamatic2-2.0.3.dist-info/METADATA +34 -0
  66. mosamatic2-2.0.3.dist-info/RECORD +70 -0
  67. mosamatic2-2.0.3.dist-info/entry_points.txt +5 -0
  68. mosamatic2/api.py +0 -2
  69. mosamatic2-2.0.1.dist-info/METADATA +0 -12
  70. mosamatic2-2.0.1.dist-info/RECORD +0 -8
  71. mosamatic2-2.0.1.dist-info/entry_points.txt +0 -4
  72. {mosamatic2-2.0.1.dist-info → mosamatic2-2.0.3.dist-info}/WHEEL +0 -0
@@ -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 ADDED
@@ -0,0 +1,98 @@
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
+
9
+ app = Flask(__name__)
10
+
11
+
12
+ @app.route('/test')
13
+ def run_tests():
14
+ return 'PASSED'
15
+
16
+
17
+ @app.route('/rescaledicomimages')
18
+ def run_rescaledicomimages():
19
+ images = request.args.get('images')
20
+ target_size = request.args.get('target_size', default=512, type=int)
21
+ output = request.args.get('output')
22
+ overwrite = request.args.get('overwrite', default=True, type=bool)
23
+ task = RescaleDicomImagesTask(
24
+ inputs={'images': images},
25
+ params={'target_size': target_size},
26
+ output=output,
27
+ overwrite=overwrite,
28
+ )
29
+ task.run()
30
+ return 'PASSED'
31
+
32
+
33
+ @app.route('/segmentmusclefatl3tensorflow')
34
+ def run_segmentmusclefatl3tensorflow():
35
+ images = request.args.get('images')
36
+ model_files = request.args.get('model_files')
37
+ output = request.args.get('output')
38
+ overwrite = request.args.get('overwrite', default=True, type=bool)
39
+ task = SegmentMuscleFatL3TensorFlowTask(
40
+ inputs={
41
+ 'images': images,
42
+ 'model_files': model_files,
43
+ },
44
+ params={'model_version': 1.0},
45
+ output=output,
46
+ overwrite=overwrite,
47
+ )
48
+ task.run()
49
+ return 'PASSED'
50
+
51
+
52
+ @app.route('/calculatescores')
53
+ def run_calculatescores():
54
+ images = request.args.get('images')
55
+ segmentations = request.args.get('segmentations')
56
+ file_type = request.args.get('file_type', default='npy', type=str)
57
+ output = request.args.get('output')
58
+ overwrite = request.args.get('overwrite', default=True, type=bool)
59
+ task = CalculateScoresTask(
60
+ inputs={
61
+ 'images': images,
62
+ 'segmentations': segmentations,
63
+ },
64
+ params={'file_type': file_type},
65
+ output=output,
66
+ overwrite=overwrite,
67
+ )
68
+ task.run()
69
+ return 'PASSED'
70
+
71
+
72
+ @app.route('/createpngsfromsegmentations')
73
+ def run_createpngsfromsegmentations():
74
+ segmentations = request.args.get('segmentations')
75
+ fig_width = request.args.get('fig_width', default=10, type=int)
76
+ fig_height = request.args.get('fig_height', default=10, type=int)
77
+ output = request.args.get('output')
78
+ overwrite = request.args.get('overwrite', default=True, type=bool)
79
+ task = CreatePngsFromSegmentationsTask(
80
+ inputs={'segmentations': segmentations},
81
+ params={
82
+ 'fig_width': fig_width,
83
+ 'fig_height': fig_height,
84
+ },
85
+ output=output,
86
+ overwrite=overwrite,
87
+ )
88
+ task.run()
89
+ return 'PASSED'
90
+
91
+
92
+
93
+ def main():
94
+ parser = argparse.ArgumentParser()
95
+ parser.add_argument('--port', type=int, default=constants.MOSAMATIC2_SERVER_PORT)
96
+ parser.add_argument('--debug', type=bool, default=constants.MOSAMATIC2_SERVER_DEBUG)
97
+ args = parser.parse_args()
98
+ app.run(host='0.0.0.0', port=args.port, debug=args.debug)
File without changes
@@ -0,0 +1,196 @@
1
+ import os
2
+ import mosamatic2.constants as constants
3
+ from PySide6.QtWidgets import (
4
+ QMainWindow,
5
+ )
6
+ from PySide6.QtGui import (
7
+ QGuiApplication,
8
+ QAction,
9
+ QIcon,
10
+ )
11
+ from PySide6.QtCore import Qt, QByteArray
12
+ from mosamatic2.ui.utils import version, resource_path, is_macos
13
+ from mosamatic2.core.managers.logmanager import LogManager
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.pipelines.defaultpipelinepanel import DefaultPipelinePanel
22
+
23
+ LOG = LogManager()
24
+
25
+
26
+ class MainWindow(QMainWindow):
27
+ def __init__(self):
28
+ super(MainWindow, self).__init__()
29
+ self._settings = None
30
+ self._main_panel = None
31
+ self._log_panel = None
32
+ self._decompress_dicom_files_task_panel = None
33
+ self._rescale_dicom_images_task_panel = None
34
+ self._segment_muscle_fat_l3_tensorflow_task_panel = None
35
+ self._create_pngs_from_segmentations_task_panel = None
36
+ self._calculate_scores_task_panel = None
37
+ self._default_pipeline_panel = None
38
+ self.init_window()
39
+
40
+ def init_window(self):
41
+ self.setWindowTitle(f'{constants.MOSAMATIC2_WINDOW_TITLE} {version()}')
42
+ icon_file_name = constants.MOSAMATIC2_APP_ICON_FILE_NAME_MAC if is_macos() else constants.MOSAMATIC2_APP_ICON_FILE_NAME_WIN
43
+ icon_path = resource_path(os.path.join(constants.MOSAMATIC2_ICONS_DIR_PATH, icon_file_name))
44
+ self.setWindowIcon(QIcon(icon_path))
45
+ if not self.load_geometry_and_state():
46
+ self.set_default_size_and_position()
47
+ self.init_menus()
48
+ self.init_status_bar()
49
+ self.addDockWidget(Qt.DockWidgetArea.TopDockWidgetArea, self.main_panel())
50
+ self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.log_panel())
51
+
52
+ def init_menus(self):
53
+ self.init_app_menu()
54
+ self.init_tasks_menu()
55
+ self.init_pipelines_menu()
56
+ if is_macos():
57
+ self.menuBar().setNativeMenuBar(False)
58
+
59
+ def init_app_menu(self):
60
+ exit_action = QAction('Exit', self)
61
+ exit_action.triggered.connect(self.close)
62
+ app_menu = self.menuBar().addMenu('Application')
63
+ app_menu.addAction(exit_action)
64
+
65
+ def init_tasks_menu(self):
66
+ rescale_dicom_images_task_action = QAction('RescaleDicomImagesTask', self)
67
+ rescale_dicom_images_task_action.triggered.connect(self.handle_rescale_dicom_images_task_action)
68
+ segment_muscle_fat_l3_tensorflow_task_action = QAction('SegmentMuscleAndFatTask (TensorFlow)', self)
69
+ segment_muscle_fat_l3_tensorflow_task_action.triggered.connect(self.handle_segment_muscle_fat_l3_tensorflow_task_action)
70
+ calculate_scores_task_action = QAction('CalculateScoresTask', self)
71
+ calculate_scores_task_action.triggered.connect(self.handle_calculate_scores_task_action)
72
+ create_pngs_from_segmentations_task_action = QAction('CreatePngsFromSegmentationsTask', self)
73
+ create_pngs_from_segmentations_task_action.triggered.connect(self.handle_create_pngs_from_segmentations_task_action)
74
+ tasks_menu = self.menuBar().addMenu('Tasks')
75
+ tasks_menu.addAction(rescale_dicom_images_task_action)
76
+ tasks_menu.addAction(segment_muscle_fat_l3_tensorflow_task_action)
77
+ tasks_menu.addAction(calculate_scores_task_action)
78
+ tasks_menu.addAction(create_pngs_from_segmentations_task_action)
79
+
80
+ def init_pipelines_menu(self):
81
+ default_pipeline_action = QAction('DefaultPipeline', self)
82
+ default_pipeline_action.triggered.connect(self.handle_default_pipeline_action)
83
+ pipelines_menu = self.menuBar().addMenu('Pipelines')
84
+ pipelines_menu.addAction(default_pipeline_action)
85
+
86
+ def init_status_bar(self):
87
+ self.set_status('Ready')
88
+
89
+ # GET
90
+
91
+ def settings(self):
92
+ if not self._settings:
93
+ self._settings = Settings()
94
+ return self._settings
95
+
96
+ def main_panel(self):
97
+ if not self._main_panel:
98
+ self._main_panel = MainPanel(self)
99
+ self._main_panel.add_panel(self.rescale_dicom_images_task_panel(), 'rescaledicomimagestaskpanel')
100
+ self._main_panel.add_panel(self.segment_muscle_fat_l3_tensorflow_task_panel(), 'segmentmusclefatl3tensorflowtaskpanel')
101
+ self._main_panel.add_panel(self.create_pngs_from_segmentations_task_panel(), 'createpngsfromsegmentationstaskpanel')
102
+ self._main_panel.add_panel(self.calculate_scores_task_panel(), 'calculatescorestaskpanel')
103
+ self._main_panel.add_panel(self.default_pipeline_panel(), 'defaultpipelinepanel')
104
+ self._main_panel.select_panel('defaultpipelinepanel')
105
+ return self._main_panel
106
+
107
+ def log_panel(self):
108
+ if not self._log_panel:
109
+ self._log_panel = LogPanel()
110
+ LOG.add_listener(self._log_panel)
111
+ return self._log_panel
112
+
113
+ # MISCELLANEOUS
114
+
115
+ def rescale_dicom_images_task_panel(self):
116
+ if not self._rescale_dicom_images_task_panel:
117
+ self._rescale_dicom_images_task_panel = RescaleDicomImagesTaskPanel()
118
+ return self._rescale_dicom_images_task_panel
119
+
120
+ def segment_muscle_fat_l3_tensorflow_task_panel(self):
121
+ if not self._segment_muscle_fat_l3_tensorflow_task_panel:
122
+ self._segment_muscle_fat_l3_tensorflow_task_panel = SegmentMuscleFatL3TensorFlowTaskPanel()
123
+ return self._segment_muscle_fat_l3_tensorflow_task_panel
124
+
125
+ def create_pngs_from_segmentations_task_panel(self):
126
+ if not self._create_pngs_from_segmentations_task_panel:
127
+ self._create_pngs_from_segmentations_task_panel = CreatePngsFromSegmentationsTaskPanel()
128
+ return self._create_pngs_from_segmentations_task_panel
129
+
130
+ def calculate_scores_task_panel(self):
131
+ if not self._calculate_scores_task_panel:
132
+ self._calculate_scores_task_panel = CalculateScoresTaskPanel()
133
+ return self._calculate_scores_task_panel
134
+
135
+ def default_pipeline_panel(self):
136
+ if not self._default_pipeline_panel:
137
+ self._default_pipeline_panel = DefaultPipelinePanel()
138
+ return self._default_pipeline_panel
139
+
140
+ # SETTERS
141
+
142
+ def set_status(self, message):
143
+ self.statusBar().showMessage(message)
144
+
145
+ # EVENT HANDLERS
146
+
147
+ def handle_rescale_dicom_images_task_action(self):
148
+ self.main_panel().select_panel('rescaledicomimagestaskpanel')
149
+
150
+ def handle_segment_muscle_fat_l3_tensorflow_task_action(self):
151
+ self.main_panel().select_panel('segmentmusclefatl3tensorflowtaskpanel')
152
+
153
+ def handle_create_pngs_from_segmentations_task_action(self):
154
+ self.main_panel().select_panel('createpngsfromsegmentationstaskpanel')
155
+
156
+ def handle_calculate_scores_task_action(self):
157
+ self.main_panel().select_panel('calculatescorestaskpanel')
158
+
159
+ def handle_default_pipeline_action(self):
160
+ self.main_panel().select_panel('defaultpipelinepanel')
161
+
162
+ def showEvent(self, event):
163
+ return super().showEvent(event)
164
+
165
+ def closeEvent(self, event):
166
+ self.save_geometry_and_state()
167
+ # Save inputs and parameters of relevant panels
168
+ self.rescale_dicom_images_task_panel().save_inputs_and_parameters()
169
+ self.segment_muscle_fat_l3_tensorflow_task_panel().save_inputs_and_parameters()
170
+ self.create_pngs_from_segmentations_task_panel().save_inputs_and_parameters()
171
+ self.calculate_scores_task_panel().save_inputs_and_parameters()
172
+ self.default_pipeline_panel().save_inputs_and_parameters()
173
+ return super().closeEvent(event)
174
+
175
+ def load_geometry_and_state(self):
176
+ geometry = self.settings().get(constants.MOSAMATIC2_WINDOW_GEOMETRY_KEY)
177
+ state = self.settings().get(constants.MOSAMATIC2_WINDOW_STATE_KEY)
178
+ if isinstance(geometry, QByteArray) and self.restoreGeometry(geometry):
179
+ if isinstance(state, QByteArray):
180
+ self.restoreState(state)
181
+ return True
182
+ return False
183
+
184
+ def save_geometry_and_state(self):
185
+ self.settings().set(constants.MOSAMATIC2_WINDOW_GEOMETRY_KEY, self.saveGeometry())
186
+ self.settings().set(constants.MOSAMATIC2_WINDOW_STATE_KEY, self.saveState())
187
+
188
+ def set_default_size_and_position(self):
189
+ self.resize(constants.MOSAMATIC2_WINDOW_W, constants.MOSAMATIC2_WINDOW_H)
190
+ self.center_window()
191
+
192
+ def center_window(self):
193
+ screen = QGuiApplication.primaryScreen().geometry()
194
+ x = (screen.width() - self.geometry().width()) / 2
195
+ y = (screen.height() - self.geometry().height()) / 2
196
+ self.move(int(x), int(y))
@@ -0,0 +1 @@
1
+ 2.0.3
@@ -0,0 +1,62 @@
1
+ import mosamatic2.constants as constants
2
+ from PySide6.QtCore import QSettings
3
+ from mosamatic2.core.managers.logmanager import LogManager
4
+
5
+ LOG = LogManager()
6
+
7
+
8
+ class Settings(QSettings):
9
+ def __init__(self):
10
+ super(Settings, self).__init__(
11
+ QSettings.IniFormat,
12
+ QSettings.UserScope,
13
+ constants.MOSAMATIC2_BUNDLE_IDENTIFIER,
14
+ constants.MOSAMATIC2_APP_NAME,
15
+ )
16
+
17
+ def prepend_bundle_identifier_and_name(self, name):
18
+ return '{}.{}.{}'.format(constants.MOSAMATIC2_BUNDLE_IDENTIFIER, constants.MOSAMATIC2_APP_NAME, name)
19
+
20
+ def get(self, name, default=None):
21
+ if not name.startswith(constants.MOSAMATIC2_BUNDLE_IDENTIFIER):
22
+ name = self.prepend_bundle_identifier_and_name(name)
23
+ value = self.value(name)
24
+ if value is None or value == '':
25
+ return default
26
+ return value
27
+
28
+ def get_int(self, name, default=None):
29
+ try:
30
+ return int(self.get(name, default))
31
+ except ValueError as e:
32
+ return default
33
+
34
+ def get_float(self, name, default=None):
35
+ try:
36
+ return float(self.get(name, default))
37
+ except ValueError as e:
38
+ return default
39
+
40
+ def get_bool(self, name, default=None):
41
+ try:
42
+ value = self.get(name, default)
43
+ if value and isinstance(value, str):
44
+ if value == '0' or value.lower() == 'false':
45
+ return False
46
+ elif value == '1' or value.lower() == 'true':
47
+ return True
48
+ else:
49
+ return default
50
+ if value and isinstance(value, bool):
51
+ return value
52
+ except ValueError as e:
53
+ return default
54
+
55
+ def set(self, name, value):
56
+ name = self.prepend_bundle_identifier_and_name(name)
57
+ self.setValue(name, value)
58
+
59
+ def print(self):
60
+ LOG.info(f'Settings path: {self.fileName()}')
61
+ for key in self.allKeys():
62
+ LOG.info(f'Settings: {key}')