mosamatic2 2.0.3__py3-none-any.whl → 2.0.5__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.

Potentially problematic release.


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

Files changed (26) hide show
  1. mosamatic2/cli.py +4 -0
  2. mosamatic2/commands/calculatescores.py +1 -1
  3. mosamatic2/commands/dicom2nifti.py +46 -0
  4. mosamatic2/commands/rescaledicomimages.py +1 -1
  5. mosamatic2/commands/segmentmusclefatl3tensorflow.py +1 -1
  6. mosamatic2/commands/selectslicefromscans.py +65 -0
  7. mosamatic2/core/data/multinumpyimage.py +26 -0
  8. mosamatic2/core/data/numpyimage.py +13 -0
  9. mosamatic2/core/tasks/__init__.py +3 -1
  10. mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py +6 -6
  11. mosamatic2/core/tasks/dicom2niftitask/__init__.py +0 -0
  12. mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py +24 -0
  13. mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorflowtask.py +1 -3
  14. mosamatic2/core/tasks/selectslicefromscanstask/__init__.py +0 -0
  15. mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py +109 -0
  16. mosamatic2/core/tasks/task.py +5 -4
  17. mosamatic2/core/utils.py +10 -2
  18. mosamatic2/server.py +32 -0
  19. mosamatic2/ui/mainwindow.py +30 -0
  20. mosamatic2/ui/resources/VERSION +1 -1
  21. mosamatic2/ui/widgets/panels/tasks/dicom2niftitaskpanel.py +183 -0
  22. mosamatic2/ui/widgets/panels/tasks/{selectslicefromscantaskpanel.py → selectslicefromscanstaskpanel.py} +26 -17
  23. {mosamatic2-2.0.3.dist-info → mosamatic2-2.0.5.dist-info}/METADATA +2 -1
  24. {mosamatic2-2.0.3.dist-info → mosamatic2-2.0.5.dist-info}/RECORD +26 -17
  25. {mosamatic2-2.0.3.dist-info → mosamatic2-2.0.5.dist-info}/WHEEL +0 -0
  26. {mosamatic2-2.0.3.dist-info → mosamatic2-2.0.5.dist-info}/entry_points.txt +0 -0
mosamatic2/cli.py CHANGED
@@ -4,6 +4,8 @@ from mosamatic2.commands import (
4
4
  rescaledicomimages,
5
5
  segmentmusclefatl3tensorflow,
6
6
  createpngsfromsegmentations,
7
+ dicom2nifti,
8
+ selectslicefromscans,
7
9
  )
8
10
  from mosamatic2.core.utils import show_doc_command
9
11
 
@@ -29,4 +31,6 @@ main.add_command(calculatescores.calculatescores)
29
31
  main.add_command(rescaledicomimages.rescaledicomimages)
30
32
  main.add_command(segmentmusclefatl3tensorflow.segmentmusclefatl3tensorflow)
31
33
  main.add_command(createpngsfromsegmentations.createpngsfromsegmentations)
34
+ main.add_command(dicom2nifti.dicom2nifti)
35
+ main.add_command(selectslicefromscans.selectslicefromscans)
32
36
  main.add_command(show_doc_command(main)) # Special command to show long description for command
@@ -48,7 +48,7 @@ def calculatescores(images, segmentations, output, file_type, overwrite):
48
48
  Parameters
49
49
  ----------
50
50
  --images : str
51
- Directory with with input L3 images
51
+ Directory with input L3 images
52
52
 
53
53
  --segmentations : str
54
54
  Directory with L3 muscle and fat segmenation files. Must be output of
@@ -0,0 +1,46 @@
1
+ import click
2
+
3
+ from mosamatic2.core.tasks import Dicom2NiftiTask
4
+
5
+
6
+ @click.command(help='Converts DICOM series to NIFTI')
7
+ @click.option(
8
+ '--images',
9
+ required=True,
10
+ type=click.Path(exists=True),
11
+ help='Directory with images',
12
+ )
13
+ @click.option(
14
+ '--output',
15
+ required=True,
16
+ type=click.Path(),
17
+ help='Output directory'
18
+ )
19
+ @click.option(
20
+ '--overwrite',
21
+ type=click.BOOL,
22
+ default=False,
23
+ help='Overwrite [true|false]'
24
+ )
25
+ def dicom2nifti(images, output, overwrite):
26
+ """
27
+ Converts single DICOM series (scan) to NIFTI
28
+
29
+ Parameters
30
+ ----------
31
+ --images : str
32
+ Directory with DICOM images of a single series
33
+
34
+ --output : str
35
+ Path to output directory
36
+
37
+ --overwrite : bool
38
+ Overwrite contents output directory [true|false]
39
+ """
40
+ task = Dicom2NiftiTask(
41
+ inputs={'images': images},
42
+ params=None,
43
+ output=output,
44
+ overwrite=overwrite,
45
+ )
46
+ task.run()
@@ -34,7 +34,7 @@ def rescaledicomimages(images, output, target_size, overwrite):
34
34
  Parameters
35
35
  ----------
36
36
  --images : str
37
- Directory with with input DICOM images
37
+ Directory with input DICOM images
38
38
 
39
39
  --output : str
40
40
  Path to output directory
@@ -35,7 +35,7 @@ def segmentmusclefatl3tensorflow(images, model_files, output, overwrite):
35
35
  Parameters
36
36
  ----------
37
37
  --images : str
38
- Directory with with input L3 images
38
+ Directory with input L3 images
39
39
 
40
40
  --model_files : str
41
41
  Directory with AI model files (model-1.0.zip, contour_model-1.0.zip, params-1.0.json)
@@ -0,0 +1,65 @@
1
+ import click
2
+
3
+ from mosamatic2.core.tasks import SelectSliceFromScansTask
4
+
5
+
6
+ @click.command(help='Selects specific slice from 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
+ '--vertebra',
21
+ required=True,
22
+ help='Vertebral level for selecting slice (default: "L3")'
23
+ )
24
+ @click.option(
25
+ '--overwrite',
26
+ type=click.BOOL,
27
+ default=False,
28
+ help='Overwrite [true|false]'
29
+ )
30
+ def selectslicefromscans(scans, vertebra, output, overwrite):
31
+ """
32
+ Selects specific slice from list of CT scans
33
+
34
+ Parameters
35
+ ----------
36
+ --scans : str
37
+ Directory to scans. Each patient's scan should be in a separate
38
+ subdirectory. For example:
39
+
40
+ /scans
41
+ /scans/patient1
42
+ /scans/patient1/file1.dcm
43
+ /scans/patient1/file2.dcm
44
+ ...
45
+ /scans/patient2
46
+ ...
47
+
48
+ --output : str
49
+ Path to output directory where selected slices will be placed. Each
50
+ slice's file name will be the same as the scan directory name, so in
51
+ the example above that would be "patient1", "patient2", etc.
52
+
53
+ --vertebra : str
54
+ Vertebral level where to take slice [L3|T4]
55
+
56
+ --overwrite : bool
57
+ Overwrite contents output directory [true|false]
58
+ """
59
+ task = SelectSliceFromScansTask(
60
+ inputs={'scans': scans},
61
+ params={'vertebra': vertebra},
62
+ output=output,
63
+ overwrite=overwrite,
64
+ )
65
+ task.run()
@@ -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.numpyimage import NumPyImage
5
+
6
+ LOG = LogManager()
7
+
8
+
9
+ class MultiNumPyImage(FileData):
10
+ def __init__(self):
11
+ super(MultiNumPyImage, 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 = NumPyImage()
22
+ image.set_path(f_path)
23
+ if image.load():
24
+ self._images.append(image)
25
+ return True
26
+ return False
@@ -0,0 +1,13 @@
1
+ from mosamatic2.core.data.filedata import FileData
2
+ from mosamatic2.core.utils import (
3
+ is_numpy,
4
+ load_numpy_array,
5
+ )
6
+
7
+ class NumPyImage(FileData):
8
+ def load(self):
9
+ if self.path():
10
+ if is_numpy(self.path()):
11
+ self.set_object(load_numpy_array(self.path()))
12
+ return True
13
+ return False
@@ -1,4 +1,6 @@
1
1
  from mosamatic2.core.tasks.rescaledicomimagestask.rescaledicomimagestask import RescaleDicomImagesTask
2
2
  from mosamatic2.core.tasks.segmentmusclefatl3tensorflowtask.segmentmusclefatl3tensorflowtask import SegmentMuscleFatL3TensorFlowTask
3
3
  from mosamatic2.core.tasks.calculatescorestask.calculatescorestask import CalculateScoresTask
4
- from mosamatic2.core.tasks.createpngsfromsegmentationstask.createpngsfromsegmentationstask import CreatePngsFromSegmentationsTask
4
+ from mosamatic2.core.tasks.createpngsfromsegmentationstask.createpngsfromsegmentationstask import CreatePngsFromSegmentationsTask
5
+ from mosamatic2.core.tasks.dicom2niftitask.dicom2niftitask import Dicom2NiftiTask
6
+ from mosamatic2.core.tasks.selectslicefromscanstask.selectslicefromscanstask import SelectSliceFromScansTask
@@ -4,10 +4,8 @@ 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
8
  from mosamatic2.core.utils import (
8
- is_dicom,
9
- load_dicom,
10
- is_jpeg2000_compressed,
11
9
  get_pixels_from_dicom_object,
12
10
  calculate_area,
13
11
  calculate_mean_radiation_attenuation,
@@ -41,13 +39,11 @@ class CalculateScoresTask(Task):
41
39
  if file_type == '.seg.npy':
42
40
  f_seg_name = f_seg_name.removesuffix(file_type)
43
41
  if f_seg_name == f_img_name:
44
- # img_seg_pairs.append((f_img_path, f_seg_path))
45
42
  img_seg_pairs.append((image, f_seg_path))
46
43
  elif file_type == '.tag':
47
44
  f_seg_name = f_seg_name.removesuffix(file_type).removesuffix('.dcm')
48
45
  f_img_name = f_img_name.removesuffix('.dcm')
49
46
  if f_seg_name == f_img_name:
50
- # img_seg_pairs.append((f_img_path, f_seg_path))
51
47
  img_seg_pairs.append((image, f_seg_path))
52
48
  else:
53
49
  raise RuntimeError('Unknown file type')
@@ -78,7 +74,11 @@ class CalculateScoresTask(Task):
78
74
 
79
75
  def load_segmentation(self, f, file_type='npy'):
80
76
  if file_type == 'npy':
81
- return np.load(f)
77
+ segmentation = NumPyImage()
78
+ segmentation.set_path(f)
79
+ if segmentation.load():
80
+ return segmentation.object()
81
+ LOG.error(f'Could not load segmentation file {f}')
82
82
  if file_type == 'tag':
83
83
  pixels = get_pixels_from_tag_file(f)
84
84
  try:
File without changes
@@ -0,0 +1,24 @@
1
+ import os
2
+ import dicom2nifti
3
+ from mosamatic2.core.tasks.task import Task
4
+ from mosamatic2.core.managers.logmanager import LogManager
5
+
6
+ LOG = LogManager()
7
+
8
+
9
+ class Dicom2NiftiTask(Task):
10
+ INPUTS = ['images']
11
+ PARAMS = []
12
+
13
+ def __init__(self, inputs, params, output, overwrite):
14
+ super(Dicom2NiftiTask, self).__init__(inputs, params, output, overwrite)
15
+
16
+ def run(self):
17
+ nifti_file_name = os.path.split(self.input('images'))[1] + '.nii.gz'
18
+ LOG.info(f'Converting DICOM directory to {nifti_file_name}')
19
+ dicom2nifti.dicom_series_to_nifti(
20
+ self.input('images'),
21
+ os.path.join(self.output(), nifti_file_name),
22
+ reorient_nifti=True,
23
+ )
24
+ self.set_progress(0, 1)
@@ -28,7 +28,7 @@ class SegmentMuscleFatL3TensorFlowTask(Task):
28
28
  def __init__(self, inputs, params, output, overwrite=True):
29
29
  super(SegmentMuscleFatL3TensorFlowTask, self).__init__(inputs, params, output, overwrite)
30
30
 
31
- def load_images(self):
31
+ def load_images(self):
32
32
  image_data = MultiDicomImage()
33
33
  image_data.set_path(self.input('images'))
34
34
  if image_data.load():
@@ -55,7 +55,6 @@ class SegmentMuscleFatL3TensorFlowTask(Task):
55
55
  import tensorflow as tf
56
56
  tfLoaded = True
57
57
  with tempfile.TemporaryDirectory() as model_dir_unzipped:
58
- # model_dir_unzipped = os.path.join(os.path.split(f_path)[0], 'model_unzipped')
59
58
  os.makedirs(model_dir_unzipped, exist_ok=True)
60
59
  with zipfile.ZipFile(f_path) as zipObj:
61
60
  zipObj.extractall(path=model_dir_unzipped)
@@ -65,7 +64,6 @@ class SegmentMuscleFatL3TensorFlowTask(Task):
65
64
  import tensorflow as tf
66
65
  tfLoaded = True
67
66
  with tempfile.TemporaryDirectory() as contour_model_dir_unzipped:
68
- # contour_model_dir_unzipped = os.path.join(os.path.split(f_path)[0], 'contour_model_unzipped')
69
67
  os.makedirs(contour_model_dir_unzipped, exist_ok=True)
70
68
  with zipfile.ZipFile(f_path) as zipObj:
71
69
  zipObj.extractall(path=contour_model_dir_unzipped)
@@ -0,0 +1,109 @@
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
+
29
+ def load_scan_dirs(self):
30
+ scan_dirs = []
31
+ for d in os.listdir(self.input('scans')):
32
+ scan_dir = os.path.join(self.input('scans'), d)
33
+ if os.path.isdir(scan_dir):
34
+ scan_dirs.append(scan_dir)
35
+ return scan_dirs
36
+
37
+ def extract_masks(self, scan_dir):
38
+ os.makedirs(TOTAL_SEGMENTATOR_OUTPUT_DIR, exist_ok=True)
39
+ totalsegmentator(input=scan_dir, output=TOTAL_SEGMENTATOR_OUTPUT_DIR, fast=True)
40
+ # os.system(f'TotalSegmentator -i {scan_dir} -o {TOTAL_SEGMENTATOR_OUTPUT_DIR} --fast')
41
+
42
+ def delete_total_segmentator_output(self):
43
+ if os.path.exists(TOTAL_SEGMENTATOR_OUTPUT_DIR):
44
+ shutil.rmtree(TOTAL_SEGMENTATOR_OUTPUT_DIR)
45
+
46
+ def get_z_delta_offset_for_mask(self, mask_name):
47
+ if mask_name not in Z_DELTA_OFFSETS.keys():
48
+ return None
49
+ return Z_DELTA_OFFSETS[mask_name]
50
+
51
+ def find_slice(self, scan_dir, vertebra):
52
+ if vertebra == 'L3':
53
+ vertebral_level = 'vertebrae_L3'
54
+ elif vertebra == 'T4':
55
+ vertebral_level = 'vertebrae_T4'
56
+ else:
57
+ raise RuntimeError(f'Unknown vertbra {vertebra}. Options are "L3" and "T4"')
58
+ # Find Z-positions DICOM images
59
+ z_positions = {}
60
+ for f in os.listdir(scan_dir):
61
+ f_path = os.path.join(scan_dir, f)
62
+ p = load_dicom(f_path, stop_before_pixels=True)
63
+ if p is not None:
64
+ z_positions[p.ImagePositionPatient[2]] = f_path
65
+ # Find Z-position L3 image
66
+ mask_file = os.path.join(TOTAL_SEGMENTATOR_OUTPUT_DIR, f'{vertebral_level}.nii.gz')
67
+ mask_obj = nib.load(mask_file)
68
+ mask = mask_obj.get_fdata()
69
+ affine_transform = mask_obj.affine
70
+ indexes = np.array(np.where(mask == 1))
71
+ index_min = indexes.min(axis=1)
72
+ index_max = indexes.max(axis=1)
73
+ world_min = nib.affines.apply_affine(affine_transform, index_min)
74
+ world_max = nib.affines.apply_affine(affine_transform, index_max)
75
+ z_direction = affine_transform[:3, 2][2]
76
+ z_sign = math.copysign(1, z_direction)
77
+ z_delta_offset = self.get_z_delta_offset_for_mask(vertebral_level)
78
+ if z_delta_offset is None:
79
+ return None
80
+ z_delta = 0.333 * abs(world_max[2] - world_min[2]) # This needs to be vertebra-specific perhaps
81
+ z_l3 = world_max[2] - z_sign * z_delta
82
+ # Find closest L3 image in DICOM set
83
+ positions = sorted(z_positions.keys())
84
+ closest_file = None
85
+ for z1, z2 in zip(positions[:-1], positions[1:]):
86
+ if min(z1, z2) <= z_l3 <= max(z1, z2):
87
+ closest_z = min(z_positions.keys(), key=lambda z: abs(z - z_l3))
88
+ closest_file = z_positions[closest_z]
89
+ LOG.info(f'Closest image: {closest_file}')
90
+ break
91
+ return closest_file
92
+
93
+ def run(self):
94
+ scan_dirs = self.load_scan_dirs()
95
+ vertebra = self.param('vertebra')
96
+ nr_steps = len(scan_dirs)
97
+ for step in range(nr_steps):
98
+ scan_dir = scan_dirs[step]
99
+ scan_name = os.path.split(scan_dir)[1]
100
+ self.extract_masks(scan_dir)
101
+ file_path = self.find_slice(scan_dir, vertebra)
102
+ if file_path is not None:
103
+ extension = '' if file_path.endswith('.dcm') else '.dcm'
104
+ target_file_path = os.path.join(self.output(), vertebra + '_' + scan_name + extension)
105
+ shutil.copyfile(file_path, target_file_path)
106
+ else:
107
+ LOG.error(f'Could not find slice for vertebral level: {vertebra}')
108
+ self.delete_total_segmentator_output()
109
+ self.set_progress(step, nr_steps)
@@ -25,10 +25,11 @@ class Task:
25
25
  for k, v in self._inputs.items():
26
26
  assert k in self.__class__.INPUTS
27
27
  assert isinstance(v, str)
28
- # Check that param names match specification
29
- assert len(self._params.keys()) == len(self.__class__.PARAMS)
30
- for k in self._params.keys():
31
- assert k in self.__class__.PARAMS
28
+ # Check that param names match specification (if not None)
29
+ if self._params:
30
+ assert len(self._params.keys()) == len(self.__class__.PARAMS)
31
+ for k in self._params.keys():
32
+ assert k in self.__class__.PARAMS
32
33
 
33
34
  def input(self, name):
34
35
  return self._inputs[name]
mosamatic2/core/utils.py CHANGED
@@ -119,10 +119,18 @@ def is_numpy_array(value):
119
119
  return isinstance(value, np.array)
120
120
 
121
121
 
122
- def load_numpy_array(f):
122
+ def is_numpy(f):
123
123
  try:
124
+ np.load(f)
125
+ return True
126
+ except:
127
+ return False
128
+
129
+
130
+ def load_numpy_array(f):
131
+ if is_numpy(f):
124
132
  return np.load(f)
125
- except Exception as e:
133
+ else:
126
134
  return None
127
135
 
128
136
 
mosamatic2/server.py CHANGED
@@ -5,6 +5,8 @@ from mosamatic2.core.tasks import RescaleDicomImagesTask
5
5
  from mosamatic2.core.tasks import SegmentMuscleFatL3TensorFlowTask
6
6
  from mosamatic2.core.tasks import CalculateScoresTask
7
7
  from mosamatic2.core.tasks import CreatePngsFromSegmentationsTask
8
+ from mosamatic2.core.tasks import Dicom2NiftiTask
9
+ from mosamatic2.core.tasks import SelectSliceFromScansTask
8
10
 
9
11
  app = Flask(__name__)
10
12
 
@@ -89,6 +91,36 @@ def run_createpngsfromsegmentations():
89
91
  return 'PASSED'
90
92
 
91
93
 
94
+ @app.route('/dicom2nifti')
95
+ def run_dicom2nifti():
96
+ images = request.args.get('images')
97
+ output = request.args.get('output')
98
+ overwrite = request.args.get('overwrite', default=True, type=bool)
99
+ task = Dicom2NiftiTask(
100
+ inputs={'images': images},
101
+ params=None,
102
+ output=output,
103
+ overwrite=overwrite,
104
+ )
105
+ task.run()
106
+ return 'PASSED'
107
+
108
+
109
+ @app.route('/selectslicefromscans')
110
+ def run_selectslicefromscans():
111
+ scans = request.args.get('scans')
112
+ vertebra = request.args.get('vertebra')
113
+ output = request.args.get('output')
114
+ overwrite = request.args.get('overwrite', default=True, type=bool)
115
+ task = SelectSliceFromScansTask(
116
+ inputs={'scans': scans},
117
+ params={'vertebra': vertebra},
118
+ output=output,
119
+ overwrite=overwrite,
120
+ )
121
+ task.run()
122
+ return 'PASSED'
123
+
92
124
 
93
125
  def main():
94
126
  parser = argparse.ArgumentParser()
@@ -18,6 +18,8 @@ from mosamatic2.ui.widgets.panels.tasks.rescaledicomimagestaskpanel import Resca
18
18
  from mosamatic2.ui.widgets.panels.tasks.segmentmusclefatl3tensorflowtaskpanel import SegmentMuscleFatL3TensorFlowTaskPanel
19
19
  from mosamatic2.ui.widgets.panels.tasks.createpngsfromsegmentationstaskpanel import CreatePngsFromSegmentationsTaskPanel
20
20
  from mosamatic2.ui.widgets.panels.tasks.calculatescorestaskpanel import CalculateScoresTaskPanel
21
+ from mosamatic2.ui.widgets.panels.tasks.dicom2niftitaskpanel import Dicom2NiftiTaskPanel
22
+ from mosamatic2.ui.widgets.panels.tasks.selectslicefromscanstaskpanel import SelectSliceFromScansTaskPanel
21
23
  from mosamatic2.ui.widgets.panels.pipelines.defaultpipelinepanel import DefaultPipelinePanel
22
24
 
23
25
  LOG = LogManager()
@@ -34,6 +36,8 @@ class MainWindow(QMainWindow):
34
36
  self._segment_muscle_fat_l3_tensorflow_task_panel = None
35
37
  self._create_pngs_from_segmentations_task_panel = None
36
38
  self._calculate_scores_task_panel = None
39
+ self._dicom2nifti_task_panel = None
40
+ self._select_slice_from_scans_task_panel = None
37
41
  self._default_pipeline_panel = None
38
42
  self.init_window()
39
43
 
@@ -71,11 +75,17 @@ class MainWindow(QMainWindow):
71
75
  calculate_scores_task_action.triggered.connect(self.handle_calculate_scores_task_action)
72
76
  create_pngs_from_segmentations_task_action = QAction('CreatePngsFromSegmentationsTask', self)
73
77
  create_pngs_from_segmentations_task_action.triggered.connect(self.handle_create_pngs_from_segmentations_task_action)
78
+ dicom2nifti_task_action = QAction('Dicom2NiftiTask', self)
79
+ dicom2nifti_task_action.triggered.connect(self.handle_dicom2nifti_task_action)
80
+ select_slice_from_scans_task_action = QAction('SelectSliceFromScansTask', self)
81
+ select_slice_from_scans_task_action.triggered.connect(self.handle_select_slice_from_scans_task_action)
74
82
  tasks_menu = self.menuBar().addMenu('Tasks')
75
83
  tasks_menu.addAction(rescale_dicom_images_task_action)
76
84
  tasks_menu.addAction(segment_muscle_fat_l3_tensorflow_task_action)
77
85
  tasks_menu.addAction(calculate_scores_task_action)
78
86
  tasks_menu.addAction(create_pngs_from_segmentations_task_action)
87
+ tasks_menu.addAction(dicom2nifti_task_action)
88
+ tasks_menu.addAction(select_slice_from_scans_task_action)
79
89
 
80
90
  def init_pipelines_menu(self):
81
91
  default_pipeline_action = QAction('DefaultPipeline', self)
@@ -100,6 +110,8 @@ class MainWindow(QMainWindow):
100
110
  self._main_panel.add_panel(self.segment_muscle_fat_l3_tensorflow_task_panel(), 'segmentmusclefatl3tensorflowtaskpanel')
101
111
  self._main_panel.add_panel(self.create_pngs_from_segmentations_task_panel(), 'createpngsfromsegmentationstaskpanel')
102
112
  self._main_panel.add_panel(self.calculate_scores_task_panel(), 'calculatescorestaskpanel')
113
+ self._main_panel.add_panel(self.dicom2nifti_task_panel(), 'dicom2niftitaskpanel')
114
+ self._main_panel.add_panel(self.select_slice_from_scans_task_panel(), 'selectslicefromscanstaskpanel')
103
115
  self._main_panel.add_panel(self.default_pipeline_panel(), 'defaultpipelinepanel')
104
116
  self._main_panel.select_panel('defaultpipelinepanel')
105
117
  return self._main_panel
@@ -132,6 +144,16 @@ class MainWindow(QMainWindow):
132
144
  self._calculate_scores_task_panel = CalculateScoresTaskPanel()
133
145
  return self._calculate_scores_task_panel
134
146
 
147
+ def dicom2nifti_task_panel(self):
148
+ if not self._dicom2nifti_task_panel:
149
+ self._dicom2nifti_task_panel = Dicom2NiftiTaskPanel()
150
+ return self._dicom2nifti_task_panel
151
+
152
+ def select_slice_from_scans_task_panel(self):
153
+ if not self._select_slice_from_scans_task_panel:
154
+ self._select_slice_from_scans_task_panel = SelectSliceFromScansTaskPanel()
155
+ return self._select_slice_from_scans_task_panel
156
+
135
157
  def default_pipeline_panel(self):
136
158
  if not self._default_pipeline_panel:
137
159
  self._default_pipeline_panel = DefaultPipelinePanel()
@@ -156,6 +178,12 @@ class MainWindow(QMainWindow):
156
178
  def handle_calculate_scores_task_action(self):
157
179
  self.main_panel().select_panel('calculatescorestaskpanel')
158
180
 
181
+ def handle_dicom2nifti_task_action(self):
182
+ self.main_panel().select_panel('dicom2niftitaskpanel')
183
+
184
+ def handle_select_slice_from_scans_task_action(self):
185
+ self.main_panel().select_panel('selectslicefromscanstaskpanel')
186
+
159
187
  def handle_default_pipeline_action(self):
160
188
  self.main_panel().select_panel('defaultpipelinepanel')
161
189
 
@@ -169,6 +197,8 @@ class MainWindow(QMainWindow):
169
197
  self.segment_muscle_fat_l3_tensorflow_task_panel().save_inputs_and_parameters()
170
198
  self.create_pngs_from_segmentations_task_panel().save_inputs_and_parameters()
171
199
  self.calculate_scores_task_panel().save_inputs_and_parameters()
200
+ self.dicom2nifti_task_panel().save_inputs_and_parameters()
201
+ self.select_slice_from_scans_task_panel().save_inputs_and_parameters()
172
202
  self.default_pipeline_panel().save_inputs_and_parameters()
173
203
  return super().closeEvent(event)
174
204
 
@@ -1 +1 @@
1
- 2.0.3
1
+ 2.0.5
@@ -0,0 +1,183 @@
1
+ import os
2
+
3
+ from PySide6.QtWidgets import (
4
+ QLineEdit,
5
+ QCheckBox,
6
+ QSpinBox,
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.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 Dicom2NiftiTask
25
+
26
+ LOG = LogManager()
27
+
28
+ PANEL_TITLE = 'Dicom2NiftiTask'
29
+ PANEL_NAME = 'dicom2niftitaskpanel'
30
+
31
+
32
+ class Dicom2NiftiTaskPanel(TaskPanel):
33
+ def __init__(self):
34
+ super(Dicom2NiftiTaskPanel, self).__init__()
35
+ self.set_title(PANEL_TITLE)
36
+ self._images_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_task_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 images_dir_line_edit(self):
50
+ if not self._images_dir_line_edit:
51
+ self._images_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/images_dir'))
52
+ return self._images_dir_line_edit
53
+
54
+ def images_dir_select_button(self):
55
+ if not self._images_dir_select_button:
56
+ self._images_dir_select_button = QPushButton('Select')
57
+ self._images_dir_select_button.clicked.connect(self.handle_images_dir_select_button)
58
+ return self._images_dir_select_button
59
+
60
+ def output_dir_line_edit(self):
61
+ if not self._output_dir_line_edit:
62
+ self._output_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/output_dir'))
63
+ return self._output_dir_line_edit
64
+
65
+ def output_dir_select_button(self):
66
+ if not self._output_dir_select_button:
67
+ self._output_dir_select_button = QPushButton('Select')
68
+ self._output_dir_select_button.clicked.connect(self.handle_output_dir_select_button)
69
+ return self._output_dir_select_button
70
+
71
+ def overwrite_checkbox(self):
72
+ if not self._overwrite_checkbox:
73
+ self._overwrite_checkbox = QCheckBox('')
74
+ self._overwrite_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/overwrite', True))
75
+ return self._overwrite_checkbox
76
+
77
+ def form_layout(self):
78
+ if not self._form_layout:
79
+ self._form_layout = QFormLayout()
80
+ if is_macos():
81
+ self._form_layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
82
+ return self._form_layout
83
+
84
+ def run_task_button(self):
85
+ if not self._run_task_button:
86
+ self._run_task_button = QPushButton('Run task')
87
+ self._run_task_button.clicked.connect(self.handle_run_task_button)
88
+ return self._run_task_button
89
+
90
+ def settings(self):
91
+ if not self._settings:
92
+ self._settings = Settings()
93
+ return self._settings
94
+
95
+ def init_layout(self):
96
+ images_dir_layout = QHBoxLayout()
97
+ images_dir_layout.addWidget(self.images_dir_line_edit())
98
+ images_dir_layout.addWidget(self.images_dir_select_button())
99
+ output_dir_layout = QHBoxLayout()
100
+ output_dir_layout.addWidget(self.output_dir_line_edit())
101
+ output_dir_layout.addWidget(self.output_dir_select_button())
102
+ self.form_layout().addRow('Images directory', images_dir_layout)
103
+ self.form_layout().addRow('Output directory', output_dir_layout)
104
+ self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
105
+ layout = QVBoxLayout()
106
+ layout.addLayout(self.form_layout())
107
+ layout.addWidget(self.run_task_button())
108
+ self.setLayout(layout)
109
+ self.setObjectName(PANEL_NAME)
110
+
111
+ def handle_images_dir_select_button(self):
112
+ last_directory = self.settings().get('last_directory')
113
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
114
+ if directory:
115
+ self.images_dir_line_edit().setText(directory)
116
+ self.settings().set('last_directory', directory)
117
+
118
+ def handle_output_dir_select_button(self):
119
+ last_directory = self.settings().get('last_directory')
120
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
121
+ if directory:
122
+ self.output_dir_line_edit().setText(directory)
123
+ self.settings().set('last_directory', directory)
124
+
125
+ def handle_run_task_button(self):
126
+ errors = self.check_inputs_and_parameters()
127
+ if len(errors) > 0:
128
+ error_message = 'Following errors were encountered:\n'
129
+ for error in errors:
130
+ error_message += f' - {error}\n'
131
+ QMessageBox.information(self, 'Error', error_message)
132
+ else:
133
+ LOG.info('Running task...')
134
+ self.run_task_button().setEnabled(False)
135
+ self.save_inputs_and_parameters()
136
+ self._task = Dicom2NiftiTask(
137
+ inputs={'images': self.images_dir_line_edit().text()},
138
+ params=None,
139
+ output=self.output_dir_line_edit().text(),
140
+ overwrite=self.overwrite_checkbox().isChecked(),
141
+ )
142
+ self._worker = Worker(self._task)
143
+ self._thread = QThread()
144
+ self._worker.moveToThread(self._thread)
145
+ self._thread.started.connect(self._worker.run)
146
+ self._worker.progress.connect(self.handle_progress)
147
+ self._worker.status.connect(self.handle_status)
148
+ self._worker.finished.connect(self.handle_finished)
149
+ self._worker.finished.connect(self._thread.quit)
150
+ self._worker.finished.connect(self._worker.deleteLater)
151
+ self._thread.finished.connect(self._thread.deleteLater)
152
+ self._thread.start()
153
+
154
+ @Slot(int)
155
+ def handle_progress(self, progress):
156
+ LOG.info(f'Progress: {progress} / 100%')
157
+
158
+ @Slot(str)
159
+ def handle_status(self, status):
160
+ LOG.info(f'Status: {status}')
161
+
162
+ @Slot()
163
+ def handle_finished(self):
164
+ self.run_task_button().setEnabled(True)
165
+
166
+ # HELPERS
167
+
168
+ def check_inputs_and_parameters(self):
169
+ errors = []
170
+ if self.images_dir_line_edit().text() == '':
171
+ errors.append('Empty images directory path')
172
+ if not os.path.isdir(self.images_dir_line_edit().text()):
173
+ errors.append('Images directory does not exist')
174
+ if self.output_dir_line_edit().text() == '':
175
+ errors.append('Empty output directory path')
176
+ if os.path.isdir(self.output_dir_line_edit().text()) and not self.overwrite_checkbox().isChecked():
177
+ errors.append('Output directory exists but overwrite=False. Please remove output directory first')
178
+ return errors
179
+
180
+ def save_inputs_and_parameters(self):
181
+ self.settings().set(f'{PANEL_NAME}/images_dir', self.images_dir_line_edit().text())
182
+ self.settings().set(f'{PANEL_NAME}/output_dir', self.output_dir_line_edit().text())
183
+ self.settings().set(f'{PANEL_NAME}/overwrite', self.overwrite_checkbox().isChecked())
@@ -3,7 +3,7 @@ import os
3
3
  from PySide6.QtWidgets import (
4
4
  QLineEdit,
5
5
  QCheckBox,
6
- QSpinBox,
6
+ QComboBox,
7
7
  QHBoxLayout,
8
8
  QVBoxLayout,
9
9
  QFormLayout,
@@ -16,26 +16,26 @@ from PySide6.QtCore import (
16
16
  Slot,
17
17
  )
18
18
 
19
- from mosamaticdesktop.core.utils.logmanager import LogManager
20
- from mosamaticdesktop.ui.panels.taskpanel import TaskPanel
21
- from mosamaticdesktop.ui.settings import Settings
22
- from mosamaticdesktop.ui.utils import is_macos
23
- from mosamaticdesktop.ui.worker import Worker
24
-
25
- from mosamatic.tasks import SelectSliceFromScanTask
19
+ from mosamatic2.core.managers.logmanager import LogManager
20
+ from mosamatic2.ui.widgets.panels.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 SelectSliceFromScansTask
26
25
 
27
26
  LOG = LogManager()
28
27
 
29
- PANEL_TITLE = 'Automatically select L3 or T4 slice from full CT scan'
30
- PANEL_NAME = 'selectslicefromscantaskpanel'
28
+ PANEL_TITLE = 'Automatically select L3 slice from full CT scan'
29
+ PANEL_NAME = 'selectslicefromscanstaskpanel'
31
30
 
32
31
 
33
- class SelectSliceFromScanTaskPanel(TaskPanel):
32
+ class SelectSliceFromScansTaskPanel(TaskPanel):
34
33
  def __init__(self):
35
- super(SelectSliceFromScanTaskPanel, self).__init__()
34
+ super(SelectSliceFromScansTaskPanel, self).__init__()
36
35
  self.set_title(PANEL_TITLE)
37
36
  self._scans_dir_line_edit = None
38
37
  self._scans_dir_select_button = None
38
+ self._vertebra_combobox = None
39
39
  self._output_dir_line_edit = None
40
40
  self._output_dir_select_button = None
41
41
  self._overwrite_checkbox = None
@@ -58,6 +58,13 @@ class SelectSliceFromScanTaskPanel(TaskPanel):
58
58
  self._scans_dir_select_button.clicked.connect(self.handle_scans_dir_select_button)
59
59
  return self._scans_dir_select_button
60
60
 
61
+ def vertebra_combobox(self):
62
+ if not self._vertebra_combobox:
63
+ self._vertebra_combobox = QComboBox()
64
+ self._vertebra_combobox.addItems(['L3'])
65
+ self._vertebra_combobox.setCurrentText(self.settings().get(f'{PANEL_NAME}/vertebra'))
66
+ return self._vertebra_combobox
67
+
61
68
  def output_dir_line_edit(self):
62
69
  if not self._output_dir_line_edit:
63
70
  self._output_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/output_dir'))
@@ -101,6 +108,7 @@ class SelectSliceFromScanTaskPanel(TaskPanel):
101
108
  output_dir_layout.addWidget(self.output_dir_line_edit())
102
109
  output_dir_layout.addWidget(self.output_dir_select_button())
103
110
  self.form_layout().addRow('Scans directory', scans_dir_layout)
111
+ # self.form_layout().addRow('Vertebra', self.vertebra_combobox())
104
112
  self.form_layout().addRow('Output directory', output_dir_layout)
105
113
  self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
106
114
  layout = QVBoxLayout()
@@ -134,11 +142,11 @@ class SelectSliceFromScanTaskPanel(TaskPanel):
134
142
  LOG.info('Running task...')
135
143
  self.run_task_button().setEnabled(False)
136
144
  self.save_inputs_and_parameters()
137
- self._task = SelectSliceFromScanTask(
138
- self.scans_dir_line_edit().text(),
139
- self.output_dir_line_edit().text(),
140
- 'vertebrae_L3',
141
- self.overwrite_checkbox().isChecked()
145
+ self._task = SelectSliceFromScansTask(
146
+ inputs={'scans': self.scans_dir_line_edit().text()},
147
+ params={'vertebra': 'L3'},
148
+ output=self.output_dir_line_edit().text(),
149
+ overwrite=self.overwrite_checkbox().isChecked(),
142
150
  )
143
151
  self._worker = Worker(self._task)
144
152
  self._thread = QThread()
@@ -180,5 +188,6 @@ class SelectSliceFromScanTaskPanel(TaskPanel):
180
188
 
181
189
  def save_inputs_and_parameters(self):
182
190
  self.settings().set(f'{PANEL_NAME}/scans_dir', self.scans_dir_line_edit().text())
191
+ self.settings().set(f'{PANEL_NAME}/vertebra', self.vertebra_combobox().currentText())
183
192
  self.settings().set(f'{PANEL_NAME}/output_dir', self.output_dir_line_edit().text())
184
193
  self.settings().set(f'{PANEL_NAME}/overwrite', self.overwrite_checkbox().isChecked())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mosamatic2
3
- Version: 2.0.3
3
+ Version: 2.0.5
4
4
  Summary:
5
5
  Author: Ralph Brecheisen
6
6
  Author-email: r.brecheisen@maastrichtuniversity.nl
@@ -8,6 +8,7 @@ Requires-Python: >=3.11,<3.12
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.11
10
10
  Requires-Dist: antspyx (>=0.5.4)
11
+ Requires-Dist: dicom2nifti (>=2.6.2)
11
12
  Requires-Dist: flask (>=3.1.2)
12
13
  Requires-Dist: nibabel (>=5.3.2)
13
14
  Requires-Dist: numpy (>=1.26.4)
@@ -1,12 +1,14 @@
1
1
  models.py,sha256=Kx6oWKt7IpTTxrhBDrX61X-ZX12J7yPkJFuhVDsDHoQ,8807
2
2
  mosamatic2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  mosamatic2/app.py,sha256=RIUa5tvMYFcmEII4xZPLZZdx9dXWqBvwkxkl_R97Jkw,860
4
- mosamatic2/cli.py,sha256=nrWwbU90RUOcYgmTJ1u5PspO_qy-MPdHX65Nx6kYSkk,1140
4
+ mosamatic2/cli.py,sha256=RTogXBXiYW_EDTmnX7TXx2T22Yth_1AwLFiJP2IUDMc,1289
5
5
  mosamatic2/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- mosamatic2/commands/calculatescores.py,sha256=WtNWB9OKFpiv0z0yqIFJbKuP7ZE5IaeDb70Qlt_WlDY,1989
6
+ mosamatic2/commands/calculatescores.py,sha256=Lb8Q8L2yq7Tt6VBJ6_lltRuldrev_pac6fcgF-xzZyE,1984
7
7
  mosamatic2/commands/createpngsfromsegmentations.py,sha256=uUAQJVTqOkBCfENzi21RBNYvf6_nuesx1MeR3j_-7dM,1682
8
- mosamatic2/commands/rescaledicomimages.py,sha256=9yQxk31apPXSJcICbsWgk-2Z6VJkC1bZIMWTJYkbAmo,1285
9
- mosamatic2/commands/segmentmusclefatl3tensorflow.py,sha256=cTjJZ4uA6IbnuxyGvnjfQmcR4_T5umAzEx4uXsdU8Yg,1429
8
+ mosamatic2/commands/dicom2nifti.py,sha256=4hMvglxdOid7p7P_Jc-Tudsf93JRMlYaQsEQctLq2d4,1015
9
+ mosamatic2/commands/rescaledicomimages.py,sha256=25QdCzB5s0sRwkTb3o5zco2bIwy6LttNf7i97kGBDYQ,1280
10
+ mosamatic2/commands/segmentmusclefatl3tensorflow.py,sha256=CdScmA_EQicaN4GY5bBUOYwfhDPqy9om2sxY3WrtmM0,1424
11
+ mosamatic2/commands/selectslicefromscans.py,sha256=3398PM2uBcxF6wpb0-c-Itp_qxoAxBf0SE2nDDI3Ct4,1715
10
12
  mosamatic2/constants.py,sha256=MVYMwO-x2jQSN37o3zNkRseVQ1nYRA3mLv3v_Q0Mlds,1284
11
13
  mosamatic2/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
14
  mosamatic2/core/data/__init__.py,sha256=j9iGqUTJlGF0N0gPrzzpe_Dhv0Bj9c6FdQ1g7U-_j2g,298
@@ -15,6 +17,8 @@ mosamatic2/core/data/dicomimageseries.py,sha256=OZkNi15crL8nEA-PGYsM0k9NMi2mMHRv
15
17
  mosamatic2/core/data/dixonseries.py,sha256=kq9fy65MSM2XwiScqp7b3rQ09JmpyGwbG6ldZsuPRrM,516
16
18
  mosamatic2/core/data/filedata.py,sha256=hCnpizGqOpxzIADJkDS2_NSmKVLL1u49TYjSJE5UXQo,515
17
19
  mosamatic2/core/data/multidicomimage.py,sha256=cdd0H4Dq49h7NLKBx51_h_HZVnH7-reu48PY8m6tXwU,1034
20
+ mosamatic2/core/data/multinumpyimage.py,sha256=MU5_ec00vx9_JhkibgkcgyGFfaj69STy85ifa8MNsSc,753
21
+ mosamatic2/core/data/numpyimage.py,sha256=ZbJ9JRAVOgvXzTZ6kscEuGZJqKT-MPdt42IAlmh3iXo,359
18
22
  mosamatic2/core/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
23
  mosamatic2/core/managers/logmanager.py,sha256=NEaXvhl0aILjBbK710GaWanVuuNvB51HpHhE5rgYvng,1391
20
24
  mosamatic2/core/managers/logmanagerlistener.py,sha256=Gaig07yjBnyQq9I8sN85olTEeDCDyCFQnEJdwzvmgvc,99
@@ -22,26 +26,30 @@ mosamatic2/core/pipelines/__init__.py,sha256=MXoylu34AlcyudJpjuLa4B7K9gZmxLnZGDa
22
26
  mosamatic2/core/pipelines/defaultpipeline.py,sha256=EUblRhLZXhslz9bdBmx8v9yjpgOdsHviqQZ5elK8gNQ,2901
23
27
  mosamatic2/core/pipelines/pipeline.py,sha256=mRxKXLKwgKDpc8R9mCI6gDKGJ2lKVxRQ__Sf0Mfn_Qc,384
24
28
  mosamatic2/core/singleton.py,sha256=FV0k_LlOCmFhlWN6gf1c2x7YXWyd8-7DsIMvOKrI6NY,224
25
- mosamatic2/core/tasks/__init__.py,sha256=ZLdp-N1AdVYDsrXvtBLnoQO5BqFh4xDXRDsM9y73_fc,462
29
+ mosamatic2/core/tasks/__init__.py,sha256=az_IatVtpT7M2AiOl7ez6EW_oR7_n9GtC1nL0_mYwZk,655
26
30
  mosamatic2/core/tasks/calculatescorestask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
- mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py,sha256=1CUJK9IsePBbEuF3-0LnZp1sWMxPDiJaNy0a-8DaTUE,6576
31
+ mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py,sha256=jEbogrEnEtMbmfQ8KOGSUdGMhdeX69Bv01-82faRIlY,6617
28
32
  mosamatic2/core/tasks/createpngsfromsegmentationstask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
33
  mosamatic2/core/tasks/createpngsfromsegmentationstask/createpngsfromsegmentationstask.py,sha256=1UpOsp1CH668BQ0g4tALou_tFgisC306VcvqOKSDuTo,1884
34
+ mosamatic2/core/tasks/dicom2niftitask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
+ mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py,sha256=9VqDDCShUG09Qhppia-5X-rkVbUzI4VF8rBVia19Tvs,762
30
36
  mosamatic2/core/tasks/rescaledicomimagestask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
37
  mosamatic2/core/tasks/rescaledicomimagestask/rescaledicomimagestask.py,sha256=vGSpMJoXFtE-IHGxTEO9DMkerMJcfG5r9tXgtvkxm6Y,3053
32
38
  mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
39
  mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/paramloader.py,sha256=VxTCOYK_1VRAG83P-ulm0LPvqXI-0iT5BCr0Rdr7MWg,900
34
- mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorflowtask.py,sha256=bDRG9phYSF4o4mFsdJhyJ613CqOixFPTd5yL2gBFSPY,5351
35
- mosamatic2/core/tasks/task.py,sha256=yu6XbxFYQCFH3yxL9MVfuNv1rgiyJWtZH8p43gfPfFA,1639
36
- mosamatic2/core/utils.py,sha256=RHBcvrUshese6NG_OigO4ZGGR3iCOmmeDJjgPnLpaBo,10753
37
- mosamatic2/server.py,sha256=j-uvJB9YdAKBDSd1eKxFN-t6nQdVjcJFlAGQOWNZTSQ,3173
40
+ mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorflowtask.py,sha256=E3GHzGtl-v_qIdLjSy3cbDnf8vfWK6Bax3RAZcKCqXg,5149
41
+ mosamatic2/core/tasks/selectslicefromscanstask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
+ mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py,sha256=1a_QHcIdS4VB39cNEBniaaDzUx3k8DcR0wCtdr8G1d0,4586
43
+ mosamatic2/core/tasks/task.py,sha256=APPnid6dpSGkPuDqU1vm2RIMR5vkpvbP1CPHUMjympg,1691
44
+ mosamatic2/core/utils.py,sha256=zh44FNOWxNKuZ4FcM7VIfMvdlzOr4AA8_PJ1r-6_83k,10855
45
+ mosamatic2/server.py,sha256=jiUc7-aFaDMSY2hg6BhMC9C2FU4V2OrMBSoiIAP8Zcs,4158
38
46
  mosamatic2/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- mosamatic2/ui/mainwindow.py,sha256=SZRv5GRyliufGHrbxDQ4hZMJ-cooZk1T9pwlBGNSFQI,9275
47
+ mosamatic2/ui/mainwindow.py,sha256=k5yxHTjwvpwXXWx94sJ45q1BcH-06B9I7Qnqfd1Zbr8,11119
40
48
  mosamatic2/ui/resources/icons/mosamatic2.icns,sha256=OfhC-diJTIgaNMOezxKKilGsY7mRkaGdU5dGr0MOjIA,2994125
41
49
  mosamatic2/ui/resources/icons/mosamatic2.ico,sha256=ySD3RYluHK3pgS0Eas7eKrVk_AskdLQ4qs_IT-wNhq4,12229
42
50
  mosamatic2/ui/resources/icons/spinner.gif,sha256=rvaac6GUZauHSPFSOLWr0RmLfjmtZih2Q8knQ2WP3Po,16240
43
51
  mosamatic2/ui/resources/images/body-composition.jpg,sha256=KD-BudbXwThB4lJOZZN-ad5-TZRaaZ5cKTH0Ar1TOZs,21227
44
- mosamatic2/ui/resources/VERSION,sha256=Hapa2Ade-cQ7nT9Ty0T8o97c5XqhA-txBDtd1-VscFo,8
52
+ mosamatic2/ui/resources/VERSION,sha256=I2Qe9unc-Hx4iKTROuxBK26Iy4oCUcMjfIY6KzAtKAY,8
45
53
  mosamatic2/ui/settings.py,sha256=YEVHYJIfNsqMO3v1pjzgh7Pih9GGoUX7S9s8S-sBNUk,2121
46
54
  mosamatic2/ui/utils.py,sha256=6bbPIrh4RJ_yhQKNZrgPbL4XeUEogjIjbk_e5c3QS5g,853
47
55
  mosamatic2/ui/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -59,12 +67,13 @@ mosamatic2/ui/widgets/panels/taskpanel.py,sha256=t8lIx1P8sS1Fa-aNm6eEha6297pJQNb
59
67
  mosamatic2/ui/widgets/panels/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
68
  mosamatic2/ui/widgets/panels/tasks/calculatescorestaskpanel.py,sha256=kWtRI0SnjSzvE4MGTxs2-W8f3X3uP3LRtXnfyAl8HL0,9381
61
69
  mosamatic2/ui/widgets/panels/tasks/createpngsfromsegmentationstaskpanel.py,sha256=Be7SJfYHDAvPCC52iVqbrgr3CbwnAck37Ph_BbyhKBE,7873
70
+ mosamatic2/ui/widgets/panels/tasks/dicom2niftitaskpanel.py,sha256=mb5LxDuhkwA08LcHGigul7xmef_wTGrmr-DZ72O_joc,7474
62
71
  mosamatic2/ui/widgets/panels/tasks/rescaledicomimagestaskpanel.py,sha256=_K-XM4zvzq_AMugLqN4p87iEsfX594qYSVnfjNlmAfU,7591
63
72
  mosamatic2/ui/widgets/panels/tasks/segmentmusclefatl3tensorflowtaskpanel.py,sha256=pLEpUXx_vi6PfOykAwp95m5yLRrIvdF_XaupHBOW31U,9624
64
- mosamatic2/ui/widgets/panels/tasks/selectslicefromscantaskpanel.py,sha256=Q5txmTPssKEQpy_yc-iKCN22K5h3B_7WzPP885uP79Q,7506
73
+ mosamatic2/ui/widgets/panels/tasks/selectslicefromscanstaskpanel.py,sha256=ysIoX_IHTPBfEhJQkdM54S3ZL24IAeA8A_ldzgr8BwU,8071
65
74
  mosamatic2/ui/widgets/splashscreen.py,sha256=MS-OczOWfwwEQNQd-JWe9_Mh57css0cSQgbu973rwQo,4056
66
75
  mosamatic2/ui/worker.py,sha256=v7e3gq7MUudgpB1BJW-P7j5wurzu6-HG5m7I6WHgJp0,699
67
- mosamatic2-2.0.3.dist-info/entry_points.txt,sha256=MCUpKkgbej1clgp8EqlLQGs0BIKwGPcBPiVWLfGz9Gw,126
68
- mosamatic2-2.0.3.dist-info/METADATA,sha256=KaFTFjQNx_s-DKle87crkY3K8A5OTUsva4RjwSXmaOQ,1365
69
- mosamatic2-2.0.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
70
- mosamatic2-2.0.3.dist-info/RECORD,,
76
+ mosamatic2-2.0.5.dist-info/entry_points.txt,sha256=MCUpKkgbej1clgp8EqlLQGs0BIKwGPcBPiVWLfGz9Gw,126
77
+ mosamatic2-2.0.5.dist-info/METADATA,sha256=ODtnfcQqYDrCk6jNK9J1vr7eV3XgUi1yT33ctWPzQ_E,1402
78
+ mosamatic2-2.0.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
79
+ mosamatic2-2.0.5.dist-info/RECORD,,