diff --git a/broker/JFJochBrokerHttp.cpp b/broker/JFJochBrokerHttp.cpp index b722a6600..88cd13b6c 100644 --- a/broker/JFJochBrokerHttp.cpp +++ b/broker/JFJochBrokerHttp.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -152,7 +153,10 @@ static const std::set PROTECTED_PATHS{ "/image_buffer/image.tiff", "/preview/plot", "/preview/plot.bin", - "/live/events" + "/live/events", + "/config/rugnux", + "/config/rugnux/model", + "/rugnux/status" }; std::optional JFJochBrokerHttp::AuthorizationHeader(const httplib::Request &req) { @@ -251,6 +255,11 @@ void JFJochBrokerHttp::register_routes(httplib::Server &server) { server.Post("/config/image_format/raw", bind_noarg(&JFJochBrokerHttp::config_image_format_raw_post)); server.Get("/config/indexing", bind_noarg(&JFJochBrokerHttp::config_indexing_get)); server.Put("/config/indexing", bind_json(&JFJochBrokerHttp::config_indexing_put, Indexing_settings{})); + server.Get("/config/rugnux", bind_noarg(&JFJochBrokerHttp::config_rugnux_get)); + server.Put("/config/rugnux", bind_json(&JFJochBrokerHttp::config_rugnux_put, Rugnux_settings{})); + server.Put("/config/rugnux/model", bind_req(&JFJochBrokerHttp::config_rugnux_model_put)); + server.Delete("/config/rugnux/model", bind_noarg(&JFJochBrokerHttp::config_rugnux_model_delete)); + server.Get("/rugnux/status", bind_noarg(&JFJochBrokerHttp::rugnux_status_get)); server.Get("/config/bragg_integration", bind_noarg(&JFJochBrokerHttp::config_bragg_integration_get)); server.Put("/config/bragg_integration", bind_json(&JFJochBrokerHttp::config_bragg_integration_put, Bragg_integration_settings{})); server.Get("/config/instrument", bind_noarg(&JFJochBrokerHttp::config_instrument_get)); @@ -963,6 +972,52 @@ void JFJochBrokerHttp::config_indexing_put(const Indexing_settings &indexingSett response.status = 200; } +void JFJochBrokerHttp::config_rugnux_get(httplib::Response &response) { + const RugnuxLiveSettings s = state_machine.GetRugnuxLive(); + Rugnux_status ret; + ret.setEnable(s.enable); + ret.setReservedCpus(s.reserved_cpus); + ret.setDeadlineS(s.deadline_s); + ret.setModelLoaded(!s.model_path.empty()); + if (!s.model_path.empty()) + ret.setModelSizeBytes(static_cast(std::filesystem::file_size(s.model_path))); + ProcessOutput(ret, response); +} + +void JFJochBrokerHttp::config_rugnux_put(const Rugnux_settings &rugnuxSettings, + httplib::Response &response) { + state_machine.SetRugnuxLive(rugnuxSettings.isEnable(), rugnuxSettings.getReservedCpus(), + rugnuxSettings.getDeadlineS()); + response.status = 200; +} + +void JFJochBrokerHttp::config_rugnux_model_put(const httplib::Request &request, + httplib::Response &response) { + if (request.body.empty()) { + response.status = 400; + response.set_content("Request body cannot be empty", "text/plain"); + return; + } + if (request.body.size() > 64 * 1024 * 1024) { + response.status = 413; + response.set_content("Model cannot be larger than 64 MiB", "text/plain"); + return; + } + state_machine.SetRugnuxModel(request.body); + logger.Info("Rugnux model uploaded ({} bytes)", request.body.size()); + response.status = 200; +} + +void JFJochBrokerHttp::config_rugnux_model_delete(httplib::Response &response) { + state_machine.SetRugnuxModel(""); + logger.Info("Rugnux model removed"); + response.status = 200; +} + +void JFJochBrokerHttp::rugnux_status_get(httplib::Response &response) { + ProcessOutput(Convert(state_machine.GetRugnuxStatus()), response); +} + void JFJochBrokerHttp::config_bragg_integration_get(httplib::Response &response) { ProcessOutput(Convert(state_machine.GetBraggIntegrationSettings()), response); } diff --git a/broker/JFJochBrokerHttp.h b/broker/JFJochBrokerHttp.h index 8d42a4593..3528ab5d3 100644 --- a/broker/JFJochBrokerHttp.h +++ b/broker/JFJochBrokerHttp.h @@ -34,6 +34,8 @@ #include "gen/model/Jfjoch_statistics.h" #include "gen/model/Plots.h" #include "gen/model/Roi_definitions.h" +#include "gen/model/Rugnux_settings.h" +#include "gen/model/Rugnux_status.h" #include "gen/model/Scan_result.h" #include "gen/model/Spot_finding_settings.h" #include "gen/model/Zeromq_metadata_settings.h" @@ -198,6 +200,12 @@ class JFJochBrokerHttp { void config_indexing_get(httplib::Response &response); void config_indexing_put(const org::openapitools::server::model::Indexing_settings &indexingSettings, httplib::Response &response); + void config_rugnux_get(httplib::Response &response); + void config_rugnux_put(const org::openapitools::server::model::Rugnux_settings &rugnuxSettings, + httplib::Response &response); + void config_rugnux_model_put(const httplib::Request &request, httplib::Response &response); + void config_rugnux_model_delete(httplib::Response &response); + void rugnux_status_get(httplib::Response &response); void config_bragg_integration_get(httplib::Response &response); void config_bragg_integration_put(const org::openapitools::server::model::Bragg_integration_settings &braggIntegrationSettings, httplib::Response &response); diff --git a/broker/JFJochBrokerParser.cpp b/broker/JFJochBrokerParser.cpp index 7f4efcfbc..45409ca9c 100644 --- a/broker/JFJochBrokerParser.cpp +++ b/broker/JFJochBrokerParser.cpp @@ -276,6 +276,15 @@ void ParseReceiverSettings(const org::openapitools::server::model::Jfjoch_settin } } +void ParseRugnuxSettings(const org::openapitools::server::model::Jfjoch_settings &input, JFJochServices &services) { + if (input.rugnuxIsSet()) + services.SetRugnuxLive(RugnuxLiveSettings{ + .enable = input.getRugnux().isEnable(), + .reserved_cpus = input.getRugnux().getReservedCpus(), + .deadline_s = input.getRugnux().getDeadlineS() + }); +} + SpotFindingSettings ParseSpotFindingSettings(const org::openapitools::server::model::Jfjoch_settings &input) { if (input.spotFindingIsSet()) return Convert(input.getSpotFinding()); diff --git a/broker/JFJochBrokerParser.h b/broker/JFJochBrokerParser.h index f5f2f8399..aff60a211 100644 --- a/broker/JFJochBrokerParser.h +++ b/broker/JFJochBrokerParser.h @@ -7,6 +7,7 @@ #include "../acquisition_device/AcquisitionDeviceGroup.h" #include "../image_pusher/ImagePusher.h" #include "../receiver/JFJochReceiverService.h" +#include "JFJochServices.h" #include "gen/model/Jfjoch_settings.h" DetectorGeometryModular ParseStandardDetectorGeometry(const org::openapitools::server::model::Detector &j); @@ -20,4 +21,5 @@ std::unique_ptr ParseImagePusher(const org::openapitools::server::m void ParseAcquisitionDeviceGroup(const org::openapitools::server::model::Jfjoch_settings &input, AcquisitionDeviceGroup &aq_devices); void ParseReceiverSettings(const org::openapitools::server::model::Jfjoch_settings &input, JFJochReceiverService &service); +void ParseRugnuxSettings(const org::openapitools::server::model::Jfjoch_settings &input, JFJochServices &services); SpotFindingSettings ParseSpotFindingSettings(const org::openapitools::server::model::Jfjoch_settings &input); diff --git a/broker/JFJochServices.cpp b/broker/JFJochServices.cpp index 8eb9edb96..8bd4ff15c 100644 --- a/broker/JFJochServices.cpp +++ b/broker/JFJochServices.cpp @@ -2,6 +2,10 @@ // SPDX-License-Identifier: GPL-3.0-only #include "JFJochServices.h" + +#include +#include +#include #include "../common/JFJochException.h" #include "../detector_control/SLSDetectorWrapper.h" #include "../detector_control/DectrisDetectorWrapper.h" @@ -370,6 +374,51 @@ void JFJochServices::SetupIndexing(const IndexingSettings &input) { receiver->Indexing(input); } +void JFJochServices::SetRugnuxLive(const RugnuxLiveSettings &input) { + if (receiver) + receiver->RugnuxLive(input.enable, input.model_path, input.reserved_cpus, input.deadline_s); + rugnux = input; +} + +RugnuxLiveSettings JFJochServices::GetRugnuxLive() const { + return rugnux; +} + +RugnuxLiveSnapshot JFJochServices::GetRugnuxStatus() const { + if (receiver) + return receiver->GetRugnuxStatus(); + return {}; +} + +void JFJochServices::SetRugnuxModel(const std::string &model) { + RugnuxLiveSettings s = rugnux; + if (!s.model_path.empty()) + std::filesystem::remove(s.model_path); + s.model_path.clear(); + + if (!model.empty()) { + if (rugnux_model_dir.empty()) { + char dir[] = "/tmp/jfjoch_rugnux_XXXXXX"; // mkdtemp creates it 0700 + if (mkdtemp(dir) == nullptr) + throw JFJochException(JFJochExceptionCategory::FileWriteError, + "Cannot create directory for the rugnux model"); + rugnux_model_dir = dir; + } + // gemmi detects PDB/mmCIF from the content, but gzip only from the extension + const bool gzipped = model.size() >= 2 && model[0] == '\x1f' && model[1] == '\x8b'; + s.model_path = rugnux_model_dir + (gzipped ? "/model.gz" : "/model"); + { + std::ofstream f(s.model_path, std::ios::binary); + f.write(model.data(), static_cast(model.size())); + if (!f) + throw JFJochException(JFJochExceptionCategory::FileWriteError, "Cannot write the rugnux model"); + } + std::filesystem::permissions(s.model_path, + std::filesystem::perms::owner_read | std::filesystem::perms::owner_write); + } + SetRugnuxLive(s); +} + LiveCollection *JFJochServices::GetLiveCollection() { return receiver ? receiver->GetLiveCollection() : nullptr; } diff --git a/broker/JFJochServices.h b/broker/JFJochServices.h index 01f051cc0..3a732c20d 100644 --- a/broker/JFJochServices.h +++ b/broker/JFJochServices.h @@ -14,6 +14,13 @@ struct JFJochServicesOutput { JFJochReceiverOutput receiver_output; }; +struct RugnuxLiveSettings { + bool enable = false; + int64_t reserved_cpus = 4; + double deadline_s = 0; // the analysis is cut this long after the last image (0 = never) + std::string model_path; // empty = no model +}; + class JFJochServices { mutable std::shared_timed_mutex detector_mutex; @@ -22,6 +29,9 @@ class JFJochServices { std::shared_ptr image_puller; std::atomic cannot_stop_detector = false; + + RugnuxLiveSettings rugnux; + std::string rugnux_model_dir; // private directory, created at the first model upload Logger &logger; public: explicit JFJochServices(Logger &in_logger); @@ -76,6 +86,12 @@ public: void SetupIndexing(const IndexingSettings& input); + void SetRugnuxLive(const RugnuxLiveSettings &input); + RugnuxLiveSettings GetRugnuxLive() const; + // The model file is written 0600 into a private directory; an empty model removes it + void SetRugnuxModel(const std::string &model); + RugnuxLiveSnapshot GetRugnuxStatus() const; + ImagePusherStatus GetImagePusherStatus() const; LiveCollection *GetLiveCollection(); }; diff --git a/broker/JFJochStateMachine.cpp b/broker/JFJochStateMachine.cpp index 1b93ad2a7..47dacea7f 100644 --- a/broker/JFJochStateMachine.cpp +++ b/broker/JFJochStateMachine.cpp @@ -1209,6 +1209,35 @@ IndexingSettings JFJochStateMachine::GetIndexingSettings() const { return experiment.GetIndexingSettings(); } +void JFJochStateMachine::SetRugnuxLive(bool enable, int64_t reserved_cpus, double deadline_s) { + std::unique_lock ul(m); + if (IsRunning()) + throw WrongDAQStateException("Cannot change rugnux settings during data collection"); + std::unique_lock ul2(rugnux_mutex); + RugnuxLiveSettings s = services.GetRugnuxLive(); + s.enable = enable; + s.reserved_cpus = reserved_cpus; + s.deadline_s = deadline_s; + services.SetRugnuxLive(s); +} + +void JFJochStateMachine::SetRugnuxModel(const std::string &model) { + std::unique_lock ul(m); + if (IsRunning()) + throw WrongDAQStateException("Cannot change rugnux model during data collection"); + std::unique_lock ul2(rugnux_mutex); + services.SetRugnuxModel(model); +} + +RugnuxLiveSettings JFJochStateMachine::GetRugnuxLive() const { + std::unique_lock ul(rugnux_mutex); + return services.GetRugnuxLive(); +} + +RugnuxLiveSnapshot JFJochStateMachine::GetRugnuxStatus() const { + return services.GetRugnuxStatus(); +} + void JFJochStateMachine::SetIndexingSettings(const IndexingSettings &input) { std::unique_lock ul(m); if (IsRunning()) diff --git a/broker/JFJochStateMachine.h b/broker/JFJochStateMachine.h index a28d9cecc..ca053658a 100644 --- a/broker/JFJochStateMachine.h +++ b/broker/JFJochStateMachine.h @@ -90,6 +90,7 @@ class JFJochStateMachine { mutable std::mutex experiment_file_writer_settings_mutex; mutable std::mutex experiment_indexing_settings_mutex; mutable std::mutex experiment_dark_mask_settings_mutex; + mutable std::mutex rugnux_mutex; DiffractionExperiment experiment; // mutex m is protecting: @@ -247,6 +248,11 @@ public: void SetIndexingSettings(const IndexingSettings &input); IndexingSettings GetIndexingSettings() const; + void SetRugnuxLive(bool enable, int64_t reserved_cpus, double deadline_s); + void SetRugnuxModel(const std::string &model); // empty = remove the model + RugnuxLiveSettings GetRugnuxLive() const; + RugnuxLiveSnapshot GetRugnuxStatus() const; + void SetBraggIntegrationSettings(const BraggIntegrationSettings &input); BraggIntegrationSettings GetBraggIntegrationSettings() const; PixelMaskStatistics GetPixelMaskStatistics() const; diff --git a/broker/OpenAPIConvert.cpp b/broker/OpenAPIConvert.cpp index 8cf6f8e6d..5f8a85376 100644 --- a/broker/OpenAPIConvert.cpp +++ b/broker/OpenAPIConvert.cpp @@ -1221,6 +1221,60 @@ DarkMaskSettings Convert(const org::openapitools::server::model::Dark_mask_setti return ret; } +org::openapitools::server::model::Rugnux_live_status Convert(const RugnuxLiveSnapshot& input) { + org::openapitools::server::model::Rugnux_live_status ret; + switch (input.state) { + case RugnuxLiveState::Idle: ret.setState("Idle"); break; + case RugnuxLiveState::Running: ret.setState("Running"); break; + case RugnuxLiveState::Done: ret.setState("Done"); break; + case RugnuxLiveState::Failed: ret.setState("Failed"); break; + case RugnuxLiveState::Cancelled: ret.setState("Cancelled"); break; + case RugnuxLiveState::Skipped: ret.setState("Skipped"); break; + } + ret.setMessage(input.message); + ret.setImagesExpected(input.images_expected); + ret.setImagesReceived(input.images_received); + ret.setElapsedS(input.elapsed_s); + ret.setProbe(input.probe); + if (input.probe_s) + ret.setProbeS(input.probe_s.value()); + if (input.step_progress) + ret.setStepProgress(input.step_progress.value()); + + std::vector steps; + for (const auto &s: input.steps) { + org::openapitools::server::model::Rugnux_live_step step; + step.setTitle(s.title); + step.setStartedS(s.started_s); + if (s.duration_s) + step.setDurationS(s.duration_s.value()); + step.setNotes(s.notes); + steps.push_back(step); + } + ret.setSteps(steps); + + if (input.last_image_s) + ret.setLastImageS(input.last_image_s.value()); + ret.setCut(input.cut); + if (input.cut_s) + ret.setCutS(input.cut_s.value()); + if (input.last_image_to_written_s) + ret.setLastImageToWrittenS(input.last_image_to_written_s.value()); + ret.setVerdict(input.verdict); + ret.setVerdictText(input.verdict_text); + + std::vector summary; + for (const auto &[label, value]: input.summary) { + org::openapitools::server::model::Rugnux_summary_row row; + row.setLabel(label); + row.setValue(value); + summary.push_back(row); + } + ret.setSummary(summary); + ret.setWarnings(input.warnings); + return ret; +} + org::openapitools::server::model::Image_pusher_status Convert(const ImagePusherStatus& input) { org::openapitools::server::model::Image_pusher_status ret; ret.setAddr(input.address); diff --git a/broker/OpenAPIConvert.h b/broker/OpenAPIConvert.h index fd36ea1da..6556cc0bc 100644 --- a/broker/OpenAPIConvert.h +++ b/broker/OpenAPIConvert.h @@ -30,6 +30,7 @@ #include "gen/model/Indexing_settings.h" #include "gen/model/Bragg_integration_settings.h" #include "gen/model/Scan_result.h" +#include "gen/model/Rugnux_live_status.h" #include "../common/JFJochMessages.h" #include "../common/DatasetSettings.h" @@ -41,6 +42,7 @@ #include "../common/InstrumentMetadata.h" #include "../common/ScanResult.h" #include "../image_pusher/ImagePusher.h" +#include "../receiver/RugnuxLiveStatus.h" SpotFindingSettings Convert(const org::openapitools::server::model::Spot_finding_settings &input); org::openapitools::server::model::Spot_finding_settings Convert(const SpotFindingSettings &input); @@ -99,4 +101,5 @@ org::openapitools::server::model::Dark_mask_settings Convert(const DarkMaskSetti DarkMaskSettings Convert(const org::openapitools::server::model::Dark_mask_settings& input); org::openapitools::server::model::Image_pusher_status Convert(const ImagePusherStatus& input); +org::openapitools::server::model::Rugnux_live_status Convert(const RugnuxLiveSnapshot& input); diff --git a/broker/gen/model/Jfjoch_settings.cpp b/broker/gen/model/Jfjoch_settings.cpp index fb16b7494..9ea19e055 100644 --- a/broker/gen/model/Jfjoch_settings.cpp +++ b/broker/gen/model/Jfjoch_settings.cpp @@ -44,6 +44,7 @@ Jfjoch_settings::Jfjoch_settings() m_Zeromq_previewIsSet = false; m_Zeromq_metadataIsSet = false; m_Dark_maskIsSet = false; + m_RugnuxIsSet = false; } @@ -141,7 +142,7 @@ bool Jfjoch_settings::validate(std::stringstream& msg, const std::string& pathPr } } - + return success; } @@ -211,7 +212,10 @@ bool Jfjoch_settings::operator==(const Jfjoch_settings& rhs) const ((!zeromqMetadataIsSet() && !rhs.zeromqMetadataIsSet()) || (zeromqMetadataIsSet() && rhs.zeromqMetadataIsSet() && getZeromqMetadata() == rhs.getZeromqMetadata())) && - ((!darkMaskIsSet() && !rhs.darkMaskIsSet()) || (darkMaskIsSet() && rhs.darkMaskIsSet() && getDarkMask() == rhs.getDarkMask())) + ((!darkMaskIsSet() && !rhs.darkMaskIsSet()) || (darkMaskIsSet() && rhs.darkMaskIsSet() && getDarkMask() == rhs.getDarkMask())) && + + + ((!rugnuxIsSet() && !rhs.rugnuxIsSet()) || (rugnuxIsSet() && rhs.rugnuxIsSet() && getRugnux() == rhs.getRugnux())) ; } @@ -263,6 +267,8 @@ void to_json(nlohmann::json& j, const Jfjoch_settings& o) j["zeromq_metadata"] = o.m_Zeromq_metadata; if(o.darkMaskIsSet()) j["dark_mask"] = o.m_Dark_mask; + if(o.rugnuxIsSet()) + j["rugnux"] = o.m_Rugnux; } @@ -361,6 +367,11 @@ void from_json(const nlohmann::json& j, Jfjoch_settings& o) j.at("dark_mask").get_to(o.m_Dark_mask); o.m_Dark_maskIsSet = true; } + if(j.find("rugnux") != j.end()) + { + j.at("rugnux").get_to(o.m_Rugnux); + o.m_RugnuxIsSet = true; + } } @@ -694,6 +705,23 @@ void Jfjoch_settings::unsetDark_mask() { m_Dark_maskIsSet = false; } +org::openapitools::server::model::Rugnux_settings Jfjoch_settings::getRugnux() const +{ + return m_Rugnux; +} +void Jfjoch_settings::setRugnux(org::openapitools::server::model::Rugnux_settings const& value) +{ + m_Rugnux = value; + m_RugnuxIsSet = true; +} +bool Jfjoch_settings::rugnuxIsSet() const +{ + return m_RugnuxIsSet; +} +void Jfjoch_settings::unsetRugnux() +{ + m_RugnuxIsSet = false; +} } // namespace org::openapitools::server::model diff --git a/broker/gen/model/Jfjoch_settings.h b/broker/gen/model/Jfjoch_settings.h index 434ebd2c4..f9033a435 100644 --- a/broker/gen/model/Jfjoch_settings.h +++ b/broker/gen/model/Jfjoch_settings.h @@ -31,6 +31,7 @@ #include "Image_format_settings.h" #include "Zeromq_metadata_settings.h" #include "Detector_settings.h" +#include "Rugnux_settings.h" #include #include "Indexing_settings.h" #include "Detector.h" @@ -216,6 +217,13 @@ public: void setDarkMask(org::openapitools::server::model::Dark_mask_settings const& value); bool darkMaskIsSet() const; void unsetDark_mask(); + /// + /// + /// + org::openapitools::server::model::Rugnux_settings getRugnux() const; + void setRugnux(org::openapitools::server::model::Rugnux_settings const& value); + bool rugnuxIsSet() const; + void unsetRugnux(); friend void to_json(nlohmann::json& j, const Jfjoch_settings& o); friend void from_json(const nlohmann::json& j, Jfjoch_settings& o); @@ -262,6 +270,8 @@ protected: bool m_Zeromq_metadataIsSet; org::openapitools::server::model::Dark_mask_settings m_Dark_mask; bool m_Dark_maskIsSet; + org::openapitools::server::model::Rugnux_settings m_Rugnux; + bool m_RugnuxIsSet; }; diff --git a/broker/gen/model/Rugnux_live_status.cpp b/broker/gen/model/Rugnux_live_status.cpp new file mode 100644 index 000000000..765004d94 --- /dev/null +++ b/broker/gen/model/Rugnux_live_status.cpp @@ -0,0 +1,484 @@ +/** +* Jungfraujoch +* API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. +* +* The version of the OpenAPI document: 1.0.0-rc.175 +* Contact: filip.leonarski@psi.ch +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ + + +#include "Rugnux_live_status.h" +#include "Helpers.h" + +#include + +namespace org::openapitools::server::model +{ + +Rugnux_live_status::Rugnux_live_status() +{ + m_State = ""; + m_Probe = ""; + m_Probe_s = 0.0; + m_Probe_sIsSet = false; + m_Message = ""; + m_Images_expected = 0L; + m_Images_received = 0L; + m_Elapsed_s = 0.0; + m_Step_progress = 0.0f; + m_Step_progressIsSet = false; + m_Last_image_s = 0.0; + m_Last_image_sIsSet = false; + m_Cut = ""; + m_CutIsSet = false; + m_Cut_s = 0.0; + m_Cut_sIsSet = false; + m_Last_image_to_written_s = 0.0; + m_Last_image_to_written_sIsSet = false; + m_Verdict = ""; + m_Verdict_text = ""; + +} + +void Rugnux_live_status::validate() const +{ + std::stringstream msg; + if (!validate(msg)) + { + throw org::openapitools::server::helpers::ValidationException(msg.str()); + } +} + +bool Rugnux_live_status::validate(std::stringstream& msg) const +{ + return validate(msg, ""); +} + +bool Rugnux_live_status::validate(std::stringstream& msg, const std::string& pathPrefix) const +{ + bool success = true; + const std::string _pathPrefix = pathPrefix.empty() ? "Rugnux_live_status" : pathPrefix; + + + if (stepProgressIsSet()) + { + const float& value = m_Step_progress; + const std::string currentValuePath = _pathPrefix + ".stepProgress"; + + + if (value < static_cast(0.0)) + { + success = false; + msg << currentValuePath << ": must be greater than or equal to 0.0;"; + } + if (value > static_cast(1.0)) + { + success = false; + msg << currentValuePath << ": must be less than or equal to 1.0;"; + } + + } + + + /* Steps */ { + const std::vector& value = m_Steps; + const std::string currentValuePath = _pathPrefix + ".steps"; + + + { // Recursive validation of array elements + const std::string oldValuePath = currentValuePath; + int i = 0; + for (const org::openapitools::server::model::Rugnux_live_step& value : value) + { + const std::string currentValuePath = oldValuePath + "[" + std::to_string(i) + "]"; + + success = value.validate(msg, currentValuePath + ".steps") && success; + + i++; + } + } + + } + + + /* Summary */ { + const std::vector& value = m_Summary; + const std::string currentValuePath = _pathPrefix + ".summary"; + + + { // Recursive validation of array elements + const std::string oldValuePath = currentValuePath; + int i = 0; + for (const org::openapitools::server::model::Rugnux_summary_row& value : value) + { + const std::string currentValuePath = oldValuePath + "[" + std::to_string(i) + "]"; + + success = value.validate(msg, currentValuePath + ".summary") && success; + + i++; + } + } + + } + + + /* Warnings */ { + const std::vector& value = m_Warnings; + const std::string currentValuePath = _pathPrefix + ".warnings"; + + + { // Recursive validation of array elements + const std::string oldValuePath = currentValuePath; + int i = 0; + for (const std::string& value : value) + { + const std::string currentValuePath = oldValuePath + "[" + std::to_string(i) + "]"; + + + + i++; + } + } + + } + + return success; +} + +bool Rugnux_live_status::operator==(const Rugnux_live_status& rhs) const +{ + return + + + (getState() == rhs.getState()) + && + + (getProbe() == rhs.getProbe()) + && + + + ((!probeSIsSet() && !rhs.probeSIsSet()) || (probeSIsSet() && rhs.probeSIsSet() && getProbeS() == rhs.getProbeS())) && + + (getMessage() == rhs.getMessage()) + && + + (getImagesExpected() == rhs.getImagesExpected()) + && + + (getImagesReceived() == rhs.getImagesReceived()) + && + + (getElapsedS() == rhs.getElapsedS()) + && + + + ((!stepProgressIsSet() && !rhs.stepProgressIsSet()) || (stepProgressIsSet() && rhs.stepProgressIsSet() && getStepProgress() == rhs.getStepProgress())) && + + (getSteps() == rhs.getSteps()) + && + + + ((!lastImageSIsSet() && !rhs.lastImageSIsSet()) || (lastImageSIsSet() && rhs.lastImageSIsSet() && getLastImageS() == rhs.getLastImageS())) && + + + ((!cutIsSet() && !rhs.cutIsSet()) || (cutIsSet() && rhs.cutIsSet() && getCut() == rhs.getCut())) && + + + ((!cutSIsSet() && !rhs.cutSIsSet()) || (cutSIsSet() && rhs.cutSIsSet() && getCutS() == rhs.getCutS())) && + + + ((!lastImageToWrittenSIsSet() && !rhs.lastImageToWrittenSIsSet()) || (lastImageToWrittenSIsSet() && rhs.lastImageToWrittenSIsSet() && getLastImageToWrittenS() == rhs.getLastImageToWrittenS())) && + + (getVerdict() == rhs.getVerdict()) + && + + (getVerdictText() == rhs.getVerdictText()) + && + + (getSummary() == rhs.getSummary()) + && + + (getWarnings() == rhs.getWarnings()) + + + ; +} + +bool Rugnux_live_status::operator!=(const Rugnux_live_status& rhs) const +{ + return !(*this == rhs); +} + +void to_json(nlohmann::json& j, const Rugnux_live_status& o) +{ + j = nlohmann::json::object(); + j["state"] = o.m_State; + j["probe"] = o.m_Probe; + if(o.probeSIsSet()) + j["probe_s"] = o.m_Probe_s; + j["message"] = o.m_Message; + j["images_expected"] = o.m_Images_expected; + j["images_received"] = o.m_Images_received; + j["elapsed_s"] = o.m_Elapsed_s; + if(o.stepProgressIsSet()) + j["step_progress"] = o.m_Step_progress; + j["steps"] = o.m_Steps; + if(o.lastImageSIsSet()) + j["last_image_s"] = o.m_Last_image_s; + if(o.cutIsSet()) + j["cut"] = o.m_Cut; + if(o.cutSIsSet()) + j["cut_s"] = o.m_Cut_s; + if(o.lastImageToWrittenSIsSet()) + j["last_image_to_written_s"] = o.m_Last_image_to_written_s; + j["verdict"] = o.m_Verdict; + j["verdict_text"] = o.m_Verdict_text; + j["summary"] = o.m_Summary; + j["warnings"] = o.m_Warnings; + +} + +void from_json(const nlohmann::json& j, Rugnux_live_status& o) +{ + j.at("state").get_to(o.m_State); + j.at("probe").get_to(o.m_Probe); + if(j.find("probe_s") != j.end()) + { + j.at("probe_s").get_to(o.m_Probe_s); + o.m_Probe_sIsSet = true; + } + j.at("message").get_to(o.m_Message); + j.at("images_expected").get_to(o.m_Images_expected); + j.at("images_received").get_to(o.m_Images_received); + j.at("elapsed_s").get_to(o.m_Elapsed_s); + if(j.find("step_progress") != j.end()) + { + j.at("step_progress").get_to(o.m_Step_progress); + o.m_Step_progressIsSet = true; + } + j.at("steps").get_to(o.m_Steps); + if(j.find("last_image_s") != j.end()) + { + j.at("last_image_s").get_to(o.m_Last_image_s); + o.m_Last_image_sIsSet = true; + } + if(j.find("cut") != j.end()) + { + j.at("cut").get_to(o.m_Cut); + o.m_CutIsSet = true; + } + if(j.find("cut_s") != j.end()) + { + j.at("cut_s").get_to(o.m_Cut_s); + o.m_Cut_sIsSet = true; + } + if(j.find("last_image_to_written_s") != j.end()) + { + j.at("last_image_to_written_s").get_to(o.m_Last_image_to_written_s); + o.m_Last_image_to_written_sIsSet = true; + } + j.at("verdict").get_to(o.m_Verdict); + j.at("verdict_text").get_to(o.m_Verdict_text); + j.at("summary").get_to(o.m_Summary); + j.at("warnings").get_to(o.m_Warnings); + +} + +std::string Rugnux_live_status::getState() const +{ + return m_State; +} +void Rugnux_live_status::setState(std::string const& value) +{ + m_State = value; +} +std::string Rugnux_live_status::getProbe() const +{ + return m_Probe; +} +void Rugnux_live_status::setProbe(std::string const& value) +{ + m_Probe = value; +} +double Rugnux_live_status::getProbeS() const +{ + return m_Probe_s; +} +void Rugnux_live_status::setProbeS(double const value) +{ + m_Probe_s = value; + m_Probe_sIsSet = true; +} +bool Rugnux_live_status::probeSIsSet() const +{ + return m_Probe_sIsSet; +} +void Rugnux_live_status::unsetProbe_s() +{ + m_Probe_sIsSet = false; +} +std::string Rugnux_live_status::getMessage() const +{ + return m_Message; +} +void Rugnux_live_status::setMessage(std::string const& value) +{ + m_Message = value; +} +int64_t Rugnux_live_status::getImagesExpected() const +{ + return m_Images_expected; +} +void Rugnux_live_status::setImagesExpected(int64_t const value) +{ + m_Images_expected = value; +} +int64_t Rugnux_live_status::getImagesReceived() const +{ + return m_Images_received; +} +void Rugnux_live_status::setImagesReceived(int64_t const value) +{ + m_Images_received = value; +} +double Rugnux_live_status::getElapsedS() const +{ + return m_Elapsed_s; +} +void Rugnux_live_status::setElapsedS(double const value) +{ + m_Elapsed_s = value; +} +float Rugnux_live_status::getStepProgress() const +{ + return m_Step_progress; +} +void Rugnux_live_status::setStepProgress(float const value) +{ + m_Step_progress = value; + m_Step_progressIsSet = true; +} +bool Rugnux_live_status::stepProgressIsSet() const +{ + return m_Step_progressIsSet; +} +void Rugnux_live_status::unsetStep_progress() +{ + m_Step_progressIsSet = false; +} +std::vector Rugnux_live_status::getSteps() const +{ + return m_Steps; +} +void Rugnux_live_status::setSteps(std::vector const& value) +{ + m_Steps = value; +} +double Rugnux_live_status::getLastImageS() const +{ + return m_Last_image_s; +} +void Rugnux_live_status::setLastImageS(double const value) +{ + m_Last_image_s = value; + m_Last_image_sIsSet = true; +} +bool Rugnux_live_status::lastImageSIsSet() const +{ + return m_Last_image_sIsSet; +} +void Rugnux_live_status::unsetLast_image_s() +{ + m_Last_image_sIsSet = false; +} +std::string Rugnux_live_status::getCut() const +{ + return m_Cut; +} +void Rugnux_live_status::setCut(std::string const& value) +{ + m_Cut = value; + m_CutIsSet = true; +} +bool Rugnux_live_status::cutIsSet() const +{ + return m_CutIsSet; +} +void Rugnux_live_status::unsetCut() +{ + m_CutIsSet = false; +} +double Rugnux_live_status::getCutS() const +{ + return m_Cut_s; +} +void Rugnux_live_status::setCutS(double const value) +{ + m_Cut_s = value; + m_Cut_sIsSet = true; +} +bool Rugnux_live_status::cutSIsSet() const +{ + return m_Cut_sIsSet; +} +void Rugnux_live_status::unsetCut_s() +{ + m_Cut_sIsSet = false; +} +double Rugnux_live_status::getLastImageToWrittenS() const +{ + return m_Last_image_to_written_s; +} +void Rugnux_live_status::setLastImageToWrittenS(double const value) +{ + m_Last_image_to_written_s = value; + m_Last_image_to_written_sIsSet = true; +} +bool Rugnux_live_status::lastImageToWrittenSIsSet() const +{ + return m_Last_image_to_written_sIsSet; +} +void Rugnux_live_status::unsetLast_image_to_written_s() +{ + m_Last_image_to_written_sIsSet = false; +} +std::string Rugnux_live_status::getVerdict() const +{ + return m_Verdict; +} +void Rugnux_live_status::setVerdict(std::string const& value) +{ + m_Verdict = value; +} +std::string Rugnux_live_status::getVerdictText() const +{ + return m_Verdict_text; +} +void Rugnux_live_status::setVerdictText(std::string const& value) +{ + m_Verdict_text = value; +} +std::vector Rugnux_live_status::getSummary() const +{ + return m_Summary; +} +void Rugnux_live_status::setSummary(std::vector const& value) +{ + m_Summary = value; +} +std::vector Rugnux_live_status::getWarnings() const +{ + return m_Warnings; +} +void Rugnux_live_status::setWarnings(std::vector const& value) +{ + m_Warnings = value; +} + + +} // namespace org::openapitools::server::model + diff --git a/broker/gen/model/Rugnux_live_status.h b/broker/gen/model/Rugnux_live_status.h new file mode 100644 index 000000000..d2faceeed --- /dev/null +++ b/broker/gen/model/Rugnux_live_status.h @@ -0,0 +1,204 @@ +/** +* Jungfraujoch +* API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. +* +* The version of the OpenAPI document: 1.0.0-rc.175 +* Contact: filip.leonarski@psi.ch +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ +/* + * Rugnux_live_status.h + * + * Live rugnux processing of the last collection; kept until the next collection starts + */ + +#ifndef Rugnux_live_status_H_ +#define Rugnux_live_status_H_ + + +#include "Rugnux_summary_row.h" +#include +#include "Rugnux_live_step.h" +#include +#include + +namespace org::openapitools::server::model +{ + +/// +/// Live rugnux processing of the last collection; kept until the next collection starts +/// +class Rugnux_live_status +{ +public: + Rugnux_live_status(); + virtual ~Rugnux_live_status() = default; + + + /// + /// Validate the current data in the model. Throws a ValidationException on failure. + /// + void validate() const; + + /// + /// Validate the current data in the model. Returns false on error and writes an error + /// message into the given stringstream. + /// + bool validate(std::stringstream& msg) const; + + /// + /// Helper overload for validate. Used when one model stores another model and calls it's validate. + /// Not meant to be called outside that case. + /// + bool validate(std::stringstream& msg, const std::string& pathPrefix) const; + + bool operator==(const Rugnux_live_status& rhs) const; + bool operator!=(const Rugnux_live_status& rhs) const; + + ///////////////////////////////////////////// + /// Rugnux_live_status members + + /// + /// + /// + std::string getState() const; + void setState(std::string const& value); + /// + /// Answer of the early lattice probe on the first 60 degrees; empty until it answers + /// + std::string getProbe() const; + void setProbe(std::string const& value); + /// + /// When the probe answered, since the collection started + /// + double getProbeS() const; + void setProbeS(double const value); + bool probeSIsSet() const; + void unsetProbe_s(); + /// + /// Why the run failed or was skipped + /// + std::string getMessage() const; + void setMessage(std::string const& value); + /// + /// + /// + int64_t getImagesExpected() const; + void setImagesExpected(int64_t const value); + /// + /// + /// + int64_t getImagesReceived() const; + void setImagesReceived(int64_t const value); + /// + /// + /// + double getElapsedS() const; + void setElapsedS(double const value); + /// + /// Fraction of the current pass's images + /// + float getStepProgress() const; + void setStepProgress(float const value); + bool stepProgressIsSet() const; + void unsetStep_progress(); + /// + /// + /// + std::vector getSteps() const; + void setSteps(std::vector const& value); + /// + /// When the last image arrived (and the cutoff timer started), since the collection started + /// + double getLastImageS() const; + void setLastImageS(double const value); + bool lastImageSIsSet() const; + void unsetLast_image_s(); + /// + /// Empty, or why the analysis was cut: `deadline` (`deadline_s` after the last image) or `cancel` (`POST /cancel`). The end message then carried what was established at the cut. + /// + std::string getCut() const; + void setCut(std::string const& value); + bool cutIsSet() const; + void unsetCut(); + /// + /// When the analysis was cut, since the collection started + /// + double getCutS() const; + void setCutS(double const value); + bool cutSIsSet() const; + void unsetCut_s(); + /// + /// From the last image received to all results written + /// + double getLastImageToWrittenS() const; + void setLastImageToWrittenS(double const value); + bool lastImageToWrittenSIsSet() const; + void unsetLast_image_to_written_s(); + /// + /// OK, WARNINGS, UNUSABLE or FAILED; empty until the run is over + /// + std::string getVerdict() const; + void setVerdict(std::string const& value); + /// + /// + /// + std::string getVerdictText() const; + void setVerdictText(std::string const& value); + /// + /// + /// + std::vector getSummary() const; + void setSummary(std::vector const& value); + /// + /// + /// + std::vector getWarnings() const; + void setWarnings(std::vector const& value); + + friend void to_json(nlohmann::json& j, const Rugnux_live_status& o); + friend void from_json(const nlohmann::json& j, Rugnux_live_status& o); +protected: + std::string m_State; + + std::string m_Probe; + + double m_Probe_s; + bool m_Probe_sIsSet; + std::string m_Message; + + int64_t m_Images_expected; + + int64_t m_Images_received; + + double m_Elapsed_s; + + float m_Step_progress; + bool m_Step_progressIsSet; + std::vector m_Steps; + + double m_Last_image_s; + bool m_Last_image_sIsSet; + std::string m_Cut; + bool m_CutIsSet; + double m_Cut_s; + bool m_Cut_sIsSet; + double m_Last_image_to_written_s; + bool m_Last_image_to_written_sIsSet; + std::string m_Verdict; + + std::string m_Verdict_text; + + std::vector m_Summary; + + std::vector m_Warnings; + + +}; + +} // namespace org::openapitools::server::model + +#endif /* Rugnux_live_status_H_ */ diff --git a/broker/gen/model/Rugnux_live_step.cpp b/broker/gen/model/Rugnux_live_step.cpp new file mode 100644 index 000000000..4b05aabc2 --- /dev/null +++ b/broker/gen/model/Rugnux_live_step.cpp @@ -0,0 +1,168 @@ +/** +* Jungfraujoch +* API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. +* +* The version of the OpenAPI document: 1.0.0-rc.175 +* Contact: filip.leonarski@psi.ch +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ + + +#include "Rugnux_live_step.h" +#include "Helpers.h" + +#include + +namespace org::openapitools::server::model +{ + +Rugnux_live_step::Rugnux_live_step() +{ + m_Title = ""; + m_Started_s = 0.0; + m_Duration_s = 0.0; + m_Duration_sIsSet = false; + +} + +void Rugnux_live_step::validate() const +{ + std::stringstream msg; + if (!validate(msg)) + { + throw org::openapitools::server::helpers::ValidationException(msg.str()); + } +} + +bool Rugnux_live_step::validate(std::stringstream& msg) const +{ + return validate(msg, ""); +} + +bool Rugnux_live_step::validate(std::stringstream& msg, const std::string& pathPrefix) const +{ + bool success = true; + const std::string _pathPrefix = pathPrefix.empty() ? "Rugnux_live_step" : pathPrefix; + + + + /* Notes */ { + const std::vector& value = m_Notes; + const std::string currentValuePath = _pathPrefix + ".notes"; + + + { // Recursive validation of array elements + const std::string oldValuePath = currentValuePath; + int i = 0; + for (const std::string& value : value) + { + const std::string currentValuePath = oldValuePath + "[" + std::to_string(i) + "]"; + + + + i++; + } + } + + } + + return success; +} + +bool Rugnux_live_step::operator==(const Rugnux_live_step& rhs) const +{ + return + + + (getTitle() == rhs.getTitle()) + && + + (getStartedS() == rhs.getStartedS()) + && + + + ((!durationSIsSet() && !rhs.durationSIsSet()) || (durationSIsSet() && rhs.durationSIsSet() && getDurationS() == rhs.getDurationS())) && + + (getNotes() == rhs.getNotes()) + + + ; +} + +bool Rugnux_live_step::operator!=(const Rugnux_live_step& rhs) const +{ + return !(*this == rhs); +} + +void to_json(nlohmann::json& j, const Rugnux_live_step& o) +{ + j = nlohmann::json::object(); + j["title"] = o.m_Title; + j["started_s"] = o.m_Started_s; + if(o.durationSIsSet()) + j["duration_s"] = o.m_Duration_s; + j["notes"] = o.m_Notes; + +} + +void from_json(const nlohmann::json& j, Rugnux_live_step& o) +{ + j.at("title").get_to(o.m_Title); + j.at("started_s").get_to(o.m_Started_s); + if(j.find("duration_s") != j.end()) + { + j.at("duration_s").get_to(o.m_Duration_s); + o.m_Duration_sIsSet = true; + } + j.at("notes").get_to(o.m_Notes); + +} + +std::string Rugnux_live_step::getTitle() const +{ + return m_Title; +} +void Rugnux_live_step::setTitle(std::string const& value) +{ + m_Title = value; +} +double Rugnux_live_step::getStartedS() const +{ + return m_Started_s; +} +void Rugnux_live_step::setStartedS(double const value) +{ + m_Started_s = value; +} +double Rugnux_live_step::getDurationS() const +{ + return m_Duration_s; +} +void Rugnux_live_step::setDurationS(double const value) +{ + m_Duration_s = value; + m_Duration_sIsSet = true; +} +bool Rugnux_live_step::durationSIsSet() const +{ + return m_Duration_sIsSet; +} +void Rugnux_live_step::unsetDuration_s() +{ + m_Duration_sIsSet = false; +} +std::vector Rugnux_live_step::getNotes() const +{ + return m_Notes; +} +void Rugnux_live_step::setNotes(std::vector const& value) +{ + m_Notes = value; +} + + +} // namespace org::openapitools::server::model + diff --git a/broker/gen/model/Rugnux_live_step.h b/broker/gen/model/Rugnux_live_step.h new file mode 100644 index 000000000..ed908d260 --- /dev/null +++ b/broker/gen/model/Rugnux_live_step.h @@ -0,0 +1,101 @@ +/** +* Jungfraujoch +* API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. +* +* The version of the OpenAPI document: 1.0.0-rc.175 +* Contact: filip.leonarski@psi.ch +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ +/* + * Rugnux_live_step.h + * + * + */ + +#ifndef Rugnux_live_step_H_ +#define Rugnux_live_step_H_ + + +#include +#include +#include + +namespace org::openapitools::server::model +{ + +/// +/// +/// +class Rugnux_live_step +{ +public: + Rugnux_live_step(); + virtual ~Rugnux_live_step() = default; + + + /// + /// Validate the current data in the model. Throws a ValidationException on failure. + /// + void validate() const; + + /// + /// Validate the current data in the model. Returns false on error and writes an error + /// message into the given stringstream. + /// + bool validate(std::stringstream& msg) const; + + /// + /// Helper overload for validate. Used when one model stores another model and calls it's validate. + /// Not meant to be called outside that case. + /// + bool validate(std::stringstream& msg, const std::string& pathPrefix) const; + + bool operator==(const Rugnux_live_step& rhs) const; + bool operator!=(const Rugnux_live_step& rhs) const; + + ///////////////////////////////////////////// + /// Rugnux_live_step members + + /// + /// + /// + std::string getTitle() const; + void setTitle(std::string const& value); + /// + /// Start of the step, since the collection started + /// + double getStartedS() const; + void setStartedS(double const value); + /// + /// Set once the next step starts or the run ends + /// + double getDurationS() const; + void setDurationS(double const value); + bool durationSIsSet() const; + void unsetDuration_s(); + /// + /// What the step found (e.g. lattice, space group) + /// + std::vector getNotes() const; + void setNotes(std::vector const& value); + + friend void to_json(nlohmann::json& j, const Rugnux_live_step& o); + friend void from_json(const nlohmann::json& j, Rugnux_live_step& o); +protected: + std::string m_Title; + + double m_Started_s; + + double m_Duration_s; + bool m_Duration_sIsSet; + std::vector m_Notes; + + +}; + +} // namespace org::openapitools::server::model + +#endif /* Rugnux_live_step_H_ */ diff --git a/broker/gen/model/Rugnux_settings.cpp b/broker/gen/model/Rugnux_settings.cpp new file mode 100644 index 000000000..de476f436 --- /dev/null +++ b/broker/gen/model/Rugnux_settings.cpp @@ -0,0 +1,192 @@ +/** +* Jungfraujoch +* API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. +* +* The version of the OpenAPI document: 1.0.0-rc.175 +* Contact: filip.leonarski@psi.ch +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ + + +#include "Rugnux_settings.h" +#include "Helpers.h" + +#include + +namespace org::openapitools::server::model +{ + +Rugnux_settings::Rugnux_settings() +{ + m_Enable = false; + m_EnableIsSet = false; + m_Reserved_cpus = 4L; + m_Reserved_cpusIsSet = false; + m_Deadline_s = 0.0; + m_Deadline_sIsSet = false; + +} + +void Rugnux_settings::validate() const +{ + std::stringstream msg; + if (!validate(msg)) + { + throw org::openapitools::server::helpers::ValidationException(msg.str()); + } +} + +bool Rugnux_settings::validate(std::stringstream& msg) const +{ + return validate(msg, ""); +} + +bool Rugnux_settings::validate(std::stringstream& msg, const std::string& pathPrefix) const +{ + bool success = true; + const std::string _pathPrefix = pathPrefix.empty() ? "Rugnux_settings" : pathPrefix; + + + if (reservedCpusIsSet()) + { + const int64_t& value = m_Reserved_cpus; + const std::string currentValuePath = _pathPrefix + ".reservedCpus"; + + + if (value < 0ll) + { + success = false; + msg << currentValuePath << ": must be greater than or equal to 0;"; + } + + } + + if (deadlineSIsSet()) + { + const double& value = m_Deadline_s; + const std::string currentValuePath = _pathPrefix + ".deadlineS"; + + + if (value < 0.0) + { + success = false; + msg << currentValuePath << ": must be greater than or equal to 0.0;"; + } + + } + + return success; +} + +bool Rugnux_settings::operator==(const Rugnux_settings& rhs) const +{ + return + + + + ((!enableIsSet() && !rhs.enableIsSet()) || (enableIsSet() && rhs.enableIsSet() && isEnable() == rhs.isEnable())) && + + + ((!reservedCpusIsSet() && !rhs.reservedCpusIsSet()) || (reservedCpusIsSet() && rhs.reservedCpusIsSet() && getReservedCpus() == rhs.getReservedCpus())) && + + + ((!deadlineSIsSet() && !rhs.deadlineSIsSet()) || (deadlineSIsSet() && rhs.deadlineSIsSet() && getDeadlineS() == rhs.getDeadlineS())) + + ; +} + +bool Rugnux_settings::operator!=(const Rugnux_settings& rhs) const +{ + return !(*this == rhs); +} + +void to_json(nlohmann::json& j, const Rugnux_settings& o) +{ + j = nlohmann::json::object(); + if(o.enableIsSet()) + j["enable"] = o.m_Enable; + if(o.reservedCpusIsSet()) + j["reserved_cpus"] = o.m_Reserved_cpus; + if(o.deadlineSIsSet()) + j["deadline_s"] = o.m_Deadline_s; + +} + +void from_json(const nlohmann::json& j, Rugnux_settings& o) +{ + if(j.find("enable") != j.end()) + { + j.at("enable").get_to(o.m_Enable); + o.m_EnableIsSet = true; + } + if(j.find("reserved_cpus") != j.end()) + { + j.at("reserved_cpus").get_to(o.m_Reserved_cpus); + o.m_Reserved_cpusIsSet = true; + } + if(j.find("deadline_s") != j.end()) + { + j.at("deadline_s").get_to(o.m_Deadline_s); + o.m_Deadline_sIsSet = true; + } + +} + +bool Rugnux_settings::isEnable() const +{ + return m_Enable; +} +void Rugnux_settings::setEnable(bool const value) +{ + m_Enable = value; + m_EnableIsSet = true; +} +bool Rugnux_settings::enableIsSet() const +{ + return m_EnableIsSet; +} +void Rugnux_settings::unsetEnable() +{ + m_EnableIsSet = false; +} +int64_t Rugnux_settings::getReservedCpus() const +{ + return m_Reserved_cpus; +} +void Rugnux_settings::setReservedCpus(int64_t const value) +{ + m_Reserved_cpus = value; + m_Reserved_cpusIsSet = true; +} +bool Rugnux_settings::reservedCpusIsSet() const +{ + return m_Reserved_cpusIsSet; +} +void Rugnux_settings::unsetReserved_cpus() +{ + m_Reserved_cpusIsSet = false; +} +double Rugnux_settings::getDeadlineS() const +{ + return m_Deadline_s; +} +void Rugnux_settings::setDeadlineS(double const value) +{ + m_Deadline_s = value; + m_Deadline_sIsSet = true; +} +bool Rugnux_settings::deadlineSIsSet() const +{ + return m_Deadline_sIsSet; +} +void Rugnux_settings::unsetDeadline_s() +{ + m_Deadline_sIsSet = false; +} + + +} // namespace org::openapitools::server::model + diff --git a/broker/gen/model/Rugnux_settings.h b/broker/gen/model/Rugnux_settings.h new file mode 100644 index 000000000..8f4c60b5b --- /dev/null +++ b/broker/gen/model/Rugnux_settings.h @@ -0,0 +1,96 @@ +/** +* Jungfraujoch +* API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. +* +* The version of the OpenAPI document: 1.0.0-rc.175 +* Contact: filip.leonarski@psi.ch +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ +/* + * Rugnux_settings.h + * + * Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux inside the broker while they arrive, and the merged result is sent to the writer. + */ + +#ifndef Rugnux_settings_H_ +#define Rugnux_settings_H_ + + +#include + +namespace org::openapitools::server::model +{ + +/// +/// Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux inside the broker while they arrive, and the merged result is sent to the writer. +/// +class Rugnux_settings +{ +public: + Rugnux_settings(); + virtual ~Rugnux_settings() = default; + + + /// + /// Validate the current data in the model. Throws a ValidationException on failure. + /// + void validate() const; + + /// + /// Validate the current data in the model. Returns false on error and writes an error + /// message into the given stringstream. + /// + bool validate(std::stringstream& msg) const; + + /// + /// Helper overload for validate. Used when one model stores another model and calls it's validate. + /// Not meant to be called outside that case. + /// + bool validate(std::stringstream& msg, const std::string& pathPrefix) const; + + bool operator==(const Rugnux_settings& rhs) const; + bool operator!=(const Rugnux_settings& rhs) const; + + ///////////////////////////////////////////// + /// Rugnux_settings members + + /// + /// Process DECTRIS rotation collections with rugnux live + /// + bool isEnable() const; + void setEnable(bool const value); + bool enableIsSet() const; + void unsetEnable(); + /// + /// CPU threads left to the receiver and the image pusher while rugnux runs + /// + int64_t getReservedCpus() const; + void setReservedCpus(int64_t const value); + bool reservedCpusIsSet() const; + void unsetReserved_cpus(); + /// + /// Fixed cutoff: this long after the last image the analysis is cut, and the collection ends with what it has established (as `POST /cancel` after the last image does). 0 means no cutoff. + /// + double getDeadlineS() const; + void setDeadlineS(double const value); + bool deadlineSIsSet() const; + void unsetDeadline_s(); + + friend void to_json(nlohmann::json& j, const Rugnux_settings& o); + friend void from_json(const nlohmann::json& j, Rugnux_settings& o); +protected: + bool m_Enable; + bool m_EnableIsSet; + int64_t m_Reserved_cpus; + bool m_Reserved_cpusIsSet; + double m_Deadline_s; + bool m_Deadline_sIsSet; + +}; + +} // namespace org::openapitools::server::model + +#endif /* Rugnux_settings_H_ */ diff --git a/broker/gen/model/Rugnux_status.cpp b/broker/gen/model/Rugnux_status.cpp new file mode 100644 index 000000000..da879400f --- /dev/null +++ b/broker/gen/model/Rugnux_status.cpp @@ -0,0 +1,177 @@ +/** +* Jungfraujoch +* API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. +* +* The version of the OpenAPI document: 1.0.0-rc.175 +* Contact: filip.leonarski@psi.ch +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ + + +#include "Rugnux_status.h" +#include "Helpers.h" + +#include + +namespace org::openapitools::server::model +{ + +Rugnux_status::Rugnux_status() +{ + m_Enable = false; + m_Reserved_cpus = 0L; + m_Deadline_s = 0.0; + m_Deadline_sIsSet = false; + m_Model_loaded = false; + m_Model_size_bytes = 0L; + m_Model_size_bytesIsSet = false; + +} + +void Rugnux_status::validate() const +{ + std::stringstream msg; + if (!validate(msg)) + { + throw org::openapitools::server::helpers::ValidationException(msg.str()); + } +} + +bool Rugnux_status::validate(std::stringstream& msg) const +{ + return validate(msg, ""); +} + +bool Rugnux_status::validate(std::stringstream& msg, const std::string& pathPrefix) const +{ + bool success = true; + const std::string _pathPrefix = pathPrefix.empty() ? "Rugnux_status" : pathPrefix; + + + return success; +} + +bool Rugnux_status::operator==(const Rugnux_status& rhs) const +{ + return + + + (isEnable() == rhs.isEnable()) + && + + (getReservedCpus() == rhs.getReservedCpus()) + && + + + ((!deadlineSIsSet() && !rhs.deadlineSIsSet()) || (deadlineSIsSet() && rhs.deadlineSIsSet() && getDeadlineS() == rhs.getDeadlineS())) && + + (isModelLoaded() == rhs.isModelLoaded()) + && + + + ((!modelSizeBytesIsSet() && !rhs.modelSizeBytesIsSet()) || (modelSizeBytesIsSet() && rhs.modelSizeBytesIsSet() && getModelSizeBytes() == rhs.getModelSizeBytes())) + + ; +} + +bool Rugnux_status::operator!=(const Rugnux_status& rhs) const +{ + return !(*this == rhs); +} + +void to_json(nlohmann::json& j, const Rugnux_status& o) +{ + j = nlohmann::json::object(); + j["enable"] = o.m_Enable; + j["reserved_cpus"] = o.m_Reserved_cpus; + if(o.deadlineSIsSet()) + j["deadline_s"] = o.m_Deadline_s; + j["model_loaded"] = o.m_Model_loaded; + if(o.modelSizeBytesIsSet()) + j["model_size_bytes"] = o.m_Model_size_bytes; + +} + +void from_json(const nlohmann::json& j, Rugnux_status& o) +{ + j.at("enable").get_to(o.m_Enable); + j.at("reserved_cpus").get_to(o.m_Reserved_cpus); + if(j.find("deadline_s") != j.end()) + { + j.at("deadline_s").get_to(o.m_Deadline_s); + o.m_Deadline_sIsSet = true; + } + j.at("model_loaded").get_to(o.m_Model_loaded); + if(j.find("model_size_bytes") != j.end()) + { + j.at("model_size_bytes").get_to(o.m_Model_size_bytes); + o.m_Model_size_bytesIsSet = true; + } + +} + +bool Rugnux_status::isEnable() const +{ + return m_Enable; +} +void Rugnux_status::setEnable(bool const value) +{ + m_Enable = value; +} +int64_t Rugnux_status::getReservedCpus() const +{ + return m_Reserved_cpus; +} +void Rugnux_status::setReservedCpus(int64_t const value) +{ + m_Reserved_cpus = value; +} +double Rugnux_status::getDeadlineS() const +{ + return m_Deadline_s; +} +void Rugnux_status::setDeadlineS(double const value) +{ + m_Deadline_s = value; + m_Deadline_sIsSet = true; +} +bool Rugnux_status::deadlineSIsSet() const +{ + return m_Deadline_sIsSet; +} +void Rugnux_status::unsetDeadline_s() +{ + m_Deadline_sIsSet = false; +} +bool Rugnux_status::isModelLoaded() const +{ + return m_Model_loaded; +} +void Rugnux_status::setModelLoaded(bool const value) +{ + m_Model_loaded = value; +} +int64_t Rugnux_status::getModelSizeBytes() const +{ + return m_Model_size_bytes; +} +void Rugnux_status::setModelSizeBytes(int64_t const value) +{ + m_Model_size_bytes = value; + m_Model_size_bytesIsSet = true; +} +bool Rugnux_status::modelSizeBytesIsSet() const +{ + return m_Model_size_bytesIsSet; +} +void Rugnux_status::unsetModel_size_bytes() +{ + m_Model_size_bytesIsSet = false; +} + + +} // namespace org::openapitools::server::model + diff --git a/broker/gen/model/Rugnux_status.h b/broker/gen/model/Rugnux_status.h new file mode 100644 index 000000000..f5bf36405 --- /dev/null +++ b/broker/gen/model/Rugnux_status.h @@ -0,0 +1,108 @@ +/** +* Jungfraujoch +* API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. +* +* The version of the OpenAPI document: 1.0.0-rc.175 +* Contact: filip.leonarski@psi.ch +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ +/* + * Rugnux_status.h + * + * + */ + +#ifndef Rugnux_status_H_ +#define Rugnux_status_H_ + + +#include + +namespace org::openapitools::server::model +{ + +/// +/// +/// +class Rugnux_status +{ +public: + Rugnux_status(); + virtual ~Rugnux_status() = default; + + + /// + /// Validate the current data in the model. Throws a ValidationException on failure. + /// + void validate() const; + + /// + /// Validate the current data in the model. Returns false on error and writes an error + /// message into the given stringstream. + /// + bool validate(std::stringstream& msg) const; + + /// + /// Helper overload for validate. Used when one model stores another model and calls it's validate. + /// Not meant to be called outside that case. + /// + bool validate(std::stringstream& msg, const std::string& pathPrefix) const; + + bool operator==(const Rugnux_status& rhs) const; + bool operator!=(const Rugnux_status& rhs) const; + + ///////////////////////////////////////////// + /// Rugnux_status members + + /// + /// Rugnux live mode is enabled + /// + bool isEnable() const; + void setEnable(bool const value); + /// + /// CPU threads left to the receiver and the image pusher while rugnux runs + /// + int64_t getReservedCpus() const; + void setReservedCpus(int64_t const value); + /// + /// Seconds after the last image at which the analysis is cut; 0 means no cutoff + /// + double getDeadlineS() const; + void setDeadlineS(double const value); + bool deadlineSIsSet() const; + void unsetDeadline_s(); + /// + /// A model was uploaded with `PUT /config/rugnux/model` + /// + bool isModelLoaded() const; + void setModelLoaded(bool const value); + /// + /// Size of the uploaded model file + /// + int64_t getModelSizeBytes() const; + void setModelSizeBytes(int64_t const value); + bool modelSizeBytesIsSet() const; + void unsetModel_size_bytes(); + + friend void to_json(nlohmann::json& j, const Rugnux_status& o); + friend void from_json(const nlohmann::json& j, Rugnux_status& o); +protected: + bool m_Enable; + + int64_t m_Reserved_cpus; + + double m_Deadline_s; + bool m_Deadline_sIsSet; + bool m_Model_loaded; + + int64_t m_Model_size_bytes; + bool m_Model_size_bytesIsSet; + +}; + +} // namespace org::openapitools::server::model + +#endif /* Rugnux_status_H_ */ diff --git a/broker/gen/model/Rugnux_summary_row.cpp b/broker/gen/model/Rugnux_summary_row.cpp new file mode 100644 index 000000000..5aa9514b3 --- /dev/null +++ b/broker/gen/model/Rugnux_summary_row.cpp @@ -0,0 +1,105 @@ +/** +* Jungfraujoch +* API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. +* +* The version of the OpenAPI document: 1.0.0-rc.175 +* Contact: filip.leonarski@psi.ch +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ + + +#include "Rugnux_summary_row.h" +#include "Helpers.h" + +#include + +namespace org::openapitools::server::model +{ + +Rugnux_summary_row::Rugnux_summary_row() +{ + m_Label = ""; + m_Value = ""; + +} + +void Rugnux_summary_row::validate() const +{ + std::stringstream msg; + if (!validate(msg)) + { + throw org::openapitools::server::helpers::ValidationException(msg.str()); + } +} + +bool Rugnux_summary_row::validate(std::stringstream& msg) const +{ + return validate(msg, ""); +} + +bool Rugnux_summary_row::validate(std::stringstream& msg, const std::string& pathPrefix) const +{ + bool success = true; + const std::string _pathPrefix = pathPrefix.empty() ? "Rugnux_summary_row" : pathPrefix; + + + return success; +} + +bool Rugnux_summary_row::operator==(const Rugnux_summary_row& rhs) const +{ + return + + + (getLabel() == rhs.getLabel()) + && + + (getValue() == rhs.getValue()) + + + ; +} + +bool Rugnux_summary_row::operator!=(const Rugnux_summary_row& rhs) const +{ + return !(*this == rhs); +} + +void to_json(nlohmann::json& j, const Rugnux_summary_row& o) +{ + j = nlohmann::json::object(); + j["label"] = o.m_Label; + j["value"] = o.m_Value; + +} + +void from_json(const nlohmann::json& j, Rugnux_summary_row& o) +{ + j.at("label").get_to(o.m_Label); + j.at("value").get_to(o.m_Value); + +} + +std::string Rugnux_summary_row::getLabel() const +{ + return m_Label; +} +void Rugnux_summary_row::setLabel(std::string const& value) +{ + m_Label = value; +} +std::string Rugnux_summary_row::getValue() const +{ + return m_Value; +} +void Rugnux_summary_row::setValue(std::string const& value) +{ + m_Value = value; +} + + +} // namespace org::openapitools::server::model + diff --git a/broker/gen/model/Rugnux_summary_row.h b/broker/gen/model/Rugnux_summary_row.h new file mode 100644 index 000000000..c868fe387 --- /dev/null +++ b/broker/gen/model/Rugnux_summary_row.h @@ -0,0 +1,84 @@ +/** +* Jungfraujoch +* API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. +* +* The version of the OpenAPI document: 1.0.0-rc.175 +* Contact: filip.leonarski@psi.ch +* +* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). +* https://openapi-generator.tech +* Do not edit the class manually. +*/ +/* + * Rugnux_summary_row.h + * + * + */ + +#ifndef Rugnux_summary_row_H_ +#define Rugnux_summary_row_H_ + + +#include +#include + +namespace org::openapitools::server::model +{ + +/// +/// +/// +class Rugnux_summary_row +{ +public: + Rugnux_summary_row(); + virtual ~Rugnux_summary_row() = default; + + + /// + /// Validate the current data in the model. Throws a ValidationException on failure. + /// + void validate() const; + + /// + /// Validate the current data in the model. Returns false on error and writes an error + /// message into the given stringstream. + /// + bool validate(std::stringstream& msg) const; + + /// + /// Helper overload for validate. Used when one model stores another model and calls it's validate. + /// Not meant to be called outside that case. + /// + bool validate(std::stringstream& msg, const std::string& pathPrefix) const; + + bool operator==(const Rugnux_summary_row& rhs) const; + bool operator!=(const Rugnux_summary_row& rhs) const; + + ///////////////////////////////////////////// + /// Rugnux_summary_row members + + /// + /// + /// + std::string getLabel() const; + void setLabel(std::string const& value); + /// + /// + /// + std::string getValue() const; + void setValue(std::string const& value); + + friend void to_json(nlohmann::json& j, const Rugnux_summary_row& o); + friend void from_json(const nlohmann::json& j, Rugnux_summary_row& o); +protected: + std::string m_Label; + + std::string m_Value; + + +}; + +} // namespace org::openapitools::server::model + +#endif /* Rugnux_summary_row_H_ */ diff --git a/broker/jfjoch_api.yaml b/broker/jfjoch_api.yaml index 0370f3029..1f3bef805 100644 --- a/broker/jfjoch_api.yaml +++ b/broker/jfjoch_api.yaml @@ -2684,6 +2684,167 @@ components: $ref: '#/components/schemas/zeromq_metadata_settings' dark_mask: $ref: '#/components/schemas/dark_mask_settings' + rugnux: + $ref: '#/components/schemas/rugnux_settings' + rugnux_settings: + type: object + description: | + Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux + inside the broker while they arrive, and the merged result is sent to the writer. + properties: + enable: + type: boolean + default: false + description: Process DECTRIS rotation collections with rugnux live + reserved_cpus: + type: integer + format: int64 + minimum: 0 + default: 4 + description: CPU threads left to the receiver and the image pusher while rugnux runs + deadline_s: + type: number + format: double + minimum: 0.0 + default: 0.0 + description: | + Fixed cutoff: this long after the last image the analysis is cut, and the collection ends + with what it has established (as `POST /cancel` after the last image does). 0 means no cutoff. + rugnux_status: + type: object + required: + - enable + - reserved_cpus + - model_loaded + properties: + enable: + type: boolean + description: Rugnux live mode is enabled + reserved_cpus: + type: integer + format: int64 + description: CPU threads left to the receiver and the image pusher while rugnux runs + deadline_s: + type: number + format: double + description: Seconds after the last image at which the analysis is cut; 0 means no cutoff + model_loaded: + type: boolean + description: A model was uploaded with `PUT /config/rugnux/model` + model_size_bytes: + type: integer + format: int64 + description: Size of the uploaded model file + rugnux_live_step: + type: object + required: + - title + - started_s + - notes + properties: + title: + type: string + started_s: + type: number + format: double + description: Start of the step, since the collection started + duration_s: + type: number + format: double + description: Set once the next step starts or the run ends + notes: + type: array + description: What the step found (e.g. lattice, space group) + items: + type: string + rugnux_summary_row: + type: object + required: + - label + - value + properties: + label: + type: string + value: + type: string + rugnux_live_status: + type: object + description: Live rugnux processing of the last collection; kept until the next collection starts + required: + - state + - message + - images_expected + - images_received + - elapsed_s + - steps + - verdict + - verdict_text + - summary + - warnings + - probe + properties: + state: + type: string + enum: [Idle, Running, Done, Failed, Cancelled, Skipped] + probe: + type: string + description: Answer of the early lattice probe on the first 60 degrees; empty until it answers + probe_s: + type: number + format: double + description: When the probe answered, since the collection started + message: + type: string + description: Why the run failed or was skipped + images_expected: + type: integer + format: int64 + images_received: + type: integer + format: int64 + elapsed_s: + type: number + format: double + step_progress: + type: number + format: float + minimum: 0.0 + maximum: 1.0 + description: Fraction of the current pass's images + steps: + type: array + items: + $ref: '#/components/schemas/rugnux_live_step' + last_image_s: + type: number + format: double + description: When the last image arrived (and the cutoff timer started), since the collection started + cut: + type: string + description: | + Empty, or why the analysis was cut: `deadline` (`deadline_s` after the last image) or `cancel` + (`POST /cancel`). The end message then carried what was established at the cut. + cut_s: + type: number + format: double + description: When the analysis was cut, since the collection started + last_image_to_written_s: + type: number + format: double + description: From the last image received to all results written + verdict: + type: string + description: OK, WARNINGS, UNUSABLE or FAILED; empty until the run is over + verdict_text: + type: string + summary: + type: array + items: + $ref: '#/components/schemas/rugnux_summary_row' + warnings: + type: array + items: + type: string zeromq_metadata_settings: type: object required: @@ -2895,6 +3056,11 @@ paths: Any frame that is currently being processed by CPU will be finished and sent to writer. Given the command is making sure to gracefully stop data acquisition and detector, it might take some time to switch back after command finished to `Idle` state. + With rugnux live mode (`/config/rugnux`) the command also cuts the rugnux analysis, during the collection + or after its last image alike: the collection ends with what the analysis has established + (see `cut` in `/rugnux/status`). After the last image the collection itself is complete and is not + marked as cancelled. + If data collection is not running, the command has no effect. responses: "200": @@ -3331,6 +3497,133 @@ paths: application/json: schema: $ref: '#/components/schemas/detector_list' + /config/rugnux: + put: + summary: Change rugnux live mode settings + description: | + This can only be done when detector is `Idle`, `Error` or `Inactive` states. + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/rugnux_settings' + responses: + "401": + description: The current dataset is protected and no valid bearer token was presented. + content: + text/plain: + schema: + type: string + "200": + description: Everything OK + "400": + description: Input parsing or validation error + content: + text/plain: + schema: + type: string + description: Exception error + "500": + description: Error within Jungfraujoch code - see output message. + content: + application/json: + schema: + $ref: '#/components/schemas/error_message' + get: + summary: Get rugnux live mode settings and model status + description: Can be done anytime + security: + - bearerAuth: [] + responses: + "401": + description: The current dataset is protected and no valid bearer token was presented. + content: + text/plain: + schema: + type: string + "200": + description: Everything OK + content: + application/json: + schema: + $ref: '#/components/schemas/rugnux_status' + /config/rugnux/model: + put: + summary: Upload the model used by rugnux live mode + description: | + This can only be done when detector is `Idle`, `Error` or `Inactive` states. + The model (PDB or mmCIF, optionally gzipped; at most 64 MiB) is used for molecular replacement + and validation of every following rugnux live collection, until it is removed with + `DELETE /config/rugnux/model` or replaced by another upload. + security: + - bearerAuth: [] + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "401": + description: The current dataset is protected and no valid bearer token was presented. + content: + text/plain: + schema: + type: string + "200": + description: All good + "400": + description: Empty body + "413": + description: Model larger than 64 MiB + "500": + description: Error within Jungfraujoch code - see output message. + content: + application/json: + schema: + $ref: '#/components/schemas/error_message' + delete: + summary: Remove the model used by rugnux live mode + description: | + This can only be done when detector is `Idle`, `Error` or `Inactive` states. + security: + - bearerAuth: [] + responses: + "401": + description: The current dataset is protected and no valid bearer token was presented. + content: + text/plain: + schema: + type: string + "200": + description: All good + "500": + description: Error within Jungfraujoch code - see output message. + content: + application/json: + schema: + $ref: '#/components/schemas/error_message' + /rugnux/status: + get: + summary: Get live rugnux processing status of the last collection + description: Can be done anytime + security: + - bearerAuth: [] + responses: + "401": + description: The current dataset is protected and no valid bearer token was presented. + content: + text/plain: + schema: + type: string + "200": + description: Everything OK + content: + application/json: + schema: + $ref: '#/components/schemas/rugnux_live_status' /config/zeromq_preview: put: summary: Set ZeroMQ preview settings diff --git a/broker/jfjoch_broker.cpp b/broker/jfjoch_broker.cpp index 7457e5a3f..d26812935 100644 --- a/broker/jfjoch_broker.cpp +++ b/broker/jfjoch_broker.cpp @@ -116,6 +116,7 @@ int main(int argc, char **argv) { if (receiver) broker.Services().Receiver(receiver.get()); + ParseRugnuxSettings(settings, broker.Services()); httplib::Server server; httpServer = &server; diff --git a/common/ParallelFor.h b/common/ParallelFor.h index 65b9e262c..e8dbb4e4d 100644 --- a/common/ParallelFor.h +++ b/common/ParallelFor.h @@ -16,6 +16,12 @@ #include "ThreadAffinity.h" +#ifdef __linux__ +#include +#include +#include +#endif + // Two shapes of "run this over a range on several threads", used by the analysis code. Both take the // worker count from the caller rather than asking the hardware, so a run that was told how many // threads to use keeps to it. @@ -48,6 +54,20 @@ namespace parallel_detail { size_t WorkerCount() const { return workers.size(); } + // The scheduling priority of every pool thread (Linux; elsewhere nothing). The pool is made once + // per process, so a thread that lowers its own priority does not lower the work it hands to the + // pool: the broker running rugnux beside its receiving threads sets it here as well. Raising + // it back needs the privilege to do so; returns false where that was refused. + bool SetNice(int nice) { + bool ok = true; +#ifdef __linux__ + std::lock_guard lock(m); + for (const int tid: tids) + ok = (setpriority(PRIO_PROCESS, static_cast(tid), nice) == 0) && ok; +#endif + return ok; + } + void Submit(std::function job) { { std::lock_guard lock(m); @@ -66,6 +86,12 @@ namespace parallel_detail { // It inherited whatever CPUs the thread that first used the pool was kept to. RestoreThreadAffinity(); InWorkerFlag() = true; +#ifdef __linux__ + { + std::lock_guard lock(m); + tids.push_back(static_cast(syscall(SYS_gettid))); + } +#endif Loop(); }); } catch (...) { @@ -106,6 +132,7 @@ namespace parallel_detail { std::condition_variable cv; std::deque > queue; std::vector workers; + std::vector tids; // the workers' kernel thread ids, for SetNice bool stop = false; // A function-local thread_local rather than an inline static member: Apple's linker rejects the diff --git a/docs/JFJOCH_BROKER.md b/docs/JFJOCH_BROKER.md index 739df1d68..4293a5147 100644 --- a/docs/JFJOCH_BROKER.md +++ b/docs/JFJOCH_BROKER.md @@ -170,6 +170,27 @@ The writers report each closed data file to the broker (TCP protocol version 5, for `FILE_CLOSED`); the in-process HDF5 writer reports to it directly. The ZeroMQ writer has no way back to the broker, so with it the route answers 409. +## Rugnux live mode + +With rugnux live mode on, rotation collections from a DECTRIS detector are processed by rugnux inside +the broker while they arrive. It is off by default; the configuration file switches it on with +`"rugnux": {"enable": true, "reserved_cpus": 4}`, where `reserved_cpus` is the number of CPU threads +left to the receiver and the image pusher. + +The same settings are read with `GET /config/rugnux` and changed with `PUT /config/rugnux`. A model +for molecular replacement and validation (PDB or mmCIF, optionally gzipped, at most 64 MiB) is +uploaded as the raw file with `PUT /config/rugnux/model` (`application/octet-stream`; a larger body +gets `413`). The broker keeps it, readable only by its own user, for every following collection until +`DELETE /config/rugnux/model` removes it or another upload replaces it. `GET /config/rugnux` reports +whether a model is loaded and its size, never its contents. Changes are refused during a collection, +and while the current dataset was started with tokens, all of these routes need its bearer token. + +`GET /rugnux/status` reports the live run of the last collection, kept until the next one starts: its +state, the images received, the step it is on and how far into it, the steps so far with what each +found, and once it is over the verdict, the summary, warnings and the time from the last image to +every result written. The web frontend shows it on the dashboard. It needs the dataset's bearer token +like the routes above. + ## Setting up a local test for Jungfraujoch For development, it is possible to set up a local installation of Jungfraujoch. This will work without FPGA installed in the computer and allows testing the Jungfraujoch software layer, including diff --git a/docs/python_client/README.md b/docs/python_client/README.md index 8ca6a35c5..f4ec077d9 100644 --- a/docs/python_client/README.md +++ b/docs/python_client/README.md @@ -126,6 +126,10 @@ Class | Method | HTTP request | Description *DefaultApi* | [**config_mask_tiff_get**](docs/DefaultApi.md#config_mask_tiff_get) | **GET** /config/mask.tiff | Get mask of the detector (TIFF) *DefaultApi* | [**config_roi_get**](docs/DefaultApi.md#config_roi_get) | **GET** /config/roi | Get ROI definitions *DefaultApi* | [**config_roi_put**](docs/DefaultApi.md#config_roi_put) | **PUT** /config/roi | Upload ROI definitions +*DefaultApi* | [**config_rugnux_get**](docs/DefaultApi.md#config_rugnux_get) | **GET** /config/rugnux | Get rugnux live mode settings and model status +*DefaultApi* | [**config_rugnux_model_delete**](docs/DefaultApi.md#config_rugnux_model_delete) | **DELETE** /config/rugnux/model | Remove the model used by rugnux live mode +*DefaultApi* | [**config_rugnux_model_put**](docs/DefaultApi.md#config_rugnux_model_put) | **PUT** /config/rugnux/model | Upload the model used by rugnux live mode +*DefaultApi* | [**config_rugnux_put**](docs/DefaultApi.md#config_rugnux_put) | **PUT** /config/rugnux | Change rugnux live mode settings *DefaultApi* | [**config_select_detector_get**](docs/DefaultApi.md#config_select_detector_get) | **GET** /config/select_detector | List available detectors *DefaultApi* | [**config_select_detector_put**](docs/DefaultApi.md#config_select_detector_put) | **PUT** /config/select_detector | Select detector *DefaultApi* | [**config_spot_finding_get**](docs/DefaultApi.md#config_spot_finding_get) | **GET** /config/spot_finding | Get data processing configuration @@ -149,11 +153,13 @@ Class | Method | HTTP request | Description *DefaultApi* | [**image_buffer_status_get**](docs/DefaultApi.md#image_buffer_status_get) | **GET** /image_buffer/status | Get status of the image buffers *DefaultApi* | [**image_pusher_status_get**](docs/DefaultApi.md#image_pusher_status_get) | **GET** /image_pusher/status | Get status of image pusher *DefaultApi* | [**initialize_post**](docs/DefaultApi.md#initialize_post) | **POST** /initialize | Initialize detector and data acquisition +*DefaultApi* | [**live_events_get**](docs/DefaultApi.md#live_events_get) | **GET** /live/events | Event stream of the collection, for a reader following it *DefaultApi* | [**pedestal_post**](docs/DefaultApi.md#pedestal_post) | **POST** /pedestal | Collect dark current for the detector *DefaultApi* | [**preview_pedestal_tiff_get**](docs/DefaultApi.md#preview_pedestal_tiff_get) | **GET** /preview/pedestal.tiff | Get pedestal in TIFF format *DefaultApi* | [**preview_plot_bin_get**](docs/DefaultApi.md#preview_plot_bin_get) | **GET** /preview/plot.bin | Generate 1D plot from Jungfraujoch and send in raw binary format. Data are provided as (32-bit) float binary array. This format doesn't transmit information about X-axis, only values, so it is of limited use for azimuthal integration. *DefaultApi* | [**preview_plot_get**](docs/DefaultApi.md#preview_plot_get) | **GET** /preview/plot | Generate 1D plot from Jungfraujoch *DefaultApi* | [**result_scan_get**](docs/DefaultApi.md#result_scan_get) | **GET** /result/scan | Get full scan result +*DefaultApi* | [**rugnux_status_get**](docs/DefaultApi.md#rugnux_status_get) | **GET** /rugnux/status | Get live rugnux processing status of the last collection *DefaultApi* | [**start_post**](docs/DefaultApi.md#start_post) | **POST** /start | Start detector *DefaultApi* | [**statistics_calibration_get**](docs/DefaultApi.md#statistics_calibration_get) | **GET** /statistics/calibration | Get calibration statistics *DefaultApi* | [**statistics_data_collection_get**](docs/DefaultApi.md#statistics_data_collection_get) | **GET** /statistics/data_collection | Get data collection statistics @@ -223,6 +229,11 @@ Class | Method | HTTP request | Description - [RoiCircleList](docs/RoiCircleList.md) - [RoiDefinitions](docs/RoiDefinitions.md) - [RotationAxis](docs/RotationAxis.md) + - [RugnuxLiveStatus](docs/RugnuxLiveStatus.md) + - [RugnuxLiveStep](docs/RugnuxLiveStep.md) + - [RugnuxSettings](docs/RugnuxSettings.md) + - [RugnuxStatus](docs/RugnuxStatus.md) + - [RugnuxSummaryRow](docs/RugnuxSummaryRow.md) - [ScanResult](docs/ScanResult.md) - [ScanResultImagesInner](docs/ScanResultImagesInner.md) - [SpotFindingSettings](docs/SpotFindingSettings.md) diff --git a/docs/python_client/docs/DefaultApi.md b/docs/python_client/docs/DefaultApi.md index beb8542ed..67c3b3a53 100644 --- a/docs/python_client/docs/DefaultApi.md +++ b/docs/python_client/docs/DefaultApi.md @@ -29,6 +29,10 @@ Method | HTTP request | Description [**config_mask_tiff_get**](DefaultApi.md#config_mask_tiff_get) | **GET** /config/mask.tiff | Get mask of the detector (TIFF) [**config_roi_get**](DefaultApi.md#config_roi_get) | **GET** /config/roi | Get ROI definitions [**config_roi_put**](DefaultApi.md#config_roi_put) | **PUT** /config/roi | Upload ROI definitions +[**config_rugnux_get**](DefaultApi.md#config_rugnux_get) | **GET** /config/rugnux | Get rugnux live mode settings and model status +[**config_rugnux_model_delete**](DefaultApi.md#config_rugnux_model_delete) | **DELETE** /config/rugnux/model | Remove the model used by rugnux live mode +[**config_rugnux_model_put**](DefaultApi.md#config_rugnux_model_put) | **PUT** /config/rugnux/model | Upload the model used by rugnux live mode +[**config_rugnux_put**](DefaultApi.md#config_rugnux_put) | **PUT** /config/rugnux | Change rugnux live mode settings [**config_select_detector_get**](DefaultApi.md#config_select_detector_get) | **GET** /config/select_detector | List available detectors [**config_select_detector_put**](DefaultApi.md#config_select_detector_put) | **PUT** /config/select_detector | Select detector [**config_spot_finding_get**](DefaultApi.md#config_spot_finding_get) | **GET** /config/spot_finding | Get data processing configuration @@ -52,11 +56,13 @@ Method | HTTP request | Description [**image_buffer_status_get**](DefaultApi.md#image_buffer_status_get) | **GET** /image_buffer/status | Get status of the image buffers [**image_pusher_status_get**](DefaultApi.md#image_pusher_status_get) | **GET** /image_pusher/status | Get status of image pusher [**initialize_post**](DefaultApi.md#initialize_post) | **POST** /initialize | Initialize detector and data acquisition +[**live_events_get**](DefaultApi.md#live_events_get) | **GET** /live/events | Event stream of the collection, for a reader following it [**pedestal_post**](DefaultApi.md#pedestal_post) | **POST** /pedestal | Collect dark current for the detector [**preview_pedestal_tiff_get**](DefaultApi.md#preview_pedestal_tiff_get) | **GET** /preview/pedestal.tiff | Get pedestal in TIFF format [**preview_plot_bin_get**](DefaultApi.md#preview_plot_bin_get) | **GET** /preview/plot.bin | Generate 1D plot from Jungfraujoch and send in raw binary format. Data are provided as (32-bit) float binary array. This format doesn't transmit information about X-axis, only values, so it is of limited use for azimuthal integration. [**preview_plot_get**](DefaultApi.md#preview_plot_get) | **GET** /preview/plot | Generate 1D plot from Jungfraujoch [**result_scan_get**](DefaultApi.md#result_scan_get) | **GET** /result/scan | Get full scan result +[**rugnux_status_get**](DefaultApi.md#rugnux_status_get) | **GET** /rugnux/status | Get live rugnux processing status of the last collection [**start_post**](DefaultApi.md#start_post) | **POST** /start | Start detector [**statistics_calibration_get**](DefaultApi.md#statistics_calibration_get) | **GET** /statistics/calibration | Get calibration statistics [**statistics_data_collection_get**](DefaultApi.md#statistics_data_collection_get) | **GET** /statistics/data_collection | Get data collection statistics @@ -1729,6 +1735,318 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **config_rugnux_get** +> RugnuxStatus config_rugnux_get() + +Get rugnux live mode settings and model status + +Can be done anytime + +### Example + +* Bearer Authentication (bearerAuth): + +```python +import jfjoch_client +from jfjoch_client.models.rugnux_status import RugnuxStatus +from jfjoch_client.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to http://localhost:5232 +# See configuration.py for a list of all supported configuration parameters. +configuration = jfjoch_client.Configuration( + host = "http://localhost:5232" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Configure Bearer authorization: bearerAuth +configuration = jfjoch_client.Configuration( + access_token = os.environ["BEARER_TOKEN"] +) + +# Enter a context with an instance of the API client +with jfjoch_client.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = jfjoch_client.DefaultApi(api_client) + + try: + # Get rugnux live mode settings and model status + api_response = api_instance.config_rugnux_get() + print("The response of DefaultApi->config_rugnux_get:\n") + pprint(api_response) + except Exception as e: + print("Exception when calling DefaultApi->config_rugnux_get: %s\n" % e) +``` + + + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +[**RugnuxStatus**](RugnuxStatus.md) + +### Authorization + +[bearerAuth](../README.md#bearerAuth) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: text/plain, application/json + +### HTTP response details + +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**401** | The current dataset is protected and no valid bearer token was presented. | - | +**200** | Everything OK | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **config_rugnux_model_delete** +> config_rugnux_model_delete() + +Remove the model used by rugnux live mode + +This can only be done when detector is `Idle`, `Error` or `Inactive` states. + + +### Example + +* Bearer Authentication (bearerAuth): + +```python +import jfjoch_client +from jfjoch_client.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to http://localhost:5232 +# See configuration.py for a list of all supported configuration parameters. +configuration = jfjoch_client.Configuration( + host = "http://localhost:5232" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Configure Bearer authorization: bearerAuth +configuration = jfjoch_client.Configuration( + access_token = os.environ["BEARER_TOKEN"] +) + +# Enter a context with an instance of the API client +with jfjoch_client.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = jfjoch_client.DefaultApi(api_client) + + try: + # Remove the model used by rugnux live mode + api_instance.config_rugnux_model_delete() + except Exception as e: + print("Exception when calling DefaultApi->config_rugnux_model_delete: %s\n" % e) +``` + + + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +void (empty response body) + +### Authorization + +[bearerAuth](../README.md#bearerAuth) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: text/plain, application/json + +### HTTP response details + +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**401** | The current dataset is protected and no valid bearer token was presented. | - | +**200** | All good | - | +**500** | Error within Jungfraujoch code - see output message. | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **config_rugnux_model_put** +> config_rugnux_model_put(body=body) + +Upload the model used by rugnux live mode + +This can only be done when detector is `Idle`, `Error` or `Inactive` states. +The model (PDB or mmCIF, optionally gzipped; at most 64 MiB) is used for molecular replacement +and validation of every following rugnux live collection, until it is removed with +`DELETE /config/rugnux/model` or replaced by another upload. + + +### Example + +* Bearer Authentication (bearerAuth): + +```python +import jfjoch_client +from jfjoch_client.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to http://localhost:5232 +# See configuration.py for a list of all supported configuration parameters. +configuration = jfjoch_client.Configuration( + host = "http://localhost:5232" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Configure Bearer authorization: bearerAuth +configuration = jfjoch_client.Configuration( + access_token = os.environ["BEARER_TOKEN"] +) + +# Enter a context with an instance of the API client +with jfjoch_client.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = jfjoch_client.DefaultApi(api_client) + body = None # bytearray | (optional) + + try: + # Upload the model used by rugnux live mode + api_instance.config_rugnux_model_put(body=body) + except Exception as e: + print("Exception when calling DefaultApi->config_rugnux_model_put: %s\n" % e) +``` + + + +### Parameters + + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **body** | **bytearray**| | [optional] + +### Return type + +void (empty response body) + +### Authorization + +[bearerAuth](../README.md#bearerAuth) + +### HTTP request headers + + - **Content-Type**: application/octet-stream + - **Accept**: text/plain, application/json + +### HTTP response details + +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**401** | The current dataset is protected and no valid bearer token was presented. | - | +**200** | All good | - | +**400** | Empty body | - | +**413** | Model larger than 64 MiB | - | +**500** | Error within Jungfraujoch code - see output message. | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +# **config_rugnux_put** +> config_rugnux_put(rugnux_settings=rugnux_settings) + +Change rugnux live mode settings + +This can only be done when detector is `Idle`, `Error` or `Inactive` states. + + +### Example + +* Bearer Authentication (bearerAuth): + +```python +import jfjoch_client +from jfjoch_client.models.rugnux_settings import RugnuxSettings +from jfjoch_client.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to http://localhost:5232 +# See configuration.py for a list of all supported configuration parameters. +configuration = jfjoch_client.Configuration( + host = "http://localhost:5232" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Configure Bearer authorization: bearerAuth +configuration = jfjoch_client.Configuration( + access_token = os.environ["BEARER_TOKEN"] +) + +# Enter a context with an instance of the API client +with jfjoch_client.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = jfjoch_client.DefaultApi(api_client) + rugnux_settings = jfjoch_client.RugnuxSettings() # RugnuxSettings | (optional) + + try: + # Change rugnux live mode settings + api_instance.config_rugnux_put(rugnux_settings=rugnux_settings) + except Exception as e: + print("Exception when calling DefaultApi->config_rugnux_put: %s\n" % e) +``` + + + +### Parameters + + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **rugnux_settings** | [**RugnuxSettings**](RugnuxSettings.md)| | [optional] + +### Return type + +void (empty response body) + +### Authorization + +[bearerAuth](../README.md#bearerAuth) + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: text/plain, application/json + +### HTTP response details + +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**401** | The current dataset is protected and no valid bearer token was presented. | - | +**200** | Everything OK | - | +**400** | Input parsing or validation error | - | +**500** | Error within Jungfraujoch code - see output message. | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **config_select_detector_get** > DetectorList config_select_detector_get() @@ -3326,6 +3644,96 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **live_events_get** +> str live_events_get() + +Event stream of the collection, for a reader following it + +Server-sent events (text/event-stream) for the current collection, from the moment /start has +accepted it: first its whole history, then every change, until it ends or a new collection +begins. Only one reader at a time. Events, each with a JSON `data` line: +- `start` {run_number, run_name, file_prefix, number_of_images, images_per_file, cbor} - first, + once the start message is known: at once for a JUNGFRAU, when the detector sends its own for + a DECTRIS. `cbor` is the start message the writer of the master file received, CBOR in base64; + a reader builds the dataset from it; +- `file` {file_number, file, first_image, image_count} - a data file closed and renamed into + place; `file` is relative to the collection's root, as the writer named it; +- `end` {images, error?} - every writer acknowledged the end: the data files and the master + file are in place; `images` is the number of images collected. Also sent, with `error`, when + the measurement ends any other way - before the start message, for one. The stream then closes; +- `superseded` {run_number} - a new collection began. The stream then closes. +A comment line (`: keepalive`) every 10 s when nothing happens. + + +### Example + +* Bearer Authentication (bearerAuth): + +```python +import jfjoch_client +from jfjoch_client.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to http://localhost:5232 +# See configuration.py for a list of all supported configuration parameters. +configuration = jfjoch_client.Configuration( + host = "http://localhost:5232" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Configure Bearer authorization: bearerAuth +configuration = jfjoch_client.Configuration( + access_token = os.environ["BEARER_TOKEN"] +) + +# Enter a context with an instance of the API client +with jfjoch_client.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = jfjoch_client.DefaultApi(api_client) + + try: + # Event stream of the collection, for a reader following it + api_response = api_instance.live_events_get() + print("The response of DefaultApi->live_events_get:\n") + pprint(api_response) + except Exception as e: + print("Exception when calling DefaultApi->live_events_get: %s\n" % e) +``` + + + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +**str** + +### Authorization + +[bearerAuth](../README.md#bearerAuth) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: text/event-stream, text/plain + +### HTTP response details + +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**200** | Event stream | - | +**401** | The current dataset is protected and no valid bearer token was presented. | - | +**404** | No collection has started | - | +**409** | Another reader is following the collection, or the image pusher does not report closed data files | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **pedestal_post** > pedestal_post() @@ -3702,6 +4110,81 @@ This endpoint does not need any parameter. [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **rugnux_status_get** +> RugnuxLiveStatus rugnux_status_get() + +Get live rugnux processing status of the last collection + +Can be done anytime + +### Example + +* Bearer Authentication (bearerAuth): + +```python +import jfjoch_client +from jfjoch_client.models.rugnux_live_status import RugnuxLiveStatus +from jfjoch_client.rest import ApiException +from pprint import pprint + +# Defining the host is optional and defaults to http://localhost:5232 +# See configuration.py for a list of all supported configuration parameters. +configuration = jfjoch_client.Configuration( + host = "http://localhost:5232" +) + +# The client must configure the authentication and authorization parameters +# in accordance with the API server security policy. +# Examples for each auth method are provided below, use the example that +# satisfies your auth use case. + +# Configure Bearer authorization: bearerAuth +configuration = jfjoch_client.Configuration( + access_token = os.environ["BEARER_TOKEN"] +) + +# Enter a context with an instance of the API client +with jfjoch_client.ApiClient(configuration) as api_client: + # Create an instance of the API class + api_instance = jfjoch_client.DefaultApi(api_client) + + try: + # Get live rugnux processing status of the last collection + api_response = api_instance.rugnux_status_get() + print("The response of DefaultApi->rugnux_status_get:\n") + pprint(api_response) + except Exception as e: + print("Exception when calling DefaultApi->rugnux_status_get: %s\n" % e) +``` + + + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +[**RugnuxLiveStatus**](RugnuxLiveStatus.md) + +### Authorization + +[bearerAuth](../README.md#bearerAuth) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: text/plain, application/json + +### HTTP response details + +| Status code | Description | Response headers | +|-------------|-------------|------------------| +**401** | The current dataset is protected and no valid bearer token was presented. | - | +**200** | Everything OK | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **start_post** > start_post(dataset_settings=dataset_settings) diff --git a/docs/python_client/docs/JfjochSettings.md b/docs/python_client/docs/JfjochSettings.md index b783f858d..f2ebba9f6 100644 --- a/docs/python_client/docs/JfjochSettings.md +++ b/docs/python_client/docs/JfjochSettings.md @@ -27,6 +27,7 @@ Name | Type | Description | Notes **zeromq_preview** | [**ZeromqPreviewSettings**](ZeromqPreviewSettings.md) | | [optional] **zeromq_metadata** | [**ZeromqMetadataSettings**](ZeromqMetadataSettings.md) | | [optional] **dark_mask** | [**DarkMaskSettings**](DarkMaskSettings.md) | | [optional] +**rugnux** | [**RugnuxSettings**](RugnuxSettings.md) | | [optional] ## Example diff --git a/docs/python_client/docs/RugnuxLiveStatus.md b/docs/python_client/docs/RugnuxLiveStatus.md new file mode 100644 index 000000000..631dae4d0 --- /dev/null +++ b/docs/python_client/docs/RugnuxLiveStatus.md @@ -0,0 +1,43 @@ +# RugnuxLiveStatus + +Live rugnux processing of the last collection; kept until the next collection starts + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**state** | **str** | | +**probe** | **str** | Answer of the early lattice probe on the first 60 degrees; empty until it answers | +**probe_s** | **float** | When the probe answered, since the collection started | [optional] +**message** | **str** | Why the run failed or was skipped | +**images_expected** | **int** | | +**images_received** | **int** | | +**elapsed_s** | **float** | | +**step_progress** | **float** | Fraction of the current pass's images | [optional] +**steps** | [**List[RugnuxLiveStep]**](RugnuxLiveStep.md) | | +**last_image_to_written_s** | **float** | From the last image received to all results written | [optional] +**verdict** | **str** | OK, WARNINGS, UNUSABLE or FAILED; empty until the run is over | +**verdict_text** | **str** | | +**summary** | [**List[RugnuxSummaryRow]**](RugnuxSummaryRow.md) | | +**warnings** | **List[str]** | | + +## Example + +```python +from jfjoch_client.models.rugnux_live_status import RugnuxLiveStatus + +# TODO update the JSON string below +json = "{}" +# create an instance of RugnuxLiveStatus from a JSON string +rugnux_live_status_instance = RugnuxLiveStatus.from_json(json) +# print the JSON string representation of the object +print(RugnuxLiveStatus.to_json()) + +# convert the object into a dict +rugnux_live_status_dict = rugnux_live_status_instance.to_dict() +# create an instance of RugnuxLiveStatus from a dict +rugnux_live_status_from_dict = RugnuxLiveStatus.from_dict(rugnux_live_status_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/python_client/docs/RugnuxLiveStep.md b/docs/python_client/docs/RugnuxLiveStep.md new file mode 100644 index 000000000..546a6c19c --- /dev/null +++ b/docs/python_client/docs/RugnuxLiveStep.md @@ -0,0 +1,32 @@ +# RugnuxLiveStep + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**title** | **str** | | +**started_s** | **float** | Start of the step, since the collection started | +**duration_s** | **float** | Set once the next step starts or the run ends | [optional] +**notes** | **List[str]** | What the step found (e.g. lattice, space group) | + +## Example + +```python +from jfjoch_client.models.rugnux_live_step import RugnuxLiveStep + +# TODO update the JSON string below +json = "{}" +# create an instance of RugnuxLiveStep from a JSON string +rugnux_live_step_instance = RugnuxLiveStep.from_json(json) +# print the JSON string representation of the object +print(RugnuxLiveStep.to_json()) + +# convert the object into a dict +rugnux_live_step_dict = rugnux_live_step_instance.to_dict() +# create an instance of RugnuxLiveStep from a dict +rugnux_live_step_from_dict = RugnuxLiveStep.from_dict(rugnux_live_step_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/python_client/docs/RugnuxSettings.md b/docs/python_client/docs/RugnuxSettings.md new file mode 100644 index 000000000..830ba02ff --- /dev/null +++ b/docs/python_client/docs/RugnuxSettings.md @@ -0,0 +1,31 @@ +# RugnuxSettings + +Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux inside the broker while they arrive, and the merged result is sent to the writer. + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**enable** | **bool** | Process DECTRIS rotation collections with rugnux live | [optional] [default to False] +**reserved_cpus** | **int** | CPU threads left to the receiver and the image pusher while rugnux runs | [optional] [default to 4] + +## Example + +```python +from jfjoch_client.models.rugnux_settings import RugnuxSettings + +# TODO update the JSON string below +json = "{}" +# create an instance of RugnuxSettings from a JSON string +rugnux_settings_instance = RugnuxSettings.from_json(json) +# print the JSON string representation of the object +print(RugnuxSettings.to_json()) + +# convert the object into a dict +rugnux_settings_dict = rugnux_settings_instance.to_dict() +# create an instance of RugnuxSettings from a dict +rugnux_settings_from_dict = RugnuxSettings.from_dict(rugnux_settings_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/python_client/docs/RugnuxStatus.md b/docs/python_client/docs/RugnuxStatus.md new file mode 100644 index 000000000..8859133d0 --- /dev/null +++ b/docs/python_client/docs/RugnuxStatus.md @@ -0,0 +1,32 @@ +# RugnuxStatus + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**enable** | **bool** | Rugnux live mode is enabled | +**reserved_cpus** | **int** | CPU threads left to the receiver and the image pusher while rugnux runs | +**model_loaded** | **bool** | A model was uploaded with `PUT /config/rugnux/model` | +**model_size_bytes** | **int** | Size of the uploaded model file | [optional] + +## Example + +```python +from jfjoch_client.models.rugnux_status import RugnuxStatus + +# TODO update the JSON string below +json = "{}" +# create an instance of RugnuxStatus from a JSON string +rugnux_status_instance = RugnuxStatus.from_json(json) +# print the JSON string representation of the object +print(RugnuxStatus.to_json()) + +# convert the object into a dict +rugnux_status_dict = rugnux_status_instance.to_dict() +# create an instance of RugnuxStatus from a dict +rugnux_status_from_dict = RugnuxStatus.from_dict(rugnux_status_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/python_client/docs/RugnuxSummaryRow.md b/docs/python_client/docs/RugnuxSummaryRow.md new file mode 100644 index 000000000..abee1e439 --- /dev/null +++ b/docs/python_client/docs/RugnuxSummaryRow.md @@ -0,0 +1,30 @@ +# RugnuxSummaryRow + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**label** | **str** | | +**value** | **str** | | + +## Example + +```python +from jfjoch_client.models.rugnux_summary_row import RugnuxSummaryRow + +# TODO update the JSON string below +json = "{}" +# create an instance of RugnuxSummaryRow from a JSON string +rugnux_summary_row_instance = RugnuxSummaryRow.from_json(json) +# print the JSON string representation of the object +print(RugnuxSummaryRow.to_json()) + +# convert the object into a dict +rugnux_summary_row_dict = rugnux_summary_row_instance.to_dict() +# create an instance of RugnuxSummaryRow from a dict +rugnux_summary_row_from_dict = RugnuxSummaryRow.from_dict(rugnux_summary_row_dict) +``` +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c3f749f13..23c657b5d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -24,6 +24,7 @@ import MenuBookIcon from "@mui/icons-material/MenuBook"; import HistoryIcon from "@mui/icons-material/History"; import CodeIcon from "@mui/icons-material/Code"; import GavelIcon from "@mui/icons-material/Gavel"; +import AutoGraphIcon from "@mui/icons-material/AutoGraph"; import _ from "lodash"; import Markdown from "react-markdown"; import remarkGfm from "remark-gfm"; @@ -32,6 +33,8 @@ import StatusBar from "./components/StatusBar"; import ErrorMessage from "./components/ErrorMessage"; import DataProcessingPlots from "./components/DataProcessingPlots"; import MeasurementStatistics from "./components/MeasurementStatistics"; +import RugnuxStatus from "./components/RugnuxStatus"; +import RugnuxPage from "./components/RugnuxPage"; import DataProcessingSettings from "./components/DataProcessingSettings"; import PreviewImage from "./components/PreviewImage"; import DataCollection from "./components/DataCollection"; @@ -168,7 +171,10 @@ function App() { - + + + + @@ -188,6 +194,9 @@ function App() { { id: 'data-collection', label: 'Data collection', icon: , render: () => ( )}, + { id: 'rugnux', label: 'Rugnux live', icon: , render: () => ( + + )}, ]}, { subheader: 'Expert', items: [ { id: 'detector', label: 'Detector', icon: , render: () => ( diff --git a/frontend/src/client/@tanstack/react-query.gen.ts b/frontend/src/client/@tanstack/react-query.gen.ts index c75023d1d..db804e38d 100644 --- a/frontend/src/client/@tanstack/react-query.gen.ts +++ b/frontend/src/client/@tanstack/react-query.gen.ts @@ -3,8 +3,8 @@ import { type DefaultError, queryOptions, type UseMutationOptions } from '@tanstack/react-query'; import { client } from '../client.gen'; -import { getConfigAzimInt, getConfigBraggIntegration, getConfigDarkMask, getConfigDetector, getConfigFileWriter, getConfigImageFormat, getConfigIndexing, getConfigInstrument, getConfigMask, getConfigMaskTiff, getConfigRoi, getConfigSelectDetector, getConfigSpotFinding, getConfigUserMask, getConfigUserMaskTiff, getConfigZeromqMetadata, getConfigZeromqPreview, getDetectorStatus, getFpgaStatus, getImageBufferImageCbor, getImageBufferImageJpeg, getImageBufferImageTiff, getImageBufferStartCbor, getImageBufferStatus, getImagePusherStatus, getPreviewPedestalTiff, getPreviewPlot, getPreviewPlotBin, getResultScan, getStatistics, getStatisticsCalibration, getStatisticsDataCollection, getStatus, getVersion, getXfelEventCode, getXfelPulseId, type Options, postCancel, postConfigImageFormatConversion, postConfigImageFormatRaw, postDeactivate, postImageBufferClear, postInitialize, postPedestal, postStart, postTrigger, postWaitTillDone, postWaitUntilRunning, putConfigAzimInt, putConfigBraggIntegration, putConfigDarkMask, putConfigDetector, putConfigFileWriter, putConfigImageFormat, putConfigIndexing, putConfigInstrument, putConfigInternalGeneratorImage, putConfigInternalGeneratorImageTiff, putConfigRoi, putConfigSelectDetector, putConfigSpotFinding, putConfigUserMask, putConfigUserMaskTiff, putConfigZeromqMetadata, putConfigZeromqPreview } from '../sdk.gen'; -import type { getConfigAzimIntData, getConfigAzimIntResponse, getConfigBraggIntegrationData, getConfigBraggIntegrationResponse, getConfigDarkMaskData, getConfigDarkMaskResponse, getConfigDetectorData, getConfigDetectorResponse, getConfigFileWriterData, getConfigFileWriterResponse, getConfigImageFormatData, getConfigImageFormatResponse, getConfigIndexingData, getConfigIndexingResponse, getConfigInstrumentData, getConfigInstrumentResponse, getConfigMaskData, getConfigMaskResponse, getConfigMaskTiffData, getConfigMaskTiffResponse, getConfigRoiData, getConfigRoiResponse, getConfigSelectDetectorData, getConfigSelectDetectorResponse, getConfigSpotFindingData, getConfigSpotFindingResponse, getConfigUserMaskData, getConfigUserMaskResponse, getConfigUserMaskTiffData, getConfigUserMaskTiffResponse, getConfigZeromqMetadataData, getConfigZeromqMetadataResponse, getConfigZeromqPreviewData, getConfigZeromqPreviewResponse, getDetectorStatusData, getDetectorStatusError, getDetectorStatusResponse, getFpgaStatusData, getFpgaStatusResponse, getImageBufferImageCborData, getImageBufferImageCborError, getImageBufferImageCborResponse, getImageBufferImageJpegData, getImageBufferImageJpegError, getImageBufferImageJpegResponse, getImageBufferImageTiffData, getImageBufferImageTiffError, getImageBufferImageTiffResponse, getImageBufferStartCborData, getImageBufferStartCborError, getImageBufferStartCborResponse, getImageBufferStatusData, getImageBufferStatusError, getImageBufferStatusResponse, getImagePusherStatusData, getImagePusherStatusError, getImagePusherStatusResponse, getPreviewPedestalTiffData, getPreviewPedestalTiffResponse, getPreviewPlotBinData, getPreviewPlotBinError, getPreviewPlotBinResponse, getPreviewPlotData, getPreviewPlotError, getPreviewPlotResponse, getResultScanData, getResultScanError, getResultScanResponse, getStatisticsCalibrationData, getStatisticsCalibrationResponse, getStatisticsData, getStatisticsDataCollectionData, getStatisticsDataCollectionError, getStatisticsDataCollectionResponse, getStatisticsResponse, getStatusData, getStatusResponse, getVersionData, getVersionResponse, getXfelEventCodeData, getXfelEventCodeResponse, getXfelPulseIdData, getXfelPulseIdResponse, postCancelData, postConfigImageFormatConversionData, postConfigImageFormatConversionError, postConfigImageFormatRawData, postConfigImageFormatRawError, postDeactivateData, postDeactivateError, postImageBufferClearData, postImageBufferClearError, postInitializeData, postInitializeError, postPedestalData, postPedestalError, postStartData, postStartError, postTriggerData, postWaitTillDoneData, postWaitTillDoneError, postWaitUntilRunningData, postWaitUntilRunningError, putConfigAzimIntData, putConfigAzimIntError, putConfigBraggIntegrationData, putConfigBraggIntegrationError, putConfigDarkMaskData, putConfigDarkMaskError, putConfigDetectorData, putConfigDetectorError, putConfigFileWriterData, putConfigFileWriterError, putConfigImageFormatData, putConfigImageFormatError, putConfigIndexingData, putConfigIndexingError, putConfigInstrumentData, putConfigInstrumentError, putConfigInternalGeneratorImageData, putConfigInternalGeneratorImageError, putConfigInternalGeneratorImageTiffData, putConfigInternalGeneratorImageTiffError, putConfigRoiData, putConfigRoiError, putConfigSelectDetectorData, putConfigSelectDetectorError, putConfigSpotFindingData, putConfigSpotFindingError, putConfigUserMaskData, putConfigUserMaskError, putConfigUserMaskTiffData, putConfigUserMaskTiffError, putConfigZeromqMetadataData, putConfigZeromqMetadataError, putConfigZeromqPreviewData, putConfigZeromqPreviewError } from '../types.gen'; +import { deleteConfigRugnuxModel, getConfigAzimInt, getConfigBraggIntegration, getConfigDarkMask, getConfigDetector, getConfigFileWriter, getConfigImageFormat, getConfigIndexing, getConfigInstrument, getConfigMask, getConfigMaskTiff, getConfigRoi, getConfigRugnux, getConfigSelectDetector, getConfigSpotFinding, getConfigUserMask, getConfigUserMaskTiff, getConfigZeromqMetadata, getConfigZeromqPreview, getDetectorStatus, getFpgaStatus, getImageBufferImageCbor, getImageBufferImageJpeg, getImageBufferImageTiff, getImageBufferStartCbor, getImageBufferStatus, getImagePusherStatus, getPreviewPedestalTiff, getPreviewPlot, getPreviewPlotBin, getResultScan, getRugnuxStatus, getStatistics, getStatisticsCalibration, getStatisticsDataCollection, getStatus, getVersion, getXfelEventCode, getXfelPulseId, type Options, postCancel, postConfigImageFormatConversion, postConfigImageFormatRaw, postDeactivate, postImageBufferClear, postInitialize, postPedestal, postStart, postTrigger, postWaitTillDone, postWaitUntilRunning, putConfigAzimInt, putConfigBraggIntegration, putConfigDarkMask, putConfigDetector, putConfigFileWriter, putConfigImageFormat, putConfigIndexing, putConfigInstrument, putConfigInternalGeneratorImage, putConfigInternalGeneratorImageTiff, putConfigRoi, putConfigRugnux, putConfigRugnuxModel, putConfigSelectDetector, putConfigSpotFinding, putConfigUserMask, putConfigUserMaskTiff, putConfigZeromqMetadata, putConfigZeromqPreview } from '../sdk.gen'; +import type { deleteConfigRugnuxModelData, deleteConfigRugnuxModelError, getConfigAzimIntData, getConfigAzimIntResponse, getConfigBraggIntegrationData, getConfigBraggIntegrationResponse, getConfigDarkMaskData, getConfigDarkMaskResponse, getConfigDetectorData, getConfigDetectorResponse, getConfigFileWriterData, getConfigFileWriterResponse, getConfigImageFormatData, getConfigImageFormatResponse, getConfigIndexingData, getConfigIndexingResponse, getConfigInstrumentData, getConfigInstrumentResponse, getConfigMaskData, getConfigMaskResponse, getConfigMaskTiffData, getConfigMaskTiffResponse, getConfigRoiData, getConfigRoiResponse, getConfigRugnuxData, getConfigRugnuxError, getConfigRugnuxResponse, getConfigSelectDetectorData, getConfigSelectDetectorResponse, getConfigSpotFindingData, getConfigSpotFindingResponse, getConfigUserMaskData, getConfigUserMaskResponse, getConfigUserMaskTiffData, getConfigUserMaskTiffResponse, getConfigZeromqMetadataData, getConfigZeromqMetadataResponse, getConfigZeromqPreviewData, getConfigZeromqPreviewResponse, getDetectorStatusData, getDetectorStatusError, getDetectorStatusResponse, getFpgaStatusData, getFpgaStatusResponse, getImageBufferImageCborData, getImageBufferImageCborError, getImageBufferImageCborResponse, getImageBufferImageJpegData, getImageBufferImageJpegError, getImageBufferImageJpegResponse, getImageBufferImageTiffData, getImageBufferImageTiffError, getImageBufferImageTiffResponse, getImageBufferStartCborData, getImageBufferStartCborError, getImageBufferStartCborResponse, getImageBufferStatusData, getImageBufferStatusError, getImageBufferStatusResponse, getImagePusherStatusData, getImagePusherStatusError, getImagePusherStatusResponse, getPreviewPedestalTiffData, getPreviewPedestalTiffResponse, getPreviewPlotBinData, getPreviewPlotBinError, getPreviewPlotBinResponse, getPreviewPlotData, getPreviewPlotError, getPreviewPlotResponse, getResultScanData, getResultScanError, getResultScanResponse, getRugnuxStatusData, getRugnuxStatusError, getRugnuxStatusResponse, getStatisticsCalibrationData, getStatisticsCalibrationResponse, getStatisticsData, getStatisticsDataCollectionData, getStatisticsDataCollectionError, getStatisticsDataCollectionResponse, getStatisticsResponse, getStatusData, getStatusResponse, getVersionData, getVersionResponse, getXfelEventCodeData, getXfelEventCodeResponse, getXfelPulseIdData, getXfelPulseIdResponse, postCancelData, postConfigImageFormatConversionData, postConfigImageFormatConversionError, postConfigImageFormatRawData, postConfigImageFormatRawError, postDeactivateData, postDeactivateError, postImageBufferClearData, postImageBufferClearError, postInitializeData, postInitializeError, postPedestalData, postPedestalError, postStartData, postStartError, postTriggerData, postWaitTillDoneData, postWaitTillDoneError, postWaitUntilRunningData, postWaitUntilRunningError, putConfigAzimIntData, putConfigAzimIntError, putConfigBraggIntegrationData, putConfigBraggIntegrationError, putConfigDarkMaskData, putConfigDarkMaskError, putConfigDetectorData, putConfigDetectorError, putConfigFileWriterData, putConfigFileWriterError, putConfigImageFormatData, putConfigImageFormatError, putConfigIndexingData, putConfigIndexingError, putConfigInstrumentData, putConfigInstrumentError, putConfigInternalGeneratorImageData, putConfigInternalGeneratorImageError, putConfigInternalGeneratorImageTiffData, putConfigInternalGeneratorImageTiffError, putConfigRoiData, putConfigRoiError, putConfigRugnuxData, putConfigRugnuxError, putConfigRugnuxModelData, putConfigRugnuxModelError, putConfigSelectDetectorData, putConfigSelectDetectorError, putConfigSpotFindingData, putConfigSpotFindingError, putConfigUserMaskData, putConfigUserMaskError, putConfigUserMaskTiffData, putConfigUserMaskTiffError, putConfigZeromqMetadataData, putConfigZeromqMetadataError, putConfigZeromqPreviewData, putConfigZeromqPreviewError } from '../types.gen'; /** * Initialize detector and data acquisition @@ -156,6 +156,11 @@ export const postTriggerMutation = (options?: Partial>) * Any frame that is currently being processed by CPU will be finished and sent to writer. * Given the command is making sure to gracefully stop data acquisition and detector, it might take some time to switch back after command finished to `Idle` state. * + * With rugnux live mode (`/config/rugnux`) the command also cuts the rugnux analysis, during the collection + * or after its last image alike: the collection ends with what the analysis has established + * (see `cut` in `/rugnux/status`). After the last image the collection itself is complete and is not + * marked as cancelled. + * * If data collection is not running, the command has no effect. * */ @@ -675,6 +680,109 @@ export const putConfigSelectDetectorMutation = (options?: Partial) => createQueryKey('getConfigRugnux', options); + +/** + * Get rugnux live mode settings and model status + * + * Can be done anytime + */ +export const getConfigRugnuxOptions = (options?: Options) => queryOptions>({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getConfigRugnux({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getConfigRugnuxQueryKey(options) +}); + +/** + * Change rugnux live mode settings + * + * This can only be done when detector is `Idle`, `Error` or `Inactive` states. + * + */ +export const putConfigRugnuxMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await putConfigRugnux({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Remove the model used by rugnux live mode + * + * This can only be done when detector is `Idle`, `Error` or `Inactive` states. + * + */ +export const deleteConfigRugnuxModelMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await deleteConfigRugnuxModel({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +/** + * Upload the model used by rugnux live mode + * + * This can only be done when detector is `Idle`, `Error` or `Inactive` states. + * The model (PDB or mmCIF, optionally gzipped; at most 64 MiB) is used for molecular replacement + * and validation of every following rugnux live collection, until it is removed with + * `DELETE /config/rugnux/model` or replaced by another upload. + * + */ +export const putConfigRugnuxModelMutation = (options?: Partial>): UseMutationOptions> => { + const mutationOptions: UseMutationOptions> = { + mutationFn: async (fnOptions) => { + const { data } = await putConfigRugnuxModel({ + ...options, + ...fnOptions, + throwOnError: true + }); + return data; + } + }; + return mutationOptions; +}; + +export const getRugnuxStatusQueryKey = (options?: Options) => createQueryKey('getRugnuxStatus', options); + +/** + * Get live rugnux processing status of the last collection + * + * Can be done anytime + */ +export const getRugnuxStatusOptions = (options?: Options) => queryOptions>({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getRugnuxStatus({ + ...options, + ...queryKey[0], + signal, + throwOnError: true + }); + return data; + }, + queryKey: getRugnuxStatusQueryKey(options) +}); + export const getConfigZeromqPreviewQueryKey = (options?: Options) => createQueryKey('getConfigZeromqPreview', options); /** diff --git a/frontend/src/client/index.ts b/frontend/src/client/index.ts index 12298acb4..a99adbd54 100644 --- a/frontend/src/client/index.ts +++ b/frontend/src/client/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { getConfigAzimInt, getConfigBraggIntegration, getConfigDarkMask, getConfigDetector, getConfigFileWriter, getConfigImageFormat, getConfigIndexing, getConfigInstrument, getConfigMask, getConfigMaskTiff, getConfigRoi, getConfigSelectDetector, getConfigSpotFinding, getConfigUserMask, getConfigUserMaskTiff, getConfigZeromqMetadata, getConfigZeromqPreview, getDetectorStatus, getFpgaStatus, getImageBufferImageCbor, getImageBufferImageJpeg, getImageBufferImageTiff, getImageBufferStartCbor, getImageBufferStatus, getImagePusherStatus, getPreviewPedestalTiff, getPreviewPlot, getPreviewPlotBin, getResultScan, getStatistics, getStatisticsCalibration, getStatisticsDataCollection, getStatus, getVersion, getXfelEventCode, getXfelPulseId, type Options, postCancel, postConfigImageFormatConversion, postConfigImageFormatRaw, postDeactivate, postImageBufferClear, postInitialize, postPedestal, postStart, postTrigger, postWaitTillDone, postWaitUntilRunning, putConfigAzimInt, putConfigBraggIntegration, putConfigDarkMask, putConfigDetector, putConfigFileWriter, putConfigImageFormat, putConfigIndexing, putConfigInstrument, putConfigInternalGeneratorImage, putConfigInternalGeneratorImageTiff, putConfigRoi, putConfigSelectDetector, putConfigSpotFinding, putConfigUserMask, putConfigUserMaskTiff, putConfigZeromqMetadata, putConfigZeromqPreview } from './sdk.gen'; -export { type azim_int_settings, azint_unit, type background, type binning, type bragg_integration_settings, type broker_status, type calibration_statistics, type ClientOptions, color_scale, type crystal_lattice, type dark_mask_settings, type dataset_settings, type dataset_settingsWritable, type detector, type detector_list, type detector_list_element, type detector_module, detector_module_direction, detector_power_state, type detector_selection, type detector_settings, detector_state, type detector_status, detector_timing, detector_type, type error_message, type experimental_coord, file_writer_format, type file_writer_settings, type fill_value, type fpga_status, geom_refinement_algorithm, type getConfigAzimIntData, type getConfigAzimIntResponse, type getConfigAzimIntResponses, type getConfigBraggIntegrationData, type getConfigBraggIntegrationResponse, type getConfigBraggIntegrationResponses, type getConfigDarkMaskData, type getConfigDarkMaskResponse, type getConfigDarkMaskResponses, type getConfigDetectorData, type getConfigDetectorResponse, type getConfigDetectorResponses, type getConfigFileWriterData, type getConfigFileWriterResponse, type getConfigFileWriterResponses, type getConfigImageFormatData, type getConfigImageFormatResponse, type getConfigImageFormatResponses, type getConfigIndexingData, type getConfigIndexingResponse, type getConfigIndexingResponses, type getConfigInstrumentData, type getConfigInstrumentResponse, type getConfigInstrumentResponses, type getConfigMaskData, type getConfigMaskResponse, type getConfigMaskResponses, type getConfigMaskTiffData, type getConfigMaskTiffResponse, type getConfigMaskTiffResponses, type getConfigRoiData, type getConfigRoiResponse, type getConfigRoiResponses, type getConfigSelectDetectorData, type getConfigSelectDetectorResponse, type getConfigSelectDetectorResponses, type getConfigSpotFindingData, type getConfigSpotFindingResponse, type getConfigSpotFindingResponses, type getConfigUserMaskData, type getConfigUserMaskResponse, type getConfigUserMaskResponses, type getConfigUserMaskTiffData, type getConfigUserMaskTiffResponse, type getConfigUserMaskTiffResponses, type getConfigZeromqMetadataData, type getConfigZeromqMetadataResponse, type getConfigZeromqMetadataResponses, type getConfigZeromqPreviewData, type getConfigZeromqPreviewResponse, type getConfigZeromqPreviewResponses, type getDetectorStatusData, type getDetectorStatusError, type getDetectorStatusErrors, type getDetectorStatusResponse, type getDetectorStatusResponses, type getFpgaStatusData, type getFpgaStatusResponse, type getFpgaStatusResponses, type getImageBufferImageCborData, type getImageBufferImageCborError, type getImageBufferImageCborErrors, type getImageBufferImageCborResponse, type getImageBufferImageCborResponses, type getImageBufferImageJpegData, type getImageBufferImageJpegError, type getImageBufferImageJpegErrors, type getImageBufferImageJpegResponse, type getImageBufferImageJpegResponses, type getImageBufferImageTiffData, type getImageBufferImageTiffError, type getImageBufferImageTiffErrors, type getImageBufferImageTiffResponse, type getImageBufferImageTiffResponses, type getImageBufferStartCborData, type getImageBufferStartCborError, type getImageBufferStartCborErrors, type getImageBufferStartCborResponse, type getImageBufferStartCborResponses, type getImageBufferStatusData, type getImageBufferStatusError, type getImageBufferStatusErrors, type getImageBufferStatusResponse, type getImageBufferStatusResponses, type getImagePusherStatusData, type getImagePusherStatusError, type getImagePusherStatusErrors, type getImagePusherStatusResponse, type getImagePusherStatusResponses, type getPreviewPedestalTiffData, type getPreviewPedestalTiffErrors, type getPreviewPedestalTiffResponse, type getPreviewPedestalTiffResponses, type getPreviewPlotBinData, type getPreviewPlotBinError, type getPreviewPlotBinErrors, type getPreviewPlotBinResponse, type getPreviewPlotBinResponses, type getPreviewPlotData, type getPreviewPlotError, type getPreviewPlotErrors, type getPreviewPlotResponse, type getPreviewPlotResponses, type getResultScanData, type getResultScanError, type getResultScanErrors, type getResultScanResponse, type getResultScanResponses, type getStatisticsCalibrationData, type getStatisticsCalibrationResponse, type getStatisticsCalibrationResponses, type getStatisticsData, type getStatisticsDataCollectionData, type getStatisticsDataCollectionError, type getStatisticsDataCollectionErrors, type getStatisticsDataCollectionResponse, type getStatisticsDataCollectionResponses, type getStatisticsResponse, type getStatisticsResponses, type getStatusData, type getStatusResponse, type getStatusResponses, type getVersionData, type getVersionResponse, type getVersionResponses, type getXfelEventCodeData, type getXfelEventCodeErrors, type getXfelEventCodeResponse, type getXfelEventCodeResponses, type getXfelPulseIdData, type getXfelPulseIdErrors, type getXfelPulseIdResponse, type getXfelPulseIdResponses, type grid_scan, type image_buffer_status, type image_format_settings, type image_id, type image_pusher_status, image_pusher_type, indexing_algorithm, type indexing_settings, type instrument_metadata, integration_model, type jfjoch_settings, type jfjoch_statistics, type jpeg_quality, type measurement_statistics, type pcie_devices, type pixel_mask_statistics, type plot, plot_type, plot_unit_x, type plots, type postCancelData, type postCancelResponses, type postConfigImageFormatConversionData, type postConfigImageFormatConversionError, type postConfigImageFormatConversionErrors, type postConfigImageFormatConversionResponses, type postConfigImageFormatRawData, type postConfigImageFormatRawError, type postConfigImageFormatRawErrors, type postConfigImageFormatRawResponses, type postDeactivateData, type postDeactivateError, type postDeactivateErrors, type postDeactivateResponses, type postImageBufferClearData, type postImageBufferClearError, type postImageBufferClearErrors, type postImageBufferClearResponses, type postInitializeData, type postInitializeError, type postInitializeErrors, type postInitializeResponses, type postPedestalData, type postPedestalError, type postPedestalErrors, type postPedestalResponses, type postStartData, type postStartError, type postStartErrors, type postStartResponses, type postTriggerData, type postTriggerResponses, type postWaitTillDoneData, type postWaitTillDoneError, type postWaitTillDoneErrors, type postWaitTillDoneResponses, type postWaitUntilRunningData, type postWaitUntilRunningError, type postWaitUntilRunningErrors, type postWaitUntilRunningResponses, type powder_calibration_fit_sigma, type powder_calibration_output, type powder_calibration_outputWritable, type powder_calibration_quality, type powder_calibration_spot_check, type putConfigAzimIntData, type putConfigAzimIntError, type putConfigAzimIntErrors, type putConfigAzimIntResponses, type putConfigBraggIntegrationData, type putConfigBraggIntegrationError, type putConfigBraggIntegrationErrors, type putConfigBraggIntegrationResponses, type putConfigDarkMaskData, type putConfigDarkMaskError, type putConfigDarkMaskErrors, type putConfigDarkMaskResponses, type putConfigDetectorData, type putConfigDetectorError, type putConfigDetectorErrors, type putConfigDetectorResponses, type putConfigFileWriterData, type putConfigFileWriterError, type putConfigFileWriterErrors, type putConfigFileWriterResponses, type putConfigImageFormatData, type putConfigImageFormatError, type putConfigImageFormatErrors, type putConfigImageFormatResponses, type putConfigIndexingData, type putConfigIndexingError, type putConfigIndexingErrors, type putConfigIndexingResponses, type putConfigInstrumentData, type putConfigInstrumentError, type putConfigInstrumentErrors, type putConfigInstrumentResponses, type putConfigInternalGeneratorImageData, type putConfigInternalGeneratorImageError, type putConfigInternalGeneratorImageErrors, type putConfigInternalGeneratorImageResponses, type putConfigInternalGeneratorImageTiffData, type putConfigInternalGeneratorImageTiffError, type putConfigInternalGeneratorImageTiffErrors, type putConfigInternalGeneratorImageTiffResponses, type putConfigRoiData, type putConfigRoiError, type putConfigRoiErrors, type putConfigRoiResponses, type putConfigSelectDetectorData, type putConfigSelectDetectorError, type putConfigSelectDetectorErrors, type putConfigSelectDetectorResponses, type putConfigSpotFindingData, type putConfigSpotFindingError, type putConfigSpotFindingErrors, type putConfigSpotFindingResponses, type putConfigUserMaskData, type putConfigUserMaskError, type putConfigUserMaskErrors, type putConfigUserMaskResponses, type putConfigUserMaskTiffData, type putConfigUserMaskTiffError, type putConfigUserMaskTiffErrors, type putConfigUserMaskTiffResponses, type putConfigZeromqMetadataData, type putConfigZeromqMetadataError, type putConfigZeromqMetadataErrors, type putConfigZeromqMetadataResponses, type putConfigZeromqPreviewData, type putConfigZeromqPreviewError, type putConfigZeromqPreviewErrors, type putConfigZeromqPreviewResponses, type resolution_estimate, type resolution_ring, type roi, type roi_azim_list, type roi_azimuthal, type roi_box, type roi_box_list, type roi_circle, type roi_circle_list, type roi_definitions, type rotation_axis, type saturation, type scan_result, type show_beam_center, type show_predictions, type show_roi, type show_spots, type show_user_mask, type spot_finding_settings, type standard_detector_geometry, type tcp_settings, type unit_cell, type zeromq_metadata_settings, type zeromq_preview_settings, type zeromq_settings } from './types.gen'; +export { deleteConfigRugnuxModel, getConfigAzimInt, getConfigBraggIntegration, getConfigDarkMask, getConfigDetector, getConfigFileWriter, getConfigImageFormat, getConfigIndexing, getConfigInstrument, getConfigMask, getConfigMaskTiff, getConfigRoi, getConfigRugnux, getConfigSelectDetector, getConfigSpotFinding, getConfigUserMask, getConfigUserMaskTiff, getConfigZeromqMetadata, getConfigZeromqPreview, getDetectorStatus, getFpgaStatus, getImageBufferImageCbor, getImageBufferImageJpeg, getImageBufferImageTiff, getImageBufferStartCbor, getImageBufferStatus, getImagePusherStatus, getLiveEvents, getPreviewPedestalTiff, getPreviewPlot, getPreviewPlotBin, getResultScan, getRugnuxStatus, getStatistics, getStatisticsCalibration, getStatisticsDataCollection, getStatus, getVersion, getXfelEventCode, getXfelPulseId, type Options, postCancel, postConfigImageFormatConversion, postConfigImageFormatRaw, postDeactivate, postImageBufferClear, postInitialize, postPedestal, postStart, postTrigger, postWaitTillDone, postWaitUntilRunning, putConfigAzimInt, putConfigBraggIntegration, putConfigDarkMask, putConfigDetector, putConfigFileWriter, putConfigImageFormat, putConfigIndexing, putConfigInstrument, putConfigInternalGeneratorImage, putConfigInternalGeneratorImageTiff, putConfigRoi, putConfigRugnux, putConfigRugnuxModel, putConfigSelectDetector, putConfigSpotFinding, putConfigUserMask, putConfigUserMaskTiff, putConfigZeromqMetadata, putConfigZeromqPreview } from './sdk.gen'; +export { type azim_int_settings, azint_unit, type background, type binning, type bragg_integration_settings, type broker_status, type calibration_statistics, type ClientOptions, color_scale, type crystal_lattice, type dark_mask_settings, type dataset_settings, type dataset_settingsWritable, type deleteConfigRugnuxModelData, type deleteConfigRugnuxModelError, type deleteConfigRugnuxModelErrors, type deleteConfigRugnuxModelResponses, type detector, type detector_list, type detector_list_element, type detector_module, detector_module_direction, detector_power_state, type detector_selection, type detector_settings, detector_state, type detector_status, detector_timing, detector_type, type error_message, type experimental_coord, file_writer_format, type file_writer_settings, type fill_value, type fpga_status, geom_refinement_algorithm, type getConfigAzimIntData, type getConfigAzimIntResponse, type getConfigAzimIntResponses, type getConfigBraggIntegrationData, type getConfigBraggIntegrationResponse, type getConfigBraggIntegrationResponses, type getConfigDarkMaskData, type getConfigDarkMaskResponse, type getConfigDarkMaskResponses, type getConfigDetectorData, type getConfigDetectorResponse, type getConfigDetectorResponses, type getConfigFileWriterData, type getConfigFileWriterResponse, type getConfigFileWriterResponses, type getConfigImageFormatData, type getConfigImageFormatResponse, type getConfigImageFormatResponses, type getConfigIndexingData, type getConfigIndexingResponse, type getConfigIndexingResponses, type getConfigInstrumentData, type getConfigInstrumentResponse, type getConfigInstrumentResponses, type getConfigMaskData, type getConfigMaskResponse, type getConfigMaskResponses, type getConfigMaskTiffData, type getConfigMaskTiffResponse, type getConfigMaskTiffResponses, type getConfigRoiData, type getConfigRoiResponse, type getConfigRoiResponses, type getConfigRugnuxData, type getConfigRugnuxError, type getConfigRugnuxErrors, type getConfigRugnuxResponse, type getConfigRugnuxResponses, type getConfigSelectDetectorData, type getConfigSelectDetectorResponse, type getConfigSelectDetectorResponses, type getConfigSpotFindingData, type getConfigSpotFindingResponse, type getConfigSpotFindingResponses, type getConfigUserMaskData, type getConfigUserMaskResponse, type getConfigUserMaskResponses, type getConfigUserMaskTiffData, type getConfigUserMaskTiffResponse, type getConfigUserMaskTiffResponses, type getConfigZeromqMetadataData, type getConfigZeromqMetadataResponse, type getConfigZeromqMetadataResponses, type getConfigZeromqPreviewData, type getConfigZeromqPreviewResponse, type getConfigZeromqPreviewResponses, type getDetectorStatusData, type getDetectorStatusError, type getDetectorStatusErrors, type getDetectorStatusResponse, type getDetectorStatusResponses, type getFpgaStatusData, type getFpgaStatusResponse, type getFpgaStatusResponses, type getImageBufferImageCborData, type getImageBufferImageCborError, type getImageBufferImageCborErrors, type getImageBufferImageCborResponse, type getImageBufferImageCborResponses, type getImageBufferImageJpegData, type getImageBufferImageJpegError, type getImageBufferImageJpegErrors, type getImageBufferImageJpegResponse, type getImageBufferImageJpegResponses, type getImageBufferImageTiffData, type getImageBufferImageTiffError, type getImageBufferImageTiffErrors, type getImageBufferImageTiffResponse, type getImageBufferImageTiffResponses, type getImageBufferStartCborData, type getImageBufferStartCborError, type getImageBufferStartCborErrors, type getImageBufferStartCborResponse, type getImageBufferStartCborResponses, type getImageBufferStatusData, type getImageBufferStatusError, type getImageBufferStatusErrors, type getImageBufferStatusResponse, type getImageBufferStatusResponses, type getImagePusherStatusData, type getImagePusherStatusError, type getImagePusherStatusErrors, type getImagePusherStatusResponse, type getImagePusherStatusResponses, type getLiveEventsData, type getLiveEventsError, type getLiveEventsErrors, type getLiveEventsResponse, type getLiveEventsResponses, type getPreviewPedestalTiffData, type getPreviewPedestalTiffErrors, type getPreviewPedestalTiffResponse, type getPreviewPedestalTiffResponses, type getPreviewPlotBinData, type getPreviewPlotBinError, type getPreviewPlotBinErrors, type getPreviewPlotBinResponse, type getPreviewPlotBinResponses, type getPreviewPlotData, type getPreviewPlotError, type getPreviewPlotErrors, type getPreviewPlotResponse, type getPreviewPlotResponses, type getResultScanData, type getResultScanError, type getResultScanErrors, type getResultScanResponse, type getResultScanResponses, type getRugnuxStatusData, type getRugnuxStatusError, type getRugnuxStatusErrors, type getRugnuxStatusResponse, type getRugnuxStatusResponses, type getStatisticsCalibrationData, type getStatisticsCalibrationResponse, type getStatisticsCalibrationResponses, type getStatisticsData, type getStatisticsDataCollectionData, type getStatisticsDataCollectionError, type getStatisticsDataCollectionErrors, type getStatisticsDataCollectionResponse, type getStatisticsDataCollectionResponses, type getStatisticsResponse, type getStatisticsResponses, type getStatusData, type getStatusResponse, type getStatusResponses, type getVersionData, type getVersionResponse, type getVersionResponses, type getXfelEventCodeData, type getXfelEventCodeErrors, type getXfelEventCodeResponse, type getXfelEventCodeResponses, type getXfelPulseIdData, type getXfelPulseIdErrors, type getXfelPulseIdResponse, type getXfelPulseIdResponses, type grid_scan, type image_buffer_status, type image_format_settings, type image_id, type image_pusher_status, image_pusher_type, indexing_algorithm, type indexing_settings, type instrument_metadata, integration_model, type jfjoch_settings, type jfjoch_statistics, type jpeg_quality, type measurement_statistics, type pcie_devices, type pixel_mask_statistics, type plot, plot_type, plot_unit_x, type plots, type postCancelData, type postCancelResponses, type postConfigImageFormatConversionData, type postConfigImageFormatConversionError, type postConfigImageFormatConversionErrors, type postConfigImageFormatConversionResponses, type postConfigImageFormatRawData, type postConfigImageFormatRawError, type postConfigImageFormatRawErrors, type postConfigImageFormatRawResponses, type postDeactivateData, type postDeactivateError, type postDeactivateErrors, type postDeactivateResponses, type postImageBufferClearData, type postImageBufferClearError, type postImageBufferClearErrors, type postImageBufferClearResponses, type postInitializeData, type postInitializeError, type postInitializeErrors, type postInitializeResponses, type postPedestalData, type postPedestalError, type postPedestalErrors, type postPedestalResponses, type postStartData, type postStartError, type postStartErrors, type postStartResponses, type postTriggerData, type postTriggerResponses, type postWaitTillDoneData, type postWaitTillDoneError, type postWaitTillDoneErrors, type postWaitTillDoneResponses, type postWaitUntilRunningData, type postWaitUntilRunningError, type postWaitUntilRunningErrors, type postWaitUntilRunningResponses, type powder_calibration_fit_sigma, type powder_calibration_output, type powder_calibration_outputWritable, type powder_calibration_quality, type powder_calibration_spot_check, type putConfigAzimIntData, type putConfigAzimIntError, type putConfigAzimIntErrors, type putConfigAzimIntResponses, type putConfigBraggIntegrationData, type putConfigBraggIntegrationError, type putConfigBraggIntegrationErrors, type putConfigBraggIntegrationResponses, type putConfigDarkMaskData, type putConfigDarkMaskError, type putConfigDarkMaskErrors, type putConfigDarkMaskResponses, type putConfigDetectorData, type putConfigDetectorError, type putConfigDetectorErrors, type putConfigDetectorResponses, type putConfigFileWriterData, type putConfigFileWriterError, type putConfigFileWriterErrors, type putConfigFileWriterResponses, type putConfigImageFormatData, type putConfigImageFormatError, type putConfigImageFormatErrors, type putConfigImageFormatResponses, type putConfigIndexingData, type putConfigIndexingError, type putConfigIndexingErrors, type putConfigIndexingResponses, type putConfigInstrumentData, type putConfigInstrumentError, type putConfigInstrumentErrors, type putConfigInstrumentResponses, type putConfigInternalGeneratorImageData, type putConfigInternalGeneratorImageError, type putConfigInternalGeneratorImageErrors, type putConfigInternalGeneratorImageResponses, type putConfigInternalGeneratorImageTiffData, type putConfigInternalGeneratorImageTiffError, type putConfigInternalGeneratorImageTiffErrors, type putConfigInternalGeneratorImageTiffResponses, type putConfigRoiData, type putConfigRoiError, type putConfigRoiErrors, type putConfigRoiResponses, type putConfigRugnuxData, type putConfigRugnuxError, type putConfigRugnuxErrors, type putConfigRugnuxModelData, type putConfigRugnuxModelError, type putConfigRugnuxModelErrors, type putConfigRugnuxModelResponses, type putConfigRugnuxResponses, type putConfigSelectDetectorData, type putConfigSelectDetectorError, type putConfigSelectDetectorErrors, type putConfigSelectDetectorResponses, type putConfigSpotFindingData, type putConfigSpotFindingError, type putConfigSpotFindingErrors, type putConfigSpotFindingResponses, type putConfigUserMaskData, type putConfigUserMaskError, type putConfigUserMaskErrors, type putConfigUserMaskResponses, type putConfigUserMaskTiffData, type putConfigUserMaskTiffError, type putConfigUserMaskTiffErrors, type putConfigUserMaskTiffResponses, type putConfigZeromqMetadataData, type putConfigZeromqMetadataError, type putConfigZeromqMetadataErrors, type putConfigZeromqMetadataResponses, type putConfigZeromqPreviewData, type putConfigZeromqPreviewError, type putConfigZeromqPreviewErrors, type putConfigZeromqPreviewResponses, type resolution_estimate, type resolution_ring, type roi, type roi_azim_list, type roi_azimuthal, type roi_box, type roi_box_list, type roi_circle, type roi_circle_list, type roi_definitions, type rotation_axis, type rugnux_live_status, type rugnux_live_step, type rugnux_settings, type rugnux_status, type rugnux_summary_row, type saturation, type scan_result, type show_beam_center, type show_predictions, type show_roi, type show_spots, type show_user_mask, type spot_finding_settings, type standard_detector_geometry, type tcp_settings, type unit_cell, type zeromq_metadata_settings, type zeromq_preview_settings, type zeromq_settings } from './types.gen'; diff --git a/frontend/src/client/sdk.gen.ts b/frontend/src/client/sdk.gen.ts index 56ad7f27d..9e0922bea 100644 --- a/frontend/src/client/sdk.gen.ts +++ b/frontend/src/client/sdk.gen.ts @@ -1,8 +1,8 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Client, ClientMeta, Options as Options2, RequestResult, TDataShape } from './client'; +import type { Client, ClientMeta, Options as Options2, RequestResult, ServerSentEventsResult, TDataShape } from './client'; import { client } from './client.gen'; -import type { getConfigAzimIntData, getConfigAzimIntResponses, getConfigBraggIntegrationData, getConfigBraggIntegrationResponses, getConfigDarkMaskData, getConfigDarkMaskResponses, getConfigDetectorData, getConfigDetectorResponses, getConfigFileWriterData, getConfigFileWriterResponses, getConfigImageFormatData, getConfigImageFormatResponses, getConfigIndexingData, getConfigIndexingResponses, getConfigInstrumentData, getConfigInstrumentResponses, getConfigMaskData, getConfigMaskResponses, getConfigMaskTiffData, getConfigMaskTiffResponses, getConfigRoiData, getConfigRoiResponses, getConfigSelectDetectorData, getConfigSelectDetectorResponses, getConfigSpotFindingData, getConfigSpotFindingResponses, getConfigUserMaskData, getConfigUserMaskResponses, getConfigUserMaskTiffData, getConfigUserMaskTiffResponses, getConfigZeromqMetadataData, getConfigZeromqMetadataResponses, getConfigZeromqPreviewData, getConfigZeromqPreviewResponses, getDetectorStatusData, getDetectorStatusErrors, getDetectorStatusResponses, getFpgaStatusData, getFpgaStatusResponses, getImageBufferImageCborData, getImageBufferImageCborErrors, getImageBufferImageCborResponses, getImageBufferImageJpegData, getImageBufferImageJpegErrors, getImageBufferImageJpegResponses, getImageBufferImageTiffData, getImageBufferImageTiffErrors, getImageBufferImageTiffResponses, getImageBufferStartCborData, getImageBufferStartCborErrors, getImageBufferStartCborResponses, getImageBufferStatusData, getImageBufferStatusErrors, getImageBufferStatusResponses, getImagePusherStatusData, getImagePusherStatusErrors, getImagePusherStatusResponses, getPreviewPedestalTiffData, getPreviewPedestalTiffErrors, getPreviewPedestalTiffResponses, getPreviewPlotBinData, getPreviewPlotBinErrors, getPreviewPlotBinResponses, getPreviewPlotData, getPreviewPlotErrors, getPreviewPlotResponses, getResultScanData, getResultScanErrors, getResultScanResponses, getStatisticsCalibrationData, getStatisticsCalibrationResponses, getStatisticsData, getStatisticsDataCollectionData, getStatisticsDataCollectionErrors, getStatisticsDataCollectionResponses, getStatisticsResponses, getStatusData, getStatusResponses, getVersionData, getVersionResponses, getXfelEventCodeData, getXfelEventCodeErrors, getXfelEventCodeResponses, getXfelPulseIdData, getXfelPulseIdErrors, getXfelPulseIdResponses, postCancelData, postCancelResponses, postConfigImageFormatConversionData, postConfigImageFormatConversionErrors, postConfigImageFormatConversionResponses, postConfigImageFormatRawData, postConfigImageFormatRawErrors, postConfigImageFormatRawResponses, postDeactivateData, postDeactivateErrors, postDeactivateResponses, postImageBufferClearData, postImageBufferClearErrors, postImageBufferClearResponses, postInitializeData, postInitializeErrors, postInitializeResponses, postPedestalData, postPedestalErrors, postPedestalResponses, postStartData, postStartErrors, postStartResponses, postTriggerData, postTriggerResponses, postWaitTillDoneData, postWaitTillDoneErrors, postWaitTillDoneResponses, postWaitUntilRunningData, postWaitUntilRunningErrors, postWaitUntilRunningResponses, putConfigAzimIntData, putConfigAzimIntErrors, putConfigAzimIntResponses, putConfigBraggIntegrationData, putConfigBraggIntegrationErrors, putConfigBraggIntegrationResponses, putConfigDarkMaskData, putConfigDarkMaskErrors, putConfigDarkMaskResponses, putConfigDetectorData, putConfigDetectorErrors, putConfigDetectorResponses, putConfigFileWriterData, putConfigFileWriterErrors, putConfigFileWriterResponses, putConfigImageFormatData, putConfigImageFormatErrors, putConfigImageFormatResponses, putConfigIndexingData, putConfigIndexingErrors, putConfigIndexingResponses, putConfigInstrumentData, putConfigInstrumentErrors, putConfigInstrumentResponses, putConfigInternalGeneratorImageData, putConfigInternalGeneratorImageErrors, putConfigInternalGeneratorImageResponses, putConfigInternalGeneratorImageTiffData, putConfigInternalGeneratorImageTiffErrors, putConfigInternalGeneratorImageTiffResponses, putConfigRoiData, putConfigRoiErrors, putConfigRoiResponses, putConfigSelectDetectorData, putConfigSelectDetectorErrors, putConfigSelectDetectorResponses, putConfigSpotFindingData, putConfigSpotFindingErrors, putConfigSpotFindingResponses, putConfigUserMaskData, putConfigUserMaskErrors, putConfigUserMaskResponses, putConfigUserMaskTiffData, putConfigUserMaskTiffErrors, putConfigUserMaskTiffResponses, putConfigZeromqMetadataData, putConfigZeromqMetadataErrors, putConfigZeromqMetadataResponses, putConfigZeromqPreviewData, putConfigZeromqPreviewErrors, putConfigZeromqPreviewResponses } from './types.gen'; +import type { deleteConfigRugnuxModelData, deleteConfigRugnuxModelErrors, deleteConfigRugnuxModelResponses, getConfigAzimIntData, getConfigAzimIntResponses, getConfigBraggIntegrationData, getConfigBraggIntegrationResponses, getConfigDarkMaskData, getConfigDarkMaskResponses, getConfigDetectorData, getConfigDetectorResponses, getConfigFileWriterData, getConfigFileWriterResponses, getConfigImageFormatData, getConfigImageFormatResponses, getConfigIndexingData, getConfigIndexingResponses, getConfigInstrumentData, getConfigInstrumentResponses, getConfigMaskData, getConfigMaskResponses, getConfigMaskTiffData, getConfigMaskTiffResponses, getConfigRoiData, getConfigRoiResponses, getConfigRugnuxData, getConfigRugnuxErrors, getConfigRugnuxResponses, getConfigSelectDetectorData, getConfigSelectDetectorResponses, getConfigSpotFindingData, getConfigSpotFindingResponses, getConfigUserMaskData, getConfigUserMaskResponses, getConfigUserMaskTiffData, getConfigUserMaskTiffResponses, getConfigZeromqMetadataData, getConfigZeromqMetadataResponses, getConfigZeromqPreviewData, getConfigZeromqPreviewResponses, getDetectorStatusData, getDetectorStatusErrors, getDetectorStatusResponses, getFpgaStatusData, getFpgaStatusResponses, getImageBufferImageCborData, getImageBufferImageCborErrors, getImageBufferImageCborResponses, getImageBufferImageJpegData, getImageBufferImageJpegErrors, getImageBufferImageJpegResponses, getImageBufferImageTiffData, getImageBufferImageTiffErrors, getImageBufferImageTiffResponses, getImageBufferStartCborData, getImageBufferStartCborErrors, getImageBufferStartCborResponses, getImageBufferStatusData, getImageBufferStatusErrors, getImageBufferStatusResponses, getImagePusherStatusData, getImagePusherStatusErrors, getImagePusherStatusResponses, getLiveEventsData, getLiveEventsErrors, getLiveEventsResponse, getLiveEventsResponses, getPreviewPedestalTiffData, getPreviewPedestalTiffErrors, getPreviewPedestalTiffResponses, getPreviewPlotBinData, getPreviewPlotBinErrors, getPreviewPlotBinResponses, getPreviewPlotData, getPreviewPlotErrors, getPreviewPlotResponses, getResultScanData, getResultScanErrors, getResultScanResponses, getRugnuxStatusData, getRugnuxStatusErrors, getRugnuxStatusResponses, getStatisticsCalibrationData, getStatisticsCalibrationResponses, getStatisticsData, getStatisticsDataCollectionData, getStatisticsDataCollectionErrors, getStatisticsDataCollectionResponses, getStatisticsResponses, getStatusData, getStatusResponses, getVersionData, getVersionResponses, getXfelEventCodeData, getXfelEventCodeErrors, getXfelEventCodeResponses, getXfelPulseIdData, getXfelPulseIdErrors, getXfelPulseIdResponses, postCancelData, postCancelResponses, postConfigImageFormatConversionData, postConfigImageFormatConversionErrors, postConfigImageFormatConversionResponses, postConfigImageFormatRawData, postConfigImageFormatRawErrors, postConfigImageFormatRawResponses, postDeactivateData, postDeactivateErrors, postDeactivateResponses, postImageBufferClearData, postImageBufferClearErrors, postImageBufferClearResponses, postInitializeData, postInitializeErrors, postInitializeResponses, postPedestalData, postPedestalErrors, postPedestalResponses, postStartData, postStartErrors, postStartResponses, postTriggerData, postTriggerResponses, postWaitTillDoneData, postWaitTillDoneErrors, postWaitTillDoneResponses, postWaitUntilRunningData, postWaitUntilRunningErrors, postWaitUntilRunningResponses, putConfigAzimIntData, putConfigAzimIntErrors, putConfigAzimIntResponses, putConfigBraggIntegrationData, putConfigBraggIntegrationErrors, putConfigBraggIntegrationResponses, putConfigDarkMaskData, putConfigDarkMaskErrors, putConfigDarkMaskResponses, putConfigDetectorData, putConfigDetectorErrors, putConfigDetectorResponses, putConfigFileWriterData, putConfigFileWriterErrors, putConfigFileWriterResponses, putConfigImageFormatData, putConfigImageFormatErrors, putConfigImageFormatResponses, putConfigIndexingData, putConfigIndexingErrors, putConfigIndexingResponses, putConfigInstrumentData, putConfigInstrumentErrors, putConfigInstrumentResponses, putConfigInternalGeneratorImageData, putConfigInternalGeneratorImageErrors, putConfigInternalGeneratorImageResponses, putConfigInternalGeneratorImageTiffData, putConfigInternalGeneratorImageTiffErrors, putConfigInternalGeneratorImageTiffResponses, putConfigRoiData, putConfigRoiErrors, putConfigRoiResponses, putConfigRugnuxData, putConfigRugnuxErrors, putConfigRugnuxModelData, putConfigRugnuxModelErrors, putConfigRugnuxModelResponses, putConfigRugnuxResponses, putConfigSelectDetectorData, putConfigSelectDetectorErrors, putConfigSelectDetectorResponses, putConfigSpotFindingData, putConfigSpotFindingErrors, putConfigSpotFindingResponses, putConfigUserMaskData, putConfigUserMaskErrors, putConfigUserMaskResponses, putConfigUserMaskTiffData, putConfigUserMaskTiffErrors, putConfigUserMaskTiffResponses, putConfigZeromqMetadataData, putConfigZeromqMetadataErrors, putConfigZeromqMetadataResponses, putConfigZeromqPreviewData, putConfigZeromqPreviewErrors, putConfigZeromqPreviewResponses } from './types.gen'; export type Options = Options2 & { /** @@ -103,6 +103,11 @@ export const postTrigger = (options?: Opti * Any frame that is currently being processed by CPU will be finished and sent to writer. * Given the command is making sure to gracefully stop data acquisition and detector, it might take some time to switch back after command finished to `Idle` state. * + * With rugnux live mode (`/config/rugnux`) the command also cuts the rugnux analysis, during the collection + * or after its last image alike: the collection ends with what the analysis has established + * (see `cut` in `/rugnux/status`). After the last image the collection itself is complete and is not + * marked as cancelled. + * * If data collection is not running, the command has no effect. * */ @@ -371,6 +376,76 @@ export const putConfigSelectDetector = (op } }); +/** + * Get rugnux live mode settings and model status + * + * Can be done anytime + */ +export const getConfigRugnux = (options?: Options): RequestResult => (options?.client ?? client).get({ + security: [{ scheme: 'bearer', type: 'http' }], + url: '/config/rugnux', + ...options +}); + +/** + * Change rugnux live mode settings + * + * This can only be done when detector is `Idle`, `Error` or `Inactive` states. + * + */ +export const putConfigRugnux = (options?: Options): RequestResult => (options?.client ?? client).put({ + security: [{ scheme: 'bearer', type: 'http' }], + url: '/config/rugnux', + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers + } +}); + +/** + * Remove the model used by rugnux live mode + * + * This can only be done when detector is `Idle`, `Error` or `Inactive` states. + * + */ +export const deleteConfigRugnuxModel = (options?: Options): RequestResult => (options?.client ?? client).delete({ + security: [{ scheme: 'bearer', type: 'http' }], + url: '/config/rugnux/model', + ...options +}); + +/** + * Upload the model used by rugnux live mode + * + * This can only be done when detector is `Idle`, `Error` or `Inactive` states. + * The model (PDB or mmCIF, optionally gzipped; at most 64 MiB) is used for molecular replacement + * and validation of every following rugnux live collection, until it is removed with + * `DELETE /config/rugnux/model` or replaced by another upload. + * + */ +export const putConfigRugnuxModel = (options?: Options): RequestResult => (options?.client ?? client).put({ + bodySerializer: null, + security: [{ scheme: 'bearer', type: 'http' }], + url: '/config/rugnux/model', + ...options, + headers: { + 'Content-Type': 'application/octet-stream', + ...options?.headers + } +}); + +/** + * Get live rugnux processing status of the last collection + * + * Can be done anytime + */ +export const getRugnuxStatus = (options?: Options): RequestResult => (options?.client ?? client).get({ + security: [{ scheme: 'bearer', type: 'http' }], + url: '/rugnux/status', + ...options +}); + /** * Get ZeroMQ preview settings */ @@ -648,6 +723,31 @@ export const getImageBufferStartCbor = (op ...options }); +/** + * Event stream of the collection, for a reader following it + * + * Server-sent events (text/event-stream) for the current collection, from the moment /start has + * accepted it: first its whole history, then every change, until it ends or a new collection + * begins. Only one reader at a time. Events, each with a JSON `data` line: + * - `start` {run_number, run_name, file_prefix, number_of_images, images_per_file, cbor} - first, + * once the start message is known: at once for a JUNGFRAU, when the detector sends its own for + * a DECTRIS. `cbor` is the start message the writer of the master file received, CBOR in base64; + * a reader builds the dataset from it; + * - `file` {file_number, file, first_image, image_count} - a data file closed and renamed into + * place; `file` is relative to the collection's root, as the writer named it; + * - `end` {images, error?} - every writer acknowledged the end: the data files and the master + * file are in place; `images` is the number of images collected. Also sent, with `error`, when + * the measurement ends any other way - before the start message, for one. The stream then closes; + * - `superseded` {run_number} - a new collection began. The stream then closes. + * A comment line (`: keepalive`) every 10 s when nothing happens. + * + */ +export const getLiveEvents = (options?: Options): Promise> => (options?.client ?? client).sse.get({ + security: [{ scheme: 'bearer', type: 'http' }], + url: '/live/events', + ...options +}); + /** * Get image message in CBOR format * diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts index 8338b2b5e..8b17d4515 100644 --- a/frontend/src/client/types.gen.ts +++ b/frontend/src/client/types.gen.ts @@ -1873,6 +1873,125 @@ export type jfjoch_settings = { zeromq_preview?: zeromq_preview_settings; zeromq_metadata?: zeromq_metadata_settings; dark_mask?: dark_mask_settings; + rugnux?: rugnux_settings; +}; + +/** + * Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux + * inside the broker while they arrive, and the merged result is sent to the writer. + * + */ +export type rugnux_settings = { + /** + * Process DECTRIS rotation collections with rugnux live + */ + enable?: boolean; + /** + * CPU threads left to the receiver and the image pusher while rugnux runs + */ + reserved_cpus?: number; + /** + * Fixed cutoff: this long after the last image the analysis is cut, and the collection ends + * with what it has established (as `POST /cancel` after the last image does). 0 means no cutoff. + * + */ + deadline_s?: number; +}; + +export type rugnux_status = { + /** + * Rugnux live mode is enabled + */ + enable: boolean; + /** + * CPU threads left to the receiver and the image pusher while rugnux runs + */ + reserved_cpus: number; + /** + * Seconds after the last image at which the analysis is cut; 0 means no cutoff + */ + deadline_s?: number; + /** + * A model was uploaded with `PUT /config/rugnux/model` + */ + model_loaded: boolean; + /** + * Size of the uploaded model file + */ + model_size_bytes?: number; +}; + +export type rugnux_live_step = { + title: string; + /** + * Start of the step, since the collection started + */ + started_s: number; + /** + * Set once the next step starts or the run ends + */ + duration_s?: number; + /** + * What the step found (e.g. lattice, space group) + */ + notes: Array; +}; + +export type rugnux_summary_row = { + label: string; + value: string; +}; + +/** + * Live rugnux processing of the last collection; kept until the next collection starts + */ +export type rugnux_live_status = { + state: 'Idle' | 'Running' | 'Done' | 'Failed' | 'Cancelled' | 'Skipped'; + /** + * Answer of the early lattice probe on the first 60 degrees; empty until it answers + */ + probe: string; + /** + * When the probe answered, since the collection started + */ + probe_s?: number; + /** + * Why the run failed or was skipped + */ + message: string; + images_expected: number; + images_received: number; + elapsed_s: number; + /** + * Fraction of the current pass's images + */ + step_progress?: number; + steps: Array; + /** + * When the last image arrived (and the cutoff timer started), since the collection started + */ + last_image_s?: number; + /** + * Empty, or why the analysis was cut: `deadline` (`deadline_s` after the last image) or `cancel` + * (`POST /cancel`). The end message then carried what was established at the cut. + * + */ + cut?: string; + /** + * When the analysis was cut, since the collection started + */ + cut_s?: number; + /** + * From the last image received to all results written + */ + last_image_to_written_s?: number; + /** + * OK, WARNINGS, UNUSABLE or FAILED; empty until the run is over + */ + verdict: string; + verdict_text: string; + summary: Array; + warnings: Array; }; export type zeromq_metadata_settings = { @@ -3060,6 +3179,149 @@ export type putConfigSelectDetectorResponses = { 200: unknown; }; +export type getConfigRugnuxData = { + body?: never; + path?: never; + query?: never; + url: '/config/rugnux'; +}; + +export type getConfigRugnuxErrors = { + /** + * The current dataset is protected and no valid bearer token was presented. + */ + 401: string; +}; + +export type getConfigRugnuxError = getConfigRugnuxErrors[keyof getConfigRugnuxErrors]; + +export type getConfigRugnuxResponses = { + /** + * Everything OK + */ + 200: rugnux_status; +}; + +export type getConfigRugnuxResponse = getConfigRugnuxResponses[keyof getConfigRugnuxResponses]; + +export type putConfigRugnuxData = { + body?: rugnux_settings; + path?: never; + query?: never; + url: '/config/rugnux'; +}; + +export type putConfigRugnuxErrors = { + /** + * Exception error + */ + 400: string; + /** + * The current dataset is protected and no valid bearer token was presented. + */ + 401: string; + /** + * Error within Jungfraujoch code - see output message. + */ + 500: error_message; +}; + +export type putConfigRugnuxError = putConfigRugnuxErrors[keyof putConfigRugnuxErrors]; + +export type putConfigRugnuxResponses = { + /** + * Everything OK + */ + 200: unknown; +}; + +export type deleteConfigRugnuxModelData = { + body?: never; + path?: never; + query?: never; + url: '/config/rugnux/model'; +}; + +export type deleteConfigRugnuxModelErrors = { + /** + * The current dataset is protected and no valid bearer token was presented. + */ + 401: string; + /** + * Error within Jungfraujoch code - see output message. + */ + 500: error_message; +}; + +export type deleteConfigRugnuxModelError = deleteConfigRugnuxModelErrors[keyof deleteConfigRugnuxModelErrors]; + +export type deleteConfigRugnuxModelResponses = { + /** + * All good + */ + 200: unknown; +}; + +export type putConfigRugnuxModelData = { + body?: Blob | File; + path?: never; + query?: never; + url: '/config/rugnux/model'; +}; + +export type putConfigRugnuxModelErrors = { + /** + * Empty body + */ + 400: unknown; + /** + * The current dataset is protected and no valid bearer token was presented. + */ + 401: string; + /** + * Model larger than 64 MiB + */ + 413: unknown; + /** + * Error within Jungfraujoch code - see output message. + */ + 500: error_message; +}; + +export type putConfigRugnuxModelError = putConfigRugnuxModelErrors[keyof putConfigRugnuxModelErrors]; + +export type putConfigRugnuxModelResponses = { + /** + * All good + */ + 200: unknown; +}; + +export type getRugnuxStatusData = { + body?: never; + path?: never; + query?: never; + url: '/rugnux/status'; +}; + +export type getRugnuxStatusErrors = { + /** + * The current dataset is protected and no valid bearer token was presented. + */ + 401: string; +}; + +export type getRugnuxStatusError = getRugnuxStatusErrors[keyof getRugnuxStatusErrors]; + +export type getRugnuxStatusResponses = { + /** + * Everything OK + */ + 200: rugnux_live_status; +}; + +export type getRugnuxStatusResponse = getRugnuxStatusResponses[keyof getRugnuxStatusResponses]; + export type getConfigZeromqPreviewData = { body?: never; path?: never; @@ -3738,6 +4000,39 @@ export type getImageBufferStartCborResponses = { export type getImageBufferStartCborResponse = getImageBufferStartCborResponses[keyof getImageBufferStartCborResponses]; +export type getLiveEventsData = { + body?: never; + path?: never; + query?: never; + url: '/live/events'; +}; + +export type getLiveEventsErrors = { + /** + * The current dataset is protected and no valid bearer token was presented. + */ + 401: string; + /** + * No collection has started + */ + 404: unknown; + /** + * Another reader is following the collection, or the image pusher does not report closed data files + */ + 409: unknown; +}; + +export type getLiveEventsError = getLiveEventsErrors[keyof getLiveEventsErrors]; + +export type getLiveEventsResponses = { + /** + * Event stream + */ + 200: string; +}; + +export type getLiveEventsResponse = getLiveEventsResponses[keyof getLiveEventsResponses]; + export type getImageBufferImageCborData = { body?: never; path?: never; diff --git a/frontend/src/client/zod.gen.ts b/frontend/src/client/zod.gen.ts index 2b962afbf..0b49a5242 100644 --- a/frontend/src/client/zod.gen.ts +++ b/frontend/src/client/zod.gen.ts @@ -792,6 +792,67 @@ export const zBraggIntegrationSettings = z.object({ max_hkl: z.int().gte(1).lte(511).optional().default(100) }); +/** + * Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux + * inside the broker while they arrive, and the merged result is sent to the writer. + * + */ +export const zRugnuxSettings = z.object({ + enable: z.boolean().optional().default(false), + reserved_cpus: z.coerce.bigint().gte(BigInt(0)).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).optional().default(BigInt(4)), + deadline_s: z.number().gte(0).optional().default(0) +}); + +export const zRugnuxStatus = z.object({ + enable: z.boolean(), + reserved_cpus: z.coerce.bigint().min(BigInt('-9223372036854775808'), { error: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }), + deadline_s: z.number().optional(), + model_loaded: z.boolean(), + model_size_bytes: z.coerce.bigint().min(BigInt('-9223372036854775808'), { error: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).optional() +}); + +export const zRugnuxLiveStep = z.object({ + title: z.string(), + started_s: z.number(), + duration_s: z.number().optional(), + notes: z.array(z.string()) +}); + +export const zRugnuxSummaryRow = z.object({ + label: z.string(), + value: z.string() +}); + +/** + * Live rugnux processing of the last collection; kept until the next collection starts + */ +export const zRugnuxLiveStatus = z.object({ + state: z.enum([ + 'Idle', + 'Running', + 'Done', + 'Failed', + 'Cancelled', + 'Skipped' + ]), + probe: z.string(), + probe_s: z.number().optional(), + message: z.string(), + images_expected: z.coerce.bigint().min(BigInt('-9223372036854775808'), { error: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }), + images_received: z.coerce.bigint().min(BigInt('-9223372036854775808'), { error: 'Invalid value: Expected int64 to be >= -9223372036854775808' }).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }), + elapsed_s: z.number(), + step_progress: z.number().gte(0).lte(1).optional(), + steps: z.array(zRugnuxLiveStep), + last_image_s: z.number().optional(), + cut: z.string().optional(), + cut_s: z.number().optional(), + last_image_to_written_s: z.number().optional(), + verdict: z.string(), + verdict_text: z.string(), + summary: z.array(zRugnuxSummaryRow), + warnings: z.array(z.string()) +}); + export const zZeromqMetadataSettings = z.object({ enabled: z.boolean().default(true), period_ms: z.coerce.bigint().gte(BigInt(1)).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).default(BigInt(1000)), @@ -857,7 +918,8 @@ export const zJfjochSettings = z.object({ image_pusher: zImagePusherType, zeromq_preview: zZeromqPreviewSettings.optional(), zeromq_metadata: zZeromqMetadataSettings.optional(), - dark_mask: zDarkMaskSettings.optional() + dark_mask: zDarkMaskSettings.optional(), + rugnux: zRugnuxSettings.optional() }); export const zDatasetSettingsWritable = z.object({ @@ -1186,6 +1248,20 @@ export const zGetConfigSelectDetectorResponse = zDetectorList; export const zPutConfigSelectDetectorBody = zDetectorSelection; +/** + * Everything OK + */ +export const zGetConfigRugnuxResponse = zRugnuxStatus; + +export const zPutConfigRugnuxBody = zRugnuxSettings; + +export const zPutConfigRugnuxModelBody = z.string(); + +/** + * Everything OK + */ +export const zGetRugnuxStatusResponse = zRugnuxLiveStatus; + /** * Everything OK */ @@ -1410,6 +1486,11 @@ export const zGetResultScanResponse = zScanResult; */ export const zGetImageBufferStartCborResponse = z.string(); +/** + * Event stream + */ +export const zGetLiveEventsResponse = z.string(); + export const zGetImageBufferImageCborQuery = z.object({ id: z.coerce.bigint().gte(BigInt(-2)).max(BigInt('9223372036854775807'), { error: 'Invalid value: Expected int64 to be <= 9223372036854775807' }).optional().default(BigInt(-1)) }); diff --git a/frontend/src/components/RugnuxPage.tsx b/frontend/src/components/RugnuxPage.tsx new file mode 100644 index 000000000..6dc22e4ac --- /dev/null +++ b/frontend/src/components/RugnuxPage.tsx @@ -0,0 +1,166 @@ +import {Alert, Box, Chip, LinearProgress, Paper, Stack, Typography} from "@mui/material"; +import {indigo, lime, deepOrange, grey} from "@mui/material/colors"; +import {useQuery} from "@tanstack/react-query"; +import {getConfigRugnuxOptions, getRugnuxStatusOptions} from "../client/@tanstack/react-query.gen"; +import {rugnux_live_status} from "../client"; +import {useTokenRequired} from "../auth"; +import EmptyState from "./EmptyState"; +import RugnuxTimeline from "./RugnuxTimeline"; +import RugnuxSettings from "./RugnuxSettings"; +import {CutAlert, lastImageTime, ProbeAlert, StepTable, VerdictAlert} from "./RugnuxParts"; +import BoltIcon from "@mui/icons-material/Bolt"; + +const STATE_COLOUR: Record = { + Running: {bg: indigo[500], fg: "#fff"}, + Done: {bg: lime[600], fg: "rgba(0,0,0,.87)"}, + Failed: {bg: deepOrange[700], fg: "#fff"}, + Cancelled: {bg: grey[500], fg: "#fff"}, + Skipped: {bg: grey[400], fg: "rgba(0,0,0,.87)"}, +}; + +// The summary rows rugnux writes; the ones a crystallographer looks at first get a stronger tile. +const KEY_ROW = /cell|space group|resolution|cc1\/2|r-?free|model/i; + +function Section({title, children}: { title: string, children: React.ReactNode }) { + return ( + + + {title} + + {children} + + ); +} + +function Header({s}: { s: rugnux_live_status }) { + const colour = STATE_COLOUR[s.state] ?? STATE_COLOUR.Skipped; + const latency = s.last_image_to_written_s; + const lastImage = lastImageTime(s); + return ( + + + + Rugnux live + + + + {s.images_received} / {s.images_expected} images + {lastImage !== undefined && ` · collected in ${lastImage.toFixed(1)} s`} + {` · ${s.elapsed_s.toFixed(1)} s since the first image`} + + {s.message && {s.message}} + + + + {latency !== undefined ? "Last image → all results written" : "Processing for"} + + + {(latency ?? s.elapsed_s).toFixed(1)} s + + + + ); +} + +function CurrentStep({s}: { s: rugnux_live_status }) { + const current = s.state === 'Running' && s.steps.length > 0 ? s.steps[s.steps.length - 1] : undefined; + if (!current) + return null; + return ( + + + {current.title} + {s.step_progress !== undefined && + {(s.step_progress * 100).toFixed(0)} %} + + + + ); +} + +function SummaryTiles({s}: { s: rugnux_live_status }) { + if (s.summary.length === 0) + return null; + return ( + + {s.summary.map((row, i) => { + const key = KEY_ROW.test(row.label) || KEY_ROW.test(row.value); + const short = row.value.length <= 24; + return ( + + + {row.label} + + + {row.value} + + + ); + })} + + ); +} + +function RugnuxPage({measuring}: { measuring: boolean }) { + const {data: s, isError} = useQuery({ + ...getRugnuxStatusOptions(), + refetchInterval: 1000, + }); + const {data: settings} = useQuery({...getConfigRugnuxOptions(), refetchInterval: 2000}); + const tokenRequired = useTokenRequired(); + + let live; + if (isError && tokenRequired) + live = } + text="Dataset token required (key button in the top bar)"/>; + else if (isError || s === undefined || s.state === 'Idle') + live = } + text="No rugnux run yet - start a rotation collection with live processing enabled"/>; + else + live = ( + +
+ + +
+ +
+ +
+ + +
+
+ {s.verdict + ? + : + {s.state === 'Running' ? "Results appear when the run is over." : "No result."} + } + + 0 ? 2 : 0}}> + {s.warnings.map((w, i) => {w})} + +
+
+ + ); + + return ( + + {live} + + + ); +} + +export default RugnuxPage; diff --git a/frontend/src/components/RugnuxParts.tsx b/frontend/src/components/RugnuxParts.tsx new file mode 100644 index 000000000..45d37a6a7 --- /dev/null +++ b/frontend/src/components/RugnuxParts.tsx @@ -0,0 +1,88 @@ +import {Alert, AlertTitle, Box, Paper, Table, TableBody, TableCell, TableContainer, TableRow, Typography} from "@mui/material"; +import {rugnux_live_status, rugnux_live_step} from "../client"; + +// Pieces of the rugnux live status shared by the dashboard card and the Rugnux page. + +export const verdictSeverity = (verdict: string) => { + switch (verdict) { + case "OK": + return "success"; + case "WARNINGS": + return "warning"; + default: + return "error"; + } +}; + +// When the last image arrived, in seconds since the collection started. Older brokers do not report +// it; there it follows from the latency (every result written `latency` after the last image). +export const lastImageTime = (s: rugnux_live_status) => + s.last_image_s ?? (s.last_image_to_written_s !== undefined ? s.elapsed_s - s.last_image_to_written_s : undefined); + +export function CutAlert({s}: { s: rugnux_live_status }) { + if (!s.cut) + return null; + return ( + + Analysis cut ({s.cut}){s.cut_s !== undefined && ` at ${s.cut_s.toFixed(1)} s`} - results as established + + ); +} + +export function ProbeAlert({s, large}: { s: rugnux_live_status, large?: boolean }) { + if (!s.probe) + return null; + const noLattice = s.probe.startsWith("no lattice"); + return ( + + {large && + Lattice probe{s.probe_s !== undefined && ` · ${s.probe_s.toFixed(1)} s after the first image`} + } + {s.probe}{!large && s.probe_s !== undefined && ` (at ${s.probe_s.toFixed(1)} s)`} + + ); +} + +export function VerdictAlert({s}: { s: rugnux_live_status }) { + if (!s.verdict) + return null; + return ( + + {s.verdict} + {s.verdict_text} + + ); +} + +const stepDuration = (step: rugnux_live_step) => + step.duration_s !== undefined ? step.duration_s.toFixed(1) + " s" : "running"; + +export function StepTable({steps}: { steps: rugnux_live_step[] }) { + if (steps.length === 0) + return null; + return ( + + + + {steps.map((step, i) => ( + + + {step.started_s.toFixed(1)} s + + + {step.title} + {step.notes.map((note, j) => + {note})} + + + {stepDuration(step)} + + + ))} + +
+
+ ); +} diff --git a/frontend/src/components/RugnuxSettings.tsx b/frontend/src/components/RugnuxSettings.tsx new file mode 100644 index 000000000..8fd76bbca --- /dev/null +++ b/frontend/src/components/RugnuxSettings.tsx @@ -0,0 +1,94 @@ +import {ChangeEvent, useEffect, useState} from 'react'; + +import {Button, FormControlLabel, Stack, Switch, Typography} from "@mui/material"; +import UploadFileIcon from "@mui/icons-material/UploadFile"; +import DeleteIcon from "@mui/icons-material/Delete"; +import {useQuery, useQueryClient} from "@tanstack/react-query"; +import { + deleteConfigRugnuxModelMutation, getConfigRugnuxOptions, getConfigRugnuxQueryKey, + putConfigRugnuxModelMutation, putConfigRugnuxMutation +} from "../client/@tanstack/react-query.gen"; +import NumberTextField from "./NumberTextField"; +import SettingsPanel, {UploadSnackbarView} from "./SettingsPanel"; +import {useUpload} from "./useUpload"; + +const sizeText = (bytes?: number) => + bytes === undefined ? "" : bytes >= 1024 * 1024 ? `${(bytes / 1024 / 1024).toFixed(1)} MiB` : `${(bytes / 1024).toFixed(0)} KiB`; + +// Rugnux live mode (enable, reserved CPUs) and the model used for MR, as two panels side by side. +function RugnuxSettings({measuring}: { measuring: boolean }) { + const queryClient = useQueryClient(); + const {data: s} = useQuery({...getConfigRugnuxOptions(), refetchInterval: 2000}); + const refresh = {onSuccess: () => queryClient.invalidateQueries({queryKey: getConfigRugnuxQueryKey()})}; + + const settings = useUpload({...putConfigRugnuxMutation(), ...refresh}); + const upload = useUpload({...putConfigRugnuxModelMutation(), ...refresh}); + const remove = useUpload({...deleteConfigRugnuxModelMutation(), ...refresh}); + + const [enable, setEnable] = useState(false); + const [cpus, setCpus] = useState(4); + const [cpusError, setCpusError] = useState(false); + const [deadline, setDeadline] = useState(0); + const [deadlineError, setDeadlineError] = useState(false); + const [counter, setCounter] = useState(0); + const [file, setFile] = useState(); + + useEffect(() => { + if (s !== undefined) { + setEnable(s.enable); + setCpus(s.reserved_cpus); + setDeadline(s.deadline_s ?? 0); + setCounter(c => c + 1); + } + }, [s?.enable, s?.reserved_cpus, s?.deadline_s]); + + const dirty = s !== undefined && (enable !== s.enable || cpus !== s.reserved_cpus || deadline !== (s.deadline_s ?? 0)); + + return ( + + settings.submit({enable, reserved_cpus: cpus, deadline_s: deadline})} + uploadDisabled={measuring || cpusError || deadlineError || settings.pending} + snackbar={settings.snackbar}> + setEnable(e.target.checked)}/>}/> + { setCpus(v); setCpusError(err); }}/> + { setDeadline(v); setDeadlineError(err); }}/> + + + + + {s?.model_loaded ? `Model loaded · ${sizeText(s.model_size_bytes)}` : "No model loaded"} + + + PDB or mmCIF, optionally gzipped, up to 64 MiB. Kept until removed. + + + + + + + + + + + ); +} + +export default RugnuxSettings; diff --git a/frontend/src/components/RugnuxStatus.tsx b/frontend/src/components/RugnuxStatus.tsx new file mode 100644 index 000000000..e54a3f4ab --- /dev/null +++ b/frontend/src/components/RugnuxStatus.tsx @@ -0,0 +1,62 @@ +import {memo} from 'react'; + +import {Alert, Box, LinearProgress, Typography} from "@mui/material"; +import {useQuery} from "@tanstack/react-query"; +import {getRugnuxStatusOptions} from "../client/@tanstack/react-query.gen"; +import {useTokenRequired} from "../auth"; +import SettingsPanel from "./SettingsPanel"; +import {CutAlert, ProbeAlert, StepTable, VerdictAlert} from "./RugnuxParts"; + +// Live rugnux processing of the last collection, as a dashboard card: the step it is on, the steps +// behind it with what they found, and once it is over the verdict. Nothing while rugnux has not run. +function RugnuxStatus() { + const {data: s, isError} = useQuery({ + ...getRugnuxStatusOptions(), + refetchInterval: 1000, + }); + const tokenRequired = useTokenRequired(); + + if (isError && tokenRequired) + return ( + + Dataset token required (key button in the top bar) + + ); + if (isError || s === undefined || s.state === 'Idle') + return null; + + const current = s.state === 'Running' && s.steps.length > 0 ? s.steps[s.steps.length - 1] : undefined; + + return ( + + + {s.state} · {s.images_received} / {s.images_expected} images · {s.elapsed_s.toFixed(0)} s + + {s.message && {s.message}} + + + + {current && ( + + {current.title} + + + )} + + {s.last_image_to_written_s !== undefined && ( + + Last image → all written: {s.last_image_to_written_s.toFixed(1)} s + + )} + + + + {s.warnings.map((w, i) => ( + {w} + ))} + + ); +} + +export default memo(RugnuxStatus); diff --git a/frontend/src/components/RugnuxTimeline.tsx b/frontend/src/components/RugnuxTimeline.tsx new file mode 100644 index 000000000..2eccfc1a1 --- /dev/null +++ b/frontend/src/components/RugnuxTimeline.tsx @@ -0,0 +1,174 @@ +import {Box, Tooltip, Typography} from "@mui/material"; +import {keyframes} from "@mui/system"; +import {indigo, lime, lightBlue, orange, grey, deepOrange} from "@mui/material/colors"; +import {rugnux_live_status} from "../client"; +import {lastImageTime} from "./RugnuxParts"; + +// The overlap of collection and processing on one time axis (seconds since the first image): the +// detector lane fills while images arrive, the rugnux lane shows its steps where they ran, and the +// gap between "last image" and "all written" is the headline latency. + +const stripes = keyframes` + from { background-position: 0 0; } + to { background-position: 40px 0; } +`; + +const runningSx = { + backgroundImage: "linear-gradient(135deg, rgba(255,255,255,.28) 25%, transparent 25%, transparent 50%, " + + "rgba(255,255,255,.28) 50%, rgba(255,255,255,.28) 75%, transparent 75%, transparent)", + backgroundSize: "40px 40px", + animation: `${stripes} 1s linear infinite`, +}; + +const STEP_COLOURS = [indigo[500], indigo[300], indigo[700], lightBlue[700], indigo[400], lightBlue[900]]; + +const LABEL_WIDTH = 120; +const LANE_HEIGHT = 64; +const AXIS_HEIGHT = 28; + +function niceStep(span: number) { + for (const s of [1, 2, 5, 10, 20, 30, 60, 120]) + if (span / s <= 12) + return s; + return 300; +} + +function Marker({x, colour, label, top, dashed}: { x: number, colour: string, label: string, top: number, dashed?: boolean }) { + return ( + + + + {label} + + + ); +} + +// deadline_s: the configured cutoff after the last image, 0 = none +function RugnuxTimeline({s, deadline_s}: { s: rugnux_live_status, deadline_s: number }) { + const lastImage = lastImageTime(s); + const collecting = lastImage === undefined && s.state === 'Running'; + const collectionEnd = lastImage ?? s.elapsed_s; + const rate = collectionEnd > 0 ? s.images_received / collectionEnd : 0; + const expectedEnd = rate > 0 ? s.images_expected / rate : 0; + + const deadline = lastImage !== undefined && deadline_s > 0 ? lastImage + deadline_s : undefined; + const span = Math.max(s.elapsed_s, expectedEnd, deadline ?? 0, 10) * 1.06; + const pct = (t: number) => 100 * Math.min(Math.max(t, 0), span) / span; + const tick = niceStep(span); + const ticks = Array.from({length: Math.floor(span / tick) + 1}, (_, i) => i * tick); + + const done = s.state !== 'Running'; + const latency = s.last_image_to_written_s; + + return ( + + + {["Detector", "Rugnux"].map((l, i) => ( + + {l} + + ))} + + + + {/* grid lines */} + {ticks.map(t => ( + + ))} + + {/* detector lane */} + + + + {s.images_received} / {s.images_expected} images{rate > 0 && ` · ${rate.toFixed(0)} Hz`} + + + {collecting && expectedEnd > collectionEnd && ( + + )} + + + {/* rugnux lane */} + + {s.steps.map((step, i) => { + const open = step.duration_s === undefined; + const end = open ? s.elapsed_s : step.started_s + step.duration_s!; + const width = Math.max(pct(end) - pct(step.started_s), 0.4); + return ( + + {step.title} · {open ? "running" : `${step.duration_s!.toFixed(1)} s`} + {step.notes.map((n, j) =>
{n}
)} +
}> + + + {step.title} + + + + ); + })} +
+ + {/* axis */} + + {ticks.map(t => ( + + {t} s + + ))} + + + {/* markers */} + {s.probe_s !== undefined && ( + + )} + {lastImage !== undefined && ( + + )} + {deadline !== undefined && s.cut !== 'deadline' && ( + + )} + {s.cut && s.cut_s !== undefined && ( + + )} + {!done && } + + {/* latency bracket */} + {latency !== undefined && lastImage !== undefined && ( + + + +{latency.toFixed(1)} s → all written + + + )} +
+
+ ); +} + +export default RugnuxTimeline; diff --git a/reader/CMakeLists.txt b/reader/CMakeLists.txt index de234d23f..0d6350514 100644 --- a/reader/CMakeLists.txt +++ b/reader/CMakeLists.txt @@ -19,6 +19,8 @@ ADD_LIBRARY(JFJochReader STATIC HDF5ImageLocator.cpp JFJochStartMessageReader.cpp JFJochStartMessageReader.h + JFJochImageBufferReader.cpp + JFJochImageBufferReader.h HDF5ImageLocator.h HDF5ImageSource.cpp HDF5ImageSource.h diff --git a/reader/JFJochImageBufferReader.cpp b/reader/JFJochImageBufferReader.cpp new file mode 100644 index 000000000..a7ca4e2cc --- /dev/null +++ b/reader/JFJochImageBufferReader.cpp @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "JFJochImageBufferReader.h" + +#include +#include + +#include "JFJochStartMessageReader.h" +#include "../common/JFJochException.h" +#include "../frame_serialize/CBORStream2Deserializer.h" + +JFJochImageBufferReader::JFJochImageBufferReader(ImageBuffer &buffer) : buffer_(buffer) {} + +void JFJochImageBufferReader::Open(const StartMessage &start) { + number_of_images_ = start.number_of_images; + ended_ = false; + SetStartMessage(DatasetFromStartMessage(start, default_experiment)); +} + +void JFJochImageBufferReader::Ended() { + ended_ = true; +} + +uint64_t JFJochImageBufferReader::GetNumberOfImages() const { + return number_of_images_; +} + +void JFJochImageBufferReader::Close() { + SetStartMessage({}); +} + +bool JFJochImageBufferReader::WaitForMessage(int64_t image_number, std::vector &cbor) { + if (image_number < 0 || static_cast(image_number) >= number_of_images_) + throw JFJochException(JFJochExceptionCategory::ArrayOutOfBounds, "Image out of bounds"); + // The buffer has no notification of its own; at 100 Hz a millisecond's poll costs nothing. + while (!buffer_.GetImage(cbor, image_number)) { + if (ended_) + return buffer_.GetImage(cbor, image_number); // one that came in just before the end + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + return true; +} + +bool JFJochImageBufferReader::ReadRawImage(int64_t image_number, JFJochReaderRawImage &ret) { + if (!WaitForMessage(image_number, ret.read_buffer)) + return false; + const auto msg = CBORStream2Deserialize(ret.read_buffer); + if (msg->msg_type != CBORImageType::IMAGE || !msg->data_message) + return false; + // Copied out of the message: read_buffer is scratch a caller may hand to the next read. + const CompressedImage &image = msg->data_message->image; + ret.image_buffer.resize(image.GetCompressedSize()); + memcpy(ret.image_buffer.data(), image.GetCompressed(), image.GetCompressedSize()); + ret.image = CompressedImage(ret.image_buffer.data(), ret.image_buffer.size(), image.GetWidth(), + image.GetHeight(), image.GetMode(), image.GetCompressionAlgorithm()); + return true; +} + +bool JFJochImageBufferReader::LoadImage_i(std::shared_ptr &dataset, DataMessage &message, + std::vector &buffer, int64_t image_number, bool) { + if (!dataset || !WaitForMessage(image_number, buffer)) + return false; + const auto msg = CBORStream2Deserialize(buffer); + if (msg->msg_type != CBORImageType::IMAGE || !msg->data_message) + return false; + message = *msg->data_message; + return true; +} diff --git a/reader/JFJochImageBufferReader.h b/reader/JFJochImageBufferReader.h new file mode 100644 index 000000000..5a7c5203d --- /dev/null +++ b/reader/JFJochImageBufferReader.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include +#include +#include + +#include "JFJochReader.h" +#include "../common/ImageBuffer.h" + +// Reads a collection inside the broker while it arrives: the dataset comes from its start message, +// the images from the broker's image buffer, where the receiver puts each one as it forwards it. A +// read of an image not there yet waits for it; after Ended it returns false for an image that never +// came. The buffer must hold the whole collection (slots >= images) - a slot taken again for a later +// image would hand out that image instead. +class JFJochImageBufferReader : public JFJochReader { +public: + explicit JFJochImageBufferReader(ImageBuffer &buffer); + + void Open(const StartMessage &start); + // No image comes after this: the collection is over, or abandoned. + void Ended(); + + [[nodiscard]] uint64_t GetNumberOfImages() const override; + void Close() override; + bool ReadRawImage(int64_t image_number, JFJochReaderRawImage &image) override; + std::vector ReadSpots(int64_t image) const override { return {}; } + +private: + bool LoadImage_i(std::shared_ptr &dataset, DataMessage &message, + std::vector &buffer, int64_t image_number, bool update_dataset) override; + // The image's CBOR message, as the receiver put it in the buffer; false when it never came. + bool WaitForMessage(int64_t image_number, std::vector &cbor); + + ImageBuffer &buffer_; + uint64_t number_of_images_ = 0; + std::atomic ended_ = false; +}; diff --git a/reader/JFJochStartMessageReader.cpp b/reader/JFJochStartMessageReader.cpp index eb8f768f4..f0c5fc14a 100644 --- a/reader/JFJochStartMessageReader.cpp +++ b/reader/JFJochStartMessageReader.cpp @@ -15,78 +15,77 @@ namespace { const std::string prefix = std::filesystem::path(start.file_prefix).filename().string(); return fmt::format("{:s}_data_{:06d}.h5", prefix, file_number + 1); } +} - // The dataset as the start message describes it - the same quantities an HDF5 master states, taken - // from the message the writer wrote it from. - std::shared_ptr Dataset(const StartMessage &start, const DiffractionExperiment &defaults) { - auto dataset = std::make_shared(); - dataset->arm_date = start.arm_date; - dataset->jfjoch_release = start.jfjoch_release; - dataset->error_value = start.error_value; - dataset->file_detect_ice_rings = start.detect_ice_rings; +std::shared_ptr DatasetFromStartMessage(const StartMessage &start, + const DiffractionExperiment &defaults) { + auto dataset = std::make_shared(); + dataset->arm_date = start.arm_date; + dataset->jfjoch_release = start.jfjoch_release; + dataset->error_value = start.error_value; + dataset->file_detect_ice_rings = start.detect_ice_rings; - auto &x = dataset->experiment; - x = defaults; - // The reader hands every image out as signed 32-bit, as JFJochHDF5Reader does. - x.BitDepthImage(32); - x.PixelSigned(true); - x.FilePrefix(std::filesystem::path(start.file_prefix).filename().string()); - x.BeamX_pxl(start.beam_center_x); - x.BeamY_pxl(start.beam_center_y); - x.DetectorDistance_mm(start.detector_distance * 1000.0f); - x.PoniRot1_rad(start.poni_rot1.value_or(0.0f)); - x.PoniRot2_rad(start.poni_rot2.value_or(0.0f)); - x.PoniRot3_rad(start.poni_rot3.value_or(0.0f)); - x.IncidentEnergy_keV(WVL_1A_IN_KEV / start.incident_wavelength); - if (start.incident_wavelength_spread) - x.BandwidthFWHM(start.incident_wavelength_spread.value() / start.incident_wavelength); - x.DetectIceRings(start.detect_ice_rings.value_or(false)); + auto &x = dataset->experiment; + x = defaults; + // The reader hands every image out as signed 32-bit, as JFJochHDF5Reader does. + x.BitDepthImage(32); + x.PixelSigned(true); + x.FilePrefix(std::filesystem::path(start.file_prefix).filename().string()); + x.BeamX_pxl(start.beam_center_x); + x.BeamY_pxl(start.beam_center_y); + x.DetectorDistance_mm(start.detector_distance * 1000.0f); + x.PoniRot1_rad(start.poni_rot1.value_or(0.0f)); + x.PoniRot2_rad(start.poni_rot2.value_or(0.0f)); + x.PoniRot3_rad(start.poni_rot3.value_or(0.0f)); + x.IncidentEnergy_keV(WVL_1A_IN_KEV / start.incident_wavelength); + if (start.incident_wavelength_spread) + x.BandwidthFWHM(start.incident_wavelength_spread.value() / start.incident_wavelength); + x.DetectIceRings(start.detect_ice_rings.value_or(false)); - DetectorSetup detector = DetDECTRIS(start.image_size_x, start.image_size_y, start.detector_description, {}); - detector.PixelSize_um(start.pixel_size_x * 1e6f); - detector.MirrorY(start.mirror_y); - detector.ImageOrientation(DetectorOrientation(start.detector_orientation_mirror_y, - start.detector_orientation_quarter_turns)); - detector.SensorThickness_um(start.sensor_thickness * 1e6f); - detector.SensorMaterial(start.sensor_material); - detector.SaturationLimit(SaturationLimitFromValue(start.saturation_value)); - detector.BitDepthImage(32); - detector.MinFrameTime(std::chrono::microseconds(0)); - detector.MinCountTime(std::chrono::microseconds(0)); - detector.ReadOutTime(std::chrono::nanoseconds(0)); - x.Detector(detector); + DetectorSetup detector = DetDECTRIS(start.image_size_x, start.image_size_y, start.detector_description, {}); + detector.PixelSize_um(start.pixel_size_x * 1e6f); + detector.MirrorY(start.mirror_y); + detector.ImageOrientation(DetectorOrientation(start.detector_orientation_mirror_y, + start.detector_orientation_quarter_turns)); + detector.SensorThickness_um(start.sensor_thickness * 1e6f); + detector.SensorMaterial(start.sensor_material); + detector.SaturationLimit(SaturationLimitFromValue(start.saturation_value)); + detector.BitDepthImage(32); + detector.MinFrameTime(std::chrono::microseconds(0)); + detector.MinCountTime(std::chrono::microseconds(0)); + detector.ReadOutTime(std::chrono::nanoseconds(0)); + x.Detector(detector); - x.FrameTime(std::chrono::duration_cast(std::chrono::duration(start.frame_time)), - std::chrono::duration_cast(std::chrono::duration(start.count_time))); + x.FrameTime(std::chrono::duration_cast(std::chrono::duration(start.frame_time)), + std::chrono::duration_cast(std::chrono::duration(start.count_time))); - x.Goniometer(start.goniometer); - x.GridScan(start.grid_scan); - x.Smargon(start.smargon_position); - x.NumTriggers(1); - x.ImagesPerTrigger(static_cast(start.number_of_images)); + x.Goniometer(start.goniometer); + x.GridScan(start.grid_scan); + x.Smargon(start.smargon_position); + x.NumTriggers(1); + x.ImagesPerTrigger(static_cast(start.number_of_images)); - InstrumentMetadata instrument; - instrument.SourceName(start.source_name); - instrument.SourceType(start.source_type); - instrument.InstrumentName(start.instrument_name); - x.ImportInstrumentMetadata(instrument); - x.SampleName(start.sample_name); - x.SampleTemperature_K(start.sample_temperature_K); - x.RingCurrent_mA(start.ring_current_mA); - x.TotalFlux(start.total_flux); - x.AttenuatorTransmission(start.attenuator_transmission); - if (start.beam_size_x && start.beam_size_y) { - x.BeamSizeX_um(start.beam_size_x.value() * 1e6f); - x.BeamSizeY_um(start.beam_size_y.value() * 1e6f); - } - - if (!start.pixel_mask.empty()) - dataset->pixel_mask = std::make_shared(start.pixel_mask.begin()->second); - else - dataset->pixel_mask = std::make_shared( - std::vector(start.image_size_x * start.image_size_y)); - return dataset; + InstrumentMetadata instrument; + instrument.SourceName(start.source_name); + instrument.SourceType(start.source_type); + instrument.InstrumentName(start.instrument_name); + x.ImportInstrumentMetadata(instrument); + x.SampleName(start.sample_name); + x.SampleTemperature_K(start.sample_temperature_K); + x.RingCurrent_mA(start.ring_current_mA); + x.TotalFlux(start.total_flux); + x.AttenuatorTransmission(start.attenuator_transmission); + if (start.beam_size_x && start.beam_size_y) { + x.BeamSizeX_um(start.beam_size_x.value() * 1e6f); + x.BeamSizeY_um(start.beam_size_y.value() * 1e6f); } + + if (!start.pixel_mask.empty()) + dataset->pixel_mask = std::make_shared(start.pixel_mask.begin()->second); + else + dataset->pixel_mask = std::make_shared( + std::vector(start.image_size_x * start.image_size_y)); + return dataset; } void JFJochStartMessageReader::Open(const StartMessage &start, const std::string &master_path) { @@ -114,7 +113,7 @@ void JFJochStartMessageReader::Open(const StartMessage &start, const std::string ended_ = false; lost_.clear(); } - SetStartMessage(Dataset(start, default_experiment)); + SetStartMessage(DatasetFromStartMessage(start, default_experiment)); } void JFJochStartMessageReader::FileClosed(uint64_t file_number, uint64_t image_count) { diff --git a/reader/JFJochStartMessageReader.h b/reader/JFJochStartMessageReader.h index 75d943f69..064c6d58b 100644 --- a/reader/JFJochStartMessageReader.h +++ b/reader/JFJochStartMessageReader.h @@ -19,6 +19,11 @@ public: explicit LiveCollectionLost(const std::string &what) : std::runtime_error(what) {} }; +// The dataset as the start message describes it - the same quantities an HDF5 master states, taken +// from the message the writer wrote it from. +std::shared_ptr DatasetFromStartMessage(const StartMessage &start, + const DiffractionExperiment &defaults); + // Reads a collection while it is being written: the dataset comes from its start message, as the // broker sends it, and the images straight from the data files - /entry/data/data in each - once the // broker reports a file closed and renamed into place (FileClosed). A read of an image whose file has diff --git a/receiver/CMakeLists.txt b/receiver/CMakeLists.txt index 228f49597..1cfd1aa69 100644 --- a/receiver/CMakeLists.txt +++ b/receiver/CMakeLists.txt @@ -11,7 +11,9 @@ ADD_LIBRARY(JFJochReceiver STATIC JFJochReceiverCurrentStatus.h JFJochReceiverOutput.h JFJochReceiverLite.h JFJochReceiverLite.cpp + JFJochReceiverRugnux.h JFJochReceiverRugnux.cpp + RugnuxLiveStatus.h RugnuxLiveStatus.cpp JFJochReceiver.cpp JFJochReceiver.h) -TARGET_LINK_LIBRARIES(JFJochReceiver ImagePusher JFJochImagePuller JFJochImageAnalysis JFJochAcquisitionDevice JFJochCommon JFJochHLSSimulation JFJochPreview) +TARGET_LINK_LIBRARIES(JFJochReceiver ImagePusher JFJochImagePuller JFJochImageAnalysis JFJochAcquisitionDevice JFJochCommon JFJochHLSSimulation JFJochPreview JFJochRugnux) diff --git a/receiver/JFJochReceiver.cpp b/receiver/JFJochReceiver.cpp index abb36ea42..42910ecb1 100644 --- a/receiver/JFJochReceiver.cpp +++ b/receiver/JFJochReceiver.cpp @@ -197,6 +197,7 @@ void JFJochReceiver::SendEndMessage() { message.adu_histogram["module" + std::to_string(i)] = adu_histogram_module[i]->GetHistogram(); scan_result.FillEndMessage(message); + AddToEndMessage(message); if (push_images_to_writer) { if (!image_pusher.EndDataCollection(message)) diff --git a/receiver/JFJochReceiver.h b/receiver/JFJochReceiver.h index d65f6235c..93fb213ae 100644 --- a/receiver/JFJochReceiver.h +++ b/receiver/JFJochReceiver.h @@ -96,6 +96,9 @@ protected: void SendStartMessage(); void SendEndMessage(); void SaveStartMessageToImageBuffer(const StartMessage &msg); + // The last word on the end message before it is sent: a receiver whose analysis is not the + // per-image one fills in what it found. + virtual void AddToEndMessage(EndMessage &message) {} virtual void Cancel(const JFJochException &e); public: JFJochReceiver(const DiffractionExperiment& experiment, diff --git a/receiver/JFJochReceiverRugnux.cpp b/receiver/JFJochReceiverRugnux.cpp new file mode 100644 index 000000000..b876253f2 --- /dev/null +++ b/receiver/JFJochReceiverRugnux.cpp @@ -0,0 +1,482 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "JFJochReceiverRugnux.h" + +#include +#include +#include +#include +#include + +#include "spdlog/fmt/fmt.h" + +#include "JFJochReceiverLite.h" +#include "../common/ParallelFor.h" +#include "../frame_serialize/CBORStream2Deserializer.h" +#include "../rugnux/RugnuxLive.h" +#include "../rugnux/ResultReport.h" + +using namespace std::chrono_literals; + +namespace { + int64_t SteadyNowNs() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); + } + + double SecondsSince(int64_t ns) { + return static_cast(SteadyNowNs() - ns) / 1e9; + } + + // A run reachable for Cut while it exists: registered in `slot` (under `m`, cancelled at once where the + // cut came first) and taken off again before it is destroyed, an exception included. + class Reachable { + std::mutex &m; + Rugnux *&slot; + public: + Reachable(std::mutex &m, Rugnux *&slot, Rugnux &run, const bool &cut, const std::atomic &cancelled) + : m(m), slot(slot) { + std::unique_lock ul(m); + slot = &run; + if (cut || cancelled) + run.Cancel(); + } + ~Reachable() { + std::unique_lock ul(m); + slot = nullptr; + } + }; + + // What rugnux reports as it goes: its per-image results into the receiver's plots and scan + // result, which a later pass over the same image overwrites - so the canonical pass, the last to + // read every image, is what stays - and its steps into the log. + class LiveObserver : public RugnuxObserver { + JFJochReceiverPlots &plots; + ScanResultGenerator &scan_result; + const AzimuthalIntegrationMapping &mapping; + RugnuxLiveStatus &status; + Logger &logger; + public: + LiveObserver(JFJochReceiverPlots &plots, ScanResultGenerator &scan_result, + const AzimuthalIntegrationMapping &mapping, RugnuxLiveStatus &status, Logger &logger) + : plots(plots), scan_result(scan_result), mapping(mapping), status(status), logger(logger) {} + + void OnImageProcessed(const DataMessage &msg) override { + plots.Add(msg, AzimuthalIntegrationProfile(mapping)); + scan_result.Add(msg); + } + void OnProgress(uint64_t done, uint64_t total) override { status.Progress(done, total); } + void OnStep(const std::string &title) override { + status.Step(title); + logger.Info("rugnux: {}", title); + } + void OnNote(const std::string &text) override { + status.Note(text); + logger.Info("rugnux: {}", text); + } + }; +} + +JFJochReceiverRugnux::JFJochReceiverRugnux(const DiffractionExperiment &in_experiment, + const PixelMask &in_pixel_mask, + ImagePuller &in_image_puller, + ImagePusher &in_image_pusher, + Logger &in_logger, + int64_t in_forward_nthreads, + int in_rugnux_nthreads, + const std::string &in_model_path, + double in_deadline_s, + RugnuxLiveStatus &in_live_status, + const SpotFindingSettings &in_spot_finding_settings, + JFJochReceiverCurrentStatus &in_current_status, + JFJochReceiverPlots &in_plots, + ImageBuffer &in_image_buffer, + ZMQPreviewSocket *in_zmq_preview_socket, + ZMQMetadataSocket *in_zmq_metadata_socket) + : JFJochReceiver(in_experiment, in_image_buffer, in_image_pusher, in_current_status, in_plots, + in_spot_finding_settings, in_logger, in_pixel_mask, in_zmq_preview_socket, + in_zmq_metadata_socket, nullptr), + image_puller(in_image_puller), + reader(in_image_buffer), + forward_nthreads(JFJochReceiverLite::NumberOfDataAnalysisThreads(in_forward_nthreads, in_experiment)), + rugnux_nthreads(in_rugnux_nthreads), + model_path(in_model_path), + deadline_s(in_deadline_s), + live_status(in_live_status), + measurement_started(1), + forward_started(forward_nthreads) { + logger.Info("Starting {} forward threads; rugnux runs on {} threads while the images arrive", + forward_nthreads, rugnux_nthreads); + for (int i = 0; i < forward_nthreads; i++) + forward_futures.emplace_back(std::async(std::launch::async, &JFJochReceiverRugnux::ForwardThread, this, i)); + measurement = std::async(std::launch::async, &JFJochReceiverRugnux::MeasurementThread, this); + forward_started.wait(); +} + +JFJochReceiverRugnux::~JFJochReceiverRugnux() { + cancelled = true; + reader.Ended(); + { + std::unique_lock ul(rugnux_m); + if (rugnux != nullptr) + rugnux->Cancel(); + if (probe != nullptr) + probe->Cancel(); + } + try { + if (measurement.valid()) + measurement.get(); + } catch (const std::exception &e) { + logger.Error("ReceiverRugnux teardown failed: {}", e.what()); + } +} + +void JFJochReceiverRugnux::MeasurementThread() { + std::future rugnux_done, probe_done; + try { + // Waiting for the start message, as JFJochReceiverLite::MeasurementThread. + std::optional give_up_at; + auto msg = image_puller.PollImage(); + while (!cancelled && (!msg.has_value() || !msg->cbor || !msg->cbor->start_message)) { + if (detector_finished && !give_up_at) + give_up_at = std::chrono::steady_clock::now() + StartMessageGrace; + if (give_up_at && (std::chrono::steady_clock::now() > *give_up_at)) { + logger.Error("Detector finished, but no start message arrived - abandoning measurement"); + cancelled = true; + break; + } + msg = image_puller.PollImage(); + } + if (cancelled) { + current_status.SetProgress({}); + current_status.SetStatus(GetStatus()); + measurement_started.count_down(); + return; + } + + Configure(msg->cbor->start_message.value()); + image_buffer.StartMeasurement(experiment); + start_time = std::chrono::system_clock::now(); + SendStartMessage(); + + // Rugnux reads the images back from the buffer, so it needs one slot per image: a slot taken + // again would hand it a later image. Without that the collection is only forwarded. + const auto slots = image_buffer.GetStatus().total_slots; + live_status.Start(experiment.GetImageNum()); + if (slots < experiment.GetImageNum()) { + logger.Error("Image buffer holds {} images, the collection has {}: no rugnux analysis for it", + slots, experiment.GetImageNum()); + live_status.Finish(RugnuxLiveState::Skipped, "the image buffer cannot hold the whole collection"); + } else { + // The start message as the writer gets it, so rugnux sees the dataset an HDF5 master states. + std::vector start_cbor; + image_buffer.GetStartMessage(start_cbor); + reader.Open(CBORStream2Deserialize(start_cbor)->start_message.value()); + char dir_template[] = "/tmp/jfjoch_rugnux_XXXXXX"; + if (mkdtemp(dir_template) == nullptr) + throw JFJochException(JFJochExceptionCategory::FileWriteError, "Cannot create rugnux scratch directory"); + scratch_dir = dir_template; + rugnux_done = std::async(std::launch::async, &JFJochReceiverRugnux::RugnuxThread, this); + probe_done = std::async(std::launch::async, &JFJochReceiverRugnux::ProbeThread, this); + } + measurement_started.count_down(); + } catch (const JFJochException &e) { + logger.ErrorException(e); + Cancel(e); + measurement_started.count_down(); + throw; + } + + logger.Info("Receiving started"); + for (auto &f: forward_futures) + f.get(); + reader.Ended(); + images_done = true; + live_status.LastImage(); + const int64_t last_image = last_image_ns.load(); + logger.Info("Receiving finished, {} images", images_collected.load()); + + if (rugnux_done.valid()) { + // The fixed cutoff: the analysis gets deadline_s after the last image, then gives what it has. + if (deadline_s > 0 && rugnux_done.wait_for(std::chrono::duration(deadline_s)) + == std::future_status::timeout) + Cut("deadline"); + rugnux_done.wait(); + logger.Info("rugnux live: done {:.2f} s after the last image", SecondsSince(last_image)); + } + if (probe_done.valid()) + probe_done.wait(); + + current_status.SetProgress(1.0); + current_status.SetStatus(GetStatus()); + + SendEndMessage(); + const double end_sent_s = SecondsSince(last_image); + + if (!image_buffer.CheckIfBufferReturned(SendFinalizeTimeout)) { + logger.Error("Send commands not finalized in {} seconds", SendFinalizeTimeout.count()); + throw JFJochException(JFJochExceptionCategory::ZeroMQ, "Send commands not finalized in " + + std::to_string(SendFinalizeTimeout.count()) + " seconds"); + } + if (push_images_to_writer) + writer_error = image_pusher.Finalize(); + if (last_image > 0 && rugnux_done.valid()) { + const double written_s = SecondsSince(last_image); + live_status.Latency(written_s); + logger.Info("rugnux live: last image -> all written {:.2f} s (end message sent at {:.2f} s)", + written_s, end_sent_s); + } + + if (!scratch_dir.empty()) { + std::error_code ec; + std::filesystem::remove_all(scratch_dir, ec); + } + end_time = std::chrono::system_clock::now(); + current_status.SetProgress({}); + current_status.SetStatus(GetStatus()); +} + +void JFJochReceiverRugnux::RugnuxThread() { + // Below the forward threads: every thread rugnux starts inherits this, so an image arriving finds a + // core however busy rugnux keeps the machine. + // The process-wide ParallelFor pool is not started by this thread, so it is lowered separately. + setpriority(PRIO_PROCESS, static_cast(syscall(SYS_gettid)), 10); + parallel_detail::WorkerPool::Instance().SetNice(10); + try { + const auto dataset = reader.GetDataset(); + const RugnuxLiveSetup setup = MakeRugnuxLiveSetup(*dataset, rugnux_nthreads, scratch_dir + "/p", model_path); + LiveObserver observer(plots, scan_result, *az_int_mapping, live_status, logger); + ProcessResult result; + { + Rugnux process(reader, setup.experiment, *dataset->pixel_mask, setup.config); + Reachable reachable(rugnux_m, rugnux, process, cut, cancelled); + result = process.Run(&observer); + } + // The verdict and summary of the report a `rugnux` run writes, without writing it. + const ReportDocument doc = BuildReportDocument(scratch_dir + "/p", experiment.GetFilePrefix(), + setup.experiment, result); + std::vector warnings; + for (const auto &w: doc.warnings) + if (!w.developer_only) + warnings.push_back(w.text); + live_status.Result(doc.verdict, doc.verdict_text, doc.summary_rows, warnings); + live_status.Finish(result.cancelled ? RugnuxLiveState::Cancelled : RugnuxLiveState::Done); + std::unique_lock ul(rugnux_m); + rugnux_result = std::move(result); + } catch (const std::exception &e) { + logger.Error("rugnux live: {}", e.what()); + live_status.Finish(RugnuxLiveState::Failed, e.what()); + } + if (!parallel_detail::WorkerPool::Instance().SetNice(0)) + logger.Warning("rugnux live: the ParallelFor pool stays at nice 10 - raising it back needs CAP_SYS_NICE"); +} + +void JFJochReceiverRugnux::ProbeThread() { + // Rugnux waits for half the sweep before it decides on a lattice. In the meantime the machine is + // idle, so a small run of its own indexes the first 60 degrees and says at once whether there is + // a crystal - quick feedback for the person at the beamline, above all where there is none. It + // decides nothing for the run, which goes on as it would without it. + constexpr double PROBE_DEG = 60.0; + setpriority(PRIO_PROCESS, static_cast(syscall(SYS_gettid)), 10); + try { + const auto dataset = reader.GetDataset(); + const auto gonio = dataset->experiment.GetGoniometer(); + if (!gonio || gonio->GetIncrement_deg() == 0.0f) + return; + const int64_t frames = std::lround(PROBE_DEG / std::abs(gonio->GetIncrement_deg())); + if (2 * frames > static_cast(reader.GetNumberOfImages())) + return; // a sweep this short is decided on soon enough by the run itself + RugnuxLiveSetup setup = MakeRugnuxLiveSetup(*dataset, rugnux_nthreads, "", ""); + setup.config.end_image = static_cast(frames); + setup.config.rotation_postrefine_geometry = false; + setup.config.run_scaling = false; + ProcessResult r; + { + Rugnux probe_run(reader, setup.experiment, *dataset->pixel_mask, setup.config); + Reachable reachable(rugnux_m, probe, probe_run, cut, cancelled); + r = probe_run.Run(nullptr); + } + if (r.consensus_cell && r.rotation_lattice_type) { + const auto &c = *r.consensus_cell; + live_status.Probe(fmt::format("lattice on the first {:.0f} deg: {}-centred {}, {:.1f} {:.1f} {:.1f} " + "{:.1f} {:.1f} {:.1f}, {:.0f}% of the images indexed", PROBE_DEG, + r.rotation_lattice_type->centering, + gemmi::crystal_system_str(r.rotation_lattice_type->crystal_system), + c.a, c.b, c.c, c.alpha, c.beta, c.gamma, + 100.0 * r.indexing_rate.value_or(0.0f))); + } else + live_status.Probe(fmt::format("no lattice on the first {:.0f} deg", PROBE_DEG)); + } catch (const NoLatticeFound &e) { + live_status.Probe(fmt::format("no lattice on the first {:.0f} deg: {}", PROBE_DEG, e.reason)); + } catch (const std::exception &e) { + logger.Warning("rugnux live: the lattice probe failed: {}", e.what()); + } + logger.Info("rugnux live: lattice probe - {}", live_status.Get().probe); +} + +void JFJochReceiverRugnux::AddToEndMessage(EndMessage &message) { + std::unique_lock ul(rugnux_m); + if (!rugnux_result) + return; + const EndMessage &r = rugnux_result->end_message; + message.indexing_rate = r.indexing_rate; + message.bkg_estimate = r.bkg_estimate; + message.spindle_blind_fraction = r.spindle_blind_fraction; + message.ice_ring_score_mean = r.ice_ring_score_mean; + message.az_int_result = r.az_int_result; + message.rotation_lattice = r.rotation_lattice; + message.rotation_lattice_type = r.rotation_lattice_type; + message.rotation_extra_lattices = r.rotation_extra_lattices; + message.unit_cell = r.unit_cell; + message.space_group_name = r.space_group_name; + message.space_group_number = r.space_group_number; + message.reindex_matrix = r.reindex_matrix; + message.refined_beam_center_x = r.refined_beam_center_x; + message.refined_beam_center_y = r.refined_beam_center_y; + message.refined_poni_rot1 = r.refined_poni_rot1; + message.refined_poni_rot2 = r.refined_poni_rot2; + message.refined_poni_rot3 = r.refined_poni_rot3; + message.refined_rotation_axis = r.refined_rotation_axis; + // TODO: merged data, map coefficients and placed model from scratch_dir (writer branch). Only here, + // with rugnux_result set: every file in scratch_dir is then whole - rugnux writes synchronously and a + // cut never stops a write half-way - while a run that threw may have left one half-written. +} + +void JFJochReceiverRugnux::Configure(const StartMessage &msg) { + // As JFJochReceiverLite::Configure. + if ((experiment.GetXPixelsNum() != msg.image_size_x) + || (experiment.GetYPixelsNum() != msg.image_size_y)) + throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Mismatch in detector size"); + if (fabs(experiment.GetPixelSize_mm() - msg.pixel_size_x * 1e3) > 1e-7) + throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Mismatch in pixel size"); + + experiment.Detector().Description(msg.detector_description); + experiment.Detector().SerialNumber(msg.detector_serial_number); + experiment.Detector().SensorMaterial(msg.sensor_material); + experiment.Detector().SensorThickness_um(msg.sensor_thickness * 1e6); + experiment.Detector().SaturationLimit(SaturationLimitFromValue(msg.saturation_value)); + experiment.Detector().CountRateCorrectionApplied(msg.countrate_correction_enabled); + experiment.Detector().CountRateCorrectionLookupTable(msg.countrate_correction_lookup_table); + experiment.Detector().FlatfieldApplied(msg.flatfield_enabled); + experiment.Detector().VirtualPixelInterpolationApplied(msg.virtual_pixel_interpolation_enabled); + experiment.ApplyPixelMask(false); + experiment.Detector().BitDepthImage(msg.bit_depth_image); + experiment.PixelSigned(msg.pixel_signed); +} + +void JFJochReceiverRugnux::ForwardThread(uint32_t id) { + forward_started.count_down(); + measurement_started.wait(); + + while (!cancelled && !end_message_received) { + try { + auto msg = image_puller.PollImage(std::chrono::milliseconds(5)); + if (!msg.has_value() || !msg->cbor) + continue; + if (msg->cbor->end_message) { + end_message_received = true; + } else if (msg->cbor->calibration) { + if (push_images_to_writer) + image_pusher.SendCalibration(msg->cbor->calibration.value()); + } else if (msg->cbor->data_message) { + last_image_ns = SteadyNowNs(); + live_status.ImageReceived(); + DataMessage data_msg = msg->cbor->data_message.value(); + ++images_collected; + + const auto compressed_size_img = data_msg.image.GetCompressedSize(); + const auto uncompressed_size_img = data_msg.image.GetUncompressedSize(); + compressed_size += compressed_size_img; + uncompressed_size += uncompressed_size_img; + UpdateMaxImageReceived(data_msg.number); + + data_msg.original_number = data_msg.number; + data_msg.user_data = experiment.GetImageAppendix(); + data_msg.run_number = experiment.GetRunNumber(); + data_msg.run_name = experiment.GetRunName(); + data_msg.receiver_buf_available = image_buffer.GetAvailSlots(); + data_msg.receiver_aq_dev_delay = image_puller.GetCurrentFifoUtilization(); + if (compressed_size_img > 0) + data_msg.compression_ratio = static_cast(uncompressed_size_img) + / static_cast(compressed_size_img); + + auto loc = image_buffer.GetImageSlot(); + if (loc == nullptr) + writer_queue_full = true; + else { + auto writer_buffer = (uint8_t *) loc->GetImage(); + CBORStream2Serializer serializer(writer_buffer, experiment.GetImageBufferLocationSize()); + serializer.SerializeImage(data_msg); + loc->SetImageNumber(data_msg.number); + loc->SetImageSize(serializer.GetBufferSize()); + loc->SetIndexed(false); + loc->ReadyToSend(); + + if (zmq_preview_socket != nullptr) + zmq_preview_socket->SendImage(writer_buffer, serializer.GetBufferSize()); + if (zmq_metadata_socket != nullptr) + zmq_metadata_socket->AddDataMessage(data_msg); + if (push_images_to_writer) { + if (image_pusher.SendImage(*loc)) + ++images_sent; + } else + loc->release(); + UpdateMaxImageSent(data_msg.number); + } + } + UpdateMaxDelay(image_puller.GetCurrentFifoUtilization()); + current_status.SetProgress(GetProgress()); + current_status.SetStatus(GetStatus()); + } catch (const JFJochException &e) { + logger.ErrorException(e); + Cancel(e); + } + } +} + +void JFJochReceiverRugnux::Cancel(bool silent) { + detector_finished = true; + // The silent cancel is the detector reading idle at the end of every collection: rugnux goes on. + if (silent) + return; + // /cancel is the one way in for the beamline, which cannot tell the collection from its analysis: it + // ends the collection while the images still arrive, as JFJochReceiverLite does, and in every case + // cuts the analysis, so the end message goes out at once with what rugnux has established. + if (!images_done) + JFJochReceiver::Cancel(silent); + Cut("cancel"); +} + +void JFJochReceiverRugnux::Cut(const std::string &reason) { + std::unique_lock ul(rugnux_m); + if (cut || rugnux_result) + return; + cut = true; + reader.Ended(); // no image is waited for any more + if (rugnux != nullptr) + rugnux->Cancel(); + if (probe != nullptr) + probe->Cancel(); + live_status.Cut(reason); + logger.Info("rugnux live: analysis cut ({}) - the end message carries what is established", reason); +} + +void JFJochReceiverRugnux::StopReceiver() { + if (measurement.valid()) { + measurement.get(); + logger.Info("Receiver stopped"); + } +} + +float JFJochReceiverRugnux::GetEfficiency() const { + if (experiment.GetFrameNum() == 0) + return 0; + return static_cast(images_collected) / static_cast(experiment.GetFrameNum()); +} + +float JFJochReceiverRugnux::GetProgress() const { + if (experiment.GetFrameNum() == 0) + return 0.0; + return static_cast(max_image_number_received) / static_cast(experiment.GetFrameNum()); +} diff --git a/receiver/JFJochReceiverRugnux.h b/receiver/JFJochReceiverRugnux.h new file mode 100644 index 000000000..1e480e4df --- /dev/null +++ b/receiver/JFJochReceiverRugnux.h @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include +#include + +#include "JFJochReceiver.h" +#include "../image_puller/ImagePuller.h" +#include "../reader/JFJochImageBufferReader.h" +#include "../rugnux/Rugnux.h" +#include "RugnuxLiveStatus.h" + +// A DECTRIS rotation collection forwarded to the writer as JFJochReceiverLite does, but analysed by +// rugnux while it arrives instead of image by image: the forward threads put each image into the +// image buffer and send it on, and one rugnux thread reads the collection back from that buffer. The +// end message waits for rugnux, and carries what it found - the lattice, refined geometry, per-image +// results and its merged data, map coefficients and placed model (see AddToEndMessage). +class JFJochReceiverRugnux : public JFJochReceiver { + using JFJochReceiver::Cancel; + + ImagePuller &image_puller; + JFJochImageBufferReader reader; + + std::atomic end_message_received{false}; + std::atomic detector_finished{false}; // see JFJochReceiverLite + constexpr static auto StartMessageGrace = std::chrono::seconds(5); + + const int64_t forward_nthreads; + const int rugnux_nthreads; + const std::string model_path; + const double deadline_s; // after the last image, the analysis is cut (0 = never) + std::string scratch_dir; // rugnux's own output, read back into the end message + RugnuxLiveStatus &live_status; + + std::vector> forward_futures; + std::future measurement; + std::latch measurement_started; + std::latch forward_started; + + std::mutex rugnux_m; + Rugnux *rugnux = nullptr; // while it runs, for Cut + Rugnux *probe = nullptr; // the same for the lattice probe + std::optional rugnux_result; + bool cut = false; + std::atomic images_done{false}; // the forward threads have ended + + // The latency of the demonstration: from the last image in to every result in place. + std::atomic last_image_ns{0}; + + void MeasurementThread(); + void ForwardThread(uint32_t id); + void RugnuxThread(); + void ProbeThread(); + void Configure(const StartMessage &message); + // Stops the analysis where it is: rugnux returns what it has established (see Rugnux::Cancel), and + // the end message goes out with that. Reason: "deadline" or "cancel". + void Cut(const std::string &reason); + void AddToEndMessage(EndMessage &message) override; +public: + JFJochReceiverRugnux(const DiffractionExperiment &experiment, + const PixelMask &pixel_mask, + ImagePuller &image_puller, + ImagePusher &image_pusher, + Logger &logger, + int64_t forward_nthreads, + int rugnux_nthreads, + const std::string &model_path, + double deadline_s, + RugnuxLiveStatus &live_status, + const SpotFindingSettings &spot_finding_settings, + JFJochReceiverCurrentStatus ¤t_status, + JFJochReceiverPlots &plots, + ImageBuffer &image_buffer, + ZMQPreviewSocket *zmq_preview_socket = nullptr, + ZMQMetadataSocket *zmq_metadata_socket = nullptr); + ~JFJochReceiverRugnux() override; + + void StopReceiver() override; + void Cancel(bool silent) override; + + float GetEfficiency() const override; + float GetProgress() const override; + + void SetSpotFindingSettings(const SpotFindingSettings &spot_finding_settings) override {} +}; diff --git a/receiver/JFJochReceiverService.cpp b/receiver/JFJochReceiverService.cpp index ecbab954e..ce0c633c3 100644 --- a/receiver/JFJochReceiverService.cpp +++ b/receiver/JFJochReceiverService.cpp @@ -2,6 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-only #include "JFJochReceiverService.h" +#include "JFJochReceiverRugnux.h" +#include "../common/ThreadAffinity.h" +#include "../rugnux/RugnuxDefaults.h" #include "JFJochReceiverFPGA.h" #include "JFJochReceiverLite.h" #include "../preview/JFJochJPEG.h" @@ -17,6 +20,23 @@ JFJochReceiverService::JFJochReceiverService(AcquisitionDeviceGroup &in_aq_devic spot_finding_settings(DiffractionExperiment::DefaultDataProcessingSettings()) { } +JFJochReceiverService &JFJochReceiverService::RugnuxLive(bool enable, const std::string &model_path, + int reserved_cpus, double deadline_s) { + std::unique_lock ul_state(state_mutex); + if (state != ReceiverState::Idle) + throw JFJochException(JFJochExceptionCategory::WrongDAQState, + "Cannot change rugnux settings during data collection"); + rugnux_enable = enable; + rugnux_model_path = model_path; + rugnux_reserved_cpus = reserved_cpus; + rugnux_deadline_s = deadline_s; + return *this; +} + +RugnuxLiveSnapshot JFJochReceiverService::GetRugnuxStatus() const { + return rugnux_status.Get(); +} + JFJochReceiverService &JFJochReceiverService::NumThreads(int64_t input) { if (input <= 0) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Thread number must be above zero"); @@ -103,6 +123,14 @@ void JFJochReceiverService::Start(const DiffractionExperiment &experiment, image_puller = std::make_shared( experiment.GetDetectorSetup().GetDECTRISStream2Addr()); } + if (rugnux_enable && RugnuxRotationData(experiment)) { + receiver = std::make_unique( + experiment, pixel_mask, *image_puller, image_pusher, logger, nthreads_local, + std::max(1, static_cast(AvailableCpus()) - rugnux_reserved_cpus), + rugnux_model_path, rugnux_deadline_s, rugnux_status, spot_finding_settings, receiver_status, plots, image_buffer, + zmq_preview_socket.get(), zmq_metadata_socket.get()); + break; + } receiver = std::make_unique(experiment, pixel_mask, *image_puller, diff --git a/receiver/JFJochReceiverService.h b/receiver/JFJochReceiverService.h index cb9045b1a..57a9985a5 100644 --- a/receiver/JFJochReceiverService.h +++ b/receiver/JFJochReceiverService.h @@ -3,6 +3,8 @@ #pragma once +#include "RugnuxLiveStatus.h" + #include #include @@ -21,6 +23,14 @@ class JFJochReceiverService { std::shared_ptr image_puller; int64_t nthreads = 8; + // Rotation collections from a DECTRIS detector analysed by rugnux while they arrive + // (JFJochReceiverRugnux), instead of image by image. Off unless asked for. + bool rugnux_enable = false; + std::string rugnux_model_path; + int rugnux_reserved_cpus = 4; // left to the forward threads and the pusher + double rugnux_deadline_s = 0; // the analysis is cut this long after the last image (0 = never) + RugnuxLiveStatus rugnux_status; // of the last collection, kept after it ends + enum class ReceiverState {Idle, Running}; ReceiverState state = ReceiverState::Idle; mutable std::mutex state_mutex; @@ -43,6 +53,9 @@ public: ImagePusher &pusher, size_t send_buffer_size_MiB = 512); JFJochReceiverService& NumThreads(int64_t input); + JFJochReceiverService& RugnuxLive(bool enable, const std::string &model_path, int reserved_cpus, + double deadline_s); + RugnuxLiveSnapshot GetRugnuxStatus() const; JFJochReceiverService& PreviewSocket(const std::string &addr, const std::optional &watermark = {}); JFJochReceiverService& MetadataSocket(const std::string &addr); diff --git a/receiver/RugnuxLiveStatus.cpp b/receiver/RugnuxLiveStatus.cpp new file mode 100644 index 000000000..a0247a8c9 --- /dev/null +++ b/receiver/RugnuxLiveStatus.cpp @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "RugnuxLiveStatus.h" + +double RugnuxLiveStatus::Now() const { + return std::chrono::duration(std::chrono::steady_clock::now() - start).count(); +} + +void RugnuxLiveStatus::CloseStep() { + if (!s.steps.empty() && !s.steps.back().duration_s) + s.steps.back().duration_s = Now() - s.steps.back().started_s; +} + +void RugnuxLiveStatus::Start(int64_t images_expected) { + std::unique_lock ul(m); + s = RugnuxLiveSnapshot{}; + s.state = RugnuxLiveState::Running; + s.images_expected = images_expected; + start = std::chrono::steady_clock::now(); +} + +void RugnuxLiveStatus::ImageReceived() { + std::unique_lock ul(m); + ++s.images_received; +} + +void RugnuxLiveStatus::Step(const std::string &title) { + std::unique_lock ul(m); + CloseStep(); + s.steps.push_back(RugnuxLiveStep{.title = title, .started_s = Now()}); + s.step_progress.reset(); +} + +void RugnuxLiveStatus::Note(const std::string &text) { + std::unique_lock ul(m); + if (s.steps.empty()) + s.steps.push_back(RugnuxLiveStep{.title = "", .started_s = Now()}); + s.steps.back().notes.push_back(text); +} + +void RugnuxLiveStatus::Progress(uint64_t done, uint64_t total) { + std::unique_lock ul(m); + if (total > 0) + s.step_progress = static_cast(done) / static_cast(total); +} + +void RugnuxLiveStatus::Finish(RugnuxLiveState state, const std::string &message) { + std::unique_lock ul(m); + CloseStep(); + s.elapsed_s = Now(); + s.state = state; + s.message = message; + s.step_progress.reset(); +} + +void RugnuxLiveStatus::Result(const std::string &verdict, const std::string &verdict_text, + const std::vector> &summary, + const std::vector &warnings) { + std::unique_lock ul(m); + s.verdict = verdict; + s.verdict_text = verdict_text; + s.summary = summary; + s.warnings = warnings; +} + +void RugnuxLiveStatus::Latency(double last_image_to_written_s) { + std::unique_lock ul(m); + s.last_image_to_written_s = last_image_to_written_s; +} + +void RugnuxLiveStatus::LastImage() { + std::unique_lock ul(m); + s.last_image_s = Now(); +} + +void RugnuxLiveStatus::Cut(const std::string &reason) { + std::unique_lock ul(m); + s.cut = reason; + s.cut_s = Now(); +} + +void RugnuxLiveStatus::Probe(const std::string &text) { + std::unique_lock ul(m); + s.probe = text; + s.probe_s = Now(); +} + +RugnuxLiveSnapshot RugnuxLiveStatus::Get() const { + std::unique_lock ul(m); + RugnuxLiveSnapshot ret = s; + if (s.state == RugnuxLiveState::Running) + ret.elapsed_s = Now(); + return ret; +} diff --git a/receiver/RugnuxLiveStatus.h b/receiver/RugnuxLiveStatus.h new file mode 100644 index 000000000..04e03f00c --- /dev/null +++ b/receiver/RugnuxLiveStatus.h @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include +#include +#include +#include +#include +#include + +// Where the live rugnux run of the last collection is, for the broker's status route: the steps it has +// taken with what each found ("lattice found", "space group P 21 21 21"), the step it is on and how far +// into its images, and - once it is over - its verdict and summary as the report states them, and the +// latency from the last image to every result written. +enum class RugnuxLiveState { Idle, Running, Done, Failed, Cancelled, Skipped }; + +struct RugnuxLiveStep { + std::string title; + double started_s = 0.0; // since the collection started + std::optional duration_s; // set once the next step starts or the run ends + std::vector notes; +}; + +struct RugnuxLiveSnapshot { + RugnuxLiveState state = RugnuxLiveState::Idle; + std::string message; // why it failed or was skipped + int64_t images_expected = 0; + int64_t images_received = 0; + double elapsed_s = 0.0; + std::optional step_progress; // fraction of the current pass's images + std::vector steps; + // The lattice probe on the first 60 degrees (JFJochReceiverRugnux::ProbeThread): an early answer + // to "is there a crystal", seconds into the collection; the run's own answer comes later. + std::string probe; + std::optional probe_s; // when it answered, since the collection started + std::optional last_image_s; // when the last image arrived, since the collection started + // The analysis was cut - "deadline" (the broker's deadline_s after the last image) or "cancel" - at + // cut_s since the collection started; what it had established went out, the steps after it were left out. + std::string cut; + std::optional cut_s; + std::optional last_image_to_written_s; + std::string verdict; // OK | WARNINGS | UNUSABLE | FAILED + std::string verdict_text; + std::vector> summary; + std::vector warnings; +}; + +class RugnuxLiveStatus { + mutable std::mutex m; + RugnuxLiveSnapshot s; + std::chrono::steady_clock::time_point start; + double Now() const; + void CloseStep(); +public: + void Start(int64_t images_expected); + void ImageReceived(); + void Step(const std::string &title); + void Note(const std::string &text); + void Progress(uint64_t done, uint64_t total); + void Finish(RugnuxLiveState state, const std::string &message = ""); + void Result(const std::string &verdict, const std::string &verdict_text, + const std::vector> &summary, + const std::vector &warnings); + void Latency(double last_image_to_written_s); + void LastImage(); + void Cut(const std::string &reason); + void Probe(const std::string &text); + RugnuxLiveSnapshot Get() const; +}; diff --git a/rugnux/CMakeLists.txt b/rugnux/CMakeLists.txt index 27eb8cfb1..83444105f 100644 --- a/rugnux/CMakeLists.txt +++ b/rugnux/CMakeLists.txt @@ -17,6 +17,8 @@ ADD_LIBRARY(JFJochRugnux STATIC RugnuxScaleMerge.cpp RugnuxDefaults.cpp RugnuxDefaults.h + RugnuxLive.cpp + RugnuxLive.h Rugnux.h RugnuxCommandLine.cpp RugnuxCommandLine.h diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index e6e6eb076..a7c231288 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -419,11 +419,11 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) { result = RunAllPasses(observer); } catch (const NoLatticeFound &) { // A run that found no lattice after deciding a near tie early is made again deciding late. - if (!near_tie_rechecks_->carried) + if (!near_tie_rechecks_->carried || config_.fast_live) throw; lost = true; } - if (lost || (!result.cancelled && NearTieOverturned())) { + if (lost || (!result.cancelled && !config_.fast_live && NearTieOverturned())) { JoinBeamCenterCapture(); Logger logger("Rugnux"); logger.Info("A near tie decided early ends elsewhere on the rest of the sweep - the run is made again, " @@ -431,6 +431,12 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) { result = again->RunAllPasses(observer); run = again.get(); } + // Cut short in fast_live: a pass whose images were not all read has no lattice and no merge, so the + // run's answer is the last pass that ran to its end. + if (config_.fast_live && result.cancelled && established_) { + result = *established_; + result.cancelled = true; + } result.total_time_s = std::chrono::duration(std::chrono::steady_clock::now() - run_start).count(); // For the report's beam-centre warning: what the background measured, and what this crystal asks // of the centre - at the file's geometry, as the pre-scan's check asks it, but on the cell the run @@ -707,7 +713,7 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b // skip the search that is the whole point of running again. // The run as this pass finds it, for the re-check of a near tie its first pass may decide early. std::shared_ptr recheck; - if (!decide_late_ && config_.mode == ProcessMode::FullAnalysis && config_.rotation_indexing + if (!decide_late_ && !config_.fast_live && config_.mode == ProcessMode::FullAnalysis && config_.rotation_indexing && config_.two_pass_rotation && !force_rotation_result_.has_value() && !config_.forced_rotation_lattice.has_value() && experiment_.IsRotationIndexing()) recheck = std::make_shared(*this); @@ -1072,7 +1078,9 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b image_pass_mark.Stop(); // Scaling and merging (full analysis only). - if (full && !cancelled_ && (result.indexing_rate.value_or(0.0f) > 0 || result.rotation_lattice_found) + // As ImagePass's stopped(): in fast_live a pass that read all its images merges whatever comes after. + const bool images_cut = config_.fast_live ? result.cancelled : static_cast(cancelled_); + if (full && !images_cut && (result.indexing_rate.value_or(0.0f) > 0 || result.rotation_lattice_found) && (config_.run_scaling || !config_.reference_data.empty())) { TimingMark mark(logger, "scale, merge and symmetry"); std::optional gpu_merge; // what the speculative probe waits for on a small card @@ -1190,6 +1198,7 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b writer->Finalize(); result.written_master_path = config_.output_prefix + "_process.h5"; } + result.end_message = end_msg; output_mark.Stop(); diff --git a/rugnux/Rugnux.h b/rugnux/Rugnux.h index e5cca61d9..afbdca2aa 100644 --- a/rugnux/Rugnux.h +++ b/rugnux/Rugnux.h @@ -110,6 +110,13 @@ struct ProcessConfig { // are decided (Rugnux::decide_late_). bool live = false; + // Live in the broker, where the run has to end a fixed time after the last frame: every decision + // stands where it was first taken, and nothing the run would otherwise do again once the sweep is + // in - the near-tie re-checks and the run made again, the rotation-scale walk, the metric-symmetry + // arms, the geometry walk, the supercell and quality guards, the doubled cell - is done. A + // difficult crystal can come out worse; an easy one comes out the same, sooner. + bool fast_live = false; + // Rotation indexing (FullAnalysis) bool rotation_indexing = false; bool two_pass_rotation = true; @@ -326,6 +333,9 @@ struct ProcessResult { // beam-centre pre-scan that runs before the first one. processing_time_s is the last pass alone // and so understates a two-pass run by more than a factor of two. double total_time_s = 0.0; + // The end message of this pass, as the _process.h5 records it: the lattice, refined geometry, + // reindexing and the run means - what the broker puts into its own when it runs rugnux live. + EndMessage end_message; // Workers the per-image loop actually ran, which is not config.nthreads once the loop is capped. int image_loop_threads = 0; double frame_rate_hz = 0.0; @@ -813,6 +823,9 @@ class Rugnux { CancelFlag Own() const { CancelFlag f; f.run = flag; return f; } }; CancelFlag cancelled_; + // fast_live: the last pass that ran to its end. Where a cancel cuts the pass after it short, Run() + // returns this one, so the broker sends what is established (lattice, geometry) rather than nothing. + std::optional established_; // Where the steps and notes of the pass now running go: the observer on a pass that makes the // result, nullptr on a probe pass and on a copy run beside the main one. Set by RunPipeline. @@ -1357,6 +1370,8 @@ public: const PowderRings &GetPowderRings() const { return powder_; } // Request cancellation; safe to call from any thread (the worker loop checks between images). + // fast_live makes it a cut: a pass that is past its images still merges and writes its files, + // only the model is left out, and a pass cut in its images gives way to the last complete one. void Cancel() { cancelled_ = true; } bool IsCancelled() const { return cancelled_; } }; diff --git a/rugnux/RugnuxImagePass.cpp b/rugnux/RugnuxImagePass.cpp index dc17a0419..0510a79e9 100644 --- a/rugnux/RugnuxImagePass.cpp +++ b/rugnux/RugnuxImagePass.cpp @@ -409,6 +409,10 @@ void Rugnux::ImagePass(PipelineLocals &p) { std::chrono::steady_clock::now() - image_loop_start).count(); result.cancelled = cancelled_; + // Whether the pass stops here. In fast_live a cut that comes after the last image read lets the pass + // finish - its lattice, its merge and its files are what the run has established - and only the model + // after them is left out (ScaleMergeAndSymmetry). + const auto stopped = [&] { return config_.fast_live ? result.cancelled : static_cast(cancelled_); }; result.images_processed = finished_count.load(); Note(fmt::format("{} images, {:.0f} per second", result.images_processed, result.images_processed / std::max(1e-6, result.image_loop_time_s))); @@ -424,7 +428,7 @@ void Rugnux::ImagePass(PipelineLocals &p) { // The footprint of the reflection rather than of the spot: the pre-scan's widths plus where the // spots sit against their predictions (SpotFootprint.h). Like the widths it acts only where the // reflection outgrows the r1 disk, and like them it is held back for the canonical pass. - if (measure_offsets && !cancelled_) { + if (measure_offsets && !stopped()) { const SpotFootprint fp = FootprintWithOffsets(*prescan_footprint_, footprint_offsets); const BraggIntegrationSettings canonical = bragg_adaptive_.value_or(experiment_.GetBraggIntegrationSettings()); bool outgrows = false; @@ -446,7 +450,7 @@ void Rugnux::ImagePass(PipelineLocals &p) { } } - if (full && indexer && experiment_.IsRotationIndexing() && !cancelled_) { + if (full && indexer && experiment_.IsRotationIndexing() && !stopped()) { const SupercellProbe probe = indexer->GetSupercellProbe(); if (probe[0][0].n > 0 && probe[0][1].n > 0) { std::string table; @@ -594,7 +598,7 @@ void Rugnux::ImagePass(PipelineLocals &p) { predicted += p_i; crowded += c_i; } - if (predicted > 0 && !cancelled_) { + if (predicted > 0 && !stopped()) { const double f = static_cast(crowded) / static_cast(predicted); const double n = static_cast(crowding_frames.size()); double ss = 0.0; @@ -611,7 +615,7 @@ void Rugnux::ImagePass(PipelineLocals &p) { // Every image failing is a total failure, not a run that produced nothing: it used to be reported // only as per-image log lines while the process still exited 0 with no output file. - if (!cancelled_ && images_to_process > 0 && result.images_processed == 0) + if (!stopped() && images_to_process > 0 && result.images_processed == 0) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "No image could be analyzed - see the per-image errors above"); @@ -621,7 +625,7 @@ void Rugnux::ImagePass(PipelineLocals &p) { // Powder calibration: the whole run has now been accumulated - the summed (q x azimuth) profile in // plots, or the pooled spot list - so fit the detector geometry to it. The caller reports it and // writes the PONI file; nothing here is stored in the _process.h5. - if (calibration && !cancelled_) + if (calibration && !stopped()) FitCalibration(p, calibration_spot_list); // End message (also written to the file). @@ -642,7 +646,7 @@ void Rugnux::ImagePass(PipelineLocals &p) { end_msg.az_int_result["dataset"] = plots.GetAzIntProfile(); end_msg.indexing_rate = result.indexing_rate; - if (full && !cancelled_) { + if (full && !stopped()) { const auto rot = indexer->FinalizeRotationIndexing(); indexer->RethrowIndexerError(); if (rot.has_value()) { diff --git a/rugnux/RugnuxLive.cpp b/rugnux/RugnuxLive.cpp new file mode 100644 index 000000000..afd1bc3f1 --- /dev/null +++ b/rugnux/RugnuxLive.cpp @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "RugnuxLive.h" + +#include "RugnuxDefaults.h" + +RugnuxLiveSetup MakeRugnuxLiveSetup(const JFJochReaderDataset &dataset, int nthreads, + const std::string &output_prefix, const std::string &model_path) { + RugnuxLiveSetup s; + + // The experiment as rugnux_cli.cpp sets it up for a rotation sweep with no options given. + DiffractionExperiment &x = s.experiment; + x = dataset.experiment; + ApplyRugnuxExperimentDefaults(x); + x.NumTriggers(1); + ClearStoredCrystal(x); + x.MaxSpotCount(RUGNUX_MAX_SPOT_COUNT); + + IndexingSettings indexing; + indexing.Algorithm(IndexingAlgorithmEnum::Auto); + indexing.RotationIndexing(true); + indexing.GeomRefinementAlgorithm(GeomRefinementAlgorithmEnum::BeamCenter); + indexing.IndexIceRings(false); + x.ImportIndexingSettings(indexing); + x.DetectIceRings(RugnuxDetectIceRings(dataset.file_detect_ice_rings, true)); + + ScalingSettings scaling = RugnuxDefaultScalingSettings(true); + scaling.ScaleFulls(true); + scaling.RelativeBDegrees(0.0); + scaling.MergeFriedel(true); + scaling.MinPartiality(0.02); + scaling.MinCCForImage(0.0); + x.ImportScalingSettings(scaling); + + ProcessConfig &c = s.config; + c.mode = ProcessMode::FullAnalysis; + c.nthreads = nthreads; + c.output_prefix = output_prefix; + c.write_process_h5 = false; + c.export_unmerged = false; + c.spot_finding = RugnuxDefaultSpotFinding(true); + c.rotation_indexing = true; + c.two_pass_rotation = true; + c.rotation_postrefine_geometry = true; + c.rotation_indexing_image_count = RUGNUX_ROTATION_INDEXING_IMAGES; + c.detect_beam_stop = RUGNUX_DETECT_BEAM_STOP_FRAMES; + c.beam_center_check = true; + c.measure_spot_budget = true; + c.run_scaling = true; + c.model_path = model_path; + c.live = true; + c.fast_live = true; + // Decided late, on the whole sweep, as it re-checks its radius there - which fast_live does not. + c.adaptive_integration_radius = false; + return s; +} diff --git a/rugnux/RugnuxLive.h b/rugnux/RugnuxLive.h new file mode 100644 index 000000000..c4aba1297 --- /dev/null +++ b/rugnux/RugnuxLive.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include + +#include "Rugnux.h" +#include "../reader/JFJochReaderDataset.h" + +// A rotation collection processed inside the broker while its frames arrive: what a bare `rugnux` on +// the same file would set, with the run made fast_live. The output goes to output_prefix - a place +// private to the broker, from which it builds the END message - and the model, where one is given, +// is placed and validated as `rugnux --model` does. +struct RugnuxLiveSetup { + DiffractionExperiment experiment; + ProcessConfig config; +}; + +RugnuxLiveSetup MakeRugnuxLiveSetup(const JFJochReaderDataset &dataset, int nthreads, + const std::string &output_prefix, const std::string &model_path); diff --git a/rugnux/RugnuxPasses.cpp b/rugnux/RugnuxPasses.cpp index 01ae4bc01..6b41c4e23 100644 --- a/rugnux/RugnuxPasses.cpp +++ b/rugnux/RugnuxPasses.cpp @@ -387,6 +387,8 @@ ProcessResult Rugnux::RunAllPasses(RugnuxObserver *observer) { pass1.pass_number = 1; pass1.pass_count = 2; if (cancelled_) { config_.output_prefix = base_prefix; return pass1; } + if (config_.fast_live) + established_ = pass1; // Beam centre as a LATTICE hypothesis, decided on the merge. The first pass indexed both // centres, found the same cell with a different metric symmetry read off it, and measured that @@ -658,7 +660,7 @@ ProcessResult Rugnux::RunAllPasses(RugnuxObserver *observer) { // pass off and throws it away, so the walk runs first, on its own, and the refined passes // after it. Either way the run ends where the sequential walk leaves it. constexpr double ROTATION_SCALE_STEP = 0.01; - if (!cancelled_ && gonio_snapshot && pass1.post_refine && !config_.rotation_scale) { + if (!cancelled_ && !config_.fast_live && gonio_snapshot && pass1.post_refine && !config_.rotation_scale) { const auto check = std::make_shared(); RotationScaleWalk walk; if (std::fabs(pass1.post_refine->rotation_scale - 1.0) >= ROTATION_SCALE_STEP) { @@ -940,7 +942,8 @@ std::optional Rugnux::RefinedPasses(RugnuxObserver *observer, Pro // Let every refined pass from here on ask the quality guard's question of itself, so one that // is going to be thrown away can skip the report and the files it would have superseded. - quality_guard_pass1_ = pass1; + if (!config_.fast_live) + quality_guard_pass1_ = pass1; config_.output_prefix = base_prefix; // the refined pass is the canonical result (no _02 suffix) // The canonical pass measures the post-refinement again where the pre-pass moved the geometry // (it applies nothing), so that the walk below has something to decide on. @@ -997,7 +1000,7 @@ std::optional Rugnux::RefinedPasses(RugnuxObserver *observer, Pro double split = 0.0; std::optional demoted_arm; // run where the data resolve the equality std::shared_ptr demoted_recheck; // the demoted arm again on all the frames, for a near tie - if (!cancelled_ && (length_arm || angle_arm) && pass1.rotation_lattice_type) { + if (!cancelled_ && !config_.fast_live && (length_arm || angle_arm) && pass1.rotation_lattice_type) { split = length_arm ? *pass1.lattice_length_split : *pass1.lattice_angle_departure_deg * PI / 180.0; const double dx = std::max(experiment_.GetBeamX_pxl(), static_cast(experiment_.GetXPixelsNum()) - experiment_.GetBeamX_pxl()); @@ -1101,8 +1104,8 @@ std::optional Rugnux::RefinedPasses(RugnuxObserver *observer, Pro logger.Info("Rotation two-pass geometry post-refinement: second pass (refined geometry, canonical) -> {}_*", base_prefix); - speculate_geometry_probe_ = true; // a walk that follows starts with an indexing probe - rerun_if_starved_ = true; + speculate_geometry_probe_ = !config_.fast_live; // a walk that follows starts with an indexing probe + rerun_if_starved_ = !config_.fast_live; const DiffractionExperiment before_pass2 = experiment_; auto pass2 = RunPipeline(observer, /*write_output=*/true, /*geometry_prepass=*/false); if (radius_recheck_) { @@ -1296,7 +1299,7 @@ std::optional Rugnux::RefinedPasses(RugnuxObserver *observer, Pro // from here, not from where its own probe left the run. DiffractionExperiment last_round_start = experiment_; bool walk_shown = false; - while (!cancelled_ && prepass_detector_geometry_ + while (!cancelled_ && !config_.fast_live && prepass_detector_geometry_ && pass2.post_refine && pass2.post_refine->detector_refined) { if (!walk_shown) { step("Geometry check"); @@ -1437,7 +1440,7 @@ std::optional Rugnux::RefinedPasses(RugnuxObserver *observer, Pro // RunPipeline) - so each is followed by it. After the first re-run the radius is the fixed one // and this never fires again. const auto rerun_if_starved = [&](ProcessResult &pass, const std::string decision) { - if (cancelled_ || !bragg_adaptive_ || !bkg_starved_fraction_ + if (cancelled_ || config_.fast_live || !bragg_adaptive_ || !bkg_starved_fraction_ || *bkg_starved_fraction_ <= spot_width::BKG_STARVED_MAX_FRACTION) return; logger.Info("Two-pass: at the widened integration radius r1={:.1f} the neighbouring " @@ -1487,7 +1490,7 @@ std::optional Rugnux::RefinedPasses(RugnuxObserver *observer, Pro }; const double v1 = primitive_volume(*pass1.consensus_cell, pass1.consensus_centering); const double v2 = primitive_volume(*pass2.consensus_cell, pass2.consensus_centering); - if (v1 > 0.0 && v2 > 1.5 * v1 && !supercell_settled_) { + if (v1 > 0.0 && v2 > 1.5 * v1 && !supercell_settled_ && !config_.fast_live) { logger.Info("Two-pass: second-pass primitive cell volume {:.0f} A^3 is {:.2f}x pass-1's " "({:.0f} A^3) - a spurious supercell; re-running the second pass with " "pass-1's result forced", @@ -1519,7 +1522,7 @@ std::optional Rugnux::RefinedPasses(RugnuxObserver *observer, Pro // but it is not guaranteed to be, and the guard that decides is RefinedPassIsWorse above. It // has already been asked once, inside the pass itself, which is how a pass that is about to be // thrown away knows not to write a report; this is where the answer is acted on. - if (!cancelled_ && pass1.has_merge_statistics && pass2.has_merge_statistics) { + if (!cancelled_ && !config_.fast_live && pass1.has_merge_statistics && pass2.has_merge_statistics) { logger.Info("Two-pass quality guard: the refined pass holds {} reflections at I/sigma >= 2 and " "{} at <= -2 of {} in a {:.0f} A^3 primitive cell, the header-geometry pass {} and {} " "of {} in {:.0f} A^3; low-order axial {} against {}", @@ -1800,7 +1803,7 @@ namespace { ProcessResult Rugnux::DoubledCellHypothesis(RugnuxObserver *observer, ProcessResult half, const std::string &base_prefix) { - if (cancelled_ || user_cell_ || user_fixed_sg_ || !half.supercell || !last_rotation_result_ + if (cancelled_ || config_.fast_live || user_cell_ || user_fixed_sg_ || !half.supercell || !last_rotation_result_ || !half.has_merge_statistics || !half.consensus_cell || !DoubledCellWorthMerging(*half.supercell)) return half; Logger logger("Rugnux"); diff --git a/rugnux/RugnuxScaleMerge.cpp b/rugnux/RugnuxScaleMerge.cpp index 658fadb27..78ea9d081 100644 --- a/rugnux/RugnuxScaleMerge.cpp +++ b/rugnux/RugnuxScaleMerge.cpp @@ -3992,8 +3992,16 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) { gemmi::SpaceGroup space_group; }; std::optional placed_model_out; + // fast_live, cut (the broker's deadline or a cancel): the model is left out, and the files are written + // as a run without one writes them. Asked again before the search below. + const auto model_cut = [&] { + if (!config_.fast_live || !cancelled_) + return false; + Note("cut: the model is left out"); + return true; + }; const bool model_run = result.consensus_cell && write_files && !config_.model_path.empty() && !geometry_prepass - && !superseded; + && !superseded && !model_cut(); // The twin hypothesis the model is asked about: its own subgroup's merge where it has one, the data's // otherwise - and whether that merge was made only for the model, so is written only where it chooses it. std::optional &model_twin = model_twin_merge ? model_twin_file : twin_file; @@ -4307,7 +4315,7 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) { // veto - and nothing else, never an indexing: a different group or the subgroup it chooses goes to the // files of its own, _mr, from the merge it was chosen on. It never makes the rigid body's // verdict (MODEL_FIT) either; its own null is what placed it. - if (model_run && (model_predicted || config_.force_mr || !placed_by_rigid_body)) { + if (model_run && (model_predicted || config_.force_mr || !placed_by_rigid_body) && !model_cut()) { phase("Molecular replacement"); Step("Molecular replacement"); TimingMark mark(logger, "model prior"); diff --git a/rugnux/rugnux_cli.cpp b/rugnux/rugnux_cli.cpp index 33f713b9f..e218c8a70 100644 --- a/rugnux/rugnux_cli.cpp +++ b/rugnux/rugnux_cli.cpp @@ -318,6 +318,7 @@ enum { OPT_WRITE_PROCESS_H5, OPT_FINALIST_LEDGER, OPT_SIMULATE_LIVE, + OPT_FAST_LIVE, OPT_DEVELOPER, OPT_NO_GPU, OPT_FORCE_STILL, @@ -376,6 +377,7 @@ static option long_options[] = { {"write-process-h5", no_argument, nullptr, OPT_WRITE_PROCESS_H5}, {"finalist-ledger", no_argument, nullptr, OPT_FINALIST_LEDGER}, {"simulate-live", required_argument, nullptr, OPT_SIMULATE_LIVE}, + {"fast-live", no_argument, nullptr, OPT_FAST_LIVE}, // undocumented: the broker's live run (ProcessConfig::fast_live) {"developer", no_argument, nullptr, OPT_DEVELOPER}, {"no-gpu", no_argument, nullptr, OPT_NO_GPU}, {"smooth-g", optional_argument, nullptr, OPT_SMOOTH_G}, @@ -942,6 +944,7 @@ static int RunRugnux(int argc, char **argv, bool no_gpu) { std::optional scale_fulls_arg; // --scale-fulls / --no-scale-fulls; default on for rot3d bool write_process_h5_flag = false; // --write-process-h5; also write _process.h5 when merging bool simulate_live = false; // --simulate-live + bool fast_live = false; // --fast-live, with --simulate-live bool finalist_ledger_flag = false; // --finalist-ledger; report-only symmetry evidence table std::optional detect_ice_rings; // --detect-ice-rings[=on|off]; unset => use the dataset (file) value bool index_ice_rings = false; // --index-ice-rings[=on|off]; index on the ice-band spots too @@ -1367,6 +1370,9 @@ static int RunRugnux(int argc, char **argv, bool no_gpu) { case OPT_FINALIST_LEDGER: finalist_ledger_flag = true; break; + case OPT_FAST_LIVE: + fast_live = true; + break; case OPT_SIMULATE_LIVE: simulate_live = true; simulated_frame_time = std::chrono::microseconds( @@ -3136,6 +3142,9 @@ static int RunRugnux(int argc, char **argv, bool no_gpu) { config.write_process_h5 = run_scaling ? write_process_h5_flag : true; config.finalist_ledger = finalist_ledger_flag; config.live = broker_feed || simulate_live; + config.fast_live = fast_live && config.live; + if (config.fast_live) + config.adaptive_integration_radius = false; // as MakeRugnuxLiveSetup if (simulate_live) logger.Info("Simulated live collection: image i readable (i+1) x {:.1f} ms after the run starts", simulated_frame_time.count() / 1000.0);