brkraw 0.5.3__py3-none-any.whl → 0.5.5__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.
@@ -1,19 +1,25 @@
1
- """Internal helper functions for BrukerLoader.
2
-
3
- Last updated: 2025-12-30
4
- """
5
-
6
1
  from __future__ import annotations
7
2
 
8
3
  from types import MethodType
9
4
  from functools import partial
10
5
  import inspect
11
- from typing import TYPE_CHECKING, Optional, Tuple, Union, Any, Mapping, cast, List, Dict, Literal
6
+ from typing import (
7
+ TYPE_CHECKING,
8
+ cast,
9
+ Optional,
10
+ Tuple,
11
+ Union,
12
+ Any,
13
+ Mapping,
14
+ List,
15
+ Dict
16
+ )
12
17
  from pathlib import Path
13
18
  from warnings import warn
14
19
  import logging
15
20
 
16
21
  import numpy as np
22
+ from numpy.typing import NDArray
17
23
  from nibabel.nifti1 import Nifti1Image
18
24
 
19
25
  from ...core.config import resolve_root
@@ -21,22 +27,38 @@ from ...core.parameters import Parameters
21
27
  from ...specs.remapper import load_spec, map_parameters, load_context_map, apply_context_map
22
28
  from ...specs.rules import load_rules, select_rule_use
23
29
  from ...dataclasses import Reco, Scan, Study
24
- from .types import ScanLoader, ToFilename, ConvertType, GetDataobjType, GetAffineType
25
30
  from ...specs import hook as converter_core
26
31
  from ...resolver import affine as affine_resolver
27
32
  from ...resolver import image as image_resolver
28
33
  from ...resolver import fid as fid_resolver
29
34
  from ...resolver import nifti as nifti_resolver
30
35
  from ...resolver.helpers import get_file
31
-
36
+ from .types import (
37
+ ScanLoader,
38
+ ConvertType,
39
+ GetDataobjType,
40
+ GetAffineType
41
+ )
32
42
  if TYPE_CHECKING:
33
43
  from ...resolver.nifti import Nifti1HeaderContents
34
- from .types import SubjectType, SubjectPose, XYZUNIT, TUNIT, AffineReturn, AffineSpace
44
+ from .types import (
45
+ SubjectType,
46
+ SubjectPose,
47
+ XYZUNIT,
48
+ TUNIT,
49
+ Dataobjs,
50
+ Affines,
51
+ AffineSpace,
52
+ ConvertedObj,
53
+ Metadata
54
+ )
35
55
 
36
- logger = logging.getLogger("brkraw")
56
+ logger = logging.getLogger(__name__)
37
57
 
38
58
  __all__ = [
59
+ "resolve_reco_id",
39
60
  "resolve_data_and_affine",
61
+ "resolve_converter_hook",
40
62
  "search_parameters",
41
63
  "get_dataobj",
42
64
  "get_affine",
@@ -55,7 +77,7 @@ def make_dir(names: List[str]):
55
77
  return _dir
56
78
 
57
79
 
58
- def _resolve_reco_id(
80
+ def resolve_reco_id(
59
81
  scan: Union["Scan", "ScanLoader"],
60
82
  reco_id: Optional[int],
61
83
  ) -> Optional[int]:
@@ -79,7 +101,7 @@ def _resolve_reco_id(
79
101
 
80
102
 
81
103
  def resolve_data_and_affine(
82
- scan: Union["Scan", "ScanLoader"],
104
+ scan: "Scan",
83
105
  reco_id: Optional[int] = None,
84
106
  *,
85
107
  affine_decimals: int = 6,
@@ -92,6 +114,9 @@ def resolve_data_and_affine(
92
114
  affine_decimals: Decimal rounding applied to resolved affines.
93
115
  """
94
116
  scan = cast(ScanLoader, scan)
117
+ scan.get_fid = MethodType(fid_resolver.resolve, scan)
118
+ scan.image_info = {}
119
+ scan.affine_info = {}
95
120
 
96
121
  reco_ids = [reco_id] if reco_id is not None else list(scan.avail.keys())
97
122
  if not reco_ids:
@@ -108,7 +133,7 @@ def resolve_data_and_affine(
108
133
  )
109
134
  continue
110
135
  try:
111
- image_info = image_resolver.resolve(scan, rid)
136
+ image_info = image_resolver.resolve(scan, rid, load_data=False)
112
137
  except Exception as exc:
113
138
  logger.warning(
114
139
  "Failed to resolve image data for scan %s reco %s: %s",
@@ -122,6 +147,7 @@ def resolve_data_and_affine(
122
147
  affine_info = affine_resolver.resolve(
123
148
  scan, rid, decimals=affine_decimals, unwrap_pose=False,
124
149
  )
150
+
125
151
  except Exception as exc:
126
152
  logger.warning(
127
153
  "Failed to resolve affine for scan %s reco %s: %s",
@@ -131,15 +157,65 @@ def resolve_data_and_affine(
131
157
  )
132
158
  affine_info = None
133
159
 
134
- if hasattr(scan, "image_info"):
135
- scan.image_info[rid] = image_info
136
- else:
137
- setattr(scan, "image_info", {rid: image_info})
138
- if hasattr(scan, "affine_info"):
139
- scan.affine_info[rid] = affine_info
160
+ scan.image_info[rid] = image_info
161
+ scan.affine_info[rid] = affine_info
162
+
163
+ def _load_rules(base):
164
+ try:
165
+ rules = load_rules(root=base, validate=False)
166
+ except Exception:
167
+ rules = {}
168
+ return rules
169
+
170
+
171
+ def resolve_converter_hook(
172
+ scan: "Scan",
173
+ base: Path,
174
+ *,
175
+ affine_decimals: int = 6,
176
+ ):
177
+ scan = cast(ScanLoader, scan)
178
+ rules = _load_rules(base)
179
+ if rules:
180
+ try:
181
+ hook_name = select_rule_use(
182
+ scan,
183
+ rules.get("converter_hook", []),
184
+ base=base,
185
+ resolve_paths=False,
186
+ )
187
+ except Exception as exc:
188
+ logger.debug(
189
+ "Converter hook rule selection failed for scan %s: %s",
190
+ getattr(scan, "scan_id", "?"),
191
+ exc,
192
+ exc_info=True,
193
+ )
194
+ hook_name = None
195
+
196
+ if isinstance(hook_name, str):
197
+ try:
198
+ entry = converter_core.resolve_hook(hook_name)
199
+ except Exception as exc:
200
+ logger.warning(
201
+ "Converter hook %r not available: %s",
202
+ hook_name,
203
+ exc,
204
+ )
205
+ entry = None
206
+ if entry:
207
+ logger.debug("Applying converter hook: %s", hook_name)
208
+ scan._converter_hook_name = hook_name
209
+ apply_converter_hook(
210
+ scan,
211
+ entry,
212
+ affine_decimals=affine_decimals,
213
+ )
214
+ else:
215
+ logger.debug("Converter hook %r resolved to no entry.", hook_name)
140
216
  else:
141
- setattr(scan, "affine_info", {rid: affine_info})
142
- scan.get_fid = MethodType(fid_resolver.resolve, scan)
217
+ logger.debug("No converter hook selected for scan %s.", getattr(scan, "scan_id", "?"))
218
+ scan._hook_resolved = True
143
219
 
144
220
 
145
221
  def search_parameters(
@@ -242,7 +318,7 @@ def search_parameters(
242
318
  if scan_id is None:
243
319
  warn("To search from Study object, specifying <scan_id> is required.")
244
320
  return None
245
- scan = self.get_scan(scan_id)
321
+ scan = cast(ScanLoader, self.get_scan(scan_id))
246
322
  scan_hits = search_node(scan)
247
323
  if reco_id is None:
248
324
  reco_hits = search_recos(scan)
@@ -281,10 +357,10 @@ def search_parameters(
281
357
 
282
358
 
283
359
  def _finalize_affines(
284
- affines: list[np.ndarray],
360
+ affines: List[NDArray],
285
361
  num_slice_packs: int,
286
362
  decimals: Optional[int],
287
- ) -> AffineReturn:
363
+ ) -> Affines:
288
364
  if num_slice_packs == 1:
289
365
  affine = affines[0]
290
366
  if decimals is not None:
@@ -298,38 +374,73 @@ def _finalize_affines(
298
374
 
299
375
 
300
376
  def get_dataobj(
301
- self: Union["Scan", "ScanLoader"],
377
+ self: "ScanLoader",
302
378
  reco_id: Optional[int] = None,
303
- **_: Any,
304
- ) -> Optional[Union[Tuple["np.ndarray", ...], "np.ndarray"]]:
379
+ **kwargs: Dict[str, Any]
380
+ ) -> Dataobjs:
305
381
  """Return reconstructed data for a reco, split by slice pack if needed.
306
382
 
307
383
  Args:
308
384
  self: Scan or ScanLoader instance.
309
- reco_id: Reco identifier to read (defaults to the first available).
385
+ reco_id: Reco identifier to read (defaults to the first available).
386
+ cycle_index: Optional cycle start index (last axis), reads all cycles when None.
387
+ cycle_count: Optional number of cycles to read from cycle_index; reads to end when None.
388
+ Ignored when the dataset reports <= 1 total cycle.
310
389
 
311
390
  Returns:
312
391
  Single ndarray when one slice pack exists; otherwise a tuple of arrays.
313
392
  Returns None when required metadata is unavailable.
314
393
  """
315
- if not hasattr(self, "image_info") or not hasattr(self, "affine_info"):
316
- return None
317
- self = cast(ScanLoader, self)
318
- resolved_reco_id = _resolve_reco_id(self, reco_id)
394
+ cycle_index = cast(Optional[int], kwargs.get('cycle_index'))
395
+ cycle_count = cast(Optional[int], kwargs.get('cycle_count'))
396
+ resolved_reco_id = resolve_reco_id(self, reco_id)
319
397
  if resolved_reco_id is None:
320
398
  return None
399
+
321
400
  affine_info = self.affine_info.get(resolved_reco_id)
401
+ if affine_info is None:
402
+ logger.warning(
403
+ "affine_info is not available for scan %s",
404
+ getattr(self, "scan_id", "?")
405
+ )
406
+ return None
322
407
  image_info = self.image_info.get(resolved_reco_id)
323
- if affine_info is None or image_info is None:
408
+ if image_info is None:
409
+ logger.warning(
410
+ "image_info is not available for scan %s",
411
+ getattr(self, "scan_id", "?")
412
+ )
324
413
  return None
325
414
 
326
- num_slices = affine_info["num_slices"]
327
- dataobj = image_info["dataobj"]
415
+ # Normalize cycle arguments if provided.
416
+ cycle_args_requested = cycle_index is not None or cycle_count is not None
417
+ if cycle_index is None and cycle_count is not None:
418
+ cycle_index = 0
419
+
420
+ # If the dataset has <= 1 cycle, ignore cycle slicing to avoid block reads.
421
+ if cycle_args_requested:
422
+ total_cycles = int(image_info["num_cycles"])
423
+ if total_cycles <= 1:
424
+ cycle_index = None
425
+ cycle_count = None
426
+ cycle_args_requested = False
427
+
428
+ if cycle_args_requested or image_info.get("dataobj") is None:
429
+ image_info = image_resolver.resolve(
430
+ self,
431
+ resolved_reco_id,
432
+ load_data=True,
433
+ cycle_index=cycle_index,
434
+ cycle_count=cycle_count,
435
+ )
436
+ self.image_info[resolved_reco_id] = image_info
328
437
 
438
+ num_slices = affine_info["num_slices"]
439
+ dataobj = cast(dict, image_info).get("dataobj")
329
440
  slice_pack = []
330
441
  slice_offset = 0
331
442
  for _num_slices in num_slices:
332
- _dataobj = dataobj[:, :, slice(slice_offset, slice_offset + _num_slices)]
443
+ _dataobj = cast(NDArray, dataobj)[:, :, slice(slice_offset, slice_offset + _num_slices)]
333
444
  slice_offset += _num_slices
334
445
  slice_pack.append(_dataobj)
335
446
 
@@ -339,7 +450,7 @@ def get_dataobj(
339
450
 
340
451
 
341
452
  def get_affine(
342
- self: Union["Scan", "ScanLoader"],
453
+ self: "ScanLoader",
343
454
  reco_id: Optional[int] = None,
344
455
  *,
345
456
  space: AffineSpace = "subject_ras",
@@ -347,7 +458,7 @@ def get_affine(
347
458
  override_subject_pose: Optional["SubjectPose"] = None,
348
459
  decimals: Optional[int] = None,
349
460
  **kwargs: Any,
350
- ) -> AffineReturn:
461
+ ) -> Affines:
351
462
  """
352
463
  Return affine(s) for a reco in the requested coordinate space.
353
464
 
@@ -379,7 +490,7 @@ def get_affine(
379
490
  return None
380
491
 
381
492
  self = cast("ScanLoader", self)
382
- resolved_reco_id = _resolve_reco_id(self, reco_id)
493
+ resolved_reco_id = resolve_reco_id(self, reco_id)
383
494
  if resolved_reco_id is None:
384
495
  return None
385
496
 
@@ -427,7 +538,7 @@ def get_affine(
427
538
  return _apply_affine_post_transform(result, kwargs=kwargs)
428
539
 
429
540
 
430
- def _apply_affine_post_transform(affines: AffineReturn, *, kwargs: Mapping[str, Any]) -> AffineReturn:
541
+ def _apply_affine_post_transform(affines: Affines, *, kwargs: Mapping[str, Any]) -> Affines:
431
542
  """Apply optional flips/rotations to affines right before returning.
432
543
 
433
544
  These transforms are applied in world space and do not depend on output
@@ -458,7 +569,7 @@ def _apply_affine_post_transform(affines: AffineReturn, *, kwargs: Mapping[str,
458
569
  if not (flip_x or flip_y or flip_z or rad_x or rad_y or rad_z):
459
570
  return affines
460
571
 
461
- def apply_one(a: np.ndarray) -> np.ndarray:
572
+ def apply_one(a: NDArray) -> NDArray:
462
573
  out = np.asarray(a, dtype=float)
463
574
  if flip_x or flip_y or flip_z:
464
575
  out = affine_resolver.flip_affine(out, flip_x=flip_x, flip_y=flip_y, flip_z=flip_z)
@@ -474,13 +585,13 @@ def _apply_affine_post_transform(affines: AffineReturn, *, kwargs: Mapping[str,
474
585
  def get_nifti1image(
475
586
  self: Union["Scan", "ScanLoader"],
476
587
  reco_id: int,
477
- dataobjs: Tuple[np.ndarray, ...],
478
- affines: Tuple[np.ndarray, ...],
588
+ dataobjs: Tuple[NDArray, ...],
589
+ affines: Tuple[NDArray, ...],
479
590
  *,
480
591
  xyz_units: XYZUNIT = "mm",
481
592
  t_units: TUNIT = "sec",
482
593
  override_header: Optional[Nifti1HeaderContents] = None,
483
- ) -> Optional[Union[Tuple["Nifti1Image", ...], "Nifti1Image"]]:
594
+ ) -> ConvertedObj:
484
595
  """Return NIfTI image(s) for a reco.
485
596
 
486
597
  Args:
@@ -495,9 +606,12 @@ def get_nifti1image(
495
606
  metadata is unavailable.
496
607
  """
497
608
  self = cast(ScanLoader, self)
498
-
609
+
499
610
  image_info = self.image_info.get(reco_id)
500
- if dataobjs is None or affines is None or image_info is None:
611
+ if image_info is None:
612
+ return None
613
+
614
+ if dataobjs is None or affines is None:
501
615
  return None
502
616
 
503
617
  niiobjs = []
@@ -520,7 +634,7 @@ def get_nifti1image(
520
634
 
521
635
 
522
636
  def convert(
523
- self: Union["Scan", "ScanLoader"],
637
+ self: "ScanLoader",
524
638
  reco_id: Optional[int] = None,
525
639
  *,
526
640
  space: AffineSpace = "subject_ras",
@@ -528,11 +642,9 @@ def convert(
528
642
  override_subject_type: Optional[SubjectType] = None,
529
643
  override_subject_pose: Optional[SubjectPose] = None,
530
644
  flatten_fg: bool = False,
531
- xyz_units: XYZUNIT = "mm",
532
- t_units: TUNIT = "sec",
533
645
  hook_args_by_name: Optional[Mapping[str, Mapping[str, Any]]] = None,
534
646
  **kwargs: Any,
535
- ) -> Optional[Union["ToFilename", Tuple["ToFilename", ...]]]:
647
+ ) -> ConvertedObj:
536
648
  """Convert a reco to output object(s).
537
649
 
538
650
  Args:
@@ -541,12 +653,8 @@ def convert(
541
653
  override_subject_type: Subject type override for subject-view wrapping.
542
654
  override_subject_pose: Subject pose override for subject-view wrapping.
543
655
  flatten_fg: If True, flatten foreground dimensions.
544
- xyz_units: Spatial units for NIfTI header.
545
- t_units: Temporal units for NIfTI header.
546
656
  hook_args_by_name: Optional hook args mapping (split per helper signature).
547
657
  flatten_fg: If True, flatten foreground dimensions.
548
- xyz_units: Spatial units for NIfTI header.
549
- t_units: Temporal units for NIfTI header.
550
658
  Returns:
551
659
  Single NIfTI image when one slice pack exists; otherwise a tuple of
552
660
  images. Returns None when required metadata is unavailable.
@@ -555,9 +663,10 @@ def convert(
555
663
  hasattr(self, attr) for attr in ["image_info", "affine_info", "get_dataobj", "get_affine"]
556
664
  ):
557
665
  return None
558
-
666
+
559
667
  self = cast(ScanLoader, self)
560
- resolved_reco_id = _resolve_reco_id(self, reco_id)
668
+ resolved_reco_id = resolve_reco_id(self, reco_id)
669
+ logger.debug("Resolved reco_id = %s", resolved_reco_id)
561
670
  if resolved_reco_id is None:
562
671
  return None
563
672
 
@@ -577,12 +686,17 @@ def convert(
577
686
  )
578
687
 
579
688
  hook_kwargs = _resolve_hook_kwargs(self, hook_args_by_name)
580
- data_kwargs = _filter_hook_kwargs(self.get_dataobj, hook_kwargs)
581
- convert_kwargs = {
582
- key: value
583
- for key, value in hook_kwargs.items()
584
- if key not in data_kwargs
585
- }
689
+
690
+ # Merge explicit **kwargs (CLI/user) with hook kwargs. Explicit kwargs win.
691
+ merged_kwargs: Dict[str, Any] = dict(hook_kwargs) if hook_kwargs else {}
692
+ merged_kwargs.update(kwargs)
693
+
694
+ data_kwargs = _filter_hook_kwargs(self.get_dataobj, merged_kwargs)
695
+ # flip_* are affine-only options; never pass them to get_dataobj.
696
+ for key in ("flip_x", "flip_y", "flip_z"):
697
+ data_kwargs.pop(key, None)
698
+
699
+ convert_kwargs = {key: value for key, value in merged_kwargs.items() if key not in data_kwargs}
586
700
  if data_kwargs:
587
701
  logger.debug(
588
702
  "Calling get_dataobj for scan %s reco %s with args %s",
@@ -598,7 +712,8 @@ def convert(
598
712
  resolved_reco_id,
599
713
  )
600
714
  dataobjs = self.get_dataobj(resolved_reco_id)
601
- affine_kwargs = _filter_hook_kwargs(self.get_affine, hook_kwargs)
715
+
716
+ affine_kwargs = _filter_hook_kwargs(self.get_affine, merged_kwargs)
602
717
  convert_kwargs = {
603
718
  key: value
604
719
  for key, value in convert_kwargs.items()
@@ -646,7 +761,7 @@ def convert(
646
761
  flattened = int(np.prod(dataobj.shape[3:]))
647
762
  dataobjs[i] = dataobj.reshape((*spatial_shape, flattened), order="A")
648
763
  dataobjs = tuple(dataobjs)
649
-
764
+
650
765
  converter_func = getattr(self, "converter_func", None)
651
766
  if isinstance(converter_func, ConvertType):
652
767
  hook_call_kwargs = _filter_hook_kwargs(converter_func, convert_kwargs)
@@ -663,8 +778,6 @@ def convert(
663
778
  )
664
779
 
665
780
  nifti1image_kwargs = {
666
- "xyz_units": xyz_units,
667
- "t_units": t_units,
668
781
  "override_header": override_header,
669
782
  **kwargs,
670
783
  }
@@ -813,7 +926,7 @@ def get_metadata(
813
926
  spec: Optional[Union[Mapping[str, Any], str, Path]] = None,
814
927
  context_map: Optional[Union[str, Path]] = None,
815
928
  return_spec: bool = False,
816
- ):
929
+ ) -> Metadata:
817
930
  """Resolve metadata using a remapper spec.
818
931
 
819
932
  Args:
@@ -828,7 +941,7 @@ def get_metadata(
828
941
  return_spec is True, returns (metadata, spec_info).
829
942
  """
830
943
  scan = cast(ScanLoader, self)
831
- resolved_reco_id = _resolve_reco_id(scan, reco_id)
944
+ resolved_reco_id = resolve_reco_id(scan, reco_id)
832
945
  if resolved_reco_id is None:
833
946
  if return_spec:
834
947
  return None, None
@@ -8,9 +8,9 @@ from ....specs.remapper.validator import validate_spec
8
8
 
9
9
 
10
10
  if TYPE_CHECKING:
11
- from ..types import ScanLoader, RecoLoader
11
+ from ..types import ScanLoader
12
12
 
13
- logger = logging.getLogger("brkraw")
13
+ logger = logging.getLogger(__name__)
14
14
 
15
15
 
16
16
  def resolve(
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Union, Optional, Tuple, List, Any, cast
4
4
  from datetime import datetime, timezone, timedelta
5
- import numpy as np
6
5
  import re
7
6
 
8
7
  def strip_jcamp_string(value: Optional[str]) -> str:
@@ -5,7 +5,7 @@ Last updated: 2025-12-30
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
- from typing import Any, Union, Tuple, Dict, Optional, Protocol, Literal, Mapping, Callable, List, TYPE_CHECKING, runtime_checkable
8
+ from typing import Any, Union, Tuple, Dict, Optional, Protocol, Literal, Mapping, List, TYPE_CHECKING, runtime_checkable
9
9
  if TYPE_CHECKING:
10
10
  from typing_extensions import ParamSpec, TypeAlias
11
11
  else:
@@ -16,21 +16,26 @@ else:
16
16
  from ...dataclasses.study import Study
17
17
  from ...dataclasses.scan import Scan
18
18
  from ...dataclasses.reco import Reco
19
+ from ...resolver.affine import SubjectType, SubjectPose
19
20
  import numpy as np
21
+ from numpy.typing import NDArray
20
22
 
21
23
  if TYPE_CHECKING:
22
24
  from pathlib import Path
23
25
  from ...core.parameters import Parameters
24
26
  from ...resolver.image import ResolvedImage
25
- from ...resolver.affine import ResolvedAffine, SubjectType, SubjectPose
27
+ from ...resolver.affine import ResolvedAffine
26
28
  from ...resolver.nifti import Nifti1HeaderContents, XYZUNIT, TUNIT
27
- from nibabel.nifti1 import Nifti1Image
28
-
29
29
 
30
30
 
31
31
  InfoScope = Literal['full', 'study', 'scan']
32
- AffineReturn = Optional[Union[np.ndarray, Tuple[np.ndarray, ...]]]
32
+ Dataobjs = Optional[Union[NDArray, Tuple[NDArray, ...]]]
33
+ Affines = Optional[Union[NDArray, Tuple[NDArray, ...]]]
33
34
  AffineSpace = Literal["raw", "scanner", "subject_ras"]
35
+ ConvertedObj = Optional[Union["ToFilename", Tuple["ToFilename", ...]]]
36
+ Metadata = Optional[Union[Dict, Tuple[Optional[Dict], ...]]]
37
+ HookArgs = Optional[Mapping[str, Mapping[str, Any]]]
38
+
34
39
 
35
40
  P = ParamSpec("P")
36
41
 
@@ -42,9 +47,11 @@ class GetDataobjType(Protocol[P]):
42
47
  self,
43
48
  scan: "Scan",
44
49
  reco_id: Optional[int],
50
+ cycle_index: Optional[int],
51
+ cycle_count: Optional[int],
45
52
  *args: P.args,
46
53
  **kwargs: P.kwargs
47
- ) -> Optional[Union[Tuple["np.ndarray", ...], "np.ndarray"]]:
54
+ ) -> Dataobjs:
48
55
  ...
49
56
 
50
57
 
@@ -61,7 +68,7 @@ class GetAffineType(Protocol):
61
68
  override_subject_pose: Optional[SubjectPose],
62
69
  decimals: Optional[int] = None,
63
70
  **kwargs: Any
64
- ) -> Optional[Union[Tuple["np.ndarray", ...], "np.ndarray"]]:
71
+ ) -> Affines:
65
72
  ...
66
73
 
67
74
 
@@ -74,7 +81,7 @@ class ConvertType(Protocol):
74
81
  dataobj: Union[Tuple["np.ndarray", ...], "np.ndarray"],
75
82
  affine: Union[Tuple["np.ndarray", ...], "np.ndarray"],
76
83
  **kwargs: Any,
77
- ) -> Optional[Union["ToFilename", Tuple["ToFilename", ...]]]:
84
+ ) -> ConvertedObj:
78
85
  ...
79
86
 
80
87
  class ToFilename(Protocol):
@@ -108,6 +115,7 @@ class ScanLoader(Scan, BaseLoader):
108
115
  converter_func: Optional[ConvertType]
109
116
  _converter_hook: Optional[ConverterHook]
110
117
  _converter_hook_name: Optional[str]
118
+ _hook_resolved: bool = False
111
119
 
112
120
 
113
121
  def get_fid(self,
@@ -119,8 +127,12 @@ class ScanLoader(Scan, BaseLoader):
119
127
 
120
128
  def get_dataobj(
121
129
  self,
122
- reco_id: Optional[int] = None
123
- ) -> Optional[Union[Tuple["np.ndarray", ...], "np.ndarray"]]:
130
+ reco_id: Optional[int] = None,
131
+ *,
132
+ cycle_index: Optional[int] = None,
133
+ cycle_count: Optional[int] = None,
134
+ **kwargs: Any
135
+ ) -> Dataobjs:
124
136
  ...
125
137
 
126
138
  def get_affine(
@@ -132,7 +144,7 @@ class ScanLoader(Scan, BaseLoader):
132
144
  override_subject_pose: Optional[SubjectPose],
133
145
  decimals: Optional[int] = None,
134
146
  **kwargs: Any,
135
- ) -> Optional[Union[Tuple["np.ndarray", ...], "np.ndarray"]]:
147
+ ) -> Affines:
136
148
  ...
137
149
 
138
150
  def get_nifti1image(
@@ -144,7 +156,7 @@ class ScanLoader(Scan, BaseLoader):
144
156
  override_header: Optional[Union[dict, "Nifti1HeaderContents"]],
145
157
  xyz_units: XYZUNIT,
146
158
  t_units: TUNIT
147
- ) -> Optional[Union[Tuple["Nifti1Image", ...], "Nifti1Image"]]:
159
+ ) -> ConvertedObj:
148
160
  ...
149
161
 
150
162
  def convert(
@@ -158,9 +170,9 @@ class ScanLoader(Scan, BaseLoader):
158
170
  flatten_fg: bool,
159
171
  xyz_units: XYZUNIT,
160
172
  t_units: TUNIT,
161
- hook_args_by_name: Optional[Mapping[str, Mapping[str, Any]]] = None,
173
+ hook_args_by_name: HookArgs = None,
162
174
  **kwargs: Any,
163
- ) -> Optional[Union["ToFilename", Tuple["ToFilename", ...]]]:
175
+ ) -> ConvertedObj:
164
176
  ...
165
177
 
166
178
  def get_metadata(
@@ -169,7 +181,7 @@ class ScanLoader(Scan, BaseLoader):
169
181
  spec: Optional[Union[Mapping[str, Any], str, "Path"]] = None,
170
182
  context_map: Optional[Union[str, "Path"]] = None,
171
183
  return_spec: bool = False,
172
- ) -> Optional[Union[dict, Tuple[Optional[dict], Optional[dict]]]]:
184
+ ) -> Metadata:
173
185
  ...
174
186
 
175
187
 
@@ -191,6 +203,14 @@ __all__ = [
191
203
  'StudyLoader',
192
204
  'ScanLoader',
193
205
  'RecoLoader',
206
+ 'SubjectType',
207
+ 'SubjectPose',
208
+ 'Affines',
209
+ 'Dataobjs',
210
+ 'Metadata',
211
+ 'ConvertedObj',
212
+ 'HookArgs',
213
+ 'AffineSpace',
194
214
  ]
195
215
 
196
216
  def __dir__() -> List[str]:
@@ -10,7 +10,7 @@ from brkraw.core import config as config_core
10
10
  from brkraw.core import formatter
11
11
  from brkraw.apps import addon as addon_app
12
12
 
13
- logger = logging.getLogger("brkraw")
13
+ logger = logging.getLogger(__name__)
14
14
 
15
15
 
16
16
  def cmd_addon(args: argparse.Namespace) -> int: