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,82 @@
1
+ import webbrowser
2
+
3
+ from PySide6.QtWidgets import (
4
+ QWidget,
5
+ QLabel,
6
+ QPushButton,
7
+ QVBoxLayout,
8
+ QDockWidget,
9
+ )
10
+
11
+ from mosamatic2.ui.settings import Settings
12
+ from mosamatic2.ui.widgets.panels.stackedpanel import StackedPanel
13
+ from mosamatic2.core.managers.logmanager import LogManager
14
+
15
+ LOG = LogManager()
16
+
17
+ PANEL_NAME = 'mainpanel'
18
+ DONATE_URL = 'https://rbeesoft.nl/wordpress/'
19
+
20
+
21
+ class MainPanel(QDockWidget):
22
+ def __init__(self, parent):
23
+ super(MainPanel, self).__init__(parent)
24
+ self._settings = None
25
+ self._title_label = None
26
+ self._donate_button = None
27
+ self._stacked_panel = None
28
+ self._panels = {}
29
+ self.init_layout()
30
+
31
+ def init_layout(self):
32
+ layout = QVBoxLayout()
33
+ layout.addWidget(self.title_label())
34
+ # layout.addWidget(self.donate_button())
35
+ layout.addWidget(self.stacked_panel())
36
+ container = QWidget()
37
+ container.setLayout(layout)
38
+ self.setObjectName(PANEL_NAME)
39
+ self.setWidget(container)
40
+
41
+ # GETTERS
42
+
43
+ def settings(self):
44
+ if not self._settings:
45
+ self._settings = Settings()
46
+ return self._settings
47
+
48
+ def title_label(self):
49
+ if not self._title_label:
50
+ self._title_label = QLabel('')
51
+ self._title_label.setStyleSheet('color: black; font-weight: bold; font-size: 14pt;')
52
+ return self._title_label
53
+
54
+ def donate_button(self):
55
+ if not self._donate_button:
56
+ self._donate_button = QPushButton('Donate')
57
+ self._donate_button.setStyleSheet('background-color: blue; color: white; font-weight: bold;')
58
+ self._donate_button.clicked.connect(self.handle_donate_button)
59
+ return self._donate_button
60
+
61
+ def stacked_panel(self):
62
+ if not self._stacked_panel:
63
+ self._stacked_panel = StackedPanel()
64
+ return self._stacked_panel
65
+
66
+ def panels(self):
67
+ return self._panels
68
+
69
+ # ADDING PANELS
70
+
71
+ def add_panel(self, panel, name):
72
+ self.panels()[name] = panel.title()
73
+ self.stacked_panel().add_panel(panel, name)
74
+
75
+ def select_panel(self, name):
76
+ self.title_label().setText(self.panels().get(name))
77
+ self.stacked_panel().switch_to(name)
78
+
79
+ # EVENT HANDLERS
80
+
81
+ def handle_donate_button(self):
82
+ webbrowser.open(DONATE_URL)
File without changes
@@ -0,0 +1,195 @@
1
+ import os
2
+
3
+ from PySide6.QtWidgets import (
4
+ QLineEdit,
5
+ QCheckBox,
6
+ QHBoxLayout,
7
+ QVBoxLayout,
8
+ QFormLayout,
9
+ QPushButton,
10
+ QFileDialog,
11
+ QMessageBox,
12
+ )
13
+ from PySide6.QtCore import (
14
+ QThread,
15
+ Slot,
16
+ )
17
+
18
+ from mosamatic2.core.managers.logmanager import LogManager
19
+ from mosamatic2.core.utils import is_docker_running, is_path_docker_compatible
20
+ from mosamatic2.ui.widgets.panels.pipelines.pipelinepanel import PipelinePanel
21
+ from mosamatic2.ui.settings import Settings
22
+ from mosamatic2.ui.utils import is_macos
23
+ from mosamatic2.ui.worker import Worker
24
+ from mosamatic2.core.pipelines import BoaDockerPipeline
25
+
26
+ LOG = LogManager()
27
+
28
+ PANEL_TITLE = 'BoaDockerPipeline'
29
+ PANEL_NAME = 'boadockerpipeline'
30
+
31
+
32
+ class BoaDockerPipelinePanel(PipelinePanel):
33
+ def __init__(self):
34
+ super(BoaDockerPipelinePanel, self).__init__()
35
+ self.set_title(PANEL_TITLE)
36
+ self._scans_dir_line_edit = None
37
+ self._images_dir_select_button = None
38
+ self._output_dir_line_edit = None
39
+ self._output_dir_select_button = None
40
+ self._overwrite_checkbox = None
41
+ self._form_layout = None
42
+ self._run_pipeline_button = None
43
+ self._settings = None
44
+ self._task = None
45
+ self._worker = None
46
+ self._thread = None
47
+ self.init_layout()
48
+
49
+ def scans_dir_line_edit(self):
50
+ if not self._scans_dir_line_edit:
51
+ self._scans_dir_line_edit = QLineEdit()
52
+ self._scans_dir_line_edit.setText(self.settings().get(f'{PANEL_NAME}/scans_dir'))
53
+ return self._scans_dir_line_edit
54
+
55
+ def scans_dir_select_button(self):
56
+ if not self._images_dir_select_button:
57
+ self._images_dir_select_button = QPushButton('Select')
58
+ self._images_dir_select_button.clicked.connect(self.handle_scans_dir_select_button)
59
+ return self._images_dir_select_button
60
+
61
+ def output_dir_line_edit(self):
62
+ if not self._output_dir_line_edit:
63
+ self._output_dir_line_edit = QLineEdit()
64
+ self._output_dir_line_edit.setText(self.settings().get(f'{PANEL_NAME}/output_dir'))
65
+ return self._output_dir_line_edit
66
+
67
+ def output_dir_select_button(self):
68
+ if not self._output_dir_select_button:
69
+ self._output_dir_select_button = QPushButton('Select')
70
+ self._output_dir_select_button.clicked.connect(self.handle_output_dir_select_button)
71
+ return self._output_dir_select_button
72
+
73
+ def overwrite_checkbox(self):
74
+ if not self._overwrite_checkbox:
75
+ self._overwrite_checkbox = QCheckBox('')
76
+ self._overwrite_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/overwrite', True))
77
+ return self._overwrite_checkbox
78
+
79
+ def form_layout(self):
80
+ if not self._form_layout:
81
+ self._form_layout = QFormLayout()
82
+ if is_macos():
83
+ self._form_layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
84
+ return self._form_layout
85
+
86
+ def run_pipeline_button(self):
87
+ if not self._run_pipeline_button:
88
+ self._run_pipeline_button = QPushButton('Run pipeline')
89
+ self._run_pipeline_button.clicked.connect(self.handle_run_pipeline_button)
90
+ return self._run_pipeline_button
91
+
92
+ def settings(self):
93
+ if not self._settings:
94
+ self._settings = Settings()
95
+ return self._settings
96
+
97
+ def init_help_dialog(self):
98
+ self.help_dialog().set_text('Show some help information')
99
+
100
+ def init_layout(self):
101
+ scans_dir_layout = QHBoxLayout()
102
+ scans_dir_layout.addWidget(self.scans_dir_line_edit())
103
+ scans_dir_layout.addWidget(self.scans_dir_select_button())
104
+ output_dir_layout = QHBoxLayout()
105
+ output_dir_layout.addWidget(self.output_dir_line_edit())
106
+ output_dir_layout.addWidget(self.output_dir_select_button())
107
+ self.form_layout().addRow('Scans directory', scans_dir_layout)
108
+ self.form_layout().addRow('Output directory', output_dir_layout)
109
+ self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
110
+ layout = QVBoxLayout()
111
+ layout.addLayout(self.form_layout())
112
+ layout.addWidget(self.run_pipeline_button())
113
+ self.setLayout(layout)
114
+ self.setObjectName(PANEL_NAME)
115
+
116
+ def handle_scans_dir_select_button(self):
117
+ last_directory = self.settings().get('last_directory')
118
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
119
+ if directory:
120
+ self.scans_dir_line_edit().setText(directory)
121
+ self.settings().set('last_directory', directory)
122
+
123
+ def handle_output_dir_select_button(self):
124
+ last_directory = self.settings().get('last_directory')
125
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
126
+ if directory:
127
+ self.output_dir_line_edit().setText(directory)
128
+ self.settings().set('last_directory', directory)
129
+
130
+ def handle_run_pipeline_button(self):
131
+ if not is_docker_running():
132
+ QMessageBox.information(self, 'Error', 'Docker is not running. Please start Docker Desktop first')
133
+ return
134
+ if not is_path_docker_compatible(self.scans_dir_line_edit().text()):
135
+ QMessageBox.information(self, 'Error', 'Path to scans directory contains spaces and is not Docker compatible')
136
+ return
137
+ if not is_path_docker_compatible(self.output_dir_line_edit().text()):
138
+ QMessageBox.information(self, 'Error', 'Path to output directory contains spaces and is not Docker compatible')
139
+ return
140
+ errors = self.check_inputs_and_parameters()
141
+ if len(errors) > 0:
142
+ error_message = 'Following errors were encountered:\n'
143
+ for error in errors:
144
+ error_message += f' - {error}\n'
145
+ QMessageBox.information(self, 'Error', error_message)
146
+ else:
147
+ LOG.info('Running pipeline...')
148
+ self.run_pipeline_button().setEnabled(False)
149
+ self.save_inputs_and_parameters()
150
+ self._task = BoaDockerPipeline(
151
+ inputs={'scans': self.scans_dir_line_edit().text()},
152
+ params=None,
153
+ output=self.output_dir_line_edit().text(),
154
+ overwrite=self.overwrite_checkbox().isChecked(),
155
+ )
156
+ self._worker = Worker(self._task)
157
+ self._thread = QThread()
158
+ self._worker.moveToThread(self._thread)
159
+ self._thread.started.connect(self._worker.run)
160
+ self._worker.progress.connect(self.handle_progress)
161
+ self._worker.status.connect(self.handle_status)
162
+ self._worker.finished.connect(self.handle_finished)
163
+ self._worker.finished.connect(self._thread.quit)
164
+ self._worker.finished.connect(self._worker.deleteLater)
165
+ self._thread.finished.connect(self._thread.deleteLater)
166
+ self._thread.start()
167
+
168
+ @Slot(int)
169
+ def handle_progress(self, progress):
170
+ LOG.info(f'Progress: {progress} / 100%')
171
+
172
+ @Slot(str)
173
+ def handle_status(self, status):
174
+ LOG.info(f'Status: {status}')
175
+
176
+ @Slot()
177
+ def handle_finished(self):
178
+ self.run_pipeline_button().setEnabled(True)
179
+
180
+ def check_inputs_and_parameters(self):
181
+ errors = []
182
+ if self.scans_dir_line_edit().text() == '':
183
+ errors.append('Empty scans directory path')
184
+ elif not os.path.isdir(self.scans_dir_line_edit().text()):
185
+ errors.append('Scans directory does not exist')
186
+ if self.output_dir_line_edit().text() == '':
187
+ errors.append('Empty output directory path')
188
+ elif os.path.isdir(self.output_dir_line_edit().text()) and not self.overwrite_checkbox().isChecked():
189
+ errors.append('Output directory exists but overwrite=False. Please remove output directory first')
190
+ return errors
191
+
192
+ def save_inputs_and_parameters(self):
193
+ self.settings().set(f'{PANEL_NAME}/scans_dir', self.scans_dir_line_edit().text())
194
+ self.settings().set(f'{PANEL_NAME}/output_dir', self.output_dir_line_edit().text())
195
+ self.settings().set(f'{PANEL_NAME}/overwrite', self.overwrite_checkbox().isChecked())
@@ -0,0 +1,314 @@
1
+ import os
2
+
3
+ from PySide6.QtWidgets import (
4
+ QLineEdit,
5
+ QSpinBox,
6
+ QComboBox,
7
+ QCheckBox,
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.core.utils import is_docker_running, is_path_docker_compatible
22
+ from mosamatic2.ui.widgets.panels.pipelines.pipelinepanel import PipelinePanel
23
+ from mosamatic2.ui.settings import Settings
24
+ from mosamatic2.ui.utils import is_macos
25
+ from mosamatic2.ui.worker import Worker
26
+ from mosamatic2.core.pipelines import DefaultDockerPipeline
27
+
28
+ LOG = LogManager()
29
+
30
+ PANEL_TITLE = 'DefaultDockerPipeline'
31
+ PANEL_NAME = 'defaultdockerpipeline'
32
+ MODEL_TYPE_ITEM_NAMES = ['tensorflow', 'pytorch']
33
+ MODEL_VERSION_ITEM_NAMES = ['1.0', '2.2']
34
+
35
+
36
+ class DefaultDockerPipelinePanel(PipelinePanel):
37
+ def __init__(self):
38
+ super(DefaultDockerPipelinePanel, self).__init__()
39
+ self.set_title(PANEL_TITLE)
40
+ self._images_dir_line_edit = None
41
+ self._images_dir_select_button = None
42
+ self._model_files_dir_line_edit = None
43
+ self._model_files_dir_select_button = None
44
+ self._output_dir_line_edit = None
45
+ self._output_dir_select_button = None
46
+ self._target_size_spinbox = None
47
+ self._model_type_combobox = None
48
+ self._model_version_combobox = None
49
+ self._fig_width_spinbox = None
50
+ self._fig_height_spinbox = None
51
+ self._full_scan_checkbox = None
52
+ self._overwrite_checkbox = None
53
+ self._version_line_edit = None
54
+ self._form_layout = None
55
+ self._run_pipeline_button = None
56
+ self._settings = None
57
+ self._task = None
58
+ self._worker = None
59
+ self._thread = None
60
+ self.init_layout()
61
+
62
+ def images_dir_line_edit(self):
63
+ if not self._images_dir_line_edit:
64
+ self._images_dir_line_edit = QLineEdit()
65
+ self._images_dir_line_edit.setText(self.settings().get(f'{PANEL_NAME}/images_dir'))
66
+ return self._images_dir_line_edit
67
+
68
+ def images_dir_select_button(self):
69
+ if not self._images_dir_select_button:
70
+ self._images_dir_select_button = QPushButton('Select')
71
+ self._images_dir_select_button.clicked.connect(self.handle_images_dir_select_button)
72
+ return self._images_dir_select_button
73
+
74
+ def model_files_dir_line_edit(self):
75
+ if not self._model_files_dir_line_edit:
76
+ self._model_files_dir_line_edit = QLineEdit()
77
+ self._model_files_dir_line_edit.setText(self.settings().get(f'{PANEL_NAME}/model_files_dir'))
78
+ return self._model_files_dir_line_edit
79
+
80
+ def model_files_dir_select_button(self):
81
+ if not self._model_files_dir_select_button:
82
+ self._model_files_dir_select_button = QPushButton('Select')
83
+ self._model_files_dir_select_button.clicked.connect(self.handle_model_files_dir_select_button)
84
+ return self._model_files_dir_select_button
85
+
86
+ def output_dir_line_edit(self):
87
+ if not self._output_dir_line_edit:
88
+ self._output_dir_line_edit = QLineEdit()
89
+ self._output_dir_line_edit.setText(self.settings().get(f'{PANEL_NAME}/output_dir'))
90
+ return self._output_dir_line_edit
91
+
92
+ def output_dir_select_button(self):
93
+ if not self._output_dir_select_button:
94
+ self._output_dir_select_button = QPushButton('Select')
95
+ self._output_dir_select_button.clicked.connect(self.handle_output_dir_select_button)
96
+ return self._output_dir_select_button
97
+
98
+ def target_size_spinbox(self):
99
+ if not self._target_size_spinbox:
100
+ self._target_size_spinbox = QSpinBox()
101
+ self._target_size_spinbox.setMinimum(0)
102
+ self._target_size_spinbox.setMaximum(1024)
103
+ self._target_size_spinbox.setValue(self.settings().get_int(f'{PANEL_NAME}/target_size', 512))
104
+ return self._target_size_spinbox
105
+
106
+ def model_type_combobox(self):
107
+ if not self._model_type_combobox:
108
+ self._model_type_combobox = QComboBox()
109
+ self._model_type_combobox.addItems(MODEL_TYPE_ITEM_NAMES)
110
+ self._model_type_combobox.setCurrentText(self.settings().get(f'{PANEL_NAME}/model_type'))
111
+ self._model_type_combobox.currentTextChanged.connect(self.handle_model_type_combobox)
112
+ return self._model_type_combobox
113
+
114
+ def model_version_combobox(self):
115
+ if not self._model_version_combobox:
116
+ self._model_version_combobox = QComboBox()
117
+ self._model_version_combobox.addItems(MODEL_VERSION_ITEM_NAMES)
118
+ self._model_version_combobox.setCurrentText(self.settings().get(f'{PANEL_NAME}/model_version'))
119
+ self._model_version_combobox.currentTextChanged.connect(self.handle_model_version_combobox)
120
+ return self._model_version_combobox
121
+
122
+ def fig_width_spinbox(self):
123
+ if not self._fig_width_spinbox:
124
+ self._fig_width_spinbox = QSpinBox()
125
+ self._fig_width_spinbox.setValue(self.settings().get_int(f'{PANEL_NAME}/fig_width', default=10))
126
+ return self._fig_width_spinbox
127
+
128
+ def fig_height_spinbox(self):
129
+ if not self._fig_height_spinbox:
130
+ self._fig_height_spinbox = QSpinBox()
131
+ self._fig_height_spinbox.setValue(self.settings().get_int(f'{PANEL_NAME}/fig_height', default=10))
132
+ return self._fig_height_spinbox
133
+
134
+ def full_scan_checkbox(self):
135
+ if not self._full_scan_checkbox:
136
+ self._full_scan_checkbox = QCheckBox('')
137
+ self._full_scan_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/full_scan', False))
138
+ return self._full_scan_checkbox
139
+
140
+ def overwrite_checkbox(self):
141
+ if not self._overwrite_checkbox:
142
+ self._overwrite_checkbox = QCheckBox('')
143
+ self._overwrite_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/overwrite', True))
144
+ return self._overwrite_checkbox
145
+
146
+ def version_line_edit(self):
147
+ if not self._version_line_edit:
148
+ self._version_line_edit = QLineEdit('2.0.10')
149
+ self._version_line_edit.setText(self.settings().get(f'{PANEL_NAME}/version'))
150
+ return self._version_line_edit
151
+
152
+ def form_layout(self):
153
+ if not self._form_layout:
154
+ self._form_layout = QFormLayout()
155
+ if is_macos():
156
+ self._form_layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
157
+ return self._form_layout
158
+
159
+ def run_pipeline_button(self):
160
+ if not self._run_pipeline_button:
161
+ self._run_pipeline_button = QPushButton('Run pipeline')
162
+ self._run_pipeline_button.clicked.connect(self.handle_run_pipeline_button)
163
+ return self._run_pipeline_button
164
+
165
+ def settings(self):
166
+ if not self._settings:
167
+ self._settings = Settings()
168
+ return self._settings
169
+
170
+ def init_help_dialog(self):
171
+ self.help_dialog().set_text('Show some help information')
172
+
173
+ def init_layout(self):
174
+ images_dir_layout = QHBoxLayout()
175
+ images_dir_layout.addWidget(self.images_dir_line_edit())
176
+ images_dir_layout.addWidget(self.images_dir_select_button())
177
+ model_files_dir_layout = QHBoxLayout()
178
+ model_files_dir_layout.addWidget(self.model_files_dir_line_edit())
179
+ model_files_dir_layout.addWidget(self.model_files_dir_select_button())
180
+ output_dir_layout = QHBoxLayout()
181
+ output_dir_layout.addWidget(self.output_dir_line_edit())
182
+ output_dir_layout.addWidget(self.output_dir_select_button())
183
+ self.form_layout().addRow('Images directory', images_dir_layout)
184
+ self.form_layout().addRow('Model files directory', model_files_dir_layout)
185
+ self.form_layout().addRow('Docker image version', self.version_line_edit())
186
+ self.form_layout().addRow('Output directory', output_dir_layout)
187
+ self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
188
+ layout = QVBoxLayout()
189
+ layout.addLayout(self.form_layout())
190
+ layout.addWidget(self.run_pipeline_button())
191
+ self.setLayout(layout)
192
+ self.setObjectName(PANEL_NAME)
193
+
194
+ def handle_images_dir_select_button(self):
195
+ last_directory = self.settings().get('last_directory')
196
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
197
+ if directory:
198
+ self.images_dir_line_edit().setText(directory)
199
+ self.settings().set('last_directory', directory)
200
+
201
+ def handle_model_files_dir_select_button(self):
202
+ last_directory = self.settings().get('last_directory')
203
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
204
+ if directory:
205
+ self.model_files_dir_line_edit().setText(directory)
206
+ self.settings().set('last_directory', directory)
207
+
208
+ def handle_output_dir_select_button(self):
209
+ last_directory = self.settings().get('last_directory')
210
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
211
+ if directory:
212
+ self.output_dir_line_edit().setText(directory)
213
+ self.settings().set('last_directory', directory)
214
+
215
+ def handle_model_type_combobox(self, text):
216
+ if text == 'tensorflow':
217
+ self.model_version_combobox().setCurrentText('1.0')
218
+ if text == 'pytorch':
219
+ self.model_version_combobox().setCurrentText('2.2')
220
+
221
+ def handle_model_version_combobox(self, text):
222
+ if text == '1.0':
223
+ self.model_type_combobox().setCurrentText('tensorflow')
224
+ if text == '2.2':
225
+ self.model_type_combobox().setCurrentText('pytorch')
226
+
227
+ def handle_run_pipeline_button(self):
228
+ if not is_docker_running():
229
+ QMessageBox.information(self, 'Error', 'Docker is not running. Please start Docker Desktop first')
230
+ return
231
+ if not is_path_docker_compatible(self.images_dir_line_edit().text()):
232
+ QMessageBox.information(self, 'Error', 'Path to images directory contains spaces and is not Docker compatible')
233
+ return
234
+ if not is_path_docker_compatible(self.model_files_dir_line_edit().text()):
235
+ QMessageBox.information(self, 'Error', 'Path to model files directory contains spaces and is not Docker compatible')
236
+ return
237
+ if not is_path_docker_compatible(self.output_dir_line_edit().text()):
238
+ QMessageBox.information(self, 'Error', 'Path to output directory contains spaces and is not Docker compatible')
239
+ return
240
+ errors = self.check_inputs_and_parameters()
241
+ if len(errors) > 0:
242
+ error_message = 'Following errors were encountered:\n'
243
+ for error in errors:
244
+ error_message += f' - {error}\n'
245
+ QMessageBox.information(self, 'Error', error_message)
246
+ else:
247
+ LOG.info('Running pipeline...')
248
+ self.run_pipeline_button().setEnabled(False)
249
+ self.save_inputs_and_parameters()
250
+ self._task = DefaultDockerPipeline(
251
+ inputs={
252
+ 'images': self.images_dir_line_edit().text(),
253
+ 'model_files': self.model_files_dir_line_edit().text(),
254
+ },
255
+ params={
256
+ 'file_type': 'npy',
257
+ 'model_type': 'tensorflow',
258
+ 'model_version': 1.0,
259
+ 'target_size': 512,
260
+ 'fig_width': 10,
261
+ 'fig_height': 10,
262
+ 'version': '2.0.10',
263
+ },
264
+ output=self.output_dir_line_edit().text(),
265
+ overwrite=self.overwrite_checkbox().isChecked(),
266
+ )
267
+ self._worker = Worker(self._task)
268
+ self._thread = QThread()
269
+ self._worker.moveToThread(self._thread)
270
+ self._thread.started.connect(self._worker.run)
271
+ self._worker.progress.connect(self.handle_progress)
272
+ self._worker.status.connect(self.handle_status)
273
+ self._worker.finished.connect(self.handle_finished)
274
+ self._worker.finished.connect(self._thread.quit)
275
+ self._worker.finished.connect(self._worker.deleteLater)
276
+ self._thread.finished.connect(self._thread.deleteLater)
277
+ self._thread.start()
278
+
279
+ @Slot(int)
280
+ def handle_progress(self, progress):
281
+ LOG.info(f'Progress: {progress} / 100%')
282
+
283
+ @Slot(str)
284
+ def handle_status(self, status):
285
+ LOG.info(f'Status: {status}')
286
+
287
+ @Slot()
288
+ def handle_finished(self):
289
+ self.run_pipeline_button().setEnabled(True)
290
+
291
+ def check_inputs_and_parameters(self):
292
+ errors = []
293
+ if self.images_dir_line_edit().text() == '':
294
+ errors.append('Empty images directory path')
295
+ elif not os.path.isdir(self.images_dir_line_edit().text()):
296
+ errors.append('Images directory does not exist')
297
+ if self.model_files_dir_line_edit().text() == '':
298
+ errors.append('Empty model files directory path')
299
+ elif not os.path.isdir(self.model_files_dir_line_edit().text()):
300
+ errors.append('Model files directory does not exist')
301
+ if self.version_line_edit().text() == '':
302
+ errors.append('Empty Docker image version')
303
+ if self.output_dir_line_edit().text() == '':
304
+ errors.append('Empty output directory path')
305
+ elif os.path.isdir(self.output_dir_line_edit().text()) and not self.overwrite_checkbox().isChecked():
306
+ errors.append('Output directory exists but overwrite=False. Please remove output directory first')
307
+ return errors
308
+
309
+ def save_inputs_and_parameters(self):
310
+ self.settings().set(f'{PANEL_NAME}/images_dir', self.images_dir_line_edit().text())
311
+ self.settings().set(f'{PANEL_NAME}/model_files_dir', self.model_files_dir_line_edit().text())
312
+ self.settings().set(f'{PANEL_NAME}/version', self.version_line_edit().text())
313
+ self.settings().set(f'{PANEL_NAME}/output_dir', self.output_dir_line_edit().text())
314
+ self.settings().set(f'{PANEL_NAME}/overwrite', self.overwrite_checkbox().isChecked())