@@ -100,6 +100,14 @@ def lta_list(in_file):
100
100
return lta_list
101
101
102
102
103
+ def _find_highest_uptake_frame (in_files : list [str ]) -> int :
104
+ import nibabel as nb
105
+ import numpy as np
106
+
107
+ uptake = [np .sum (nb .load (f ).get_fdata (dtype = np .float32 )) for f in in_files ]
108
+ return int (np .argmax (uptake )) + 1
109
+
110
+
103
111
class _LTAList2ITKInputSpec (BaseInterfaceInputSpec ):
104
112
in_xforms = InputMultiObject (File (exists = True ), mandatory = True )
105
113
in_reference = File (exists = True , mandatory = True )
@@ -139,6 +147,8 @@ def init_pet_hmc_wf(
139
147
start_time : float = 120.0 ,
140
148
frame_durations : Sequence [float ] | None = None ,
141
149
frame_start_times : Sequence [float ] | None = None ,
150
+ initial_frame : int | str | None = 'auto' ,
151
+ fixed_frame : bool = False ,
142
152
name : str = 'pet_hmc_wf' ,
143
153
):
144
154
r"""
@@ -177,6 +187,15 @@ def init_pet_hmc_wf(
177
187
frame_start_times : :class:`~typing.Sequence`\[:obj:`float`] or ``None``
178
188
Optional list of frame onset times used together with
179
189
``frame_durations`` to locate the start frame.
190
+ initial_frame : :obj:`int`, ``'auto'`` or ``None``
191
+ 0-based index of the frame used to initialize motion correction. If ``'auto'`` or
192
+ ``None`` (default), the frame with the highest uptake is selected automatically.
193
+ FreeSurfer's ``initial_timepoint`` is 1-based; this workflow applies the
194
+ required offset internally.
195
+ fixed_frame : :obj:`bool`
196
+ Whether to keep the initial time point fixed during robust template
197
+ estimation (``fs.RobustTemplate``'s ``fixtp`` parameter). If ``True``,
198
+ iterations are skipped to reduce runtime.
180
199
name : :obj:`str`
181
200
Name of workflow (default: ``pet_hmc_wf``)
182
201
@@ -274,6 +293,17 @@ def num_files(filelist):
274
293
name = 'create_lta_list' ,
275
294
)
276
295
296
+ auto_init_frame = initial_frame in (None , 'auto' )
297
+ if auto_init_frame :
298
+ find_highest_uptake_frame = pe .Node (
299
+ niu .Function (
300
+ input_names = ['in_files' ],
301
+ output_names = ['index' ],
302
+ function = _find_highest_uptake_frame ,
303
+ ),
304
+ name = 'find_highest_uptake_frame' ,
305
+ )
306
+
277
307
# Motion estimation
278
308
robust_template = pe .Node (
279
309
fs .RobustTemplate (
@@ -282,9 +312,13 @@ def num_files(filelist):
282
312
average_metric = 'mean' ,
283
313
args = '--cras' ,
284
314
num_threads = omp_nthreads ,
315
+ fixed_timepoint = fixed_frame ,
316
+ no_iteration = fixed_frame ,
285
317
),
286
318
name = 'est_robust_hmc' ,
287
319
)
320
+ if not auto_init_frame :
321
+ robust_template .inputs .initial_timepoint = int (initial_frame ) + 1
288
322
upd_xfm = pe .Node (
289
323
niu .Function (
290
324
input_names = ['xforms' , 'idx' ],
@@ -324,4 +358,12 @@ def num_files(filelist):
324
358
(ref_to_nii , outputnode , [('out_file' , 'petref' )]),
325
359
]) # fmt:skip
326
360
361
+ if auto_init_frame :
362
+ workflow .connect (
363
+ [
364
+ (thresh , find_highest_uptake_frame , [('out_file' , 'in_files' )]),
365
+ (find_highest_uptake_frame , robust_template , [('index' , 'initial_timepoint' )]),
366
+ ]
367
+ )
368
+
327
369
return workflow
0 commit comments