Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions examples/z_align_config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Example z-align configuration matching the MATLAB z-shift scripts.
# Assumes these files are present in `root`:
# - compensated.tiff
# - file_00004_00001.tif

# === Data Location ===
root = "." # Run from folder containing MATLAB-style inputs
input_file = "compensated.tiff" # Recording to z-correct
volume_input_file = "file_00004_00001.tif" # Input for reference volume creation
reference_source_file = "compensated.tiff" # Used to compute Stage-1 reference image

# MATLAB snippet: get_video_file_reader("compensated.tiff", 10, 20); mean(first 2000)
reference_source_frames = 2000
reference_source_buffer_size = 10
reference_source_bin_size = 20

# === Output Paths ===
# Kept identical to MATLAB output names/locations.
output_root = "."
volume_output_dir = "aligned_stack"
z_shift_file = "z_shift.HDF5"
corrected_output_file = "compensated_shift_corrected.tif"
simulated_output_file = "simulated_from_z.tif"

# === Pipeline Controls ===
resume = true
write_corrected = true # direct z-corrected signal
write_simulated = true # baseline simulated from z_shift + volume

# === Stage 1: Reference Volume Build (compensate_recording) ===
stage1_alpha = 5.0
stage1_quality_setting = "quality"
stage1_buffer_size = 500
stage1_bin_size = 1
stage1_update_reference = true
flow_backend = "flowreg"

# === Stage 2: Patch-Based z Estimation ===
input_buffer_size = 50
input_bin_size = 1
volume_buffer_size = 500
volume_bin_size = 1

win_half = 10
patch_size = 128
overlap = 0.75

spatial_sigma = 1.5
temporal_sigma = 1.5
z_smooth_sigma_spatial = 5.0
z_smooth_sigma_temporal = 1.5
parabolic_tau_scale = 1e-3

output_dtype = "uint16"

[backend_params]
91 changes: 91 additions & 0 deletions examples/z_shift_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""
Z-Shift Demo - MATLAB-style z alignment workflow

This example assumes the same input files as the MATLAB scripts:
- compensated.tiff (time recording to z-correct)
- file_00004_00001.tif (stack/source for reference volume creation)

Outputs (matching MATLAB names) are written to the working directory:
- aligned_stack/compensated.HDF5
- z_shift.HDF5
- compensated_shift_corrected.tif
- simulated_from_z.tif
"""

from pathlib import Path

from pyflowreg.z_align import ZAlignConfig, run_all_stages


def main():
root = Path(".").resolve()

required = [root / "compensated.tiff", root / "file_00004_00001.tif"]
missing = [p.name for p in required if not p.exists()]
if missing:
raise FileNotFoundError(
"Missing required input files in working directory: " + ", ".join(missing)
)

config = ZAlignConfig(
root=root,
# MATLAB-style inputs
input_file="compensated.tiff",
volume_input_file="file_00004_00001.tif",
reference_source_file="compensated.tiff",
# MATLAB script: read first 2000 frames with buffer/bin (10, 20)
reference_source_frames=2000,
reference_source_buffer_size=10,
reference_source_bin_size=20,
# Keep MATLAB output paths/names
output_root=".",
volume_output_dir="aligned_stack",
z_shift_file="z_shift.HDF5",
corrected_output_file="compensated_shift_corrected.tif",
simulated_output_file="simulated_from_z.tif",
# Stage toggles:
# write_corrected=True -> direct z-corrected signal
# write_simulated=True -> baseline simulated from z-shifts
write_corrected=True,
write_simulated=True,
resume=True,
# Stage 1 (volume build) defaults from MATLAB snippet
stage1_alpha=5.0,
stage1_quality_setting="quality",
stage1_buffer_size=500,
stage1_bin_size=1,
stage1_update_reference=True,
# Stage 2 (patch-based z estimation) defaults from MATLAB snippet
input_buffer_size=50,
input_bin_size=1,
win_half=10,
patch_size=128,
overlap=0.75,
spatial_sigma=1.5,
temporal_sigma=1.5,
z_smooth_sigma_spatial=5.0,
z_smooth_sigma_temporal=1.5,
)

print("=" * 60)
print("Z-SHIFT DEMO")
print("=" * 60)
print(f"Root: {root}")
print("Input recording: compensated.tiff")
print("Volume source: file_00004_00001.tif")

outputs = run_all_stages(config)

print("\n" + "=" * 60)
print("DEMO COMPLETE")
print("=" * 60)
print(f"Reference volume: {outputs['volume_path']}")
print(f"Z-shift file: {outputs['z_shift_path']}")
if outputs["corrected_path"] is not None:
print(f"Corrected signal: {outputs['corrected_path']}")
if outputs["simulated_path"] is not None:
print(f"Simulated baseline:{outputs['simulated_path']}")


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ dependencies = [

[project.scripts]
pyflowreg-session = "pyflowreg.session.cli:main"
pyflowreg-z-align = "pyflowreg.z_align.cli:main"

[project.optional-dependencies]
vis = [
Expand Down
26 changes: 26 additions & 0 deletions src/pyflowreg/z_align/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Z-alignment pipeline for depth-shift correction.

The ``z_align`` module implements a stage-based workflow that mirrors the
MATLAB prototype used for z-shift estimation/correction:

1. Build/load a reference volume
2. Estimate per-pixel z shifts and optionally write z-corrected output
3. Optionally simulate a baseline recording from the estimated z shifts
"""

from pyflowreg.z_align.config import ZAlignConfig
from pyflowreg.z_align.pipeline import (
run_stage1,
run_stage2,
run_stage3,
run_all_stages,
)

__all__ = [
"ZAlignConfig",
"run_stage1",
"run_stage2",
"run_stage3",
"run_all_stages",
]
136 changes: 136 additions & 0 deletions src/pyflowreg/z_align/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
"""
Command-line interface for z-alignment workflows.

Provides the ``pyflowreg-z-align`` command.
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path
from typing import Any, Dict, Optional

from pyflowreg.z_align.config import ZAlignConfig
from pyflowreg.z_align.pipeline import (
run_all_stages,
run_stage1,
run_stage2,
run_stage3,
)


def _parse_value(raw: str) -> Any:
"""Parse CLI override values."""
lower = raw.lower()
if lower == "true":
return True
if lower == "false":
return False

for cast in (int, float):
try:
return cast(raw)
except ValueError:
pass

# Optional JSON parsing for lists/dicts
if raw.startswith("[") or raw.startswith("{"):
try:
return json.loads(raw)
except json.JSONDecodeError:
return raw
return raw


def _parse_overrides(params: Optional[list[str]]) -> Dict[str, Any]:
"""Parse KEY=VALUE CLI overrides."""
overrides: Dict[str, Any] = {}
if not params:
return overrides

for item in params:
if "=" not in item:
print(f"Warning: ignoring malformed override '{item}' (expected KEY=VALUE)")
continue
key, value = item.split("=", 1)
overrides[key] = _parse_value(value)
return overrides


def cmd_run(args: argparse.Namespace) -> None:
"""Handle the ``run`` subcommand."""
config_path = Path(args.config)
if not config_path.exists():
print(f"Error: configuration file not found: {config_path}")
sys.exit(1)

config = ZAlignConfig.from_file(config_path)
overrides = _parse_overrides(args.of_params)

if args.stage == "1":
run_stage1(config, overrides or None)
return

if args.stage == "2":
run_stage2(config)
return

if args.stage == "3":
run_stage3(config)
return

run_all_stages(config, overrides or None)


def main() -> None:
"""CLI entry point."""
parser = argparse.ArgumentParser(
description="PyFlowReg z-shift alignment pipeline",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Run full z-align workflow
pyflowreg-z-align run --config z_align.toml

# Run only z-shift estimation/correction
pyflowreg-z-align run --config z_align.toml --stage 2

# Override stage-1 OFOptions from CLI
pyflowreg-z-align run --config z_align.toml --of-params alpha=8 quality_setting=balanced
""",
)

subparsers = parser.add_subparsers(dest="command", help="Command to run")

run_parser = subparsers.add_parser("run", help="Run z-align processing")
run_parser.add_argument(
"--config",
"-c",
required=True,
help="Path to z-align config file (.toml/.yaml/.yml)",
)
run_parser.add_argument(
"--stage",
"-s",
choices=["1", "2", "3"],
help="Run only one stage (default: run all applicable stages)",
)
run_parser.add_argument(
"--of-params",
nargs="*",
metavar="KEY=VALUE",
help="Override stage-1 OFOptions parameters",
)
run_parser.set_defaults(func=cmd_run)

args = parser.parse_args()
if not hasattr(args, "func"):
parser.print_help()
sys.exit(1)
args.func(args)


if __name__ == "__main__":
main()
Loading