// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #include "JFJochStateMachine.h" #include "../preview/JFJochTIFF.h" JFJochStateMachine::JFJochStateMachine(JFJochServices &in_services, Logger &in_logger) : logger(in_logger), services(in_services), pixel_mask(experiment), current_detector_setup(0), data_processing_settings(DiffractionExperiment::DefaultDataProcessingSettings()), pixel_mask_statistics({0,0,0}) { SupressTIFFErrors(); } 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::TakePedestalInternalAll(std::unique_lock &ul) { if (experiment.GetDetectorSetup().GetDetectorType() == DetectorType::EIGER) { logger.Info("EIGER configuration"); services.ConfigureDetector(experiment); logger.Info(" ... done "); return; } calibration = std::make_unique(experiment); 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]; } cancel_sequence = false; logger.Info("Pedestal sequence started"); try { TakePedestalInternalG0(ul); if (!experiment.IsFixedGainG1()) { for (int i = 0; i < experiment.GetStorageCellNumber(); i++) { TakePedestalInternalG1(ul, i); TakePedestalInternalG2(ul, i); } } services.ConfigureDetector(experiment); pixel_mask.LoadDetectorBadPixelMask(experiment, calibration.get()); UpdatePixelMaskStatistics(pixel_mask.GetStatistics()); } catch (const std::exception &e) { logger.Error("Pedestal sequence error {}", e.what()); state = JFJochState::Error; throw; } logger.Info("Pedestal sequence done"); } void JFJochStateMachine::TakePedestalInternalG0(std::unique_lock &ul) { DiffractionExperiment local_experiment(experiment); if (local_experiment.IsFixedGainG1()) local_experiment.Mode(DetectorMode::PedestalG1); else local_experiment.Mode(DetectorMode::PedestalG0); if (local_experiment.GetStorageCellNumber() == 1) local_experiment.StorageCellStart(15); else local_experiment.StorageCellStart(0); if (cancel_sequence) { state = JFJochState::Inactive; return; } if (local_experiment.GetPedestalG0Frames() == 0) { state = JFJochState::Idle; return; } state = JFJochState::Pedestal; services.ConfigureDetector(local_experiment); services.Start(local_experiment, pixel_mask, *calibration); 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)) state = JFJochState::Idle; else state = JFJochState::Inactive; } void JFJochStateMachine::TakePedestalInternalG1(std::unique_lock &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) { state = JFJochState::Inactive; return; } if (local_experiment.GetPedestalG1Frames() == 0) { state = JFJochState::Idle; return; } state = JFJochState::Pedestal; services.ConfigureDetector(local_experiment); services.Start(local_experiment, pixel_mask, *calibration); 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)) state = JFJochState::Idle; else state = JFJochState::Inactive; } void JFJochStateMachine::TakePedestalInternalG2(std::unique_lock &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) { state = JFJochState::Inactive; return; } if (local_experiment.GetPedestalG2Frames() == 0) { state = JFJochState::Idle; return; } state = JFJochState::Pedestal; services.ConfigureDetector(local_experiment); services.Start(local_experiment, pixel_mask, *calibration); 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)) state = JFJochState::Idle; else state = JFJochState::Inactive; } 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"); CheckError(); // Clear error, we don't care what was it logger.Info("Initialize"); state = JFJochState::Busy; 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"); measurement = std::async(std::launch::async, &JFJochStateMachine::PedestalThread, this, std::move(ul)); } void JFJochStateMachine::PedestalThread(std::unique_lock ul) { TakePedestalInternalAll(ul); } void JFJochStateMachine::InitializeThread(std::unique_lock ul) { try { services.On(experiment); } catch (...) { state = JFJochState::Error; throw; } TakePedestalInternalAll(ul); } void JFJochStateMachine::Trigger() { std::unique_lock ul(m); if (state == JFJochState::Measuring) services.Trigger(); } void JFJochStateMachine::Start(const DatasetSettings& settings) { 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(); try { state = JFJochState::Busy; services.SetSpotFindingSettings(GetSpotFindingSettings()); services.Start(experiment, pixel_mask, *calibration); state = JFJochState::Measuring; measurement = std::async(std::launch::async, &JFJochStateMachine::MeasurementThread, this); } catch (...) { state = JFJochState::Error; services.Cancel(); throw; } } 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 { auto tmp_output = services.Stop(); { std::unique_lock ul(m); state = JFJochState::Idle; } } catch (...) { std::unique_lock ul(m); state = JFJochState::Error; } c.notify_all(); } void JFJochStateMachine::Cancel() { // This is inconsistency in naming - need to solve later std::unique_lock ul(m); if ((state == JFJochState::Pedestal) || (state == JFJochState::Measuring)) { services.Cancel(); cancel_sequence = true; } } void JFJochStateMachine::DebugOnly_SetState(JFJochState in_state) { std::unique_lock ul(m); state = in_state; } void JFJochStateMachine::Deactivate() { std::unique_lock ul(m); try { if (measurement.valid()) measurement.get(); services.Off(); state = JFJochState::Inactive; } catch (...) { state = JFJochState::Error; throw; } } JFJochStateMachine::~JFJochStateMachine() { try { if (measurement.valid()) measurement.get(); } catch (...) {} } std::optional 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; } return tmp; } std::vector JFJochStateMachine::GetCalibrationStatistics() const { std::unique_lock ul(calibration_statistics_mutex); return calibration_statistics; } void JFJochStateMachine::SetCalibrationStatistics(const std::vector &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(); } void JFJochStateMachine::ImportDetectorSettings(const DetectorSettings &input) { std::unique_lock ul(experiment_detector_settings_mutex); experiment.ImportDetectorSettings(input); } 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: state = JFJochState::Busy; ImportDetectorSettings(settings); measurement = std::async(std::launch::async, &JFJochStateMachine::PedestalThread, this, std::move(ul)); break; case JFJochState::Measuring: case JFJochState::Busy: case JFJochState::Pedestal: throw WrongDAQStateException ("Cannot change detector settings during data collection"); } } DiffractionExperiment &JFJochStateMachine::NotThreadSafe_Experiment() { return experiment; } BrokerStatus JFJochStateMachine::GetStatus() const { BrokerStatus ret{}; ret.broker_state = state; ret.progress = services.GetReceiverProgress(); return ret; } MultiLinePlot JFJochStateMachine::GetPlots(const PlotRequest &request) const { return services.GetPlots(request); } void JFJochStateMachine::SetSpotFindingSettings(const SpotFindingSettings &settings) { std::unique_lock ul(data_processing_settings_mutex); DiffractionExperiment::CheckDataProcessingSettings(settings); data_processing_settings = settings; services.SetSpotFindingSettings(settings); } SpotFindingSettings JFJochStateMachine::GetSpotFindingSettings() const { std::unique_lock ul(data_processing_settings_mutex); return data_processing_settings; } JFJochState JFJochStateMachine::GetState() const { return state; } void JFJochStateMachine::AddDetectorSetup(const DetectorSetup &setup) { std::unique_lock ul(m); if (detector_setup.empty()) { experiment.Detector(setup); gain_calibration = setup.GetGainCalibration(); current_detector_setup = 0; pixel_mask = PixelMask(experiment); } detector_setup.emplace_back(setup); } DetectorList JFJochStateMachine::GetDetectorsList() const { std::unique_lock ul(m); DetectorList ret; for (const auto & i : detector_setup) { DetectorListElement tmp; tmp.description = i.GetDescription(); tmp.nmodules = i.GetModulesNum(); tmp.width = i.GetGeometry().GetWidth(); tmp.height = i.GetGeometry().GetHeight(); tmp.serial_number = i.GetSerialNumber(); tmp.base_ipv4_addr = i.GetBaseIPv4Addr(); tmp.udp_interface_count = i.GetUDPInterfaceCount(); ret.detector.emplace_back(std::move(tmp)); } ret.current_id = current_detector_setup; return ret; } std::optional 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"); try { experiment.Detector(detector_setup[id]); gain_calibration = detector_setup[id].GetGainCalibration(); pixel_mask = PixelMask(experiment); state = JFJochState::Inactive; current_detector_setup = id; } catch (JFJochException &e) { logger.ErrorException(e); state = JFJochState::Inactive; } } void JFJochStateMachine::SetRadialIntegrationSettings(const AzimuthalIntegrationSettings &settings) { std::unique_lock ul(m); if (IsRunning()) throw WrongDAQStateException ("Cannot change radial integration settings during data collection"); experiment.ImportRadialIntegrationSettings(settings); } AzimuthalIntegrationSettings JFJochStateMachine::GetRadialIntegrationSettings() const { std::unique_lock ul(m); return experiment.GetRadialIntegrationSettings(); } 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::Pedestal: return true; default: throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "State unknown"); } } JFJochState JFJochStateMachine::WaitTillMeasurementDone() { std::unique_lock ul(m); c.wait(ul, [&] { return !IsRunning(); }); return state; } JFJochState JFJochStateMachine::WaitTillMeasurementDone(std::chrono::milliseconds timeout) { std::unique_lock ul(m); c.wait_for(ul, timeout, [&] { return !IsRunning(); }); return state; } std::optional JFJochStateMachine::CheckError() { try { if (measurement.valid()) measurement.get(); } catch (JFJochException &e) { return e.what(); } return {}; } std::string JFJochStateMachine::GetPreviewJPEG(const PreviewJPEGSettings& settings) const { return services.GetPreviewJPEG(settings); } std::string JFJochStateMachine::GetPreviewTIFF(bool calibration) const { return services.GetPreviewTIFF(calibration); } 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); return WriteTIFFToString(tmp.data(), RAW_MODULE_COLS, RAW_MODULE_LINES * experiment.GetModulesNum(), sizeof(uint16_t), false); } 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 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_SIZE) && (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::SetBoxROI(const std::vector &input) { std::unique_lock ul(m); if (IsRunning()) throw WrongDAQStateException ("ROI can be modified only when detector is not running"); experiment.ROI().SetROIBox(input); } void JFJochStateMachine::SetCircleROI(const std::vector &input) { std::unique_lock ul(m); if (IsRunning()) throw WrongDAQStateException ("ROI can be modified only when detector is not running"); experiment.ROI().SetROICircle(input); } std::vector JFJochStateMachine::GetBoxROI() const { std::unique_lock ul(m); return experiment.ROI().GetROIBox(); } std::vector JFJochStateMachine::GetCircleROI() const { std::unique_lock ul(m); return experiment.ROI().GetROICircle(); } std::vector JFJochStateMachine::GetXFELPulseID() const { std::vector ret; services.GetXFELPulseID(ret); return ret; } std::vector JFJochStateMachine::GetXFELEventCode() const { std::vector ret; services.GetXFELEventCode(ret); return ret; } std::string JFJochStateMachine::GetFullPixelMaskTIFF() const { std::unique_lock ul(m); std::vector v = pixel_mask.GetMask(experiment); return WriteTIFFToString(v.data(), experiment.GetXPixelsNum(), experiment.GetYPixelsNum(), sizeof(uint32_t), false); } std::string JFJochStateMachine::GetUserPixelMaskTIFF() const { std::unique_lock ul(m); std::vector v = pixel_mask.GetUserMask(experiment); return WriteTIFFToString(v.data(), experiment.GetXPixelsNum(), experiment.GetYPixelsNum(), sizeof(uint32_t), false); } std::vector JFJochStateMachine::GetFullPixelMask() const { std::unique_lock ul(m); return pixel_mask.GetMask(experiment); } std::vector JFJochStateMachine::GetUserPixelMask() const { std::unique_lock ul(m); return pixel_mask.GetUserMask(experiment); } void JFJochStateMachine::SetUserPixelMask(const std::vector &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())); } } InstrumentMetadata JFJochStateMachine::GetInstrumentMetadata() const { std::unique_lock ul(m); 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"); experiment.ImportInstrumentMetadata(settings); } ImageFormatSettings JFJochStateMachine::GetImageFormatSettings() const { std::unique_lock ul(m); 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"); experiment.ImportImageFormatSettings(settings); pixel_mask.Update(settings); 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 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(); }