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.
- models.py +259 -0
- mosamatic2/__init__.py +0 -0
- mosamatic2/app.py +32 -0
- mosamatic2/cli.py +50 -0
- mosamatic2/commands/__init__.py +0 -0
- mosamatic2/commands/boadockerpipeline.py +48 -0
- mosamatic2/commands/calculatemaskstatistics.py +59 -0
- mosamatic2/commands/calculatescores.py +73 -0
- mosamatic2/commands/createdicomsummary.py +61 -0
- mosamatic2/commands/createpngsfromsegmentations.py +65 -0
- mosamatic2/commands/defaultdockerpipeline.py +84 -0
- mosamatic2/commands/defaultpipeline.py +70 -0
- mosamatic2/commands/dicom2nifti.py +55 -0
- mosamatic2/commands/liveranalysispipeline.py +61 -0
- mosamatic2/commands/rescaledicomimages.py +54 -0
- mosamatic2/commands/segmentmusclefatl3tensorflow.py +55 -0
- mosamatic2/commands/selectslicefromscans.py +66 -0
- mosamatic2/commands/totalsegmentator.py +77 -0
- mosamatic2/constants.py +27 -0
- mosamatic2/core/__init__.py +0 -0
- mosamatic2/core/data/__init__.py +5 -0
- mosamatic2/core/data/dicomimage.py +27 -0
- mosamatic2/core/data/dicomimageseries.py +26 -0
- mosamatic2/core/data/dixonseries.py +22 -0
- mosamatic2/core/data/filedata.py +26 -0
- mosamatic2/core/data/multidicomimage.py +30 -0
- mosamatic2/core/data/multiniftiimage.py +26 -0
- mosamatic2/core/data/multinumpyimage.py +26 -0
- mosamatic2/core/data/niftiimage.py +13 -0
- mosamatic2/core/data/numpyimage.py +13 -0
- mosamatic2/core/managers/__init__.py +0 -0
- mosamatic2/core/managers/logmanager.py +45 -0
- mosamatic2/core/managers/logmanagerlistener.py +3 -0
- mosamatic2/core/pipelines/__init__.py +4 -0
- mosamatic2/core/pipelines/boadockerpipeline/__init__.py +0 -0
- mosamatic2/core/pipelines/boadockerpipeline/boadockerpipeline.py +70 -0
- mosamatic2/core/pipelines/defaultdockerpipeline/__init__.py +0 -0
- mosamatic2/core/pipelines/defaultdockerpipeline/defaultdockerpipeline.py +28 -0
- mosamatic2/core/pipelines/defaultpipeline/__init__.py +0 -0
- mosamatic2/core/pipelines/defaultpipeline/defaultpipeline.py +90 -0
- mosamatic2/core/pipelines/liveranalysispipeline/__init__.py +0 -0
- mosamatic2/core/pipelines/liveranalysispipeline/liveranalysispipeline.py +48 -0
- mosamatic2/core/pipelines/pipeline.py +14 -0
- mosamatic2/core/singleton.py +9 -0
- mosamatic2/core/tasks/__init__.py +13 -0
- mosamatic2/core/tasks/applythresholdtosegmentationstask/__init__.py +0 -0
- mosamatic2/core/tasks/applythresholdtosegmentationstask/applythresholdtosegmentationstask.py +117 -0
- mosamatic2/core/tasks/calculatemaskstatisticstask/__init__.py +0 -0
- mosamatic2/core/tasks/calculatemaskstatisticstask/calculatemaskstatisticstask.py +104 -0
- mosamatic2/core/tasks/calculatescorestask/__init__.py +0 -0
- mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py +152 -0
- mosamatic2/core/tasks/createdicomsummarytask/__init__.py +0 -0
- mosamatic2/core/tasks/createdicomsummarytask/createdicomsummarytask.py +88 -0
- mosamatic2/core/tasks/createpngsfromsegmentationstask/__init__.py +0 -0
- mosamatic2/core/tasks/createpngsfromsegmentationstask/createpngsfromsegmentationstask.py +101 -0
- mosamatic2/core/tasks/dicom2niftitask/__init__.py +0 -0
- mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py +45 -0
- mosamatic2/core/tasks/rescaledicomimagestask/__init__.py +0 -0
- mosamatic2/core/tasks/rescaledicomimagestask/rescaledicomimagestask.py +64 -0
- mosamatic2/core/tasks/segmentationnifti2numpytask/__init__.py +0 -0
- mosamatic2/core/tasks/segmentationnifti2numpytask/segmentationnifti2numpytask.py +57 -0
- mosamatic2/core/tasks/segmentationnumpy2niftitask/__init__.py +0 -0
- mosamatic2/core/tasks/segmentationnumpy2niftitask/segmentationnumpy2niftitask.py +86 -0
- mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/__init__.py +0 -0
- mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/paramloader.py +39 -0
- mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorflowtask.py +122 -0
- mosamatic2/core/tasks/segmentmusclefatt4pytorchtask/__init__.py +0 -0
- mosamatic2/core/tasks/segmentmusclefatt4pytorchtask/paramloader.py +39 -0
- mosamatic2/core/tasks/segmentmusclefatt4pytorchtask/segmentmusclefatt4pytorchtask.py +128 -0
- mosamatic2/core/tasks/selectslicefromscanstask/__init__.py +0 -0
- mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py +249 -0
- mosamatic2/core/tasks/task.py +50 -0
- mosamatic2/core/tasks/totalsegmentatortask/__init__.py +0 -0
- mosamatic2/core/tasks/totalsegmentatortask/totalsegmentatortask.py +75 -0
- mosamatic2/core/utils.py +405 -0
- mosamatic2/server.py +146 -0
- mosamatic2/ui/__init__.py +0 -0
- mosamatic2/ui/mainwindow.py +426 -0
- mosamatic2/ui/resources/VERSION +1 -0
- mosamatic2/ui/resources/icons/mosamatic2.icns +0 -0
- mosamatic2/ui/resources/icons/mosamatic2.ico +0 -0
- mosamatic2/ui/resources/icons/spinner.gif +0 -0
- mosamatic2/ui/resources/images/body-composition.jpg +0 -0
- mosamatic2/ui/settings.py +62 -0
- mosamatic2/ui/utils.py +36 -0
- mosamatic2/ui/widgets/__init__.py +0 -0
- mosamatic2/ui/widgets/dialogs/__init__.py +0 -0
- mosamatic2/ui/widgets/dialogs/dialog.py +16 -0
- mosamatic2/ui/widgets/dialogs/helpdialog.py +9 -0
- mosamatic2/ui/widgets/panels/__init__.py +0 -0
- mosamatic2/ui/widgets/panels/defaultpanel.py +31 -0
- mosamatic2/ui/widgets/panels/logpanel.py +65 -0
- mosamatic2/ui/widgets/panels/mainpanel.py +82 -0
- mosamatic2/ui/widgets/panels/pipelines/__init__.py +0 -0
- mosamatic2/ui/widgets/panels/pipelines/boadockerpipelinepanel.py +195 -0
- mosamatic2/ui/widgets/panels/pipelines/defaultdockerpipelinepanel.py +314 -0
- mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py +302 -0
- mosamatic2/ui/widgets/panels/pipelines/liveranalysispipelinepanel.py +187 -0
- mosamatic2/ui/widgets/panels/pipelines/pipelinepanel.py +6 -0
- mosamatic2/ui/widgets/panels/settingspanel.py +16 -0
- mosamatic2/ui/widgets/panels/stackedpanel.py +22 -0
- mosamatic2/ui/widgets/panels/tasks/__init__.py +0 -0
- mosamatic2/ui/widgets/panels/tasks/applythresholdtosegmentationstaskpanel.py +271 -0
- mosamatic2/ui/widgets/panels/tasks/calculatemaskstatisticstaskpanel.py +215 -0
- mosamatic2/ui/widgets/panels/tasks/calculatescorestaskpanel.py +238 -0
- mosamatic2/ui/widgets/panels/tasks/createdicomsummarytaskpanel.py +206 -0
- mosamatic2/ui/widgets/panels/tasks/createpngsfromsegmentationstaskpanel.py +247 -0
- mosamatic2/ui/widgets/panels/tasks/dicom2niftitaskpanel.py +183 -0
- mosamatic2/ui/widgets/panels/tasks/rescaledicomimagestaskpanel.py +184 -0
- mosamatic2/ui/widgets/panels/tasks/segmentationnifti2numpytaskpanel.py +192 -0
- mosamatic2/ui/widgets/panels/tasks/segmentationnumpy2niftitaskpanel.py +213 -0
- mosamatic2/ui/widgets/panels/tasks/segmentmusclefatl3tensorflowtaskpanel.py +216 -0
- mosamatic2/ui/widgets/panels/tasks/segmentmusclefatt4pytorchtaskpanel.py +217 -0
- mosamatic2/ui/widgets/panels/tasks/selectslicefromscanstaskpanel.py +193 -0
- mosamatic2/ui/widgets/panels/tasks/taskpanel.py +6 -0
- mosamatic2/ui/widgets/panels/tasks/totalsegmentatortaskpanel.py +195 -0
- mosamatic2/ui/widgets/panels/visualizations/__init__.py +0 -0
- mosamatic2/ui/widgets/panels/visualizations/liversegmentvisualization/__init__.py +0 -0
- mosamatic2/ui/widgets/panels/visualizations/liversegmentvisualization/liversegmentpicker.py +96 -0
- mosamatic2/ui/widgets/panels/visualizations/liversegmentvisualization/liversegmentviewer.py +130 -0
- mosamatic2/ui/widgets/panels/visualizations/liversegmentvisualization/liversegmentvisualization.py +120 -0
- mosamatic2/ui/widgets/panels/visualizations/sliceselectionvisualization/__init__.py +0 -0
- mosamatic2/ui/widgets/panels/visualizations/sliceselectionvisualization/sliceselectionviewer.py +61 -0
- mosamatic2/ui/widgets/panels/visualizations/sliceselectionvisualization/sliceselectionvisualization.py +133 -0
- mosamatic2/ui/widgets/panels/visualizations/sliceselectionvisualization/slicetile.py +63 -0
- mosamatic2/ui/widgets/panels/visualizations/slicevisualization/__init__.py +0 -0
- mosamatic2/ui/widgets/panels/visualizations/slicevisualization/custominteractorstyle.py +80 -0
- mosamatic2/ui/widgets/panels/visualizations/slicevisualization/sliceviewer.py +116 -0
- mosamatic2/ui/widgets/panels/visualizations/slicevisualization/slicevisualization.py +141 -0
- mosamatic2/ui/widgets/panels/visualizations/visualization.py +6 -0
- mosamatic2/ui/widgets/splashscreen.py +101 -0
- mosamatic2/ui/worker.py +29 -0
- mosamatic2-2.0.24.dist-info/METADATA +43 -0
- mosamatic2-2.0.24.dist-info/RECORD +136 -0
- mosamatic2-2.0.24.dist-info/WHEEL +4 -0
- 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())
|