from os import PathLike
from typing import List
import numpy
import pytest
from ewoksorange.tests.utils import execute_task
from orangecontrib.ewoksxrpd.calculategeometry import OWCalculateGeometry
from orangecontrib.ewoksxrpd.calibratemulti import OWCalibrateMulti
from orangecontrib.ewoksxrpd.calibratesingle import OWCalibrateSingle
from orangecontrib.ewoksxrpd.diagnose_multicalib import OWDiagnoseCalibrateMultiResults
from orangecontrib.ewoksxrpd.diagnose_singlecalib import (
OWDiagnoseCalibrateSingleResults,
)
from ..pyfai_api import AzimuthalIntegrator
from ..tasks import calibrate
from .xrpd_theory import Calibration
from .xrpd_theory import Setup
[docs]
@pytest.mark.parametrize(
"fixed",
[[], ["energy"], ["rot2", "dist"]],
ids=["fixed=none", "fixed=energy", "fixed=rot2,dist"],
)
def test_calibrate_single_distance_task(
fixed: List[str],
imageSetup1Calibrant1: Calibration,
setup1: Setup,
aiSetup1: AzimuthalIntegrator,
tmpdir: PathLike,
):
assert_calibrate_single_distance(
fixed, imageSetup1Calibrant1, setup1, aiSetup1, tmpdir, None
)
[docs]
@pytest.mark.parametrize(
"fixed",
[[], ["energy"], ["rot2", "dist"]],
ids=["fixed=none", "fixed=energy", "fixed=rot2,dist"],
)
def test_calibrate_multi_distance_task(
fixed: List[str],
imageSetup1Calibrant1: Calibration,
setup1: Setup,
imageSetup2Calibrant1: Calibration,
setup2: Setup,
aiSetup1: AzimuthalIntegrator,
aiSetup2: AzimuthalIntegrator,
tmpdir: PathLike,
):
assert_calibrate_multi_distance(
fixed,
imageSetup1Calibrant1,
setup1,
imageSetup2Calibrant1,
setup2,
aiSetup1,
aiSetup2,
tmpdir,
None,
)
[docs]
def test_calibrate_single_multiple_max_rings(
imageSetup1Calibrant1: Calibration,
setup1: Setup,
aiSetup1: AzimuthalIntegrator,
tmpdir: PathLike,
):
fixed = ["energy"]
geometry0, energy0 = guess_fit_parameters(
setup1.geometry, setup1.energy, aiSetup1, fixed=fixed
)
inputs = {
"image": imageSetup1Calibrant1.image,
"geometry": geometry0,
"detector": setup1.detector,
"energy": energy0,
"calibrant": imageSetup1Calibrant1.calibrant,
"fixed": fixed,
"max_rings": [2, 4, 6],
}
outputs_values = execute_task(
OWCalibrateSingle.ewokstaskclass,
inputs=inputs,
timeout=3,
)
assert_calibration(
False,
fixed,
outputs_values["geometry"],
outputs_values["energy"],
outputs_values["rings"],
setup1,
imageSetup1Calibrant1,
aiSetup1,
tmpdir,
)
[docs]
def assert_calibrate_single_distance(
fixed: List[str],
imageSetup1Calibrant1: Calibration,
setup1: Setup,
aiSetup1: AzimuthalIntegrator,
tmpdir: PathLike,
qtapp: None,
):
geometry0, energy0 = guess_fit_parameters(
setup1.geometry, setup1.energy, aiSetup1, fixed=fixed
)
inputs = {
"image": imageSetup1Calibrant1.image,
"geometry": geometry0,
"detector": setup1.detector,
"energy": energy0,
"calibrant": imageSetup1Calibrant1.calibrant,
"fixed": fixed,
}
outputs_values = execute_task(
OWCalibrateSingle.ewokstaskclass if qtapp is None else OWCalibrateSingle,
inputs=inputs,
timeout=10,
)
assert_calibration(
False,
fixed,
outputs_values["geometry"],
outputs_values["energy"],
outputs_values["rings"],
setup1,
imageSetup1Calibrant1,
aiSetup1,
tmpdir,
)
[docs]
def assert_calibrate_multi_distance(
fixed: List[str],
imageSetup1Calibrant1: Calibration,
setup1: Setup,
imageSetup2Calibrant1: Calibration,
setup2: Setup,
aiSetup1: AzimuthalIntegrator,
aiSetup2: AzimuthalIntegrator,
tmpdir: PathLike,
qtapp,
):
images = [imageSetup1Calibrant1.image, imageSetup2Calibrant1.image]
positionunits_in_meter = 1e-2 # cm
positions = [
setup1.geometry["dist"] / positionunits_in_meter,
setup2.geometry["dist"] / positionunits_in_meter,
]
geometry0, energy0 = guess_fit_parameters(
setup1.geometry, setup1.energy, aiSetup1, fixed=fixed
)
reference_position = positions[0]
inputs = {
"images": images,
"positions": positions,
"positionunits_in_meter": positionunits_in_meter,
"reference_position": reference_position,
"geometry": geometry0,
"detector": setup1.detector,
"energy": energy0,
"calibrant": imageSetup1Calibrant1.calibrant,
"fixed": fixed,
}
calibresults = execute_task(
OWCalibrateMulti.ewokstaskclass if qtapp is None else OWCalibrateMulti,
inputs=inputs,
)
assert_calibration(
True,
fixed,
calibresults["geometry"],
calibresults["energy"],
calibresults["rings"]["0"],
setup1,
imageSetup1Calibrant1,
aiSetup1,
tmpdir,
)
geometry = dict(calibresults["geometry"])
shift_in_meter = setup2.geometry["dist"] - setup1.geometry["dist"]
geometry["dist"] += shift_in_meter
assert_calibration(
True,
fixed,
geometry,
calibresults["energy"],
calibresults["rings"]["1"],
setup2,
imageSetup2Calibrant1,
aiSetup2,
tmpdir,
)
inputs = {
"parametrization": calibresults["parametrization"],
"parameters": calibresults["parameters"],
"position": positions[0],
}
calcresults = execute_task(
OWCalculateGeometry.ewokstaskclass if qtapp is None else OWCalculateGeometry,
inputs=inputs,
)
assert_geometry(
True,
fixed,
calcresults["geometry"],
calcresults["energy"],
setup1,
aiSetup1,
)
inputs["position"] = positions[1]
calcresults = execute_task(
OWCalculateGeometry.ewokstaskclass if qtapp is None else OWCalculateGeometry,
inputs=inputs,
)
assert_geometry(
True,
fixed,
calcresults["geometry"],
calcresults["energy"],
setup2,
aiSetup2,
)
# Set show=True to visualize the calibration results
filename = tmpdir / "test.png"
inputs = {
"images": images,
"positions": positions,
"detector": setup1.detector,
"calibrant": imageSetup1Calibrant1.calibrant,
"rings": calibresults["rings"],
"parametrization": calibresults["parametrization"],
"parameters": calibresults["parameters"],
"show": False,
"filename": str(filename),
}
execute_task(
(
OWDiagnoseCalibrateMultiResults.ewokstaskclass
if qtapp is None
else OWDiagnoseCalibrateMultiResults
),
inputs=inputs,
)
assert filename.exists()
[docs]
def assert_calibration(
multi_distance: bool,
fixed: List[str],
geometry: dict,
energy: float,
rings: dict,
setup: Setup,
calibration: Calibration,
ai: AzimuthalIntegrator,
tmpdir: PathLike,
qtapp=None,
):
assert_geometry(multi_distance, fixed, geometry, energy, setup, ai)
filename = tmpdir / "diagnose.png"
inputs = {
"image": calibration.image,
"geometry": geometry,
"detector": setup.detector,
"energy": energy,
"calibrant": calibration.calibrant,
"rings": rings,
"show": False,
"filename": str(filename),
}
execute_task(
(
OWDiagnoseCalibrateSingleResults.ewokstaskclass
if qtapp is None
else OWDiagnoseCalibrateSingleResults
),
inputs=inputs,
timeout=10,
)
assert filename.exists()
[docs]
def guess_fit_parameters(
geometry: dict,
energy: float,
ai: AzimuthalIntegrator,
fixed: List[str],
) -> dict:
"""Start fitting with parameters that are reasonably close to the real values"""
def modify(pname, real_value):
if pname in fixed:
return real_value
elif pname.startswith("rot"):
# wrong by 1 degree
return real_value + numpy.radians(0.1)
elif pname == "dist":
# wrong by 1 mm
return real_value + 1e-3
elif pname == "poni1":
# wrong by 5 pixels
return real_value + 5 * ai.detector.pixel1
elif pname == "poni2":
# wrong by 5 pixels
return real_value - 5 * ai.detector.pixel2
elif pname == "energy":
# wrong by 50 eV
return real_value + 50e-3
else:
raise ValueError(pname)
geometry = {
pname: modify(pname, real_value) for pname, real_value in geometry.items()
}
return geometry, modify("energy", energy)
[docs]
def assert_geometry(
multi_distance: bool,
fixed: List[str],
geometry: dict,
energy: float,
setup: Setup,
ai: AzimuthalIntegrator,
):
"""Energy and distance cannot be distinguished by a single distance calibration"""
expected_parameters = dict(setup.geometry)
expected_parameters["energy"] = setup.energy
parameters = dict(geometry)
parameters["energy"] = energy
assert parameters.keys() == expected_parameters.keys()
for pname, expected in expected_parameters.items():
if pname in fixed:
margin = 0
elif pname.startswith("rot"):
# wrong by +/- 0.2 degree
margin = numpy.radians(0.2)
elif pname == "dist":
if not multi_distance:
continue
# wrong by +/- 50 um
margin = 50e-6
elif pname == "energy":
if not multi_distance:
continue
# wrong by +/- 5 eV
margin = 5e-3
elif pname == "poni1":
# wrong by +/- 0.5 pixels
margin = 0.5 * ai.detector.pixel1
elif pname == "poni2":
# wrong by +/- 0.5 pixels
margin = 0.5 * ai.detector.pixel2
else:
raise ValueError(pname)
refined_value = parameters[pname]
err_msg = f"{pname}: {refined_value} too far from {expected}"
assert pytest.approx(expected, abs=margin) == refined_value, err_msg
[docs]
def test_fixed_parameters_sets():
all_parameters = {
"dist_offset",
"dist_scale",
"poni1_offset",
"poni2_offset",
"arot1",
"arot2",
"arot3",
"poni1_scale",
"poni2_scale",
"energy",
}
# Release incrementally until we have nothing fixed at the end
always_fixed_params = set()
incremental_release_groups = [
{"dist_offset", "dist_scale"},
{"poni1_offset", "poni2_offset"},
{"arot1", "arot2", "arot3"},
{"poni1_scale", "poni2_scale"},
{"energy"},
]
fixed_parameter_sets = calibrate._fixed_parameters_sets(
all_parameters, always_fixed_params, incremental_release_groups
)
expected = [
{
"poni1_offset",
"poni2_offset",
"arot1",
"arot2",
"arot3",
"poni1_scale",
"poni2_scale",
"energy",
},
{"arot1", "arot2", "arot3", "poni1_scale", "poni2_scale", "energy"},
{"poni1_scale", "poni2_scale", "energy"},
{"energy"},
None,
]
assert fixed_parameter_sets == expected
# Skip redundant release steps
always_fixed_params = set()
incremental_release_groups = [
set(),
{"dist_offset", "dist_scale"},
{"poni1_offset", "poni2_offset"},
{"arot1", "arot2", "arot3"},
set(),
{"arot1", "arot2", "arot3", "poni2_offset"},
{"poni1_scale", "poni2_scale"},
{"energy"},
{"energy"},
set(),
]
fixed_parameter_sets = calibrate._fixed_parameters_sets(
all_parameters, always_fixed_params, incremental_release_groups
)
expected = [
{
"poni1_offset",
"poni2_offset",
"arot1",
"arot2",
"arot3",
"poni1_scale",
"poni2_scale",
"energy",
},
{"arot1", "arot2", "arot3", "poni1_scale", "poni2_scale", "energy"},
{"poni1_scale", "poni2_scale", "energy"},
{"energy"},
None,
]
assert fixed_parameter_sets == expected
# Release incrementally but keep arot3 and energy fixed
always_fixed_params = {"arot3", "energy"}
incremental_release_groups = [
{"dist_offset", "dist_scale"},
{"poni1_offset", "poni2_offset"},
{"arot1", "arot2", "arot3"},
{"poni1_scale", "poni2_scale"},
{"energy"},
]
fixed_parameter_sets = calibrate._fixed_parameters_sets(
all_parameters, always_fixed_params, incremental_release_groups
)
expected = [
{
"poni1_offset",
"poni2_offset",
"arot1",
"arot2",
"arot3",
"poni1_scale",
"poni2_scale",
"energy",
},
{"arot1", "arot2", "arot3", "poni1_scale", "poni2_scale", "energy"},
{"arot3", "poni1_scale", "poni2_scale", "energy"},
{"arot3", "energy"},
]
assert fixed_parameter_sets == expected
# Release all parameters at once
always_fixed_params = set()
incremental_release_groups = [all_parameters]
fixed_parameter_sets = calibrate._fixed_parameters_sets(
all_parameters, always_fixed_params, incremental_release_groups
)
expected = [None]
assert fixed_parameter_sets == expected
# Release all parameters at once but keep energy fixed
always_fixed_params = {"energy"}
incremental_release_groups = [all_parameters]
fixed_parameter_sets = calibrate._fixed_parameters_sets(
all_parameters, always_fixed_params, incremental_release_groups
)
expected = [{"energy"}]
assert fixed_parameter_sets == expected