Files
Jungfraujoch/broker/JFJochStateMachine.cpp
T
leonarski_f 749db470ca
Build Packages / build:rpm (rocky9) (push) Successful in 19m56s
Build Packages / Unit tests (push) Skipped
Build Packages / build:windows:nocuda (push) Successful in 16m57s
Build Packages / build:windows:cuda (push) Successful in 19m18s
Build Packages / build:viewer-tgz:cpu (push) Successful in 14m48s
Build Packages / build:viewer-tgz:cuda (push) Successful in 16m18s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 14m19s
Build Packages / build:rugnux:windows (push) Successful in 10m34s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 8m49s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 20m55s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 17m4s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 20m48s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m15s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 24m26s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 20m32s
Build Packages / build:rpm (rocky8) (push) Successful in 23m39s
Build Packages / Generate python client (push) Successful in 46s
Build Packages / Build documentation (push) Successful in 1m45s
Build Packages / Create release (push) Skipped
Build Packages / XDS test (durin plugin) (push) Successful in 11m3s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 11m30s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 20m10s
Build Packages / XDS test (neggia plugin) (push) Successful in 10m17s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 23m12s
Build Packages / DIALS test (push) Successful in 20m12s
v1.0.0-rc.164 (#74)
* rugnux now tells you whether a crystal diffracts anisotropically and how far it reaches in each direction, without a second program: a new `9. DIFFRACTION ANISOTROPY` section in `<prefix>_report.txt` and matching `_reflns.pdbx_aniso_B_tensor_*` / `_reflns.jfjoch_aniso_*` items in the merged mmCIF report the anisotropic deltaB, the diffraction limit along each principal direction, and a `NOT DETECTED` / `DETECTED` / `CANNOT DETERMINE` verdict measured against the data set's own systematic error. It is a description only - no intensity is corrected, no reflection is removed, and the merged data do not depend on direction.
* rugnux can hand its integrated observations to another scaling program: `--export-unmerged` writes `<prefix>_unmerged.mtz`, an unmerged MTZ readable by aimless, pointless, careless and `iotbx.merging_statistics`, in `--mode mx` and `--mode scale` alike. Each rotation reflection's partials are summed into one full; `--export-unmerged-partials` writes one row per image instead. Intensities carry the Lorentz-polarization factor and nothing else, since those programs scale the data themselves. Lattice-centring absences are not written; screw and glide absences are.
* rugnux integrates crystals with broad spots better - where it changes anything, per-shell mean I/sigma improves by up to 31% and R_meas by up to 24% - because on rotation data the integration signal radius is now taken from the crystal's own measured spot width instead of a fixed 4 px. `--adaptive-integration-radius=off` restores the fixed radius and an explicit `--integration-radius` still overrides both. The widened radius applies to the final integration pass only, and a pattern too dense for it is re-integrated at 4 px with a note in the log.
* rugnux discards fewer stills reflections for want of a background ring, improving per-shell R_meas over most of the signal-bearing range: the stills background ring now runs to 14 px instead of 12. The gain reverses in shells below a mean I/sigma of about 4.
* rugnux determines the space group with thresholds that mean the same thing on a weak crystal as on a strong one: symmetry operators are scored on resolution-normalised intensities (E squared) instead of raw merged intensities, and a reflection counts as genuinely present on its counting significance instead of on the merged I/sigma, which saturates at the merge's own ISa. The search resolution cut is no longer able to move the answer, and the twin-law H bound moves from 1.70 to 1.85, which stops one class of correct high-symmetry assignment being refused as twinning.
* rugnux says what the space-group search tested and what it could not: the twin-law disagreement H is printed for every operator together with the adopted point group's H ratio and its bound; alternatives that are not on the reported lattice are named with how their cell differs; and a lattice centring the data could not test - the crystal having been integrated on the primitive sub-cell, so the reflections it extinguishes were never measured - is marked `UNTESTED` and warned about where it is adopted, as coming from the lattice metric rather than from the intensities.
* rugnux `--mode scale` re-merges a `_process.h5` in the right symmetry without being told it: the file now records the space group on every run - a two-pass rotation run wrote none before, so re-merging defaulted to P1 - together with the change of basis under `/entry/MX/reindexMatrix` where the lattice was re-seated, and `--mode scale` also reports the Wilson B-factor estimate instead of `WILSON_B= nan`. A file written before this stops with a message naming the two cells and the override to use, instead of failing inside the merge. A third-party reader of a `_process.h5` must apply `reindexMatrix` where it is present.
* rugnux installs on its own, as a package called `rugnux` - `dnf install rugnux` or `apt install rugnux` - instead of arriving inside `jfjoch-viewer`. It pulls in none of the acquisition stack, so a machine that only processes data no longer has to carry the broker, the detector libraries or Qt to get it. Installing it over a `jfjoch-viewer` from rc.163 or earlier, which still owns `/usr/bin/rugnux`, upgrades cleanly rather than failing on the duplicate file.
* rugnux is also a standalone download, built for arm64 as well as x86_64: `rugnux-<version>-linux-{x86_64|aarch64}-cuda<major>.tgz` and `rugnux-<version>-win64-cuda<major>.zip` on the release page, for machines that are not managed by a package manager. The aarch64 build targets GH200 and DGX Spark, and is untested on hardware.
* Every portable Linux binary is now a single self-contained file: cuFFT is linked statically instead of being shipped beside the executable and found through an rpath, so `rugnux` and `jfjoch_viewer` need nothing but an NVIDIA driver, and only to use the GPU. The `.rpm`/`.deb` continue to take cuFFT from the distribution. The developer utilities `jfjoch_extract_hkl` and `jfjoch_recompress` are no longer packaged anywhere.
* Jungfraujoch needs six fewer shared libraries on the machine - libopenblas and libmetis, and libgfortran, libquadmath, libgomp and libz behind them - because the Ceres LAPACK, METIS and SuiteSparse back-ends are no longer built. Nothing in the code ever selected them, and results are unchanged.
* The PCIe driver DKMS package builds for the kernel it is being installed for instead of the running one, so a module built while a kernel update is being applied loads after the reboot.
* The PCIe driver builds on RHEL 9.5 and later, and on their CentOS Stream, Rocky and AlmaLinux equivalents, where the `vm_flags` kernel interface was backported into the 5.14 kernel.
* A data collection started with `async_start` that fails to start - a writer refusing to overwrite an existing file, for instance - is reported as an error by `/wait_until_running` and `/wait_till_done` instead of as a timeout and a successful collection respectively. The error message is the one the writer gave.
* A calibration that is cancelled or that fails to collect its pedestals is no longer reported as a successful one. The broker goes to `Inactive` with an error message and has to be initialized again, instead of sitting in `Idle` looking ready to measure while holding partial pedestals - data collected in that state was silently mis-converted.
* A failed `/initialize` is reported to `/wait_until_running` and `/wait_till_done` as soon as it happens, instead of when their timeout expires.
* `space_group_number` accepts space groups up to 230 in the API schema, so cubic space groups can be recorded. The broker always accepted them; the generated clients rejected them before the request was sent.
* The results report's `REPORT_VERSION` is 3, two sections having been added. Existing key names and table columns are unchanged.
* The merged statistics table has **9** resolution shells instead of 10, which is what XDS reports. The bins were already XDS's - equal steps in 1/d^2 between the lowest- and the highest-resolution reflection the merge kept - so at the same resolution limits the two tables now have the same shell boundaries and can be read row for row. `--resolution-shells` sets a different count.
* `rugnux --model` now settles the frame the merged reflections are written in, not only the frame the R-factors and the maps are computed in: the `.mtz`/`.cif`/`.hkl` come out in the model's indexing, and where the data were merged in the model's enantiomorph they take the model's hand and space group - which on anomalous data puts I(+) and I(-) the right way round. The indexing choice is logged with the winning R-free and the runner-up, so a decision made within noise is visible.
* `rugnux --model` can resolve the indexing ambiguity of a **serial stills** run, which a model could not do before: structure factors computed from the model become the per-image reference, the same role a reference MTZ plays. It needs the cell and space group up front (`-C` / `-S`). Without one or the other, a merohedral serial run still merges both hands together and says so.
* The rugnux documentation opens with a quick start - the default run, and runs with a reference MTZ, with a model, or with the space group and cell pinned - and explains the indexing ambiguity: what it costs on rotation and on serial data, and which of `-z` / `--model` resolves it in each case. The long reference pages now carry a table of contents.

Reviewed-on: #74
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-08-26 22:47:00 +02:00

1218 lines
47 KiB
C++

// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include <thread>
#include "JFJochStateMachine.h"
#include "../preview/JFJochTIFF.h"
#include "../common/CUDAWrapper.h"
#include "../common/GitInfo.h"
#include "../common/JFJochException.h"
JFJochStateMachine::JFJochStateMachine(const DiffractionExperiment& in_experiment,
JFJochServices &in_services,
Logger &in_logger,
const SpotFindingSettings &spot_finding_settings)
: logger(in_logger),
services(in_services),
experiment(in_experiment),
pixel_mask(experiment),
current_detector_setup(0),
data_processing_settings(spot_finding_settings),
pixel_mask_statistics({0, 0, 0}),
gpu_count(get_gpu_count()) {
#ifndef JFJOCH_USE_FFTW
indexing_possible = (get_gpu_count() > 0);
if (!indexing_possible)
data_processing_settings.indexing = false;
#else
data_processing_settings.indexing = true;
#endif
SuppressTIFFErrors();
}
bool JFJochStateMachine::ImportPedestalG0(const JFJochReceiverOutput &receiver_output) {
if (receiver_output.pedestal_result.empty())
return false;
if (receiver_output.pedestal_result.size() != experiment.GetModulesNum() * experiment.GetStorageCellNumber())
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Mismatch in pedestal output");
size_t gain_level = experiment.IsFixedGainG1() ? 1 : 0;
for (int s = 0; s < experiment.GetStorageCellNumber(); s++) {
for (int module = 0; module < experiment.GetModulesNum(); module++)
calibration->Pedestal(module, gain_level, s)
= receiver_output.pedestal_result[module + s * experiment.GetModulesNum()];
}
SetCalibrationStatistics(calibration->GetModuleStatistics());
return true;
}
bool JFJochStateMachine::ImportPedestalG1G2(const JFJochReceiverOutput &receiver_output, size_t gain_level,
size_t storage_cell) {
if (receiver_output.pedestal_result.empty())
return false;
if (receiver_output.pedestal_result.size() != experiment.GetModulesNum())
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Mismatch in pedestal output");
for (int i = 0; i < receiver_output.pedestal_result.size(); i++)
calibration->Pedestal(i, gain_level, storage_cell) = receiver_output.pedestal_result[i];
SetCalibrationStatistics(calibration->GetModuleStatistics());
return true;
}
bool JFJochStateMachine::CalibrateJUNGFRAU(std::unique_lock<std::mutex> &ul) {
if (!gain_calibration.empty()) {
if (gain_calibration.size() != experiment.GetModulesNum())
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Mismatch in gain files number");
for (int i = 0; i < gain_calibration.size(); i++)
calibration->GainCalibration(i) = gain_calibration[i];
}
// Abandon the sequence on the first failure. Collecting G1 on top of a G0 that was never
// measured only produces a calibration that looks complete.
if (!TakePedestalInternalG0(ul))
return false;
if (!experiment.IsFixedGainG1()) {
for (int i = 0; i < experiment.GetStorageCellNumber(); i++) {
if (!TakePedestalInternalG1(ul, i))
return false;
if (!TakePedestalInternalG2(ul, i))
return false;
}
}
pixel_mask.LoadDetectorBadPixelMask(experiment, calibration.get());
return true;
}
void JFJochStateMachine::CalibrateDetector(std::unique_lock<std::mutex> ul) {
cancel_sequence = false;
pixel_mask = PixelMask(experiment);
UpdatePixelMaskStatistics(pixel_mask.GetStatistics());
logger.Info("Calibration sequence started");
bool calibrated;
try {
if (experiment.GetDetectorType() == DetectorType::EIGER) {
// PSI EIGER - only reset calibration
calibration.reset();
calibrated = true;
} else if (experiment.GetDetectorType() == DetectorType::DECTRIS) {
// DECTRIS - take dark data for mask
calibration.reset();
calibrated = TakeDarkMaskInternal(ul);
} else {
// PSI JUNGFRAU - take pedestal
calibration = std::make_unique<JFCalibration>(experiment);
calibrated = CalibrateJUNGFRAU(ul);
}
// Update pixel mask statistics
UpdatePixelMaskStatistics(pixel_mask.GetStatistics());
// configure detector for standard operation - only worth doing if there is a calibration to
// operate with, and a cancelled sequence has left the detector mid-abort anyway
if (calibrated)
services.ConfigureDetector(experiment);
} catch (const std::exception &e) {
logger.Error("Calibration sequence error {}", e.what());
// The calibration is in an undefined state, so the detector has to be initialized again.
SetState(JFJochState::Inactive, e.what(), BrokerStatus::MessageSeverity::Error);
c.notify_all(); // ul unlocks on the way out
throw;
}
// The steps above report a cancellation or a failure through SetState and return false; that
// must not be overwritten with success here.
if (calibrated) {
SetState(JFJochState::Idle, "Calibration sequence done", BrokerStatus::MessageSeverity::Success);
logger.Info("Calibration sequence done");
}
ul.unlock(); // Notify all outside of mutex
c.notify_all();
}
bool JFJochStateMachine::TakeDarkMaskInternal(std::unique_lock<std::mutex> &ul) {
if (cancel_sequence) {
SetState(JFJochState::Inactive,
"Mask sequence cancelled",
BrokerStatus::MessageSeverity::Error);
return false;
}
services.LoadDetectorPixelMask(pixel_mask);
if (experiment.GetDarkMaskNumberOfFrames() == 0)
return true;
DiffractionExperiment local_experiment(experiment);
local_experiment.Mode(DetectorMode::DarkMask);
SetState(JFJochState::Calibration, "Dark sequence for mask calculation", BrokerStatus::MessageSeverity::Info);
services.ConfigureDetector(local_experiment);
services.Start(local_experiment, pixel_mask, nullptr);
services.Trigger();
ul.unlock();
// Allow to cancel/abort during the mask data collection
auto mask_output = services.Stop();
ul.lock();
if (mask_output.receiver_output.dark_mask_result.size() != local_experiment.GetPixelsNum()) {
SetState(JFJochState::Inactive, "Mask not collected properly", BrokerStatus::MessageSeverity::Error);
return false;
}
pixel_mask.LoadDarkBadPixelMask(local_experiment, mask_output.receiver_output.dark_mask_result);
SetState(JFJochState::Idle);
return true;
}
bool JFJochStateMachine::TakePedestalInternalG0(std::unique_lock<std::mutex> &ul) {
DiffractionExperiment local_experiment(experiment);
std::string message;
if (local_experiment.IsFixedGainG1()) {
local_experiment.Mode(DetectorMode::PedestalG1);
message = "Pedestal G1";
} else {
local_experiment.Mode(DetectorMode::PedestalG0);
message = "Pedestal G0";
}
if (local_experiment.GetStorageCellNumber() == 1)
local_experiment.StorageCellStart(15);
else
local_experiment.StorageCellStart(0);
if (cancel_sequence) {
SetState(JFJochState::Inactive,
"Pedestal sequence cancelled",
BrokerStatus::MessageSeverity::Error);
return false;
}
if (local_experiment.GetPedestalG0Frames() == 0)
return true;
SetState(JFJochState::Calibration, message, BrokerStatus::MessageSeverity::Info);
services.ConfigureDetector(local_experiment);
services.Start(local_experiment, pixel_mask, calibration.get());
services.Trigger();
ul.unlock();
// Allow to cancel/abort during the pedestal data collection
// Must ensure that while state is Pedestal, nothing can take lock for longer time, to avoid deadlock
auto pedestal_output = services.Stop();
ul.lock();
if (!ImportPedestalG0(pedestal_output.receiver_output)) {
SetState(JFJochState::Inactive,
"Pedestal not collected properly",
BrokerStatus::MessageSeverity::Error);
return false;
}
SetState(JFJochState::Idle);
return true;
}
bool JFJochStateMachine::TakePedestalInternalG1(std::unique_lock<std::mutex> &ul, int32_t storage_cell) {
DiffractionExperiment local_experiment(experiment);
local_experiment.Mode(DetectorMode::PedestalG1);
if (local_experiment.GetStorageCellNumber() == 2)
local_experiment.StorageCellStart((storage_cell + 15) % 16); // one previous
else
local_experiment.StorageCellStart(15);
if (cancel_sequence) {
SetState(JFJochState::Inactive,
"Pedestal sequence cancelled",
BrokerStatus::MessageSeverity::Error);
return false;
}
if (local_experiment.GetPedestalG1Frames() == 0)
return true;
SetState(JFJochState::Calibration,
"Pedestal G1 SC" + std::to_string(storage_cell),
BrokerStatus::MessageSeverity::Info);
services.ConfigureDetector(local_experiment);
services.Start(local_experiment, pixel_mask, calibration.get());
services.Trigger();
ul.unlock();
// Allow to cancel/abort during the pedestal data collection
// Must ensure that while state is Pedestal, nothing can take lock for longer time, to avoid deadlock
auto pedestal_output = services.Stop();
ul.lock();
if (!ImportPedestalG1G2(pedestal_output.receiver_output, 1, storage_cell)) {
SetState(JFJochState::Inactive,
"Pedestal not collected properly",
BrokerStatus::MessageSeverity::Error);
return false;
}
return true;
}
bool JFJochStateMachine::TakePedestalInternalG2(std::unique_lock<std::mutex> &ul, int32_t storage_cell) {
DiffractionExperiment local_experiment(experiment);
local_experiment.Mode(DetectorMode::PedestalG2);
if (local_experiment.GetStorageCellNumber() == 2)
local_experiment.StorageCellStart((storage_cell + 15) % 16); // one previous
else
local_experiment.StorageCellStart(15);
if (cancel_sequence) {
SetState(JFJochState::Inactive,
"Pedestal sequence cancelled",
BrokerStatus::MessageSeverity::Error);
return false;
}
if (local_experiment.GetPedestalG2Frames() == 0)
return true;
SetState(JFJochState::Calibration,
"Pedestal G2 SC" + std::to_string(storage_cell),
BrokerStatus::MessageSeverity::Info);
services.ConfigureDetector(local_experiment);
services.Start(local_experiment, pixel_mask, calibration.get());
services.Trigger();
ul.unlock();
// Allow to cancel/abort during the pedestal data collection
// Must ensure that while state is Pedestal, nothing can take lock for longer time, to avoid deadlock
auto pedestal_output = services.Stop();
ul.lock();
if (!ImportPedestalG1G2(pedestal_output.receiver_output, 2, storage_cell)) {
SetState(JFJochState::Inactive,
"Pedestal not collected properly",
BrokerStatus::MessageSeverity::Error);
return false;
}
return true;
}
void JFJochStateMachine::Initialize() {
std::unique_lock ul(m);
if (IsRunning())
throw WrongDAQStateException("Cannot initialize during measurement");
if (detector_setup.empty())
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Detector information not provided");
ResetError(); // Clear error, we don't care what was it
start_exception = nullptr; // Re-initialising discards a pending start failure
logger.Info("Initialize");
SetState(JFJochState::Busy, "Configuring indexing threads", BrokerStatus::MessageSeverity::Info);
try {
services.SetupIndexing(experiment.GetIndexingSettings());
} catch (const JFJochException &e) {
SetState(JFJochState::Error, e.what(), BrokerStatus::MessageSeverity::Error);
throw;
}
SetState(JFJochState::Busy, "Configuring detector", BrokerStatus::MessageSeverity::Info);
scan_result = {}; // Clear scan result
measurement = std::async(std::launch::async, &JFJochStateMachine::InitializeThread, this, std::move(ul));
}
void JFJochStateMachine::Pedestal() {
std::unique_lock ul(m);
if (state != JFJochState::Idle)
throw WrongDAQStateException("Must be idle to take pedestal");
start_exception = nullptr; // A new operation supersedes a pending start failure
SetState(JFJochState::Busy, "Updating calibration", BrokerStatus::MessageSeverity::Info);
measurement = std::async(std::launch::async, &JFJochStateMachine::CalibrateDetector, this, std::move(ul));
}
void JFJochStateMachine::InitializeThread(std::unique_lock<std::mutex> ul) {
try {
// services.On can potentially take a lot of time, so better to unlock main mutex
// Since On might modify the experiment (reads DECTRIS configuration), one has to have a local copy for unlocked part
DiffractionExperiment local_experiment(experiment);
if (state != JFJochState::Busy)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"State must be busy for safe operation");
ul.unlock();
services.On(local_experiment);
ul.lock();
experiment = local_experiment;
detector_setup[current_detector_setup] = experiment.GetDetectorSetup();
} catch (const std::exception &e) {
logger.Error("Initialize error {}", e.what());
SetState(JFJochState::Error, e.what(), BrokerStatus::MessageSeverity::Error);
// Wake anyone in WaitTillNotBusy/WaitTillMeasurementDone - the state has left Busy, and
// without this they sleep out their whole timeout before noticing.
c.notify_all(); // ul unlocks on the way out
throw;
}
CalibrateDetector(std::move(ul));
}
void JFJochStateMachine::Trigger() {
services.Trigger();
}
void JFJochStateMachine::Start(const DatasetSettings &settings, bool async) {
std::unique_lock ul(m);
if (state != JFJochState::Idle)
throw WrongDAQStateException("Must be idle to start measurement");
if (measurement.valid())
measurement.get(); // In case measurement was running - clear thread
// Clear before ImportDatasetSettings, which can throw: a rejected /start must not leave the
// previous run's failure behind for the next wait call to report.
start_exception = nullptr;
experiment.ImportDatasetSettings(settings);
cancel_sequence = false;
if (experiment.GetStorageCellNumber() == 1)
experiment.StorageCellStart(15);
else
experiment.StorageCellStart(0);
experiment.IncrementRunNumber();
SetState(JFJochState::Busy, "Preparing measurement", BrokerStatus::MessageSeverity::Info);
measurement = std::async(std::launch::async, &JFJochStateMachine::MeasurementThread, this);
if (!async) {
c.wait(ul, [&]() { return state != JFJochState::Busy; });
// A synchronous start propagates the failure to the caller. The state has already been set
// by MeasurementThread (Idle for an ordinary failure, Error for a critical detector fault).
// start_exception is left in place - the next Start() or Initialize() clears it - so that a
// wait call made afterwards reports the same failure instead of an apparent timeout.
if (start_exception)
std::rethrow_exception(start_exception);
}
}
BrokerStatus JFJochStateMachine::WaitTillNotBusy(std::chrono::milliseconds timeout) {
std::unique_lock ul(m);
c.wait_for(ul, timeout, [&]() { return state != JFJochState::Busy; });
// An asynchronous start reports its failure here, since /start itself returned before the
// measurement thread ran. Without this the state is plain Idle and the caller cannot tell a
// failed start from a timeout. rethrow_exception does not consume the exception_ptr, so
// repeated calls all report the same failure.
if (start_exception)
std::rethrow_exception(start_exception);
return GetStatus();
}
void JFJochStateMachine::UpdatePixelMaskStatistics(const PixelMaskStatistics &input) {
std::unique_lock ul(pixel_mask_statistics_mutex);
pixel_mask_statistics = input;
}
PixelMaskStatistics JFJochStateMachine::GetPixelMaskStatistics() const {
std::unique_lock ul(pixel_mask_statistics_mutex);
return pixel_mask_statistics;
}
void JFJochStateMachine::MeasurementThread() {
try {
services.SetSpotFindingSettings(GetSpotFindingSettings());
services.Start(experiment, pixel_mask, calibration.get());
{
std::unique_lock ul(m);
SetState(JFJochState::Measuring, "Measuring ...", BrokerStatus::MessageSeverity::Info);
}
c.notify_all();
} catch (const JFJochCriticalException &e) {
// Detector left in an undefined state - force re-initialisation via the Error state.
logger.Error("Critical error starting measurement: {}", e.what());
{
std::unique_lock ul(m);
SetState(JFJochState::Error, e.what(), BrokerStatus::MessageSeverity::Error);
start_exception = std::current_exception();
}
c.notify_all();
return;
} catch (const std::exception &e) {
// Ordinary acquisition failure - the detector is still configured/calibrated, so return to
// Idle and let the user retry without re-initialising. services.Start has already stopped the
// receiver it launched.
logger.Error("Error starting measurement: {}", e.what());
{
std::unique_lock ul(m);
SetState(JFJochState::Idle, e.what(), BrokerStatus::MessageSeverity::Error);
start_exception = std::current_exception();
}
c.notify_all();
return;
}
try {
auto tmp_output = services.Stop();
{
std::unique_lock ul(m);
scan_result = tmp_output.receiver_output.scan_result;
auto image_mean_time = tmp_output.receiver_output.processing_time;
logger.Info("Per-image mean processing time (microseconds): compression {:.0f} preprocess {:.0f} azint {:.0f} spot finding {:.0f} indexing {:.0f} refinement {:.0f} indexing analysis {:.0f} prediction {:.0f} integration {:.0f} total {:.0f}",
image_mean_time.compression * 1e6,
image_mean_time.preprocessing * 1e6,
image_mean_time.azint * 1e6,
image_mean_time.spot_finding * 1e6,
image_mean_time.indexing * 1e6,
image_mean_time.refinement * 1e6,
image_mean_time.indexing_analysis * 1e6,
image_mean_time.bragg_prediction * 1e6,
image_mean_time.integration * 1e6,
image_mean_time.processing * 1e6);
// Priority order matters. A cancel is checked first (it legitimately leaves efficiency < 1),
// then the hard errors (missing packets, truncated writer output). The queue-full warning is
// only the primary status when the run otherwise succeeded - it must not mask a real error
// by downgrading an incomplete/truncated dataset to a "reduce frame rate" warning.
if (tmp_output.receiver_output.status.cancelled)
SetState(JFJochState::Idle,
"Data collection cancelled",
BrokerStatus::MessageSeverity::Info);
else if (tmp_output.receiver_output.efficiency != 1.0)
SetState(JFJochState::Idle,
"Missing packets in data collection; reduce frame rate",
BrokerStatus::MessageSeverity::Error);
else if (!tmp_output.receiver_output.writer_err.empty())
SetState(JFJochState::Idle,
"Writer error, written data may be incomplete: " + tmp_output.receiver_output.writer_err,
BrokerStatus::MessageSeverity::Error);
else if (tmp_output.receiver_output.writer_queue_full_warning)
SetState(JFJochState::Idle,
"Stream receiver (writer or downstream analysis) cannot cope with data; reduce frame rate",
BrokerStatus::MessageSeverity::Warning);
else
SetState(JFJochState::Idle,
"Data collection without problems",
BrokerStatus::MessageSeverity::Success);
}
} catch (const JFJochCriticalException &e) {
// Detector faulted during the run - it needs re-initialisation, so go to the Error state.
logger.Error("Critical error finishing measurement: {}", e.what());
std::unique_lock ul(m);
SetState(JFJochState::Error, e.what(), BrokerStatus::MessageSeverity::Error);
} catch (const std::exception &e) {
// Receiver/writer problem - the data may be incomplete, but the detector is still usable, so
// return to Idle rather than forcing re-initialisation.
logger.Error("Error finishing measurement: {}", e.what());
std::unique_lock ul(m);
SetState(JFJochState::Idle, e.what(), BrokerStatus::MessageSeverity::Error);
}
c.notify_all();
}
void JFJochStateMachine::Cancel() {
// This is inconsistency in naming - need to solve later
std::unique_lock ul(m);
if ((state == JFJochState::Calibration) || (state == JFJochState::Measuring)) {
services.Cancel();
cancel_sequence = true;
}
}
void JFJochStateMachine::DebugOnly_SetState(JFJochState in_state,
const std::optional<std::string> &message,
BrokerStatus::MessageSeverity message_severity) {
std::unique_lock ul(m);
SetState(in_state, message, message_severity);
}
void JFJochStateMachine::Deactivate() {
std::unique_lock ul(m);
// Powering the detector off holds m for the whole sequence, so it must not be started while a
// measurement, calibration or initialisation thread is still live - those re-acquire m to
// finish, and waiting for them here would deadlock the whole control plane.
if (IsRunning())
throw WrongDAQStateException("Cannot deactivate while the detector is busy");
// Reap the finished thread, but do not let a failure it stored stop the power-off: the state is
// Error precisely because that run failed, and leaving the detector powered is worse than
// losing an error message that was already reported when it happened.
if (measurement.valid()) {
try {
measurement.get();
} catch (const std::exception &e) {
logger.Warning("Deactivating after an earlier failure: {}", e.what());
}
}
try {
services.Off();
SetState(JFJochState::Inactive,
"Detector safe to turn off",
BrokerStatus::MessageSeverity::Info);
} catch (const std::exception &e) {
SetState(JFJochState::Error,
e.what(),
BrokerStatus::MessageSeverity::Error);
throw;
}
}
JFJochStateMachine::~JFJochStateMachine() {
ResetError();
}
std::optional<MeasurementStatistics> JFJochStateMachine::GetMeasurementStatistics() const {
MeasurementStatistics tmp{};
tmp.file_prefix = experiment.GetFilePrefix();
tmp.run_number = experiment.GetRunNumber();
tmp.experiment_group = experiment.GetExperimentGroup();
tmp.detector_width = experiment.GetXPixelsNum();
tmp.detector_height = experiment.GetYPixelsNum();
tmp.detector_pixel_depth = experiment.GetByteDepthImage();
tmp.images_expected = experiment.GetImageNum();
tmp.unit_cell = experiment.GetUnitCellString();
auto rcv_status = services.GetReceiverStatus();
if (rcv_status) {
tmp.compression_ratio = rcv_status->compressed_ratio;
tmp.images_collected = rcv_status->images_collected;
tmp.images_sent = rcv_status->images_sent;
tmp.images_skipped = rcv_status->images_skipped;
tmp.cancelled = rcv_status->cancelled;
tmp.max_image_number_sent = rcv_status->max_image_number_sent;
tmp.max_receive_delay = rcv_status->max_receive_delay;
tmp.indexing_rate = rcv_status->indexing_rate;
tmp.bkg_estimate = rcv_status->bkg_estimate;
tmp.collection_efficiency = rcv_status->efficiency;
tmp.error_pixels = rcv_status->error_pixels;
tmp.saturated_pixels = rcv_status->saturated_pixels;
tmp.roi_beam_sum = rcv_status->roi_beam_sum;
tmp.roi_beam_npixel = rcv_status->roi_beam_npixel;
tmp.images_written = rcv_status->images_written;
}
return tmp;
}
std::vector<JFCalibrationModuleStatistics> JFJochStateMachine::GetCalibrationStatistics() const {
std::unique_lock ul(calibration_statistics_mutex);
return calibration_statistics;
}
void JFJochStateMachine::SetCalibrationStatistics(const std::vector<JFCalibrationModuleStatistics> &input) {
std::unique_lock ul(calibration_statistics_mutex);
calibration_statistics = input;
}
DetectorSettings JFJochStateMachine::GetDetectorSettings() const {
std::unique_lock ul(experiment_detector_settings_mutex);
return experiment.GetDetectorSettings();
}
bool JFJochStateMachine::ImportDetectorSettings(const DetectorSettings &input) {
std::unique_lock ul(experiment_detector_settings_mutex);
// For JUNGFRAU detector, if detector settings changes key parameters
// need to recalibrate the detector
bool recalib = input.NeedsJUNGFRAURecalibration(experiment.GetDetectorSettings())
&& experiment.GetDetectorType() == DetectorType::JUNGFRAU;
experiment.ImportDetectorSettings(input);
return recalib;
}
void JFJochStateMachine::LoadDetectorSettings(const DetectorSettings &settings) {
std::unique_lock ul(m);
switch (state) {
case JFJochState::Inactive:
case JFJochState::Error:
ImportDetectorSettings(settings);
break;
case JFJochState::Idle:
if (ImportDetectorSettings(settings)) {
start_exception = nullptr; // A new operation supersedes a pending start failure
SetState(JFJochState::Busy, "Loading settings", BrokerStatus::MessageSeverity::Info);
measurement = std::async(std::launch::async, &JFJochStateMachine::CalibrateDetector, this, std::move(ul));
} else {
try {
SetState(JFJochState::Busy, "Configure detector", BrokerStatus::MessageSeverity::Info);
services.ConfigureDetector(experiment);
SetState(JFJochState::Idle, "Detector configured", BrokerStatus::MessageSeverity::Info);
} catch (const std::exception &e) {
logger.Error("Detector configuration error {}", e.what());
SetState(JFJochState::Error, e.what(), BrokerStatus::MessageSeverity::Error);
}
}
break;
case JFJochState::Measuring:
case JFJochState::Busy:
case JFJochState::Calibration:
throw WrongDAQStateException("Cannot change detector settings during data collection");
}
}
DiffractionExperiment JFJochStateMachine::Experiment() {
return experiment;
}
BrokerStatus JFJochStateMachine::GetStatus() const {
std::unique_lock ul(broker_status_mutex);
BrokerStatus ret = broker_status;
ret.progress = services.GetReceiverProgress();
ret.gpu_count = gpu_count;
ret.broker_version = jfjoch_version();
return ret;
}
void JFJochStateMachine::SetState(JFJochState curr_state,
const std::optional<std::string> &message,
BrokerStatus::MessageSeverity message_severity) {
std::unique_lock ul(broker_status_mutex);
state = curr_state;
broker_status = BrokerStatus{
.state = curr_state,
.message = message,
.message_severity = message_severity
};
}
MultiLinePlot JFJochStateMachine::GetPlots(const PlotRequest &request) const {
return services.GetPlots(request);
}
void JFJochStateMachine::GetPlotRaw(std::vector<float> &v, PlotType type, const std::string &roi) const {
services.GetPlotRaw(v, type, roi);
}
void JFJochStateMachine::SetSpotFindingSettings(const SpotFindingSettings &settings) {
std::unique_lock ul(data_processing_settings_mutex);
DiffractionExperiment::CheckDataProcessingSettings(settings);
// The adaptive threshold is a property of the software spot finder, which only the DECTRIS
// (SIMPLON) workflow runs - JUNGFRAU and EIGER find spots on the FPGA, at its own fixed
// threshold. Refuse it there rather than accept it and do nothing: a silently ignored detection
// setting is indistinguishable from one that had no effect on the data.
if (settings.adaptive_threshold && experiment.GetDetectorType() != DetectorType::DECTRIS)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Adaptive spot-finding threshold is not available on this detector: spots "
"are found on the FPGA, which applies its own fixed threshold");
data_processing_settings = settings;
// If there is no capability to use the features, make sure these are disabled
if (!indexing_possible)
data_processing_settings.indexing = false;
services.SetSpotFindingSettings(data_processing_settings);
}
SpotFindingSettings JFJochStateMachine::GetSpotFindingSettings() const {
std::unique_lock ul(data_processing_settings_mutex);
return data_processing_settings;
}
void JFJochStateMachine::AddDetectorSetup(const DetectorSetup &setup) {
// Not thread safe, only during setup
if (detector_setup.empty()) {
experiment.Detector(setup);
UpdateROIDefinition();
gain_calibration = setup.GetGainCalibration();
current_detector_setup = 0;
pixel_mask = PixelMask(experiment);
}
detector_setup.emplace_back(setup);
}
DetectorList JFJochStateMachine::GetDetectorsList() const {
DetectorList ret;
for (const auto &i: detector_setup) {
DetectorListElement tmp;
tmp.description = i.GetDescription();
tmp.nmodules = i.GetModulesNum();
tmp.width = i.GetGeometry().GetWidth(true);
tmp.height = i.GetGeometry().GetHeight(true);
tmp.serial_number = i.GetSerialNumber();
tmp.base_ipv4_addr = i.GetBaseIPv4Addr();
tmp.udp_interface_count = i.GetUDPInterfaceCount();
tmp.min_frame_time = i.GetMinFrameTime();
tmp.min_count_time = i.GetMinCountTime();
tmp.readout_time = i.GetReadOutTime();
tmp.detector_type = i.GetDetectorType();
tmp.pixel_size_mm = i.GetPixelSize_mm();
ret.detector.emplace_back(std::move(tmp));
}
ret.current_id = current_detector_setup;
return ret;
}
std::optional<DetectorStatus> JFJochStateMachine::GetDetectorStatus() const {
return services.GetDetectorStatus();
}
void JFJochStateMachine::SelectDetector(int64_t id) {
std::unique_lock ul(m);
if ((id < 0) || (id >= detector_setup.size()))
throw JFJochException(JFJochExceptionCategory::ArrayOutOfBounds, "Detector doesn't exist");
if (IsRunning())
throw WrongDAQStateException("Cannot change detector during data collection");
// Do nothing if this is the same detector as currently used
if (id == current_detector_setup)
return;
// Try to deactivate current detector (if actually running)
if (state != JFJochState::Inactive) {
try {
SetState(JFJochState::Busy, "Deactivating existing detector");
ul.unlock();
services.Off();
ul.lock();
} catch (const std::exception &e) {
logger.ErrorException(e);
logger.Warning("Cannot turn off existing detector - proceeding anyway");
}
}
try {
experiment.Detector(detector_setup[id]);
UpdateROIDefinition();
gain_calibration = detector_setup[id].GetGainCalibration();
pixel_mask = PixelMask(experiment);
SetState(JFJochState::Inactive, detector_setup[id].GetDescription() + " selected; please initialize");
current_detector_setup = id;
} catch (const JFJochException &e) {
logger.ErrorException(e);
SetState(JFJochState::Error, e.what(), BrokerStatus::MessageSeverity::Error);
throw; // re-throw the exception, so it is populated to caller
}
}
void JFJochStateMachine::SetRadialIntegrationSettings(const AzimuthalIntegrationSettings &settings) {
std::unique_lock ul(m);
if (IsRunning())
throw WrongDAQStateException("Cannot change radial integration settings during data collection");
{
std::unique_lock ul2(experiment_azimuthal_integration_settings_mutex);
experiment.ImportAzimuthalIntegrationSettings(settings);
}
}
AzimuthalIntegrationSettings JFJochStateMachine::GetRadialIntegrationSettings() const {
std::unique_lock ul(experiment_azimuthal_integration_settings_mutex);
return experiment.GetAzimuthalIntegrationSettings();
}
bool JFJochStateMachine::IsRunning() const {
switch (state) {
case JFJochState::Inactive:
case JFJochState::Error:
case JFJochState::Idle:
return false;
case JFJochState::Measuring:
case JFJochState::Busy:
case JFJochState::Calibration:
return true;
default:
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "State unknown");
}
}
BrokerStatus JFJochStateMachine::WaitTillMeasurementDone() {
std::unique_lock ul(m);
c.wait(ul, [&] { return !IsRunning(); });
// A start that failed asynchronously never reached Measuring, so the state is Idle and would
// otherwise be reported as a successfully finished collection.
if (start_exception)
std::rethrow_exception(start_exception);
return GetStatus();
}
BrokerStatus JFJochStateMachine::WaitTillMeasurementDone(std::chrono::milliseconds timeout) {
std::unique_lock ul(m);
c.wait_for(ul, timeout, [&] { return !IsRunning(); });
if (start_exception)
std::rethrow_exception(start_exception);
return GetStatus();
}
void JFJochStateMachine::ResetError() noexcept {
try {
if (measurement.valid())
measurement.get();
} catch (...) {
}
}
std::string JFJochStateMachine::GetPreviewJPEG(const PreviewImageSettings &settings, int64_t image_number) const {
return services.GetPreviewJPEG(settings, image_number);
}
std::string JFJochStateMachine::GetPreviewTIFF(int64_t image_number) const {
return services.GetPreviewTIFF(image_number);
}
std::string JFJochStateMachine::GetPedestalTIFF(size_t gain_level, size_t sc) const {
std::unique_lock ul(m);
if (state != JFJochState::Idle)
throw WrongDAQStateException("Pedestal can be only retrieved in Idle state");
if ((experiment.GetDetectorSetup().GetDetectorType() == DetectorType::JUNGFRAU) && calibration) {
auto tmp = calibration->GetPedestal(gain_level, sc);
CompressedImage image(tmp, RAW_MODULE_COLS, RAW_MODULE_LINES * experiment.GetModulesNum());
return WriteTIFFToString(image);
} else
return {};
}
void JFJochStateMachine::LoadInternalGeneratorImage(const void *data, size_t size, uint64_t image_number) {
std::unique_lock ul(m);
if (state != JFJochState::Idle)
throw WrongDAQStateException("Can change internal generator image only when detector in Idle state");
if ((size != experiment.GetPixelsNum() * sizeof(uint16_t))
&& (size != experiment.GetModulesNum() * RAW_MODULE_SIZE * sizeof(uint16_t)))
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Image size doesn't match current detector");
if (image_number >= experiment.GetInternalPacketGeneratorImages())
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Image for internal generator out of bounds");
std::vector<uint16_t> image(size / sizeof(uint16_t));
memcpy(image.data(), data, size);
services.LoadInternalGeneratorImage(experiment, image, image_number);
}
void JFJochStateMachine::LoadInternalGeneratorImageTIFF(const std::string &s, uint64_t image_number) {
std::unique_lock ul(m);
if (state != JFJochState::Idle)
throw WrongDAQStateException("Can change internal generator image only when detector in Idle state");
uint32_t cols, lines;
auto v = ReadTIFFFromString16(s, cols, lines);
if (((cols == experiment.GetXPixelsNum()) && (lines == experiment.GetYPixelsNum()))
|| ((cols == RAW_MODULE_COLS) && (lines == RAW_MODULE_LINES * experiment.GetModulesNum())))
services.LoadInternalGeneratorImage(experiment, v, image_number);
else
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Image size doesn't match current detector");
}
void JFJochStateMachine::UpdateROIDefinition() {
std::unique_lock ul(roi_mutex);
roi = experiment.ROI().GetROIDefinition();
}
void JFJochStateMachine::SetROIDefinition(const ROIDefinition &input) {
std::unique_lock ul(m);
if (IsRunning())
throw WrongDAQStateException("ROI can be modified only when detector is not running");
experiment.ROI().SetROI(input);
UpdateROIDefinition();
}
ROIDefinition JFJochStateMachine::GetROIDefintion() const {
std::unique_lock ul(roi_mutex);
return roi;
}
std::vector<uint64_t> JFJochStateMachine::GetXFELPulseID() const {
std::vector<uint64_t> ret;
services.GetXFELPulseID(ret);
return ret;
}
std::vector<uint64_t> JFJochStateMachine::GetXFELEventCode() const {
std::vector<uint64_t> ret;
services.GetXFELEventCode(ret);
return ret;
}
std::string JFJochStateMachine::GetFullPixelMaskTIFF() const {
std::unique_lock ul(m);
if (state == JFJochState::Inactive)
return {};
std::vector v = pixel_mask.GetMask(experiment);
CompressedImage mask_image(v, experiment.GetXPixelsNum(), experiment.GetYPixelsNum());
return WriteTIFFToString(mask_image);
}
std::string JFJochStateMachine::GetUserPixelMaskTIFF() const {
std::unique_lock ul(m);
if (state == JFJochState::Inactive)
return {};
std::vector v = pixel_mask.GetUserMask(experiment);
CompressedImage mask_image(v, experiment.GetXPixelsNum(), experiment.GetYPixelsNum());
return WriteTIFFToString(mask_image);
}
std::vector<uint32_t> JFJochStateMachine::GetFullPixelMask() const {
std::unique_lock ul(m);
if (state == JFJochState::Inactive)
return {};
return pixel_mask.GetMask(experiment);
}
std::vector<uint32_t> JFJochStateMachine::GetUserPixelMask() const {
std::unique_lock ul(m);
if (state == JFJochState::Inactive)
return {};
return pixel_mask.GetUserMask(experiment);
}
void JFJochStateMachine::SetUserPixelMask(const std::vector<uint32_t> &v) {
std::unique_lock ul(m);
if (state != JFJochState::Idle)
throw WrongDAQStateException("User mask can be only modified in Idle state");
try {
pixel_mask.LoadUserMask(experiment, v);
UpdatePixelMaskStatistics(pixel_mask.GetStatistics());
} catch (const JFJochException &e) {
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Problem handling user mask " + std::string(e.what()));
}
}
void JFJochStateMachine::SetUserPixelMask(const CompressedImage &image) {
std::unique_lock ul(m);
if (state != JFJochState::Idle)
throw WrongDAQStateException("User mask can be only modified in Idle state");
try {
pixel_mask.LoadUserMask(experiment, image);
UpdatePixelMaskStatistics(pixel_mask.GetStatistics());
} catch (const JFJochException &e) {
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Problem handling user mask " + std::string(e.what()));
}
}
InstrumentMetadata JFJochStateMachine::GetInstrumentMetadata() const {
std::unique_lock ul(experiment_instrument_metadata_mutex);
return experiment.GetInstrumentMetadata();
}
void JFJochStateMachine::LoadInstrumentMetadata(const InstrumentMetadata &settings) {
std::unique_lock ul(m);
if (IsRunning())
throw WrongDAQStateException("Cannot change instrument metadata during data collection");
{
std::unique_lock ul2(experiment_instrument_metadata_mutex);
experiment.ImportInstrumentMetadata(settings);
}
}
ImageFormatSettings JFJochStateMachine::GetImageFormatSettings() const {
std::unique_lock ul(experiment_image_format_settings_mutex);
return experiment.GetImageFormatSettings();
}
void JFJochStateMachine::LoadImageFormatSettings(const ImageFormatSettings &settings) {
std::unique_lock ul(m);
if (IsRunning())
throw WrongDAQStateException("Cannot change image format settings during data collection");
bool recalc_mask = (experiment.GetPedestalG0RMSLimit() != settings.GetPedestalG0RMSLimit());
{
std::unique_lock ul2(experiment_image_format_settings_mutex);
experiment.ImportImageFormatSettings(settings);
}
if (recalc_mask)
pixel_mask.LoadDetectorBadPixelMask(experiment, calibration.get());
else
pixel_mask.CalcEdgePixels(experiment);
UpdatePixelMaskStatistics(pixel_mask.GetStatistics());
}
void JFJochStateMachine::RawImageFormatSettings() {
std::unique_lock ul(m);
if (IsRunning())
throw WrongDAQStateException("Cannot change instrument metadata during data collection");
experiment.Raw();
}
void JFJochStateMachine::ConvImageFormatSettings() {
std::unique_lock ul(m);
if (IsRunning())
throw WrongDAQStateException("Cannot change instrument metadata during data collection");
experiment.Conversion();
}
std::vector<DeviceStatus> JFJochStateMachine::GetDeviceStatus() const {
return services.GetDeviceStatus();
}
void JFJochStateMachine::SetPreviewSocketSettings(const ZMQPreviewSettings &input) {
services.SetPreviewSocketSettings(input);
}
ZMQPreviewSettings JFJochStateMachine::GetPreviewSocketSettings() {
return services.GetPreviewSocketSettings();
}
void JFJochStateMachine::SetMetadataSocketSettings(const ZMQMetadataSettings &input) {
services.SetMetadataSocketSettings(input);
}
ZMQMetadataSettings JFJochStateMachine::GetMetadataSocketSettings() {
return services.GetMetadataSocketSettings();
}
void JFJochStateMachine::GetStartMessageFromBuffer(std::vector<uint8_t> &v) {
return services.GetStartMessageFromBuffer(v);
}
void JFJochStateMachine::GetImageFromBuffer(std::vector<uint8_t> &v, int64_t image_number) {
services.GetImageFromBuffer(v, image_number);
}
ImageBufferStatus JFJochStateMachine::GetImageBufferStatus() const {
return services.GetImageBufferStatus();
}
void JFJochStateMachine::ClearImageBuffer() const {
std::unique_lock ul(m);
if (IsRunning())
throw WrongDAQStateException("Cannot clear image buffer during data collection");
services.ClearImageBuffer();
}
FileWriterSettings JFJochStateMachine::GetFileWriterSettings() const {
std::unique_lock ul(experiment_file_writer_settings_mutex);
return experiment.GetFileWriterSettings();
}
void JFJochStateMachine::LoadFileWriterSettings(const FileWriterSettings &settings) {
std::unique_lock ul(m);
if (IsRunning())
throw WrongDAQStateException("Cannot change instrument metadata during data collection");
{
std::unique_lock ul2(experiment_file_writer_settings_mutex);
experiment.ImportFileWriterSettings(settings);
}
}
IndexingSettings JFJochStateMachine::GetIndexingSettings() const {
std::unique_lock ul(experiment_indexing_settings_mutex);
return experiment.GetIndexingSettings();
}
void JFJochStateMachine::SetIndexingSettings(const IndexingSettings &input) {
std::unique_lock ul(m);
if (IsRunning())
throw WrongDAQStateException("Cannot change instrument metadata during data collection");
{
std::unique_lock ul2(experiment_indexing_settings_mutex);
experiment.ImportIndexingSettings(input);
try {
services.SetupIndexing(input);
} catch (const JFJochException &e) {
logger.ErrorException(e);
SetState(JFJochState::Error,
e.what(),
BrokerStatus::MessageSeverity::Error);
throw;
}
}
}
BraggIntegrationSettings JFJochStateMachine::GetBraggIntegrationSettings() const {
std::unique_lock ul(experiment_indexing_settings_mutex);
return experiment.GetBraggIntegrationSettings();
}
void JFJochStateMachine::SetBraggIntegrationSettings(const BraggIntegrationSettings &input) {
std::unique_lock ul(m);
if (IsRunning())
throw WrongDAQStateException("Cannot change Bragg integration settings during data collection");
// The analysis engines read the integrator mode off the experiment when they are built at the start
// of the next run, so importing it here is all that is needed.
std::unique_lock ul2(experiment_indexing_settings_mutex);
experiment.ImportBraggIntegrationSettings(input);
}
std::optional<ScanResult> JFJochStateMachine::GetScanResult() const {
std::unique_lock ul(m);
if (IsRunning())
throw WrongDAQStateException("Cannot check scan result, when running");
return scan_result;
}
DarkMaskSettings JFJochStateMachine::GetDarkMaskSettings() const {
std::unique_lock ul(experiment_dark_mask_settings_mutex);
return experiment.GetDarkMaskSettings();
}
void JFJochStateMachine::SetDarkMaskSettings(const DarkMaskSettings &settings) {
std::unique_lock ul(m);
if (IsRunning())
throw WrongDAQStateException("Cannot change dark mask calculation settings during data collection");
{
// Setting dark mask settings in experiment requires BOTH mutexes
std::unique_lock ul2(experiment_dark_mask_settings_mutex);
experiment.ImportDarkMaskSettings(settings);
}
if ((experiment.GetDetectorType() == DetectorType::DECTRIS) && (state == JFJochState::Idle)) {
// Need to redo the calibration
start_exception = nullptr; // A new operation supersedes a pending start failure
SetState(JFJochState::Busy, "Loading settings", BrokerStatus::MessageSeverity::Info);
measurement = std::async(std::launch::async, &JFJochStateMachine::CalibrateDetector, this, std::move(ul));
}
}
ImagePusherStatus JFJochStateMachine::GetImagePusherStatus() const {
return services.GetImagePusherStatus();
}