Every calibration step signals failure by calling SetState and returning normally - none of them throws, so none reached the catch in CalibrateDetector. The unconditional SetState(Idle, "Calibration sequence done", Success) after the try block then overwrote all of them. /cancel during a JUNGFRAU pedestal therefore left the broker Idle and apparently ready to measure while holding a truncated G0 and default-constructed zeros for G1/G2, and every subsequent run was silently mis-converted with nothing in /status to show it. The genuine failures - "Pedestal not collected properly", "Mask not collected properly" - were hidden the same way. The steps now return whether they succeeded, and the sequence reports success only if they all did. A cancellation or a failure leaves the state Inactive with Error severity rather than Idle or Error: the calibration is undefined, so the detector has to be initialized again, which is what Inactive means everywhere else in the machine. The exception path joins them, since a throw mid-sequence leaves the calibration no better defined. Cancelled pedestals were already Inactive but carried Warning severity, which reads as an advisory. CalibrateJUNGFRAU now abandons the sequence at the first failure instead of collecting G1 and G2 on top of a G0 that was never measured - the cancel path already behaved that way - and ConfigureDetector is skipped when there is no calibration to operate with, a cancelled sequence having left the detector mid-abort. Both error paths that end an Initialize now notify the condition variable. The state has left Busy, but without the notification a client in /wait_until_running slept out its whole timeout - up to an hour, if it asked for one - before noticing a failure that had already happened. Separately, dataset_settings.space_group_number allowed 1..194 in the OpenAPI schema while the broker accepts 1..230, so every generated client's validate() rejected all 36 cubic space groups before the request left. The regenerated clients follow in the version bump. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uwv9ScHtDH6g8tYgfSuApo
1218 lines
47 KiB
C++
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();
|
|
}
|