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

mosamatic2/cli.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import click
2
2
  from mosamatic2.commands import (
3
3
  calculatescores,
4
+ calculatemaskstatistics,
4
5
  rescaledicomimages,
5
6
  segmentmusclefatl3tensorflow,
6
7
  createpngsfromsegmentations,
@@ -10,6 +11,8 @@ from mosamatic2.commands import (
10
11
  defaultpipeline,
11
12
  defaultdockerpipeline,
12
13
  boadockerpipeline,
14
+ liveranalysispipeline,
15
+ totalsegmentator,
13
16
  )
14
17
  from mosamatic2.core.utils import show_doc_command
15
18
 
@@ -32,6 +35,7 @@ def main():
32
35
 
33
36
 
34
37
  main.add_command(calculatescores.calculatescores)
38
+ main.add_command(calculatemaskstatistics.calculatemaskstatistics)
35
39
  main.add_command(rescaledicomimages.rescaledicomimages)
36
40
  main.add_command(segmentmusclefatl3tensorflow.segmentmusclefatl3tensorflow)
37
41
  main.add_command(createpngsfromsegmentations.createpngsfromsegmentations)
@@ -41,4 +45,6 @@ main.add_command(createdicomsummary.createdicomsummary)
41
45
  main.add_command(defaultpipeline.defaultpipeline)
42
46
  main.add_command(defaultdockerpipeline.defaultdockerpipeline)
43
47
  main.add_command(boadockerpipeline.boadockerpipeline)
48
+ main.add_command(liveranalysispipeline.liveranalysispipeline)
49
+ main.add_command(totalsegmentator.totalsegmentator)
44
50
  main.add_command(show_doc_command(main)) # Special command to show long description for command
@@ -0,0 +1,59 @@
1
+ import click
2
+
3
+ from mosamatic2.core.tasks import CalculateMaskStatisticsTask
4
+
5
+
6
+ @click.command(help='Calculates segmentation mask statistics')
7
+ @click.option(
8
+ '--scans',
9
+ required=True,
10
+ type=click.Path(exists=True),
11
+ help='Directory with scans in NIFTI format',
12
+ )
13
+ @click.option(
14
+ '--masks',
15
+ required=True,
16
+ type=click.Path(exists=True),
17
+ help='Directory with segmentation mask files in NIFTI format',
18
+ )
19
+ @click.option(
20
+ '--output',
21
+ required=True,
22
+ type=click.Path(),
23
+ help='Output directory'
24
+ )
25
+ @click.option(
26
+ '--overwrite',
27
+ type=click.BOOL,
28
+ default=False,
29
+ help='Overwrite [true|false]'
30
+ )
31
+ def calculatemaskstatistics(scans, masks, output, overwrite):
32
+ """
33
+ Calculates segmentation mask statistics. The following metrics are calculated:
34
+
35
+ (1) Mean radiation attenuation (HU)
36
+ (2) Standard deviation radiation attenuation (HU)
37
+ (3) Segmentation mask volume (mL)
38
+
39
+ Parameters
40
+ ----------
41
+ --scans : str
42
+ Directory with scans in NIFTI format
43
+
44
+ --masks : str
45
+ Directory with segmentation mask files in NIFTI format
46
+
47
+ --output : str
48
+ Path to output directory
49
+
50
+ --overwrite : bool
51
+ Overwrite contents output directory [true|false]
52
+ """
53
+ task = CalculateMaskStatisticsTask(
54
+ inputs={'scans': scans, 'masks': masks},
55
+ params=None,
56
+ output=output,
57
+ overwrite=overwrite,
58
+ )
59
+ task.run()
@@ -16,6 +16,11 @@ from mosamatic2.core.pipelines import DefaultDockerPipeline
16
16
  type=click.Path(),
17
17
  help='Input directory with AI model files (no spaces allowed)'
18
18
  )
19
+ @click.option(
20
+ '--version',
21
+ required=True,
22
+ help='Docker image version'
23
+ )
19
24
  @click.option(
20
25
  '--output',
21
26
  required=True,
@@ -28,7 +33,7 @@ from mosamatic2.core.pipelines import DefaultDockerPipeline
28
33
  default=False,
29
34
  help='Overwrite [true|false]'
30
35
  )
31
- def defaultdockerpipeline(images, model_files, output, overwrite):
36
+ def defaultdockerpipeline(images, model_files, version, output, overwrite):
32
37
  """
33
38
  Runs default L3 analysis pipeline through Docker
34
39
 
@@ -51,6 +56,11 @@ def defaultdockerpipeline(images, model_files, output, overwrite):
51
56
 
52
57
  Warning: This directory path cannot contain any spaces!
53
58
 
59
+ --version : str
60
+ Docker image version, e.g., 2.0.16
61
+ Check https://hub.docker.com/repository/docker/brecheisen/mosamatic2-cli/general
62
+ for the latest version and older versions.
63
+
54
64
  --output : str
55
65
  Path to output directory (no spaces!)
56
66
 
@@ -66,6 +76,7 @@ def defaultdockerpipeline(images, model_files, output, overwrite):
66
76
  'file_type': 'npy',
67
77
  'fig_width': 10,
68
78
  'fig_height': 10,
79
+ 'version': version,
69
80
  },
70
81
  output=output,
71
82
  overwrite=overwrite,
@@ -0,0 +1,61 @@
1
+ import click
2
+
3
+ from mosamatic2.core.pipelines import LiverAnalysisPipeline
4
+
5
+
6
+ @click.command(help='Runs liver analysis pipeline')
7
+ @click.option(
8
+ '--scans',
9
+ required=True,
10
+ type=click.Path(exists=True),
11
+ help='Root directory with scan directories for each patient (no spaces allowed)',
12
+ )
13
+ @click.option(
14
+ '--compressed',
15
+ default=True,
16
+ help='Whether to produce compressed NIFTI file or not (default: True)'
17
+ )
18
+ @click.option(
19
+ '--output',
20
+ required=True,
21
+ type=click.Path(),
22
+ help='Output directory (no spaces allowed)'
23
+ )
24
+ @click.option(
25
+ '--overwrite',
26
+ type=click.BOOL,
27
+ default=False,
28
+ help='Overwrite [true|false]'
29
+ )
30
+ def liveranalysispipeline(scans, compressed, output, overwrite):
31
+ """
32
+ Runs liver analysis pipeline. This pipeline run the following steps on each scan:
33
+
34
+ (1) DICOM to NIFTI conversion
35
+ (2) Extract liver segments using Total Segmentator
36
+ (3) Calculate segment statistics, e.g., volume (mL), mean HU, std HU and PNG image
37
+ of each segments HU histogram.
38
+
39
+ Parameters
40
+ ----------
41
+ --scans : str
42
+ Root directory with scan directories for each patient. Each scan directory should
43
+ contain DICOM images for a single scan only and nothing else. Also, the directory
44
+ paths cannot contain any spaces.
45
+
46
+ --compressed : str
47
+ Whether to produce compressed NIFTI file or not (default: True)
48
+
49
+ --output : str
50
+ Path to output directory. No spaces allowed.
51
+
52
+ --overwrite : bool
53
+ Overwrite contents output directory [true|false]
54
+ """
55
+ pipeline = LiverAnalysisPipeline(
56
+ inputs={'scans': scans},
57
+ params={'compressed': compressed},
58
+ output=output,
59
+ overwrite=overwrite,
60
+ )
61
+ pipeline.run()
@@ -21,13 +21,18 @@ from mosamatic2.core.tasks import TotalSegmentatorTask
21
21
  default='total',
22
22
  help='Comma-separated list of Total Segmentator tasks to run (no spaces!)'
23
23
  )
24
+ @click.option(
25
+ '--format',
26
+ default='dicom',
27
+ help='Process scans in DICOM or NIFTI format [dicom|nifti] (default: dicom)'
28
+ )
24
29
  @click.option(
25
30
  '--overwrite',
26
31
  type=click.BOOL,
27
32
  default=False,
28
33
  help='Overwrite [true|false]'
29
34
  )
30
- def totalsegmentator(scans, tasks, output, overwrite):
35
+ def totalsegmentator(scans, tasks, format, output, overwrite):
31
36
  """
32
37
  Run Total Segmentator on CT scans. If you want to run specialized tasks
33
38
  like "liver_segments" or "liver_vessels" you need an educational license.
@@ -56,12 +61,16 @@ def totalsegmentator(scans, tasks, output, overwrite):
56
61
  --tasks : str
57
62
  Comma-separated list of Total Segmentator tasks to run (no spaces!)
58
63
 
64
+ --format : str
65
+ Process scans in DICOM or NIFTI format. Options: [dicom|nifti]
66
+ Default is 'dicom'.
67
+
59
68
  --overwrite : bool
60
69
  Overwrite contents output directory [true|false]
61
70
  """
62
71
  task = TotalSegmentatorTask(
63
72
  inputs={'scans': scans},
64
- params={'tasks': tasks},
73
+ params={'tasks': tasks, 'format': format},
65
74
  output=output,
66
75
  overwrite=overwrite,
67
76
  )
@@ -1,3 +1,4 @@
1
1
  from mosamatic2.core.pipelines.defaultpipeline.defaultpipeline import DefaultPipeline
2
2
  from mosamatic2.core.pipelines.defaultdockerpipeline.defaultdockerpipeline import DefaultDockerPipeline
3
- from mosamatic2.core.pipelines.boadockerpipeline.boadockerpipeline import BoaDockerPipeline
3
+ from mosamatic2.core.pipelines.boadockerpipeline.boadockerpipeline import BoaDockerPipeline
4
+ from mosamatic2.core.pipelines.liveranalysispipeline.liveranalysispipeline import LiverAnalysisPipeline
@@ -11,15 +11,8 @@ class DefaultDockerPipeline(Pipeline):
11
11
  'images',
12
12
  'model_files',
13
13
  ]
14
- PARAMS = [
15
- 'target_size',
16
- 'file_type',
17
- 'fig_width',
18
- 'fig_height',
19
- 'model_type',
20
- 'model_version',
21
- 'version',
22
- ]
14
+ PARAMS = ['version']
15
+
23
16
  def __init__(self, inputs, params, output, overwrite):
24
17
  super(DefaultDockerPipeline, self).__init__(inputs, params, output, overwrite)
25
18
 
@@ -0,0 +1,48 @@
1
+ import os
2
+ from mosamatic2.core.pipelines.pipeline import Pipeline
3
+ from mosamatic2.core.tasks import (
4
+ Dicom2NiftiTask,
5
+ TotalSegmentatorTask,
6
+ CalculateMaskStatisticsTask,
7
+ )
8
+ from mosamatic2.core.managers.logmanager import LogManager
9
+
10
+ LOG = LogManager()
11
+
12
+
13
+ class LiverAnalysisPipeline(Pipeline):
14
+ INPUTS = ['scans']
15
+ PARAMS = ['compressed']
16
+
17
+ def __init__(self, inputs, params, output, overwrite):
18
+ super(LiverAnalysisPipeline, self).__init__(inputs, params, output, overwrite)
19
+ self.add_task(
20
+ Dicom2NiftiTask(
21
+ inputs={'scans': self.input('scans')},
22
+ params={'compressed': self.param('compressed')},
23
+ output=self.output(),
24
+ overwrite=self.overwrite(),
25
+ )
26
+ )
27
+ self.add_task(
28
+ TotalSegmentatorTask(
29
+ inputs={'scans': os.path.join(self.output(), 'dicom2niftitask')},
30
+ params={
31
+ 'tasks': 'liver_segments',
32
+ 'format': 'nifti',
33
+ },
34
+ output=self.output(),
35
+ overwrite=self.overwrite(),
36
+ )
37
+ )
38
+ self.add_task(
39
+ CalculateMaskStatisticsTask(
40
+ inputs={
41
+ 'scans': os.path.join(self.output(), 'dicom2niftitask'),
42
+ 'masks': os.path.join(self.output(), 'totalsegmentatortask'),
43
+ },
44
+ params=None,
45
+ output=self.output(),
46
+ overwrite=self.overwrite(),
47
+ )
48
+ )
@@ -5,4 +5,5 @@ from mosamatic2.core.tasks.createpngsfromsegmentationstask.createpngsfromsegment
5
5
  from mosamatic2.core.tasks.dicom2niftitask.dicom2niftitask import Dicom2NiftiTask
6
6
  from mosamatic2.core.tasks.selectslicefromscanstask.selectslicefromscanstask import SelectSliceFromScansTask
7
7
  from mosamatic2.core.tasks.createdicomsummarytask.createdicomsummarytask import CreateDicomSummaryTask
8
- from mosamatic2.core.tasks.totalsegmentatortask.totalsegmentatortask import TotalSegmentatorTask
8
+ from mosamatic2.core.tasks.totalsegmentatortask.totalsegmentatortask import TotalSegmentatorTask
9
+ from mosamatic2.core.tasks.calculatemaskstatisticstask.calculatemaskstatisticstask import CalculateMaskStatisticsTask
@@ -0,0 +1,104 @@
1
+ import os
2
+ import numpy as np
3
+ import nibabel as nb
4
+ import pandas as pd
5
+ import matplotlib.pyplot as plt
6
+ from mosamatic2.core.tasks.task import Task
7
+ from mosamatic2.core.managers.logmanager import LogManager
8
+
9
+ LOG = LogManager()
10
+
11
+
12
+ class CalculateMaskStatisticsTask(Task):
13
+ INPUTS = ['scans', 'masks']
14
+ PARAMS = []
15
+
16
+ def __init__(self, inputs, params, output, overwrite):
17
+ super(CalculateMaskStatisticsTask, self).__init__(inputs, params, output, overwrite)
18
+
19
+ def load_scans(self):
20
+ scans = []
21
+ for f in os.listdir(self.input('scans')):
22
+ if f.endswith('.nii.gz'):
23
+ scan = os.path.join(self.input('scans'), f)
24
+ if os.path.isfile(scan):
25
+ scans.append(scan)
26
+ return scans
27
+
28
+ def load_mask_files(self):
29
+ mask_files = []
30
+ for f in os.listdir(self.input('masks')):
31
+ if f.endswith('.nii.gz'):
32
+ mask_file = os.path.join(self.input('masks'), f)
33
+ if os.path.isfile(mask_file):
34
+ mask_files.append(mask_file)
35
+ return mask_files
36
+
37
+ def collect_scan_mask_file_pairs(self, scans, mask_files):
38
+ scan_mask_file_pairs = []
39
+ for scan in scans:
40
+ scan_name = os.path.split(scan)[1][:-7]
41
+ for mask_file in mask_files:
42
+ mask_file_name = os.path.split(mask_file)[1][:-7]
43
+ if scan_name in mask_file_name:
44
+ scan_mask_file_pairs.append((scan, mask_file))
45
+ return scan_mask_file_pairs
46
+
47
+ def get_masked_voxels(self, scan_mask_file_pair):
48
+ scan_image_file = scan_mask_file_pair[0]
49
+ scan_image = nb.load(scan_image_file)
50
+ scan_image_data = scan_image.get_fdata()
51
+ mask_file_image_file = scan_mask_file_pair[1]
52
+ mask_file_image_file_name = os.path.split(mask_file_image_file)[1]
53
+ mask_file_image = nb.load(mask_file_image_file)
54
+ mask_file_image_data = mask_file_image.get_fdata()
55
+ mask = mask_file_image_data > 0
56
+ masked_voxels = scan_image_data[mask]
57
+ return masked_voxels, mask_file_image_file_name
58
+
59
+ def calculate_volume_in_mL(self, mask_file):
60
+ nifti_image = nb.load(mask_file)
61
+ nifti_image_data = nifti_image.get_fdata()
62
+ voxel_dims = nifti_image.header.get_zooms()[:3]
63
+ voxel_volume = np.prod(voxel_dims)
64
+ num_voxels = np.sum(nifti_image_data > 0.5)
65
+ mask_volume = num_voxels * voxel_volume / 1000.0
66
+ return mask_volume
67
+
68
+ def create_histogram_png(self, masked_voxels, file_name):
69
+ histogram_png_file = os.path.join(self.output(), f'{file_name}.png')
70
+ plt.figure(figsize=(8,6))
71
+ plt.hist(masked_voxels, bins=100, color='steelblue', edgecolor='black')
72
+ plt.title(f'Histogram of HU values inside {file_name}')
73
+ plt.xlabel('HU')
74
+ plt.ylabel('Frequency')
75
+ plt.savefig(histogram_png_file, dpi=300, bbox_inches='tight')
76
+ plt.close()
77
+
78
+ def save_data_to_file(self, data):
79
+ csv_file_path = os.path.join(self.output(), 'statistics.csv')
80
+ xls_file_path = os.path.join(self.output(), 'statistics.xlsx')
81
+ df = pd.DataFrame(data=data)
82
+ df.to_csv(csv_file_path, index=False, sep=';')
83
+ df.to_excel(xls_file_path, index=False, engine='openpyxl')
84
+
85
+ def run(self):
86
+ scans = self.load_scans()
87
+ mask_files = self.load_mask_files()
88
+ scan_mask_file_pairs = self.collect_scan_mask_file_pairs(scans, mask_files)
89
+ nr_steps = len(scan_mask_file_pairs)
90
+ data = {
91
+ 'file': [],
92
+ 'mean_HU': [],
93
+ 'std_HU': [],
94
+ 'volume_mL': [],
95
+ }
96
+ for step in range(nr_steps):
97
+ masked_voxels, file_name = self.get_masked_voxels(scan_mask_file_pairs[step])
98
+ data['file'].append(file_name)
99
+ data['mean_HU'].append(round(np.mean(masked_voxels)))
100
+ data['std_HU'].append(round(np.std(masked_voxels)))
101
+ data['volume_mL'].append(round(self.calculate_volume_in_mL(scan_mask_file_pairs[step][1])))
102
+ self.create_histogram_png(masked_voxels, file_name)
103
+ self.set_progress(step, nr_steps)
104
+ self.save_data_to_file(data)
@@ -11,7 +11,7 @@ TOTAL_SEGMENTATOR_OUTPUT_DIR = os.path.join(tempfile.gettempdir(), 'total_segmen
11
11
 
12
12
  class TotalSegmentatorTask(Task):
13
13
  INPUTS = ['scans']
14
- PARAMS = ['tasks']
14
+ PARAMS = ['tasks', 'format']
15
15
 
16
16
  def __init__(self, inputs, params, output, overwrite):
17
17
  super(TotalSegmentatorTask, self).__init__(inputs, params, output, overwrite)
@@ -25,26 +25,51 @@ class TotalSegmentatorTask(Task):
25
25
  scan_dirs.append(scan_dir)
26
26
  return scan_dirs
27
27
 
28
- def extract_masks(self, scan_dir):
28
+ def load_scans(self):
29
+ scans = []
30
+ for f in os.listdir(self.input('scans')):
31
+ if f.endswith('.nii.gz'):
32
+ scan = os.path.join(self.input('scans'), f)
33
+ if os.path.isfile(scan):
34
+ scans.append(scan)
35
+ return scans
36
+
37
+ def extract_masks(self, scan_dir_or_file):
29
38
  os.makedirs(TOTAL_SEGMENTATOR_OUTPUT_DIR, exist_ok=True)
30
39
  tasks = self.param('tasks').split(",") if self.param('tasks') else []
31
40
  for task in tasks:
32
41
  LOG.info(f'Running task {task}...')
33
- totalsegmentator(input=scan_dir, output=TOTAL_SEGMENTATOR_OUTPUT_DIR, task=task)
42
+ totalsegmentator(input=scan_dir_or_file, output=TOTAL_SEGMENTATOR_OUTPUT_DIR, task=task)
43
+
44
+ def delete_total_segmentator_output(self):
45
+ if os.path.exists(TOTAL_SEGMENTATOR_OUTPUT_DIR):
46
+ shutil.rmtree(TOTAL_SEGMENTATOR_OUTPUT_DIR)
34
47
 
35
48
  def run(self):
36
- scan_dirs = self.load_scan_dirs()
37
- nr_steps = len(scan_dirs)
49
+ if self.param('format') == 'dicom':
50
+ scan_dirs_or_files = self.load_scan_dirs()
51
+ elif self.param('format') == 'nifti':
52
+ scan_dirs_or_files = self.load_scans()
53
+ else:
54
+ LOG.error('Unknown format: {}'.format(self.param('format')))
55
+ return
56
+ nr_steps = len(scan_dirs_or_files)
38
57
  for step in range(nr_steps):
39
- scan_dir = scan_dirs[step]
58
+ scan_dir_or_file = scan_dirs_or_files[step]
59
+ scan_dir_or_file_name = os.path.split(scan_dir_or_file)[1]
60
+ if self.param('format') == 'nifti':
61
+ scan_dir_or_file_name = scan_dir_or_file_name[:-7]
40
62
  try:
41
- self.extract_masks(scan_dir)
63
+ self.extract_masks(scan_dir_or_file)
42
64
  except Exception as e:
43
- LOG.error(f'{scan_dir}: Could not extract masks [{str(e)}]. Skipping scan...')
65
+ LOG.error(f'{scan_dir_or_file}: Could not extract masks [{str(e)}]. Skipping scan...')
44
66
  self.set_progress(step, nr_steps)
45
67
  LOG.info(f'Copying temporary output to final output directory...')
46
68
  for f in os.listdir(TOTAL_SEGMENTATOR_OUTPUT_DIR):
47
69
  if f.endswith('.nii') or f.endswith('.nii.gz'):
48
70
  f_path = os.path.join(TOTAL_SEGMENTATOR_OUTPUT_DIR, f)
49
- shutil.move(f_path, self.output())
50
- LOG.info(f'Copied {f}')
71
+ target_file = os.path.join(self.output(), f'{scan_dir_or_file_name}_{f}')
72
+ shutil.copyfile(f_path, target_file)
73
+ LOG.info(f'Copied {f} to {target_file}')
74
+ LOG.info('Cleaning up Total Segmentator temporary output...')
75
+ self.delete_total_segmentator_output()
@@ -22,9 +22,11 @@ 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.tasks.totalsegmentatortaskpanel import TotalSegmentatorTaskPanel
25
+ from mosamatic2.ui.widgets.panels.tasks.calculatemaskstatisticstaskpanel import CalculateMaskStatisticsTaskPanel
25
26
  from mosamatic2.ui.widgets.panels.pipelines.defaultpipelinepanel import DefaultPipelinePanel
26
27
  from mosamatic2.ui.widgets.panels.pipelines.defaultdockerpipelinepanel import DefaultDockerPipelinePanel
27
28
  from mosamatic2.ui.widgets.panels.pipelines.boadockerpipelinepanel import BoaDockerPipelinePanel
29
+ from mosamatic2.ui.widgets.panels.pipelines.liveranalysispipelinepanel import LiverAnalysisPipelinePanel
28
30
  from mosamatic2.ui.widgets.panels.visualizations.slicevisualization.slicevisualization import SliceVisualization
29
31
 
30
32
  LOG = LogManager()
@@ -45,9 +47,11 @@ class MainWindow(QMainWindow):
45
47
  self._create_dicom_summary_task_panel = None
46
48
  self._select_slice_from_scans_task_panel = None
47
49
  self._total_segmentator_task_panel = None
50
+ self._calculate_mask_statistics_task_panel = None
48
51
  self._default_pipeline_panel = None
49
52
  self._default_docker_pipeline_panel = None
50
53
  self._boa_docker_pipeline_panel = None
54
+ self._liver_analysis_pipeline_panel = None
51
55
  self._slice_visualization = None
52
56
  self.init_window()
53
57
 
@@ -94,6 +98,8 @@ class MainWindow(QMainWindow):
94
98
  select_slice_from_scans_task_action.triggered.connect(self.handle_select_slice_from_scans_task_action)
95
99
  total_segmentator_task_action = QAction('TotalSegmentatorTask', self)
96
100
  total_segmentator_task_action.triggered.connect(self.handle_total_segmentator_task_action)
101
+ calculate_mask_statistics_task_action = QAction('CalculateMaskStatisticsTask', self)
102
+ calculate_mask_statistics_task_action.triggered.connect(self.handle_calculate_mask_statistics_task_action)
97
103
  tasks_menu = self.menuBar().addMenu('Tasks')
98
104
  tasks_menu.addAction(rescale_dicom_images_task_action)
99
105
  tasks_menu.addAction(segment_muscle_fat_l3_tensorflow_task_action)
@@ -103,6 +109,7 @@ class MainWindow(QMainWindow):
103
109
  tasks_menu.addAction(create_dicom_summary_task_action)
104
110
  tasks_menu.addAction(select_slice_from_scans_task_action)
105
111
  tasks_menu.addAction(total_segmentator_task_action)
112
+ tasks_menu.addAction(calculate_mask_statistics_task_action)
106
113
 
107
114
  def init_pipelines_menu(self):
108
115
  default_pipeline_action = QAction('DefaultPipeline', self)
@@ -111,10 +118,13 @@ class MainWindow(QMainWindow):
111
118
  default_docker_pipeline_action.triggered.connect(self.handle_default_docker_pipeline_action)
112
119
  boa_docker_pipeline_action = QAction('BoaDockerPipeline', self)
113
120
  boa_docker_pipeline_action.triggered.connect(self.handle_boa_docker_pipeline_action)
121
+ liver_analysis_pipeline_action = QAction('LiverAnalysisPipeline', self)
122
+ liver_analysis_pipeline_action.triggered.connect(self.handle_liver_analysis_pipeline_action)
114
123
  pipelines_menu = self.menuBar().addMenu('Pipelines')
115
124
  pipelines_menu.addAction(default_pipeline_action)
116
125
  pipelines_menu.addAction(default_docker_pipeline_action)
117
126
  pipelines_menu.addAction(boa_docker_pipeline_action)
127
+ pipelines_menu.addAction(liver_analysis_pipeline_action)
118
128
 
119
129
  def init_visualizations_menu(self):
120
130
  slice_visualization_action = QAction('SliceVisualization', self)
@@ -143,9 +153,11 @@ class MainWindow(QMainWindow):
143
153
  self._main_panel.add_panel(self.create_dicom_summary_task_panel(), 'createdicomsummarytaskpanel')
144
154
  self._main_panel.add_panel(self.select_slice_from_scans_task_panel(), 'selectslicefromscanstaskpanel')
145
155
  self._main_panel.add_panel(self.total_segmentator_task_panel(), 'totalsegmentatortaskpanel')
156
+ self._main_panel.add_panel(self.calculate_mask_statistics_task_panel(), 'calculatemaskstatisticstaskpanel')
146
157
  self._main_panel.add_panel(self.default_pipeline_panel(), 'defaultpipelinepanel')
147
158
  self._main_panel.add_panel(self.default_docker_pipeline_panel(), 'defaultdockerpipelinepanel')
148
159
  self._main_panel.add_panel(self.boa_docker_pipeline_panel(), 'boadockerpipelinepanel')
160
+ self._main_panel.add_panel(self.liver_analysis_pipeline_panel(), 'liveranalysispipelinepanel')
149
161
  self._main_panel.add_panel(self.slice_visualization(), 'slicevisualization')
150
162
  self._main_panel.select_panel('defaultpipelinepanel')
151
163
  return self._main_panel
@@ -198,6 +210,11 @@ class MainWindow(QMainWindow):
198
210
  self._total_segmentator_task_panel = TotalSegmentatorTaskPanel()
199
211
  return self._total_segmentator_task_panel
200
212
 
213
+ def calculate_mask_statistics_task_panel(self):
214
+ if not self._calculate_mask_statistics_task_panel:
215
+ self._calculate_mask_statistics_task_panel = CalculateMaskStatisticsTaskPanel()
216
+ return self._calculate_mask_statistics_task_panel
217
+
201
218
  def default_pipeline_panel(self):
202
219
  if not self._default_pipeline_panel:
203
220
  self._default_pipeline_panel = DefaultPipelinePanel()
@@ -213,6 +230,11 @@ class MainWindow(QMainWindow):
213
230
  self._boa_docker_pipeline_panel = BoaDockerPipelinePanel()
214
231
  return self._boa_docker_pipeline_panel
215
232
 
233
+ def liver_analysis_pipeline_panel(self):
234
+ if not self._liver_analysis_pipeline_panel:
235
+ self._liver_analysis_pipeline_panel = LiverAnalysisPipelinePanel()
236
+ return self._liver_analysis_pipeline_panel
237
+
216
238
  def slice_visualization(self):
217
239
  if not self._slice_visualization:
218
240
  self._slice_visualization = SliceVisualization()
@@ -249,6 +271,9 @@ class MainWindow(QMainWindow):
249
271
  def handle_total_segmentator_task_action(self):
250
272
  self.main_panel().select_panel('totalsegmentatortaskpanel')
251
273
 
274
+ def handle_calculate_mask_statistics_task_action(self):
275
+ self.main_panel().select_panel('calculatemaskstatisticstaskpanel')
276
+
252
277
  def handle_default_pipeline_action(self):
253
278
  self.main_panel().select_panel('defaultpipelinepanel')
254
279
 
@@ -258,6 +283,9 @@ class MainWindow(QMainWindow):
258
283
  def handle_boa_docker_pipeline_action(self):
259
284
  self.main_panel().select_panel('boadockerpipelinepanel')
260
285
 
286
+ def handle_liver_analysis_pipeline_action(self):
287
+ self.main_panel().select_panel('liveranalysispipelinepanel')
288
+
261
289
  def handle_slice_visualization_action(self):
262
290
  self.main_panel().select_panel('slicevisualization')
263
291
 
@@ -275,9 +303,11 @@ class MainWindow(QMainWindow):
275
303
  self.create_dicom_summary_task_panel().save_inputs_and_parameters()
276
304
  self.select_slice_from_scans_task_panel().save_inputs_and_parameters()
277
305
  self.total_segmentator_task_panel().save_inputs_and_parameters()
306
+ self.calculate_mask_statistics_task_panel().save_inputs_and_parameters()
278
307
  self.default_pipeline_panel().save_inputs_and_parameters()
279
308
  self.default_docker_pipeline_panel().save_inputs_and_parameters()
280
309
  self.boa_docker_pipeline_panel().save_inputs_and_parameters()
310
+ self.liver_analysis_pipeline_panel().save_inputs_and_parameters()
281
311
  self.slice_visualization().save_inputs_and_parameters()
282
312
  return super().closeEvent(event)
283
313
 
@@ -1 +1 @@
1
- 2.0.16
1
+ 2.0.17
@@ -27,7 +27,7 @@ from mosamatic2.core.pipelines import DefaultPipeline
27
27
  LOG = LogManager()
28
28
 
29
29
  PANEL_TITLE = 'DefaultPipeline'
30
- PANEL_NAME = 'defaultpipeline'
30
+ PANEL_NAME = 'defaultpipelinepanel'
31
31
  MODEL_TYPE_ITEM_NAMES = ['tensorflow', 'pytorch']
32
32
  MODEL_VERSION_ITEM_NAMES = ['1.0', '2.2']
33
33
 
@@ -0,0 +1,187 @@
1
+ import os
2
+
3
+ from PySide6.QtWidgets import (
4
+ QLineEdit,
5
+ QSpinBox,
6
+ QComboBox,
7
+ QCheckBox,
8
+ QHBoxLayout,
9
+ QVBoxLayout,
10
+ QFormLayout,
11
+ QPushButton,
12
+ QFileDialog,
13
+ QMessageBox,
14
+ )
15
+ from PySide6.QtCore import (
16
+ QThread,
17
+ Slot,
18
+ )
19
+
20
+ from mosamatic2.core.managers.logmanager import LogManager
21
+ from mosamatic2.ui.widgets.panels.pipelines.pipelinepanel import PipelinePanel
22
+ from mosamatic2.ui.settings import Settings
23
+ from mosamatic2.ui.utils import is_macos
24
+ from mosamatic2.ui.worker import Worker
25
+ from mosamatic2.core.pipelines import LiverAnalysisPipeline
26
+
27
+ LOG = LogManager()
28
+
29
+ PANEL_TITLE = 'LiverAnalysisPipeline'
30
+ PANEL_NAME = 'liveranalysispipelinepanel'
31
+
32
+
33
+ class LiverAnalysisPipelinePanel(PipelinePanel):
34
+ def __init__(self):
35
+ super(LiverAnalysisPipelinePanel, self).__init__()
36
+ self.set_title(PANEL_TITLE)
37
+ self._scans_dir_line_edit = None
38
+ self._scans_dir_select_button = 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_pipeline_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()
53
+ self._scans_dir_line_edit.setText(self.settings().get(f'{PANEL_NAME}/scans_dir'))
54
+ return self._scans_dir_line_edit
55
+
56
+ def scans_dir_select_button(self):
57
+ if not self._scans_dir_select_button:
58
+ self._scans_dir_select_button = QPushButton('Select')
59
+ self._scans_dir_select_button.clicked.connect(self.handle_scans_dir_select_button)
60
+ return self._scans_dir_select_button
61
+
62
+ def output_dir_line_edit(self):
63
+ if not self._output_dir_line_edit:
64
+ self._output_dir_line_edit = QLineEdit()
65
+ self._output_dir_line_edit.setText(self.settings().get(f'{PANEL_NAME}/output_dir'))
66
+ return self._output_dir_line_edit
67
+
68
+ def output_dir_select_button(self):
69
+ if not self._output_dir_select_button:
70
+ self._output_dir_select_button = QPushButton('Select')
71
+ self._output_dir_select_button.clicked.connect(self.handle_output_dir_select_button)
72
+ return self._output_dir_select_button
73
+
74
+ def overwrite_checkbox(self):
75
+ if not self._overwrite_checkbox:
76
+ self._overwrite_checkbox = QCheckBox('')
77
+ self._overwrite_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/overwrite', True))
78
+ return self._overwrite_checkbox
79
+
80
+ def form_layout(self):
81
+ if not self._form_layout:
82
+ self._form_layout = QFormLayout()
83
+ if is_macos():
84
+ self._form_layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
85
+ return self._form_layout
86
+
87
+ def run_pipeline_button(self):
88
+ if not self._run_pipeline_button:
89
+ self._run_pipeline_button = QPushButton('Run pipeline')
90
+ self._run_pipeline_button.clicked.connect(self.handle_run_pipeline_button)
91
+ return self._run_pipeline_button
92
+
93
+ def settings(self):
94
+ if not self._settings:
95
+ self._settings = Settings()
96
+ return self._settings
97
+
98
+ def init_help_dialog(self):
99
+ self.help_dialog().set_text('Show some help information')
100
+
101
+ def init_layout(self):
102
+ scans_dir_layout = QHBoxLayout()
103
+ scans_dir_layout.addWidget(self.scans_dir_line_edit())
104
+ scans_dir_layout.addWidget(self.scans_dir_select_button())
105
+ output_dir_layout = QHBoxLayout()
106
+ output_dir_layout.addWidget(self.output_dir_line_edit())
107
+ output_dir_layout.addWidget(self.output_dir_select_button())
108
+ self.form_layout().addRow('Scans directory', scans_dir_layout)
109
+ self.form_layout().addRow('Output directory', output_dir_layout)
110
+ self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
111
+ layout = QVBoxLayout()
112
+ layout.addLayout(self.form_layout())
113
+ layout.addWidget(self.run_pipeline_button())
114
+ self.setLayout(layout)
115
+ self.setObjectName(PANEL_NAME)
116
+
117
+ def handle_scans_dir_select_button(self):
118
+ last_directory = self.settings().get('last_directory')
119
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
120
+ if directory:
121
+ self.scans_dir_line_edit().setText(directory)
122
+ self.settings().set('last_directory', directory)
123
+
124
+ def handle_output_dir_select_button(self):
125
+ last_directory = self.settings().get('last_directory')
126
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
127
+ if directory:
128
+ self.output_dir_line_edit().setText(directory)
129
+ self.settings().set('last_directory', directory)
130
+
131
+ def handle_run_pipeline_button(self):
132
+ errors = self.check_inputs_and_parameters()
133
+ if len(errors) > 0:
134
+ error_message = 'Following errors were encountered:\n'
135
+ for error in errors:
136
+ error_message += f' - {error}\n'
137
+ QMessageBox.information(self, 'Error', error_message)
138
+ else:
139
+ LOG.info('Running pipeline...')
140
+ self.run_pipeline_button().setEnabled(False)
141
+ self.save_inputs_and_parameters()
142
+ self._task = LiverAnalysisPipeline(
143
+ inputs={'scans': self.scans_dir_line_edit().text()},
144
+ params={'compressed': True},
145
+ output=self.output_dir_line_edit().text(),
146
+ overwrite=self.overwrite_checkbox().isChecked(),
147
+ )
148
+ self._worker = Worker(self._task)
149
+ self._thread = QThread()
150
+ self._worker.moveToThread(self._thread)
151
+ self._thread.started.connect(self._worker.run)
152
+ self._worker.progress.connect(self.handle_progress)
153
+ self._worker.status.connect(self.handle_status)
154
+ self._worker.finished.connect(self.handle_finished)
155
+ self._worker.finished.connect(self._thread.quit)
156
+ self._worker.finished.connect(self._worker.deleteLater)
157
+ self._thread.finished.connect(self._thread.deleteLater)
158
+ self._thread.start()
159
+
160
+ @Slot(int)
161
+ def handle_progress(self, progress):
162
+ LOG.info(f'Progress: {progress} / 100%')
163
+
164
+ @Slot(str)
165
+ def handle_status(self, status):
166
+ LOG.info(f'Status: {status}')
167
+
168
+ @Slot()
169
+ def handle_finished(self):
170
+ self.run_pipeline_button().setEnabled(True)
171
+
172
+ def check_inputs_and_parameters(self):
173
+ errors = []
174
+ if self.scans_dir_line_edit().text() == '':
175
+ errors.append('Empty scans directory path')
176
+ elif not os.path.isdir(self.scans_dir_line_edit().text()):
177
+ errors.append('Scans directory does not exist')
178
+ if self.output_dir_line_edit().text() == '':
179
+ errors.append('Empty output directory path')
180
+ elif os.path.isdir(self.output_dir_line_edit().text()) and not self.overwrite_checkbox().isChecked():
181
+ errors.append('Output directory exists but overwrite=False. Please remove output directory first')
182
+ return errors
183
+
184
+ def save_inputs_and_parameters(self):
185
+ self.settings().set(f'{PANEL_NAME}/scans_dir', self.scans_dir_line_edit().text())
186
+ self.settings().set(f'{PANEL_NAME}/output_dir', self.output_dir_line_edit().text())
187
+ self.settings().set(f'{PANEL_NAME}/overwrite', self.overwrite_checkbox().isChecked())
@@ -0,0 +1,215 @@
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.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 CalculateMaskStatisticsTask
25
+
26
+ LOG = LogManager()
27
+
28
+ PANEL_TITLE = 'CalculateMaskStatisticsTask'
29
+ PANEL_NAME = 'calculatemaskstatisticstaskpanel'
30
+
31
+
32
+ class CalculateMaskStatisticsTaskPanel(TaskPanel):
33
+ def __init__(self):
34
+ super(CalculateMaskStatisticsTaskPanel, self).__init__()
35
+ self.set_title(PANEL_TITLE)
36
+ self._scans_dir_line_edit = None
37
+ self._scans_dir_select_button = None
38
+ self._masks_dir_line_edit = None
39
+ self._masks_dir_select_button = None
40
+ self._output_dir_line_edit = None
41
+ self._output_dir_select_button = None
42
+ self._overwrite_checkbox = None
43
+ self._form_layout = None
44
+ self._run_task_button = None
45
+ self._settings = None
46
+ self._task = None
47
+ self._worker = None
48
+ self._thread = None
49
+ self.init_layout()
50
+
51
+ def scans_dir_line_edit(self):
52
+ if not self._scans_dir_line_edit:
53
+ self._scans_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/scans_dir'))
54
+ return self._scans_dir_line_edit
55
+
56
+ def scans_dir_select_button(self):
57
+ if not self._scans_dir_select_button:
58
+ self._scans_dir_select_button = QPushButton('Select')
59
+ self._scans_dir_select_button.clicked.connect(self.handle_scans_dir_select_button)
60
+ return self._scans_dir_select_button
61
+
62
+ def masks_dir_line_edit(self):
63
+ if not self._masks_dir_line_edit:
64
+ self._masks_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/masks_dir'))
65
+ return self._masks_dir_line_edit
66
+
67
+ def masks_dir_select_button(self):
68
+ if not self._masks_dir_select_button:
69
+ self._masks_dir_select_button = QPushButton('Select')
70
+ self._masks_dir_select_button.clicked.connect(self.handle_masks_dir_select_button)
71
+ return self._masks_dir_select_button
72
+
73
+ def output_dir_line_edit(self):
74
+ if not self._output_dir_line_edit:
75
+ self._output_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/output_dir'))
76
+ return self._output_dir_line_edit
77
+
78
+ def output_dir_select_button(self):
79
+ if not self._output_dir_select_button:
80
+ self._output_dir_select_button = QPushButton('Select')
81
+ self._output_dir_select_button.clicked.connect(self.handle_output_dir_select_button)
82
+ return self._output_dir_select_button
83
+
84
+ def overwrite_checkbox(self):
85
+ if not self._overwrite_checkbox:
86
+ self._overwrite_checkbox = QCheckBox('')
87
+ self._overwrite_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/overwrite', True))
88
+ return self._overwrite_checkbox
89
+
90
+ def form_layout(self):
91
+ if not self._form_layout:
92
+ self._form_layout = QFormLayout()
93
+ if is_macos():
94
+ self._form_layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
95
+ return self._form_layout
96
+
97
+ def run_task_button(self):
98
+ if not self._run_task_button:
99
+ self._run_task_button = QPushButton('Run task')
100
+ self._run_task_button.clicked.connect(self.handle_run_task_button)
101
+ return self._run_task_button
102
+
103
+ def settings(self):
104
+ if not self._settings:
105
+ self._settings = Settings()
106
+ return self._settings
107
+
108
+ def init_layout(self):
109
+ scans_dir_layout = QHBoxLayout()
110
+ scans_dir_layout.addWidget(self.scans_dir_line_edit())
111
+ scans_dir_layout.addWidget(self.scans_dir_select_button())
112
+ masks_dir_layout = QHBoxLayout()
113
+ masks_dir_layout.addWidget(self.masks_dir_line_edit())
114
+ masks_dir_layout.addWidget(self.masks_dir_select_button())
115
+ output_dir_layout = QHBoxLayout()
116
+ output_dir_layout.addWidget(self.output_dir_line_edit())
117
+ output_dir_layout.addWidget(self.output_dir_select_button())
118
+ self.form_layout().addRow('Scans directory', scans_dir_layout)
119
+ self.form_layout().addRow('Masks directory', masks_dir_layout)
120
+ self.form_layout().addRow('Output directory', output_dir_layout)
121
+ self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
122
+ layout = QVBoxLayout()
123
+ layout.addLayout(self.form_layout())
124
+ layout.addWidget(self.run_task_button())
125
+ self.setLayout(layout)
126
+ self.setObjectName(PANEL_NAME)
127
+
128
+ def handle_scans_dir_select_button(self):
129
+ last_directory = self.settings().get('last_directory')
130
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
131
+ if directory:
132
+ self.scans_dir_line_edit().setText(directory)
133
+ self.settings().set('last_directory', directory)
134
+
135
+ def handle_masks_dir_select_button(self):
136
+ last_directory = self.settings().get('last_directory')
137
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
138
+ if directory:
139
+ self.masks_dir_line_edit().setText(directory)
140
+ self.settings().set('last_directory', directory)
141
+
142
+ def handle_output_dir_select_button(self):
143
+ last_directory = self.settings().get('last_directory')
144
+ directory = QFileDialog.getExistingDirectory(dir=last_directory)
145
+ if directory:
146
+ self.output_dir_line_edit().setText(directory)
147
+ self.settings().set('last_directory', directory)
148
+
149
+ def handle_run_task_button(self):
150
+ errors = self.check_inputs_and_parameters()
151
+ if len(errors) > 0:
152
+ error_message = 'Following errors were encountered:\n'
153
+ for error in errors:
154
+ error_message += f' - {error}\n'
155
+ QMessageBox.information(self, 'Error', error_message)
156
+ else:
157
+ LOG.info('Running task...')
158
+ self.run_task_button().setEnabled(False)
159
+ self.save_inputs_and_parameters()
160
+ self._task = CalculateMaskStatisticsTask(
161
+ inputs={
162
+ 'scans': self.scans_dir_line_edit().text(),
163
+ 'masks': self.masks_dir_line_edit().text(),
164
+ },
165
+ params=None,
166
+ output=self.output_dir_line_edit().text(),
167
+ overwrite=self.overwrite_checkbox().isChecked(),
168
+ )
169
+ self._worker = Worker(self._task)
170
+ self._thread = QThread()
171
+ self._worker.moveToThread(self._thread)
172
+ self._thread.started.connect(self._worker.run)
173
+ self._worker.progress.connect(self.handle_progress)
174
+ self._worker.status.connect(self.handle_status)
175
+ self._worker.finished.connect(self.handle_finished)
176
+ self._worker.finished.connect(self._thread.quit)
177
+ self._worker.finished.connect(self._worker.deleteLater)
178
+ self._thread.finished.connect(self._thread.deleteLater)
179
+ self._thread.start()
180
+
181
+ @Slot(int)
182
+ def handle_progress(self, progress):
183
+ LOG.info(f'Progress: {progress} / 100%')
184
+
185
+ @Slot(str)
186
+ def handle_status(self, status):
187
+ LOG.info(f'Status: {status}')
188
+
189
+ @Slot()
190
+ def handle_finished(self):
191
+ self.run_task_button().setEnabled(True)
192
+
193
+ # HELPERS
194
+
195
+ def check_inputs_and_parameters(self):
196
+ errors = []
197
+ if self.scans_dir_line_edit().text() == '':
198
+ errors.append('Empty scans directory path')
199
+ if not os.path.isdir(self.scans_dir_line_edit().text()):
200
+ errors.append('Scans directory does not exist')
201
+ if self.masks_dir_line_edit().text() == '':
202
+ errors.append('Empty masks directory path')
203
+ if not os.path.isdir(self.masks_dir_line_edit().text()):
204
+ errors.append('Masks directory does not exist')
205
+ if self.output_dir_line_edit().text() == '':
206
+ errors.append('Empty output directory path')
207
+ if os.path.isdir(self.output_dir_line_edit().text()) and not self.overwrite_checkbox().isChecked():
208
+ errors.append('Output directory exists but overwrite=False. Please remove output directory first')
209
+ return errors
210
+
211
+ def save_inputs_and_parameters(self):
212
+ self.settings().set(f'{PANEL_NAME}/scans_dir', self.scans_dir_line_edit().text())
213
+ self.settings().set(f'{PANEL_NAME}/masks_dir', self.masks_dir_line_edit().text())
214
+ self.settings().set(f'{PANEL_NAME}/output_dir', self.output_dir_line_edit().text())
215
+ 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.16
3
+ Version: 2.0.17
4
4
  Summary:
5
5
  Author: Ralph Brecheisen
6
6
  Author-email: r.brecheisen@maastrichtuniversity.nl
@@ -9,9 +9,9 @@ Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.11
10
10
  Requires-Dist: antspyx (>=0.5.4)
11
11
  Requires-Dist: dicom2nifti (>=2.6.2)
12
- Requires-Dist: docker
12
+ Requires-Dist: docker (>=7.1.0)
13
13
  Requires-Dist: flask (>=3.1.2)
14
- Requires-Dist: moosez
14
+ Requires-Dist: moosez (>=3.0.29)
15
15
  Requires-Dist: nibabel (>=5.3.2)
16
16
  Requires-Dist: numpy (>=1.26.4)
17
17
  Requires-Dist: openpyxl (>=3.1.5)
@@ -23,7 +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
+ Requires-Dist: slicer (>=0.0.8)
27
27
  Requires-Dist: tensorboard (==2.15.2)
28
28
  Requires-Dist: tensorboard-data-server (==0.7.2)
29
29
  Requires-Dist: tensorflow (==2.15.*) ; platform_system == "Linux"
@@ -1,19 +1,21 @@
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=Fzsz1-uziNRabnBRvqyRcXFpQ7CGLF1dt4w7Js-Y1u8,1614
4
+ mosamatic2/cli.py,sha256=pZIcsNPBYF1VLiQvo6CNv6_W-lw7toon1Mi0R9Huobw,1878
5
5
  mosamatic2/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  mosamatic2/commands/boadockerpipeline.py,sha256=-k9rQlhixfcqGUy29SsKYVXxZFri6DcnWaV6utJ6mhI,1344
7
+ mosamatic2/commands/calculatemaskstatistics.py,sha256=cCQkLhSczD0sk2wTmfwWF0A3ARkUPqqgrDoz8sT3kDw,1498
7
8
  mosamatic2/commands/calculatescores.py,sha256=Lb8Q8L2yq7Tt6VBJ6_lltRuldrev_pac6fcgF-xzZyE,1984
8
9
  mosamatic2/commands/createdicomsummary.py,sha256=qbVgWGIJPBL8vTBcAxfThloQ_q15OfQy5ohMprzZL4E,1955
9
10
  mosamatic2/commands/createpngsfromsegmentations.py,sha256=uUAQJVTqOkBCfENzi21RBNYvf6_nuesx1MeR3j_-7dM,1682
10
- mosamatic2/commands/defaultdockerpipeline.py,sha256=IxnpnIIsFH2u3aBLKgrHUb_Au72ajlIzzVe6Y0MFHJU,2025
11
+ mosamatic2/commands/defaultdockerpipeline.py,sha256=oPi4UuZ0Xne0_fsAyzRyvhCgNV9KDH3nTyZJJlT3LpI,2368
11
12
  mosamatic2/commands/defaultpipeline.py,sha256=-kQRSXbEDimAdy_kT1qMxRlZWUoXdu3PkWf70PCIS5Y,1734
12
13
  mosamatic2/commands/dicom2nifti.py,sha256=uzI3-QT8_HtwXKkX5kRJzMKssM5dvJGUjZ1Z-XkwLYc,1366
14
+ mosamatic2/commands/liveranalysispipeline.py,sha256=CiTTTIEpOAHwx7p919WH8XsEflEKyV6rgfgeG6XjIDs,1831
13
15
  mosamatic2/commands/rescaledicomimages.py,sha256=25QdCzB5s0sRwkTb3o5zco2bIwy6LttNf7i97kGBDYQ,1280
14
16
  mosamatic2/commands/segmentmusclefatl3tensorflow.py,sha256=CdScmA_EQicaN4GY5bBUOYwfhDPqy9om2sxY3WrtmM0,1424
15
17
  mosamatic2/commands/selectslicefromscans.py,sha256=3398PM2uBcxF6wpb0-c-Itp_qxoAxBf0SE2nDDI3Ct4,1715
16
- mosamatic2/commands/totalsegmentator.py,sha256=GY8wAFDHJW-dMWa3ZskHJnaYd71bKWUjpwV2-Wf38W4,1956
18
+ mosamatic2/commands/totalsegmentator.py,sha256=VY3OWPmnQzqkWPjKj_HIcQu2kJoiwVAh2HveFC1Z6uE,2245
17
19
  mosamatic2/constants.py,sha256=MVYMwO-x2jQSN37o3zNkRseVQ1nYRA3mLv3v_Q0Mlds,1284
18
20
  mosamatic2/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
21
  mosamatic2/core/data/__init__.py,sha256=j9iGqUTJlGF0N0gPrzzpe_Dhv0Bj9c6FdQ1g7U-_j2g,298
@@ -29,16 +31,20 @@ mosamatic2/core/data/numpyimage.py,sha256=bnG6WVGSRxNdzIlb2DNj5u6Gv4BAYscIj2Buyj
29
31
  mosamatic2/core/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
32
  mosamatic2/core/managers/logmanager.py,sha256=NEaXvhl0aILjBbK710GaWanVuuNvB51HpHhE5rgYvng,1391
31
33
  mosamatic2/core/managers/logmanagerlistener.py,sha256=Gaig07yjBnyQq9I8sN85olTEeDCDyCFQnEJdwzvmgvc,99
32
- mosamatic2/core/pipelines/__init__.py,sha256=YPlyO5L_DMrTyO2-2TBq_Bdg-bbMHQiicXJafn66Dg8,283
34
+ mosamatic2/core/pipelines/__init__.py,sha256=l2YJ5bc2PdscGYJGWUL1UvA-LeVvnckNDaMlxMvzdCc,388
33
35
  mosamatic2/core/pipelines/boadockerpipeline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
36
  mosamatic2/core/pipelines/boadockerpipeline/boadockerpipeline.py,sha256=KFZk_L_0KaTdUqHjqGW8MysbCIOKlUKWP8S-T9xyvis,2939
35
37
  mosamatic2/core/pipelines/defaultdockerpipeline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
- mosamatic2/core/pipelines/defaultdockerpipeline/defaultdockerpipeline.py,sha256=m_f9c7MmvSRqERF6n-nLGxa6xP13xtoSihW53d387zU,1315
38
+ mosamatic2/core/pipelines/defaultdockerpipeline/defaultdockerpipeline.py,sha256=FlofeJE_eDHydoo-j_zBiorZXc9wg0a4YKpEesZYqgo,1164
37
39
  mosamatic2/core/pipelines/defaultpipeline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
40
  mosamatic2/core/pipelines/defaultpipeline/defaultpipeline.py,sha256=Bme0r_shnrllWYCYDNc6cLM2fQC2yD8RJKpRdoh_6Uc,3077
41
+ mosamatic2/core/pipelines/liveranalysispipeline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
+ mosamatic2/core/pipelines/liveranalysispipeline/liveranalysispipeline.py,sha256=OcIYJGI5eSZjNpnv5CBAz7cungCmgyT7aPmx7-_eGDc,1604
39
43
  mosamatic2/core/pipelines/pipeline.py,sha256=mRxKXLKwgKDpc8R9mCI6gDKGJ2lKVxRQ__Sf0Mfn_Qc,384
40
44
  mosamatic2/core/singleton.py,sha256=FV0k_LlOCmFhlWN6gf1c2x7YXWyd8-7DsIMvOKrI6NY,224
41
- mosamatic2/core/tasks/__init__.py,sha256=iJ4uMgLtN-PmZ43FAw8Y_QLh03ctiTaCGZxAUsVFXPM,857
45
+ mosamatic2/core/tasks/__init__.py,sha256=JE-654XG0bel8n3EMM8nJmdZwQfMh_OzIRl61JWk-kw,976
46
+ mosamatic2/core/tasks/calculatemaskstatisticstask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
+ mosamatic2/core/tasks/calculatemaskstatisticstask/calculatemaskstatisticstask.py,sha256=e0VdrRLqEPQGqRTpAmCCkI4gDyo0IISd_0szonrbebI,4325
42
48
  mosamatic2/core/tasks/calculatescorestask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
49
  mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py,sha256=cwGVedJR_BGSYzXq6ouTgCbSC6s2VtyD8FzRC-QBXUI,6617
44
50
  mosamatic2/core/tasks/createdicomsummarytask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -56,16 +62,16 @@ mosamatic2/core/tasks/selectslicefromscanstask/__init__.py,sha256=47DEQpj8HBSa-_
56
62
  mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py,sha256=EIEHhWoGL30rcz8qckFl465rU40P-pIkvhMOfSud7Yw,7253
57
63
  mosamatic2/core/tasks/task.py,sha256=APPnid6dpSGkPuDqU1vm2RIMR5vkpvbP1CPHUMjympg,1691
58
64
  mosamatic2/core/tasks/totalsegmentatortask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
- mosamatic2/core/tasks/totalsegmentatortask/totalsegmentatortask.py,sha256=Yzy-V6psjhmt-idvLxBoUmbyQGnCBb0OiD0s9lXLmWk,2096
65
+ mosamatic2/core/tasks/totalsegmentatortask/totalsegmentatortask.py,sha256=_phgEp-lPhsim53jdTyfzJzUF0EMcF-I-llQ3J9CyN4,3331
60
66
  mosamatic2/core/utils.py,sha256=4duREimHWkUfZIjSy16fsxF8-3ZxukfOi8eSNjAdxF8,12240
61
67
  mosamatic2/server.py,sha256=-cZ9BPsZUXoINKqwhCHN8c59mlvzzDXzTVxsYt9au70,4644
62
68
  mosamatic2/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
- mosamatic2/ui/mainwindow.py,sha256=DrX-6Hu4-3wJJy6rZkexYuTQ-pO_uliyEQVkYc4GuCM,15848
69
+ mosamatic2/ui/mainwindow.py,sha256=goQVUGuWHHaUhs-oxnSRfZBth2VYYBNsvr2siA8wPnk,17866
64
70
  mosamatic2/ui/resources/icons/mosamatic2.icns,sha256=OfhC-diJTIgaNMOezxKKilGsY7mRkaGdU5dGr0MOjIA,2994125
65
71
  mosamatic2/ui/resources/icons/mosamatic2.ico,sha256=ySD3RYluHK3pgS0Eas7eKrVk_AskdLQ4qs_IT-wNhq4,12229
66
72
  mosamatic2/ui/resources/icons/spinner.gif,sha256=rvaac6GUZauHSPFSOLWr0RmLfjmtZih2Q8knQ2WP3Po,16240
67
73
  mosamatic2/ui/resources/images/body-composition.jpg,sha256=KD-BudbXwThB4lJOZZN-ad5-TZRaaZ5cKTH0Ar1TOZs,21227
68
- mosamatic2/ui/resources/VERSION,sha256=-cxAVNmHfDH3lZMAtwEaTEwW3lYerVGKI5FoPFpnQq0,9
74
+ mosamatic2/ui/resources/VERSION,sha256=krMCj4G6-pLwCXMT7JLhq0fbjfA7zA6XnzVBHS_mgSc,9
69
75
  mosamatic2/ui/settings.py,sha256=YEVHYJIfNsqMO3v1pjzgh7Pih9GGoUX7S9s8S-sBNUk,2121
70
76
  mosamatic2/ui/utils.py,sha256=6bbPIrh4RJ_yhQKNZrgPbL4XeUEogjIjbk_e5c3QS5g,853
71
77
  mosamatic2/ui/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -79,10 +85,12 @@ mosamatic2/ui/widgets/panels/mainpanel.py,sha256=KqI8dA7GpLFd2unqVRTBkNxdnh6AWGp
79
85
  mosamatic2/ui/widgets/panels/pipelines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
86
  mosamatic2/ui/widgets/panels/pipelines/boadockerpipelinepanel.py,sha256=f1VKob6SRi8dO2m1HF4JXwV_EkrzYWgTdkIOILDD5y4,8406
81
87
  mosamatic2/ui/widgets/panels/pipelines/defaultdockerpipelinepanel.py,sha256=VAYx8Ksb9yVaA2GngB5h-sJNfALXeQMmWqOEXQNCjKY,14215
82
- mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py,sha256=qkfI4BnLIXqE_YvSQj4sO_FjnK0eVdIMqAZ8sktgI-8,13727
88
+ mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py,sha256=gxkDPLZIRbaOEPr0ekumfa9TlCRlzoaAYF8kiL1BzRw,13732
89
+ mosamatic2/ui/widgets/panels/pipelines/liveranalysispipelinepanel.py,sha256=-bUv52mNWsrdWU0OBiOMfokOIgDnj8DWGzh47oRjvZ0,7778
83
90
  mosamatic2/ui/widgets/panels/pipelines/pipelinepanel.py,sha256=SlkKme8Wv2Bvp2Alen98mFjv3F5eZCwJylj294gd5uU,178
84
91
  mosamatic2/ui/widgets/panels/stackedpanel.py,sha256=dK1YWuHUzxRhVb5gP0Lu9rAiW4XagjcHmGF__5Lpufk,657
85
92
  mosamatic2/ui/widgets/panels/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
+ mosamatic2/ui/widgets/panels/tasks/calculatemaskstatisticstaskpanel.py,sha256=o6cXLgzopOgT27DZb5DVdhGnRIABNGkTDu5yiIImJOg,9149
86
94
  mosamatic2/ui/widgets/panels/tasks/calculatescorestaskpanel.py,sha256=NmPLQizj4x9jgf9UA7VZSjARNHYJB_jrfB0kvaVncdw,9387
87
95
  mosamatic2/ui/widgets/panels/tasks/createdicomsummarytaskpanel.py,sha256=cRCaEmjjZP6RwEhezj2Axxuv8uAGe7ZzHoU67asoZ5s,7530
88
96
  mosamatic2/ui/widgets/panels/tasks/createpngsfromsegmentationstaskpanel.py,sha256=JFnmYjPemRtXPXV2fk2cjB45fseN5BZ8gI_T1zVLGV8,7879
@@ -100,7 +108,7 @@ mosamatic2/ui/widgets/panels/visualizations/slicevisualization/slicevisualizatio
100
108
  mosamatic2/ui/widgets/panels/visualizations/visualization.py,sha256=JvqTJi7cCGYK1-wrN2oURdCOBoPS2clVUyYglhkoVJg,178
101
109
  mosamatic2/ui/widgets/splashscreen.py,sha256=MS-OczOWfwwEQNQd-JWe9_Mh57css0cSQgbu973rwQo,4056
102
110
  mosamatic2/ui/worker.py,sha256=v7e3gq7MUudgpB1BJW-P7j5wurzu6-HG5m7I6WHgJp0,699
103
- mosamatic2-2.0.16.dist-info/entry_points.txt,sha256=MCUpKkgbej1clgp8EqlLQGs0BIKwGPcBPiVWLfGz9Gw,126
104
- mosamatic2-2.0.16.dist-info/METADATA,sha256=DZje4-e8ccI8_fErMX-LIRP0rjq-_p5QQpEezDXWIm0,1534
105
- mosamatic2-2.0.16.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
106
- mosamatic2-2.0.16.dist-info/RECORD,,
111
+ mosamatic2-2.0.17.dist-info/entry_points.txt,sha256=MCUpKkgbej1clgp8EqlLQGs0BIKwGPcBPiVWLfGz9Gw,126
112
+ mosamatic2-2.0.17.dist-info/METADATA,sha256=NPrpAEWyQ33QauK_ymcmV-RE0vJTHetSGQGQxbvpfPM,1565
113
+ mosamatic2-2.0.17.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
114
+ mosamatic2-2.0.17.dist-info/RECORD,,