Skip to content
Merged
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
178 changes: 178 additions & 0 deletions tests/test_config_workspace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import autoflow.api as api
from autoflow import AutoFlowConfig, build_workspace


def test_autoflow_config_defaults_cover_workspace_and_rendering_contract():
cfg = AutoFlowConfig()

assert cfg.inputs == []
assert cfg.output_dir == "./results"

assert cfg.skip_derived is False
assert cfg.skip_plane_metrics is False
assert cfg.use_multithread is True
assert cfg.reuse_planes == ""

assert cfg.use_center_plane is True
assert cfg.cross_section_dist == 5.0
assert cfg.start_dist == 5.0
assert cfg.end_dist == 0.0

assert cfg.remove_small_cc is True
assert cfg.min_cc_volume == 50.0

assert cfg.seed_ratio == 0.02
assert cfg.tube_radius == 0.05

assert cfg.fps == 12
assert cfg.plane_rotation_frames == 180
assert cfg.make_plane_video is False
assert cfg.make_wss_video is False
assert cfg.make_streamlines_video is False
assert cfg.make_tke_video is False

assert cfg.camera_view == "right"
assert cfg.camera_distance_scale == 1.5
assert cfg.rotate_dynamic_video is True
assert cfg.dynamic_rotation_frames == 180
assert cfg.dynamic_rotation_elevation_deg == 10.0
assert cfg.dynamic_time_repeat == 3

assert cfg.add_plane_idx is False
assert cfg.add_path_idx is False

assert cfg.wss_clim == (0.0, 10.0)
assert cfg.wss_bar_cfg == api.DEFAULT_WSS_BAR_CFG
assert cfg.wss_bar_cfg is not api.DEFAULT_WSS_BAR_CFG

assert cfg.tke_clim == (0.0, 100.0)
assert cfg.tke_bar_cfg == api.DEFAULT_TKE_BAR_CFG
assert cfg.tke_bar_cfg is not api.DEFAULT_TKE_BAR_CFG

assert cfg.streamline_clim == (0.0, 1)
assert cfg.streamline_bar_cfg == api.DEFAULT_STREAMLINE_BAR_CFG
assert cfg.streamline_bar_cfg is not api.DEFAULT_STREAMLINE_BAR_CFG


def test_build_workspace_maps_default_config_values():
ws = build_workspace()

assert ws.plane_gen_params.to_dict() == {
"use_center_plane": True,
"cross_section_distance": 5.0,
"start_distance": 5.0,
"end_distance": 0.0,
"smoothing_window": 15,
"smoothing_polyorder": 2,
"inter_time": 10,
}
assert ws.skeleton_params.to_dict() == {
"remove_small_cc": True,
"min_cc_volume_mm3": 50.0,
"do_closing": True,
"do_opening": False,
"gaussian_sigma": 0.5,
"gaussian_enabled": True,
}
assert ws.streamline_params.to_dict() == {
"seed_ratio": 0.02,
"max_steps": 2000,
"min_seeds": 50,
"terminal_speed": 0.01,
"rng_seed": 0,
}
assert getattr(ws.streamline_params, "tube_radius") == 0.05


def test_build_workspace_maps_custom_plane_generation_parameters():
config = AutoFlowConfig(
use_center_plane=False,
cross_section_dist=12.5,
start_dist=1.5,
end_dist=2.5,
)

ws = build_workspace(config)

assert ws.plane_gen_params.to_dict() == {
"use_center_plane": False,
"cross_section_distance": 12.5,
"start_distance": 1.5,
"end_distance": 2.5,
"smoothing_window": 15,
"smoothing_polyorder": 2,
"inter_time": 10,
}


def test_build_workspace_maps_skeleton_and_streamline_parameters():
config = AutoFlowConfig(
remove_small_cc=False,
min_cc_volume=42.0,
seed_ratio=0.03,
tube_radius=0.12,
)

ws = build_workspace(config)

assert ws.skeleton_params.to_dict() == {
"remove_small_cc": False,
"min_cc_volume_mm3": 42.0,
"do_closing": True,
"do_opening": False,
"gaussian_sigma": 0.5,
"gaussian_enabled": True,
}
assert ws.streamline_params.to_dict() == {
"seed_ratio": 0.03,
"max_steps": 2000,
"min_seeds": 50,
"terminal_speed": 0.01,
"rng_seed": 0,
}
assert getattr(ws.streamline_params, "tube_radius") == 0.12


def test_run_case_forwards_rendering_defaults_to_process_single(monkeypatch, tmp_path):
captured = {}

def fake_process_single(input_path, case_dir, **kwargs):
captured["input_path"] = input_path
captured["case_dir"] = case_dir
captured.update(kwargs)
return {"status": "ok", "case_dir": case_dir}

monkeypatch.setattr(api, "process_single", fake_process_single)

cfg = AutoFlowConfig()
out_dir = tmp_path / "case"
summary = api.run_case("demo_input.h5", output_dir=str(out_dir), config=cfg)

assert summary == {"status": "ok", "case_dir": str(out_dir)}
assert captured["input_path"] == "demo_input.h5"
assert captured["case_dir"] == str(out_dir)

assert captured["workspace"].plane_gen_params.cross_section_distance == 5.0
assert captured["fps"] == 12
assert captured["plane_rotation_frames"] == 180
assert captured["make_plane_video"] is False
assert captured["make_wss_video"] is False
assert captured["make_streamlines_video"] is False
assert captured["make_tke_video"] is False
assert captured["camera_view"] == "right"
assert captured["camera_distance_scale"] == 1.5
assert captured["rotate_dynamic_video"] is True
assert captured["dynamic_rotation_frames"] == 180
assert captured["dynamic_rotation_elevation_deg"] == 10.0
assert captured["dynamic_time_repeat"] == 3
assert captured["add_plane_idx"] is False
assert captured["add_path_idx"] is False
assert captured["wss_clim"] == (0.0, 10.0)
assert captured["tke_clim"] == (0.0, 100.0)
assert captured["streamline_clim"] == (0.0, 1)
assert captured["wss_bar_cfg"] == api.DEFAULT_WSS_BAR_CFG
assert captured["tke_bar_cfg"] == api.DEFAULT_TKE_BAR_CFG
assert captured["streamline_bar_cfg"] == api.DEFAULT_STREAMLINE_BAR_CFG
assert captured["wss_bar_cfg"] is not cfg.wss_bar_cfg
assert captured["tke_bar_cfg"] is not cfg.tke_bar_cfg
assert captured["streamline_bar_cfg"] is not cfg.streamline_bar_cfg
130 changes: 130 additions & 0 deletions tests/test_demo_workflows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import json
import os
from pathlib import Path
import subprocess
import sys

import numpy as np

from autoflow import AutoFlowConfig, run_batch


REPO_ROOT = Path(__file__).resolve().parents[1]
DEMO_INPUT = REPO_ROOT / "data" / "demo_data.h5"
EXPECTED_PIXELWISE_KEYS = {"origin", "spacing", "tke", "tke_time", "wss"}
NOTEBOOK_VIDEO_KEYS = {"streamlines", "tke", "wss"}


def _run_cli(*args: str) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
existing = env.get("PYTHONPATH", "")
env["PYTHONPATH"] = str(REPO_ROOT) if not existing else os.pathsep.join((str(REPO_ROOT), existing))
return subprocess.run(
[sys.executable, "-m", "autoflow.cli", *args],
cwd=REPO_ROOT,
env=env,
capture_output=True,
text=True,
check=False,
)


def _assert_demo_outputs(output_root: Path, expected_video_keys: set[str]) -> dict:
case_dir = output_root / "demo_data"
summary_path = case_dir / "summary.json"
plane_positions_path = case_dir / "plane_positions.json"
planes_path = case_dir / "planes.json"
plane_metrics_path = case_dir / "plane_metrics.json"
plane_qc_path = case_dir / "plane_qc.json"
pixelwise_path = case_dir / "derived_metrics_pixelwise.npz"
batch_report_path = output_root / "batch_report.json"
time_summary_path = output_root / "time_summary.txt"

for path in (
summary_path,
plane_positions_path,
planes_path,
plane_metrics_path,
plane_qc_path,
pixelwise_path,
batch_report_path,
time_summary_path,
):
assert path.is_file(), path

summary = json.loads(summary_path.read_text(encoding="utf-8"))
assert summary["input"].endswith("data/demo_data.h5")
assert summary["output_dir"] == str(case_dir)
assert summary["n_planes"] >= 1
assert summary["n_paths"] >= 1
assert summary["n_forks"] >= 1
assert len(summary["plane_metrics"]) == summary["n_planes"]
assert set(summary["pixelwise_export"]) == EXPECTED_PIXELWISE_KEYS
assert Path(summary["plane_positions_file"]).is_file()
assert summary["reused_planes_file"] == ""

batch_report = json.loads(batch_report_path.read_text(encoding="utf-8"))
assert len(batch_report) == 1
assert batch_report[0]["status"] == "ok"
assert batch_report[0]["file"].endswith("data/demo_data.h5")

assert set(summary["videos"]) == expected_video_keys
for key in expected_video_keys:
video_path = Path(summary["videos"][key])
assert video_path.is_file(), video_path
assert video_path.suffix in {".gif", ".mp4"}

pixelwise = np.load(pixelwise_path)
assert set(pixelwise.files) == EXPECTED_PIXELWISE_KEYS

return summary


def test_cli_demo_command_processes_demo_data(tmp_path):
output_root = tmp_path / "cli_demo"

result = _run_cli(str(DEMO_INPUT), "--output-dir", str(output_root))

assert result.returncode == 0, result.stderr or result.stdout
assert "Found 1 file(s) to process." in result.stdout
assert "Done: 1/1 succeeded." in result.stdout

summary = _assert_demo_outputs(output_root, expected_video_keys=set())
assert summary["videos"] == {}


def test_library_demo_notebook_workflow_processes_demo_data(tmp_path):
output_root = tmp_path / "library_demo"
config = AutoFlowConfig(
inputs=[str(DEMO_INPUT)],
output_dir=str(output_root),
skip_derived=False,
use_multithread=True,
reuse_planes="",
use_center_plane=True,
cross_section_dist=15.0,
start_dist=5.0,
end_dist=0.0,
remove_small_cc=True,
min_cc_volume=50.0,
make_plane_video=False,
make_wss_video=True,
make_streamlines_video=True,
make_tke_video=True,
camera_view="right",
camera_distance_scale=1.5,
rotate_dynamic_video=True,
dynamic_rotation_frames=180,
dynamic_rotation_elevation_deg=10.0,
dynamic_time_repeat=3,
add_path_idx=False,
)

results, case_out = run_batch(config)

assert len(results) == 1
assert results[0]["status"] == "ok"
assert case_out == str(output_root / "demo_data")

summary = _assert_demo_outputs(output_root, expected_video_keys=NOTEBOOK_VIDEO_KEYS)
assert set(summary["videos"]) == NOTEBOOK_VIDEO_KEYS
Loading