mosamatic2 2.0.13__tar.gz → 2.0.15__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mosamatic2 might be problematic. Click here for more details.

Files changed (106) hide show
  1. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/PKG-INFO +2 -1
  2. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/pyproject.toml +2 -1
  3. mosamatic2-2.0.15/src/mosamatic2/commands/totalsegmentator.py +68 -0
  4. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/__init__.py +2 -1
  5. mosamatic2-2.0.15/src/mosamatic2/core/tasks/totalsegmentatortask/totalsegmentatortask.py +50 -0
  6. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/utils.py +4 -0
  7. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/mainwindow.py +15 -0
  8. mosamatic2-2.0.15/src/mosamatic2/ui/resources/VERSION +1 -0
  9. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/pipelines/boadockerpipelinepanel.py +10 -0
  10. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/pipelines/defaultdockerpipelinepanel.py +13 -0
  11. mosamatic2-2.0.15/src/mosamatic2/ui/widgets/panels/tasks/totalsegmentatortaskpanel.py +195 -0
  12. mosamatic2-2.0.15/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization/__init__.py +0 -0
  13. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization/sliceviewer.py +2 -2
  14. mosamatic2-2.0.13/src/mosamatic2/ui/resources/VERSION +0 -1
  15. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/README.md +0 -0
  16. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/models.py +0 -0
  17. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/__init__.py +0 -0
  18. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/app.py +0 -0
  19. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/cli.py +0 -0
  20. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/commands/__init__.py +0 -0
  21. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/commands/boadockerpipeline.py +0 -0
  22. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/commands/calculatescores.py +0 -0
  23. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/commands/createdicomsummary.py +0 -0
  24. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/commands/createpngsfromsegmentations.py +0 -0
  25. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/commands/defaultdockerpipeline.py +0 -0
  26. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/commands/defaultpipeline.py +0 -0
  27. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/commands/dicom2nifti.py +0 -0
  28. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/commands/rescaledicomimages.py +0 -0
  29. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/commands/segmentmusclefatl3tensorflow.py +0 -0
  30. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/commands/selectslicefromscans.py +0 -0
  31. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/constants.py +0 -0
  32. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/__init__.py +0 -0
  33. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/data/__init__.py +0 -0
  34. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/data/dicomimage.py +0 -0
  35. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/data/dicomimageseries.py +0 -0
  36. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/data/dixonseries.py +0 -0
  37. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/data/filedata.py +0 -0
  38. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/data/multidicomimage.py +0 -0
  39. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/data/multiniftiimage.py +0 -0
  40. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/data/multinumpyimage.py +0 -0
  41. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/data/niftiimage.py +0 -0
  42. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/data/numpyimage.py +0 -0
  43. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/managers/__init__.py +0 -0
  44. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/managers/logmanager.py +0 -0
  45. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/managers/logmanagerlistener.py +0 -0
  46. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/pipelines/__init__.py +0 -0
  47. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/pipelines/boadockerpipeline/__init__.py +0 -0
  48. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/pipelines/boadockerpipeline/boadockerpipeline.py +0 -0
  49. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/pipelines/defaultdockerpipeline/__init__.py +0 -0
  50. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/pipelines/defaultdockerpipeline/defaultdockerpipeline.py +0 -0
  51. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/pipelines/defaultpipeline/__init__.py +0 -0
  52. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/pipelines/defaultpipeline/defaultpipeline.py +0 -0
  53. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/pipelines/pipeline.py +0 -0
  54. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/singleton.py +0 -0
  55. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/calculatescorestask/__init__.py +0 -0
  56. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py +0 -0
  57. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/createdicomsummarytask/__init__.py +0 -0
  58. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/createdicomsummarytask/createdicomsummarytask.py +0 -0
  59. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/createpngsfromsegmentationstask/__init__.py +0 -0
  60. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/createpngsfromsegmentationstask/createpngsfromsegmentationstask.py +0 -0
  61. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/dicom2niftitask/__init__.py +0 -0
  62. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py +0 -0
  63. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/rescaledicomimagestask/__init__.py +0 -0
  64. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/rescaledicomimagestask/rescaledicomimagestask.py +0 -0
  65. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/__init__.py +0 -0
  66. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/paramloader.py +0 -0
  67. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorflowtask.py +0 -0
  68. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/selectslicefromscanstask/__init__.py +0 -0
  69. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py +0 -0
  70. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/core/tasks/task.py +0 -0
  71. {mosamatic2-2.0.13/src/mosamatic2/ui → mosamatic2-2.0.15/src/mosamatic2/core/tasks/totalsegmentatortask}/__init__.py +0 -0
  72. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/server.py +0 -0
  73. {mosamatic2-2.0.13/src/mosamatic2/ui/widgets → mosamatic2-2.0.15/src/mosamatic2/ui}/__init__.py +0 -0
  74. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/resources/icons/mosamatic2.icns +0 -0
  75. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/resources/icons/mosamatic2.ico +0 -0
  76. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/resources/icons/spinner.gif +0 -0
  77. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/resources/images/body-composition.jpg +0 -0
  78. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/settings.py +0 -0
  79. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/utils.py +0 -0
  80. {mosamatic2-2.0.13/src/mosamatic2/ui/widgets/dialogs → mosamatic2-2.0.15/src/mosamatic2/ui/widgets}/__init__.py +0 -0
  81. {mosamatic2-2.0.13/src/mosamatic2/ui/widgets/panels → mosamatic2-2.0.15/src/mosamatic2/ui/widgets/dialogs}/__init__.py +0 -0
  82. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/dialogs/dialog.py +0 -0
  83. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/dialogs/helpdialog.py +0 -0
  84. {mosamatic2-2.0.13/src/mosamatic2/ui/widgets/panels/pipelines → mosamatic2-2.0.15/src/mosamatic2/ui/widgets/panels}/__init__.py +0 -0
  85. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/defaultpanel.py +0 -0
  86. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/logpanel.py +0 -0
  87. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/mainpanel.py +0 -0
  88. {mosamatic2-2.0.13/src/mosamatic2/ui/widgets/panels/tasks → mosamatic2-2.0.15/src/mosamatic2/ui/widgets/panels/pipelines}/__init__.py +0 -0
  89. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py +0 -0
  90. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/pipelines/pipelinepanel.py +0 -0
  91. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/stackedpanel.py +0 -0
  92. {mosamatic2-2.0.13/src/mosamatic2/ui/widgets/panels/visualizations → mosamatic2-2.0.15/src/mosamatic2/ui/widgets/panels/tasks}/__init__.py +0 -0
  93. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/tasks/calculatescorestaskpanel.py +0 -0
  94. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/tasks/createdicomsummarytaskpanel.py +0 -0
  95. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/tasks/createpngsfromsegmentationstaskpanel.py +0 -0
  96. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/tasks/dicom2niftitaskpanel.py +0 -0
  97. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/tasks/rescaledicomimagestaskpanel.py +0 -0
  98. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/tasks/segmentmusclefatl3tensorflowtaskpanel.py +0 -0
  99. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/tasks/selectslicefromscanstaskpanel.py +0 -0
  100. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/tasks/taskpanel.py +0 -0
  101. {mosamatic2-2.0.13/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization → mosamatic2-2.0.15/src/mosamatic2/ui/widgets/panels/visualizations}/__init__.py +0 -0
  102. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization/custominteractorstyle.py +0 -0
  103. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization/slicevisualization.py +0 -0
  104. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/panels/visualizations/visualization.py +0 -0
  105. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/widgets/splashscreen.py +0 -0
  106. {mosamatic2-2.0.13 → mosamatic2-2.0.15}/src/mosamatic2/ui/worker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mosamatic2
3
- Version: 2.0.13
3
+ Version: 2.0.15
4
4
  Summary:
5
5
  Author: Ralph Brecheisen
6
6
  Author-email: r.brecheisen@maastrichtuniversity.nl
@@ -23,6 +23,7 @@ Requires-Dist: pyqtgraph (>=0.13.7)
23
23
  Requires-Dist: pyside6-essentials (>=6.9)
24
24
  Requires-Dist: python-gdcm (>=3.0.26)
25
25
  Requires-Dist: scipy (>=1.15.3)
26
+ Requires-Dist: slicer
26
27
  Requires-Dist: tensorboard (==2.15.2)
27
28
  Requires-Dist: tensorboard-data-server (==0.7.2)
28
29
  Requires-Dist: tensorflow (==2.15.*) ; platform_system == "Linux"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mosamatic2"
3
- version = "2.0.13"
3
+ version = "2.0.15"
4
4
  description = ""
5
5
  authors = [
6
6
  {name = "Ralph Brecheisen", email = "r.brecheisen@maastrichtuniversity.nl"}
@@ -35,6 +35,7 @@ dependencies = [
35
35
  "pyqtgraph>=0.13.7",
36
36
  "moosez",
37
37
  "docker",
38
+ "slicer",
38
39
  ]
39
40
 
40
41
  [tool.poetry]
@@ -0,0 +1,68 @@
1
+ import click
2
+
3
+ from mosamatic2.core.tasks import TotalSegmentatorTask
4
+
5
+
6
+ @click.command(help='Run Total Segmentator on CT scans')
7
+ @click.option(
8
+ '--scans',
9
+ required=True,
10
+ type=click.Path(exists=True),
11
+ help='Directory with scans (each patient separate subdirectory)',
12
+ )
13
+ @click.option(
14
+ '--output',
15
+ required=True,
16
+ type=click.Path(),
17
+ help='Output directory'
18
+ )
19
+ @click.option(
20
+ '--tasks',
21
+ default='total',
22
+ help='Comma-separated list of Total Segmentator tasks to run (no spaces!)'
23
+ )
24
+ @click.option(
25
+ '--overwrite',
26
+ type=click.BOOL,
27
+ default=False,
28
+ help='Overwrite [true|false]'
29
+ )
30
+ def totalsegmentator(scans, tasks, output, overwrite):
31
+ """
32
+ Run Total Segmentator on CT scans. If you want to run specialized tasks
33
+ like "liver_segments" or "liver_vessels" you need an educational license.
34
+ Check out https://github.com/wasserth/TotalSegmentator?tab=readme-ov-file
35
+ to find out how to get such a license.
36
+
37
+ Parameters
38
+ ----------
39
+ --scans : str
40
+ Directory to scans. Each patient's scan should be in a separate
41
+ subdirectory. For example:
42
+
43
+ /scans
44
+ /scans/patient1
45
+ /scans/patient1/file1.dcm
46
+ /scans/patient1/file2.dcm
47
+ ...
48
+ /scans/patient2
49
+ ...
50
+
51
+ --output : str
52
+ Path to output directory where selected slices will be placed. Each
53
+ slice's file name will be the same as the scan directory name, so in
54
+ the example above that would be "patient1", "patient2", etc.
55
+
56
+ --tasks : str
57
+ Comma-separated list of Total Segmentator tasks to run (no spaces!)
58
+
59
+ --overwrite : bool
60
+ Overwrite contents output directory [true|false]
61
+ """
62
+ task = TotalSegmentatorTask(
63
+ inputs={'scans': scans},
64
+ params={'tasks': tasks},
65
+ output=output,
66
+ overwrite=overwrite,
67
+ )
68
+ task.run()
@@ -4,4 +4,5 @@ from mosamatic2.core.tasks.calculatescorestask.calculatescorestask import Calcul
4
4
  from mosamatic2.core.tasks.createpngsfromsegmentationstask.createpngsfromsegmentationstask import CreatePngsFromSegmentationsTask
5
5
  from mosamatic2.core.tasks.dicom2niftitask.dicom2niftitask import Dicom2NiftiTask
6
6
  from mosamatic2.core.tasks.selectslicefromscanstask.selectslicefromscanstask import SelectSliceFromScansTask
7
- from mosamatic2.core.tasks.createdicomsummarytask.createdicomsummarytask import CreateDicomSummaryTask
7
+ from mosamatic2.core.tasks.createdicomsummarytask.createdicomsummarytask import CreateDicomSummaryTask
8
+ from mosamatic2.core.tasks.totalsegmentatortask.totalsegmentatortask import TotalSegmentatorTask
@@ -0,0 +1,50 @@
1
+ import os
2
+ import shutil
3
+ import tempfile
4
+ from totalsegmentator.python_api import totalsegmentator
5
+ from mosamatic2.core.tasks.task import Task
6
+ from mosamatic2.core.managers.logmanager import LogManager
7
+
8
+ LOG = LogManager()
9
+ TOTAL_SEGMENTATOR_OUTPUT_DIR = os.path.join(tempfile.gettempdir(), 'total_segmentator_output')
10
+
11
+
12
+ class TotalSegmentatorTask(Task):
13
+ INPUTS = ['scans']
14
+ PARAMS = ['tasks']
15
+
16
+ def __init__(self, inputs, params, output, overwrite):
17
+ super(TotalSegmentatorTask, self).__init__(inputs, params, output, overwrite)
18
+ LOG.info(f'Using temporary output directory: {TOTAL_SEGMENTATOR_OUTPUT_DIR}')
19
+
20
+ def load_scan_dirs(self):
21
+ scan_dirs = []
22
+ for d in os.listdir(self.input('scans')):
23
+ scan_dir = os.path.join(self.input('scans'), d)
24
+ if os.path.isdir(scan_dir):
25
+ scan_dirs.append(scan_dir)
26
+ return scan_dirs
27
+
28
+ def extract_masks(self, scan_dir):
29
+ os.makedirs(TOTAL_SEGMENTATOR_OUTPUT_DIR, exist_ok=True)
30
+ tasks = self.param('tasks').split(",") if self.param('tasks') else []
31
+ for task in tasks:
32
+ LOG.info(f'Running task {task}...')
33
+ totalsegmentator(input=scan_dir, output=TOTAL_SEGMENTATOR_OUTPUT_DIR, task=task)
34
+
35
+ def run(self):
36
+ scan_dirs = self.load_scan_dirs()
37
+ nr_steps = len(scan_dirs)
38
+ for step in range(nr_steps):
39
+ scan_dir = scan_dirs[step]
40
+ try:
41
+ self.extract_masks(scan_dir)
42
+ except Exception as e:
43
+ LOG.error(f'{scan_dir}: Could not extract masks [{str(e)}]. Skipping scan...')
44
+ self.set_progress(step, nr_steps)
45
+ LOG.info(f'Copying temporary output to final output directory...')
46
+ for f in os.listdir(TOTAL_SEGMENTATOR_OUTPUT_DIR):
47
+ if f.endswith('.nii') or f.endswith('.nii.gz'):
48
+ f_path = os.path.join(TOTAL_SEGMENTATOR_OUTPUT_DIR, f)
49
+ shutil.move(f_path, self.output())
50
+ LOG.info(f'Copied {f}')
@@ -363,5 +363,9 @@ def is_docker_running():
363
363
  return False
364
364
 
365
365
 
366
+ def is_path_docker_compatible(path):
367
+ return not ' ' in path
368
+
369
+
366
370
  def to_unix_path(path):
367
371
  return path.replace("\\", "/").replace(" ", "\\ ")
@@ -21,6 +21,7 @@ from mosamatic2.ui.widgets.panels.tasks.calculatescorestaskpanel import Calculat
21
21
  from mosamatic2.ui.widgets.panels.tasks.dicom2niftitaskpanel import Dicom2NiftiTaskPanel
22
22
  from mosamatic2.ui.widgets.panels.tasks.createdicomsummarytaskpanel import CreateDicomSummaryTaskPanel
23
23
  from mosamatic2.ui.widgets.panels.tasks.selectslicefromscanstaskpanel import SelectSliceFromScansTaskPanel
24
+ from mosamatic2.ui.widgets.panels.tasks.totalsegmentatortaskpanel import TotalSegmentatorTaskPanel
24
25
  from mosamatic2.ui.widgets.panels.pipelines.defaultpipelinepanel import DefaultPipelinePanel
25
26
  from mosamatic2.ui.widgets.panels.pipelines.defaultdockerpipelinepanel import DefaultDockerPipelinePanel
26
27
  from mosamatic2.ui.widgets.panels.pipelines.boadockerpipelinepanel import BoaDockerPipelinePanel
@@ -43,6 +44,7 @@ class MainWindow(QMainWindow):
43
44
  self._dicom2nifti_task_panel = None
44
45
  self._create_dicom_summary_task_panel = None
45
46
  self._select_slice_from_scans_task_panel = None
47
+ self._total_segmentator_task_panel = None
46
48
  self._default_pipeline_panel = None
47
49
  self._default_docker_pipeline_panel = None
48
50
  self._boa_docker_pipeline_panel = None
@@ -90,6 +92,8 @@ class MainWindow(QMainWindow):
90
92
  create_dicom_summary_task_action.triggered.connect(self.handle_create_dicom_summary_task_action)
91
93
  select_slice_from_scans_task_action = QAction('SelectSliceFromScansTask', self)
92
94
  select_slice_from_scans_task_action.triggered.connect(self.handle_select_slice_from_scans_task_action)
95
+ total_segmentator_task_action = QAction('TotalSegmentatorTask', self)
96
+ total_segmentator_task_action.triggered.connect(self.handle_total_segmentator_task_action)
93
97
  tasks_menu = self.menuBar().addMenu('Tasks')
94
98
  tasks_menu.addAction(rescale_dicom_images_task_action)
95
99
  tasks_menu.addAction(segment_muscle_fat_l3_tensorflow_task_action)
@@ -98,6 +102,7 @@ class MainWindow(QMainWindow):
98
102
  tasks_menu.addAction(dicom2nifti_task_action)
99
103
  tasks_menu.addAction(create_dicom_summary_task_action)
100
104
  tasks_menu.addAction(select_slice_from_scans_task_action)
105
+ tasks_menu.addAction(total_segmentator_task_action)
101
106
 
102
107
  def init_pipelines_menu(self):
103
108
  default_pipeline_action = QAction('DefaultPipeline', self)
@@ -137,6 +142,7 @@ class MainWindow(QMainWindow):
137
142
  self._main_panel.add_panel(self.dicom2nifti_task_panel(), 'dicom2niftitaskpanel')
138
143
  self._main_panel.add_panel(self.create_dicom_summary_task_panel(), 'createdicomsummarytaskpanel')
139
144
  self._main_panel.add_panel(self.select_slice_from_scans_task_panel(), 'selectslicefromscanstaskpanel')
145
+ self._main_panel.add_panel(self.total_segmentator_task_panel(), 'totalsegmentatortaskpanel')
140
146
  self._main_panel.add_panel(self.default_pipeline_panel(), 'defaultpipelinepanel')
141
147
  self._main_panel.add_panel(self.default_docker_pipeline_panel(), 'defaultdockerpipelinepanel')
142
148
  self._main_panel.add_panel(self.boa_docker_pipeline_panel(), 'boadockerpipelinepanel')
@@ -186,6 +192,11 @@ class MainWindow(QMainWindow):
186
192
  if not self._select_slice_from_scans_task_panel:
187
193
  self._select_slice_from_scans_task_panel = SelectSliceFromScansTaskPanel()
188
194
  return self._select_slice_from_scans_task_panel
195
+
196
+ def total_segmentator_task_panel(self):
197
+ if not self._total_segmentator_task_panel:
198
+ self._total_segmentator_task_panel = TotalSegmentatorTaskPanel()
199
+ return self._total_segmentator_task_panel
189
200
 
190
201
  def default_pipeline_panel(self):
191
202
  if not self._default_pipeline_panel:
@@ -235,6 +246,9 @@ class MainWindow(QMainWindow):
235
246
  def handle_select_slice_from_scans_task_action(self):
236
247
  self.main_panel().select_panel('selectslicefromscanstaskpanel')
237
248
 
249
+ def handle_total_segmentator_task_action(self):
250
+ self.main_panel().select_panel('totalsegmentatortaskpanel')
251
+
238
252
  def handle_default_pipeline_action(self):
239
253
  self.main_panel().select_panel('defaultpipelinepanel')
240
254
 
@@ -260,6 +274,7 @@ class MainWindow(QMainWindow):
260
274
  self.dicom2nifti_task_panel().save_inputs_and_parameters()
261
275
  self.create_dicom_summary_task_panel().save_inputs_and_parameters()
262
276
  self.select_slice_from_scans_task_panel().save_inputs_and_parameters()
277
+ self.total_segmentator_task_panel().save_inputs_and_parameters()
263
278
  self.default_pipeline_panel().save_inputs_and_parameters()
264
279
  self.default_docker_pipeline_panel().save_inputs_and_parameters()
265
280
  self.boa_docker_pipeline_panel().save_inputs_and_parameters()
@@ -16,6 +16,7 @@ from PySide6.QtCore import (
16
16
  )
17
17
 
18
18
  from mosamatic2.core.managers.logmanager import LogManager
19
+ from mosamatic2.core.utils import is_docker_running, is_path_docker_compatible
19
20
  from mosamatic2.ui.widgets.panels.pipelines.pipelinepanel import PipelinePanel
20
21
  from mosamatic2.ui.settings import Settings
21
22
  from mosamatic2.ui.utils import is_macos
@@ -127,6 +128,15 @@ class BoaDockerPipelinePanel(PipelinePanel):
127
128
  self.settings().set('last_directory', directory)
128
129
 
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
130
140
  errors = self.check_inputs_and_parameters()
131
141
  if len(errors) > 0:
132
142
  error_message = 'Following errors were encountered:\n'
@@ -18,6 +18,7 @@ from PySide6.QtCore import (
18
18
  )
19
19
 
20
20
  from mosamatic2.core.managers.logmanager import LogManager
21
+ from mosamatic2.core.utils import is_docker_running, is_path_docker_compatible
21
22
  from mosamatic2.ui.widgets.panels.pipelines.pipelinepanel import PipelinePanel
22
23
  from mosamatic2.ui.settings import Settings
23
24
  from mosamatic2.ui.utils import is_macos
@@ -223,6 +224,18 @@ class DefaultDockerPipelinePanel(PipelinePanel):
223
224
  self.model_type_combobox().setCurrentText('pytorch')
224
225
 
225
226
  def handle_run_pipeline_button(self):
227
+ if not is_docker_running():
228
+ QMessageBox.information(self, 'Error', 'Docker is not running. Please start Docker Desktop first')
229
+ return
230
+ if not is_path_docker_compatible(self.images_dir_line_edit().text()):
231
+ QMessageBox.information(self, 'Error', 'Path to images directory contains spaces and is not Docker compatible')
232
+ return
233
+ if not is_path_docker_compatible(self.model_files_dir_line_edit().text()):
234
+ QMessageBox.information(self, 'Error', 'Path to model files directory contains spaces and is not Docker compatible')
235
+ return
236
+ if not is_path_docker_compatible(self.output_dir_line_edit().text()):
237
+ QMessageBox.information(self, 'Error', 'Path to output directory contains spaces and is not Docker compatible')
238
+ return
226
239
  errors = self.check_inputs_and_parameters()
227
240
  if len(errors) > 0:
228
241
  error_message = 'Following errors were encountered:\n'
@@ -0,0 +1,195 @@
1
+ import os
2
+
3
+ from PySide6.QtWidgets import (
4
+ QLineEdit,
5
+ QCheckBox,
6
+ QComboBox,
7
+ QHBoxLayout,
8
+ QVBoxLayout,
9
+ QFormLayout,
10
+ QPushButton,
11
+ QFileDialog,
12
+ QMessageBox,
13
+ )
14
+ from PySide6.QtCore import (
15
+ QThread,
16
+ Slot,
17
+ )
18
+
19
+ from mosamatic2.core.managers.logmanager import LogManager
20
+ from mosamatic2.ui.widgets.panels.tasks.taskpanel import TaskPanel
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.tasks import TotalSegmentatorTask
25
+
26
+ LOG = LogManager()
27
+
28
+ PANEL_TITLE = 'TotalSegmentatorTaskPanel'
29
+ PANEL_NAME = 'totalsegmentatortaskpanel'
30
+
31
+
32
+ class TotalSegmentatorTaskPanel(TaskPanel):
33
+ def __init__(self):
34
+ super(TotalSegmentatorTaskPanel, self).__init__()
35
+ self.set_title(PANEL_TITLE)
36
+ self._scans_dir_line_edit = None
37
+ self._scans_dir_select_button = None
38
+ self._tasks_line_edit = None
39
+ self._output_dir_line_edit = None
40
+ self._output_dir_select_button = None
41
+ self._overwrite_checkbox = None
42
+ self._form_layout = None
43
+ self._run_task_button = None
44
+ self._settings = None
45
+ self._task = None
46
+ self._worker = None
47
+ self._thread = None
48
+ self.init_layout()
49
+
50
+ def scans_dir_line_edit(self):
51
+ if not self._scans_dir_line_edit:
52
+ self._scans_dir_line_edit = QLineEdit(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._scans_dir_select_button:
57
+ self._scans_dir_select_button = QPushButton('Select')
58
+ self._scans_dir_select_button.clicked.connect(self.handle_scans_dir_select_button)
59
+ return self._scans_dir_select_button
60
+
61
+ def tasks_line_edit(self):
62
+ if not self._tasks_line_edit:
63
+ self._tasks_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/tasks'))
64
+ if self._tasks_line_edit.text() == '':
65
+ self._tasks_line_edit.setText('total')
66
+ return self._tasks_line_edit
67
+
68
+ def output_dir_line_edit(self):
69
+ if not self._output_dir_line_edit:
70
+ self._output_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/output_dir'))
71
+ return self._output_dir_line_edit
72
+
73
+ def output_dir_select_button(self):
74
+ if not self._output_dir_select_button:
75
+ self._output_dir_select_button = QPushButton('Select')
76
+ self._output_dir_select_button.clicked.connect(self.handle_output_dir_select_button)
77
+ return self._output_dir_select_button
78
+
79
+ def overwrite_checkbox(self):
80
+ if not self._overwrite_checkbox:
81
+ self._overwrite_checkbox = QCheckBox('')
82
+ self._overwrite_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/overwrite', True))
83
+ return self._overwrite_checkbox
84
+
85
+ def form_layout(self):
86
+ if not self._form_layout:
87
+ self._form_layout = QFormLayout()
88
+ if is_macos():
89
+ self._form_layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
90
+ return self._form_layout
91
+
92
+ def run_task_button(self):
93
+ if not self._run_task_button:
94
+ self._run_task_button = QPushButton('Run task')
95
+ self._run_task_button.clicked.connect(self.handle_run_task_button)
96
+ return self._run_task_button
97
+
98
+ def settings(self):
99
+ if not self._settings:
100
+ self._settings = Settings()
101
+ return self._settings
102
+
103
+ def init_layout(self):
104
+ scans_dir_layout = QHBoxLayout()
105
+ scans_dir_layout.addWidget(self.scans_dir_line_edit())
106
+ scans_dir_layout.addWidget(self.scans_dir_select_button())
107
+ output_dir_layout = QHBoxLayout()
108
+ output_dir_layout.addWidget(self.output_dir_line_edit())
109
+ output_dir_layout.addWidget(self.output_dir_select_button())
110
+ self.form_layout().addRow('Scans directory', scans_dir_layout)
111
+ self.form_layout().addRow('Tasks', self.tasks_line_edit())
112
+ self.form_layout().addRow('Output directory', output_dir_layout)
113
+ self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
114
+ layout = QVBoxLayout()
115
+ layout.addLayout(self.form_layout())
116
+ layout.addWidget(self.run_task_button())
117
+ self.setLayout(layout)
118
+ self.setObjectName(PANEL_NAME)
119
+
120
+ def handle_scans_dir_select_button(self):
121
+ last_directory = self.settings().get('last_directory')
122
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
123
+ if directory:
124
+ self.scans_dir_line_edit().setText(directory)
125
+ self.settings().set('last_directory', directory)
126
+
127
+ def handle_output_dir_select_button(self):
128
+ last_directory = self.settings().get('last_directory')
129
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
130
+ if directory:
131
+ self.output_dir_line_edit().setText(directory)
132
+ self.settings().set('last_directory', directory)
133
+
134
+ def handle_run_task_button(self):
135
+ errors = self.check_inputs_and_parameters()
136
+ if len(errors) > 0:
137
+ error_message = 'Following errors were encountered:\n'
138
+ for error in errors:
139
+ error_message += f' - {error}\n'
140
+ QMessageBox.information(self, 'Error', error_message)
141
+ else:
142
+ LOG.info('Running task...')
143
+ self.run_task_button().setEnabled(False)
144
+ self.save_inputs_and_parameters()
145
+ self._task = TotalSegmentatorTask(
146
+ inputs={'scans': self.scans_dir_line_edit().text()},
147
+ params={'tasks': self.tasks_line_edit().text()},
148
+ output=self.output_dir_line_edit().text(),
149
+ overwrite=self.overwrite_checkbox().isChecked(),
150
+ )
151
+ self._worker = Worker(self._task)
152
+ self._thread = QThread()
153
+ self._worker.moveToThread(self._thread)
154
+ self._thread.started.connect(self._worker.run)
155
+ self._worker.progress.connect(self.handle_progress)
156
+ self._worker.status.connect(self.handle_status)
157
+ self._worker.finished.connect(self.handle_finished)
158
+ self._worker.finished.connect(self._thread.quit)
159
+ self._worker.finished.connect(self._worker.deleteLater)
160
+ self._thread.finished.connect(self._thread.deleteLater)
161
+ self._thread.start()
162
+
163
+ @Slot(int)
164
+ def handle_progress(self, progress):
165
+ LOG.info(f'Progress: {progress} / 100%')
166
+
167
+ @Slot(str)
168
+ def handle_status(self, status):
169
+ LOG.info(f'Status: {status}')
170
+
171
+ @Slot()
172
+ def handle_finished(self):
173
+ self.run_task_button().setEnabled(True)
174
+
175
+ # HELPERS
176
+
177
+ def check_inputs_and_parameters(self):
178
+ errors = []
179
+ if self.scans_dir_line_edit().text() == '':
180
+ errors.append('Empty scans directory path')
181
+ if not os.path.isdir(self.scans_dir_line_edit().text()):
182
+ errors.append('Scans directory does not exist')
183
+ if self.output_dir_line_edit().text() == '':
184
+ errors.append('Empty output directory path')
185
+ if os.path.isdir(self.output_dir_line_edit().text()) and not self.overwrite_checkbox().isChecked():
186
+ errors.append('Output directory exists but overwrite=False. Please remove output directory first')
187
+ if self.tasks_line_edit().text() == '':
188
+ errors.append('Tasks cannot be empty and must be comma-separated list of values')
189
+ return errors
190
+
191
+ def save_inputs_and_parameters(self):
192
+ self.settings().set(f'{PANEL_NAME}/scans_dir', self.scans_dir_line_edit().text())
193
+ self.settings().set(f'{PANEL_NAME}/tasks', self.tasks_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())
@@ -80,9 +80,9 @@ class SliceViewer(QWidget):
80
80
  slice.SetMapper(slice_mapper)
81
81
  slice_text_actor = self.create_text_actor("", 0.01, 0.01, 12, align_bottom=True, normalized=True)
82
82
  usage_text_actor = self.create_text_actor(
83
- "- Slice with mouse wheel or Up/Down-Key (first click inside viewer)\n"
83
+ "- Slice with mouse wheel or Up/Down keys (first click inside viewer)\n"
84
84
  "- Zoom with pressed right mouse button while dragging\n"
85
- "- Pan with middle mouse button while dragging\n"
85
+ "- Pan with Shift key and left mouse button while dragging\n"
86
86
  "- Change contrast/brightness with pressed left mouse while dragging",
87
87
  0.01, 0.99, 12, normalized=True)
88
88
  ren = vtk.vtkRenderer()
@@ -1 +0,0 @@
1
- 2.0.13
File without changes
File without changes