mosamatic2 2.0.24__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.
Files changed (136) hide show
  1. models.py +259 -0
  2. mosamatic2/__init__.py +0 -0
  3. mosamatic2/app.py +32 -0
  4. mosamatic2/cli.py +50 -0
  5. mosamatic2/commands/__init__.py +0 -0
  6. mosamatic2/commands/boadockerpipeline.py +48 -0
  7. mosamatic2/commands/calculatemaskstatistics.py +59 -0
  8. mosamatic2/commands/calculatescores.py +73 -0
  9. mosamatic2/commands/createdicomsummary.py +61 -0
  10. mosamatic2/commands/createpngsfromsegmentations.py +65 -0
  11. mosamatic2/commands/defaultdockerpipeline.py +84 -0
  12. mosamatic2/commands/defaultpipeline.py +70 -0
  13. mosamatic2/commands/dicom2nifti.py +55 -0
  14. mosamatic2/commands/liveranalysispipeline.py +61 -0
  15. mosamatic2/commands/rescaledicomimages.py +54 -0
  16. mosamatic2/commands/segmentmusclefatl3tensorflow.py +55 -0
  17. mosamatic2/commands/selectslicefromscans.py +66 -0
  18. mosamatic2/commands/totalsegmentator.py +77 -0
  19. mosamatic2/constants.py +27 -0
  20. mosamatic2/core/__init__.py +0 -0
  21. mosamatic2/core/data/__init__.py +5 -0
  22. mosamatic2/core/data/dicomimage.py +27 -0
  23. mosamatic2/core/data/dicomimageseries.py +26 -0
  24. mosamatic2/core/data/dixonseries.py +22 -0
  25. mosamatic2/core/data/filedata.py +26 -0
  26. mosamatic2/core/data/multidicomimage.py +30 -0
  27. mosamatic2/core/data/multiniftiimage.py +26 -0
  28. mosamatic2/core/data/multinumpyimage.py +26 -0
  29. mosamatic2/core/data/niftiimage.py +13 -0
  30. mosamatic2/core/data/numpyimage.py +13 -0
  31. mosamatic2/core/managers/__init__.py +0 -0
  32. mosamatic2/core/managers/logmanager.py +45 -0
  33. mosamatic2/core/managers/logmanagerlistener.py +3 -0
  34. mosamatic2/core/pipelines/__init__.py +4 -0
  35. mosamatic2/core/pipelines/boadockerpipeline/__init__.py +0 -0
  36. mosamatic2/core/pipelines/boadockerpipeline/boadockerpipeline.py +70 -0
  37. mosamatic2/core/pipelines/defaultdockerpipeline/__init__.py +0 -0
  38. mosamatic2/core/pipelines/defaultdockerpipeline/defaultdockerpipeline.py +28 -0
  39. mosamatic2/core/pipelines/defaultpipeline/__init__.py +0 -0
  40. mosamatic2/core/pipelines/defaultpipeline/defaultpipeline.py +90 -0
  41. mosamatic2/core/pipelines/liveranalysispipeline/__init__.py +0 -0
  42. mosamatic2/core/pipelines/liveranalysispipeline/liveranalysispipeline.py +48 -0
  43. mosamatic2/core/pipelines/pipeline.py +14 -0
  44. mosamatic2/core/singleton.py +9 -0
  45. mosamatic2/core/tasks/__init__.py +13 -0
  46. mosamatic2/core/tasks/applythresholdtosegmentationstask/__init__.py +0 -0
  47. mosamatic2/core/tasks/applythresholdtosegmentationstask/applythresholdtosegmentationstask.py +117 -0
  48. mosamatic2/core/tasks/calculatemaskstatisticstask/__init__.py +0 -0
  49. mosamatic2/core/tasks/calculatemaskstatisticstask/calculatemaskstatisticstask.py +104 -0
  50. mosamatic2/core/tasks/calculatescorestask/__init__.py +0 -0
  51. mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py +152 -0
  52. mosamatic2/core/tasks/createdicomsummarytask/__init__.py +0 -0
  53. mosamatic2/core/tasks/createdicomsummarytask/createdicomsummarytask.py +88 -0
  54. mosamatic2/core/tasks/createpngsfromsegmentationstask/__init__.py +0 -0
  55. mosamatic2/core/tasks/createpngsfromsegmentationstask/createpngsfromsegmentationstask.py +101 -0
  56. mosamatic2/core/tasks/dicom2niftitask/__init__.py +0 -0
  57. mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py +45 -0
  58. mosamatic2/core/tasks/rescaledicomimagestask/__init__.py +0 -0
  59. mosamatic2/core/tasks/rescaledicomimagestask/rescaledicomimagestask.py +64 -0
  60. mosamatic2/core/tasks/segmentationnifti2numpytask/__init__.py +0 -0
  61. mosamatic2/core/tasks/segmentationnifti2numpytask/segmentationnifti2numpytask.py +57 -0
  62. mosamatic2/core/tasks/segmentationnumpy2niftitask/__init__.py +0 -0
  63. mosamatic2/core/tasks/segmentationnumpy2niftitask/segmentationnumpy2niftitask.py +86 -0
  64. mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/__init__.py +0 -0
  65. mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/paramloader.py +39 -0
  66. mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorflowtask.py +122 -0
  67. mosamatic2/core/tasks/segmentmusclefatt4pytorchtask/__init__.py +0 -0
  68. mosamatic2/core/tasks/segmentmusclefatt4pytorchtask/paramloader.py +39 -0
  69. mosamatic2/core/tasks/segmentmusclefatt4pytorchtask/segmentmusclefatt4pytorchtask.py +128 -0
  70. mosamatic2/core/tasks/selectslicefromscanstask/__init__.py +0 -0
  71. mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py +249 -0
  72. mosamatic2/core/tasks/task.py +50 -0
  73. mosamatic2/core/tasks/totalsegmentatortask/__init__.py +0 -0
  74. mosamatic2/core/tasks/totalsegmentatortask/totalsegmentatortask.py +75 -0
  75. mosamatic2/core/utils.py +405 -0
  76. mosamatic2/server.py +146 -0
  77. mosamatic2/ui/__init__.py +0 -0
  78. mosamatic2/ui/mainwindow.py +426 -0
  79. mosamatic2/ui/resources/VERSION +1 -0
  80. mosamatic2/ui/resources/icons/mosamatic2.icns +0 -0
  81. mosamatic2/ui/resources/icons/mosamatic2.ico +0 -0
  82. mosamatic2/ui/resources/icons/spinner.gif +0 -0
  83. mosamatic2/ui/resources/images/body-composition.jpg +0 -0
  84. mosamatic2/ui/settings.py +62 -0
  85. mosamatic2/ui/utils.py +36 -0
  86. mosamatic2/ui/widgets/__init__.py +0 -0
  87. mosamatic2/ui/widgets/dialogs/__init__.py +0 -0
  88. mosamatic2/ui/widgets/dialogs/dialog.py +16 -0
  89. mosamatic2/ui/widgets/dialogs/helpdialog.py +9 -0
  90. mosamatic2/ui/widgets/panels/__init__.py +0 -0
  91. mosamatic2/ui/widgets/panels/defaultpanel.py +31 -0
  92. mosamatic2/ui/widgets/panels/logpanel.py +65 -0
  93. mosamatic2/ui/widgets/panels/mainpanel.py +82 -0
  94. mosamatic2/ui/widgets/panels/pipelines/__init__.py +0 -0
  95. mosamatic2/ui/widgets/panels/pipelines/boadockerpipelinepanel.py +195 -0
  96. mosamatic2/ui/widgets/panels/pipelines/defaultdockerpipelinepanel.py +314 -0
  97. mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py +302 -0
  98. mosamatic2/ui/widgets/panels/pipelines/liveranalysispipelinepanel.py +187 -0
  99. mosamatic2/ui/widgets/panels/pipelines/pipelinepanel.py +6 -0
  100. mosamatic2/ui/widgets/panels/settingspanel.py +16 -0
  101. mosamatic2/ui/widgets/panels/stackedpanel.py +22 -0
  102. mosamatic2/ui/widgets/panels/tasks/__init__.py +0 -0
  103. mosamatic2/ui/widgets/panels/tasks/applythresholdtosegmentationstaskpanel.py +271 -0
  104. mosamatic2/ui/widgets/panels/tasks/calculatemaskstatisticstaskpanel.py +215 -0
  105. mosamatic2/ui/widgets/panels/tasks/calculatescorestaskpanel.py +238 -0
  106. mosamatic2/ui/widgets/panels/tasks/createdicomsummarytaskpanel.py +206 -0
  107. mosamatic2/ui/widgets/panels/tasks/createpngsfromsegmentationstaskpanel.py +247 -0
  108. mosamatic2/ui/widgets/panels/tasks/dicom2niftitaskpanel.py +183 -0
  109. mosamatic2/ui/widgets/panels/tasks/rescaledicomimagestaskpanel.py +184 -0
  110. mosamatic2/ui/widgets/panels/tasks/segmentationnifti2numpytaskpanel.py +192 -0
  111. mosamatic2/ui/widgets/panels/tasks/segmentationnumpy2niftitaskpanel.py +213 -0
  112. mosamatic2/ui/widgets/panels/tasks/segmentmusclefatl3tensorflowtaskpanel.py +216 -0
  113. mosamatic2/ui/widgets/panels/tasks/segmentmusclefatt4pytorchtaskpanel.py +217 -0
  114. mosamatic2/ui/widgets/panels/tasks/selectslicefromscanstaskpanel.py +193 -0
  115. mosamatic2/ui/widgets/panels/tasks/taskpanel.py +6 -0
  116. mosamatic2/ui/widgets/panels/tasks/totalsegmentatortaskpanel.py +195 -0
  117. mosamatic2/ui/widgets/panels/visualizations/__init__.py +0 -0
  118. mosamatic2/ui/widgets/panels/visualizations/liversegmentvisualization/__init__.py +0 -0
  119. mosamatic2/ui/widgets/panels/visualizations/liversegmentvisualization/liversegmentpicker.py +96 -0
  120. mosamatic2/ui/widgets/panels/visualizations/liversegmentvisualization/liversegmentviewer.py +130 -0
  121. mosamatic2/ui/widgets/panels/visualizations/liversegmentvisualization/liversegmentvisualization.py +120 -0
  122. mosamatic2/ui/widgets/panels/visualizations/sliceselectionvisualization/__init__.py +0 -0
  123. mosamatic2/ui/widgets/panels/visualizations/sliceselectionvisualization/sliceselectionviewer.py +61 -0
  124. mosamatic2/ui/widgets/panels/visualizations/sliceselectionvisualization/sliceselectionvisualization.py +133 -0
  125. mosamatic2/ui/widgets/panels/visualizations/sliceselectionvisualization/slicetile.py +63 -0
  126. mosamatic2/ui/widgets/panels/visualizations/slicevisualization/__init__.py +0 -0
  127. mosamatic2/ui/widgets/panels/visualizations/slicevisualization/custominteractorstyle.py +80 -0
  128. mosamatic2/ui/widgets/panels/visualizations/slicevisualization/sliceviewer.py +116 -0
  129. mosamatic2/ui/widgets/panels/visualizations/slicevisualization/slicevisualization.py +141 -0
  130. mosamatic2/ui/widgets/panels/visualizations/visualization.py +6 -0
  131. mosamatic2/ui/widgets/splashscreen.py +101 -0
  132. mosamatic2/ui/worker.py +29 -0
  133. mosamatic2-2.0.24.dist-info/METADATA +43 -0
  134. mosamatic2-2.0.24.dist-info/RECORD +136 -0
  135. mosamatic2-2.0.24.dist-info/WHEEL +4 -0
  136. mosamatic2-2.0.24.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,238 @@
1
+ import os
2
+ import pathlib
3
+ import subprocess
4
+ import sys
5
+
6
+ from PySide6.QtWidgets import (
7
+ QLineEdit,
8
+ QCheckBox,
9
+ QSpinBox,
10
+ QHBoxLayout,
11
+ QVBoxLayout,
12
+ QFormLayout,
13
+ QPushButton,
14
+ QFileDialog,
15
+ QMessageBox,
16
+ )
17
+ from PySide6.QtCore import (
18
+ QThread,
19
+ Slot,
20
+ )
21
+
22
+ from mosamatic2.core.managers.logmanager import LogManager
23
+ from mosamatic2.ui.widgets.panels.tasks.taskpanel import TaskPanel
24
+ from mosamatic2.ui.settings import Settings
25
+ from mosamatic2.ui.utils import is_macos
26
+ from mosamatic2.ui.worker import Worker
27
+ from mosamatic2.core.tasks import CalculateScoresTask
28
+
29
+ LOG = LogManager()
30
+
31
+ PANEL_TITLE = 'CalculateScoresTask'
32
+ PANEL_NAME = 'calculatescorestaskpanel'
33
+
34
+
35
+ class CalculateScoresTaskPanel(TaskPanel):
36
+ def __init__(self):
37
+ super(CalculateScoresTaskPanel, self).__init__()
38
+ self.set_title(PANEL_TITLE)
39
+ self._images_dir_line_edit = None
40
+ self._images_dir_select_button = None
41
+ self._segmentations_dir_line_edit = None
42
+ self._segmentations_dir_select_button = None
43
+ self._output_dir_line_edit = None
44
+ self._output_dir_select_button = None
45
+ self._overwrite_checkbox = None
46
+ self._form_layout = None
47
+ self._run_task_button = None
48
+ self._open_excel_button = None
49
+ self._settings = None
50
+ self._task = None
51
+ self._worker = None
52
+ self._thread = None
53
+ self.init_layout()
54
+
55
+ def images_dir_line_edit(self):
56
+ if not self._images_dir_line_edit:
57
+ self._images_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/images_dir'))
58
+ return self._images_dir_line_edit
59
+
60
+ def images_dir_select_button(self):
61
+ if not self._images_dir_select_button:
62
+ self._images_dir_select_button = QPushButton('Select')
63
+ self._images_dir_select_button.clicked.connect(self.handle_images_dir_select_button)
64
+ return self._images_dir_select_button
65
+
66
+ def segmentations_dir_line_edit(self):
67
+ if not self._segmentations_dir_line_edit:
68
+ self._segmentations_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/segmentations_dir'))
69
+ return self._segmentations_dir_line_edit
70
+
71
+ def segmentations_dir_select_button(self):
72
+ if not self._segmentations_dir_select_button:
73
+ self._segmentations_dir_select_button = QPushButton('Select')
74
+ self._segmentations_dir_select_button.clicked.connect(self.handle_segmentations_dir_select_button)
75
+ return self._segmentations_dir_select_button
76
+
77
+ def output_dir_line_edit(self):
78
+ if not self._output_dir_line_edit:
79
+ self._output_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/output_dir'))
80
+ return self._output_dir_line_edit
81
+
82
+ def output_dir_select_button(self):
83
+ if not self._output_dir_select_button:
84
+ self._output_dir_select_button = QPushButton('Select')
85
+ self._output_dir_select_button.clicked.connect(self.handle_output_dir_select_button)
86
+ return self._output_dir_select_button
87
+
88
+ def overwrite_checkbox(self):
89
+ if not self._overwrite_checkbox:
90
+ self._overwrite_checkbox = QCheckBox('')
91
+ self._overwrite_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/overwrite', True))
92
+ return self._overwrite_checkbox
93
+
94
+ def form_layout(self):
95
+ if not self._form_layout:
96
+ self._form_layout = QFormLayout()
97
+ if is_macos():
98
+ self._form_layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
99
+ return self._form_layout
100
+
101
+ def run_task_button(self):
102
+ if not self._run_task_button:
103
+ self._run_task_button = QPushButton('Run task')
104
+ self._run_task_button.clicked.connect(self.handle_run_task_button)
105
+ return self._run_task_button
106
+
107
+ def open_excel_button(self):
108
+ if not self._open_excel_button:
109
+ self._open_excel_button = QPushButton('Open output in Excel')
110
+ self._open_excel_button.clicked.connect(self.handle_open_excel_button)
111
+ return self._open_excel_button
112
+
113
+ def settings(self):
114
+ if not self._settings:
115
+ self._settings = Settings()
116
+ return self._settings
117
+
118
+ def init_layout(self):
119
+ images_dir_layout = QHBoxLayout()
120
+ images_dir_layout.addWidget(self.images_dir_line_edit())
121
+ images_dir_layout.addWidget(self.images_dir_select_button())
122
+ segmentations_dir_layout = QHBoxLayout()
123
+ segmentations_dir_layout.addWidget(self.segmentations_dir_line_edit())
124
+ segmentations_dir_layout.addWidget(self.segmentations_dir_select_button())
125
+ output_dir_layout = QHBoxLayout()
126
+ output_dir_layout.addWidget(self.output_dir_line_edit())
127
+ output_dir_layout.addWidget(self.output_dir_select_button())
128
+ self.form_layout().addRow('Images directory', images_dir_layout)
129
+ self.form_layout().addRow('Segmentations directory', segmentations_dir_layout)
130
+ self.form_layout().addRow('Output directory', output_dir_layout)
131
+ self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
132
+ layout = QVBoxLayout()
133
+ layout.addLayout(self.form_layout())
134
+ layout.addWidget(self.run_task_button())
135
+ layout.addWidget(self.open_excel_button())
136
+ self.setLayout(layout)
137
+ self.setObjectName(PANEL_NAME)
138
+
139
+ def handle_images_dir_select_button(self):
140
+ last_directory = self.settings().get('last_directory')
141
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
142
+ if directory:
143
+ self.images_dir_line_edit().setText(directory)
144
+ self.settings().set('last_directory', directory)
145
+
146
+ def handle_segmentations_dir_select_button(self):
147
+ last_directory = self.settings().get('last_directory')
148
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
149
+ if directory:
150
+ self.segmentations_dir_line_edit().setText(directory)
151
+ self.settings().set('last_directory', directory)
152
+
153
+ def handle_output_dir_select_button(self):
154
+ last_directory = self.settings().get('last_directory')
155
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
156
+ if directory:
157
+ self.output_dir_line_edit().setText(directory)
158
+ self.settings().set('last_directory', directory)
159
+
160
+ def handle_run_task_button(self):
161
+ errors = self.check_inputs_and_parameters()
162
+ if len(errors) > 0:
163
+ error_message = 'Following errors were encountered:\n'
164
+ for error in errors:
165
+ error_message += f' - {error}\n'
166
+ QMessageBox.information(self, 'Error', error_message)
167
+ else:
168
+ LOG.info('Running task...')
169
+ self.run_task_button().setEnabled(False)
170
+ self.save_inputs_and_parameters()
171
+ self._task = CalculateScoresTask(
172
+ inputs={
173
+ 'images': self.images_dir_line_edit().text(),
174
+ 'segmentations': self.segmentations_dir_line_edit().text(),
175
+ },
176
+ params={'file_type': 'npy'},
177
+ output=self.output_dir_line_edit().text(),
178
+ overwrite=self.overwrite_checkbox().isChecked(),
179
+ )
180
+ self._worker = Worker(self._task)
181
+ self._thread = QThread()
182
+ self._worker.moveToThread(self._thread)
183
+ self._thread.started.connect(self._worker.run)
184
+ self._worker.progress.connect(self.handle_progress)
185
+ self._worker.status.connect(self.handle_status)
186
+ self._worker.finished.connect(self.handle_finished)
187
+ self._worker.finished.connect(self._thread.quit)
188
+ self._worker.finished.connect(self._worker.deleteLater)
189
+ self._thread.finished.connect(self._thread.deleteLater)
190
+ self._thread.start()
191
+
192
+ def handle_open_excel_button(self):
193
+ file_path = os.path.join(self.output_dir_line_edit().text(), 'calculatescorestask', 'bc_scores.xlsx')
194
+ file_path = pathlib.Path(file_path).expanduser().resolve()
195
+ if not file_path.exists():
196
+ raise FileNotFoundError(file_path)
197
+ if sys.platform.startswith('win'):
198
+ os.startfile(str(file_path))
199
+ elif sys.platform == 'darwin':
200
+ subprocess.run(['open', str(file_path)], check=True)
201
+ else:
202
+ subprocess.run(["xdg-open", str(file_path)], check=True)
203
+
204
+ @Slot(int)
205
+ def handle_progress(self, progress):
206
+ LOG.info(f'Progress: {progress} / 100%')
207
+
208
+ @Slot(str)
209
+ def handle_status(self, status):
210
+ LOG.info(f'Status: {status}')
211
+
212
+ @Slot()
213
+ def handle_finished(self):
214
+ self.run_task_button().setEnabled(True)
215
+
216
+ # HELPERS
217
+
218
+ def check_inputs_and_parameters(self):
219
+ errors = []
220
+ if self.images_dir_line_edit().text() == '':
221
+ errors.append('Empty images directory path')
222
+ if not os.path.isdir(self.images_dir_line_edit().text()):
223
+ errors.append('Images directory does not exist')
224
+ if self.segmentations_dir_line_edit().text() == '':
225
+ errors.append('Empty segmentations directory path')
226
+ if not os.path.isdir(self.segmentations_dir_line_edit().text()):
227
+ errors.append('Segmentations directory does not exist')
228
+ if self.output_dir_line_edit().text() == '':
229
+ errors.append('Empty output directory path')
230
+ if os.path.isdir(self.output_dir_line_edit().text()) and not self.overwrite_checkbox().isChecked():
231
+ errors.append('Output directory exists but overwrite=False. Please remove output directory first')
232
+ return errors
233
+
234
+ def save_inputs_and_parameters(self):
235
+ self.settings().set(f'{PANEL_NAME}/images_dir', self.images_dir_line_edit().text())
236
+ self.settings().set(f'{PANEL_NAME}/segmentations_dir', self.segmentations_dir_line_edit().text())
237
+ self.settings().set(f'{PANEL_NAME}/output_dir', self.output_dir_line_edit().text())
238
+ self.settings().set(f'{PANEL_NAME}/overwrite', self.overwrite_checkbox().isChecked())
@@ -0,0 +1,206 @@
1
+ import os
2
+ import subprocess
3
+ import pathlib
4
+ import sys
5
+
6
+ from PySide6.QtWidgets import (
7
+ QLineEdit,
8
+ QCheckBox,
9
+ QSpinBox,
10
+ QHBoxLayout,
11
+ QVBoxLayout,
12
+ QFormLayout,
13
+ QPushButton,
14
+ QFileDialog,
15
+ QMessageBox,
16
+ )
17
+ from PySide6.QtCore import (
18
+ QThread,
19
+ Slot,
20
+ )
21
+
22
+ from mosamatic2.core.managers.logmanager import LogManager
23
+ from mosamatic2.ui.widgets.panels.tasks.taskpanel import TaskPanel
24
+ from mosamatic2.ui.settings import Settings
25
+ from mosamatic2.ui.utils import is_macos
26
+ from mosamatic2.ui.worker import Worker
27
+ from mosamatic2.core.tasks import CreateDicomSummaryTask
28
+
29
+ LOG = LogManager()
30
+
31
+ PANEL_TITLE = 'CreateDicomSummaryTaskPanel'
32
+ PANEL_NAME = 'createdicomsummarytaskpanel'
33
+
34
+
35
+ class CreateDicomSummaryTaskPanel(TaskPanel):
36
+ def __init__(self):
37
+ super(CreateDicomSummaryTaskPanel, self).__init__()
38
+ self.set_title(PANEL_TITLE)
39
+ self._images_dir_line_edit = None
40
+ self._images_dir_select_button = None
41
+ self._output_dir_line_edit = None
42
+ self._output_dir_select_button = None
43
+ self._overwrite_checkbox = None
44
+ self._form_layout = None
45
+ self._run_task_button = None
46
+ self._open_excel_button = None
47
+ self._settings = None
48
+ self._task = None
49
+ self._worker = None
50
+ self._thread = None
51
+ self.init_layout()
52
+
53
+ def images_dir_line_edit(self):
54
+ if not self._images_dir_line_edit:
55
+ self._images_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/images_dir'))
56
+ return self._images_dir_line_edit
57
+
58
+ def images_dir_select_button(self):
59
+ if not self._images_dir_select_button:
60
+ self._images_dir_select_button = QPushButton('Select')
61
+ self._images_dir_select_button.clicked.connect(self.handle_images_dir_select_button)
62
+ return self._images_dir_select_button
63
+
64
+ def output_dir_line_edit(self):
65
+ if not self._output_dir_line_edit:
66
+ self._output_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/output_dir'))
67
+ return self._output_dir_line_edit
68
+
69
+ def output_dir_select_button(self):
70
+ if not self._output_dir_select_button:
71
+ self._output_dir_select_button = QPushButton('Select')
72
+ self._output_dir_select_button.clicked.connect(self.handle_output_dir_select_button)
73
+ return self._output_dir_select_button
74
+
75
+ def overwrite_checkbox(self):
76
+ if not self._overwrite_checkbox:
77
+ self._overwrite_checkbox = QCheckBox('')
78
+ self._overwrite_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/overwrite', True))
79
+ return self._overwrite_checkbox
80
+
81
+ def form_layout(self):
82
+ if not self._form_layout:
83
+ self._form_layout = QFormLayout()
84
+ if is_macos():
85
+ self._form_layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
86
+ return self._form_layout
87
+
88
+ def run_task_button(self):
89
+ if not self._run_task_button:
90
+ self._run_task_button = QPushButton('Run task')
91
+ self._run_task_button.clicked.connect(self.handle_run_task_button)
92
+ return self._run_task_button
93
+
94
+ def open_excel_button(self):
95
+ if not self._open_excel_button:
96
+ self._open_excel_button = QPushButton('Open output in Excel')
97
+ self._open_excel_button.clicked.connect(self.handle_open_excel_button)
98
+ return self._open_excel_button
99
+
100
+ def settings(self):
101
+ if not self._settings:
102
+ self._settings = Settings()
103
+ return self._settings
104
+
105
+ def init_layout(self):
106
+ images_dir_layout = QHBoxLayout()
107
+ images_dir_layout.addWidget(self.images_dir_line_edit())
108
+ images_dir_layout.addWidget(self.images_dir_select_button())
109
+ output_dir_layout = QHBoxLayout()
110
+ output_dir_layout.addWidget(self.output_dir_line_edit())
111
+ output_dir_layout.addWidget(self.output_dir_select_button())
112
+ self.form_layout().addRow('Images directory', images_dir_layout)
113
+ self.form_layout().addRow('Output directory', output_dir_layout)
114
+ self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
115
+ layout = QVBoxLayout()
116
+ layout.addLayout(self.form_layout())
117
+ layout.addWidget(self.run_task_button())
118
+ layout.addWidget(self.open_excel_button())
119
+ self.setLayout(layout)
120
+ self.setObjectName(PANEL_NAME)
121
+
122
+ def handle_images_dir_select_button(self):
123
+ last_directory = self.settings().get('last_directory')
124
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
125
+ if directory:
126
+ self.images_dir_line_edit().setText(directory)
127
+ self.settings().set('last_directory', directory)
128
+
129
+ def handle_output_dir_select_button(self):
130
+ last_directory = self.settings().get('last_directory')
131
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
132
+ if directory:
133
+ self.output_dir_line_edit().setText(directory)
134
+ self.settings().set('last_directory', directory)
135
+
136
+ def handle_run_task_button(self):
137
+ errors = self.check_inputs_and_parameters()
138
+ if len(errors) > 0:
139
+ error_message = 'Following errors were encountered:\n'
140
+ for error in errors:
141
+ error_message += f' - {error}\n'
142
+ QMessageBox.information(self, 'Error', error_message)
143
+ else:
144
+ LOG.info('Running task...')
145
+ self.run_task_button().setEnabled(False)
146
+ self.save_inputs_and_parameters()
147
+ self._task = CreateDicomSummaryTask(
148
+ inputs={'directory': self.images_dir_line_edit().text()},
149
+ params=None,
150
+ output=self.output_dir_line_edit().text(),
151
+ overwrite=self.overwrite_checkbox().isChecked(),
152
+ )
153
+ self._worker = Worker(self._task)
154
+ self._thread = QThread()
155
+ self._worker.moveToThread(self._thread)
156
+ self._thread.started.connect(self._worker.run)
157
+ self._worker.progress.connect(self.handle_progress)
158
+ self._worker.status.connect(self.handle_status)
159
+ self._worker.finished.connect(self.handle_finished)
160
+ self._worker.finished.connect(self._thread.quit)
161
+ self._worker.finished.connect(self._worker.deleteLater)
162
+ self._thread.finished.connect(self._thread.deleteLater)
163
+ self._thread.start()
164
+
165
+ def handle_open_excel_button(self):
166
+ file_path = os.path.join(self.output_dir_line_edit().text(), 'createdicomsummarytask', 'summary.xlsx')
167
+ file_path = pathlib.Path(file_path).expanduser().resolve()
168
+ if not file_path.exists():
169
+ raise FileNotFoundError(file_path)
170
+ if sys.platform.startswith('win'):
171
+ os.startfile(str(file_path))
172
+ elif sys.platform == 'darwin':
173
+ subprocess.run(['open', str(file_path)], check=True)
174
+ else:
175
+ subprocess.run(["xdg-open", str(file_path)], check=True)
176
+
177
+ @Slot(int)
178
+ def handle_progress(self, progress):
179
+ LOG.info(f'Progress: {progress} / 100%')
180
+
181
+ @Slot(str)
182
+ def handle_status(self, status):
183
+ LOG.info(f'Status: {status}')
184
+
185
+ @Slot()
186
+ def handle_finished(self):
187
+ self.run_task_button().setEnabled(True)
188
+
189
+ # HELPERS
190
+
191
+ def check_inputs_and_parameters(self):
192
+ errors = []
193
+ if self.images_dir_line_edit().text() == '':
194
+ errors.append('Empty images directory path')
195
+ if not os.path.isdir(self.images_dir_line_edit().text()):
196
+ errors.append('Images directory does not exist')
197
+ if self.output_dir_line_edit().text() == '':
198
+ errors.append('Empty output directory path')
199
+ if os.path.isdir(self.output_dir_line_edit().text()) and not self.overwrite_checkbox().isChecked():
200
+ errors.append('Output directory exists but overwrite=False. Please remove output directory first')
201
+ return errors
202
+
203
+ def save_inputs_and_parameters(self):
204
+ self.settings().set(f'{PANEL_NAME}/images_dir', self.images_dir_line_edit().text())
205
+ self.settings().set(f'{PANEL_NAME}/output_dir', self.output_dir_line_edit().text())
206
+ self.settings().set(f'{PANEL_NAME}/overwrite', self.overwrite_checkbox().isChecked())
@@ -0,0 +1,247 @@
1
+ import os
2
+
3
+ from PySide6.QtWidgets import (
4
+ QLineEdit,
5
+ QCheckBox,
6
+ QSpinBox,
7
+ QDoubleSpinBox,
8
+ QHBoxLayout,
9
+ QVBoxLayout,
10
+ QFormLayout,
11
+ QPushButton,
12
+ QFileDialog,
13
+ QMessageBox,
14
+ )
15
+ from PySide6.QtCore import (
16
+ QThread,
17
+ Slot,
18
+ )
19
+
20
+ from mosamatic2.core.managers.logmanager import LogManager
21
+ from mosamatic2.ui.widgets.panels.tasks.taskpanel import TaskPanel
22
+ from mosamatic2.ui.settings import Settings
23
+ from mosamatic2.ui.utils import is_macos
24
+ from mosamatic2.ui.worker import Worker
25
+ from mosamatic2.core.tasks import CreatePngsFromSegmentationsTask
26
+
27
+ LOG = LogManager()
28
+
29
+ PANEL_TITLE = 'CreatePngsFromSegmentationsTask'
30
+ PANEL_NAME = 'createpngsfromsegmentationstaskpanel'
31
+
32
+
33
+ class CreatePngsFromSegmentationsTaskPanel(TaskPanel):
34
+ def __init__(self):
35
+ super(CreatePngsFromSegmentationsTaskPanel, self).__init__()
36
+ self.set_title(PANEL_TITLE)
37
+ self._images_dir_line_edit = None
38
+ self._images_dir_select_line_button = None
39
+ self._segmentations_dir_line_edit = None
40
+ self._segmentations_dir_select_button = None
41
+ self._output_dir_line_edit = None
42
+ self._output_dir_select_button = None
43
+ self._hu_low_spinbox = None
44
+ self._hu_high_spinbox = None
45
+ self._alpha_spinbox = None
46
+ self._overwrite_checkbox = None
47
+ self._form_layout = None
48
+ self._run_task_button = None
49
+ self._settings = None
50
+ self._task = None
51
+ self._worker = None
52
+ self._thread = None
53
+ self.init_layout()
54
+
55
+ def images_dir_line_edit(self):
56
+ if not self._images_dir_line_edit:
57
+ self._images_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/images_dir'))
58
+ return self._images_dir_line_edit
59
+
60
+ def images_dir_select_button(self):
61
+ if not self._images_dir_select_line_button:
62
+ self._images_dir_select_line_button = QPushButton('Select')
63
+ self._images_dir_select_line_button.clicked.connect(self.handle_images_dir_select_button)
64
+ return self._images_dir_select_line_button
65
+
66
+ def segmentations_dir_line_edit(self):
67
+ if not self._segmentations_dir_line_edit:
68
+ self._segmentations_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/segmentations_dir'))
69
+ return self._segmentations_dir_line_edit
70
+
71
+ def segmentations_dir_select_button(self):
72
+ if not self._segmentations_dir_select_button:
73
+ self._segmentations_dir_select_button = QPushButton('Select')
74
+ self._segmentations_dir_select_button.clicked.connect(self.handle_segmentations_dir_select_button)
75
+ return self._segmentations_dir_select_button
76
+
77
+ def output_dir_line_edit(self):
78
+ if not self._output_dir_line_edit:
79
+ self._output_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/output_dir'))
80
+ return self._output_dir_line_edit
81
+
82
+ def output_dir_select_button(self):
83
+ if not self._output_dir_select_button:
84
+ self._output_dir_select_button = QPushButton('Select')
85
+ self._output_dir_select_button.clicked.connect(self.handle_output_dir_select_button)
86
+ return self._output_dir_select_button
87
+
88
+ def hu_low_spinbox(self):
89
+ if not self._hu_low_spinbox:
90
+ self._hu_low_spinbox = QSpinBox(minimum=-200, maximum=150, value=-29)
91
+ self._hu_low_spinbox.setValue(self.settings().get_int(f'{PANEL_NAME}/hu_low', -29))
92
+ return self._hu_low_spinbox
93
+
94
+ def hu_high_spinbox(self):
95
+ if not self._hu_high_spinbox:
96
+ self._hu_high_spinbox = QSpinBox(minimum=-200, maximum=150, value=150)
97
+ self._hu_high_spinbox.setValue(self.settings().get_int(f'{PANEL_NAME}/hu_high', 150))
98
+ return self._hu_high_spinbox
99
+
100
+ def alpha_spinbox(self):
101
+ if not self._alpha_spinbox:
102
+ self._alpha_spinbox = QDoubleSpinBox(minimum=0.0, maximum=1.0, value=1.0)
103
+ self._alpha_spinbox.setValue(self.settings().get_float(f'{PANEL_NAME}/alpha', 1.0))
104
+ return self._alpha_spinbox
105
+
106
+ def overwrite_checkbox(self):
107
+ if not self._overwrite_checkbox:
108
+ self._overwrite_checkbox = QCheckBox('')
109
+ self._overwrite_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/overwrite', True))
110
+ return self._overwrite_checkbox
111
+
112
+ def form_layout(self):
113
+ if not self._form_layout:
114
+ self._form_layout = QFormLayout()
115
+ if is_macos():
116
+ self._form_layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
117
+ return self._form_layout
118
+
119
+ def run_task_button(self):
120
+ if not self._run_task_button:
121
+ self._run_task_button = QPushButton('Run task')
122
+ self._run_task_button.clicked.connect(self.handle_run_task_button)
123
+ return self._run_task_button
124
+
125
+ def settings(self):
126
+ if not self._settings:
127
+ self._settings = Settings()
128
+ return self._settings
129
+
130
+ def init_layout(self):
131
+ images_dir_layout = QHBoxLayout()
132
+ images_dir_layout.addWidget(self.images_dir_line_edit())
133
+ images_dir_layout.addWidget(self.images_dir_select_button())
134
+ segmentations_dir_layout = QHBoxLayout()
135
+ segmentations_dir_layout.addWidget(self.segmentations_dir_line_edit())
136
+ segmentations_dir_layout.addWidget(self.segmentations_dir_select_button())
137
+ output_dir_layout = QHBoxLayout()
138
+ output_dir_layout.addWidget(self.output_dir_line_edit())
139
+ output_dir_layout.addWidget(self.output_dir_select_button())
140
+ self.form_layout().addRow('Images directory', images_dir_layout)
141
+ self.form_layout().addRow('Segmentations directory', segmentations_dir_layout)
142
+ self.form_layout().addRow('Output directory', output_dir_layout)
143
+ self.form_layout().addRow('HU low', self.hu_low_spinbox())
144
+ self.form_layout().addRow('HU high', self.hu_high_spinbox())
145
+ self.form_layout().addRow('Alpha', self.alpha_spinbox())
146
+ self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
147
+ layout = QVBoxLayout()
148
+ layout.addLayout(self.form_layout())
149
+ layout.addWidget(self.run_task_button())
150
+ self.setLayout(layout)
151
+ self.setObjectName(PANEL_NAME)
152
+
153
+ def handle_images_dir_select_button(self):
154
+ last_directory = self.settings().get('last_directory')
155
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
156
+ if directory:
157
+ self.images_dir_line_edit().setText(directory)
158
+ self.settings().set('last_directory', directory)
159
+
160
+ def handle_segmentations_dir_select_button(self):
161
+ last_directory = self.settings().get('last_directory')
162
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
163
+ if directory:
164
+ self.segmentations_dir_line_edit().setText(directory)
165
+ self.settings().set('last_directory', directory)
166
+
167
+ def handle_output_dir_select_button(self):
168
+ last_directory = self.settings().get('last_directory')
169
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
170
+ if directory:
171
+ self.output_dir_line_edit().setText(directory)
172
+ self.settings().set('last_directory', directory)
173
+
174
+ def handle_run_task_button(self):
175
+ errors = self.check_inputs_and_parameters()
176
+ if len(errors) > 0:
177
+ error_message = 'Following errors were encountered:\n'
178
+ for error in errors:
179
+ error_message += f' - {error}\n'
180
+ QMessageBox.information(self, 'Error', error_message)
181
+ else:
182
+ LOG.info('Running task...')
183
+ self.run_task_button().setEnabled(False)
184
+ self.save_inputs_and_parameters()
185
+ self._task = CreatePngsFromSegmentationsTask(
186
+ inputs={
187
+ 'images': self.images_dir_line_edit().text(),
188
+ 'segmentations': self.segmentations_dir_line_edit().text()
189
+ },
190
+ params={
191
+ 'fig_width': 10,
192
+ 'fig_height': 10,
193
+ 'hu_low': self.hu_low_spinbox().value(),
194
+ 'hu_high': self.hu_high_spinbox().value(),
195
+ 'alpha': self.alpha_spinbox().value(),
196
+ },
197
+ output=self.output_dir_line_edit().text(),
198
+ overwrite=self.overwrite_checkbox().isChecked(),
199
+ )
200
+ self._worker = Worker(self._task)
201
+ self._thread = QThread()
202
+ self._worker.moveToThread(self._thread)
203
+ self._thread.started.connect(self._worker.run)
204
+ self._worker.progress.connect(self.handle_progress)
205
+ self._worker.status.connect(self.handle_status)
206
+ self._worker.finished.connect(self.handle_finished)
207
+ self._worker.finished.connect(self._thread.quit)
208
+ self._worker.finished.connect(self._worker.deleteLater)
209
+ self._thread.finished.connect(self._thread.deleteLater)
210
+ self._thread.start()
211
+
212
+ @Slot(int)
213
+ def handle_progress(self, progress):
214
+ LOG.info(f'Progress: {progress} / 100%')
215
+
216
+ @Slot(str)
217
+ def handle_status(self, status):
218
+ LOG.info(f'Status: {status}')
219
+
220
+ @Slot()
221
+ def handle_finished(self):
222
+ self.run_task_button().setEnabled(True)
223
+
224
+ # HELPERS
225
+
226
+ def check_inputs_and_parameters(self):
227
+ errors = []
228
+ if self.images_dir_line_edit().text() == '':
229
+ errors.append('Empty images directory path')
230
+ if self.segmentations_dir_line_edit().text() == '':
231
+ errors.append('Empty segmentations directory path')
232
+ if not os.path.isdir(self.segmentations_dir_line_edit().text()):
233
+ errors.append('Segmentations directory does not exist')
234
+ if self.output_dir_line_edit().text() == '':
235
+ errors.append('Empty output directory path')
236
+ if os.path.isdir(self.output_dir_line_edit().text()) and not self.overwrite_checkbox().isChecked():
237
+ errors.append('Output directory exists but overwrite=False. Please remove output directory first')
238
+ return errors
239
+
240
+ def save_inputs_and_parameters(self):
241
+ self.settings().set(f'{PANEL_NAME}/images_dir', self.images_dir_line_edit().text())
242
+ self.settings().set(f'{PANEL_NAME}/segmentations_dir', self.segmentations_dir_line_edit().text())
243
+ self.settings().set(f'{PANEL_NAME}/output_dir', self.output_dir_line_edit().text())
244
+ self.settings().set(f'{PANEL_NAME}/hu_low', self.hu_low_spinbox().value())
245
+ self.settings().set(f'{PANEL_NAME}/hu_high', self.hu_high_spinbox().value())
246
+ self.settings().set(f'{PANEL_NAME}/alpha', self.alpha_spinbox().value())
247
+ self.settings().set(f'{PANEL_NAME}/overwrite', self.overwrite_checkbox().isChecked())