RadiomicsModellingSuite 0.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- radiomicsmodellingsuite-0.0.0/LICENSE +1 -0
- radiomicsmodellingsuite-0.0.0/MANIFEST.in +2 -0
- radiomicsmodellingsuite-0.0.0/PKG-INFO +151 -0
- radiomicsmodellingsuite-0.0.0/README.md +1 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/__init__.py +21 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/acquisition_parameters.py +213 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/data_processing.py +562 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/dataset_manipulation.py +484 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/exploratory_analysis.py +334 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/feature_extraction.py +767 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/feature_harmonisation.py +205 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/feature_selection.py +883 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/image_processing.py +303 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/latex_reporting.py +2153 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/model_calibration.py +701 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/model_evaluation.py +3387 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/model_explainability.py +802 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/model_training.py +508 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/radiomics_modelling_suite.py +97 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/setup.py +12 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/string_formatting.py +64 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite.egg-info/PKG-INFO +151 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite.egg-info/SOURCES.txt +26 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite.egg-info/dependency_links.txt +1 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite.egg-info/requires.txt +134 -0
- radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite.egg-info/top_level.txt +1 -0
- radiomicsmodellingsuite-0.0.0/setup.cfg +4 -0
- radiomicsmodellingsuite-0.0.0/setup.py +21 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
��
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: RadiomicsModellingSuite
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Details about the package
|
|
5
|
+
Author: AllisonNg067
|
|
6
|
+
Author-email: allison.ng@uwa.edu.au
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: asttokens==3.0.0
|
|
10
|
+
Requires-Dist: attrs==18.2.0
|
|
11
|
+
Requires-Dist: autograd==1.8.0
|
|
12
|
+
Requires-Dist: autograd-gamma==0.5.0
|
|
13
|
+
Requires-Dist: Bottleneck==1.4.2
|
|
14
|
+
Requires-Dist: Brotli==1.0.9
|
|
15
|
+
Requires-Dist: cffi==1.17.1
|
|
16
|
+
Requires-Dist: clarabel==0.11.1
|
|
17
|
+
Requires-Dist: click==8.1.8
|
|
18
|
+
Requires-Dist: cloudpickle==3.0.0
|
|
19
|
+
Requires-Dist: colorama==0.4.6
|
|
20
|
+
Requires-Dist: comm==0.2.2
|
|
21
|
+
Requires-Dist: contourpy==1.3.0
|
|
22
|
+
Requires-Dist: cvxpy==1.7.2
|
|
23
|
+
Requires-Dist: cycler==0.11.0
|
|
24
|
+
Requires-Dist: cytoolz==1.0.1
|
|
25
|
+
Requires-Dist: dask==2024.5.0
|
|
26
|
+
Requires-Dist: debugpy==1.8.14
|
|
27
|
+
Requires-Dist: decorator==5.2.1
|
|
28
|
+
Requires-Dist: dicom2nifti==2.3.4
|
|
29
|
+
Requires-Dist: docopt==0.6.2
|
|
30
|
+
Requires-Dist: ecos==2.0.14
|
|
31
|
+
Requires-Dist: exceptiongroup==1.3.0
|
|
32
|
+
Requires-Dist: executing==2.2.0
|
|
33
|
+
Requires-Dist: fonttools==4.55.3
|
|
34
|
+
Requires-Dist: formulaic==1.2.0
|
|
35
|
+
Requires-Dist: fsspec==2025.3.2
|
|
36
|
+
Requires-Dist: gmpy2==2.2.1
|
|
37
|
+
Requires-Dist: imagecodecs==2022.9.26
|
|
38
|
+
Requires-Dist: imageio==2.37.0
|
|
39
|
+
Requires-Dist: imbalanced-learn==0.12.4
|
|
40
|
+
Requires-Dist: imblearn==0.0
|
|
41
|
+
Requires-Dist: importlib_metadata==8.7.0
|
|
42
|
+
Requires-Dist: importlib_resources==6.4.0
|
|
43
|
+
Requires-Dist: interface-meta==1.3.0
|
|
44
|
+
Requires-Dist: ipykernel==6.29.5
|
|
45
|
+
Requires-Dist: ipython==8.18.1
|
|
46
|
+
Requires-Dist: jedi==0.19.2
|
|
47
|
+
Requires-Dist: Jinja2==3.1.6
|
|
48
|
+
Requires-Dist: joblib==1.4.2
|
|
49
|
+
Requires-Dist: jupyter_client==8.6.3
|
|
50
|
+
Requires-Dist: jupyter_core==5.8.1
|
|
51
|
+
Requires-Dist: kiwisolver==1.4.4
|
|
52
|
+
Requires-Dist: lazy_loader==0.4
|
|
53
|
+
Requires-Dist: lifelines==0.30.0
|
|
54
|
+
Requires-Dist: lime==0.2.0.1
|
|
55
|
+
Requires-Dist: llvmlite==0.43.0
|
|
56
|
+
Requires-Dist: locket==1.0.0
|
|
57
|
+
Requires-Dist: MarkupSafe==3.0.2
|
|
58
|
+
Requires-Dist: matplotlib==3.9.4
|
|
59
|
+
Requires-Dist: matplotlib-inline==0.1.7
|
|
60
|
+
Requires-Dist: mkl_fft==1.3.11
|
|
61
|
+
Requires-Dist: mkl_random==1.2.8
|
|
62
|
+
Requires-Dist: mkl-service==2.4.0
|
|
63
|
+
Requires-Dist: MLstatkit==0.1.9
|
|
64
|
+
Requires-Dist: mpmath==1.3.0
|
|
65
|
+
Requires-Dist: narwhals==2.1.2
|
|
66
|
+
Requires-Dist: nest_asyncio==1.6.0
|
|
67
|
+
Requires-Dist: networkx==3.2.1
|
|
68
|
+
Requires-Dist: neuroCombat==0.2.12
|
|
69
|
+
Requires-Dist: neurocombat-sklearn==0.1.3
|
|
70
|
+
Requires-Dist: nibabel==5.3.2
|
|
71
|
+
Requires-Dist: numba==0.60.0
|
|
72
|
+
Requires-Dist: numexpr==2.10.1
|
|
73
|
+
Requires-Dist: numpy==1.24.3
|
|
74
|
+
Requires-Dist: ordered-set==4.1.0
|
|
75
|
+
Requires-Dist: osqp==1.0.4
|
|
76
|
+
Requires-Dist: packaging==24.2
|
|
77
|
+
Requires-Dist: pandas==2.3.1
|
|
78
|
+
Requires-Dist: parso==0.8.4
|
|
79
|
+
Requires-Dist: partd==1.4.2
|
|
80
|
+
Requires-Dist: patsy==1.0.1
|
|
81
|
+
Requires-Dist: pdflatex==0.1.3
|
|
82
|
+
Requires-Dist: pickleshare==0.7.5
|
|
83
|
+
Requires-Dist: pillow==11.0.0
|
|
84
|
+
Requires-Dist: pip==25.1
|
|
85
|
+
Requires-Dist: platformdirs==4.3.8
|
|
86
|
+
Requires-Dist: plotly==6.3.0
|
|
87
|
+
Requires-Dist: prompt_toolkit==3.0.51
|
|
88
|
+
Requires-Dist: psutil==7.0.0
|
|
89
|
+
Requires-Dist: pure_eval==0.2.3
|
|
90
|
+
Requires-Dist: pycparser==2.21
|
|
91
|
+
Requires-Dist: pydicom==2.4.4
|
|
92
|
+
Requires-Dist: Pygments==2.19.1
|
|
93
|
+
Requires-Dist: pykwalify==1.8.0
|
|
94
|
+
Requires-Dist: PyLaTeX==1.4.2
|
|
95
|
+
Requires-Dist: pyparsing==3.2.0
|
|
96
|
+
Requires-Dist: PyQt6==6.7.1
|
|
97
|
+
Requires-Dist: PyQt6_sip==13.9.1
|
|
98
|
+
Requires-Dist: pyradiomics==3.0.1a1
|
|
99
|
+
Requires-Dist: python-dateutil==2.9.0.post0
|
|
100
|
+
Requires-Dist: pytz==2024.1
|
|
101
|
+
Requires-Dist: pywavelets==1.5.0
|
|
102
|
+
Requires-Dist: pywin32==307
|
|
103
|
+
Requires-Dist: PyYAML==6.0.2
|
|
104
|
+
Requires-Dist: pyzmq==26.2.0
|
|
105
|
+
Requires-Dist: qdldl==0.1.7.post5
|
|
106
|
+
Requires-Dist: rpy2==3.5.11
|
|
107
|
+
Requires-Dist: ruamel.yaml==0.18.10
|
|
108
|
+
Requires-Dist: ruamel.yaml.clib==0.2.12
|
|
109
|
+
Requires-Dist: scikit-image==0.24.0
|
|
110
|
+
Requires-Dist: scikit-learn==1.6.1
|
|
111
|
+
Requires-Dist: scikit-survival==0.18.0
|
|
112
|
+
Requires-Dist: scipy==1.13.1
|
|
113
|
+
Requires-Dist: scs==3.2.8
|
|
114
|
+
Requires-Dist: seaborn==0.13.2
|
|
115
|
+
Requires-Dist: setuptools==78.1.1
|
|
116
|
+
Requires-Dist: shap==0.48.0
|
|
117
|
+
Requires-Dist: simplegeneric==0.8.1
|
|
118
|
+
Requires-Dist: SimpleITK==2.2.1
|
|
119
|
+
Requires-Dist: sip==6.10.0
|
|
120
|
+
Requires-Dist: six==1.17.0
|
|
121
|
+
Requires-Dist: slicer==0.0.8
|
|
122
|
+
Requires-Dist: stack_data==0.6.3
|
|
123
|
+
Requires-Dist: statsmodels==0.14.4
|
|
124
|
+
Requires-Dist: survshap==0.4.2
|
|
125
|
+
Requires-Dist: sympy==1.14.0
|
|
126
|
+
Requires-Dist: threadpoolctl==3.5.0
|
|
127
|
+
Requires-Dist: tifffile==2022.10.10
|
|
128
|
+
Requires-Dist: tomli==2.0.1
|
|
129
|
+
Requires-Dist: toolz==1.0.0
|
|
130
|
+
Requires-Dist: tornado==6.5.1
|
|
131
|
+
Requires-Dist: tqdm==4.67.1
|
|
132
|
+
Requires-Dist: traitlets==5.14.3
|
|
133
|
+
Requires-Dist: trimesh==4.6.11
|
|
134
|
+
Requires-Dist: typing_extensions==4.14.0
|
|
135
|
+
Requires-Dist: tzdata==2025.2
|
|
136
|
+
Requires-Dist: tzlocal==5.2
|
|
137
|
+
Requires-Dist: unicodedata2==15.1.0
|
|
138
|
+
Requires-Dist: wcwidth==0.2.13
|
|
139
|
+
Requires-Dist: wheel==0.45.1
|
|
140
|
+
Requires-Dist: wrapt==1.17.3
|
|
141
|
+
Requires-Dist: xgboost==2.1.3
|
|
142
|
+
Requires-Dist: zipp==3.21.0
|
|
143
|
+
Dynamic: author
|
|
144
|
+
Dynamic: author-email
|
|
145
|
+
Dynamic: description
|
|
146
|
+
Dynamic: description-content-type
|
|
147
|
+
Dynamic: license-file
|
|
148
|
+
Dynamic: requires-dist
|
|
149
|
+
Dynamic: summary
|
|
150
|
+
|
|
151
|
+
ÿþ
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
��
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Define the __all__ variable
|
|
2
|
+
__all__ = ["acquisition_parameters", "data_processing", "dataset_manipulation", "exploratory_analysis",
|
|
3
|
+
"feature_extraction", "feature_harmonisation", "feature_selection", "image_processing",
|
|
4
|
+
"latex_reporting", "model_calibration", "model_evaluation", "model_explainability",
|
|
5
|
+
"model_training", "radiomics_modelling_suite", "string_formatting"]
|
|
6
|
+
|
|
7
|
+
from . import acquisition_parameters
|
|
8
|
+
from . import data_processing
|
|
9
|
+
from . import dataset_manipulation
|
|
10
|
+
from . import exploratory_analysis
|
|
11
|
+
from . import feature_extraction
|
|
12
|
+
from . import feature_harmonisation
|
|
13
|
+
from . import feature_selection
|
|
14
|
+
from . import image_processing
|
|
15
|
+
from . import latex_reporting
|
|
16
|
+
from . import model_calibration
|
|
17
|
+
from . import model_evaluation
|
|
18
|
+
from . import model_explainability
|
|
19
|
+
from . import model_training
|
|
20
|
+
from . import radiomics_modelling_suite
|
|
21
|
+
from . import string_formatting
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import pydicom as dicom
|
|
2
|
+
import numpy as np
|
|
3
|
+
import os
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import dicom2nifti
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
dicom2nifti.settings.disable_validate_slice_increment()
|
|
8
|
+
from . import latex_reporting as lr
|
|
9
|
+
from . import string_formatting as sf
|
|
10
|
+
|
|
11
|
+
def obtain_acquisition_parameters(directory_list, scan_types, author, filename="acquisition_parameters.csv", max_string_length = 3, latex_sections=['Information', 'Paragraphs', 'all_tables', 'Tables']):
|
|
12
|
+
"""
|
|
13
|
+
Function to obtain acquisition parameters from the dicom files in the directory_list
|
|
14
|
+
|
|
15
|
+
Parameters:
|
|
16
|
+
|
|
17
|
+
* directory_list (list[str]) - list of directories containing the dicom slices
|
|
18
|
+
|
|
19
|
+
* scan_types (list[str]) - list of scan types studied
|
|
20
|
+
|
|
21
|
+
* author (str) - author name for latex report
|
|
22
|
+
|
|
23
|
+
* filename (str) - filename to save acquisition parameters to
|
|
24
|
+
|
|
25
|
+
* max_string_length (int) - maximum number of count values to write in a paragraph, if bigger than this maximum,
|
|
26
|
+
the paragraph will be written as a table instead
|
|
27
|
+
|
|
28
|
+
* latex_sections (list[str]) - list of sections to include in latex report
|
|
29
|
+
|
|
30
|
+
Outputs:
|
|
31
|
+
|
|
32
|
+
* csv file with acquisition parameters from each scan
|
|
33
|
+
|
|
34
|
+
* pdf and latex file with acquisition parameters
|
|
35
|
+
"""
|
|
36
|
+
mri_acquisition_dict = {'Manufacturer': [], 'SeriesDescription': [], 'ManufacturerModelName': [], 'ContrastBolusAgent': [], 'ScanningSequence': [],
|
|
37
|
+
'SequenceVariant': [], 'ScanOptions': [], 'SliceThickness': [], 'ImagingFrequency': [], 'RepetitionTime': [], 'EchoTime': [], 'InversionTime': [],
|
|
38
|
+
'MagneticFieldStrength': [], 'NumberOfPhaseEncodingSteps': [], 'EchoTrainLength': [], 'ContrastBolusVolume': [], 'InPlanePhaseEncodingDirection': [],
|
|
39
|
+
'FlipAngle': [], 'PixelSpacing': [], 'SpacingBetweenSlices': [], 'dBdt': [], 'ImageDimensions': []} #dictionary to store acquisition parameters for MRI images
|
|
40
|
+
mri_numerical_keys = [key for key in mri_acquisition_dict.keys() if key not in ['Manufacturer', 'SeriesDescription', 'ManufacturerModelName', 'ContrastBolusAgent', 'ScanningSequence',
|
|
41
|
+
'SequenceVariant', 'ScanOptions', 'InPlanePhaseEncodingDirection', 'PixelSpacing', 'ImageDimensions']] #list of keys which contain numerical values in MRI acquisition parameters
|
|
42
|
+
mri_paragraphs = [['Manufacturer', 'ManufacturerModelName', 'ContrastBolusAgent', 'ContrastBolusVolume'], ['ScanningSequence', 'ScanOptions', 'SequenceVariant'],
|
|
43
|
+
['ImagingFrequency', 'RepetitionTime', 'EchoTime', 'InversionTime',
|
|
44
|
+
'NumberOfPhaseEncodingSteps', 'EchoTrainLength', 'InPlanePhaseEncodingDirection',
|
|
45
|
+
'FlipAngle',], ['MagneticFieldStrength', 'dBdt'],
|
|
46
|
+
['SliceThickness', 'SpacingBetweenSlices', 'PixelSpacing', 'ImageDimensions']]
|
|
47
|
+
pet_acquisition_dict = {'Manufacturer': [], 'SeriesDescription': [], 'ManufacturerModelName': [], 'SliceThickness': [],
|
|
48
|
+
'SoftwareVersions': [], 'TriggerTime': [], 'FrameTime': [], 'IntervalsAcquired': [], 'IntervalsRejected': [],
|
|
49
|
+
'ReconstructionDiameter': [], 'EnergyWindowLowerLimit': [], 'EnergyWindowUpperLimit': [], 'CorrectedImage': [],
|
|
50
|
+
'RadiopharmaceuticalInformationSequence':[],'Radiopharmaceutical': [],'RadiopharmaceuticalVolume': [], 'RadionuclideTotalDose': [], 'RadionuclideHalfLife': [], 'RadionuclidePositronFraction': [],
|
|
51
|
+
'SamplesPerPixel': [], 'RandomsCorrected': [], 'RandomsCorrectionMethod':[], 'AttenuationCorrectionMethod': [], 'DecayCorrection': [], 'ReconstructionMethod': [], 'SliceSensitivityFactor': [],
|
|
52
|
+
'DecayFactor': [], 'DoseCalibrationFactor': [], 'ScatterFractionFactor': [], 'DeadTimeFactor': [], 'CoincidenceWindowWidth':[],
|
|
53
|
+
'CollimatorType': [], 'GantryDetectorTilt': [],
|
|
54
|
+
'PixelSpacing': [], 'SpacingBetweenSlices': [], 'ImageDimensions': []} #dictionary to store acquisition parameters for PET images
|
|
55
|
+
pet_paragraphs = [['Manufacturer', 'ManufacturerModelName', 'SoftwareVersions', 'ReconstructionMethod'], ['TriggerTime', 'FrameTime', 'IntervalsAcquired', 'IntervalsRejected',
|
|
56
|
+
'ReconstructionDiameter', 'EnergyWindowLowerLimit', 'EnergyWindowUpperLimit', 'CorrectedImage'],
|
|
57
|
+
['RadiopharmaceuticalInformationSequence', 'Radiopharmaceutical','RadiopharmaceuticalVolume', 'RadionuclideTotalDose', 'RadionuclideHalfLife', 'RadionuclidePositronFraction'],
|
|
58
|
+
['SamplesPerPixel','RandomsCorrected', 'RandomsCorrectionMethod', 'AttenuationCorrectionMethod', 'DecayCorrection', 'SliceSensitivityFactor',
|
|
59
|
+
'DecayFactor', 'DoseCalibrationFactor', 'ScatterFractionFactor', 'DeadTimeFactor', 'CoincidenceWindowWidth'],
|
|
60
|
+
['CollimatorType', 'GantryDetectorTilt'],
|
|
61
|
+
['SliceThickness', 'SpacingBetweenSlices', 'PixelSpacing', 'ImageDimensions']]
|
|
62
|
+
pet_numerical_keys = [key for key in pet_acquisition_dict.keys() if key not in ['Manufacturer', 'SeriesDescription', 'ManufacturerModelName', 'SoftwareVersions',
|
|
63
|
+
'PixelSpacing', 'ImageDimensions', 'CorrectedImage', 'RadiopharmaceuticalInformationSequence', 'Radiopharmaceutical', 'ReconstructionMethod',
|
|
64
|
+
'AttenuationCorrectionMethod', 'RandomsCorrectionMethod', 'DecayCorrection',
|
|
65
|
+
'CollimatorType']] #list of keys which contain numerical values in PET acquisition parameters
|
|
66
|
+
ct_acquisition_dict = {'Manufacturer': [], 'SeriesDescription': [], 'ManufacturerModelName': [], 'SliceThickness': [], 'ScanOptions': [],
|
|
67
|
+
'KVP': [], 'SoftwareVersions': [], 'DataCollectionDiameter': [], 'ReconstructionDiameter': [], 'ReconstructionMethod': [], 'DistanceSourceToDetector': [],
|
|
68
|
+
'DistanceSourceToPatient': [], 'RotationDirection': [], 'ExposureTime': [], 'XRayTubeCurrent': [], 'Exposure': [], 'FilterType': [],
|
|
69
|
+
'GeneratorPower': [], 'FocalSpots': [], 'ConvolutionKernel': [], 'RevolutionTime': [],
|
|
70
|
+
'SingleCollimationWidth': [], 'TotalCollimationWidth': [], 'TableSpeed': [], 'TableFeedPerRotation': [], 'SpiralPitchFactor': [],
|
|
71
|
+
'PixelSpacing': [], 'SpacingBetweenSlices': [], 'ImageDimensions': []} #dictionary to store acquisition parameters for CT images
|
|
72
|
+
ct_paragraphs = [['Manufacturer', 'ManufacturerModelName', 'SoftwareVersions', 'ReconstructionMethod', 'ScanOptions'],
|
|
73
|
+
['DataCollectionDiameter', 'ReconstructionDiameter', 'DistanceSourceToDetector',
|
|
74
|
+
'DistanceSourceToPatient', 'ConvolutionKernel'], ['RevolutionTime', 'RotationDirection',
|
|
75
|
+
'SingleCollimationWidth', 'TotalCollimationWidth', 'TableSpeed', 'TableFeedPerRotation', 'SpiralPitchFactor'],
|
|
76
|
+
['KVP','ExposureTime', 'XRayTubeCurrent', 'Exposure', 'FilterType', 'GeneratorPower', 'FocalSpots'],
|
|
77
|
+
['SliceThickness', 'SpacingBetweenSlices', 'PixelSpacing', 'ImageDimensions']]
|
|
78
|
+
ct_numerical_keys = [key for key in ct_acquisition_dict.keys() if key not in ['Manufacturer', 'SeriesDescription', 'ManufacturerModelName', 'ScanOptions',
|
|
79
|
+
'ReconstructionMethod', 'SoftwareVersions', 'RotationDirection', 'FilterType', 'ConvolutionKernel', 'PixelSpacing', 'ImageDimensions', 'CorrectedImage']] #list of keys which contain numerical values in CT acquisition parameters
|
|
80
|
+
directories_list = list(np.transpose(np.array(directory_list)))
|
|
81
|
+
directories_list = [list(item) for item in directories_list]
|
|
82
|
+
for i, directories in enumerate(directories_list):
|
|
83
|
+
scan_type = scan_types[i]
|
|
84
|
+
|
|
85
|
+
# Initialize a fresh acquisition_dict for each scan_type
|
|
86
|
+
acquisition_dict = {}
|
|
87
|
+
numerical_keys = []
|
|
88
|
+
|
|
89
|
+
for j, directory in enumerate(directories):
|
|
90
|
+
dicom_path = os.path.join(directory, os.listdir(directory)[0]) #get the first dicom file in the folder path
|
|
91
|
+
ds = dicom.dcmread(dicom_path) #extract the dicom dataset from the dicom file
|
|
92
|
+
if ds['Modality'].value == 'MR' and j == 0: #check modality of dicom images and define acquisition dictionary and numerical keys appropriately
|
|
93
|
+
acquisition_dict = deepcopy(mri_acquisition_dict)
|
|
94
|
+
numerical_keys = mri_numerical_keys
|
|
95
|
+
paragraphs = mri_paragraphs
|
|
96
|
+
elif ds['Modality'].value == 'PT' and j == 0:
|
|
97
|
+
acquisition_dict = deepcopy(pet_acquisition_dict)
|
|
98
|
+
numerical_keys = pet_numerical_keys
|
|
99
|
+
paragraphs = pet_paragraphs
|
|
100
|
+
elif ds['Modality'].value == 'CT' and j == 0:
|
|
101
|
+
acquisition_dict = deepcopy(ct_acquisition_dict)
|
|
102
|
+
numerical_keys = ct_numerical_keys
|
|
103
|
+
paragraphs = ct_paragraphs
|
|
104
|
+
keys_list = [elem.keyword for elem in ds] #get the list of keys which are present in the dicom dataset
|
|
105
|
+
missing_keys = [key for key in acquisition_dict.keys() if key not in keys_list + ['ImageDimensions']] #check which keys are missing from the dicom dataset
|
|
106
|
+
for elem in ds:
|
|
107
|
+
if elem.keyword in acquisition_dict.keys():
|
|
108
|
+
acquisition_dict[elem.keyword].append(elem.value)
|
|
109
|
+
if missing_keys != []:
|
|
110
|
+
#if there are missing keys, append None to the dictionary
|
|
111
|
+
for key in missing_keys:
|
|
112
|
+
if key in numerical_keys:
|
|
113
|
+
acquisition_dict[key].append(np.nan)
|
|
114
|
+
else:
|
|
115
|
+
acquisition_dict[key].append(None) #append empty string for missing keys
|
|
116
|
+
shape = list(ds.pixel_array.shape) #get the shape of the pixel array for each slice
|
|
117
|
+
shape = [len(os.listdir(directory))] + shape #append the number of slices to the shape
|
|
118
|
+
acquisition_dict['ImageDimensions'].append(tuple(shape)) #append the shape to the dictionary
|
|
119
|
+
df = pd.DataFrame.from_dict(acquisition_dict) #convert the dictionary to a dataframe
|
|
120
|
+
for label, content in df.items():
|
|
121
|
+
if content.value_counts().to_string(name=False) != "Series([], )" and label not in numerical_keys:
|
|
122
|
+
df.loc[:, label] = content.replace(r'^\s*$', pd.NA, regex=True).fillna("Unspecified") #fill in missing categorical values
|
|
123
|
+
df.to_csv(scan_type + "_" + filename, index=False) #save the dataframe to a csv file
|
|
124
|
+
df = sf.clean_string_columns(df, numerical_keys)
|
|
125
|
+
# Apply to the entire column
|
|
126
|
+
if 'ContrastBolusAgent' in df.columns:
|
|
127
|
+
df['ContrastBolusAgent'] = df['ContrastBolusAgent'].apply(sf.format_contrast_label)
|
|
128
|
+
get_acquisition_text(df, scan_type, numerical_keys) #get the unique acquisition parameters and save it as a text file
|
|
129
|
+
lr.acquisition_latex(author, df, paragraphs, numerical_keys, sections=latex_sections, max_string_length=max_string_length, filename=scan_type+'_acquisition')
|
|
130
|
+
|
|
131
|
+
def write_acquisition_text(acquisition_df, numerical_keys, series_description = True, verbose = True):
|
|
132
|
+
"""
|
|
133
|
+
Extracts text of acquisition parameters from the dataframe
|
|
134
|
+
|
|
135
|
+
Parameters:
|
|
136
|
+
|
|
137
|
+
* acquisition_df (pandas.DataFrame): dataframe with acquisition parameters
|
|
138
|
+
|
|
139
|
+
* numerical_keys (list): list of keys containing numerical values
|
|
140
|
+
|
|
141
|
+
* series_description (boolean): if True, write SeriesDescription to file
|
|
142
|
+
|
|
143
|
+
* verbose (boolean): if True, write text with unique acquisition parameters and counts, otherwise summary statistics only are provided for numerical parameters
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
|
|
147
|
+
* acquisition_text (str): text with unique acquisition parameters and counts
|
|
148
|
+
"""
|
|
149
|
+
acquisition_text = "" #initialise text string
|
|
150
|
+
for label, content in acquisition_df.items(): #loop through each scan in dataframe
|
|
151
|
+
if label == 'SeriesDescription' and not series_description: #skip SeriesDescription if series_description is False
|
|
152
|
+
continue
|
|
153
|
+
if content.value_counts().to_string(name=False) != "Series([], )":
|
|
154
|
+
acquisition_text += (str(label) + ':\n') # Write the label of the acquisition parameter
|
|
155
|
+
if str(label) in numerical_keys and not verbose: #check if current label is numerical and verbose is False
|
|
156
|
+
content = content.apply(lambda x: str(x) if isinstance(x, dicom.sequence.Sequence) else x)
|
|
157
|
+
unique_values = content.dropna().unique()
|
|
158
|
+
if len(unique_values) == 1:
|
|
159
|
+
acquisition_text += (str(unique_values[0]) + '\n') # If there is only one unique value, write it
|
|
160
|
+
else:
|
|
161
|
+
#calculate mean, std, min and max for numerical values
|
|
162
|
+
mean = np.nanmean(content)
|
|
163
|
+
median = np.nanmedian(content)
|
|
164
|
+
std = np.nanstd(content)
|
|
165
|
+
min = np.nanmin(content)
|
|
166
|
+
max = np.nanmax(content)
|
|
167
|
+
#write mean, std, and range to the text
|
|
168
|
+
acquisition_text += ('Mean: ' + str(mean) + '\n')
|
|
169
|
+
acquisition_text += ('Median: ' + str(median) + '\n')
|
|
170
|
+
acquisition_text += ('Standard Deviation: ' + str(std) + '\n')
|
|
171
|
+
acquisition_text += ('Range: [' + str(min) + ' - ' + str(max) + ']\n')
|
|
172
|
+
else:
|
|
173
|
+
acquisition_text += (content.value_counts().to_string(name=False) + '\n') # Write the unique values and their counts
|
|
174
|
+
acquisition_text += '\n' # Add a newline for better readability
|
|
175
|
+
return acquisition_text
|
|
176
|
+
|
|
177
|
+
def get_acquisition_text(acquisition_df, scan_type, numerical_keys):
|
|
178
|
+
"""
|
|
179
|
+
Saves acquisition parameters from the dataframe to text files - both as a whole dataset and divided into scan types
|
|
180
|
+
|
|
181
|
+
Parameters:
|
|
182
|
+
|
|
183
|
+
* acquisition_df (pandas.DataFrame): dataframe with acquisition parameters
|
|
184
|
+
|
|
185
|
+
* scan_type (str): scan type to extract acquisition parameters for
|
|
186
|
+
|
|
187
|
+
* numerical_keys (list): list of keys containing numerical values
|
|
188
|
+
|
|
189
|
+
Output:
|
|
190
|
+
|
|
191
|
+
* text file detailing acquisition parameters from entire dataset, as well as a text file for each series description present
|
|
192
|
+
"""
|
|
193
|
+
descriptors = acquisition_df["SeriesDescription"].unique() #obtain list of unique series descriptions
|
|
194
|
+
for descriptor in descriptors: #loop through each descriptor
|
|
195
|
+
filtered_descriptor = descriptor.replace(":", "")
|
|
196
|
+
verbose_filename = sf.clean_filename(filtered_descriptor.replace(" ", "_") + "_verbose_acquisition_parameters.txt")
|
|
197
|
+
acquisition_filename = sf.clean_filename(filtered_descriptor.replace(" ", "_") + "_acquisition_parameters.txt")
|
|
198
|
+
df = acquisition_df[acquisition_df['SeriesDescription'] == descriptor] #filter dataframe to just have elements with that description
|
|
199
|
+
verbose_acquisition_text = ("Scan Type: " + str(descriptor) + ':\n') # Write the scan type
|
|
200
|
+
acquisition_text = ("Scan Type: " + str(descriptor) + ':\n') # Write the scan type
|
|
201
|
+
verbose_acquisition_text += write_acquisition_text(df, numerical_keys, series_description = False)
|
|
202
|
+
acquisition_text += write_acquisition_text(df, numerical_keys, series_description = False, verbose=False)
|
|
203
|
+
with open(verbose_filename, 'w') as f: # Save the verbose acquisition text to a file
|
|
204
|
+
f.write(verbose_acquisition_text) # Write the verbose acquisition text to the file
|
|
205
|
+
with open(acquisition_filename, 'w') as f: # Save the acquisition text to a file
|
|
206
|
+
f.write(acquisition_text) # Write the acquisition text to the file
|
|
207
|
+
#get text for entire dataset and save it
|
|
208
|
+
full_verbose_acquisition = write_acquisition_text(acquisition_df, numerical_keys)
|
|
209
|
+
full_acquisition = write_acquisition_text(acquisition_df, numerical_keys, verbose=False)
|
|
210
|
+
with open(scan_type + "_verbose_acquisition_parameters.txt", "w") as f:
|
|
211
|
+
f.write(full_verbose_acquisition)
|
|
212
|
+
with open(scan_type + "_acquisition_parameters.txt", "w") as f:
|
|
213
|
+
f.write(full_acquisition)
|