pumpia-uniform-phantom 0.0.1__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.
@@ -0,0 +1,118 @@
1
+ # Editor temporary/working/backup files #
2
+ #########################################
3
+ .#*
4
+ [#]*#
5
+ *~
6
+ *$
7
+ *.bak
8
+ *.diff
9
+ .idea/
10
+ *.iml
11
+ *.ipr
12
+ *.iws
13
+ *.org
14
+ .project
15
+ pmip
16
+ *.rej
17
+ .settings/
18
+ .*.sw[nop]
19
+ .sw[nop]
20
+ *.tmp
21
+ *.vim
22
+ .vscode
23
+ tags
24
+ cscope.out
25
+ # gnu global
26
+ GPATH
27
+ GRTAGS
28
+ GSYMS
29
+ GTAGS
30
+ .cache
31
+ .mypy_cache/
32
+
33
+ # Compiled source #
34
+ ###################
35
+ *.a
36
+ *.com
37
+ *.class
38
+ *.dll
39
+ *.exe
40
+ *.o
41
+ *.o.d
42
+ *.py[ocd]
43
+ *.so
44
+ *.mod
45
+ *.dist
46
+
47
+ # Packages #
48
+ ############
49
+ # it's better to unpack these files and commit the raw source
50
+ # git has its own built in compression methods
51
+ *.7z
52
+ *.bz2
53
+ *.bzip2
54
+ *.dmg
55
+ *.gz
56
+ *.iso
57
+ *.jar
58
+ *.rar
59
+ *.tar
60
+ *.tbz2
61
+ *.tgz
62
+ *.zip
63
+
64
+ # Python files #
65
+ ################
66
+ # meson build/installation directories
67
+ build
68
+ build-install
69
+ # meson python output
70
+ .mesonpy-native-file.ini
71
+ # sphinx build directory
72
+ _build
73
+ # dist directory is where sdist/wheel end up
74
+ dist
75
+ doc/build
76
+ doc/docenv
77
+ doc/cdoc/build
78
+ # Egg metadata
79
+ *.egg-info
80
+ # The shelf plugin uses this dir
81
+ ./.shelf
82
+ .cache
83
+ pip-wheel-metadata
84
+ .python-version
85
+
86
+ # Logs and databases #
87
+ ######################
88
+ *.log
89
+ *.sql
90
+ *.sqlite
91
+ *nuitka-crash-report.xml
92
+
93
+ # Patches #
94
+ ###########
95
+ *.patch
96
+ *.diff
97
+
98
+ # OS generated files #
99
+ ######################
100
+ .DS_Store*
101
+ .VolumeIcon.icns
102
+ .fseventsd
103
+ Icon?
104
+ .gdb_history
105
+ ehthumbs.db
106
+ Thumbs.db
107
+ .directory
108
+
109
+ # pytest generated files #
110
+ ##########################
111
+ /.pytest_cache
112
+
113
+ # doc build generated files #
114
+ #############################
115
+ doc/source/savefig/
116
+ doc/source/**/generated/
117
+ doc/source/release/notes-towncrier.rst
118
+ doc/source/.jupyterlite.doit.db
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Principle Five
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.4
2
+ Name: pumpia-uniform-phantom
3
+ Version: 0.0.1
4
+ Summary: Uniform MRI phantom analysis based using the PumpIA framework.
5
+ Project-URL: Homepage, https://www.principlefive.org.uk/
6
+ Project-URL: Documentation, https://github.com/Principle-Five/pumpia-uniform-phantom
7
+ Project-URL: Source, https://github.com/Principle-Five/pumpia-uniform-phantom
8
+ Project-URL: Issues, https://github.com/Principle-Five/pumpia-uniform-phantom/issues
9
+ Author: Zack Ravetz et al.
10
+ License-Expression: BSD-3-Clause
11
+ License-File: LICENSE
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: BSD License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Scientific/Engineering
20
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
21
+ Classifier: Topic :: Software Development
22
+ Requires-Python: >=3.12
23
+ Requires-Dist: matplotlib
24
+ Requires-Dist: numpy
25
+ Requires-Dist: pumpia==0.6.*,>=0.6.7
26
+ Requires-Dist: pylibjpeg[all]
27
+ Requires-Dist: scipy
28
+ Description-Content-Type: text/markdown
29
+
30
+ # Introduction
31
+ This repository contains code to analyse a uniform phantom using the [PumpIA](https://github.com/Principle-Five/pumpia) framework.
32
+ It uses the subtraction SNR method and therefore expects a repeat image, however the uniformity module will run with a single image.
33
+
34
+ It is currently not validated and is provided as is, see the license for more information.
35
+
36
+ The collection contains the following tests:
37
+ - SNR
38
+ - Uniformity
39
+
40
+ # Usage
41
+
42
+ Users should make themselves familiar with the [PumpIA user interface](https://principle-five.github.io/pumpia/usage/user_interface.html)
43
+
44
+ To run the collection:
45
+ 1. Clone the repository
46
+ 2. Use an environment manager to install the requirements from `requirements.txt` or install the requirements using the command `pip install -r requirements.txt` when in the repository directory
47
+ 3. Run the `run_uniform_rpt_collection.py` script
48
+
49
+ To use the collection:
50
+ 1. Load the folder with the relevant images
51
+ 2. Drag and drop the series containing the relevant images into the viewer of the `Main` tab
52
+ 3. Generate the ROIs and run analysis
53
+ 4. Correct the context if required (see below)
54
+ - Re-run generating ROIs
55
+ 5. Move any ROIs as required, this should be done through their relevant modules.
56
+ - Re-run analysis
57
+ 6. Copy the results in the relevant format. Horizontal is tab separated, vertical is new line separated.
58
+
59
+ ## Correcting Context
60
+
61
+ The context used for this collection is the Auto Phantom Context Manager provided with PumpIA, see the [relevant documentation](https://principle-five.github.io/pumpia/usage/user_interface.html) for this.
@@ -0,0 +1,32 @@
1
+ # Introduction
2
+ This repository contains code to analyse a uniform phantom using the [PumpIA](https://github.com/Principle-Five/pumpia) framework.
3
+ It uses the subtraction SNR method and therefore expects a repeat image, however the uniformity module will run with a single image.
4
+
5
+ It is currently not validated and is provided as is, see the license for more information.
6
+
7
+ The collection contains the following tests:
8
+ - SNR
9
+ - Uniformity
10
+
11
+ # Usage
12
+
13
+ Users should make themselves familiar with the [PumpIA user interface](https://principle-five.github.io/pumpia/usage/user_interface.html)
14
+
15
+ To run the collection:
16
+ 1. Clone the repository
17
+ 2. Use an environment manager to install the requirements from `requirements.txt` or install the requirements using the command `pip install -r requirements.txt` when in the repository directory
18
+ 3. Run the `run_uniform_rpt_collection.py` script
19
+
20
+ To use the collection:
21
+ 1. Load the folder with the relevant images
22
+ 2. Drag and drop the series containing the relevant images into the viewer of the `Main` tab
23
+ 3. Generate the ROIs and run analysis
24
+ 4. Correct the context if required (see below)
25
+ - Re-run generating ROIs
26
+ 5. Move any ROIs as required, this should be done through their relevant modules.
27
+ - Re-run analysis
28
+ 6. Copy the results in the relevant format. Horizontal is tab separated, vertical is new line separated.
29
+
30
+ ## Correcting Context
31
+
32
+ The context used for this collection is the Auto Phantom Context Manager provided with PumpIA, see the [relevant documentation](https://principle-five.github.io/pumpia/usage/user_interface.html) for this.
@@ -0,0 +1,192 @@
1
+ """
2
+ Subtraction SNR module for uniform phantom
3
+ """
4
+ import math
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+
8
+ from pumpia.module_handling.modules import PhantomModule
9
+ from pumpia.module_handling.fields.roi_fields import GeneralROIField
10
+ from pumpia.module_handling.fields.viewer_fields import MonochromeDicomViewerField
11
+ from pumpia.module_handling.fields.simple import (PercField,
12
+ FloatField,
13
+ BoolField)
14
+ from pumpia.image_handling.roi_structures import EllipseROI, RectangleROI
15
+ from pumpia.file_handling.dicom_structures import Series, Instance
16
+ from pumpia.file_handling.dicom_tags import MRTags
17
+ from pumpia.module_handling.context import PhantomContext
18
+ from pumpia.widgets.context_managers import AutoPhantomManager
19
+
20
+
21
+ class SubSNR(PhantomModule):
22
+ """
23
+ Module for subtraction method SNR on uniform phantom.
24
+ """
25
+ context_manager = AutoPhantomManager()
26
+ show_draw_rois_button = True
27
+ show_analyse_button = True
28
+ title = "Subtraction SNR"
29
+
30
+ viewer1 = MonochromeDicomViewerField(row=0, column=0)
31
+ viewer2 = MonochromeDicomViewerField(row=0, column=1, allow_changing_rois=False)
32
+
33
+ size = PercField(70, verbose_name="Size (%)")
34
+ ref_bandwidth = FloatField(1, verbose_name="Reference Bandwidth (Hz/px)")
35
+ bw_cor_bool = BoolField(verbose_name="Bandwidth Correction")
36
+ pix_size_bool = BoolField(verbose_name="Pixel Size Correction")
37
+ avg_cor_bool = BoolField(verbose_name="Averages Correction")
38
+ pe_cor_bool = BoolField(verbose_name="Phase Encode Correction")
39
+
40
+ im_bw = FloatField(verbose_name="Image Bandwidth (Hz/px)", read_only=True)
41
+ pixel_size_cor = FloatField(verbose_name="Pixel Size Correction", read_only=True)
42
+ pe_cor = FloatField(verbose_name="Phase Encode Correction", read_only=True)
43
+ avg_cor = FloatField(verbose_name="Averages Correction", read_only=True)
44
+ signal = FloatField(read_only=True)
45
+ noise = FloatField(read_only=True)
46
+ snr = FloatField(verbose_name="SNR", read_only=True)
47
+ cor_snr = FloatField(verbose_name="Corrected SNR", read_only=True)
48
+
49
+ signal_roi1 = GeneralROIField("SNR ROI1", default_type="ROI rectangle")
50
+ signal_roi2 = GeneralROIField("SNR ROI2", allow_manual_draw=False)
51
+
52
+ def draw_rois(self, context: PhantomContext, batch: bool = False) -> None:
53
+ if isinstance(self.viewer1.image, (Instance, Series)):
54
+ factor = self.size / 100
55
+ if context.shape == "rectangle":
56
+ xmin = round(context.xcent - (factor * context.x_length / 2))
57
+ xmax = round(context.xcent + (factor * context.x_length / 2))
58
+ ymin = round(context.ycent - (factor * context.y_length / 2))
59
+ ymax = round(context.ycent + (factor * context.y_length / 2))
60
+ self.signal_roi1.register_roi(RectangleROI(self.viewer1.image,
61
+ xmin,
62
+ ymin,
63
+ xmax - xmin,
64
+ ymax - ymin,
65
+ slice_num=self.viewer1.current_slice))
66
+ else:
67
+ a = round(factor * context.x_length / 2)
68
+ b = round(factor * context.y_length / 2)
69
+ self.signal_roi1.register_roi(EllipseROI(self.viewer1.image,
70
+ round(context.xcent),
71
+ round(context.ycent),
72
+ a,
73
+ b,
74
+ slice_num=self.viewer1.current_slice))
75
+
76
+ def post_roi_register(self, roi_input: GeneralROIField):
77
+ if (roi_input == self.signal_roi1
78
+ and self.signal_roi1.roi is not None):
79
+ if self.manager is not None:
80
+ self.manager.add_roi(self.signal_roi1.roi)
81
+
82
+ if isinstance(self.viewer2.image, (Instance, Series)):
83
+ image = self.viewer2.image
84
+ roi = self.signal_roi1.roi.copy_to_image(image,
85
+ image.current_slice,
86
+ "SNR ROI",
87
+ True)
88
+ self.signal_roi2.register_roi(roi)
89
+ if (self.signal_roi2.roi is not None
90
+ and self.manager is not None):
91
+ self.manager.add_roi(self.signal_roi2.roi)
92
+
93
+ def link_rois_viewers(self):
94
+ self.signal_roi1.viewer = self.viewer1
95
+ self.signal_roi2.viewer = self.viewer2
96
+
97
+ def analyse(self, batch: bool = False):
98
+ if self.signal_roi1.roi is not None and self.signal_roi2.roi is not None:
99
+ roi1 = self.signal_roi1.roi
100
+ roi2 = self.signal_roi2.roi
101
+ roi_sum = roi1.pixel_values + roi2.pixel_values
102
+ roi_sub = np.array(roi1.pixel_values) - np.array(roi2.pixel_values)
103
+ sum_roi = np.mean(roi_sum)
104
+ if isinstance(sum_roi, float):
105
+ self.signal = sum_roi
106
+ roi_noise = np.std(roi_sub) / math.sqrt(2)
107
+ if isinstance(roi_noise, float):
108
+ self.noise = roi_noise
109
+ snr = sum_roi / roi_noise
110
+ if isinstance(snr, float):
111
+ self.snr = snr
112
+
113
+ cor_snr = snr
114
+
115
+ image = roi1.image
116
+ if isinstance(image, Instance):
117
+ px_cor = 1
118
+ bw_cor = 1
119
+ avg_cor = 1
120
+ pe_cor = 1
121
+
122
+ if (self.pix_size_bool
123
+ and not (image.slice_thickness is None
124
+ or image.pixel_spacing is None)):
125
+ pix_size = image.slice_thickness, *image.pixel_spacing
126
+ self.logger.info("pixel size = %s", pix_size)
127
+ px_cor = 1 / math.prod(pix_size)
128
+ self.pixel_size_cor = px_cor
129
+
130
+ if self.bw_cor_bool:
131
+ ref_bw = self.ref_bandwidth
132
+ try:
133
+ im_bw = image.get_tag(MRTags.PixelBandwidth)
134
+ try:
135
+ im_bw = float(im_bw) # type: ignore
136
+ except (ValueError, TypeError):
137
+ im_bw = ref_bw
138
+ except KeyError:
139
+ im_bw = ref_bw
140
+ self.im_bw = im_bw
141
+ bw_cor = math.sqrt(im_bw / ref_bw)
142
+
143
+ if self.avg_cor_bool:
144
+ try:
145
+ im_av = image.get_tag(MRTags.NumberOfAverages)
146
+ try:
147
+ im_av = float(im_av) # type: ignore
148
+ except (ValueError, TypeError):
149
+ im_av = 1
150
+ except KeyError:
151
+ im_av = 1
152
+ avg_cor = 1 / math.sqrt(im_av)
153
+ self.avg_cor = avg_cor
154
+
155
+ if self.pe_cor_bool:
156
+ try:
157
+ im_pe = float(
158
+ image.get_tag(MRTags.NumberOfPhaseEncodingSteps)) # type: ignore
159
+ except (KeyError, ValueError, TypeError):
160
+ im_pe = 1
161
+
162
+ if im_pe == 1:
163
+ try:
164
+ if image.get_tag(MRTags.InPlanePhaseEncodingDirection) == "ROW":
165
+ num = int(
166
+ image.get_tag(MRTags.Rows)) # type: ignore
167
+ else:
168
+ num = int(
169
+ image.get_tag(MRTags.Columns)) # type: ignore
170
+ im_pe = float(
171
+ image.get_tag(MRTags.PercentSampling)) * num # type: ignore
172
+ except (KeyError, ValueError, TypeError):
173
+ im_pe = 1
174
+
175
+ pe_cor = 1 / math.sqrt(im_pe)
176
+ self.pe_cor = pe_cor
177
+
178
+ cor_snr = snr * px_cor * bw_cor * avg_cor * pe_cor
179
+
180
+ self.cor_snr = float(cor_snr)
181
+
182
+ def load_commands(self):
183
+ self.register_command("Show Subtraction Image", self.show_sub_image)
184
+
185
+ def show_sub_image(self):
186
+ if self.signal_roi1.roi is not None and self.signal_roi2.roi is not None:
187
+ roi1 = self.signal_roi1.roi
188
+ roi2 = self.signal_roi2.roi
189
+ sub_array = roi1.image.array[0] - roi2.image.array[0]
190
+ plt.imshow(sub_array, cmap='grey')
191
+ plt.colorbar()
192
+ plt.show()
@@ -0,0 +1,86 @@
1
+ """
2
+ Integral uniformity module for uniform phantom
3
+ """
4
+ import numpy as np
5
+ from scipy.signal import convolve2d
6
+
7
+ from pumpia.module_handling.modules import PhantomModule
8
+ from pumpia.module_handling.fields.roi_fields import GeneralROIField
9
+ from pumpia.module_handling.fields.viewer_fields import MonochromeDicomViewerField
10
+ from pumpia.module_handling.fields.simple import (PercField,
11
+ BoolField,
12
+ FloatField)
13
+ from pumpia.image_handling.roi_structures import EllipseROI, RectangleROI
14
+ from pumpia.file_handling.dicom_structures import Series, Instance
15
+ from pumpia.module_handling.context import PhantomContext
16
+ from pumpia.widgets.context_managers import AutoPhantomManager
17
+
18
+ LOW_PASS_KERNEL = np.array([[1, 2, 1], [2, 4, 2], [1, 2, 1]]) / 16
19
+
20
+
21
+ class Uniformity(PhantomModule):
22
+ """
23
+ Integral uniformity module for uniform phantom.
24
+ """
25
+ context_manager = AutoPhantomManager()
26
+ show_draw_rois_button = True
27
+ show_analyse_button = True
28
+ title = "Uniformity"
29
+
30
+ viewer = MonochromeDicomViewerField(row=0, column=0)
31
+
32
+ size = PercField(70, verbose_name="Size (%)")
33
+ kernel_bool = BoolField(verbose_name="Apply Low Pass Kernel")
34
+
35
+ uniformity = FloatField(verbose_name="Uniformity (%)", read_only=True)
36
+
37
+ uniformity_roi = GeneralROIField("Uniformity ROI", default_type="ROI rectangle")
38
+
39
+ def draw_rois(self, context: PhantomContext, batch: bool = False) -> None:
40
+ if isinstance(self.viewer.image, (Instance, Series)):
41
+ factor = self.size / 100
42
+ if context.shape == "rectangle":
43
+ xmin = round(context.xcent - (factor * context.x_length / 2))
44
+ xmax = round(context.xcent + (factor * context.x_length / 2))
45
+ ymin = round(context.ycent - (factor * context.y_length / 2))
46
+ ymax = round(context.ycent + (factor * context.y_length / 2))
47
+ self.uniformity_roi.register_roi(RectangleROI(self.viewer.image,
48
+ xmin,
49
+ ymin,
50
+ xmax - xmin,
51
+ ymax - ymin,
52
+ slice_num=self.viewer.current_slice))
53
+ else:
54
+ a = round(factor * context.x_length / 2)
55
+ b = round(factor * context.y_length / 2)
56
+ self.uniformity_roi.register_roi(EllipseROI(self.viewer.image,
57
+ round(context.xcent),
58
+ round(context.ycent),
59
+ a,
60
+ b,
61
+ slice_num=self.viewer.current_slice))
62
+
63
+ def post_roi_register(self, roi_input: GeneralROIField):
64
+ if (roi_input == self.uniformity_roi
65
+ and self.uniformity_roi.roi is not None
66
+ and self.manager is not None):
67
+ self.manager.add_roi(self.uniformity_roi.roi)
68
+
69
+ def link_rois_viewers(self):
70
+ self.uniformity_roi.viewer = self.viewer
71
+
72
+ def analyse(self, batch: bool = False):
73
+ if self.uniformity_roi.roi is not None:
74
+ roi = self.uniformity_roi.roi
75
+ if self.kernel_bool:
76
+ array = roi.image.array[0]
77
+ array = convolve2d(array, LOW_PASS_KERNEL, mode="same")
78
+ mask = roi.mask
79
+ pixel_values = list(array[mask])
80
+ else:
81
+ pixel_values = roi.pixel_values
82
+
83
+ max_val = max(pixel_values)
84
+ min_val = min(pixel_values)
85
+ uniformity = 100 * (1 - ((max_val - min_val) / (max_val + min_val))) # type: ignore
86
+ self.uniformity = uniformity
@@ -0,0 +1,66 @@
1
+ """
2
+ Collection for uniform phantom with repeat images.
3
+ """
4
+
5
+ from pumpia.module_handling.collections import ModuleGroup, BaseCollection
6
+ from pumpia.module_handling.fields.windows import FieldWindow
7
+ from pumpia.module_handling.fields.groups import FieldGroup
8
+ from pumpia.module_handling.fields.viewer_fields import MonochromeDicomViewerField
9
+ from pumpia.widgets.viewers import MonochromeDicomViewer
10
+ from pumpia.widgets.context_managers import AutoPhantomManager
11
+
12
+ from pumpia_uniform_phantom.modules.sub_snr import SubSNR
13
+ from pumpia_uniform_phantom.modules.uniformity import Uniformity
14
+
15
+
16
+ class RepeatImagesCollection(BaseCollection):
17
+ """
18
+ Collection for uniform phantom with repeated scans.
19
+ """
20
+ context_manager = AutoPhantomManager()
21
+ title = "Uniform Phantom Repeat Collection"
22
+
23
+ viewer1 = MonochromeDicomViewerField(row=0, column=0)
24
+ viewer2 = MonochromeDicomViewerField(row=0, column=1)
25
+
26
+ snr = SubSNR(verbose_name="SNR")
27
+
28
+ uniformity1 = Uniformity(verbose_name="Uniformity")
29
+ uniformity2 = Uniformity(verbose_name="Uniformity")
30
+
31
+ snr_output = FieldWindow(snr.fields.signal,
32
+ snr.fields.noise,
33
+ snr.fields.snr,
34
+ snr.fields.cor_snr,
35
+ verbose_name="SNR Output")
36
+ image1_output = FieldWindow(uniformity1.fields.uniformity, verbose_name="Image 1 Results")
37
+ image2_output = FieldWindow(uniformity2.fields.uniformity, verbose_name="Image 2 Results")
38
+ full_results = FieldWindow(snr.fields.signal,
39
+ snr.fields.noise,
40
+ snr.fields.snr,
41
+ snr.fields.cor_snr,
42
+ uniformity1.fields.uniformity,
43
+ uniformity2.fields.uniformity,
44
+ field_names=[None,
45
+ None,
46
+ None,
47
+ None,
48
+ "Image 1 Uniformity",
49
+ "Image 2 Uniformity"])
50
+
51
+ size_group = FieldGroup(uniformity1.fields.size, uniformity2.fields.size)
52
+ kernel_group = FieldGroup(uniformity1.fields.kernel_bool, uniformity2.fields.kernel_bool)
53
+
54
+ uniformity_window = ModuleGroup(uniformity1, uniformity2, verbose_name="Uniformity")
55
+
56
+ def on_image_load(self, viewer: MonochromeDicomViewer) -> None:
57
+ if viewer is self.viewer1:
58
+ if self.viewer1.image is not None:
59
+ image = self.viewer1.image
60
+ self.snr.viewer1.load_image(image)
61
+ self.uniformity1.viewer.load_image(image)
62
+ elif viewer is self.viewer2:
63
+ if self.viewer2.image is not None:
64
+ image = self.viewer2.image
65
+ self.snr.viewer2.load_image(image)
66
+ self.uniformity2.viewer.load_image(image)
@@ -0,0 +1,9 @@
1
+ from pumpia_uniform_phantom.repeat.collection import RepeatImagesCollection
2
+
3
+
4
+ def run_repeat_uniform():
5
+ RepeatImagesCollection.run()
6
+
7
+
8
+ if __name__ == "__main__":
9
+ run_repeat_uniform()
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pumpia-uniform-phantom"
7
+ version = "0.0.1"
8
+ authors = [{ name = "Zack Ravetz et al." }]
9
+ description = "Uniform MRI phantom analysis based using the PumpIA framework."
10
+ readme = "README.md"
11
+ requires-python = ">=3.12"
12
+ dependencies = [
13
+ "pumpia == 0.6.*, >=0.6.7",
14
+ "scipy",
15
+ "numpy",
16
+ "pylibjpeg[all]",
17
+ "matplotlib",
18
+ ]
19
+ classifiers = [
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3 :: Only",
22
+ 'Programming Language :: Python :: 3.12',
23
+ 'Programming Language :: Python :: 3.13',
24
+ 'Programming Language :: Python :: 3.14',
25
+ "Intended Audience :: Science/Research",
26
+ 'License :: OSI Approved :: BSD License',
27
+ 'Topic :: Software Development',
28
+ 'Topic :: Scientific/Engineering',
29
+ 'Topic :: Scientific/Engineering :: Image Processing',
30
+ ]
31
+
32
+ license = "BSD-3-Clause"
33
+ license-files = ["LICENSE"]
34
+
35
+ [project.urls]
36
+ Homepage = "https://www.principlefive.org.uk/"
37
+ Documentation = "https://github.com/Principle-Five/pumpia-uniform-phantom"
38
+ Source = "https://github.com/Principle-Five/pumpia-uniform-phantom"
39
+ Issues = "https://github.com/Principle-Five/pumpia-uniform-phantom/issues"
40
+
41
+ [tool.hatch.build.targets.sdist]
42
+ include = ["pumpia_uniform_phantom*"]
43
+
44
+ [project.gui-scripts]
45
+ pumpia-uniform-phantom = "pumpia_uniform_phantom.scripts.run_uniform_rpt:run_repeat_uniform"