api: record the beam size at the sample, and write it where NXmx puts it

dataset_settings gains beam_size_x_um and beam_size_y_um, the horizontal and
vertical size of the X-ray beam where it meets the sample. They follow the same
route total_flux takes - OpenAPI, DatasetSettings, the CBOR start message, the
HDF5 master, and back out of a stored file - and nothing consumes them; this is
metadata a beamline can state and a downstream program can read.

NXmx puts this in the application definition rather than the base class: not
NXbeam's extent (rank 2, nP x 2, per scan point, always FWHM of a rectangular
aperture) but NXmx's own incident_beam_size, a recommended rank-1 two-element
array in the order x, y. Both are live and neither is deprecated, so the choice
matters; the MX definition wins in an MX file. Written as one array with a
units attribute of "m", like every other length in the master, so the settings
hold micrometres and FillMessage converts once.

The unit table of ReadLength_m becomes LengthUnitFactor so the array read can
share it: a master written elsewhere may state this in millimetres.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
This commit is contained in:
2026-09-02 09:59:34 +02:00
co-authored by Claude Opus 5
parent 7890149ad6
commit 8a04773d1d
20 changed files with 253 additions and 10 deletions
+24
View File
@@ -665,6 +665,12 @@ void DiffractionExperiment::FillMessage(StartMessage &message) const {
if (const auto bw = GetBandwidthFWHM())
message.incident_wavelength_spread = bw.value() * GetWavelength_A();
message.incident_energy = GetIncidentEnergy_keV() * 1e3f;
// NXmx incident_beam_size is a length like every other in this message, so micrometres in
// the settings become metres here.
if (const auto beam_size_x = GetBeamSizeX_um())
message.beam_size_x = beam_size_x.value() * 1e-6f;
if (const auto beam_size_y = GetBeamSizeY_um())
message.beam_size_y = beam_size_y.value() * 1e-6f;
message.image_size_x = GetXPixelsNum();
message.image_size_y = GetYPixelsNum();
message.mirror_y = IsDetectorMirroredY();
@@ -920,6 +926,16 @@ DiffractionExperiment &DiffractionExperiment::TotalFlux(const std::optional<floa
return *this;
}
DiffractionExperiment &DiffractionExperiment::BeamSizeX_um(const std::optional<float> &input) {
dataset.BeamSizeX_um(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::BeamSizeY_um(const std::optional<float> &input) {
dataset.BeamSizeY_um(input);
return *this;
}
std::optional<float> DiffractionExperiment::GetAttenuatorTransmission() const {
return dataset.GetAttenuatorTransmission();
}
@@ -928,6 +944,14 @@ std::optional<float> DiffractionExperiment::GetTotalFlux() const {
return dataset.GetTotalFlux();
}
std::optional<float> DiffractionExperiment::GetBeamSizeX_um() const {
return dataset.GetBeamSizeX_um();
}
std::optional<float> DiffractionExperiment::GetBeamSizeY_um() const {
return dataset.GetBeamSizeY_um();
}
DiffractionExperiment &DiffractionExperiment::Goniometer(const std::optional<GoniometerAxis> &input) {
dataset.Goniometer(input);
return *this;