Source code for PyNutil.processing.adapters.registry
"""Registry and loading functions for the registration system.
This module provides the main entry point for loading registration data
with a composable pipeline of anchoring loaders, deformation providers,
and damage providers.
"""
from __future__ import annotations
from typing import Dict, Optional, Type
from .base import (
AnchoringLoader,
DeformationProvider,
DamageProvider,
RegistrationData,
)
from .anchoring import QuintAnchoringLoader, BrainGlobeRegistrationLoader
from .deformation import VisuAlignDeformationProvider, BrainGlobeDeformationProvider
from .damage import QCAlignDamageProvider
class AnchoringLoaderRegistry:
"""Registry for anchoring loaders."""
_loaders: Dict[str, Type[AnchoringLoader]] = {}
@classmethod
def register(cls, loader_class: Type[AnchoringLoader]) -> None:
cls._loaders[loader_class.name] = loader_class
@classmethod
def get(cls, name: str) -> AnchoringLoader:
if name not in cls._loaders:
available = ", ".join(cls._loaders.keys())
raise ValueError(
f"Unknown anchoring loader '{name}'. Available: {available}"
)
return cls._loaders[name]()
@classmethod
def detect(cls, path: str) -> Optional[AnchoringLoader]:
for loader_class in cls._loaders.values():
if loader_class.can_handle(path):
return loader_class()
return None
# Register built-in loaders — BrainGlobe first because its can_handle
# inspects JSON content (more specific than QuickNII's extension-only check).
AnchoringLoaderRegistry.register(BrainGlobeRegistrationLoader)
AnchoringLoaderRegistry.register(QuintAnchoringLoader)
[docs]
def read_alignment(
path: str,
loader_name: Optional[str] = None,
apply_deformation: bool = True,
apply_damage: bool = True,
deformation_provider: Optional[DeformationProvider] = None,
damage_provider: Optional[DamageProvider] = None,
) -> RegistrationData:
"""Load registration data with composable pipeline.
This is the main entry point for loading registration data. It supports
mixing and matching different components.
Args:
path: Path to the registration file.
loader_name: Explicit loader name, or None for auto-detection.
apply_deformation: Whether to apply deformation from the file.
Set False to use only linear anchoring.
apply_damage: Whether to apply damage masks from the file.
deformation_provider: Custom deformation provider to use instead of
the default (VisuAlign for QUINT files).
damage_provider: Custom damage provider to use instead of
the default (QCAlign for QUINT files).
Returns:
RegistrationData with all components applied.
Examples:
# Standard QUINT workflow
data = read_alignment("alignment.json")
# Linear only (no VisuAlign deformation)
data = read_alignment("alignment.json", apply_deformation=False)
# Separate anchoring and damage files
from .damage import QCAlignDamageProvider
data = read_alignment(
"quicknii.json",
damage_provider=QCAlignDamageProvider("qcalign_output.json")
)
"""
# 1. Load anchoring
if loader_name:
loader = AnchoringLoaderRegistry.get(loader_name)
else:
loader = AnchoringLoaderRegistry.detect(path)
if loader is None:
raise ValueError(f"Could not detect loader for '{path}'")
data = loader.load(path)
# 2. Apply deformation
if apply_deformation:
if deformation_provider:
data = deformation_provider.apply(data)
elif data.metadata.get("registration_type") == "brainglobe":
data = BrainGlobeDeformationProvider().apply(data)
else:
# Default: VisuAlign for QUINT files
data = VisuAlignDeformationProvider().apply(data)
# 3. Apply damage
if apply_damage:
if damage_provider:
data = damage_provider.apply(data)
else:
# Default: QCAlign for QUINT files
data = QCAlignDamageProvider().apply(data)
return data