mosamatic2 2.0.10__tar.gz → 2.0.12__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 (100) hide show
  1. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/PKG-INFO +1 -1
  2. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/pyproject.toml +1 -1
  3. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/commands/dicom2nifti.py +8 -8
  4. mosamatic2-2.0.12/src/mosamatic2/core/data/multiniftiimage.py +26 -0
  5. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/data/multinumpyimage.py +4 -4
  6. mosamatic2-2.0.12/src/mosamatic2/core/data/niftiimage.py +13 -0
  7. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/data/numpyimage.py +1 -1
  8. mosamatic2-2.0.12/src/mosamatic2/core/pipelines/__init__.py +2 -0
  9. mosamatic2-2.0.12/src/mosamatic2/core/pipelines/defaultdockerpipeline/defaultdockerpipeline.py +42 -0
  10. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py +2 -2
  11. mosamatic2-2.0.12/src/mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py +38 -0
  12. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorflowtask.py +3 -0
  13. mosamatic2-2.0.12/src/mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py +162 -0
  14. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/utils.py +12 -2
  15. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/mainwindow.py +15 -0
  16. mosamatic2-2.0.12/src/mosamatic2/ui/resources/VERSION +1 -0
  17. mosamatic2-2.0.12/src/mosamatic2/ui/widgets/panels/pipelines/defaultdockerpipelinepanel.py +311 -0
  18. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/tasks/dicom2niftitaskpanel.py +23 -23
  19. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/tasks/selectslicefromscanstaskpanel.py +1 -1
  20. mosamatic2-2.0.12/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization/__init__.py +0 -0
  21. mosamatic2-2.0.10/src/mosamatic2/core/pipelines/__init__.py +0 -1
  22. mosamatic2-2.0.10/src/mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py +0 -24
  23. mosamatic2-2.0.10/src/mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py +0 -114
  24. mosamatic2-2.0.10/src/mosamatic2/ui/resources/VERSION +0 -1
  25. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/README.md +0 -0
  26. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/models.py +0 -0
  27. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/__init__.py +0 -0
  28. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/app.py +0 -0
  29. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/cli.py +0 -0
  30. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/commands/__init__.py +0 -0
  31. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/commands/calculatescores.py +0 -0
  32. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/commands/createdicomsummary.py +0 -0
  33. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/commands/createpngsfromsegmentations.py +0 -0
  34. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/commands/defaultpipeline.py +0 -0
  35. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/commands/rescaledicomimages.py +0 -0
  36. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/commands/segmentmusclefatl3tensorflow.py +0 -0
  37. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/commands/selectslicefromscans.py +0 -0
  38. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/constants.py +0 -0
  39. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/__init__.py +0 -0
  40. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/data/__init__.py +0 -0
  41. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/data/dicomimage.py +0 -0
  42. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/data/dicomimageseries.py +0 -0
  43. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/data/dixonseries.py +0 -0
  44. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/data/filedata.py +0 -0
  45. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/data/multidicomimage.py +0 -0
  46. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/managers/__init__.py +0 -0
  47. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/managers/logmanager.py +0 -0
  48. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/managers/logmanagerlistener.py +0 -0
  49. {mosamatic2-2.0.10/src/mosamatic2/core/pipelines/defaultpipeline → mosamatic2-2.0.12/src/mosamatic2/core/pipelines/defaultdockerpipeline}/__init__.py +0 -0
  50. {mosamatic2-2.0.10/src/mosamatic2/core/tasks/calculatescorestask → mosamatic2-2.0.12/src/mosamatic2/core/pipelines/defaultpipeline}/__init__.py +0 -0
  51. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/pipelines/defaultpipeline/defaultpipeline.py +0 -0
  52. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/pipelines/pipeline.py +0 -0
  53. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/singleton.py +0 -0
  54. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/tasks/__init__.py +0 -0
  55. {mosamatic2-2.0.10/src/mosamatic2/core/tasks/createdicomsummarytask → mosamatic2-2.0.12/src/mosamatic2/core/tasks/calculatescorestask}/__init__.py +0 -0
  56. {mosamatic2-2.0.10/src/mosamatic2/core/tasks/createpngsfromsegmentationstask → mosamatic2-2.0.12/src/mosamatic2/core/tasks/createdicomsummarytask}/__init__.py +0 -0
  57. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/tasks/createdicomsummarytask/createdicomsummarytask.py +0 -0
  58. {mosamatic2-2.0.10/src/mosamatic2/core/tasks/dicom2niftitask → mosamatic2-2.0.12/src/mosamatic2/core/tasks/createpngsfromsegmentationstask}/__init__.py +0 -0
  59. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/tasks/createpngsfromsegmentationstask/createpngsfromsegmentationstask.py +0 -0
  60. {mosamatic2-2.0.10/src/mosamatic2/core/tasks/rescaledicomimagestask → mosamatic2-2.0.12/src/mosamatic2/core/tasks/dicom2niftitask}/__init__.py +0 -0
  61. {mosamatic2-2.0.10/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask → mosamatic2-2.0.12/src/mosamatic2/core/tasks/rescaledicomimagestask}/__init__.py +0 -0
  62. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/tasks/rescaledicomimagestask/rescaledicomimagestask.py +0 -0
  63. {mosamatic2-2.0.10/src/mosamatic2/core/tasks/selectslicefromscanstask → mosamatic2-2.0.12/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask}/__init__.py +0 -0
  64. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/paramloader.py +0 -0
  65. {mosamatic2-2.0.10/src/mosamatic2/ui → mosamatic2-2.0.12/src/mosamatic2/core/tasks/selectslicefromscanstask}/__init__.py +0 -0
  66. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/core/tasks/task.py +0 -0
  67. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/server.py +0 -0
  68. {mosamatic2-2.0.10/src/mosamatic2/ui/widgets → mosamatic2-2.0.12/src/mosamatic2/ui}/__init__.py +0 -0
  69. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/resources/icons/mosamatic2.icns +0 -0
  70. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/resources/icons/mosamatic2.ico +0 -0
  71. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/resources/icons/spinner.gif +0 -0
  72. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/resources/images/body-composition.jpg +0 -0
  73. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/settings.py +0 -0
  74. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/utils.py +0 -0
  75. {mosamatic2-2.0.10/src/mosamatic2/ui/widgets/dialogs → mosamatic2-2.0.12/src/mosamatic2/ui/widgets}/__init__.py +0 -0
  76. {mosamatic2-2.0.10/src/mosamatic2/ui/widgets/panels → mosamatic2-2.0.12/src/mosamatic2/ui/widgets/dialogs}/__init__.py +0 -0
  77. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/dialogs/dialog.py +0 -0
  78. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/dialogs/helpdialog.py +0 -0
  79. {mosamatic2-2.0.10/src/mosamatic2/ui/widgets/panels/pipelines → mosamatic2-2.0.12/src/mosamatic2/ui/widgets/panels}/__init__.py +0 -0
  80. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/defaultpanel.py +0 -0
  81. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/logpanel.py +0 -0
  82. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/mainpanel.py +0 -0
  83. {mosamatic2-2.0.10/src/mosamatic2/ui/widgets/panels/tasks → mosamatic2-2.0.12/src/mosamatic2/ui/widgets/panels/pipelines}/__init__.py +0 -0
  84. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py +0 -0
  85. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/pipelines/pipelinepanel.py +0 -0
  86. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/stackedpanel.py +0 -0
  87. {mosamatic2-2.0.10/src/mosamatic2/ui/widgets/panels/visualizations → mosamatic2-2.0.12/src/mosamatic2/ui/widgets/panels/tasks}/__init__.py +0 -0
  88. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/tasks/calculatescorestaskpanel.py +0 -0
  89. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/tasks/createdicomsummarytaskpanel.py +0 -0
  90. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/tasks/createpngsfromsegmentationstaskpanel.py +0 -0
  91. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/tasks/rescaledicomimagestaskpanel.py +0 -0
  92. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/tasks/segmentmusclefatl3tensorflowtaskpanel.py +0 -0
  93. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/tasks/taskpanel.py +0 -0
  94. {mosamatic2-2.0.10/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization → mosamatic2-2.0.12/src/mosamatic2/ui/widgets/panels/visualizations}/__init__.py +0 -0
  95. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization/custominteractorstyle.py +0 -0
  96. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization/sliceviewer.py +0 -0
  97. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization/slicevisualization.py +0 -0
  98. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/panels/visualizations/visualization.py +0 -0
  99. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/src/mosamatic2/ui/widgets/splashscreen.py +0 -0
  100. {mosamatic2-2.0.10 → mosamatic2-2.0.12}/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.10
3
+ Version: 2.0.12
4
4
  Summary:
5
5
  Author: Ralph Brecheisen
6
6
  Author-email: r.brecheisen@maastrichtuniversity.nl
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mosamatic2"
3
- version = "2.0.10"
3
+ version = "2.0.12"
4
4
  description = ""
5
5
  authors = [
6
6
  {name = "Ralph Brecheisen", email = "r.brecheisen@maastrichtuniversity.nl"}
@@ -3,12 +3,12 @@ import click
3
3
  from mosamatic2.core.tasks import Dicom2NiftiTask
4
4
 
5
5
 
6
- @click.command(help='Converts DICOM series to NIFTI')
6
+ @click.command(help='Converts root directory with DICOM series to NIFTI format')
7
7
  @click.option(
8
- '--images',
8
+ '--scans',
9
9
  required=True,
10
10
  type=click.Path(exists=True),
11
- help='Directory with images',
11
+ help='Root directory with DICOM scans (one for each patient)',
12
12
  )
13
13
  @click.option(
14
14
  '--output',
@@ -22,14 +22,14 @@ from mosamatic2.core.tasks import Dicom2NiftiTask
22
22
  default=False,
23
23
  help='Overwrite [true|false]'
24
24
  )
25
- def dicom2nifti(images, output, overwrite):
25
+ def dicom2nifti(scans, output, overwrite):
26
26
  """
27
- Converts single DICOM series (scan) to NIFTI
27
+ Converts DICOM scans to NIFTI format.
28
28
 
29
29
  Parameters
30
30
  ----------
31
- --images : str
32
- Directory with DICOM images of a single series
31
+ --scans : str
32
+ Root directory with DICOM scans (one subdirectory for each patient)
33
33
 
34
34
  --output : str
35
35
  Path to output directory
@@ -38,7 +38,7 @@ def dicom2nifti(images, output, overwrite):
38
38
  Overwrite contents output directory [true|false]
39
39
  """
40
40
  task = Dicom2NiftiTask(
41
- inputs={'images': images},
41
+ inputs={'scans': scans},
42
42
  params=None,
43
43
  output=output,
44
44
  overwrite=overwrite,
@@ -0,0 +1,26 @@
1
+ import os
2
+ from mosamatic2.core.managers.logmanager import LogManager
3
+ from mosamatic2.core.data.filedata import FileData
4
+ from mosamatic2.core.data.niftiimage import NiftiImage
5
+
6
+ LOG = LogManager()
7
+
8
+
9
+ class MultiNiftiImage(FileData):
10
+ def __init__(self):
11
+ super(MultiNiftiImage, self).__init__()
12
+ self._images = []
13
+
14
+ def images(self):
15
+ return self._images
16
+
17
+ def load(self):
18
+ if self.path():
19
+ for f in os.listdir(self.path()):
20
+ f_path = os.path.join(self.path(), f)
21
+ image = NiftiImage()
22
+ image.set_path(f_path)
23
+ if image.load():
24
+ self._images.append(image)
25
+ return True
26
+ return False
@@ -1,14 +1,14 @@
1
1
  import os
2
2
  from mosamatic2.core.managers.logmanager import LogManager
3
3
  from mosamatic2.core.data.filedata import FileData
4
- from mosamatic2.core.data.numpyimage import NumPyImage
4
+ from mosamatic2.core.data.numpyimage import NumpyImage
5
5
 
6
6
  LOG = LogManager()
7
7
 
8
8
 
9
- class MultiNumPyImage(FileData):
9
+ class MultiNumpyImage(FileData):
10
10
  def __init__(self):
11
- super(MultiNumPyImage, self).__init__()
11
+ super(MultiNumpyImage, self).__init__()
12
12
  self._images = []
13
13
 
14
14
  def images(self):
@@ -18,7 +18,7 @@ class MultiNumPyImage(FileData):
18
18
  if self.path():
19
19
  for f in os.listdir(self.path()):
20
20
  f_path = os.path.join(self.path(), f)
21
- image = NumPyImage()
21
+ image = NumpyImage()
22
22
  image.set_path(f_path)
23
23
  if image.load():
24
24
  self._images.append(image)
@@ -0,0 +1,13 @@
1
+ from mosamatic2.core.data.filedata import FileData
2
+ from mosamatic2.core.utils import (
3
+ is_nifti,
4
+ load_nifti,
5
+ )
6
+
7
+ class NiftiImage(FileData):
8
+ def load(self):
9
+ if self.path():
10
+ if is_nifti(self.path()):
11
+ self.set_object(load_nifti(self.path()))
12
+ return True
13
+ return False
@@ -4,7 +4,7 @@ from mosamatic2.core.utils import (
4
4
  load_numpy_array,
5
5
  )
6
6
 
7
- class NumPyImage(FileData):
7
+ class NumpyImage(FileData):
8
8
  def load(self):
9
9
  if self.path():
10
10
  if is_numpy(self.path()):
@@ -0,0 +1,2 @@
1
+ from mosamatic2.core.pipelines.defaultpipeline.defaultpipeline import DefaultPipeline
2
+ from mosamatic2.core.pipelines.defaultdockerpipeline.defaultdockerpipeline import DefaultDockerPipeline
@@ -0,0 +1,42 @@
1
+ import os
2
+
3
+ from mosamatic2.core.pipelines.pipeline import Pipeline
4
+ from mosamatic2.core.managers.logmanager import LogManager
5
+
6
+ LOG = LogManager()
7
+ DOCKER_SCRIPT = """
8
+ docker run --rm ^
9
+ -v "%IMAGES%":/data/images ^
10
+ -v "%MODEL_FILES%":/data/model_files ^
11
+ -v "%OUTPUT%":/data/output ^
12
+ brecheisen/mosamatic2-cli:%VERSION% defaultpipeline ^
13
+ --images /data/images --model_files /data/model_files --output /data/output --overwrite true
14
+ """
15
+
16
+
17
+ class DefaultDockerPipeline(Pipeline):
18
+ INPUTS = [
19
+ 'images',
20
+ 'model_files',
21
+ ]
22
+ PARAMS = [
23
+ 'target_size',
24
+ 'file_type',
25
+ 'fig_width',
26
+ 'fig_height',
27
+ 'model_type',
28
+ 'model_version',
29
+ 'version',
30
+ ]
31
+ def __init__(self, inputs, params, output, overwrite):
32
+ super(DefaultDockerPipeline, self).__init__(inputs, params, output, overwrite)
33
+
34
+ def run(self):
35
+ docker_script = 'docker run --rm ' + \
36
+ '-v "{}":/data/images '.format(self.input('images')) + \
37
+ '-v "{}":/data/model_files '.format(self.input('model_files')) + \
38
+ '-v "{}":/data/output '.format(self.output()) + \
39
+ 'brecheisen/mosamatic2-cli:{} defaultpipeline '.format(self.param('version')) + \
40
+ '--images /data/images --model_files /data/model_files --output /data/output --overwrite true'
41
+ LOG.info(f'Running Docker script: {docker_script}')
42
+ os.system(docker_script)
@@ -4,7 +4,7 @@ import pandas as pd
4
4
  from mosamatic2.core.tasks.task import Task
5
5
  from mosamatic2.core.managers.logmanager import LogManager
6
6
  from mosamatic2.core.data.multidicomimage import MultiDicomImage
7
- from mosamatic2.core.data.numpyimage import NumPyImage
7
+ from mosamatic2.core.data.numpyimage import NumpyImage
8
8
  from mosamatic2.core.utils import (
9
9
  get_pixels_from_dicom_object,
10
10
  calculate_area,
@@ -74,7 +74,7 @@ class CalculateScoresTask(Task):
74
74
 
75
75
  def load_segmentation(self, f, file_type='npy'):
76
76
  if file_type == 'npy':
77
- segmentation = NumPyImage()
77
+ segmentation = NumpyImage()
78
78
  segmentation.set_path(f)
79
79
  if segmentation.load():
80
80
  return segmentation.object()
@@ -0,0 +1,38 @@
1
+ import os
2
+ import dicom2nifti
3
+ from mosamatic2.core.tasks.task import Task
4
+ from mosamatic2.core.managers.logmanager import LogManager
5
+ from mosamatic2.core.utils import is_dicom
6
+
7
+ LOG = LogManager()
8
+
9
+
10
+ class Dicom2NiftiTask(Task):
11
+ INPUTS = ['scans']
12
+ PARAMS = []
13
+
14
+ def __init__(self, inputs, params, output, overwrite):
15
+ super(Dicom2NiftiTask, self).__init__(inputs, params, output, overwrite)
16
+
17
+ def load_scan_dirs(self):
18
+ scan_dirs = []
19
+ for d in os.listdir(self.input('scans')):
20
+ scan_dir = os.path.join(self.input('scans'), d)
21
+ if os.path.isdir(scan_dir):
22
+ scan_dirs.append(scan_dir)
23
+ return scan_dirs
24
+
25
+ def run(self):
26
+ scan_dirs = self.load_scan_dirs()
27
+ nr_steps = len(scan_dirs)
28
+ for step in range(nr_steps):
29
+ scan_dir = scan_dirs[step]
30
+ scan_name = os.path.split(scan_dir)[1]
31
+ nifti_file_name = scan_name + '.nii.gz'
32
+ LOG.info(f'Converting DICOM series in {scan_dir} to {nifti_file_name}')
33
+ dicom2nifti.dicom_series_to_nifti(
34
+ scan_dir,
35
+ os.path.join(self.output(), nifti_file_name),
36
+ reorient_nifti=True,
37
+ )
38
+ self.set_progress(step, nr_steps)
@@ -9,6 +9,7 @@ from mosamatic2.core.tasks.task import Task
9
9
  from mosamatic2.core.tasks.segmentmusclefatl3tensorflowtask.paramloader import ParamLoader
10
10
  from mosamatic2.core.data.multidicomimage import MultiDicomImage
11
11
  from mosamatic2.core.data.dicomimage import DicomImage
12
+ from mosamatic2.core.managers.logmanager import LogManager
12
13
  from mosamatic2.core.utils import (
13
14
  normalize_between,
14
15
  get_pixels_from_dicom_object,
@@ -16,6 +17,8 @@ from mosamatic2.core.utils import (
16
17
  )
17
18
 
18
19
  DEVICE = 'cpu'
20
+ L3_INDEX = 167
21
+ LOG = LogManager()
19
22
 
20
23
 
21
24
  class SegmentMuscleFatL3TensorFlowTask(Task):
@@ -0,0 +1,162 @@
1
+ import os
2
+ import math
3
+ import tempfile
4
+ import shutil
5
+ import nibabel as nib
6
+ import numpy as np
7
+ from totalsegmentator.python_api import totalsegmentator
8
+ from mosamatic2.core.tasks.task import Task
9
+ from mosamatic2.core.managers.logmanager import LogManager
10
+ from mosamatic2.core.utils import load_dicom
11
+
12
+ LOG = LogManager()
13
+
14
+ TOTAL_SEGMENTATOR_OUTPUT_DIR = os.path.join(tempfile.gettempdir(), 'total_segmentator_output')
15
+ TOTAL_SEGMENTATOR_TASK = 'total'
16
+ Z_DELTA_OFFSETS = {
17
+ 'vertebrae_L3': 0.333,
18
+ 'vertebrae_T4': 0.5,
19
+ }
20
+
21
+
22
+ class SelectSliceFromScansTask(Task):
23
+ INPUTS = ['scans']
24
+ PARAMS = ['vertebra']
25
+
26
+ def __init__(self, inputs, params, output, overwrite):
27
+ super(SelectSliceFromScansTask, self).__init__(inputs, params, output, overwrite)
28
+ self._error_dir = os.path.split(self.output())[0]
29
+ self._error_dir = os.path.join(self._error_dir, 'selectslicefromscanstask_errors')
30
+ os.makedirs(self._error_dir, exist_ok=True)
31
+ self._error_file = os.path.join(self._error_dir, 'errors.txt')
32
+ with open(self._error_file, 'w') as f:
33
+ f.write('Errors:\n\n')
34
+ LOG.info(f'Error directory: {self._error_dir}')
35
+
36
+ def write_error(self, message):
37
+ LOG.error(message)
38
+ with open(self._error_file, 'a') as f:
39
+ f.write(message + '\n')
40
+
41
+ def load_scan_dirs(self):
42
+ scan_dirs = []
43
+ for d in os.listdir(self.input('scans')):
44
+ scan_dir = os.path.join(self.input('scans'), d)
45
+ if os.path.isdir(scan_dir):
46
+ scan_dirs.append(scan_dir)
47
+ return scan_dirs
48
+
49
+ def extract_masks(self, scan_dir):
50
+ os.makedirs(TOTAL_SEGMENTATOR_OUTPUT_DIR, exist_ok=True)
51
+ totalsegmentator(input=scan_dir, output=TOTAL_SEGMENTATOR_OUTPUT_DIR, fast=True)
52
+ if not os.path.isfile(os.path.join(TOTAL_SEGMENTATOR_OUTPUT_DIR, 'vertebrae_L3.nii.gz')):
53
+ raise Exception(f'{scan_dir}: vertebrae_L3.nii.gz does not exist')
54
+ # os.system(f'TotalSegmentator -i {scan_dir} -o {TOTAL_SEGMENTATOR_OUTPUT_DIR} --fast')
55
+
56
+ def delete_total_segmentator_output(self):
57
+ if os.path.exists(TOTAL_SEGMENTATOR_OUTPUT_DIR):
58
+ shutil.rmtree(TOTAL_SEGMENTATOR_OUTPUT_DIR)
59
+
60
+ def get_z_delta_offset_for_mask(self, mask_name):
61
+ if mask_name not in Z_DELTA_OFFSETS.keys():
62
+ return None
63
+ return Z_DELTA_OFFSETS[mask_name]
64
+
65
+ def find_slice(self, scan_dir, vertebra):
66
+ if vertebra == 'L3':
67
+ vertebral_level = 'vertebrae_L3'
68
+ elif vertebra == 'T4':
69
+ vertebral_level = 'vertebrae_T4'
70
+ else:
71
+ self.write_error(f'{scan_dir}: Unknown vertbra {vertebra}. Options are "L3" and "T4"')
72
+ return None
73
+ # Find Z-positions DICOM images
74
+ z_positions = {}
75
+ for f in os.listdir(scan_dir):
76
+ f_path = os.path.join(scan_dir, f)
77
+ try:
78
+ p = load_dicom(f_path, stop_before_pixels=True)
79
+ if p is not None and hasattr(p, "ImagePositionPatient"):
80
+ z_positions[p.ImagePositionPatient[2]] = f_path
81
+ except Exception as e:
82
+ self.write_error(f"{scan_dir}: Failed to load DICOM {f_path}: {e}")
83
+ break
84
+ if not z_positions:
85
+ self.write_error(f"{scan_dir}: No valid DICOM z-positions found.")
86
+ return None
87
+ # Find Z-position L3 image
88
+ mask_file = os.path.join(TOTAL_SEGMENTATOR_OUTPUT_DIR, f'{vertebral_level}.nii.gz')
89
+ if not os.path.exists(mask_file):
90
+ self.write_error(f"{scan_dir}: Mask file not found: {mask_file}")
91
+ return None
92
+ try:
93
+ mask_obj = nib.load(mask_file)
94
+ mask = mask_obj.get_fdata()
95
+ affine_transform = mask_obj.affine
96
+ except Exception as e:
97
+ self.write_error(f"{scan_dir}: Failed to load mask {mask_file}: {e}")
98
+ return None
99
+ indexes = np.array(np.where(mask == 1))
100
+ if indexes.size == 0:
101
+ self.write_error(f"{scan_dir}: No voxels found in mask {mask_file} for {vertebral_level}")
102
+ return None
103
+ try:
104
+ index_min = indexes.min(axis=1)
105
+ index_max = indexes.max(axis=1)
106
+ except ValueError as e:
107
+ self.write_error(f"{scan_dir}: Invalid indexes array for {vertebral_level}: {e}")
108
+ return None
109
+ world_min = nib.affines.apply_affine(affine_transform, index_min)
110
+ world_max = nib.affines.apply_affine(affine_transform, index_max)
111
+ z_direction = affine_transform[:3, 2][2]
112
+ if z_direction == 0:
113
+ self.write_error(f"{scan_dir}: Affine z-direction is zero.")
114
+ return None
115
+ z_sign = math.copysign(1, z_direction)
116
+ z_delta_offset = self.get_z_delta_offset_for_mask(vertebral_level)
117
+ if z_delta_offset is None:
118
+ return None
119
+ z_delta = 0.333 * abs(world_max[2] - world_min[2]) # This needs to be vertebra-specific perhaps
120
+ z_l3 = world_max[2] - z_sign * z_delta
121
+ # Find closest L3 image in DICOM set
122
+ positions = sorted(z_positions.keys())
123
+ closest_file = None
124
+ for z1, z2 in zip(positions[:-1], positions[1:]):
125
+ if min(z1, z2) <= z_l3 <= max(z1, z2):
126
+ closest_z = min(z_positions.keys(), key=lambda z: abs(z - z_l3))
127
+ closest_file = z_positions[closest_z]
128
+ LOG.info(f'Closest image: {closest_file}')
129
+ break
130
+ if closest_file is None:
131
+ self.write_error(f"{scan_dir}: No matching slice found.")
132
+ return closest_file
133
+
134
+ def run(self):
135
+ scan_dirs = self.load_scan_dirs()
136
+ vertebra = self.param('vertebra')
137
+ nr_steps = len(scan_dirs)
138
+ for step in range(nr_steps):
139
+ scan_dir = scan_dirs[step]
140
+ scan_name = os.path.split(scan_dir)[1]
141
+ errors = False
142
+ LOG.info(f'Processing {scan_dir}...')
143
+ try:
144
+ self.extract_masks(scan_dir)
145
+ except Exception as e:
146
+ self.write_error(f'{scan_dir}: Could not extract masks [{str(e)}]. Skipping scan...')
147
+ errors = True
148
+ if not errors:
149
+ file_path = self.find_slice(scan_dir, vertebra)
150
+ if file_path is not None:
151
+ extension = '' if file_path.endswith('.dcm') else '.dcm'
152
+ target_file_path = os.path.join(self.output(), vertebra + '_' + scan_name + extension)
153
+ shutil.copyfile(file_path, target_file_path)
154
+ else:
155
+ self.write_error(f'{scan_dir}: Could not find slice for vertebral level: {vertebra}')
156
+ errors = True
157
+ self.delete_total_segmentator_output()
158
+ if errors:
159
+ LOG.info(f'Copying problematic scan {scan_dir} to error directory: {self._error_dir}')
160
+ scan_error_dir = os.path.join(self._error_dir, scan_name)
161
+ shutil.copytree(scan_dir, scan_error_dir)
162
+ self.set_progress(step, nr_steps)
@@ -5,6 +5,7 @@ import textwrap
5
5
  import math
6
6
  import pendulum
7
7
  import numpy as np
8
+ import nibabel as nb
8
9
  import struct
9
10
  import binascii
10
11
  import pydicom
@@ -134,6 +135,16 @@ def is_jpeg2000_compressed(p):
134
135
  return False
135
136
 
136
137
 
138
+ def is_nifti(f):
139
+ return f.endswith('.nii') or f.endswith('.nii.gz')
140
+
141
+
142
+ def load_nifti(f):
143
+ if is_nifti(f):
144
+ return nb.load(f)
145
+ return None
146
+
147
+
137
148
  def is_numpy_array(value):
138
149
  return isinstance(value, np.array)
139
150
 
@@ -149,8 +160,7 @@ def is_numpy(f):
149
160
  def load_numpy_array(f):
150
161
  if is_numpy(f):
151
162
  return np.load(f)
152
- else:
153
- return None
163
+ return None
154
164
 
155
165
 
156
166
  def get_pixels_from_tag_file(tag_file_path):
@@ -22,6 +22,7 @@ from mosamatic2.ui.widgets.panels.tasks.dicom2niftitaskpanel import Dicom2NiftiT
22
22
  from mosamatic2.ui.widgets.panels.tasks.createdicomsummarytaskpanel import CreateDicomSummaryTaskPanel
23
23
  from mosamatic2.ui.widgets.panels.tasks.selectslicefromscanstaskpanel import SelectSliceFromScansTaskPanel
24
24
  from mosamatic2.ui.widgets.panels.pipelines.defaultpipelinepanel import DefaultPipelinePanel
25
+ from mosamatic2.ui.widgets.panels.pipelines.defaultdockerpipelinepanel import DefaultDockerPipelinePanel
25
26
  from mosamatic2.ui.widgets.panels.visualizations.slicevisualization.slicevisualization import SliceVisualization
26
27
 
27
28
  LOG = LogManager()
@@ -42,6 +43,7 @@ class MainWindow(QMainWindow):
42
43
  self._create_dicom_summary_task_panel = None
43
44
  self._select_slice_from_scans_task_panel = None
44
45
  self._default_pipeline_panel = None
46
+ self._default_docker_pipeline_panel = None
45
47
  self._slice_visualization = None
46
48
  self.init_window()
47
49
 
@@ -98,8 +100,11 @@ class MainWindow(QMainWindow):
98
100
  def init_pipelines_menu(self):
99
101
  default_pipeline_action = QAction('DefaultPipeline', self)
100
102
  default_pipeline_action.triggered.connect(self.handle_default_pipeline_action)
103
+ default_docker_pipeline_action = QAction('DefaultDockerPipeline', self)
104
+ default_docker_pipeline_action.triggered.connect(self.handle_default_docker_pipeline_action)
101
105
  pipelines_menu = self.menuBar().addMenu('Pipelines')
102
106
  pipelines_menu.addAction(default_pipeline_action)
107
+ pipelines_menu.addAction(default_docker_pipeline_action)
103
108
 
104
109
  def init_visualizations_menu(self):
105
110
  slice_visualization_action = QAction('SliceVisualization', self)
@@ -128,6 +133,7 @@ class MainWindow(QMainWindow):
128
133
  self._main_panel.add_panel(self.create_dicom_summary_task_panel(), 'createdicomsummarytaskpanel')
129
134
  self._main_panel.add_panel(self.select_slice_from_scans_task_panel(), 'selectslicefromscanstaskpanel')
130
135
  self._main_panel.add_panel(self.default_pipeline_panel(), 'defaultpipelinepanel')
136
+ self._main_panel.add_panel(self.default_docker_pipeline_panel(), 'defaultdockerpipelinepanel')
131
137
  self._main_panel.add_panel(self.slice_visualization(), 'slicevisualization')
132
138
  self._main_panel.select_panel('defaultpipelinepanel')
133
139
  return self._main_panel
@@ -179,6 +185,11 @@ class MainWindow(QMainWindow):
179
185
  if not self._default_pipeline_panel:
180
186
  self._default_pipeline_panel = DefaultPipelinePanel()
181
187
  return self._default_pipeline_panel
188
+
189
+ def default_docker_pipeline_panel(self):
190
+ if not self._default_docker_pipeline_panel:
191
+ self._default_docker_pipeline_panel = DefaultDockerPipelinePanel()
192
+ return self._default_docker_pipeline_panel
182
193
 
183
194
  def slice_visualization(self):
184
195
  if not self._slice_visualization:
@@ -216,6 +227,9 @@ class MainWindow(QMainWindow):
216
227
  def handle_default_pipeline_action(self):
217
228
  self.main_panel().select_panel('defaultpipelinepanel')
218
229
 
230
+ def handle_default_docker_pipeline_action(self):
231
+ self.main_panel().select_panel('defaultdockerpipelinepanel')
232
+
219
233
  def handle_slice_visualization_action(self):
220
234
  self.main_panel().select_panel('slicevisualization')
221
235
 
@@ -233,6 +247,7 @@ class MainWindow(QMainWindow):
233
247
  self.create_dicom_summary_task_panel().save_inputs_and_parameters()
234
248
  self.select_slice_from_scans_task_panel().save_inputs_and_parameters()
235
249
  self.default_pipeline_panel().save_inputs_and_parameters()
250
+ self.default_docker_pipeline_panel().save_inputs_and_parameters()
236
251
  self.slice_visualization().save_inputs_and_parameters()
237
252
  return super().closeEvent(event)
238
253