brkraw 0.5.0rc1__py3-none-any.whl → 0.5.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.
- brkraw/__init__.py +1 -1
- brkraw/apps/hook/core.py +58 -10
- brkraw/apps/loader/core.py +5 -1
- brkraw/apps/loader/helper.py +155 -14
- brkraw/apps/loader/info/scan.py +18 -5
- brkraw/apps/loader/types.py +6 -1
- brkraw/cli/commands/convert.py +201 -79
- brkraw/cli/commands/hook.py +146 -4
- brkraw/cli/commands/session.py +6 -1
- brkraw/cli/hook_args.py +80 -0
- brkraw/core/config.py +8 -0
- brkraw/core/layout.py +56 -11
- brkraw/default/rules/00_default.yaml +4 -0
- brkraw/default/specs/metadata_dicom.yaml +236 -0
- brkraw/default/specs/metadata_transforms.py +18 -0
- brkraw/resolver/affine.py +56 -32
- brkraw/schema/context_map.yaml +5 -0
- brkraw/schema/remapper.yaml +6 -0
- brkraw/specs/__init__.py +2 -2
- brkraw/specs/{converter → hook}/logic.py +1 -0
- brkraw/specs/{converter → hook}/validator.py +1 -0
- brkraw/specs/remapper/logic.py +83 -16
- brkraw/specs/remapper/validator.py +21 -5
- {brkraw-0.5.0rc1.dist-info → brkraw-0.5.1.dist-info}/METADATA +31 -4
- {brkraw-0.5.0rc1.dist-info → brkraw-0.5.1.dist-info}/RECORD +29 -33
- brkraw/default/rules/10-metadata.yaml +0 -42
- brkraw/default/rules/20-mrs.yaml +0 -14
- brkraw/default/specs/metadata_anat.yaml +0 -54
- brkraw/default/specs/metadata_common.yaml +0 -129
- brkraw/default/specs/metadata_func.yaml +0 -127
- brkraw/default/specs/mrs.yaml +0 -71
- brkraw/default/specs/mrs_transforms.py +0 -26
- brkraw/specs/{converter → hook}/__init__.py +1 -1
- {brkraw-0.5.0rc1.dist-info → brkraw-0.5.1.dist-info}/WHEEL +0 -0
- {brkraw-0.5.0rc1.dist-info → brkraw-0.5.1.dist-info}/entry_points.txt +0 -0
- {brkraw-0.5.0rc1.dist-info → brkraw-0.5.1.dist-info}/licenses/LICENSE +0 -0
brkraw/__init__.py
CHANGED
brkraw/apps/hook/core.py
CHANGED
|
@@ -7,7 +7,7 @@ import importlib.metadata
|
|
|
7
7
|
import logging
|
|
8
8
|
import re
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, Union
|
|
10
|
+
from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple, Union, cast
|
|
11
11
|
|
|
12
12
|
try:
|
|
13
13
|
from importlib import resources
|
|
@@ -17,7 +17,7 @@ import yaml
|
|
|
17
17
|
|
|
18
18
|
from ...core import config as config_core
|
|
19
19
|
from ...core.entrypoints import list_entry_points
|
|
20
|
-
from ...specs.
|
|
20
|
+
from ...specs.hook.logic import DEFAULT_GROUP
|
|
21
21
|
from ..addon import installation as addon_install
|
|
22
22
|
from ..addon import dependencies as addon_deps
|
|
23
23
|
from ..addon.io import write_file as _write_file
|
|
@@ -28,6 +28,41 @@ REGISTRY_FILENAME = "hooks.yaml"
|
|
|
28
28
|
MANIFEST_NAMES = ("brkraw_hook.yaml", "brkraw_hook.yml")
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def _metadata_get(
|
|
32
|
+
dist: Optional[importlib.metadata.Distribution],
|
|
33
|
+
key: str,
|
|
34
|
+
default: Optional[str] = None,
|
|
35
|
+
) -> Optional[str]:
|
|
36
|
+
"""Typed helper to read distribution metadata."""
|
|
37
|
+
if dist is None:
|
|
38
|
+
return default
|
|
39
|
+
meta = cast(Mapping[str, str], dist.metadata)
|
|
40
|
+
return meta.get(key, default)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _packages_distributions() -> Mapping[str, List[str]]:
|
|
44
|
+
packages_distributions = getattr(importlib.metadata, "packages_distributions", None)
|
|
45
|
+
if packages_distributions is not None:
|
|
46
|
+
return cast(Callable[[], Mapping[str, List[str]]], packages_distributions)()
|
|
47
|
+
|
|
48
|
+
mapping: Dict[str, List[str]] = {}
|
|
49
|
+
for dist in importlib.metadata.distributions():
|
|
50
|
+
read_text = getattr(dist, "read_text", None)
|
|
51
|
+
top_level_text = ""
|
|
52
|
+
if callable(read_text):
|
|
53
|
+
top_level = read_text("top_level.txt")
|
|
54
|
+
if isinstance(top_level, str):
|
|
55
|
+
top_level_text = top_level
|
|
56
|
+
dist_name = _metadata_get(dist, "Name")
|
|
57
|
+
if not dist_name:
|
|
58
|
+
continue
|
|
59
|
+
for package in top_level_text.splitlines():
|
|
60
|
+
package = package.strip()
|
|
61
|
+
if package:
|
|
62
|
+
mapping.setdefault(package, []).append(dist_name)
|
|
63
|
+
return mapping
|
|
64
|
+
|
|
65
|
+
|
|
31
66
|
def list_hooks(*, root: Optional[Union[str, Path]] = None) -> List[Dict[str, Any]]:
|
|
32
67
|
hooks = _collect_hooks()
|
|
33
68
|
registry = _load_registry(root=root)
|
|
@@ -88,13 +123,26 @@ def uninstall_hook(
|
|
|
88
123
|
*,
|
|
89
124
|
root: Optional[Union[str, Path]] = None,
|
|
90
125
|
force: bool = False,
|
|
91
|
-
) -> Tuple[str, Dict[str, List[str]]]:
|
|
126
|
+
) -> Tuple[str, Dict[str, List[str]], bool]:
|
|
92
127
|
registry = _load_registry(root=root)
|
|
93
128
|
hooks = registry.get("hooks", {})
|
|
94
129
|
hook_name = _resolve_hook_name(target)
|
|
95
130
|
entry = hooks.get(hook_name)
|
|
131
|
+
if entry is None:
|
|
132
|
+
entry_matches = [
|
|
133
|
+
name
|
|
134
|
+
for name, data in hooks.items()
|
|
135
|
+
if target in (data.get("entrypoints") or [])
|
|
136
|
+
]
|
|
137
|
+
if len(entry_matches) == 1:
|
|
138
|
+
hook_name = entry_matches[0]
|
|
139
|
+
entry = hooks.get(hook_name)
|
|
140
|
+
elif entry_matches:
|
|
141
|
+
names = ", ".join(sorted(entry_matches))
|
|
142
|
+
raise ValueError(f"Multiple hooks match {target}: {names}")
|
|
96
143
|
if entry is None:
|
|
97
144
|
raise LookupError(f"Hook not installed: {hook_name}")
|
|
145
|
+
module_missing = not list_entry_points(DEFAULT_GROUP, name=hook_name)
|
|
98
146
|
removed: Dict[str, List[str]] = {
|
|
99
147
|
"specs": [],
|
|
100
148
|
"pruner_specs": [],
|
|
@@ -113,7 +161,7 @@ def uninstall_hook(
|
|
|
113
161
|
removed[kind].append(relpath)
|
|
114
162
|
hooks.pop(hook_name, None)
|
|
115
163
|
_save_registry(registry, root=root)
|
|
116
|
-
return hook_name, removed
|
|
164
|
+
return hook_name, removed, module_missing
|
|
117
165
|
|
|
118
166
|
|
|
119
167
|
def _install_hook(
|
|
@@ -347,7 +395,7 @@ def _resolve_distribution(ep: importlib.metadata.EntryPoint) -> Optional[importl
|
|
|
347
395
|
pkg = getattr(ep, "module", "").split(".")[0]
|
|
348
396
|
if not pkg:
|
|
349
397
|
return None
|
|
350
|
-
mapping =
|
|
398
|
+
mapping = _packages_distributions()
|
|
351
399
|
dist_names = mapping.get(pkg, [])
|
|
352
400
|
if not dist_names:
|
|
353
401
|
return None
|
|
@@ -360,26 +408,26 @@ def _resolve_distribution(ep: importlib.metadata.EntryPoint) -> Optional[importl
|
|
|
360
408
|
def _dist_name(dist: Optional[importlib.metadata.Distribution]) -> Optional[str]:
|
|
361
409
|
if dist is None:
|
|
362
410
|
return None
|
|
363
|
-
return dist
|
|
411
|
+
return _metadata_get(dist, "Name")
|
|
364
412
|
|
|
365
413
|
|
|
366
414
|
def _dist_version(dist: Optional[importlib.metadata.Distribution]) -> str:
|
|
367
415
|
if dist is None:
|
|
368
416
|
return "<Unknown>"
|
|
369
|
-
return dist
|
|
417
|
+
return _metadata_get(dist, "Version", "<Unknown>") or "<Unknown>"
|
|
370
418
|
|
|
371
419
|
|
|
372
420
|
def _dist_description(dist: Optional[importlib.metadata.Distribution]) -> str:
|
|
373
421
|
if dist is None:
|
|
374
422
|
return "<Unknown>"
|
|
375
|
-
return dist
|
|
423
|
+
return _metadata_get(dist, "Summary", "<Unknown>") or "<Unknown>"
|
|
376
424
|
|
|
377
425
|
|
|
378
426
|
def _dist_author(dist: Optional[importlib.metadata.Distribution]) -> str:
|
|
379
427
|
if dist is None:
|
|
380
428
|
return "<Unknown>"
|
|
381
429
|
for key in ("Author", "Author-email", "Maintainer", "Maintainer-email"):
|
|
382
|
-
value = dist
|
|
430
|
+
value = _metadata_get(dist, key)
|
|
383
431
|
if value:
|
|
384
432
|
return value
|
|
385
433
|
return "<Unknown>"
|
|
@@ -394,7 +442,7 @@ def _load_manifest(
|
|
|
394
442
|
manifest = _find_manifest(dist, packages=packages)
|
|
395
443
|
if manifest is None:
|
|
396
444
|
raise FileNotFoundError(
|
|
397
|
-
f"No hook manifest found in {dist
|
|
445
|
+
f"No hook manifest found in {_metadata_get(dist, 'Name', '<Unknown>')}"
|
|
398
446
|
)
|
|
399
447
|
data = _read_yaml(manifest)
|
|
400
448
|
return manifest, data
|
brkraw/apps/loader/core.py
CHANGED
|
@@ -19,7 +19,7 @@ from pathlib import Path
|
|
|
19
19
|
|
|
20
20
|
from ...core import config as config_core
|
|
21
21
|
from ...core.config import resolve_root
|
|
22
|
-
from ...specs import
|
|
22
|
+
from ...specs import hook as converter_core
|
|
23
23
|
from ...specs.pruner import prune_dataset_to_zip
|
|
24
24
|
from ...specs.rules import load_rules, select_rule_use
|
|
25
25
|
from ...dataclasses import Scan, Study
|
|
@@ -350,6 +350,7 @@ class BrukerLoader:
|
|
|
350
350
|
override_subject_type: Optional[SubjectType] = None,
|
|
351
351
|
override_subject_pose: Optional[SubjectPose] = None,
|
|
352
352
|
flip_x: bool = False,
|
|
353
|
+
flatten_fg: bool = False,
|
|
353
354
|
xyz_units: XYZUNIT = 'mm',
|
|
354
355
|
t_units: TUNIT = 'sec'):
|
|
355
356
|
"""Return NIfTI image(s) for a scan/reco via attached helper.
|
|
@@ -377,6 +378,7 @@ class BrukerLoader:
|
|
|
377
378
|
override_subject_type=override_subject_type,
|
|
378
379
|
override_subject_pose=override_subject_pose,
|
|
379
380
|
flip_x=flip_x,
|
|
381
|
+
flatten_fg=flatten_fg,
|
|
380
382
|
xyz_units=xyz_units,
|
|
381
383
|
t_units=t_units,
|
|
382
384
|
)
|
|
@@ -392,6 +394,7 @@ class BrukerLoader:
|
|
|
392
394
|
override_subject_type: Optional[SubjectType] = None,
|
|
393
395
|
override_subject_pose: Optional[SubjectPose] = None,
|
|
394
396
|
flip_x: bool = False,
|
|
397
|
+
flatten_fg: bool = False,
|
|
395
398
|
xyz_units: XYZUNIT = "mm",
|
|
396
399
|
t_units: TUNIT = "sec",
|
|
397
400
|
hook_args_by_name: Optional[Mapping[str, Mapping[str, Any]]] = None,
|
|
@@ -406,6 +409,7 @@ class BrukerLoader:
|
|
|
406
409
|
override_subject_type=override_subject_type,
|
|
407
410
|
override_subject_pose=override_subject_pose,
|
|
408
411
|
flip_x=flip_x,
|
|
412
|
+
flatten_fg=flatten_fg,
|
|
409
413
|
xyz_units=xyz_units,
|
|
410
414
|
t_units=t_units,
|
|
411
415
|
hook_args_by_name=hook_args_by_name,
|
brkraw/apps/loader/helper.py
CHANGED
|
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
from types import MethodType
|
|
9
9
|
from functools import partial
|
|
10
|
+
import inspect
|
|
10
11
|
from typing import TYPE_CHECKING, Optional, Tuple, Union, Any, Mapping, cast, List, Dict, Literal
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from warnings import warn
|
|
@@ -21,7 +22,7 @@ from ...specs.remapper import load_spec, map_parameters, load_context_map, apply
|
|
|
21
22
|
from ...specs.rules import load_rules, select_rule_use
|
|
22
23
|
from ...dataclasses import Reco, Scan, Study
|
|
23
24
|
from .types import ScanLoader
|
|
24
|
-
from ...specs import
|
|
25
|
+
from ...specs import hook as converter_core
|
|
25
26
|
from ...resolver import affine as affine_resolver
|
|
26
27
|
from ...resolver import image as image_resolver
|
|
27
28
|
from ...resolver import fid as fid_resolver
|
|
@@ -106,11 +107,29 @@ def resolve_data_and_affine(
|
|
|
106
107
|
list(scan.avail.keys()),
|
|
107
108
|
)
|
|
108
109
|
continue
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
try:
|
|
111
|
+
image_info = image_resolver.resolve(scan, rid)
|
|
112
|
+
except Exception as exc:
|
|
113
|
+
logger.warning(
|
|
114
|
+
"Failed to resolve image data for scan %s reco %s: %s",
|
|
115
|
+
getattr(scan, "scan_id", "?"),
|
|
116
|
+
rid,
|
|
117
|
+
exc,
|
|
118
|
+
)
|
|
119
|
+
image_info = None
|
|
120
|
+
try:
|
|
121
|
+
# store subject-view affines (scanner unwrap happens in get_affine)
|
|
122
|
+
affine_info = affine_resolver.resolve(
|
|
123
|
+
scan, rid, decimals=affine_decimals, unwrap_pose=False,
|
|
124
|
+
)
|
|
125
|
+
except Exception as exc:
|
|
126
|
+
logger.warning(
|
|
127
|
+
"Failed to resolve affine for scan %s reco %s: %s",
|
|
128
|
+
getattr(scan, "scan_id", "?"),
|
|
129
|
+
rid,
|
|
130
|
+
exc,
|
|
131
|
+
)
|
|
132
|
+
affine_info = None
|
|
114
133
|
|
|
115
134
|
if hasattr(scan, "image_info"):
|
|
116
135
|
scan.image_info[rid] = image_info
|
|
@@ -327,7 +346,7 @@ def get_affine(
|
|
|
327
346
|
override_subject_type: Optional["SubjectType"] = None,
|
|
328
347
|
override_subject_pose: Optional["SubjectPose"] = None,
|
|
329
348
|
decimals: Optional[int] = None,
|
|
330
|
-
**
|
|
349
|
+
**kwargs: Any,
|
|
331
350
|
) -> AffineReturn:
|
|
332
351
|
"""
|
|
333
352
|
Return affine(s) for a reco in the requested coordinate space.
|
|
@@ -379,7 +398,8 @@ def get_affine(
|
|
|
379
398
|
|
|
380
399
|
# "raw" does not need subject info
|
|
381
400
|
if space == "raw":
|
|
382
|
-
|
|
401
|
+
result = _finalize_affines(affines, num_slice_packs, decimals)
|
|
402
|
+
return _apply_affine_post_transform(result, kwargs=kwargs)
|
|
383
403
|
|
|
384
404
|
# Need subject type/pose for unwrap and wrap
|
|
385
405
|
visu_pars = get_file(self.avail[resolved_reco_id], "visu_pars")
|
|
@@ -392,7 +412,8 @@ def get_affine(
|
|
|
392
412
|
]
|
|
393
413
|
|
|
394
414
|
if space == "scanner":
|
|
395
|
-
|
|
415
|
+
result = _finalize_affines(affines_scanner, num_slice_packs, decimals)
|
|
416
|
+
return _apply_affine_post_transform(result, kwargs=kwargs)
|
|
396
417
|
|
|
397
418
|
# Step 2: wrap to subject RAS (optionally with override)
|
|
398
419
|
use_type = override_subject_type or subj_type
|
|
@@ -402,7 +423,52 @@ def get_affine(
|
|
|
402
423
|
affine_resolver.wrap_to_subject_ras(affine, use_type, use_pose)
|
|
403
424
|
for affine in affines_scanner
|
|
404
425
|
]
|
|
405
|
-
|
|
426
|
+
result = _finalize_affines(affines_subject_ras, num_slice_packs, decimals)
|
|
427
|
+
return _apply_affine_post_transform(result, kwargs=kwargs)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _apply_affine_post_transform(affines: AffineReturn, *, kwargs: Mapping[str, Any]) -> AffineReturn:
|
|
431
|
+
"""Apply optional flips/rotations to affines right before returning.
|
|
432
|
+
|
|
433
|
+
These transforms are applied in world space and do not depend on output
|
|
434
|
+
`space`. They are controlled via extra kwargs (intentionally not strict):
|
|
435
|
+
|
|
436
|
+
- flip_x / flip_y / flip_z: bool-like
|
|
437
|
+
- rad_x / rad_y / rad_z: radians (float-like)
|
|
438
|
+
"""
|
|
439
|
+
|
|
440
|
+
def as_bool(value: Any) -> bool:
|
|
441
|
+
if isinstance(value, str):
|
|
442
|
+
return value.strip().lower() in {"1", "true", "yes", "y", "on"}
|
|
443
|
+
return bool(value)
|
|
444
|
+
|
|
445
|
+
def as_float(value: Any) -> float:
|
|
446
|
+
try:
|
|
447
|
+
return float(value)
|
|
448
|
+
except (TypeError, ValueError):
|
|
449
|
+
return 0.0
|
|
450
|
+
|
|
451
|
+
flip_x = as_bool(kwargs.get("flip_x", False))
|
|
452
|
+
flip_y = as_bool(kwargs.get("flip_y", False))
|
|
453
|
+
flip_z = as_bool(kwargs.get("flip_z", False))
|
|
454
|
+
rad_x = as_float(kwargs.get("rad_x", 0.0))
|
|
455
|
+
rad_y = as_float(kwargs.get("rad_y", 0.0))
|
|
456
|
+
rad_z = as_float(kwargs.get("rad_z", 0.0))
|
|
457
|
+
|
|
458
|
+
if not (flip_x or flip_y or flip_z or rad_x or rad_y or rad_z):
|
|
459
|
+
return affines
|
|
460
|
+
|
|
461
|
+
def apply_one(a: np.ndarray) -> np.ndarray:
|
|
462
|
+
out = np.asarray(a, dtype=float)
|
|
463
|
+
if flip_x or flip_y or flip_z:
|
|
464
|
+
out = affine_resolver.flip_affine(out, flip_x=flip_x, flip_y=flip_y, flip_z=flip_z)
|
|
465
|
+
if rad_x or rad_y or rad_z:
|
|
466
|
+
out = affine_resolver.rotate_affine(out, rad_x=rad_x, rad_y=rad_y, rad_z=rad_z)
|
|
467
|
+
return np.asarray(out, dtype=float)
|
|
468
|
+
|
|
469
|
+
if isinstance(affines, tuple):
|
|
470
|
+
return tuple(apply_one(np.asarray(a)) for a in affines)
|
|
471
|
+
return apply_one(np.asarray(affines))
|
|
406
472
|
|
|
407
473
|
|
|
408
474
|
def get_nifti1image(
|
|
@@ -414,6 +480,7 @@ def get_nifti1image(
|
|
|
414
480
|
override_subject_type: Optional[SubjectType] = None,
|
|
415
481
|
override_subject_pose: Optional[SubjectPose] = None,
|
|
416
482
|
flip_x: bool = False,
|
|
483
|
+
flatten_fg: bool = False,
|
|
417
484
|
xyz_units: XYZUNIT = "mm",
|
|
418
485
|
t_units: TUNIT = "sec",
|
|
419
486
|
hook_args_by_name: Optional[Mapping[str, Mapping[str, Any]]] = None,
|
|
@@ -446,17 +513,19 @@ def get_nifti1image(
|
|
|
446
513
|
if resolved_reco_id is None:
|
|
447
514
|
return None
|
|
448
515
|
hook_kwargs = _resolve_hook_kwargs(self, hook_args_by_name)
|
|
449
|
-
|
|
450
|
-
|
|
516
|
+
data_kwargs = _filter_hook_kwargs(self.get_dataobj, hook_kwargs)
|
|
517
|
+
if data_kwargs:
|
|
518
|
+
dataobjs = self.get_dataobj(resolved_reco_id, **data_kwargs)
|
|
451
519
|
else:
|
|
452
520
|
dataobjs = self.get_dataobj(resolved_reco_id)
|
|
453
|
-
|
|
521
|
+
affine_kwargs = _filter_hook_kwargs(self.get_affine, hook_kwargs)
|
|
522
|
+
if affine_kwargs:
|
|
454
523
|
affines = self.get_affine(
|
|
455
524
|
resolved_reco_id,
|
|
456
525
|
space=space,
|
|
457
526
|
override_subject_type=override_subject_type,
|
|
458
527
|
override_subject_pose=override_subject_pose,
|
|
459
|
-
**
|
|
528
|
+
**affine_kwargs,
|
|
460
529
|
)
|
|
461
530
|
else:
|
|
462
531
|
affines = self.get_affine(
|
|
@@ -476,6 +545,10 @@ def get_nifti1image(
|
|
|
476
545
|
|
|
477
546
|
niiobjs = []
|
|
478
547
|
for i, dataobj in enumerate(dataobjs):
|
|
548
|
+
if flatten_fg and dataobj.ndim > 4:
|
|
549
|
+
spatial_shape = dataobj.shape[:3]
|
|
550
|
+
flattened = int(np.prod(dataobj.shape[3:]))
|
|
551
|
+
dataobj = dataobj.reshape((*spatial_shape, flattened), order="A")
|
|
479
552
|
affine = affines[i]
|
|
480
553
|
niiobj = Nifti1Image(dataobj, affine)
|
|
481
554
|
nifti1header_contents = nifti_resolver.resolve(
|
|
@@ -503,6 +576,7 @@ def convert(
|
|
|
503
576
|
override_subject_type: Optional[SubjectType] = None,
|
|
504
577
|
override_subject_pose: Optional[SubjectPose] = None,
|
|
505
578
|
flip_x: bool = False,
|
|
579
|
+
flatten_fg: bool = False,
|
|
506
580
|
xyz_units: XYZUNIT = "mm",
|
|
507
581
|
t_units: TUNIT = "sec",
|
|
508
582
|
hook_args_by_name: Optional[Mapping[str, Mapping[str, Any]]] = None,
|
|
@@ -518,6 +592,7 @@ def convert(
|
|
|
518
592
|
override_subject_type=override_subject_type,
|
|
519
593
|
override_subject_pose=override_subject_pose,
|
|
520
594
|
flip_x=flip_x,
|
|
595
|
+
flatten_fg=flatten_fg,
|
|
521
596
|
xyz_units=xyz_units,
|
|
522
597
|
t_units=t_units,
|
|
523
598
|
hook_args_by_name=hook_args_by_name,
|
|
@@ -534,9 +609,75 @@ def _resolve_hook_kwargs(
|
|
|
534
609
|
if not isinstance(hook_name, str) or not hook_name:
|
|
535
610
|
return {}
|
|
536
611
|
values = hook_args_by_name.get(hook_name)
|
|
612
|
+
if values is None:
|
|
613
|
+
seen: set[str] = set()
|
|
614
|
+
|
|
615
|
+
def _add(candidate: str) -> None:
|
|
616
|
+
cand = candidate.strip()
|
|
617
|
+
if not cand or cand in seen:
|
|
618
|
+
return
|
|
619
|
+
seen.add(cand)
|
|
620
|
+
|
|
621
|
+
_add(hook_name)
|
|
622
|
+
_add(hook_name.lower())
|
|
623
|
+
_add(hook_name.replace("_", "-"))
|
|
624
|
+
_add(hook_name.replace("-", "_"))
|
|
625
|
+
_add(hook_name.lower().replace("_", "-"))
|
|
626
|
+
_add(hook_name.lower().replace("-", "_"))
|
|
627
|
+
_add(f"brkraw-{hook_name}")
|
|
628
|
+
_add(f"brkraw_{hook_name}")
|
|
629
|
+
_add(f"brkraw-{hook_name.lower()}")
|
|
630
|
+
_add(f"brkraw_{hook_name.lower()}")
|
|
631
|
+
_add(f"brkraw-{hook_name.lower().replace('_', '-')}")
|
|
632
|
+
_add(f"brkraw_{hook_name.lower().replace('-', '_')}")
|
|
633
|
+
|
|
634
|
+
for candidate in sorted(seen):
|
|
635
|
+
if candidate == hook_name:
|
|
636
|
+
continue
|
|
637
|
+
candidate_values = hook_args_by_name.get(candidate)
|
|
638
|
+
if candidate_values is not None:
|
|
639
|
+
logger.debug(
|
|
640
|
+
"Using hook args for %r from alias %r.",
|
|
641
|
+
hook_name,
|
|
642
|
+
candidate,
|
|
643
|
+
)
|
|
644
|
+
values = candidate_values
|
|
645
|
+
break
|
|
537
646
|
return dict(values) if isinstance(values, Mapping) else {}
|
|
538
647
|
|
|
539
648
|
|
|
649
|
+
def _filter_hook_kwargs(func: Any, hook_kwargs: Mapping[str, Any]) -> Dict[str, Any]:
|
|
650
|
+
"""Drop unsupported hook kwargs for a callable.
|
|
651
|
+
|
|
652
|
+
This keeps YAML/CLI presets safe when converter hooks do not accept
|
|
653
|
+
arbitrary kwargs.
|
|
654
|
+
"""
|
|
655
|
+
if not hook_kwargs:
|
|
656
|
+
return {}
|
|
657
|
+
try:
|
|
658
|
+
sig = inspect.signature(func)
|
|
659
|
+
except (TypeError, ValueError):
|
|
660
|
+
return dict(hook_kwargs)
|
|
661
|
+
for param in sig.parameters.values():
|
|
662
|
+
if param.kind == inspect.Parameter.VAR_KEYWORD:
|
|
663
|
+
return dict(hook_kwargs)
|
|
664
|
+
allowed = {
|
|
665
|
+
param.name
|
|
666
|
+
for param in sig.parameters.values()
|
|
667
|
+
if param.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)
|
|
668
|
+
and param.name != "self"
|
|
669
|
+
}
|
|
670
|
+
filtered = {key: value for key, value in hook_kwargs.items() if key in allowed}
|
|
671
|
+
dropped = [key for key in hook_kwargs.keys() if key not in allowed]
|
|
672
|
+
if dropped:
|
|
673
|
+
logger.debug(
|
|
674
|
+
"Ignoring unsupported hook args for %s: %s",
|
|
675
|
+
getattr(func, "__name__", "<callable>"),
|
|
676
|
+
", ".join(sorted(dropped)),
|
|
677
|
+
)
|
|
678
|
+
return filtered
|
|
679
|
+
|
|
680
|
+
|
|
540
681
|
def _resolve_metadata_spec(
|
|
541
682
|
scan: "ScanLoader",
|
|
542
683
|
spec: Optional[Union[Mapping[str, Any], str, Path]],
|
brkraw/apps/loader/info/scan.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, cast, TYPE_CHECKING, Dict, Optional, Union
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
import logging
|
|
5
6
|
from ....specs.remapper import load_spec, map_parameters
|
|
6
7
|
from ....specs.remapper.validator import validate_spec
|
|
7
8
|
|
|
@@ -9,6 +10,8 @@ from ....specs.remapper.validator import validate_spec
|
|
|
9
10
|
if TYPE_CHECKING:
|
|
10
11
|
from ..types import ScanLoader, RecoLoader
|
|
11
12
|
|
|
13
|
+
logger = logging.getLogger("brkraw")
|
|
14
|
+
|
|
12
15
|
|
|
13
16
|
def resolve(
|
|
14
17
|
scan: "ScanLoader",
|
|
@@ -59,14 +62,24 @@ def resolve(
|
|
|
59
62
|
if len(scan.avail):
|
|
60
63
|
results['Reco(s)'] = {}
|
|
61
64
|
for reco_id in scan.avail.keys():
|
|
62
|
-
reco_spec = {
|
|
65
|
+
reco_spec = {
|
|
66
|
+
"Type": {
|
|
67
|
+
"sources": [
|
|
63
68
|
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
"file": "visu_pars",
|
|
70
|
+
"key": "VisuCoreFrameType",
|
|
71
|
+
"reco_id": reco_id,
|
|
67
72
|
}
|
|
68
73
|
]
|
|
69
74
|
}
|
|
70
75
|
}
|
|
71
|
-
|
|
76
|
+
try:
|
|
77
|
+
results["Reco(s)"][reco_id] = map_parameters(scan, reco_spec)
|
|
78
|
+
except (FileNotFoundError, AttributeError) as exc:
|
|
79
|
+
logger.warning(
|
|
80
|
+
"visu_pars missing for scan %s reco %s; skipping reco entry: %s",
|
|
81
|
+
getattr(scan, "scan_id", "unknown"),
|
|
82
|
+
reco_id,
|
|
83
|
+
exc,
|
|
84
|
+
)
|
|
72
85
|
return results
|
brkraw/apps/loader/types.py
CHANGED
|
@@ -75,6 +75,7 @@ class GetNifti1ImageType(Protocol):
|
|
|
75
75
|
override_subject_type: Optional[SubjectType],
|
|
76
76
|
override_subject_pose: Optional[SubjectPose],
|
|
77
77
|
flip_x: bool,
|
|
78
|
+
flatten_fg: bool,
|
|
78
79
|
xyz_units: XYZUNIT,
|
|
79
80
|
t_units: TUNIT,
|
|
80
81
|
**kwargs: Any,
|
|
@@ -95,6 +96,7 @@ class ConvertType(Protocol):
|
|
|
95
96
|
override_subject_type: Optional[SubjectType],
|
|
96
97
|
override_subject_pose: Optional[SubjectPose],
|
|
97
98
|
flip_x: bool,
|
|
99
|
+
flatten_fg: bool,
|
|
98
100
|
xyz_units: XYZUNIT,
|
|
99
101
|
t_units: TUNIT,
|
|
100
102
|
**kwargs: Any,
|
|
@@ -147,7 +149,8 @@ class ScanLoader(Scan, BaseLoader):
|
|
|
147
149
|
space: AffineSpace = "subject_ras",
|
|
148
150
|
override_subject_type: Optional[SubjectType],
|
|
149
151
|
override_subject_pose: Optional[SubjectPose],
|
|
150
|
-
decimals: Optional[int] = None
|
|
152
|
+
decimals: Optional[int] = None,
|
|
153
|
+
**kwargs: Any,
|
|
151
154
|
) -> Optional[Union[Tuple["np.ndarray", ...], "np.ndarray"]]:
|
|
152
155
|
...
|
|
153
156
|
|
|
@@ -160,6 +163,7 @@ class ScanLoader(Scan, BaseLoader):
|
|
|
160
163
|
override_subject_type: Optional[SubjectType],
|
|
161
164
|
override_subject_pose: Optional[SubjectPose],
|
|
162
165
|
flip_x: bool,
|
|
166
|
+
flatten_fg: bool,
|
|
163
167
|
xyz_units: XYZUNIT,
|
|
164
168
|
t_units: TUNIT
|
|
165
169
|
) -> Optional[Union[Tuple["Nifti1Image", ...], "Nifti1Image"]]:
|
|
@@ -175,6 +179,7 @@ class ScanLoader(Scan, BaseLoader):
|
|
|
175
179
|
override_subject_type: Optional[SubjectType],
|
|
176
180
|
override_subject_pose: Optional[SubjectPose],
|
|
177
181
|
flip_x: bool,
|
|
182
|
+
flatten_fg: bool,
|
|
178
183
|
xyz_units: XYZUNIT,
|
|
179
184
|
t_units: TUNIT,
|
|
180
185
|
hook_args_by_name: Optional[Mapping[str, Mapping[str, Any]]] = None,
|