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.
- rapidtide/Colortables.py +492 -27
- rapidtide/OrthoImageItem.py +1049 -46
- rapidtide/RapidtideDataset.py +1533 -86
- rapidtide/_version.py +3 -3
- rapidtide/calccoherence.py +196 -29
- rapidtide/calcnullsimfunc.py +188 -40
- rapidtide/calcsimfunc.py +242 -42
- rapidtide/correlate.py +1203 -383
- rapidtide/data/examples/src/testLD +56 -0
- rapidtide/data/examples/src/testalign +1 -1
- rapidtide/data/examples/src/testdelayvar +0 -1
- rapidtide/data/examples/src/testfmri +53 -3
- rapidtide/data/examples/src/testglmfilt +5 -5
- rapidtide/data/examples/src/testhappy +29 -7
- rapidtide/data/examples/src/testppgproc +17 -0
- rapidtide/data/examples/src/testrolloff +11 -0
- rapidtide/data/models/model_cnn_pytorch/best_model.pth +0 -0
- rapidtide/data/models/model_cnn_pytorch/loss.png +0 -0
- rapidtide/data/models/model_cnn_pytorch/loss.txt +1 -0
- rapidtide/data/models/model_cnn_pytorch/model.pth +0 -0
- rapidtide/data/models/model_cnn_pytorch/model_meta.json +68 -0
- rapidtide/decorators.py +91 -0
- rapidtide/dlfilter.py +2226 -110
- rapidtide/dlfiltertorch.py +4842 -0
- rapidtide/externaltools.py +327 -12
- rapidtide/fMRIData_class.py +79 -40
- rapidtide/filter.py +1899 -810
- rapidtide/fit.py +2011 -581
- rapidtide/genericmultiproc.py +93 -18
- rapidtide/happy_supportfuncs.py +2047 -172
- rapidtide/helper_classes.py +584 -43
- rapidtide/io.py +2370 -372
- rapidtide/linfitfiltpass.py +346 -99
- rapidtide/makelaggedtcs.py +210 -24
- rapidtide/maskutil.py +448 -62
- rapidtide/miscmath.py +827 -121
- rapidtide/multiproc.py +210 -22
- rapidtide/patchmatch.py +242 -42
- rapidtide/peakeval.py +31 -31
- rapidtide/ppgproc.py +2203 -0
- rapidtide/qualitycheck.py +352 -39
- rapidtide/refinedelay.py +431 -57
- rapidtide/refineregressor.py +494 -189
- rapidtide/resample.py +671 -185
- rapidtide/scripts/applyppgproc.py +28 -0
- rapidtide/scripts/showxcorr_legacy.py +7 -7
- rapidtide/scripts/stupidramtricks.py +15 -17
- rapidtide/simFuncClasses.py +1052 -77
- rapidtide/simfuncfit.py +269 -69
- rapidtide/stats.py +540 -238
- rapidtide/tests/happycomp +9 -0
- rapidtide/tests/test_cleanregressor.py +1 -2
- rapidtide/tests/test_dlfiltertorch.py +627 -0
- rapidtide/tests/test_findmaxlag.py +24 -8
- rapidtide/tests/test_fullrunhappy_v1.py +0 -2
- rapidtide/tests/test_fullrunhappy_v2.py +0 -2
- rapidtide/tests/test_fullrunhappy_v3.py +11 -4
- rapidtide/tests/test_fullrunhappy_v4.py +10 -2
- rapidtide/tests/test_fullrunrapidtide_v7.py +1 -1
- rapidtide/tests/test_getparsers.py +11 -3
- rapidtide/tests/test_refinedelay.py +0 -1
- rapidtide/tests/test_simroundtrip.py +16 -8
- rapidtide/tests/test_stcorrelate.py +3 -1
- rapidtide/tests/utils.py +9 -8
- rapidtide/tidepoolTemplate.py +142 -38
- rapidtide/tidepoolTemplate_alt.py +165 -44
- rapidtide/tidepoolTemplate_big.py +189 -52
- rapidtide/util.py +1217 -118
- rapidtide/voxelData.py +684 -37
- rapidtide/wiener.py +136 -23
- rapidtide/wiener2.py +113 -7
- rapidtide/workflows/adjustoffset.py +105 -3
- rapidtide/workflows/aligntcs.py +85 -2
- rapidtide/workflows/applydlfilter.py +87 -10
- rapidtide/workflows/applyppgproc.py +540 -0
- rapidtide/workflows/atlasaverage.py +210 -47
- rapidtide/workflows/atlastool.py +100 -3
- rapidtide/workflows/calcSimFuncMap.py +288 -69
- rapidtide/workflows/calctexticc.py +201 -9
- rapidtide/workflows/ccorrica.py +101 -6
- rapidtide/workflows/cleanregressor.py +165 -31
- rapidtide/workflows/delayvar.py +171 -23
- rapidtide/workflows/diffrois.py +81 -3
- rapidtide/workflows/endtidalproc.py +144 -4
- rapidtide/workflows/fdica.py +195 -15
- rapidtide/workflows/filtnifti.py +70 -3
- rapidtide/workflows/filttc.py +74 -3
- rapidtide/workflows/fitSimFuncMap.py +202 -51
- rapidtide/workflows/fixtr.py +73 -3
- rapidtide/workflows/gmscalc.py +113 -3
- rapidtide/workflows/happy.py +801 -199
- rapidtide/workflows/happy2std.py +144 -12
- rapidtide/workflows/happy_parser.py +163 -23
- rapidtide/workflows/histnifti.py +118 -2
- rapidtide/workflows/histtc.py +84 -3
- rapidtide/workflows/linfitfilt.py +117 -4
- rapidtide/workflows/localflow.py +328 -28
- rapidtide/workflows/mergequality.py +79 -3
- rapidtide/workflows/niftidecomp.py +322 -18
- rapidtide/workflows/niftistats.py +174 -4
- rapidtide/workflows/pairproc.py +98 -4
- rapidtide/workflows/pairwisemergenifti.py +85 -2
- rapidtide/workflows/parser_funcs.py +1421 -40
- rapidtide/workflows/physiofreq.py +137 -11
- rapidtide/workflows/pixelcomp.py +207 -5
- rapidtide/workflows/plethquality.py +103 -21
- rapidtide/workflows/polyfitim.py +151 -11
- rapidtide/workflows/proj2flow.py +75 -2
- rapidtide/workflows/rankimage.py +111 -4
- rapidtide/workflows/rapidtide.py +368 -76
- rapidtide/workflows/rapidtide2std.py +98 -2
- rapidtide/workflows/rapidtide_parser.py +109 -9
- rapidtide/workflows/refineDelayMap.py +144 -33
- rapidtide/workflows/refineRegressor.py +675 -96
- rapidtide/workflows/regressfrommaps.py +161 -37
- rapidtide/workflows/resamplenifti.py +85 -3
- rapidtide/workflows/resampletc.py +91 -3
- rapidtide/workflows/retrolagtcs.py +99 -9
- rapidtide/workflows/retroregress.py +176 -26
- rapidtide/workflows/roisummarize.py +174 -5
- rapidtide/workflows/runqualitycheck.py +71 -3
- rapidtide/workflows/showarbcorr.py +149 -6
- rapidtide/workflows/showhist.py +86 -2
- rapidtide/workflows/showstxcorr.py +160 -3
- rapidtide/workflows/showtc.py +159 -3
- rapidtide/workflows/showxcorrx.py +190 -10
- rapidtide/workflows/showxy.py +185 -15
- rapidtide/workflows/simdata.py +264 -38
- rapidtide/workflows/spatialfit.py +77 -2
- rapidtide/workflows/spatialmi.py +250 -27
- rapidtide/workflows/spectrogram.py +305 -32
- rapidtide/workflows/synthASL.py +154 -3
- rapidtide/workflows/tcfrom2col.py +76 -2
- rapidtide/workflows/tcfrom3col.py +74 -2
- rapidtide/workflows/tidepool.py +2971 -130
- rapidtide/workflows/utils.py +19 -14
- rapidtide/workflows/utils_doc.py +293 -0
- rapidtide/workflows/variabilityizer.py +116 -3
- {rapidtide-3.0.11.dist-info → rapidtide-3.1.1.dist-info}/METADATA +10 -8
- {rapidtide-3.0.11.dist-info → rapidtide-3.1.1.dist-info}/RECORD +144 -128
- {rapidtide-3.0.11.dist-info → rapidtide-3.1.1.dist-info}/entry_points.txt +1 -0
- {rapidtide-3.0.11.dist-info → rapidtide-3.1.1.dist-info}/WHEEL +0 -0
- {rapidtide-3.0.11.dist-info → rapidtide-3.1.1.dist-info}/licenses/LICENSE +0 -0
- {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(
|
|
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(
|
|
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 (
|
|
41
|
-
mask (
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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(
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
-
"""
|
|
240
|
-
|
|
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(
|
|
276
|
-
|
|
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:`
|
|
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:`
|
|
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:`
|
|
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:`
|
|
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:`
|
|
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
|
-
"""
|
|
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(
|
|
376
|
-
|
|
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:`
|
|
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(
|
|
419
|
-
|
|
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(
|
|
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(
|
|
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
|
-
"""
|
|
695
|
+
"""
|
|
696
|
+
Apply Gaussian smooth to image
|
|
497
697
|
Parameters
|
|
498
698
|
----------
|
|
499
699
|
fnm : str
|