Build Packages / Unit tests (push) Successful in 1h28m28s
Build Packages / build:windows:nocuda (push) Successful in 14m45s
Build Packages / build:windows:cuda (push) Successful in 13m13s
Build Packages / build:viewer-tgz:cpu (push) Successful in 6m47s
Build Packages / build:viewer-tgz:cuda (push) Successful in 7m22s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 13m52s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 14m16s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 13m19s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 12m50s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 14m40s
Build Packages / build:rpm (rocky8) (push) Successful in 11m18s
Build Packages / build:rpm (rocky9) (push) Successful in 12m4s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 11m55s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 11m22s
Build Packages / DIALS test (push) Successful in 13m37s
Build Packages / XDS test (durin plugin) (push) Successful in 8m47s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m4s
Build Packages / XDS test (neggia plugin) (push) Successful in 7m45s
Build Packages / Generate python client (push) Successful in 34s
Build Packages / Build documentation (push) Successful in 1m4s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 7m16s
This is an UNSTABLE release. It includes many experimental features, as well as many AI generated fixes. We recommend using rc.152 for production use. * rugnux: Rebrand the offline data-processing subsystem as `rugnux` and consolidate all offline analysis into the single `rugnux` binary - `jfjoch_process` is now `rugnux`, the former `jfjoch_azint` is now `rugnux --azint-only`, and `jfjoch_scale` is now `rugnux --scale` (see the new docs/NAMING.md and docs/RUGNUX.md). Scaling and merging are on by default for rotation and stills (`--no-merge` disables them), replacing the previous opt-in `-M, --scale-merge`. * rugnux: CLI fixes - default `-N` to all hardware threads, parse numeric option arguments strictly (reject non-numeric or trailing input instead of silently yielding 0), require `--wavelength > 0`, and correct the reproduced command line and `--scale` reference-cell handling. * rugnux: De-novo space-group improvements - recover genuine high symmetry and centred Bravais lattices from intensities, add an automatic CC1/2 high-resolution cutoff, and report L-test twinning statistics. * rugnux: Index weakly-diffracting low-resolution rotation data that previously failed (e.g. F-cubic crystals that diffract only to ~4 A on a detector reaching ~1.5 A). The per-frame indexing gate now measures the indexed fraction only within the resolution range the lattice actually diffracts to, so the many sub-diffraction ice/noise spots no longer make the fraction floor unreachable; the two-pass first pass tries several image-sampling schemes (spread across the whole rotation vs a consecutive wedge whose native stride keeps a reflection's rocking curve continuous, letting the FFT resolve a long axis) and keeps the one that indexes the most frames; and the de-novo space-group search no longer discards all reflections (and crashes) when every resolution shell falls below <I/sigma> = 1. * rugnux: Lower the low-resolution R-meas for strongly-diffracting rotation data - drop edge-of-sweep truncated fulls whose rocking curve was captured below `--min-captured-fraction` (default 0.7 for rotation), and report R-meas only over the observations kept by outlier rejection (matching XDS). The 0.7 default also strips the partiality-extrapolated fulls that dominate the intensity second moment on weakly-diffracting crystals, so the de-novo space-group search is no longer starved by the error-model I/sigma floor and recovers the correct symmetry (e.g. the F-cubic Benas crystals: Benas_3 -> F432, Benas_7 -> P6122, instead of P4/P1); on the reference battery every other crystal keeps its space group. * rugnux: Write the refined geometry (beam, tilt, axis) to _process.h5 and place non-standard mmCIF items under a reserved `jfjoch` prefix. * jfjoch_broker: Ordinary acquisition failures (receiver/writer/analysis problems, missed packets, writer disconnect) now return to the Idle state with an Error-severity message, so a run can be retried without an expensive re-initialisation; only failures that leave the detector in an undefined state (new JFJochCriticalException, e.g. PCIe/FPGA faults) go to the Error state and force re-initialisation. * jfjoch_broker: A synchronous /start now reports its failure to the HTTP caller instead of returning HTTP 200, and an incomplete or truncated dataset (missing packets, writer disconnect) is reported as an error rather than a "reduce frame rate" warning. * jfjoch_broker: Drop uncollected placeholder rows (number = -1) from the scan_result REST endpoint. * jfjoch_broker: Fix the inverted per-image compression ratio reported by the Lite receiver (was compressed/uncompressed instead of uncompressed/compressed). * jfjoch_broker: Bragg integration adds a quantization-noise variance floor with a box-sum fallback, and treats the type-maximum marker as an invalid pixel for unsigned image types. * jfjoch_writer: Detect file-overwrite conflicts at start for back-channel transports, and reset the writer when end-of-collection finalisation fails. * jfjoch_viewer: Preview overlays follow the geometry (resolution/ROI arcs, true beam centre, predictions, coral secondary-lattice spots, legend), add save-as-JPEG, and fix an HTTP live-follow memory leak. * Frontend: Improved aesthetics and usability, and added in-browser pixel-mask and JUNGFRAU-pedestal visualisation. * CI: Name the Windows installer jfjoch-viewer-* instead of jfjoch-*.Reviewed-on: #67 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
1145 lines
42 KiB
C++
1145 lines
42 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;
|
|
}
|
|
|
|
void 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];
|
|
}
|
|
|
|
TakePedestalInternalG0(ul);
|
|
if (!experiment.IsFixedGainG1()) {
|
|
for (int i = 0; i < experiment.GetStorageCellNumber(); i++) {
|
|
TakePedestalInternalG1(ul, i);
|
|
TakePedestalInternalG2(ul, i);
|
|
}
|
|
}
|
|
pixel_mask.LoadDetectorBadPixelMask(experiment, calibration.get());
|
|
}
|
|
|
|
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");
|
|
try {
|
|
if (experiment.GetDetectorType() == DetectorType::EIGER) {
|
|
// PSI EIGER - only reset calibration
|
|
calibration.reset();
|
|
} else if (experiment.GetDetectorType() == DetectorType::DECTRIS) {
|
|
// DECTRIS - take dark data for mask
|
|
calibration.reset();
|
|
TakeDarkMaskInternal(ul);
|
|
} else {
|
|
// PSI JUNGFRAU - take pedestal
|
|
calibration = std::make_unique<JFCalibration>(experiment);
|
|
CalibrateJUNGFRAU(ul);
|
|
}
|
|
// Update pixel mask statistics
|
|
UpdatePixelMaskStatistics(pixel_mask.GetStatistics());
|
|
// configure detector for standard operation
|
|
services.ConfigureDetector(experiment);
|
|
} catch (const std::exception &e) {
|
|
logger.Error("Calibration sequence error {}", e.what());
|
|
SetState(JFJochState::Error, e.what(), BrokerStatus::MessageSeverity::Error);
|
|
throw;
|
|
}
|
|
SetState(JFJochState::Idle, "Calibration sequence done", BrokerStatus::MessageSeverity::Success);
|
|
logger.Info("Calibration sequence done");
|
|
ul.unlock(); // Notify all outside of mutex
|
|
c.notify_all();
|
|
}
|
|
|
|
void JFJochStateMachine::TakeDarkMaskInternal(std::unique_lock<std::mutex> &ul) {
|
|
if (cancel_sequence) {
|
|
SetState(JFJochState::Inactive,
|
|
"Mask sequence cancelled",
|
|
BrokerStatus::MessageSeverity::Warning);
|
|
return;
|
|
}
|
|
|
|
services.LoadDetectorPixelMask(pixel_mask);
|
|
|
|
if (experiment.GetDarkMaskNumberOfFrames() == 0)
|
|
return;
|
|
|
|
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()) {
|
|
pixel_mask.LoadDarkBadPixelMask(local_experiment, mask_output.receiver_output.dark_mask_result);
|
|
SetState(JFJochState::Idle);
|
|
} else
|
|
SetState(JFJochState::Error, "Mask not collected properly", BrokerStatus::MessageSeverity::Error);
|
|
}
|
|
|
|
void 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::Warning);
|
|
return;
|
|
}
|
|
|
|
if (local_experiment.GetPedestalG0Frames() == 0)
|
|
return;
|
|
|
|
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::Idle);
|
|
else
|
|
SetState(JFJochState::Error,
|
|
"Pedestal not collected properly",
|
|
BrokerStatus::MessageSeverity::Error);
|
|
}
|
|
|
|
void 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::Warning);
|
|
return;
|
|
}
|
|
|
|
if (local_experiment.GetPedestalG1Frames() == 0)
|
|
return;
|
|
|
|
|
|
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::Error,
|
|
"Pedestal not collected properly",
|
|
BrokerStatus::MessageSeverity::Error);
|
|
}
|
|
|
|
void 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::Warning);
|
|
return;
|
|
}
|
|
|
|
if (local_experiment.GetPedestalG2Frames() == 0)
|
|
return;
|
|
|
|
|
|
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::Error,
|
|
"Pedestal not collected properly",
|
|
BrokerStatus::MessageSeverity::Error);
|
|
}
|
|
|
|
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
|
|
|
|
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");
|
|
|
|
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);
|
|
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
|
|
|
|
experiment.ImportDatasetSettings(settings);
|
|
|
|
cancel_sequence = false;
|
|
if (experiment.GetStorageCellNumber() == 1)
|
|
experiment.StorageCellStart(15);
|
|
else
|
|
experiment.StorageCellStart(0);
|
|
|
|
experiment.IncrementRunNumber();
|
|
|
|
start_exception = nullptr;
|
|
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).
|
|
if (start_exception) {
|
|
auto e = start_exception;
|
|
start_exception = nullptr;
|
|
std::rethrow_exception(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
BrokerStatus JFJochStateMachine::WaitTillNotBusy(std::chrono::milliseconds timeout) {
|
|
std::unique_lock ul(m);
|
|
c.wait_for(ul, timeout, [&]() { return state != JFJochState::Busy; });
|
|
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,
|
|
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);
|
|
try {
|
|
if (measurement.valid())
|
|
measurement.get();
|
|
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)) {
|
|
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);
|
|
|
|
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(); });
|
|
|
|
return GetStatus();
|
|
}
|
|
|
|
BrokerStatus JFJochStateMachine::WaitTillMeasurementDone(std::chrono::milliseconds timeout) {
|
|
std::unique_lock ul(m);
|
|
|
|
c.wait_for(ul, timeout, [&] { return !IsRunning(); });
|
|
|
|
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
|
|
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();
|
|
}
|