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.
Files changed (28) hide show
  1. radiomicsmodellingsuite-0.0.0/LICENSE +1 -0
  2. radiomicsmodellingsuite-0.0.0/MANIFEST.in +2 -0
  3. radiomicsmodellingsuite-0.0.0/PKG-INFO +151 -0
  4. radiomicsmodellingsuite-0.0.0/README.md +1 -0
  5. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/__init__.py +21 -0
  6. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/acquisition_parameters.py +213 -0
  7. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/data_processing.py +562 -0
  8. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/dataset_manipulation.py +484 -0
  9. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/exploratory_analysis.py +334 -0
  10. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/feature_extraction.py +767 -0
  11. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/feature_harmonisation.py +205 -0
  12. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/feature_selection.py +883 -0
  13. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/image_processing.py +303 -0
  14. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/latex_reporting.py +2153 -0
  15. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/model_calibration.py +701 -0
  16. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/model_evaluation.py +3387 -0
  17. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/model_explainability.py +802 -0
  18. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/model_training.py +508 -0
  19. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/radiomics_modelling_suite.py +97 -0
  20. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/setup.py +12 -0
  21. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite/string_formatting.py +64 -0
  22. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite.egg-info/PKG-INFO +151 -0
  23. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite.egg-info/SOURCES.txt +26 -0
  24. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite.egg-info/dependency_links.txt +1 -0
  25. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite.egg-info/requires.txt +134 -0
  26. radiomicsmodellingsuite-0.0.0/RadiomicsModellingSuite.egg-info/top_level.txt +1 -0
  27. radiomicsmodellingsuite-0.0.0/setup.cfg +4 -0
  28. radiomicsmodellingsuite-0.0.0/setup.py +21 -0
@@ -0,0 +1 @@
1
+ ��
@@ -0,0 +1,2 @@
1
+ include RadiomicsModellingSuite/config.yml
2
+ include README.md
@@ -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
+ 'Reconstruction​Diameter': [], '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
+ 'Reconstruction​Diameter', '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': [], 'Reconstruction​Diameter': [], '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', 'Reconstruction​Diameter', '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)