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
4 changes: 3 additions & 1 deletion src/crystallography_bluesky/i15_1/plans/blower_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
setup_zebra_for_hardware_triggering,
)
from crystallography_bluesky.i15_1.plans.single_temperature_collection import (
Q_MAX_OPTIONS,
CollectionSpecPerTemp,
TemperatureCollectionSpecification,
get_collection_specification,
Expand Down Expand Up @@ -58,6 +59,7 @@ def blower_collection(
ramp_rate_c_per_min: float,
settle_time: float,
scan_type: ScanType,
q_max: Q_MAX_OPTIONS | None = None,
generic_collection_devices: GenericCollectionDevices = devices,
blower: Blower = blower,
baseline_devices: list[StandardReadable] | None = None,
Expand All @@ -69,7 +71,7 @@ def blower_collection(
yield from bps.abs_set(blower.ramp_rate_c_per_sec, ramp_rate_c_per_min / 60)

single_collection_spec, total_frames = get_collection_specification(
time_per_collection, exposure_time_per_frame
time_per_collection, exposure_time_per_frame, q_max
)
minimum_dead_time = 0.0001 # Minimum value independent of timebase
trigger_pulse_width = 0.0001 # Assumes zebra is set to seconds timebase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,15 @@ def no_temperature_must_not_have_units(self) -> Self:

TemperatureCollectionSpecification: TypeAlias = list[CollectionSpecPerTemp]

COLLECTION_SPEC_FILEPATH = (
"/dls_sw/i15-1/software/daq_configuration/collection_specification.txt"
BASE_COLLECTION_SPEC_FILEPATH = (
"/dls_sw/i15-1/software/daq_configuration/"
+ "collection_specifications/collection_specification"
)

devices = inject("")

Q_MAX_OPTIONS = Literal[25, 30, 35, 40]


def _calculate_number_of_frames(
fraction_of_time: float,
Expand All @@ -90,8 +93,14 @@ def _calculate_number_of_frames(
return ceil((fraction_of_time * full_collection_time) / exposure_time_per_frame)


def _get_collection_spec_filepath(q_max: Q_MAX_OPTIONS | None):

@jacob-williamson jacob-williamson Nov 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should live in the queue and the plan takes the filepath as an argument? Would make it easier to test different collection specs

return BASE_COLLECTION_SPEC_FILEPATH + (f"_{str(q_max)}" if q_max else "") + ".txt"


def get_collection_specification(
full_collection_time: float, exposure_time_per_frame: float
full_collection_time: float,
exposure_time_per_frame: float,
q_max: Q_MAX_OPTIONS | None = None,
) -> tuple[CollectionSpecification, int]:
"""The standard collection specification is defined in configuration but needs
conversion:
Expand All @@ -100,9 +109,11 @@ def get_collection_specification(
* The transmission is just a float, we want to convert to one of the aperture
options.
"""
filepath = _get_collection_spec_filepath(q_max)
LOGGER.info(f"Using collection spec from {filepath}")
config_client = get_config_client()
collection_spec_from_config = config_client.get_file_contents(
COLLECTION_SPEC_FILEPATH,
filepath,
CollectionSpecFromConfig,
reset_cached_result=True,
).tth_angle_to_specification
Expand Down Expand Up @@ -222,6 +233,7 @@ def data_collection(
full_collection_time: float,
exposure_time_per_frame: float,
scan_type: DataCollectionScanType | AuxiliaryScanType,
q_max: Q_MAX_OPTIONS | None = None,
generic_collection_devices: GenericCollectionDevices = devices,
baseline_devices: list[StandardReadable] | None = None,
metadata: dict[str, Any] | None = None,
Expand All @@ -231,7 +243,7 @@ def data_collection(
trigger_pulse_width = 0.0001 # Assumes zebra is set to seconds timebase

collection_spec, total_frames = get_collection_specification(
full_collection_time, exposure_time_per_frame
full_collection_time, exposure_time_per_frame, q_max
)

first_frames_value = next(iter(collection_spec)).frames
Expand Down
39 changes: 37 additions & 2 deletions tests/unit_tests/i15_1/test_single_temperature_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
GenericCollectionDevices,
)
from crystallography_bluesky.i15_1.plans.single_temperature_collection import (
COLLECTION_SPEC_FILEPATH,
BASE_COLLECTION_SPEC_FILEPATH,
Q_MAX_OPTIONS,
CollectionSpecPerPosition,
_calculate_number_of_frames,
_get_collection_spec_filepath,
data_collection,
inner_collection,
)
Expand Down Expand Up @@ -262,7 +264,9 @@ def test_data_collection_gets_positions_to_fraction_from_config_server(
)
)
mock_collection_spec_config_client.get_file_contents.assert_called_once_with(
COLLECTION_SPEC_FILEPATH, CollectionSpecification, reset_cached_result=True
BASE_COLLECTION_SPEC_FILEPATH + ".txt",
CollectionSpecification,
reset_cached_result=True,
)


Expand Down Expand Up @@ -431,3 +435,34 @@ async def test_if_software_read_faster_than_time_between_frames_then_sleep_calle
assert not caplog.text
assert mock_sleep.call_count == 10
assert mock_sleep.call_args_list == [call(pytest.approx(0.9)) for _ in range(10)]


@pytest.mark.parametrize(
"q_max, expected_filepath",
[
(
25,
"/dls_sw/i15-1/software/daq_configuration/collection_specifications/collection_specification_25.txt",
),
(
30,
"/dls_sw/i15-1/software/daq_configuration/collection_specifications/collection_specification_30.txt",
),
(
35,
"/dls_sw/i15-1/software/daq_configuration/collection_specifications/collection_specification_35.txt",
),
(
40,
"/dls_sw/i15-1/software/daq_configuration/collection_specifications/collection_specification_40.txt",
),
(
None,
"/dls_sw/i15-1/software/daq_configuration/collection_specifications/collection_specification.txt",
),
],
)
def test__get_collection_spec_filepath_returns_expected_filepath(
q_max: Q_MAX_OPTIONS | None, expected_filepath: str
):
assert _get_collection_spec_filepath(q_max) == expected_filepath
Loading