Source code for thesis.workflows.registration.viewers
"""Viewer backends for registration quality control."""
from __future__ import annotations
import subprocess
from pathlib import Path
from typing import Sequence
from thesis.core.logging import get_logger
logger = get_logger(__name__)
__all__ = [
"build_fsleyes_command",
"describe_registration_viewer_command",
"launch_registration_viewer",
]
[docs]
def build_fsleyes_command(
template_image: Path,
warped_image: Path,
overlay_opacity: float = 0.5,
) -> list[str]:
"""Build an FSLeyes command for template/overlay registration review."""
alpha = int(max(0.0, min(1.0, overlay_opacity)) * 100)
return [
"fsleyes",
str(template_image),
str(warped_image),
"-a",
str(alpha),
]
def _spawn_detached(command: Sequence[str]) -> None:
"""Start a detached background process without blocking the workflow."""
subprocess.Popen(
list(command),
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True,
)
def _resolve_command(
template_image: Path,
warped_image: Path,
backend: str,
overlay_opacity: float,
) -> list[str]:
"""Validate the requested backend and build its viewer command."""
if backend == "html":
raise NotImplementedError("HTML registration viewer backend is not implemented yet")
if backend != "fsleyes":
raise ValueError(f"Unsupported registration viewer backend: {backend}")
return build_fsleyes_command(template_image, warped_image, overlay_opacity)
[docs]
def describe_registration_viewer_command(
template_image: Path,
warped_image: Path,
backend: str = "fsleyes",
overlay_opacity: float = 0.5,
) -> str:
"""Return a shell-ready viewer command string for manual registration QC."""
command = _resolve_command(template_image, warped_image, backend, overlay_opacity)
return " ".join(command)
[docs]
def launch_registration_viewer(
template_image: Path,
warped_image: Path,
backend: str = "fsleyes",
overlay_opacity: float = 0.5,
) -> list[str]:
"""Launch the configured registration QC viewer and return its command."""
command = _resolve_command(template_image, warped_image, backend, overlay_opacity)
logger.info("Launching registration viewer: {}", " ".join(command))
_spawn_detached(command)
return command