petpal 0.5.6__py3-none-any.whl → 0.5.8__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.
- petpal/cli/cli_pib_processing.py +2 -3
- petpal/cli/cli_preproc.py +64 -33
- petpal/cli/cli_vat_processing.py +3 -2
- petpal/pipelines/pipelines.py +0 -3
- petpal/pipelines/preproc_steps.py +8 -7
- petpal/preproc/__init__.py +2 -0
- petpal/preproc/decay_correction.py +11 -10
- petpal/preproc/image_operations_4d.py +13 -177
- petpal/preproc/motion_corr.py +31 -112
- petpal/preproc/motion_target.py +90 -0
- petpal/preproc/regional_tac_extraction.py +3 -3
- petpal/preproc/register.py +4 -11
- petpal/preproc/segmentation_tools.py +5 -5
- petpal/preproc/standard_uptake_value.py +159 -0
- petpal/utils/scan_timing.py +66 -19
- petpal/utils/stats.py +21 -0
- petpal/utils/useful_functions.py +106 -5
- {petpal-0.5.6.dist-info → petpal-0.5.8.dist-info}/METADATA +1 -1
- {petpal-0.5.6.dist-info → petpal-0.5.8.dist-info}/RECORD +22 -20
- {petpal-0.5.6.dist-info → petpal-0.5.8.dist-info}/WHEEL +0 -0
- {petpal-0.5.6.dist-info → petpal-0.5.8.dist-info}/entry_points.txt +0 -0
- {petpal-0.5.6.dist-info → petpal-0.5.8.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for functions calculating standard uptake value (SUV) and related measures, such as standard
|
|
3
|
+
uptake value ratio (SUVR).
|
|
4
|
+
"""
|
|
5
|
+
import ants
|
|
6
|
+
|
|
7
|
+
from ..utils.stats import mean_value_in_region
|
|
8
|
+
from ..utils.math_lib import weighted_sum_computation
|
|
9
|
+
from ..utils.useful_functions import gen_3d_img_from_timeseries, nearest_frame_to_timepoint
|
|
10
|
+
from ..utils.image_io import (get_half_life_from_nifti,
|
|
11
|
+
load_metadata_for_nifti_with_same_filename,
|
|
12
|
+
safe_copy_meta)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def weighted_sum_for_suv(input_image_path: str,
|
|
16
|
+
output_image_path: str | None,
|
|
17
|
+
start_time: float=0,
|
|
18
|
+
end_time: float=-1) -> ants.ANTsImage:
|
|
19
|
+
"""Function that calculates the weighted series sum for a PET image specifically for
|
|
20
|
+
calculating the standard uptake value (SUV) of the image.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
input_image_path (str): Path to a 4D PET image which we calculate the sum on.
|
|
24
|
+
output_image_path (str): Path to which output image is saved. If None, returns
|
|
25
|
+
calculated image without saving.
|
|
26
|
+
start_time: Time in seconds from the start of the scan from which to begin sum calculation.
|
|
27
|
+
Only frames after selected time will be included in the sum. Default 0.
|
|
28
|
+
end_time: Time in seconds from the start of the scan from which to end sum calculation.
|
|
29
|
+
Only frames before selected time will be included in the sum. If -1, use all frames
|
|
30
|
+
after `start_time` in the calculation. Default -1.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
weighted_sum_img (ants.ANTsImage): 3D image resulting from the sum calculation.
|
|
34
|
+
"""
|
|
35
|
+
half_life = get_half_life_from_nifti(image_path=input_image_path)
|
|
36
|
+
if half_life <= 0:
|
|
37
|
+
raise ValueError('(ImageOps4d): Radioisotope half life is zero or negative.')
|
|
38
|
+
pet_meta = load_metadata_for_nifti_with_same_filename(input_image_path)
|
|
39
|
+
pet_img = ants.image_read(input_image_path)
|
|
40
|
+
frame_start = pet_meta['FrameTimesStart']
|
|
41
|
+
frame_duration = pet_meta['FrameDuration']
|
|
42
|
+
|
|
43
|
+
if 'DecayCorrectionFactor' in pet_meta.keys():
|
|
44
|
+
decay_correction = pet_meta['DecayCorrectionFactor']
|
|
45
|
+
elif 'DecayFactor' in pet_meta.keys():
|
|
46
|
+
decay_correction = pet_meta['DecayFactor']
|
|
47
|
+
else:
|
|
48
|
+
raise ValueError("Neither 'DecayCorrectionFactor' nor 'DecayFactor' exist in meta-data "
|
|
49
|
+
"file")
|
|
50
|
+
|
|
51
|
+
last_frame_time = frame_start[-1]
|
|
52
|
+
if end_time!=-1:
|
|
53
|
+
last_frame_time = end_time
|
|
54
|
+
scan_start = frame_start[0]
|
|
55
|
+
nearest_frame = nearest_frame_to_timepoint(frame_times=frame_start)
|
|
56
|
+
calc_first_frame = int(nearest_frame(start_time+scan_start))
|
|
57
|
+
calc_last_frame = int(nearest_frame(last_frame_time+scan_start))
|
|
58
|
+
if calc_first_frame==calc_last_frame:
|
|
59
|
+
calc_last_frame += 1
|
|
60
|
+
pet_series_adjusted = pet_img[:,:,:,calc_first_frame:calc_last_frame]
|
|
61
|
+
frame_start_adjusted = frame_start[calc_first_frame:calc_last_frame]
|
|
62
|
+
frame_duration_adjusted = frame_duration[calc_first_frame:calc_last_frame]
|
|
63
|
+
decay_correction_adjusted = decay_correction[calc_first_frame:calc_last_frame]
|
|
64
|
+
|
|
65
|
+
weighted_sum_arr = weighted_sum_computation(frame_duration=frame_duration_adjusted,
|
|
66
|
+
half_life=half_life,
|
|
67
|
+
pet_series=pet_series_adjusted,
|
|
68
|
+
frame_start=frame_start_adjusted,
|
|
69
|
+
decay_correction=decay_correction_adjusted)
|
|
70
|
+
weighted_sum_img = ants.from_numpy_like(weighted_sum_arr,gen_3d_img_from_timeseries(pet_img))
|
|
71
|
+
|
|
72
|
+
if output_image_path is not None:
|
|
73
|
+
ants.image_write(weighted_sum_img, output_image_path)
|
|
74
|
+
safe_copy_meta(input_image_path=input_image_path,
|
|
75
|
+
out_image_path=output_image_path)
|
|
76
|
+
|
|
77
|
+
return weighted_sum_img
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def suv(input_image_path: str,
|
|
81
|
+
output_image_path: str | None,
|
|
82
|
+
weight: float,
|
|
83
|
+
dose: float,
|
|
84
|
+
start_time: float,
|
|
85
|
+
end_time: float) -> ants.ANTsImage:
|
|
86
|
+
"""Compute standard uptake value (SUV) over a pet image. Calculate the weighted image sum
|
|
87
|
+
then divide by the dose and multiplying by the weight of the participant.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
input_image_path: Path to input PET image.
|
|
91
|
+
output_image_path: Path to which SUV image is saved.
|
|
92
|
+
weight: Weight of the participant in kg.
|
|
93
|
+
dose: Dose injected during the scan in MBq.
|
|
94
|
+
start_time: Start time for the SUV calculation in seconds.
|
|
95
|
+
end_time: End time for the SUV calculation in seconds.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
suv_img (ants.ANTsImage): The standard uptake value calculated on the input image."""
|
|
99
|
+
wss_img = weighted_sum_for_suv(input_image_path=input_image_path,
|
|
100
|
+
output_image_path=None,
|
|
101
|
+
start_time=start_time,
|
|
102
|
+
end_time=end_time)
|
|
103
|
+
suv_img = wss_img / (dose*1000) * weight
|
|
104
|
+
|
|
105
|
+
if output_image_path is not None:
|
|
106
|
+
ants.image_write(suv_img, output_image_path)
|
|
107
|
+
safe_copy_meta(input_image_path=input_image_path,
|
|
108
|
+
out_image_path=output_image_path)
|
|
109
|
+
|
|
110
|
+
return suv_img
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def suvr(input_image_path: str,
|
|
114
|
+
output_image_path: str | None,
|
|
115
|
+
segmentation_image_path: str,
|
|
116
|
+
ref_region: int | list[int],
|
|
117
|
+
start_time: float,
|
|
118
|
+
end_time: float) -> ants.ANTsImage:
|
|
119
|
+
"""
|
|
120
|
+
Computes an ``SUVR`` (Standard Uptake Value Ratio) by taking the average of
|
|
121
|
+
an input image within a reference region, and dividing the input image by
|
|
122
|
+
said average value.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
input_image_path (str): Path to 4D PET image.
|
|
126
|
+
output_image_path (str): Path to output image file which is written to. If None, no output
|
|
127
|
+
is written.
|
|
128
|
+
segmentation_image_path (str): Path to segmentation image, which we use
|
|
129
|
+
to compute average uptake value in the reference region.
|
|
130
|
+
ref_region (int): Region or list of region mappings over which to compute average SUV. If a
|
|
131
|
+
list is provided, combines all regions in the list as one reference region.
|
|
132
|
+
start_time: Time in seconds from the start of the scan from which to begin sum calculation.
|
|
133
|
+
Only frames after selected time will be included in the sum. Default 0.
|
|
134
|
+
end_time: Time in seconds from the start of the scan from which to end sum calculation.
|
|
135
|
+
Only frames before selected time will be included in the sum. If -1, use all frames
|
|
136
|
+
after `start_time` in the calculation. Default -1.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
ants.ANTsImage: SUVR parametric image
|
|
140
|
+
"""
|
|
141
|
+
sum_img = weighted_sum_for_suv(input_image_path=input_image_path,
|
|
142
|
+
output_image_path=None,
|
|
143
|
+
start_time=start_time,
|
|
144
|
+
end_time=end_time)
|
|
145
|
+
segmentation_img = ants.image_read(filename=segmentation_image_path)
|
|
146
|
+
|
|
147
|
+
ref_region_avg = mean_value_in_region(input_img=sum_img,
|
|
148
|
+
seg_img=segmentation_img,
|
|
149
|
+
mappings=ref_region)
|
|
150
|
+
|
|
151
|
+
suvr_img = sum_img / ref_region_avg
|
|
152
|
+
|
|
153
|
+
if output_image_path is not None:
|
|
154
|
+
ants.image_write(image=suvr_img,
|
|
155
|
+
filename=output_image_path)
|
|
156
|
+
safe_copy_meta(input_image_path=input_image_path,
|
|
157
|
+
out_image_path=output_image_path)
|
|
158
|
+
|
|
159
|
+
return suvr_img
|
petpal/utils/scan_timing.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Module to handle timing information of PET scans.
|
|
3
3
|
"""
|
|
4
|
-
import
|
|
4
|
+
from typing import Self
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
@@ -82,8 +82,8 @@ class ScanTimingInfo:
|
|
|
82
82
|
|
|
83
83
|
"""
|
|
84
84
|
duration: np.ndarray[float]
|
|
85
|
-
end: np.ndarray[float]
|
|
86
85
|
start: np.ndarray[float]
|
|
86
|
+
end: np.ndarray[float]
|
|
87
87
|
center: np.ndarray[float]
|
|
88
88
|
decay: np.ndarray[float]
|
|
89
89
|
|
|
@@ -130,7 +130,7 @@ class ScanTimingInfo:
|
|
|
130
130
|
|
|
131
131
|
|
|
132
132
|
@classmethod
|
|
133
|
-
def from_metadata(cls, metadata_dict: dict):
|
|
133
|
+
def from_metadata(cls, metadata_dict: dict) -> Self:
|
|
134
134
|
r"""
|
|
135
135
|
Extracts frame timing information and decay factors from a json metadata.
|
|
136
136
|
Expects that the JSON metadata has ``FrameDuration`` and ``DecayFactor`` or
|
|
@@ -155,13 +155,13 @@ class ScanTimingInfo:
|
|
|
155
155
|
"""
|
|
156
156
|
frm_dur = np.asarray(metadata_dict['FrameDuration'], float)
|
|
157
157
|
try:
|
|
158
|
-
|
|
158
|
+
frm_starts = np.asarray(metadata_dict['FrameTimesStart'], float)
|
|
159
159
|
except KeyError:
|
|
160
|
-
|
|
160
|
+
frm_starts = np.cumsum(frm_dur)-frm_dur
|
|
161
161
|
try:
|
|
162
|
-
|
|
162
|
+
frm_ends = np.asarray(metadata_dict['FrameTimesEnd'], float)
|
|
163
163
|
except KeyError:
|
|
164
|
-
|
|
164
|
+
frm_ends = frm_starts+frm_dur
|
|
165
165
|
try:
|
|
166
166
|
decay = np.asarray(metadata_dict['DecayCorrectionFactor'], float)
|
|
167
167
|
except KeyError:
|
|
@@ -178,7 +178,7 @@ class ScanTimingInfo:
|
|
|
178
178
|
decay=decay)
|
|
179
179
|
|
|
180
180
|
@classmethod
|
|
181
|
-
def from_nifti(cls, image_path: str):
|
|
181
|
+
def from_nifti(cls, image_path: str) -> Self:
|
|
182
182
|
r"""
|
|
183
183
|
Extracts frame timing information and decay factors from a NIfTI image metadata.
|
|
184
184
|
Expects that the JSON metadata file has ``FrameDuration`` and ``DecayFactor`` or
|
|
@@ -204,6 +204,49 @@ class ScanTimingInfo:
|
|
|
204
204
|
_meta_data = load_metadata_for_nifti_with_same_filename(image_path=image_path)
|
|
205
205
|
return cls.from_metadata(metadata_dict=_meta_data)
|
|
206
206
|
|
|
207
|
+
@classmethod
|
|
208
|
+
def from_start_end(cls,
|
|
209
|
+
frame_starts: np.ndarray,
|
|
210
|
+
frame_ends: np.ndarray,
|
|
211
|
+
decay_correction_factor: np.ndarray | None=None) -> Self:
|
|
212
|
+
"""Infer timing properties based on start and end time.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
frame_starts (np.ndarray): Start time of each frame.
|
|
216
|
+
frame_ends (np.ndarray): End time of each frame.
|
|
217
|
+
decay_correction_factor (np.ndarray | None): Decay correction factor, which can be
|
|
218
|
+
optionally provided based on the type of analysis being done. If None, frame decay
|
|
219
|
+
will be set to ones. Default None.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
scan_timing_info (ScanTimingInfo): ScanTimingInfo object with the correct start, end,
|
|
223
|
+
duration, midpoint, and (optionally) decay correction for each frame.
|
|
224
|
+
|
|
225
|
+
Raises:
|
|
226
|
+
ValueError: If frame_starts, frame_ends, and decay_correction_factor (if provided) are
|
|
227
|
+
not of identical shape.
|
|
228
|
+
|
|
229
|
+
"""
|
|
230
|
+
if frame_starts.shape != frame_ends.shape:
|
|
231
|
+
raise ValueError("frame_ends must have the same shape as frame_starts")
|
|
232
|
+
|
|
233
|
+
frame_duration = frame_ends - frame_starts
|
|
234
|
+
frame_midpoint = frame_starts + frame_duration / 2
|
|
235
|
+
frame_decay = np.ones_like(frame_starts)
|
|
236
|
+
|
|
237
|
+
if decay_correction_factor is None:
|
|
238
|
+
frame_decay = np.ones_like(frame_starts, dtype=float)
|
|
239
|
+
else:
|
|
240
|
+
frame_decay = np.asarray(decay_correction_factor, dtype=float)
|
|
241
|
+
if frame_decay.shape != frame_starts.shape:
|
|
242
|
+
raise ValueError("decay_correction_factor must have the same shape as frame_starts")
|
|
243
|
+
|
|
244
|
+
return cls(duration=frame_duration,
|
|
245
|
+
start=frame_starts,
|
|
246
|
+
end=frame_ends,
|
|
247
|
+
center=frame_midpoint,
|
|
248
|
+
decay=frame_decay)
|
|
249
|
+
|
|
207
250
|
|
|
208
251
|
def get_window_index_pairs_from_durations(frame_durations: np.ndarray, w_size: float):
|
|
209
252
|
r"""
|
|
@@ -263,25 +306,29 @@ def get_window_index_pairs_for_image(image_path: str, w_size: float):
|
|
|
263
306
|
w_size=w_size)
|
|
264
307
|
|
|
265
308
|
|
|
266
|
-
def calculate_frame_reference_time(frame_duration:
|
|
267
|
-
frame_start:
|
|
268
|
-
half_life: float) ->
|
|
309
|
+
def calculate_frame_reference_time(frame_duration: np.ndarray,
|
|
310
|
+
frame_start: np.ndarray,
|
|
311
|
+
half_life: float) -> np.ndarray:
|
|
269
312
|
r"""Compute frame reference time as the time at which the average activity occurs.
|
|
270
313
|
|
|
271
|
-
Equation comes from the `DICOM standard documentation
|
|
314
|
+
Equation comes from the `DICOM standard documentation
|
|
315
|
+
<https://dicom.innolitics.com/ciods/positron-emission-tomography-image/pet-image/00541300>`_
|
|
272
316
|
|
|
273
317
|
:math:`T_{ave}=\frac{1}{\lambda}ln\frac{\lambda T}{1-e^{-\lambda T}}`
|
|
274
318
|
|
|
275
|
-
where lambda is the decay constant, :math:`\frac{ln2}{T_{1/2}}`, :math:`T_{1/2}` is the half
|
|
319
|
+
where lambda is the decay constant, :math:`\frac{ln2}{T_{1/2}}`, :math:`T_{1/2}` is the half
|
|
320
|
+
life, and :math:`T` is the frame duration.
|
|
276
321
|
|
|
277
322
|
Args:
|
|
278
|
-
frame_duration (
|
|
279
|
-
frame_start (
|
|
280
|
-
half_life (float): Radionuclide half life
|
|
323
|
+
frame_duration (np.ndarray): Duration of each frame in seconds.
|
|
324
|
+
frame_start (np.ndarray): Start time of each frame relative to scan start, in seconds.
|
|
325
|
+
half_life (float): Radionuclide half life in seconds.
|
|
281
326
|
|
|
282
327
|
Returns:
|
|
283
|
-
|
|
328
|
+
np.ndarray: Frame reference time for each frame in the scan in seconds.
|
|
284
329
|
"""
|
|
285
|
-
decay_constant =
|
|
286
|
-
|
|
330
|
+
decay_constant = np.log(2)/half_life
|
|
331
|
+
decay_over_frame = decay_constant*frame_duration
|
|
332
|
+
reference_time_delay = np.log((decay_over_frame)/(1-np.exp(-decay_over_frame)))/decay_constant
|
|
333
|
+
frame_reference_time = frame_start + reference_time_delay
|
|
287
334
|
return frame_reference_time
|
petpal/utils/stats.py
CHANGED
|
@@ -6,6 +6,27 @@ import ants
|
|
|
6
6
|
from ..meta.label_maps import LabelMapLoader
|
|
7
7
|
from .useful_functions import check_physical_space_for_ants_image_pair
|
|
8
8
|
|
|
9
|
+
def mean_value_in_region(input_img: ants.ANTsImage,
|
|
10
|
+
seg_img: ants.ANTsImage,
|
|
11
|
+
mappings: int | list[int]) -> float:
|
|
12
|
+
"""Calculate the mean value in a 3D PET image over a region based on one or more integer
|
|
13
|
+
mappings corresponding to regions in a segmentation image.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
input_img (ants.ANTsImage): 3D PET image over which to calculate the mean.
|
|
17
|
+
seg_img (ants.ANTsImage): Segmentation image in same space as `input_img`.
|
|
18
|
+
mappings (int | list[int]): One or more mappings to mask input_image over.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
region_mean (float): Mean PET value over voxels in the regions corresponding to
|
|
22
|
+
`mappings`."""
|
|
23
|
+
region_mask = ants.mask_image(input_img, seg_img, level=mappings)
|
|
24
|
+
region_arr = region_mask.numpy().flatten()
|
|
25
|
+
region_arr_nonzero = region_arr.nonzero()
|
|
26
|
+
voxel_arr = region_arr[region_arr_nonzero]
|
|
27
|
+
return voxel_arr.mean()
|
|
28
|
+
|
|
29
|
+
|
|
9
30
|
class RegionalStats:
|
|
10
31
|
"""Run statistics on each region in a parametric 3D PET kinetic model or other image.
|
|
11
32
|
|
petpal/utils/useful_functions.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Module to handle abstracted functionalities
|
|
3
3
|
"""
|
|
4
|
+
from collections.abc import Callable
|
|
4
5
|
import os
|
|
5
6
|
import nibabel
|
|
6
7
|
import numpy as np
|
|
@@ -88,7 +89,7 @@ def build_label_map(region_names: list[str]):
|
|
|
88
89
|
return abbreviated_names
|
|
89
90
|
|
|
90
91
|
|
|
91
|
-
def weighted_series_sum(
|
|
92
|
+
def weighted_series_sum(input_image_path: str,
|
|
92
93
|
out_image_path: str,
|
|
93
94
|
half_life: float,
|
|
94
95
|
verbose: bool=False,
|
|
@@ -124,7 +125,7 @@ def weighted_series_sum(input_image_4d_path: str,
|
|
|
124
125
|
# TODO: Determine half_life from .json rather than passing as argument.
|
|
125
126
|
|
|
126
127
|
Args:
|
|
127
|
-
|
|
128
|
+
input_image_path (str): Path to a .nii or .nii.gz file containing a 4D
|
|
128
129
|
PET image on which the weighted sum is calculated. Assume a metadata
|
|
129
130
|
file exists with the same path and file name, but with extension .json,
|
|
130
131
|
and follows BIDS standard.
|
|
@@ -147,8 +148,8 @@ def weighted_series_sum(input_image_4d_path: str,
|
|
|
147
148
|
"""
|
|
148
149
|
if half_life <= 0:
|
|
149
150
|
raise ValueError('(ImageOps4d): Radioisotope half life is zero or negative.')
|
|
150
|
-
pet_meta = image_io.load_metadata_for_nifti_with_same_filename(
|
|
151
|
-
pet_image = nibabel.load(
|
|
151
|
+
pet_meta = image_io.load_metadata_for_nifti_with_same_filename(input_image_path)
|
|
152
|
+
pet_image = nibabel.load(input_image_path)
|
|
152
153
|
pet_series = pet_image.get_fdata()
|
|
153
154
|
frame_start = pet_meta['FrameTimesStart']
|
|
154
155
|
frame_duration = pet_meta['FrameDuration']
|
|
@@ -202,7 +203,7 @@ def weighted_series_sum(input_image_4d_path: str,
|
|
|
202
203
|
nibabel.save(pet_sum_image, out_image_path)
|
|
203
204
|
if verbose:
|
|
204
205
|
print(f"(ImageOps4d): weighted sum image saved to {out_image_path}")
|
|
205
|
-
image_io.safe_copy_meta(input_image_path=
|
|
206
|
+
image_io.safe_copy_meta(input_image_path=input_image_path,
|
|
206
207
|
out_image_path=out_image_path)
|
|
207
208
|
|
|
208
209
|
return image_weighted_sum
|
|
@@ -431,3 +432,103 @@ def get_frame_from_timeseries(input_img: ants.ANTsImage, frame: int) -> ants.ANT
|
|
|
431
432
|
ants.set_direction( img_3d, subdirection )
|
|
432
433
|
|
|
433
434
|
return img_3d
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def nearest_frame_to_timepoint(frame_times: np.ndarray) -> Callable[[float],float]:
|
|
438
|
+
"""Returns a step function that gets the index of the frame closest to a provided timepoint
|
|
439
|
+
based on an array of frame times, such as the frame starts or reference times.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
frame_times (np.ndarray): The frame times on which to generate the step function.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
nearest_frame_func (Callable[[float],float]): A function that returns the time closest to
|
|
446
|
+
the provided timepoint.
|
|
447
|
+
"""
|
|
448
|
+
nearest_frame_func = interp1d(x=frame_times,
|
|
449
|
+
y=range(len(frame_times)),
|
|
450
|
+
kind='nearest',
|
|
451
|
+
bounds_error=False,
|
|
452
|
+
fill_value='extrapolate')
|
|
453
|
+
return nearest_frame_func
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def get_average_of_timeseries(input_image: ants.ANTsImage) -> ants.ANTsImage:
|
|
457
|
+
"""
|
|
458
|
+
Get average of a 4D ANTsImage and return as a 3D ANTsImage.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
input_image (ants.ANTsImage): 4D PET image over which to compute timeseries average.
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
mean_image (ants.ANTsImage): 3D mean over time in the PET image.
|
|
465
|
+
"""
|
|
466
|
+
assert len(input_image.shape) == 4, "Input image must be 4D"
|
|
467
|
+
mean_array = input_image.mean(axis=-1)
|
|
468
|
+
mean_image = ants.from_numpy(data=mean_array,
|
|
469
|
+
origin=input_image.origin[:-1],
|
|
470
|
+
spacing=input_image.spacing[:-1],
|
|
471
|
+
direction=input_image.direction[:-1,:-1])
|
|
472
|
+
return mean_image
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def gen_nd_image_based_on_image_list(image_list: list[ants.ANTsImage]) -> ants.ANTsImage:
|
|
476
|
+
r"""
|
|
477
|
+
Generate a 4D ANTsImage based on a list of 3D ANTsImages.
|
|
478
|
+
|
|
479
|
+
This function takes a list of 3D ANTsImages and constructs a new 4D ANTsImage,
|
|
480
|
+
where the additional dimension represents the number of frames (3D images) in the list.
|
|
481
|
+
The 4D image retains the spacing, origin, direction, and shape properties of the 3D images,
|
|
482
|
+
with appropriate modifications for the additional dimension.
|
|
483
|
+
|
|
484
|
+
Args:
|
|
485
|
+
image_list (list[ants.core.ants_image.ANTsImage]):
|
|
486
|
+
List of 3D ANTsImage objects to be combined into a 4D image.
|
|
487
|
+
The list must contain at least one image, and all images must have the same
|
|
488
|
+
dimensions and properties.
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
ants.ANTsImage:
|
|
492
|
+
A 4D ANTsImage constructed from the input list of 3D images. The additional
|
|
493
|
+
dimension corresponds to the number of frames (length of the image list).
|
|
494
|
+
|
|
495
|
+
Raises:
|
|
496
|
+
AssertionError: If the `image_list` is empty or if the images in the list are not 3D.
|
|
497
|
+
|
|
498
|
+
See Also
|
|
499
|
+
* :func:`petpal.preproc.motion_corr.motion_corr_frame_list_to_t1`
|
|
500
|
+
|
|
501
|
+
Example:
|
|
502
|
+
|
|
503
|
+
.. code-block:: python
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
import ants
|
|
507
|
+
image1 = ants.image_read('frame1.nii.gz')
|
|
508
|
+
image2 = ants.image_read('frame2.nii.gz')
|
|
509
|
+
image_list = [image1, image2]
|
|
510
|
+
result = _gen_nd_image_based_on_image_list(image_list)
|
|
511
|
+
print(result.dimension) # 4
|
|
512
|
+
image4d = ants.list_to_ndimage(result, image_list)
|
|
513
|
+
|
|
514
|
+
"""
|
|
515
|
+
assert len(image_list) > 0
|
|
516
|
+
assert image_list[0].dimension == 3
|
|
517
|
+
|
|
518
|
+
num_frames = len(image_list)
|
|
519
|
+
spacing_3d = image_list[0].spacing
|
|
520
|
+
origin_3d = image_list[0].origin
|
|
521
|
+
shape_3d = image_list[0].shape
|
|
522
|
+
direction_3d = image_list[0].direction
|
|
523
|
+
|
|
524
|
+
direction_4d = np.eye(4)
|
|
525
|
+
direction_4d[:3, :3] = direction_3d
|
|
526
|
+
spacing_4d = (*spacing_3d, 1.0)
|
|
527
|
+
origin_4d = (*origin_3d, 0.0)
|
|
528
|
+
shape_4d = (*shape_3d, num_frames)
|
|
529
|
+
|
|
530
|
+
tmp_image = ants.make_image(imagesize=shape_4d,
|
|
531
|
+
spacing=spacing_4d,
|
|
532
|
+
origin=origin_4d,
|
|
533
|
+
direction=direction_4d)
|
|
534
|
+
return tmp_image
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: petpal
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.8
|
|
4
4
|
Summary: PET-PAL (Positron Emission Tomography Processing and Analysis Library)
|
|
5
5
|
Project-URL: Repository, https://github.com/PETPAL-WUSM/PETPAL.git
|
|
6
6
|
Author-email: Noah Goldman <noahg@wustl.edu>, Bradley Judge <bjudge@wustl.edu>, Furqan Dar <dar@wustl.edu>, Kenan Oestreich <kenan.oestreich@wustl.edu>
|
|
@@ -4,15 +4,15 @@ petpal/cli/cli_graphical_analysis.py,sha256=nGb0afMAigJgvbyEj5EXOCt6WNU35LNo3h3K
|
|
|
4
4
|
petpal/cli/cli_graphical_plots.py,sha256=_2tlGtZ0hIVyEYtGviEzGZMNhFymUPg4ZvSVyMtT_dA,3211
|
|
5
5
|
petpal/cli/cli_idif.py,sha256=6lh_kJHcGjlHDXZOvbiuHrNqpk5FovVV5_j7_dPHTHU,5145
|
|
6
6
|
petpal/cli/cli_parametric_images.py,sha256=JBFb8QlxZoGOzqvCJPFuZ7czzGWntJP5ZcfeM5-QF4Y,7385
|
|
7
|
-
petpal/cli/cli_pib_processing.py,sha256=
|
|
7
|
+
petpal/cli/cli_pib_processing.py,sha256=ye_yw0ZQ4cSrMNemGR7cU9v6epD7Wbq1xaNAJwLzV_8,6889
|
|
8
8
|
petpal/cli/cli_plot_tacs.py,sha256=XycaYQQl9Jp5jqDp3QXOlVT2sXHYYpYSraEArxsfJec,6479
|
|
9
|
-
petpal/cli/cli_preproc.py,sha256=
|
|
9
|
+
petpal/cli/cli_preproc.py,sha256=y5YvRliXC3zNY8oBJgTkzwPH9YNwWnEUWgiltUAv7AU,21850
|
|
10
10
|
petpal/cli/cli_pvc.py,sha256=DC0JZ6p1pkc5BDgQ006bi9y0Mz32ENrjUaOtSvFobP4,3967
|
|
11
11
|
petpal/cli/cli_reference_tissue_models.py,sha256=18BlKN4rMehyFbdq_yr88oztqR99_gBtWKImhwf7CQY,13283
|
|
12
12
|
petpal/cli/cli_stats.py,sha256=Mqbzc9yYo9SVAfxzaYbAbk7EheW472l1PY_ptlD1DOI,2050
|
|
13
13
|
petpal/cli/cli_tac_fitting.py,sha256=bCYwFAbxIKwnYBteAKnot5vOsk9F4Z1EJw2Xk0tZ9oo,14989
|
|
14
14
|
petpal/cli/cli_tac_interpolation.py,sha256=Nwf0CAfyEATLeiwuPSirS0DWDeerrIH2P-U0lpXpKWk,5734
|
|
15
|
-
petpal/cli/cli_vat_processing.py,sha256=
|
|
15
|
+
petpal/cli/cli_vat_processing.py,sha256=XrWcjZqgc5JAtp4fZ94ZLJ7h6fvdMhZvR30nh7GCD4w,12404
|
|
16
16
|
petpal/input_function/__init__.py,sha256=mWdwuVdMSgaHE0wviNE7TGGoOI9Y3sEKfKpBqLS-Ph4,151
|
|
17
17
|
petpal/input_function/blood_input.py,sha256=TNx3hL7M8Z_ZaZoTpt8LgLQhXnNyW0BOhgVZompwgxA,9206
|
|
18
18
|
petpal/input_function/idif_necktangle.py,sha256=o5kyAqyT4C6o7zELY4EjyHrkJyX1BWcxvBqYiMjNyn4,7863
|
|
@@ -32,18 +32,20 @@ petpal/meta/label_maps.py,sha256=cifp_KXQAgIbdWoinMpOQstEK4-pasSXlgML6xoEjHM,227
|
|
|
32
32
|
petpal/pipelines/__init__.py,sha256=Qt9VwqAvVjadHR6Lsja05XIVQONHTCs30NFxiBvf_2k,266
|
|
33
33
|
petpal/pipelines/kinetic_modeling_steps.py,sha256=G6FyfhR0MJ83b4pnww_FhZcAY8PwAIXSYglyoj1mPoQ,32717
|
|
34
34
|
petpal/pipelines/pca_guided_idif_steps.py,sha256=tuL3stKDlUQsHBTTzlYx_WgBKwaS8eHpieUjzB90Mq4,20550
|
|
35
|
-
petpal/pipelines/pipelines.py,sha256=
|
|
36
|
-
petpal/pipelines/preproc_steps.py,sha256=
|
|
35
|
+
petpal/pipelines/pipelines.py,sha256=2KgeOvrXmjNrecTQMqIKMPv4tzvaRjr1FhvntVyvHX0,38391
|
|
36
|
+
petpal/pipelines/preproc_steps.py,sha256=VC-KoMwxnfv0BxUEGneDjDy6HUYAub6JztaRHm3gm4M,37987
|
|
37
37
|
petpal/pipelines/steps_base.py,sha256=W7GVN5-tsXe_7fvhoMRIlOk5ggY8V2wpSSmDONphLnQ,18311
|
|
38
38
|
petpal/pipelines/steps_containers.py,sha256=TV4LkPK48OnAds10vLcsPI3qtWjHsmwr6XGu3hup59Q,33767
|
|
39
|
-
petpal/preproc/__init__.py,sha256=
|
|
40
|
-
petpal/preproc/decay_correction.py,sha256=
|
|
41
|
-
petpal/preproc/image_operations_4d.py,sha256=
|
|
42
|
-
petpal/preproc/motion_corr.py,sha256=
|
|
39
|
+
petpal/preproc/__init__.py,sha256=HmTVnCiKMxetDT_w-qU_62UOC39_A83LHXhZm07Q81I,504
|
|
40
|
+
petpal/preproc/decay_correction.py,sha256=jT8X2lmMrdGf9-lo1ooKul_yomPqm1p24gB9On_GHl0,6872
|
|
41
|
+
petpal/preproc/image_operations_4d.py,sha256=IqzwxaWxoWC1gmK00uuHIwlhx8e_eQ44C6yVFuu73W4,31371
|
|
42
|
+
petpal/preproc/motion_corr.py,sha256=dz10qjXBVTF_RH5RPZ68drUVX2qyj-MnZ674_Ccwz2Y,28670
|
|
43
|
+
petpal/preproc/motion_target.py,sha256=_OJp3NoYcyD3Ke3wl2KbfOhbJ6dp6ZduR9LLz0rIaC0,3945
|
|
43
44
|
petpal/preproc/partial_volume_corrections.py,sha256=J06j_Y_lhj3b3b9M5FbB2r2EPWQvoymG3GRUffSlYdE,6799
|
|
44
|
-
petpal/preproc/regional_tac_extraction.py,sha256=
|
|
45
|
-
petpal/preproc/register.py,sha256=
|
|
46
|
-
petpal/preproc/segmentation_tools.py,sha256=
|
|
45
|
+
petpal/preproc/regional_tac_extraction.py,sha256=qQDD9Z9p21DVUKokh_en2chOGP7F01wnDN156_74X8Q,19704
|
|
46
|
+
petpal/preproc/register.py,sha256=NKg8mt_XMGa5HBdxYZh3sMu_KMJ0W41VHlX4Zl8wlyE,14171
|
|
47
|
+
petpal/preproc/segmentation_tools.py,sha256=BUy8ij45mmetenvWzODVwNIThDkYiEtY6gTAqI8sIak,25703
|
|
48
|
+
petpal/preproc/standard_uptake_value.py,sha256=YJIt0fl3fwMLl0tRYHpPPprMTaN4Q5JjQ5dx_CQX1nI,7494
|
|
47
49
|
petpal/preproc/symmetric_geometric_transfer_matrix.py,sha256=ELkr7Mo233to1Rwml5YJ-aBvmTSk3LHNSdRhnX0WBDw,17575
|
|
48
50
|
petpal/utils/__init__.py,sha256=PlxBIKUtNvtSFnNZqz8myszOysaYzS8nSILMK4haVGg,412
|
|
49
51
|
petpal/utils/bids_utils.py,sha256=3eZAzwGpOBUQ5ShVBUJJpmUBUhr3VcOikR-KpGCsdE0,7664
|
|
@@ -53,18 +55,18 @@ petpal/utils/decorators.py,sha256=9CVT4rXnSUqryNL83jmhQHiI6IGDr2E8jBqPD-KhYGA,49
|
|
|
53
55
|
petpal/utils/image_io.py,sha256=2Dj2U-OaGL15lwt8KVyl_RZZZdRYdvzUahQWegO_JrA,17995
|
|
54
56
|
petpal/utils/math_lib.py,sha256=DYt80lB1je-wFqHGuzOwfTOM1tPugomB889xBUdr99U,4725
|
|
55
57
|
petpal/utils/metadata.py,sha256=O9exRDlqAmPAEcO9v7dsqzkYcSVLgRA207owEvNXXJ8,6129
|
|
56
|
-
petpal/utils/scan_timing.py,sha256=
|
|
57
|
-
petpal/utils/stats.py,sha256=
|
|
58
|
+
petpal/utils/scan_timing.py,sha256=CYtYuFquAnOQ2QfjXdeLjWrBDPM_k4vBI9oHQdpmVZ0,13908
|
|
59
|
+
petpal/utils/stats.py,sha256=paFdwVPIjlAi0wh5xU4x5WeydjKsEHuwzMLcDG_WzPc,6449
|
|
58
60
|
petpal/utils/testing_utils.py,sha256=eMt1kklxK3rl8tm74I3yVNDotKh1CnYWLINDT7rzboM,9557
|
|
59
61
|
petpal/utils/time_activity_curve.py,sha256=gX3PDYbeWblycvtvyiuFtnv1mBml_-93sIXKh2EmglM,39137
|
|
60
|
-
petpal/utils/useful_functions.py,sha256=
|
|
62
|
+
petpal/utils/useful_functions.py,sha256=md2kTLbs45MhrjdMhvDYcbflPTRNPspRSIHiOeIxEqY,21361
|
|
61
63
|
petpal/visualizations/__init__.py,sha256=bd0NHDVl6Z2BDhisEcob2iIcqfxUfgKJ4DEmlrXJRP4,205
|
|
62
64
|
petpal/visualizations/graphical_plots.py,sha256=ZCKUeLX2TAQscuHjA4bzlFm1bACHIyCwDuNnjCakVWU,47297
|
|
63
65
|
petpal/visualizations/image_visualization.py,sha256=Ob6TD4Q0pIrxi0m9SznK1TRWbX1Ea9Pt4wNMdRrTfTs,9124
|
|
64
66
|
petpal/visualizations/qc_plots.py,sha256=iaCPe-LWWyM3OZzDPZodHZhP-z5fRdpUgaH7QS9VxPM,1243
|
|
65
67
|
petpal/visualizations/tac_plots.py,sha256=zSGdptL-EnqhfDViAX8LFunln5a1b-NJ5ft7ZDcxQ38,15116
|
|
66
|
-
petpal-0.5.
|
|
67
|
-
petpal-0.5.
|
|
68
|
-
petpal-0.5.
|
|
69
|
-
petpal-0.5.
|
|
70
|
-
petpal-0.5.
|
|
68
|
+
petpal-0.5.8.dist-info/METADATA,sha256=p2q6Rl726zTvyMW39Uw3j4jw0dXYKFhIDvkGG9nXqKk,2617
|
|
69
|
+
petpal-0.5.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
70
|
+
petpal-0.5.8.dist-info/entry_points.txt,sha256=0SZmyXqBxKzQg2eerDA16n2BdUEXyixEm0_AUo2dFns,653
|
|
71
|
+
petpal-0.5.8.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
72
|
+
petpal-0.5.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|