rapidtide 3.0.11__py3-none-any.whl → 3.1.1__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.
Files changed (144) hide show
  1. rapidtide/Colortables.py +492 -27
  2. rapidtide/OrthoImageItem.py +1049 -46
  3. rapidtide/RapidtideDataset.py +1533 -86
  4. rapidtide/_version.py +3 -3
  5. rapidtide/calccoherence.py +196 -29
  6. rapidtide/calcnullsimfunc.py +188 -40
  7. rapidtide/calcsimfunc.py +242 -42
  8. rapidtide/correlate.py +1203 -383
  9. rapidtide/data/examples/src/testLD +56 -0
  10. rapidtide/data/examples/src/testalign +1 -1
  11. rapidtide/data/examples/src/testdelayvar +0 -1
  12. rapidtide/data/examples/src/testfmri +53 -3
  13. rapidtide/data/examples/src/testglmfilt +5 -5
  14. rapidtide/data/examples/src/testhappy +29 -7
  15. rapidtide/data/examples/src/testppgproc +17 -0
  16. rapidtide/data/examples/src/testrolloff +11 -0
  17. rapidtide/data/models/model_cnn_pytorch/best_model.pth +0 -0
  18. rapidtide/data/models/model_cnn_pytorch/loss.png +0 -0
  19. rapidtide/data/models/model_cnn_pytorch/loss.txt +1 -0
  20. rapidtide/data/models/model_cnn_pytorch/model.pth +0 -0
  21. rapidtide/data/models/model_cnn_pytorch/model_meta.json +68 -0
  22. rapidtide/decorators.py +91 -0
  23. rapidtide/dlfilter.py +2226 -110
  24. rapidtide/dlfiltertorch.py +4842 -0
  25. rapidtide/externaltools.py +327 -12
  26. rapidtide/fMRIData_class.py +79 -40
  27. rapidtide/filter.py +1899 -810
  28. rapidtide/fit.py +2011 -581
  29. rapidtide/genericmultiproc.py +93 -18
  30. rapidtide/happy_supportfuncs.py +2047 -172
  31. rapidtide/helper_classes.py +584 -43
  32. rapidtide/io.py +2370 -372
  33. rapidtide/linfitfiltpass.py +346 -99
  34. rapidtide/makelaggedtcs.py +210 -24
  35. rapidtide/maskutil.py +448 -62
  36. rapidtide/miscmath.py +827 -121
  37. rapidtide/multiproc.py +210 -22
  38. rapidtide/patchmatch.py +242 -42
  39. rapidtide/peakeval.py +31 -31
  40. rapidtide/ppgproc.py +2203 -0
  41. rapidtide/qualitycheck.py +352 -39
  42. rapidtide/refinedelay.py +431 -57
  43. rapidtide/refineregressor.py +494 -189
  44. rapidtide/resample.py +671 -185
  45. rapidtide/scripts/applyppgproc.py +28 -0
  46. rapidtide/scripts/showxcorr_legacy.py +7 -7
  47. rapidtide/scripts/stupidramtricks.py +15 -17
  48. rapidtide/simFuncClasses.py +1052 -77
  49. rapidtide/simfuncfit.py +269 -69
  50. rapidtide/stats.py +540 -238
  51. rapidtide/tests/happycomp +9 -0
  52. rapidtide/tests/test_cleanregressor.py +1 -2
  53. rapidtide/tests/test_dlfiltertorch.py +627 -0
  54. rapidtide/tests/test_findmaxlag.py +24 -8
  55. rapidtide/tests/test_fullrunhappy_v1.py +0 -2
  56. rapidtide/tests/test_fullrunhappy_v2.py +0 -2
  57. rapidtide/tests/test_fullrunhappy_v3.py +11 -4
  58. rapidtide/tests/test_fullrunhappy_v4.py +10 -2
  59. rapidtide/tests/test_fullrunrapidtide_v7.py +1 -1
  60. rapidtide/tests/test_getparsers.py +11 -3
  61. rapidtide/tests/test_refinedelay.py +0 -1
  62. rapidtide/tests/test_simroundtrip.py +16 -8
  63. rapidtide/tests/test_stcorrelate.py +3 -1
  64. rapidtide/tests/utils.py +9 -8
  65. rapidtide/tidepoolTemplate.py +142 -38
  66. rapidtide/tidepoolTemplate_alt.py +165 -44
  67. rapidtide/tidepoolTemplate_big.py +189 -52
  68. rapidtide/util.py +1217 -118
  69. rapidtide/voxelData.py +684 -37
  70. rapidtide/wiener.py +136 -23
  71. rapidtide/wiener2.py +113 -7
  72. rapidtide/workflows/adjustoffset.py +105 -3
  73. rapidtide/workflows/aligntcs.py +85 -2
  74. rapidtide/workflows/applydlfilter.py +87 -10
  75. rapidtide/workflows/applyppgproc.py +540 -0
  76. rapidtide/workflows/atlasaverage.py +210 -47
  77. rapidtide/workflows/atlastool.py +100 -3
  78. rapidtide/workflows/calcSimFuncMap.py +288 -69
  79. rapidtide/workflows/calctexticc.py +201 -9
  80. rapidtide/workflows/ccorrica.py +101 -6
  81. rapidtide/workflows/cleanregressor.py +165 -31
  82. rapidtide/workflows/delayvar.py +171 -23
  83. rapidtide/workflows/diffrois.py +81 -3
  84. rapidtide/workflows/endtidalproc.py +144 -4
  85. rapidtide/workflows/fdica.py +195 -15
  86. rapidtide/workflows/filtnifti.py +70 -3
  87. rapidtide/workflows/filttc.py +74 -3
  88. rapidtide/workflows/fitSimFuncMap.py +202 -51
  89. rapidtide/workflows/fixtr.py +73 -3
  90. rapidtide/workflows/gmscalc.py +113 -3
  91. rapidtide/workflows/happy.py +801 -199
  92. rapidtide/workflows/happy2std.py +144 -12
  93. rapidtide/workflows/happy_parser.py +163 -23
  94. rapidtide/workflows/histnifti.py +118 -2
  95. rapidtide/workflows/histtc.py +84 -3
  96. rapidtide/workflows/linfitfilt.py +117 -4
  97. rapidtide/workflows/localflow.py +328 -28
  98. rapidtide/workflows/mergequality.py +79 -3
  99. rapidtide/workflows/niftidecomp.py +322 -18
  100. rapidtide/workflows/niftistats.py +174 -4
  101. rapidtide/workflows/pairproc.py +98 -4
  102. rapidtide/workflows/pairwisemergenifti.py +85 -2
  103. rapidtide/workflows/parser_funcs.py +1421 -40
  104. rapidtide/workflows/physiofreq.py +137 -11
  105. rapidtide/workflows/pixelcomp.py +207 -5
  106. rapidtide/workflows/plethquality.py +103 -21
  107. rapidtide/workflows/polyfitim.py +151 -11
  108. rapidtide/workflows/proj2flow.py +75 -2
  109. rapidtide/workflows/rankimage.py +111 -4
  110. rapidtide/workflows/rapidtide.py +368 -76
  111. rapidtide/workflows/rapidtide2std.py +98 -2
  112. rapidtide/workflows/rapidtide_parser.py +109 -9
  113. rapidtide/workflows/refineDelayMap.py +144 -33
  114. rapidtide/workflows/refineRegressor.py +675 -96
  115. rapidtide/workflows/regressfrommaps.py +161 -37
  116. rapidtide/workflows/resamplenifti.py +85 -3
  117. rapidtide/workflows/resampletc.py +91 -3
  118. rapidtide/workflows/retrolagtcs.py +99 -9
  119. rapidtide/workflows/retroregress.py +176 -26
  120. rapidtide/workflows/roisummarize.py +174 -5
  121. rapidtide/workflows/runqualitycheck.py +71 -3
  122. rapidtide/workflows/showarbcorr.py +149 -6
  123. rapidtide/workflows/showhist.py +86 -2
  124. rapidtide/workflows/showstxcorr.py +160 -3
  125. rapidtide/workflows/showtc.py +159 -3
  126. rapidtide/workflows/showxcorrx.py +190 -10
  127. rapidtide/workflows/showxy.py +185 -15
  128. rapidtide/workflows/simdata.py +264 -38
  129. rapidtide/workflows/spatialfit.py +77 -2
  130. rapidtide/workflows/spatialmi.py +250 -27
  131. rapidtide/workflows/spectrogram.py +305 -32
  132. rapidtide/workflows/synthASL.py +154 -3
  133. rapidtide/workflows/tcfrom2col.py +76 -2
  134. rapidtide/workflows/tcfrom3col.py +74 -2
  135. rapidtide/workflows/tidepool.py +2971 -130
  136. rapidtide/workflows/utils.py +19 -14
  137. rapidtide/workflows/utils_doc.py +293 -0
  138. rapidtide/workflows/variabilityizer.py +116 -3
  139. {rapidtide-3.0.11.dist-info → rapidtide-3.1.1.dist-info}/METADATA +10 -8
  140. {rapidtide-3.0.11.dist-info → rapidtide-3.1.1.dist-info}/RECORD +144 -128
  141. {rapidtide-3.0.11.dist-info → rapidtide-3.1.1.dist-info}/entry_points.txt +1 -0
  142. {rapidtide-3.0.11.dist-info → rapidtide-3.1.1.dist-info}/WHEEL +0 -0
  143. {rapidtide-3.0.11.dist-info → rapidtide-3.1.1.dist-info}/licenses/LICENSE +0 -0
  144. {rapidtide-3.0.11.dist-info → rapidtide-3.1.1.dist-info}/top_level.txt +0 -0
rapidtide/multiproc.py CHANGED
@@ -20,7 +20,10 @@ import multiprocessing as mp
20
20
  import sys
21
21
  import threading as thread
22
22
  from platform import python_version, system
23
+ from typing import Any, Callable, List, Optional, Tuple
23
24
 
25
+ import numpy as np
26
+ from numpy.typing import NDArray
24
27
  from tqdm import tqdm
25
28
 
26
29
  try:
@@ -29,14 +32,86 @@ except ImportError:
29
32
  import Queue as thrQueue
30
33
 
31
34
 
32
- def maxcpus(reservecpu=True):
35
+ def maxcpus(reservecpu: bool = True) -> int:
36
+ """Return the maximum number of CPUs that can be used for parallel processing.
37
+
38
+ This function returns the total number of CPU cores available on the system,
39
+ with an option to reserve one CPU core for system operations.
40
+
41
+ Parameters
42
+ ----------
43
+ reservecpu : bool, default=True
44
+ If True, reserves one CPU core for system operations by returning
45
+ `cpu_count() - 1`. If False, returns the total number of CPU cores
46
+ available without reservation.
47
+
48
+ Returns
49
+ -------
50
+ int
51
+ The maximum number of CPUs available for parallel processing.
52
+ If `reservecpu=True`, returns `cpu_count() - 1`.
53
+ If `reservecpu=False`, returns `cpu_count()`.
54
+
55
+ Notes
56
+ -----
57
+ This function uses `multiprocessing.cpu_count()` to determine the number
58
+ of available CPU cores. The reserved CPU core helps maintain system
59
+ responsiveness during parallel processing tasks.
60
+
61
+ Examples
62
+ --------
63
+ >>> maxcpus()
64
+ 7
65
+ >>> maxcpus(reservecpu=False)
66
+ 8
67
+ """
33
68
  if reservecpu:
34
69
  return mp.cpu_count() - 1
35
70
  else:
36
71
  return mp.cpu_count()
37
72
 
38
73
 
39
- def _process_data(data_in, inQ, outQ, showprogressbar=True, chunksize=10000):
74
+ def _process_data(
75
+ data_in: List[Any], inQ: Any, outQ: Any, showprogressbar: bool = True, chunksize: int = 10000
76
+ ) -> List[Any]:
77
+ """Process input data in chunks using multiprocessing queues.
78
+
79
+ This function distributes data into chunks and processes them using
80
+ provided input and output queues. It supports progress tracking and
81
+ handles both complete chunks and a final remainder chunk.
82
+
83
+ Parameters
84
+ ----------
85
+ data_in : List[Any]
86
+ Input data to be processed.
87
+ inQ : Any
88
+ Input queue for sending data to worker processes.
89
+ outQ : Any
90
+ Output queue for receiving processed data from worker processes.
91
+ showprogressbar : bool, optional
92
+ If True, display a progress bar during processing. Default is True.
93
+ chunksize : int, optional
94
+ Size of data chunks to process at a time. Default is 10000.
95
+
96
+ Returns
97
+ -------
98
+ List[Any]
99
+ List of processed data items retrieved from the output queue.
100
+
101
+ Notes
102
+ -----
103
+ This function assumes that `inQ` and `outQ` are properly configured
104
+ multiprocessing queues and that worker processes are running and
105
+ consuming from `inQ` and producing to `outQ`.
106
+
107
+ Examples
108
+ --------
109
+ >>> from multiprocessing import Queue
110
+ >>> data = list(range(1000))
111
+ >>> in_q = Queue()
112
+ >>> out_q = Queue()
113
+ >>> result = _process_data(data, in_q, out_q)
114
+ """
40
115
  # send pos/data to workers
41
116
  data_out = []
42
117
  totalnum = len(data_in)
@@ -84,16 +159,72 @@ def _process_data(data_in, inQ, outQ, showprogressbar=True, chunksize=10000):
84
159
 
85
160
 
86
161
  def run_multiproc(
87
- consumerfunc,
88
- inputshape,
89
- maskarray,
90
- nprocs=1,
91
- verbose=True,
92
- indexaxis=0,
93
- procunit="voxels",
94
- showprogressbar=True,
95
- chunksize=1000,
96
- ):
162
+ consumerfunc: Callable[[Any, Any], None],
163
+ inputshape: Tuple[int, ...],
164
+ maskarray: Optional[NDArray] = None,
165
+ nprocs: int = 1,
166
+ verbose: bool = True,
167
+ indexaxis: int = 0,
168
+ procunit: str = "voxels",
169
+ showprogressbar: bool = True,
170
+ chunksize: int = 1000,
171
+ ) -> List[Any]:
172
+ """
173
+ Execute a function in parallel across multiple processes using multiprocessing.
174
+
175
+ This function initializes a set of worker processes and distributes input data
176
+ across them for parallel processing. It supports optional masking of data
177
+ along a specified axis and provides progress reporting.
178
+
179
+ Parameters
180
+ ----------
181
+ consumerfunc : callable
182
+ Function to be executed in parallel. Must accept two arguments: an input queue
183
+ and an output queue for inter-process communication.
184
+ inputshape : tuple of int
185
+ Shape of the input data along all axes. The dimension along `indexaxis` is
186
+ used to determine the number of items to process.
187
+ maskarray : ndarray, optional
188
+ Boolean or binary mask array used to filter indices. Only indices where
189
+ `maskarray[d] > 0.5` are processed. If None, all indices are processed.
190
+ nprocs : int, optional
191
+ Number of worker processes to use. Default is 1 (single-threaded).
192
+ verbose : bool, optional
193
+ If True, print information about the number of units being processed.
194
+ Default is True.
195
+ indexaxis : int, optional
196
+ Axis along which to iterate for processing. Default is 0.
197
+ procunit : str, optional
198
+ Unit of processing, used for logging messages. Default is "voxels".
199
+ showprogressbar : bool, optional
200
+ If True, display a progress bar during processing. Default is True.
201
+ chunksize : int, optional
202
+ Number of items to process in each chunk. Default is 1000.
203
+
204
+ Returns
205
+ -------
206
+ list
207
+ List of results returned by the worker processes.
208
+
209
+ Notes
210
+ -----
211
+ - On Python 3.8+ and non-Windows systems, the function uses the 'fork' context
212
+ for better performance.
213
+ - The function will exit with an error if `maskarray` is provided but its
214
+ length does not match the size of the `indexaxis` dimension of `inputshape`.
215
+
216
+ Examples
217
+ --------
218
+ >>> def worker_func(inQ, outQ):
219
+ ... while True:
220
+ ... item = inQ.get()
221
+ ... if item is None:
222
+ ... break
223
+ ... outQ.put(item * 2)
224
+ ...
225
+ >>> shape = (100, 100)
226
+ >>> result = run_multiproc(worker_func, shape, nprocs=4)
227
+ """
97
228
  # initialize the workers and the queues
98
229
  __spec__ = None
99
230
  n_workers = nprocs
@@ -148,16 +279,73 @@ def run_multiproc(
148
279
 
149
280
 
150
281
  def run_multithread(
151
- consumerfunc,
152
- inputshape,
153
- maskarray,
154
- verbose=True,
155
- nprocs=1,
156
- indexaxis=0,
157
- procunit="voxels",
158
- showprogressbar=True,
159
- chunksize=1000,
160
- ):
282
+ consumerfunc: Callable[[Any, Any], None],
283
+ inputshape: Tuple[int, ...],
284
+ maskarray: Optional[NDArray] = None,
285
+ verbose: bool = True,
286
+ nprocs: int = 1,
287
+ indexaxis: int = 0,
288
+ procunit: str = "voxels",
289
+ showprogressbar: bool = True,
290
+ chunksize: int = 1000,
291
+ ) -> List[Any]:
292
+ """
293
+ Execute a multithreaded processing task using a specified consumer function.
294
+
295
+ This function initializes a set of worker threads that process data in parallel
296
+ according to the provided consumer function. It supports optional masking,
297
+ progress tracking, and configurable chunking for efficient processing.
298
+
299
+ Parameters
300
+ ----------
301
+ consumerfunc : callable
302
+ A function that takes two arguments (input queue, output queue) and
303
+ processes data in a loop until signaled to stop.
304
+ inputshape : tuple of int
305
+ Shape of the input data along all axes. The dimension along `indexaxis`
306
+ determines how many items will be processed.
307
+ maskarray : ndarray, optional
308
+ Boolean or integer array used to filter which indices are processed.
309
+ Must match the size of the axis specified by `indexaxis`.
310
+ verbose : bool, optional
311
+ If True, print information about the number of items being processed
312
+ and the number of threads used. Default is True.
313
+ nprocs : int, optional
314
+ Number of worker threads to spawn. Default is 1.
315
+ indexaxis : int, optional
316
+ Axis along which the indexing is performed. Default is 0.
317
+ procunit : str, optional
318
+ Unit of processing, used in verbose output. Default is "voxels".
319
+ showprogressbar : bool, optional
320
+ If True, display a progress bar during processing. Default is True.
321
+ chunksize : int, optional
322
+ Number of items to process in each chunk. Default is 1000.
323
+
324
+ Returns
325
+ -------
326
+ list
327
+ A list of results returned by the consumer function for each processed item.
328
+
329
+ Notes
330
+ -----
331
+ - The function uses `threading.Queue` for inter-thread communication.
332
+ - If `maskarray` is provided, only indices where `maskarray[d] > 0` are processed.
333
+ - The `consumerfunc` is expected to read from `inQ` and write to `outQ` until
334
+ a `None` is received on `inQ`, signaling the end of processing.
335
+
336
+ Examples
337
+ --------
338
+ >>> def my_consumer(inQ, outQ):
339
+ ... while True:
340
+ ... item = inQ.get()
341
+ ... if item is None:
342
+ ... break
343
+ ... result = item * 2
344
+ ... outQ.put(result)
345
+ ...
346
+ >>> shape = (100, 50)
347
+ >>> result = run_multithread(my_consumer, shape, nprocs=4)
348
+ """
161
349
  # initialize the workers and the queues
162
350
  n_workers = nprocs
163
351
  inQ = thrQueue.Queue()
rapidtide/patchmatch.py CHANGED
@@ -23,6 +23,7 @@ import sys
23
23
  import warnings
24
24
 
25
25
  import numpy as np
26
+ from numpy.typing import NDArray
26
27
  from scipy.interpolate import griddata
27
28
  from scipy.ndimage import distance_transform_edt, gaussian_filter1d
28
29
  from skimage.filters import threshold_multiotsu
@@ -31,21 +32,23 @@ from skimage.segmentation import flood_fill
31
32
  import rapidtide.io as tide_io
32
33
 
33
34
 
34
- def interpolate_masked_voxels(data, mask, method="linear", extrapolate=True):
35
+ def interpolate_masked_voxels(
36
+ data: NDArray, mask: NDArray, method: str = "linear", extrapolate: bool = True
37
+ ) -> NDArray:
35
38
  """
36
39
  Replaces masked voxels in a 3D numpy array with interpolated values
37
40
  from the unmasked region. Supports boundary extrapolation and multiple interpolation methods.
38
41
 
39
42
  Parameters:
40
- data (np.ndarray): A 3D numpy array containing the data.
41
- mask (np.ndarray): A 3D binary numpy array of the same shape as `data`,
43
+ data (NDArray): A 3D numpy array containing the data.
44
+ mask (NDArray): A 3D binary numpy array of the same shape as `data`,
42
45
  where 1 indicates masked voxels and 0 indicates unmasked voxels.
43
46
  method (str): Interpolation method ('linear', 'nearest', or 'cubic').
44
47
  extrapolate (bool): Whether to extrapolate values for masked voxels outside the convex hull
45
48
  of the unmasked points.
46
49
 
47
50
  Returns:
48
- np.ndarray: A new 3D array with interpolated (and optionally extrapolated)
51
+ NDArray: A new 3D array with interpolated (and optionally extrapolated)
49
52
  values replacing masked regions.
50
53
  """
51
54
  if data.shape != mask.shape:
@@ -87,18 +90,41 @@ def interpolate_masked_voxels(data, mask, method="linear", extrapolate=True):
87
90
  return interpolated_data
88
91
 
89
92
 
90
- def get_bounding_box(mask, value, buffer=0):
93
+ def get_bounding_box(mask: NDArray, value: int, buffer: int = 0) -> tuple[tuple, tuple]:
91
94
  """
92
95
  Computes the 3D bounding box that contains all the voxels in the mask with value value.
93
96
 
94
- Parameters:
95
- mask (np.ndarray): A 3D binary mask where non-zero values indicate the masked region.
96
- value (int): The masked region value.
97
+ Parameters
98
+ ----------
99
+ mask : NDArray
100
+ A 3D binary mask where non-zero values indicate the masked region.
101
+ value : int
102
+ The masked region value to compute the bounding box for.
103
+ buffer : int, optional
104
+ Buffer to add around the bounding box in all directions. Default is 0.
97
105
 
98
- Returns:
99
- tuple: Two tuples defining the bounding box:
100
- ((min_x, min_y, min_z), (max_x, max_y, max_z)),
101
- where min and max are inclusive coordinates of the bounding box.
106
+ Returns
107
+ -------
108
+ tuple of tuple of int
109
+ Two tuples defining the bounding box:
110
+ ((min_x, min_y, min_z), (max_x, max_y, max_z)),
111
+ where min and max are inclusive coordinates of the bounding box.
112
+
113
+ Notes
114
+ -----
115
+ The function handles edge cases where the buffer extends beyond the mask boundaries
116
+ by clamping the coordinates to the valid range [0, shape[axis]-1].
117
+
118
+ Examples
119
+ --------
120
+ >>> import numpy as np
121
+ >>> mask = np.zeros((10, 10, 10), dtype=int)
122
+ >>> mask[3:7, 3:7, 3:7] = 1
123
+ >>> get_bounding_box(mask, 1)
124
+ ((3, 3, 3), (6, 6, 6))
125
+
126
+ >>> get_bounding_box(mask, 1, buffer=1)
127
+ ((2, 2, 2), (7, 7, 7))
102
128
  """
103
129
  if mask.ndim != 3:
104
130
  raise ValueError("Input mask must be a 3D array.")
@@ -119,21 +145,94 @@ def get_bounding_box(mask, value, buffer=0):
119
145
  return tuple(min_coords), tuple(max_coords)
120
146
 
121
147
 
122
- def flood3d(
123
- image,
124
- newvalue,
125
- ):
148
+ def flood3d(image: NDArray, newvalue: int) -> NDArray:
149
+ """
150
+ Apply flood fill to each slice of a 3D image.
151
+
152
+ This function performs a connected-component flood fill operation on each
153
+ 2D slice of a 3D image, starting from the top-left corner (0, 0).
154
+
155
+ Parameters
156
+ ----------
157
+ image : NDArray
158
+ Input 3D image array of shape (height, width, depth)
159
+ newvalue : int
160
+ The value to fill the connected component with
161
+
162
+ Returns
163
+ -------
164
+ NDArray
165
+ 3D image array of the same shape as input, with flood fill applied
166
+ to each slice
167
+
168
+ Notes
169
+ -----
170
+ - Uses 4-connectivity (rook-style connectivity) for flood fill
171
+ - Each slice is processed independently
172
+ - The fill operation starts from position (0, 0) in each slice
173
+ - Original image values are preserved in the output where fill did not occur
174
+
175
+ Examples
176
+ --------
177
+ >>> import numpy as np
178
+ >>> image = np.array([[[1, 1, 0],
179
+ ... [1, 0, 0],
180
+ ... [0, 0, 0]],
181
+ ... [[1, 1, 0],
182
+ ... [1, 0, 0],
183
+ ... [0, 0, 0]]])
184
+ >>> result = flood3d(image, 5)
185
+ >>> print(result)
186
+ """
126
187
  filledim = image * 0
127
188
  for slice in range(image.shape[2]):
128
189
  filledim[:, :, slice] = flood_fill(image[:, :, slice], (0, 0), newvalue, connectivity=1)
129
190
  return filledim
130
191
 
131
192
 
132
- def invertedflood3D(image, newvalue):
193
+ def invertedflood3D(image: NDArray, newvalue: int) -> NDArray:
194
+ """
195
+ Apply inverted flood fill operation to a 3D image.
196
+
197
+ This function performs an inverted flood fill by adding the new value to the
198
+ original image and subtracting the result of a standard flood3d operation.
199
+
200
+ Parameters
201
+ ----------
202
+ image : NDArray
203
+ Input 3D image array to process
204
+ newvalue : int
205
+ Value to be added during the inverted flood fill operation
206
+
207
+ Returns
208
+ -------
209
+ NDArray
210
+ Resulting image after inverted flood fill operation
211
+
212
+ Notes
213
+ -----
214
+ The function relies on a `flood3d` function which is assumed to be defined
215
+ elsewhere in the codebase. The inverted flood fill is computed as:
216
+ result = image + newvalue - flood3d(image, newvalue)
217
+
218
+ Examples
219
+ --------
220
+ >>> import numpy as np
221
+ >>> image = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
222
+ >>> result = invertedflood3D(image, 10)
223
+ >>> print(result)
224
+ """
133
225
  return image + newvalue - flood3d(image, newvalue)
134
226
 
135
227
 
136
- def growregion(image, location, value, separatedimage, regionsize, debug=False):
228
+ def growregion(
229
+ image: NDArray,
230
+ location: tuple[int, int, int],
231
+ value: int,
232
+ separatedimage: NDArray,
233
+ regionsize: int,
234
+ debug: bool = False,
235
+ ) -> int:
137
236
  separatedimage[location[0], location[1], location[2]] = value
138
237
  regionsize += 1
139
238
  if debug:
@@ -157,7 +256,7 @@ def growregion(image, location, value, separatedimage, regionsize, debug=False):
157
256
  return regionsize
158
257
 
159
258
 
160
- def separateclusters(image, sizethresh=0, debug=False):
259
+ def separateclusters(image: NDArray, sizethresh: int = 0, debug: bool = False) -> NDArray:
161
260
  separatedclusters = image * 0
162
261
  stop = False
163
262
  value = 1
@@ -219,25 +318,50 @@ def separateclusters(image, sizethresh=0, debug=False):
219
318
  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
220
319
  # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
221
320
  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
222
- def clamp(low, high, value):
223
- """bound an integer to a range
321
+ def clamp(low: int, high: int, value: int) -> int:
322
+ """
323
+ Bound an integer to a range.
324
+
325
+ This function clamps a value to ensure it falls within the inclusive range [low, high].
326
+ If the value is less than low, it returns low. If the value is greater than high,
327
+ it returns high. Otherwise, it returns the value unchanged.
224
328
 
225
329
  Parameters
226
330
  ----------
227
331
  low : int
332
+ The lower bound of the range (inclusive).
228
333
  high : int
334
+ The upper bound of the range (inclusive).
229
335
  value : int
336
+ The value to be clamped.
230
337
 
231
338
  Returns
232
339
  -------
233
- result : int
340
+ int
341
+ The clamped value within the range [low, high].
342
+
343
+ Notes
344
+ -----
345
+ The function assumes that `low <= high`. If this condition is not met,
346
+ the behavior is undefined and may return unexpected results.
347
+
348
+ Examples
349
+ --------
350
+ >>> clamp(0, 10, 5)
351
+ 5
352
+ >>> clamp(0, 10, -1)
353
+ 0
354
+ >>> clamp(0, 10, 15)
355
+ 10
234
356
  """
235
357
  return max(low, min(high, value))
236
358
 
237
359
 
238
- def dehaze(fdata, level, debug=False):
239
- """use Otsu to threshold https://scikit-image.org/docs/stable/auto_examples/segmentation/plot_multiotsu.html
240
- n.b. threshold used to mask image: dark values are zeroed, but result is NOT binary
360
+ def dehaze(fdata: NDArray, level: int, debug: bool = False) -> NDArray:
361
+ """
362
+ use Otsu to threshold https://scikit-image.org/docs/stable/auto_examples/segmentation/plot_multiotsu.html
363
+ n.b. threshold used to mask image: dark values are zeroed, but result is NOT binary
364
+
241
365
  Parameters
242
366
  ----------
243
367
  fdata : numpy.memmap from Niimg-like object
@@ -272,26 +396,33 @@ def dehaze(fdata, level, debug=False):
272
396
 
273
397
 
274
398
  # https://github.com/nilearn/nilearn/blob/1607b52458c28953a87bbe6f42448b7b4e30a72f/nilearn/image/image.py#L164
275
- def _smooth_array(arr, affine, fwhm=None, ensure_finite=True, copy=True):
276
- """Smooth images by applying a Gaussian filter.
399
+ def _smooth_array(
400
+ arr: NDArray,
401
+ affine: NDArray | None,
402
+ fwhm: float | NDArray | tuple | list | str | None = None,
403
+ ensure_finite: bool = True,
404
+ copy: bool = True,
405
+ ) -> NDArray:
406
+ """
407
+ Smooth images by applying a Gaussian filter.
277
408
 
278
409
  Apply a Gaussian filter along the three first dimensions of `arr`.
279
410
 
280
411
  Parameters
281
412
  ----------
282
- arr : :class:`numpy.ndarray`
413
+ arr : :class:`NDArray`
283
414
  4D array, with image number as last dimension. 3D arrays are also
284
415
  accepted.
285
416
 
286
- affine : :class:`numpy.ndarray`
417
+ affine : :class:`NDArray`
287
418
  (4, 4) matrix, giving affine transformation for image. (3, 3) matrices
288
419
  are also accepted (only these coefficients are used).
289
420
  If `fwhm='fast'`, the affine is not used and can be None.
290
421
 
291
- fwhm : scalar, :class:`numpy.ndarray`/:obj:`tuple`/:obj:`list`, 'fast' or None, optional
422
+ fwhm : scalar, :class:`NDArray`/:obj:`tuple`/:obj:`list`, 'fast' or None, optional
292
423
  Smoothing strength, as a full-width at half maximum, in millimeters.
293
424
  If a nonzero scalar is given, width is identical in all 3 directions.
294
- A :class:`numpy.ndarray`, :obj:`tuple`, or :obj:`list` must have 3 elements,
425
+ A :class:`NDArray`, :obj:`tuple`, or :obj:`list` must have 3 elements,
295
426
  giving the FWHM along each axis.
296
427
  If any of the elements is zero or None, smoothing is not performed
297
428
  along that axis.
@@ -311,7 +442,7 @@ def _smooth_array(arr, affine, fwhm=None, ensure_finite=True, copy=True):
311
442
 
312
443
  Returns
313
444
  -------
314
- :class:`numpy.ndarray`
445
+ :class:`NDArray`
315
446
  Filtered `arr`.
316
447
 
317
448
  Notes
@@ -355,8 +486,10 @@ def _smooth_array(arr, affine, fwhm=None, ensure_finite=True, copy=True):
355
486
  return arr
356
487
 
357
488
 
358
- def binary_zero_crossing(fdata):
359
- """binarize (negative voxels are zero)
489
+ def binary_zero_crossing(fdata: NDArray) -> NDArray:
490
+ """
491
+ binarize (negative voxels are zero)
492
+
360
493
  Parameters
361
494
  ----------
362
495
  fdata : numpy.memmap from Niimg-like object
@@ -372,15 +505,18 @@ def binary_zero_crossing(fdata):
372
505
  return edge
373
506
 
374
507
 
375
- def difference_of_gaussian(fdata, affine, fwhmNarrow, ratioopt=True, debug=False):
376
- """Apply Difference of Gaussian (DoG) filter.
508
+ def difference_of_gaussian(
509
+ fdata: NDArray, affine: NDArray, fwhmNarrow: float, ratioopt: bool = True, debug: bool = False
510
+ ) -> NDArray:
511
+ """
512
+ Apply Difference of Gaussian (DoG) filter.
377
513
  https://en.wikipedia.org/wiki/Difference_of_Gaussians
378
514
  https://en.wikipedia.org/wiki/Marr–Hildreth_algorithm
379
515
  D. Marr and E. C. Hildreth. Theory of edge detection. Proceedings of the Royal Society, London B, 207:187-217, 1980
380
516
  Parameters
381
517
  ----------
382
518
  fdata : numpy.memmap from Niimg-like object
383
- affine : :class:`numpy.ndarray`
519
+ affine : :class:`NDArray`
384
520
  (4, 4) matrix, giving affine transformation for image. (3, 3) matrices
385
521
  are also accepted (only these coefficients are used).
386
522
  fwhmNarrow : int
@@ -415,8 +551,16 @@ def difference_of_gaussian(fdata, affine, fwhmNarrow, ratioopt=True, debug=False
415
551
  # We are operating on data in memory that are closely associated with the source
416
552
  # NIFTI files, so the affine and sizes fields are easy to come by, but unlike the
417
553
  # original library, we are not working directly with NIFTI images.
418
- def calc_DoG(thedata, theaffine, thesizes, fwhm=3, ratioopt=True, debug=False):
419
- """Find edges of a NIfTI image using the Difference of Gaussian (DoG).
554
+ def calc_DoG(
555
+ thedata: NDArray,
556
+ theaffine: NDArray,
557
+ thesizes: tuple,
558
+ fwhm: float = 3,
559
+ ratioopt: bool = True,
560
+ debug: bool = False,
561
+ ) -> NDArray:
562
+ """
563
+ Find edges of a NIfTI image using the Difference of Gaussian (DoG).
420
564
  Parameters
421
565
  ----------
422
566
  thedata : 3D data array
@@ -440,7 +584,15 @@ def calc_DoG(thedata, theaffine, thesizes, fwhm=3, ratioopt=True, debug=False):
440
584
  return difference_of_gaussian(dehazed_data, theaffine, fwhm, ratioopt=ratioopt, debug=debug)
441
585
 
442
586
 
443
- def getclusters(theimage, theaffine, thesizes, fwhm=5, ratioopt=True, sizethresh=10, debug=False):
587
+ def getclusters(
588
+ theimage: NDArray,
589
+ theaffine: NDArray,
590
+ thesizes: tuple,
591
+ fwhm: float = 5,
592
+ ratioopt: bool = True,
593
+ sizethresh: int = 10,
594
+ debug: bool = False,
595
+ ) -> NDArray:
444
596
  if debug:
445
597
  print("Detecting clusters..")
446
598
  print(f"\t{theimage.shape=}")
@@ -455,7 +607,54 @@ def getclusters(theimage, theaffine, thesizes, fwhm=5, ratioopt=True, sizethresh
455
607
  )
456
608
 
457
609
 
458
- def interppatch(img_data, separatedimage, method="linear", debug=False):
610
+ def interppatch(
611
+ img_data: NDArray, separatedimage: NDArray, method: str = "linear", debug: bool = False
612
+ ) -> tuple[NDArray, NDArray]:
613
+ """
614
+ Interpolate voxel values within labeled regions of a 3D image.
615
+
616
+ This function applies interpolation to each labeled region in a separated image,
617
+ using the specified interpolation method. It returns both the interpolated image
618
+ and a copy of the original image with the same spatial extent.
619
+
620
+ Parameters
621
+ ----------
622
+ img_data : NDArray
623
+ A 3D array representing the input image data to be interpolated.
624
+ separatedimage : NDArray
625
+ A 3D array of integers where each unique positive integer represents a
626
+ distinct region. Zero values are treated as background.
627
+ method : str, optional
628
+ The interpolation method to use. Default is "linear". Other options may
629
+ include "nearest", "cubic", etc., depending on the implementation of
630
+ `interpolate_masked_voxels`.
631
+ debug : bool, optional
632
+ If True, print debug information for each region being processed.
633
+ Default is False.
634
+
635
+ Returns
636
+ -------
637
+ tuple[NDArray, NDArray]
638
+ A tuple containing:
639
+ - `interpolated`: The image with interpolated values in each region.
640
+ - `justboxes`: A copy of the original image data, with the same shape
641
+ as `img_data`, used for reference or visualization purposes.
642
+
643
+ Notes
644
+ -----
645
+ - Each region is processed independently using its bounding box.
646
+ - The function modifies `img_data` only within the bounds of each region.
647
+ - The `interpolate_masked_voxels` function is assumed to handle the actual
648
+ interpolation logic for masked voxels.
649
+
650
+ Examples
651
+ --------
652
+ >>> import numpy as np
653
+ >>> img = np.random.rand(10, 10, 10)
654
+ >>> labels = np.zeros((10, 10, 10))
655
+ >>> labels[3:7, 3:7, 3:7] = 1
656
+ >>> interpolated, boxes = interppatch(img, labels, method="linear")
657
+ """
459
658
  interpolated = img_data + 0.0
460
659
  justboxes = img_data * 0.0
461
660
  numregions = np.max(separatedimage)
@@ -493,7 +692,8 @@ def interppatch(img_data, separatedimage, method="linear", debug=False):
493
692
 
494
693
 
495
694
  if __name__ == "__main__":
496
- """Apply Gaussian smooth to image
695
+ """
696
+ Apply Gaussian smooth to image
497
697
  Parameters
498
698
  ----------
499
699
  fnm : str