import shutil
from pathlib import Path
import h5py
import numpy as np
import pytest
from ewoksutils.exceptions import TaskExecutionError
from ..tasks.reshape_aeroystepscan import ReshapeAero
from .conftest import write_integration_output as _write_integration_output
BLISS_MASTER = Path(__file__).parent / "data" / "test_aeroystepscan.h5"
# BLISS_MASTER records an aeroystepscan with 36 rotation points (x_range=360 in
# steps of 10 from 0) at each of 3 translation points. The rotation motor keeps
# turning between rows, so rows 1 and 2 wrap through 360 degrees and are not
# recorded in ascending order.
N_TRANS = 3
N_ROT = 36
N_POINTS = N_TRANS * N_ROT
# Parameters to use to generate test ai file
N_RADIAL = 1500
RADIAL_AXIS = np.linspace(0.003, 5.477, N_RADIAL, dtype=np.float32)
INTENSITY = np.arange(N_POINTS * N_RADIAL, dtype=np.float32).reshape(N_POINTS, N_RADIAL)
[docs]
@pytest.fixture(scope="module")
def ai_file(tmp_path_factory):
"""An azimuthal-integration output with one frame per scan point, built
once and shared across tests since they only ever read it."""
path = tmp_path_factory.mktemp("ai") / "ai.h5"
_write_integration_output(path, radial_axis=RADIAL_AXIS, intensity=INTENSITY)
return path
def _recorded_positions(motor_name):
"""The positions recorded for `motor_name` in BLISS_MASTER."""
with h5py.File(BLISS_MASTER, "r") as f:
return f[f"1.1/measurement/{motor_name}"][()]
def _copy_master_with_overrides(tmp_path, overrides):
"""Copy BLISS_MASTER, replacing or adding the datasets named by the HDF5
paths in `overrides`, to build a master that differs from the recorded
scan."""
master = tmp_path / "master.h5"
shutil.copy(BLISS_MASTER, master)
with h5py.File(master, "r+") as f:
for path, value in overrides.items():
if path in f:
del f[path]
f[path] = value
return master
def _make_task(ai_file, master=BLISS_MASTER, **input_overrides):
inputs = {
"bliss_master_path": str(master),
"integration_output_path": str(ai_file),
"scan_number": 1,
"translation_motor_name": "hry",
"rotation_motor_name": "hrrz",
"integration_intensity_path": "/intensities",
"integration_radial_axis_path": "/2th_deg",
}
inputs.update(input_overrides)
return ReshapeAero(inputs=inputs)
[docs]
def test_run_wrong_motor_name_raises(ai_file):
"""A motor name that doesn't exist in the bliss master file raises ValueError."""
task = _make_task(ai_file, rotation_motor_name="does_not_exist")
with pytest.raises(TaskExecutionError, match="does_not_exist") as excinfo:
task.execute()
assert isinstance(excinfo.value.__cause__, ValueError)
[docs]
def test_run_wrong_integration_path_raises(ai_file):
"""A radial axis or intensity HDF5 path that does not exist raises ValueError."""
task = _make_task(ai_file, integration_intensity_path="/does/not/exist")
with pytest.raises(TaskExecutionError, match="/does/not/exist") as excinfo:
task.execute()
assert isinstance(excinfo.value.__cause__, ValueError)
[docs]
def test_run_warns_on_rotation_count_mismatch(tmp_path, ai_file):
"""A mismatch between the computed and the fscan-derived rotation count warns."""
# x_range=400 with x_step_size=10 implies 40 rotation points, against the 36
# the recorded array length gives.
master = _copy_master_with_overrides(
tmp_path, {"1.1/instrument/fscan_parameters/x_range": 400}
)
task = _make_task(ai_file, master=master)
with pytest.warns(UserWarning, match="reshaped incorrectly"):
task.execute()