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

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

963 lines
39 KiB
C++

// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
// Using OpenAPI licensed with Apache License 2.0
#include "JFJochBrokerHttp.h"
#include <cstring>
#include <fstream>
#include <sstream>
#include <vector>
#include "Helpers.h"
#include "../common/GitInfo.h"
#include "../preview/JFJochTIFF.h"
#include "OpenAPIConvert.h"
#include "gen/model/Error_message.h"
using namespace org::openapitools::server::model;
namespace {
template<class T>
bool fromStringValue(const std::string &s, T &out) {
std::istringstream is(s);
is >> out;
return !is.fail() && is.eof();
}
template<>
bool fromStringValue<std::string>(const std::string &s, std::string &out) {
out = s;
return true;
}
template<>
bool fromStringValue<bool>(const std::string &s, bool &out) {
if (s == "true" || s == "1") {
out = true;
return true;
}
if (s == "false" || s == "0") {
out = false;
return true;
}
return false;
}
template<class T>
std::optional<T> parse_query_value(const httplib::Request &req, const char *name) {
if (!req.has_param(name))
return std::nullopt;
T v{};
if (fromStringValue(req.get_param_value(name), v))
return v;
return std::nullopt;
}
std::optional<std::string> parse_query_string(const httplib::Request &req, const char *name) {
if (!req.has_param(name))
return std::nullopt;
return req.get_param_value(name);
}
std::string mime_from_path(const std::string &path) {
if (path.ends_with(".js")) return "text/javascript";
if (path.ends_with(".css")) return "text/css";
if (path.ends_with(".html")) return "text/html";
if (path.ends_with(".json")) return "application/json";
if (path.ends_with(".cbor")) return "application/cbor";
if (path.ends_with(".jpeg") || path.ends_with(".jpg")) return "image/jpeg";
if (path.ends_with(".tiff") || path.ends_with(".tif")) return "image/tiff";
if (path.ends_with(".png")) return "image/png";
if (path.ends_with(".svg")) return "image/svg+xml";
if (path.ends_with(".bin")) return "application/octet-stream";
return "application/octet-stream";
}
inline bool read_file_to_string(const std::string &path, std::string &out) {
std::ifstream f(path, std::ios::binary);
if (!f)
return false;
std::ostringstream ss;
ss << f.rdbuf();
out = ss.str();
return true;
}
}
JFJochBrokerHttp::JFJochBrokerHttp(const DiffractionExperiment &experiment,
const SpotFindingSettings &spot_finding_settings)
: state_machine(experiment, services, logger, spot_finding_settings) {
}
void JFJochBrokerHttp::AddDetectorSetup(const DetectorSetup &setup) {
state_machine.AddDetectorSetup(setup);
logger.Info("Added detector {}", setup.GetDescription());
}
JFJochServices &JFJochBrokerHttp::Services() {
return services;
}
JFJochBrokerHttp &JFJochBrokerHttp::FrontendDirectory(const std::string &directory) {
frontend_directory = directory;
return *this;
}
std::pair<int, std::string> JFJochBrokerHttp::handleParsingException(const std::exception &ex) const noexcept {
try {
throw;
} catch (const nlohmann::detail::exception &e) {
return {400, e.what()};
} catch (const org::openapitools::server::helpers::ValidationException &e) {
return {400, e.what()};
} catch (const std::exception &e) {
return {500, e.what()};
}
}
std::pair<int, std::string> JFJochBrokerHttp::handleOperationException(const std::exception &ex) const noexcept {
try {
throw;
} catch (const WrongDAQStateException &) {
Error_message msg;
msg.setMsg(ex.what());
msg.setReason("WrongDAQState");
nlohmann::json j;
to_json(j, msg);
return {500, j.dump()};
} catch (const std::exception &) {
Error_message msg;
msg.setMsg(ex.what());
msg.setReason("Other");
nlohmann::json j;
to_json(j, msg);
return {500, j.dump()};
}
}
void JFJochBrokerHttp::attach(httplib::Server &server) {
register_routes(server);
}
void JFJochBrokerHttp::register_routes(httplib::Server &server) {
auto bind_noarg = [this](auto method) {
return [this, method](const httplib::Request &, httplib::Response &res) {
try {
(this->*method)(res);
} catch (const std::exception &e) {
auto [c, s] = handleOperationException(e);
send_plain(res, c, s);
}
};
};
auto bind_json = [this](auto method, auto model_tag) {
return [this, method](const httplib::Request &req, httplib::Response &res) {
using Model = decltype(model_tag);
Model v;
try {
nlohmann::json::parse(req.body).get_to(v);
v.validate();
} catch (const std::exception &e) {
// Malformed or invalid request body.
auto [c, s] = handleParsingException(e);
send_plain(res, c, s);
return;
}
try {
(this->*method)(v, res);
} catch (const std::exception &e) {
// Operation failure (e.g. WrongDAQState, a failed synchronous start): return the
// structured Error_message JSON with a reason field, like the no-arg endpoints, so
// clients can distinguish the cause instead of receiving opaque plain text.
auto [c, s] = handleOperationException(e);
send_plain(res, c, s);
}
};
};
auto bind_req = [this](auto method) {
return [this, method](const httplib::Request &req, httplib::Response &res) {
try {
(this->*method)(req, res);
} catch (const std::exception &e) {
auto [c, s] = handleOperationException(e);
send_plain(res, c, s);
}
};
};
server.Post("/cancel", bind_noarg(&JFJochBrokerHttp::cancel_post));
server.Get("/config/azim_int", bind_noarg(&JFJochBrokerHttp::config_azim_int_get));
server.Put("/config/azim_int", bind_json(&JFJochBrokerHttp::config_azim_int_put, Azim_int_settings{}));
server.Get("/config/dark_mask", bind_noarg(&JFJochBrokerHttp::config_dark_mask_get));
server.Put("/config/dark_mask", bind_json(&JFJochBrokerHttp::config_dark_mask_put, Dark_mask_settings{}));
server.Get("/config/detector", bind_noarg(&JFJochBrokerHttp::config_detector_get));
server.Put("/config/detector", bind_json(&JFJochBrokerHttp::config_detector_put, Detector_settings{}));
server.Get("/config/file_writer", bind_noarg(&JFJochBrokerHttp::config_file_writer_get));
server.Put("/config/file_writer", bind_json(&JFJochBrokerHttp::config_file_writer_put, File_writer_settings{}));
server.Post("/config/image_format/conversion", bind_noarg(&JFJochBrokerHttp::config_image_format_conversion_post));
server.Get("/config/image_format", bind_noarg(&JFJochBrokerHttp::config_image_format_get));
server.Put("/config/image_format", bind_json(&JFJochBrokerHttp::config_image_format_put, Image_format_settings{}));
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/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));
server.Put("/config/instrument", bind_json(&JFJochBrokerHttp::config_instrument_put, Instrument_metadata{}));
server.Put("/config/internal_generator_image", [this](const httplib::Request &req, httplib::Response &res) {
try {
config_internal_generator_image_put(parse_query_value<int64_t>(req, "id"), req, res);
} catch (const std::exception &e) {
auto [c, s] = handleOperationException(e);
send_plain(res, c, s);
}
});
server.Put("/config/internal_generator_image.tiff", [this](const httplib::Request &req, httplib::Response &res) {
try {
config_internal_generator_image_tiff_put(parse_query_value<int64_t>(req, "id"), req, res);
} catch (const std::exception &e) {
auto [c, s] = handleOperationException(e);
send_plain(res, c, s);
}
});
server.Get("/config/mask", bind_noarg(&JFJochBrokerHttp::config_mask_get));
server.Get("/config/mask.tiff", bind_noarg(&JFJochBrokerHttp::config_mask_tiff_get));
server.Get("/config/roi", bind_noarg(&JFJochBrokerHttp::config_roi_get));
server.Put("/config/roi", bind_json(&JFJochBrokerHttp::config_roi_put, Roi_definitions{}));
server.Get("/config/select_detector", bind_noarg(&JFJochBrokerHttp::config_select_detector_get));
server.Put("/config/select_detector",
bind_json(&JFJochBrokerHttp::config_select_detector_put, Detector_selection{}));
server.Get("/config/spot_finding", bind_noarg(&JFJochBrokerHttp::config_spot_finding_get));
server.Put("/config/spot_finding", bind_json(&JFJochBrokerHttp::config_spot_finding_put, Spot_finding_settings{}));
server.Get("/config/user_mask", bind_noarg(&JFJochBrokerHttp::config_user_mask_get));
server.Put("/config/user_mask", bind_req(&JFJochBrokerHttp::config_user_mask_put));
server.Get("/config/user_mask.tiff", bind_noarg(&JFJochBrokerHttp::config_user_mask_tiff_get));
server.Put("/config/user_mask.tiff", bind_req(&JFJochBrokerHttp::config_user_mask_tiff_put));
server.Get("/config/zeromq_metadata", bind_noarg(&JFJochBrokerHttp::config_zeromq_metadata_get));
server.Put("/config/zeromq_metadata",
bind_json(&JFJochBrokerHttp::config_zeromq_metadata_put, Zeromq_metadata_settings{}));
server.Get("/config/zeromq_preview", bind_noarg(&JFJochBrokerHttp::config_zeromq_preview_get));
server.Put("/config/zeromq_preview",
bind_json(&JFJochBrokerHttp::config_zeromq_preview_put, Zeromq_preview_settings{}));
server.Post("/deactivate", bind_noarg(&JFJochBrokerHttp::deactivate_post));
server.Get("/detector/status", bind_noarg(&JFJochBrokerHttp::detector_status_get));
server.Get("/fpga_status", bind_noarg(&JFJochBrokerHttp::fpga_status_get));
server.Post("/image_buffer/clear", bind_noarg(&JFJochBrokerHttp::image_buffer_clear_post));
server.Get("/image_buffer/image.cbor", [this](const httplib::Request &req, httplib::Response &res) {
try {
image_buffer_image_cbor_get(parse_query_value<int64_t>(req, "id"), res);
} catch (const std::exception &e) {
auto [c, s] = handleOperationException(e);
send_plain(res, c, s);
}
});
server.Get("/image_buffer/image.jpeg", [this](const httplib::Request &req, httplib::Response &res) {
try {
image_buffer_image_jpeg_get(
parse_query_value<int64_t>(req, "id"),
parse_query_value<bool>(req, "show_user_mask"),
parse_query_value<bool>(req, "show_roi"),
parse_query_value<bool>(req, "show_spots"),
parse_query_value<bool>(req, "show_predictions"),
parse_query_value<bool>(req, "show_beam_center"),
parse_query_value<float>(req, "saturation"),
parse_query_value<int64_t>(req, "jpeg_quality"),
parse_query_value<float>(req, "show_res_ring"),
parse_query_string(req, "color"),
parse_query_value<bool>(req, "show_res_est"),
res
);
} catch (const std::exception &e) {
auto [c, s] = handleOperationException(e);
send_plain(res, c, s);
}
});
server.Get("/image_buffer/image.tiff", [this](const httplib::Request &req, httplib::Response &res) {
try {
image_buffer_image_tiff_get(parse_query_value<int64_t>(req, "id"), res);
} catch (const std::exception &e) {
auto [c, s] = handleOperationException(e);
send_plain(res, c, s);
}
});
server.Get("/image_buffer/start.cbor", bind_noarg(&JFJochBrokerHttp::image_buffer_start_cbor_get));
server.Get("/image_buffer/status", bind_noarg(&JFJochBrokerHttp::image_buffer_status_get));
server.Get("/image_pusher/status", bind_noarg(&JFJochBrokerHttp::image_pusher_status_get));
server.Post("/initialize", bind_noarg(&JFJochBrokerHttp::initialize_post));
server.Post("/pedestal", bind_noarg(&JFJochBrokerHttp::pedestal_post));
server.Get("/preview/pedestal.tiff", [this](const httplib::Request &req, httplib::Response &res) {
try {
preview_pedestal_tiff_get(parse_query_value<int32_t>(req, "gain_level"),
parse_query_value<int32_t>(req, "sc"),
res);
} catch (const std::exception &e) {
auto [c, s] = handleOperationException(e);
send_plain(res, c, s);
}
});
server.Get("/preview/plot.bin", [this](const httplib::Request &req, httplib::Response &res) {
try {
preview_plot_bin_get(parse_query_string(req, "type"),
parse_query_string(req, "roi"),
res);
} catch (const std::exception &e) {
auto [c, s] = handleOperationException(e);
send_plain(res, c, s);
}
});
server.Get("/preview/plot", [this](const httplib::Request &req, httplib::Response &res) {
try {
preview_plot_get(parse_query_string(req, "type"),
parse_query_value<int32_t>(req, "binning"),
parse_query_value<float>(req, "fill"),
parse_query_value<bool>(req, "experimental_coord"),
parse_query_string(req, "azint_unit"),
res);
} catch (const std::exception &e) {
auto [c, s] = handleOperationException(e);
send_plain(res, c, s);
}
});
server.Get("/result/scan", bind_noarg(&JFJochBrokerHttp::result_scan_get));
server.Post("/start", bind_json(&JFJochBrokerHttp::start_post, Dataset_settings{}));
server.Get("/statistics/calibration", bind_noarg(&JFJochBrokerHttp::statistics_calibration_get));
server.Get("/statistics/data_collection", bind_noarg(&JFJochBrokerHttp::statistics_data_collection_get));
server.Get("/statistics", [this](const httplib::Request &req, httplib::Response &res) {
try {
statistics_get(res);
} catch (const std::exception &e) {
auto [c, s] = handleOperationException(e);
send_plain(res, c, s);
}
});
server.Get("/status", bind_noarg(&JFJochBrokerHttp::status_get));
server.Post("/trigger", bind_noarg(&JFJochBrokerHttp::trigger_post));
server.Get("/version", bind_noarg(&JFJochBrokerHttp::version_get));
server.Post("/wait_till_done", [this](const httplib::Request &req, httplib::Response &res) {
try {
wait_till_done_post(parse_query_value<int32_t>(req, "timeout"), res);
} catch (const std::exception &e) {
auto [c, s] = handleOperationException(e);
send_plain(res, c, s);
}
});
server.Post("/wait_until_running", [this](const httplib::Request &req, httplib::Response &res) {
try {
wait_until_running_post(parse_query_value<int32_t>(req, "timeout"), res);
} catch (const std::exception &e) {
auto [c, s] = handleOperationException(e);
send_plain(res, c, s);
}
});
server.Get("/xfel/event_code", bind_noarg(&JFJochBrokerHttp::xfel_event_code_get));
server.Get("/xfel/pulse_id", bind_noarg(&JFJochBrokerHttp::xfel_pulse_id_get));
}
void JFJochBrokerHttp::cancel_post(httplib::Response &response) {
state_machine.Cancel();
response.status = 200;
}
void JFJochBrokerHttp::deactivate_post(httplib::Response &response) {
state_machine.Deactivate();
response.status = 200;
}
void JFJochBrokerHttp::initialize_post(httplib::Response &response) {
state_machine.Initialize();
response.status = 200;
}
void JFJochBrokerHttp::start_post(const Dataset_settings &datasetSettings, httplib::Response &response) {
nlohmann::json j = datasetSettings;
logger.Info("Start {}", j.dump());
state_machine.Start(Convert(datasetSettings), datasetSettings.isAsyncStart());
response.status = 200;
}
void JFJochBrokerHttp::status_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetStatus()), response);
}
void JFJochBrokerHttp::wait_until_running_post(const std::optional<int32_t> &timeout, httplib::Response &response) {
BrokerStatus status;
if (!timeout)
status = state_machine.WaitTillNotBusy(std::chrono::minutes(1));
else if ((timeout.value() > 3600) || (timeout.value() < 0)) {
response.status = 400;
response.set_content("timeout must be in range 0..3600", "text/plain");
return;
} else
// A zero timeout still goes through the wait function rather than GetStatus(): the predicate
// is evaluated before the wait expires, so the answer is the same, but a start that failed
// asynchronously is reported instead of being read back as a plain state.
status = state_machine.WaitTillNotBusy(std::chrono::seconds(timeout.value()));
logger.Info("Wait until running");
switch (status.state) {
case JFJochState::Measuring:
response.status = 200;
break;
case JFJochState::Inactive:
response.status = 502;
break;
case JFJochState::Error:
throw WrongDAQStateException(status.message.value_or("Unknown error"));
case JFJochState::Idle:
case JFJochState::Busy:
case JFJochState::Calibration:
response.status = 504;
break;
}
}
void JFJochBrokerHttp::wait_till_done_post(const std::optional<int32_t> &timeout, httplib::Response &response) {
BrokerStatus status;
if (!timeout)
status = state_machine.WaitTillMeasurementDone(std::chrono::minutes(1));
else if ((timeout.value() > 3600) || (timeout.value() < 0)) {
response.status = 400;
response.set_content("timeout must be in range 0..3600", "text/plain");
return;
} else
status = state_machine.WaitTillMeasurementDone(std::chrono::seconds(timeout.value()));
logger.Info("Wait till done");
switch (status.state) {
case JFJochState::Idle:
response.status = 200;
break;
case JFJochState::Inactive:
response.status = 502;
break;
case JFJochState::Error:
throw WrongDAQStateException(status.message.value_or("Unknown error"));
case JFJochState::Measuring:
case JFJochState::Busy:
case JFJochState::Calibration:
response.status = 504;
break;
}
}
void JFJochBrokerHttp::trigger_post(httplib::Response &response) {
state_machine.Trigger();
response.status = 200;
}
void JFJochBrokerHttp::pedestal_post(httplib::Response &response) {
state_machine.Pedestal();
response.status = 200;
}
void JFJochBrokerHttp::config_detector_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetDetectorSettings()), response);
}
void JFJochBrokerHttp::config_detector_put(const Detector_settings &detectorSettings,
httplib::Response &response) {
state_machine.LoadDetectorSettings(Convert(detectorSettings));
response.status = 200;
}
void JFJochBrokerHttp::config_azim_int_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetRadialIntegrationSettings()), response);
}
void JFJochBrokerHttp::config_azim_int_put(const Azim_int_settings &radIntSettings,
httplib::Response &response) {
state_machine.SetRadialIntegrationSettings(Convert(radIntSettings));
response.status = 200;
}
void JFJochBrokerHttp::config_select_detector_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetDetectorsList()), response);
}
void JFJochBrokerHttp::config_select_detector_put(const Detector_selection &detectorSelection,
httplib::Response &response) {
state_machine.SelectDetector(detectorSelection.getId());
response.status = 200;
}
void JFJochBrokerHttp::config_spot_finding_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetSpotFindingSettings()), response);
}
void JFJochBrokerHttp::config_spot_finding_put(const Spot_finding_settings &spotFindingSettings,
httplib::Response &response) {
state_machine.SetSpotFindingSettings(Convert(spotFindingSettings));
response.status = 200;
}
void JFJochBrokerHttp::statistics_calibration_get(httplib::Response &response) {
nlohmann::json j;
for (const auto &d: Convert(state_machine.GetCalibrationStatistics()))
j.push_back(d);
logger.Info("Calibration statistics: {}", j.dump());
response.set_content(j.dump(), "application/json");
response.status = 200;
}
void JFJochBrokerHttp::statistics_data_collection_get(httplib::Response &response) {
auto stats = state_machine.GetMeasurementStatistics();
if (stats)
ProcessOutput(Convert(stats.value()), response);
else
response.status = 404;
}
void JFJochBrokerHttp::config_file_writer_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetFileWriterSettings()), response);
}
void JFJochBrokerHttp::config_file_writer_put(const File_writer_settings &fileWriterSettings,
httplib::Response &response) {
state_machine.LoadFileWriterSettings(Convert(fileWriterSettings));
response.status = 200;
}
void JFJochBrokerHttp::config_image_format_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetImageFormatSettings()), response);
}
void JFJochBrokerHttp::config_image_format_put(const Image_format_settings &imageFormatSettings,
httplib::Response &response) {
state_machine.LoadImageFormatSettings(Convert(imageFormatSettings));
response.status = 200;
}
void JFJochBrokerHttp::config_image_format_conversion_post(httplib::Response &response) {
state_machine.ConvImageFormatSettings();
response.status = 200;
}
void JFJochBrokerHttp::config_image_format_raw_post(httplib::Response &response) {
state_machine.RawImageFormatSettings();
response.status = 200;
}
void JFJochBrokerHttp::fpga_status_get(httplib::Response &response) {
nlohmann::json j;
for (const auto &d: Convert(state_machine.GetDeviceStatus()))
j.push_back(d);
response.set_content(j.dump(), "application/json");
response.status = 200;
}
void JFJochBrokerHttp::statistics_get(httplib::Response &response) {
Jfjoch_statistics statistics;
auto data_collection_statistics = state_machine.GetMeasurementStatistics();
if (data_collection_statistics)
statistics.setMeasurement(Convert(data_collection_statistics.value()));
statistics.setFpga(Convert(state_machine.GetDeviceStatus()));
statistics.setCalibration(Convert(state_machine.GetCalibrationStatistics()));
statistics.setBroker(Convert(state_machine.GetStatus()));
auto det_status = state_machine.GetDetectorStatus();
if (det_status.has_value())
statistics.setDetector(Convert(det_status.value()));
statistics.setDetectorSettings(Convert(state_machine.GetDetectorSettings()));
statistics.setDetectorList(Convert(state_machine.GetDetectorsList()));
statistics.setDataProcessingSettings(Convert(state_machine.GetSpotFindingSettings()));
statistics.setInstrumentMetadata(Convert(state_machine.GetInstrumentMetadata()));
statistics.setImageFormatSettings(Convert(state_machine.GetImageFormatSettings()));
statistics.setPixelMask(Convert(state_machine.GetPixelMaskStatistics()));
statistics.setRoi(Convert(state_machine.GetROIDefintion()));
statistics.setFileWriterSettings(Convert(state_machine.GetFileWriterSettings()));
statistics.setAzInt(Convert(state_machine.GetRadialIntegrationSettings()));
statistics.setBuffer(Convert(state_machine.GetImageBufferStatus()));
statistics.setIndexing(Convert(state_machine.GetIndexingSettings()));
statistics.setBraggIntegration(Convert(state_machine.GetBraggIntegrationSettings()));
statistics.setDarkMask(Convert(state_machine.GetDarkMaskSettings()));
statistics.setImagePusher(Convert(state_machine.GetImagePusherStatus()));
auto zeromq_prev = state_machine.GetPreviewSocketSettings();
if (!zeromq_prev.address.empty())
statistics.setZeromqPreview(Convert(zeromq_prev));
auto zeromq_metadata = state_machine.GetMetadataSocketSettings();
if (!zeromq_metadata.address.empty())
statistics.setZeromqMetadata(Convert(zeromq_metadata));
nlohmann::json j = statistics;
response.set_content(j.dump(), "application/json");
response.status = 200;
}
void JFJochBrokerHttp::config_zeromq_preview_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetPreviewSocketSettings()), response);
}
void JFJochBrokerHttp::config_zeromq_preview_put(const Zeromq_preview_settings &zeromqPreviewSettings,
httplib::Response &response) {
state_machine.SetPreviewSocketSettings(Convert(zeromqPreviewSettings));
response.status = 200;
}
void JFJochBrokerHttp::config_zeromq_metadata_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetMetadataSocketSettings()), response);
}
void JFJochBrokerHttp::config_zeromq_metadata_put(const Zeromq_metadata_settings &zeromqMetadataSettings,
httplib::Response &response) {
state_machine.SetMetadataSocketSettings(Convert(zeromqMetadataSettings));
response.status = 200;
}
void JFJochBrokerHttp::config_internal_generator_image_put(const std::optional<int64_t> &id,
const httplib::Request &request,
httplib::Response &response) {
int64_t image_number = id.value_or(0);
if ((image_number < 0) || (image_number > 127)) {
response.status = 400;
response.set_content("id must be in range 0-127", "text/plain");
return;
}
state_machine.LoadInternalGeneratorImage(request.body.data(), request.body.size(), image_number);
logger.Info("Internal generator image #{} loaded", image_number);
response.status = 200;
}
void JFJochBrokerHttp::config_internal_generator_image_tiff_put(const std::optional<int64_t> &id,
const httplib::Request &request,
httplib::Response &response) {
int64_t image_number = id.value_or(0);
if ((image_number < 0) || (image_number > 127)) {
response.status = 400;
response.set_content("id must be in range 0-127", "text/plain");
return;
}
state_machine.LoadInternalGeneratorImageTIFF(request.body, image_number);
response.status = 200;
}
void JFJochBrokerHttp::image_buffer_clear_post(httplib::Response &response) {
state_machine.ClearImageBuffer();
response.status = 200;
}
void JFJochBrokerHttp::image_buffer_image_cbor_get(const std::optional<int64_t> &imageNumber,
httplib::Response &response) {
std::vector<uint8_t> tmp_vector;
state_machine.GetImageFromBuffer(tmp_vector, imageNumber.value_or(-1));
std::string s(reinterpret_cast<char *>(tmp_vector.data()), tmp_vector.size());
if (!s.empty())
response.set_content(s, "application/cbor");
else
response.status = 404;
}
void JFJochBrokerHttp::image_buffer_image_jpeg_get(const std::optional<int64_t> &id,
const std::optional<bool> &showUserMask,
const std::optional<bool> &showRoi,
const std::optional<bool> &showSpots,
const std::optional<bool> &showPredictions,
const std::optional<bool> &showBeamCenter,
const std::optional<float> &saturation,
const std::optional<int64_t> &jpegQuality,
const std::optional<float> &showResRing,
const std::optional<std::string> &color,
const std::optional<bool> &showResEst,
httplib::Response &response) {
int64_t image_id = id.value_or(ImageBuffer::MaxImage);
PreviewImageSettings settings{};
settings.show_user_mask = showUserMask.value_or(false);
settings.show_roi = showRoi.value_or(false);
settings.show_spots = showSpots.value_or(true);
settings.show_predictions = showPredictions.value_or(false);
settings.saturation_value = saturation;
settings.background_value = 0.0;
settings.jpeg_quality = jpegQuality.value_or(100);
if (showResEst.value_or(false)) {
settings.show_res_est = true;
} else {
settings.show_res_est = false;
settings.resolution_ring = showResRing;
}
settings.scale = ConvertColorScale(color);
settings.show_beam_center = showBeamCenter.value_or(true);
settings.format = PreviewImageFormat::JPEG;
std::string s = state_machine.GetPreviewJPEG(settings, image_id);
if (!s.empty())
response.set_content(s, "image/jpeg");
else
response.status = 404;
}
void JFJochBrokerHttp::image_buffer_image_tiff_get(const std::optional<int64_t> &id,
httplib::Response &response) {
int64_t image_id = id.value_or(ImageBuffer::MaxImage);
std::string s = state_machine.GetPreviewTIFF(image_id);
if (!s.empty())
response.set_content(s, "image/tiff");
else
response.status = 404;
}
void JFJochBrokerHttp::image_buffer_start_cbor_get(httplib::Response &response) {
std::vector<uint8_t> tmp_vector;
state_machine.GetStartMessageFromBuffer(tmp_vector);
std::string s(reinterpret_cast<char *>(tmp_vector.data()), tmp_vector.size());
if (!s.empty())
response.set_content(s, "application/cbor");
else
response.status = 404;
}
void JFJochBrokerHttp::image_buffer_status_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetImageBufferStatus()), response);
}
void JFJochBrokerHttp::image_pusher_status_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetImagePusherStatus()), response);
}
void JFJochBrokerHttp::config_mask_tiff_get(httplib::Response &response) {
auto s = state_machine.GetFullPixelMaskTIFF();
response.set_content(s, "image/tiff");
response.status = 200;
}
void JFJochBrokerHttp::config_user_mask_tiff_get(httplib::Response &response) {
auto s = state_machine.GetUserPixelMaskTIFF();
response.set_content(s, "image/tiff");
response.status = 200;
}
void JFJochBrokerHttp::config_user_mask_tiff_put(const httplib::Request &request,
httplib::Response &response) {
std::vector<uint8_t> buffer;
state_machine.SetUserPixelMask(ReadTIFF(request.body, buffer));
response.status = 200;
}
void JFJochBrokerHttp::config_mask_get(httplib::Response &response) {
const auto v = state_machine.GetFullPixelMask();
response.set_content(reinterpret_cast<const char *>(v.data()),
v.size() * sizeof(uint32_t),
"application/octet-stream");
response.status = 200;
}
void JFJochBrokerHttp::config_user_mask_get(httplib::Response &response) {
const auto v = state_machine.GetUserPixelMask();
response.set_content(reinterpret_cast<const char *>(v.data()),
v.size() * sizeof(uint32_t),
"application/octet-stream");
response.status = 200;
}
void JFJochBrokerHttp::config_user_mask_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() % 4 != 0) {
response.status = 400;
response.set_content("Request has to be 32-bit", "text/plain");
return;
}
std::vector<uint32_t> v(request.body.size() / 4);
std::memcpy(v.data(), request.body.data(), request.body.size());
state_machine.SetUserPixelMask(v);
response.status = 200;
}
void JFJochBrokerHttp::version_get(httplib::Response &response) {
response.set_content(jfjoch_version(), "text/plain");
response.status = 200;
}
void JFJochBrokerHttp::config_instrument_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetInstrumentMetadata()), response);
}
void JFJochBrokerHttp::config_instrument_put(const Instrument_metadata &instrumentMetadata,
httplib::Response &response) {
state_machine.LoadInstrumentMetadata(Convert(instrumentMetadata));
response.status = 200;
}
void JFJochBrokerHttp::config_indexing_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetIndexingSettings()), response);
}
void JFJochBrokerHttp::config_indexing_put(const Indexing_settings &indexingSettings,
httplib::Response &response) {
state_machine.SetIndexingSettings(Convert(indexingSettings));
response.status = 200;
}
void JFJochBrokerHttp::config_bragg_integration_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetBraggIntegrationSettings()), response);
}
void JFJochBrokerHttp::config_bragg_integration_put(const Bragg_integration_settings &braggIntegrationSettings,
httplib::Response &response) {
state_machine.SetBraggIntegrationSettings(Convert(braggIntegrationSettings));
response.status = 200;
}
void JFJochBrokerHttp::result_scan_get(httplib::Response &response) {
auto ret = state_machine.GetScanResult();
if (ret.has_value())
ProcessOutput(Convert(ret.value()), response);
else
response.status = 404;
}
void JFJochBrokerHttp::config_dark_mask_put(const Dark_mask_settings &darkMaskSettings,
httplib::Response &response) {
state_machine.SetDarkMaskSettings(Convert(darkMaskSettings));
response.status = 200;
}
void JFJochBrokerHttp::config_dark_mask_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetDarkMaskSettings()), response);
}
void JFJochBrokerHttp::detector_status_get(httplib::Response &response) {
auto out = state_machine.GetDetectorStatus();
if (out)
ProcessOutput(Convert(out.value()), response);
else
response.status = 404;
}
void JFJochBrokerHttp::preview_pedestal_tiff_get(const std::optional<int32_t> &gainLevel,
const std::optional<int32_t> &sc,
httplib::Response &response) {
if (!gainLevel) {
response.status = 400;
response.set_content("gain_level is required", "text/plain");
return;
}
std::string s = state_machine.GetPedestalTIFF(gainLevel.value(), sc ? sc.value() : 0);
if (!s.empty())
response.set_content(s, "image/tiff");
else
response.status = 404;
}
void JFJochBrokerHttp::preview_plot_bin_get(const std::optional<std::string> &type,
const std::optional<std::string> &roi,
httplib::Response &response) {
std::vector<float> ret;
state_machine.GetPlotRaw(ret, ConvertPlotType(type), roi.value_or(""));
if (ret.empty()) {
response.status = 404;
return;
}
response.set_content(reinterpret_cast<const char *>(ret.data()),
ret.size() * sizeof(float),
"application/octet-stream");
response.status = 200;
}
void JFJochBrokerHttp::preview_plot_get(const std::optional<std::string> &type,
const std::optional<int32_t> &binning,
const std::optional<float> &fill,
const std::optional<bool> &experimentalCoord,
const std::optional<std::string> &azintUnit,
httplib::Response &response) {
PlotAzintUnit unit = PlotAzintUnit::Q_recipA;
if (azintUnit.has_value()) {
if (azintUnit == "Q_recipA" || azintUnit == "q_recipa")
unit = PlotAzintUnit::Q_recipA;
else if (azintUnit == "d_A" || azintUnit == "d_a")
unit = PlotAzintUnit::d_A;
else if (azintUnit == "two_theta_deg")
unit = PlotAzintUnit::TwoTheta_deg;
}
PlotRequest req{
.type = ConvertPlotType(type),
.binning = 1,
.experimental_coord = experimentalCoord.value_or(false),
.azint_unit = unit,
.fill_value = fill
};
if (binning) {
if (binning.value() < 0) {
response.status = 400;
response.set_content("Binning cannot be negative", "text/plain");
return;
}
req.binning = binning.value();
}
auto plot = state_machine.GetPlots(req);
ProcessOutput(Convert(plot), response);
}
void JFJochBrokerHttp::config_roi_get(httplib::Response &response) {
ProcessOutput(Convert(state_machine.GetROIDefintion()), response);
}
void JFJochBrokerHttp::config_roi_put(const Roi_definitions &roiDefinitions,
httplib::Response &response) {
state_machine.SetROIDefinition(Convert(roiDefinitions));
response.status = 200;
}
void JFJochBrokerHttp::xfel_event_code_get(httplib::Response &response) {
auto array = state_machine.GetXFELEventCode();
if (array.empty()) {
response.status = 404;
return;
}
nlohmann::json j = array;
response.set_content(j.dump(), "application/json");
response.status = 200;
}
void JFJochBrokerHttp::xfel_pulse_id_get(httplib::Response &response) {
auto array = state_machine.GetXFELPulseID();
if (array.empty()) {
response.status = 404;
return;
}
nlohmann::json j = array;
response.set_content(j.dump(), "application/json");
response.status = 200;
}