From b2c60dfbd0888b0aff038a02fdb38b97998a1d76 Mon Sep 17 00:00:00 2001 From: leonarski_f Date: Fri, 19 Jun 2026 14:47:10 +0200 Subject: [PATCH] viewer: download/upload ROIs to the broker Add Download and "Upload to server" buttons to the ROI panel. The HTTP reader gains GetROIDefinitions (GET /config/roi) and UploadROIDefinitions (PUT /config/roi), converting between ROIDefinition and the generated Roi_definitions model (including the optional azimuthal phi sector), the same shapes OpenAPIConvert uses on the server. Download applies the fetched ROIs through SetROIDefinition; upload pushes the current ones. Both are no-ops unless the viewer is connected to a broker. Co-Authored-By: Claude Opus 4.8 --- reader/JFJochHttpReader.cpp | 89 ++++++++++++++++++++++++++ reader/JFJochHttpReader.h | 4 ++ viewer/JFJochImageReadingWorker.cpp | 22 +++++++ viewer/JFJochImageReadingWorker.h | 2 + viewer/JFJochViewerSidePanel.cpp | 2 + viewer/JFJochViewerSidePanel.h | 2 + viewer/JFJochViewerWindow.cpp | 4 ++ viewer/widgets/JFJochViewerROIList.cpp | 9 +++ viewer/widgets/JFJochViewerROIList.h | 2 + 9 files changed, 136 insertions(+) diff --git a/reader/JFJochHttpReader.cpp b/reader/JFJochHttpReader.cpp index dd383d27..fe1a1f6d 100644 --- a/reader/JFJochHttpReader.cpp +++ b/reader/JFJochHttpReader.cpp @@ -9,6 +9,8 @@ #include "../broker/gen/model/Image_buffer_status.h" #include "../broker/gen/model/Plots.h" #include "../broker/gen/model/Broker_status.h" +#include "../broker/gen/model/Roi_definitions.h" +#include "../common/JFJochMath.h" #include "../image_analysis/bragg_integration/CalcISigma.h" void JFJochHttpReader::Close() { @@ -402,6 +404,93 @@ void JFJochHttpReader::UploadUserMask(const std::vector& mask) { "Server rejected user mask upload"); } +ROIDefinition JFJochHttpReader::GetROIDefinitions() const { + std::unique_lock ul(http_mutex); + if (addr.empty()) + throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "HTTP address not set"); + + httplib::Client cli_cmd(addr); + auto res = cli_cmd.Get("/config/roi"); + if (!res || res->status != httplib::StatusCode::OK_200) + throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Could not get ROI definitions"); + + org::openapitools::server::model::Roi_definitions input = nlohmann::json::parse(res->body); + ROIDefinition out; + for (const auto &i : input.getBox().getRois()) + out.boxes.emplace_back(i.getName(), i.getMinXPxl(), i.getMaxXPxl(), i.getMinYPxl(), i.getMaxYPxl()); + for (const auto &i : input.getCircle().getRois()) + out.circles.emplace_back(i.getName(), i.getCenterXPxl(), i.getCenterYPxl(), i.getRadiusPxl()); + for (const auto &i : input.getAzim().getRois()) { + float phi_min = 0, phi_max = 0; + if (i.phiMinDegIsSet() && i.phiMaxDegIsSet()) { + phi_min = i.getPhiMinDeg(); + phi_max = i.getPhiMaxDeg(); + } + const float d_min = (i.getQMaxRecipA() == 0.0f) ? 0.0f : 2.0f * static_cast(PI) / i.getQMaxRecipA(); + const float d_max = (i.getQMinRecipA() == 0.0f) ? 0.0f : 2.0f * static_cast(PI) / i.getQMinRecipA(); + out.azimuthal.emplace_back(i.getName(), d_min, d_max, phi_min, phi_max); + } + return out; +} + +void JFJochHttpReader::UploadROIDefinitions(const ROIDefinition &rois) const { + std::unique_lock ul(http_mutex); + if (addr.empty()) + throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "HTTP address not set"); + + namespace model = org::openapitools::server::model; + model::Roi_definitions out; + + model::Roi_box_list bl; + std::vector boxes; + for (const auto &b : rois.boxes) { + model::Roi_box e; + e.setName(b.GetName()); + e.setMinXPxl(b.GetXMin()); e.setMaxXPxl(b.GetXMax()); + e.setMinYPxl(b.GetYMin()); e.setMaxYPxl(b.GetYMax()); + boxes.push_back(e); + } + bl.setRois(boxes); + out.setBox(bl); + + model::Roi_circle_list cl; + std::vector circles; + for (const auto &c : rois.circles) { + model::Roi_circle e; + e.setName(c.GetName()); + e.setCenterXPxl(c.GetX()); e.setCenterYPxl(c.GetY()); + e.setRadiusPxl(c.GetRadius_pxl()); + circles.push_back(e); + } + cl.setRois(circles); + out.setCircle(cl); + + model::Roi_azim_list al; + std::vector azim; + for (const auto &a : rois.azimuthal) { + model::Roi_azimuthal e; + e.setName(a.GetName()); + e.setQMinRecipA(a.GetQMin_recipA()); + e.setQMaxRecipA(a.GetQMax_recipA()); + if (a.HasPhi()) { + e.setPhiMinDeg(a.GetPhiMin_deg()); + e.setPhiMaxDeg(a.GetPhiMax_deg()); + } + azim.push_back(e); + } + al.setRois(azim); + out.setAzim(al); + + nlohmann::json j = out; + httplib::Client cli_cmd(addr); + auto res = cli_cmd.Put("/config/roi", j.dump(), "application/json"); + if (!res) + throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, + "Failed to connect to server to upload ROIs"); + if (res->status != httplib::StatusCode::OK_200) + throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Server rejected ROI upload"); +} + std::vector JFJochHttpReader::ReadSpots(int64_t image) const { std::unique_lock ul(http_mutex); diff --git a/reader/JFJochHttpReader.h b/reader/JFJochHttpReader.h index e558d98a..84b8876c 100644 --- a/reader/JFJochHttpReader.h +++ b/reader/JFJochHttpReader.h @@ -6,6 +6,7 @@ #include "JFJochReader.h" #include "../common/BrokerStatus.h" #include "../common/ImageBuffer.h" +#include "../common/ROIDefinition.h" class JFJochHttpReader : public JFJochReader { mutable std::mutex http_mutex; @@ -36,6 +37,9 @@ public: void UploadUserMask(const std::vector& mask); + [[nodiscard]] ROIDefinition GetROIDefinitions() const; // GET /config/roi + void UploadROIDefinitions(const ROIDefinition &rois) const; // PUT /config/roi + BrokerStatus GetBrokerStatus() const; std::shared_ptr GetRawImage(int64_t image_number) override; diff --git a/viewer/JFJochImageReadingWorker.cpp b/viewer/JFJochImageReadingWorker.cpp index a6ba76b2..015f47b3 100644 --- a/viewer/JFJochImageReadingWorker.cpp +++ b/viewer/JFJochImageReadingWorker.cpp @@ -600,6 +600,28 @@ void JFJochImageReadingWorker::SetROIDefinition(const ROIDefinition &rois) { SetROIDefinition_i(rois); } +void JFJochImageReadingWorker::DownloadROIsFromServer() { + QMutexLocker locker(&m); + if (!http_mode) + return; + try { + SetROIDefinition_i(http_reader.GetROIDefinitions()); + } catch (const std::exception &e) { + logger.Error("Download ROIs from server failed: {}", e.what()); + } +} + +void JFJochImageReadingWorker::UploadROIsToServer() { + QMutexLocker locker(&m); + if (!http_mode) + return; + try { + http_reader.UploadROIDefinitions(curr_experiment.ROI().GetROIDefinition()); + } catch (const std::exception &e) { + logger.Error("Upload ROIs to server failed: {}", e.what()); + } +} + void JFJochImageReadingWorker::SetROIDefinition_i(const ROIDefinition &rois) { // The worker experiment is the source of truth; the analysis ROI engine is rebuilt // for it so newly loaded images pick up the change automatically. From now on the diff --git a/viewer/JFJochImageReadingWorker.h b/viewer/JFJochImageReadingWorker.h index b9e546a0..e4857d49 100644 --- a/viewer/JFJochImageReadingWorker.h +++ b/viewer/JFJochImageReadingWorker.h @@ -171,6 +171,8 @@ public slots: void SubtractROIFromUserMask(); void SetROIDefinition(const ROIDefinition &rois); + void DownloadROIsFromServer(); + void UploadROIsToServer(); void SaveUserMaskTIFF(QString filename); void UploadUserMask(); diff --git a/viewer/JFJochViewerSidePanel.cpp b/viewer/JFJochViewerSidePanel.cpp index c944549c..e4e34f77 100644 --- a/viewer/JFJochViewerSidePanel.cpp +++ b/viewer/JFJochViewerSidePanel.cpp @@ -131,6 +131,8 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) connect(this, &JFJochViewerSidePanel::imageLoaded, roi_list, &JFJochViewerROIList::loadImage); connect(roi_list, &JFJochViewerROIList::roisChanged, this, &JFJochViewerSidePanel::roisChanged); connect(roi_list, &JFJochViewerROIList::selectedROIChanged, this, &JFJochViewerSidePanel::selectedROIChanged); + connect(roi_list, &JFJochViewerROIList::downloadROIs, this, &JFJochViewerSidePanel::downloadROIs); + connect(roi_list, &JFJochViewerROIList::uploadROIs, this, &JFJochViewerSidePanel::uploadROIs); layout->addWidget(new TitleLabel("Data analysis", this)); diff --git a/viewer/JFJochViewerSidePanel.h b/viewer/JFJochViewerSidePanel.h index 1886ba1a..549d9d8a 100644 --- a/viewer/JFJochViewerSidePanel.h +++ b/viewer/JFJochViewerSidePanel.h @@ -33,6 +33,8 @@ signals: void showROIFill(bool input); void roisChanged(ROIDefinition rois); void selectedROIChanged(QString name); + void downloadROIs(); + void uploadROIs(); void findBeamCenter(const UnitCell &input, bool guess); void analyze(); diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index 885d3183..0f2f04f5 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -197,6 +197,10 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString reading_worker, &JFJochImageReadingWorker::SetROIDefinition); connect(viewer, &JFJochDiffractionImage::roiSelected, side_panel, &JFJochViewerSidePanel::selectROIInList); + connect(side_panel, &JFJochViewerSidePanel::downloadROIs, + reading_worker, &JFJochImageReadingWorker::DownloadROIsFromServer); + connect(side_panel, &JFJochViewerSidePanel::uploadROIs, + reading_worker, &JFJochImageReadingWorker::UploadROIsToServer); connect(menuBar, &JFJochViewerMenu::clearUserMaskSelected, reading_worker, &JFJochImageReadingWorker::ClearUserMask); diff --git a/viewer/widgets/JFJochViewerROIList.cpp b/viewer/widgets/JFJochViewerROIList.cpp index 8edbb706..178e0b95 100644 --- a/viewer/widgets/JFJochViewerROIList.cpp +++ b/viewer/widgets/JFJochViewerROIList.cpp @@ -36,6 +36,15 @@ JFJochViewerROIList::JFJochViewerROIList(QWidget *parent) : QWidget(parent) { connect(ren, &QPushButton::clicked, this, &JFJochViewerROIList::OnRename); connect(del, &QPushButton::clicked, this, &JFJochViewerROIList::OnDelete); + auto *server = new QHBoxLayout(); + auto *download = new QPushButton("Download", this); + auto *upload = new QPushButton("Upload to server", this); + server->addWidget(download); + server->addWidget(upload); + layout->addLayout(server); + connect(download, &QPushButton::clicked, this, &JFJochViewerROIList::downloadROIs); + connect(upload, &QPushButton::clicked, this, &JFJochViewerROIList::uploadROIs); + auto *grid = new QGridLayout(); for (int i = 0; i < 4; i++) { flabel_[i] = new QLabel("", this); diff --git a/viewer/widgets/JFJochViewerROIList.h b/viewer/widgets/JFJochViewerROIList.h index 5a6afe41..ae468ae2 100644 --- a/viewer/widgets/JFJochViewerROIList.h +++ b/viewer/widgets/JFJochViewerROIList.h @@ -50,4 +50,6 @@ private slots: signals: void roisChanged(ROIDefinition rois); void selectedROIChanged(QString name); + void downloadROIs(); // fetch ROIs from the connected broker + void uploadROIs(); // push ROIs to the connected broker };