From 6f3c7348ca83cb983f31ed8cd49428c2067b6043 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Fri, 14 Nov 2025 20:20:19 +0100 Subject: [PATCH] jfjoch_viewer: ROI calculated inside image viewer --- common/JFJochMessages.h | 1 + reader/JFJochReaderImage.cpp | 45 +---------- reader/JFJochReaderImage.h | 8 -- viewer/JFJochImageReadingWorker.cpp | 21 +---- viewer/JFJochImageReadingWorker.h | 1 - viewer/JFJochViewerImageROIStatistics.cpp | 56 +++++++------- viewer/JFJochViewerImageROIStatistics.h | 3 +- viewer/JFJochViewerSidePanel.cpp | 5 +- viewer/JFJochViewerSidePanel.h | 1 + viewer/JFJochViewerWindow.cpp | 3 + viewer/widgets/JFJochImage.cpp | 93 ++++++++++++++++++++++- viewer/widgets/JFJochImage.h | 10 ++- 12 files changed, 140 insertions(+), 107 deletions(-) diff --git a/common/JFJochMessages.h b/common/JFJochMessages.h index 55afd02f..58403e6f 100644 --- a/common/JFJochMessages.h +++ b/common/JFJochMessages.h @@ -69,6 +69,7 @@ struct ROIMessage { uint64_t sum_square; int64_t max_count; uint64_t pixels; + uint64_t pixels_masked; // only used in the viewer for now int64_t x_weighted; int64_t y_weighted; }; diff --git a/reader/JFJochReaderImage.cpp b/reader/JFJochReaderImage.cpp index 3a4e8087..49aacd89 100644 --- a/reader/JFJochReaderImage.cpp +++ b/reader/JFJochReaderImage.cpp @@ -23,8 +23,7 @@ JFJochReaderImage::JFJochReaderImage(const JFJochReaderImage &other) message(other.message), saturated_pixel(other.saturated_pixel), error_pixel(other.error_pixel), - valid_pixel(other.valid_pixel), - roi(other.roi) { + valid_pixel(other.valid_pixel) { // Need to make image use local copy message.image = CompressedImage(image, dataset->experiment.GetXPixelsNum(), dataset->experiment.GetYPixelsNum()); @@ -201,48 +200,6 @@ void JFJochReaderImage::AddImage(const JFJochReaderImage &other) { CalcAutoContrast(); } -void JFJochReaderImage::CalcROI(const ROIElement *input) { - std::unique_lock ul(roi_mutex); - - if (!input) { - roi = {}; - return; - } - - int64_t width = dataset->experiment.GetXPixelsNum(); - int64_t height = dataset->experiment.GetYPixelsNum(); - - int64_t roi_val = 0; - uint64_t roi_val_2 = 0; - int64_t roi_max = INT64_MIN; - uint64_t roi_npixel = 0; - - for (int64_t y = 0; y < height; y++) { - for (int64_t x = 0; x < width; x++) { - int32_t val = image[x + width * y]; - if (input->CheckROI(x, y, 0.0) && (val != SATURATED_PXL_VALUE) - && (val != ERROR_PXL_VALUE) && (val != GAP_PXL_VALUE)) { - roi_val += val; - roi_val_2 += val * val; - if (val > roi_max) roi_max = val; - roi_npixel++; - } - } - } - - roi = ROIMessage{ - .sum = roi_val, - .sum_square = roi_val_2, - .max_count = roi_max, - .pixels = roi_npixel - }; -} - -const std::optional &JFJochReaderImage::GetROI() const { - std::unique_lock ul(roi_mutex); - return roi; -} - std::vector JFJochReaderImage::GetAzInt1D() const { if (dataset->azimuthal_bins <= 1) { return message.az_int_profile; diff --git a/reader/JFJochReaderImage.h b/reader/JFJochReaderImage.h index fc4c57c6..88073af2 100644 --- a/reader/JFJochReaderImage.h +++ b/reader/JFJochReaderImage.h @@ -25,8 +25,6 @@ class JFJochReaderImage { std::unordered_set saturated_pixel; std::unordered_set error_pixel; std::vector> valid_pixel; - std::optional roi; - mutable std::mutex roi_mutex; constexpr static float auto_foreground_range = 0.001f; int32_t auto_foreground; @@ -48,15 +46,9 @@ public: const std::vector> &ValidPixels() const; const JFJochReaderDataset &Dataset() const; - const std::optional &GetROI() const; - void AddImage(const JFJochReaderImage& other); std::vector GetAzInt1D() const; std::vector GetAzInt1D_BinToQ() const; - - // Functions that change the content - void CalcROI(const ROIElement *input); - std::shared_ptr CreateMutableDataset(); int32_t GetAutoContrastValue() const; diff --git a/viewer/JFJochImageReadingWorker.cpp b/viewer/JFJochImageReadingWorker.cpp index 1b21e29f..9e68e6dc 100644 --- a/viewer/JFJochImageReadingWorker.cpp +++ b/viewer/JFJochImageReadingWorker.cpp @@ -98,13 +98,6 @@ void JFJochImageReadingWorker::LoadImage(int64_t image_number, int64_t summation LoadImage_i(image_number, summation); } -void JFJochImageReadingWorker::CalcROI_i() { - if (current_image_ptr) { - if (roi) - current_image_ptr->CalcROI(roi.get()); - } -} - void JFJochImageReadingWorker::UpdateAzint_i(const JFJochReaderDataset *dataset) { if (dataset) { azint_mapping = std::make_unique(curr_experiment, dataset->pixel_mask); @@ -143,8 +136,6 @@ void JFJochImageReadingWorker::LoadImage_i(int64_t image_number, int64_t summati auto end = std::chrono::high_resolution_clock::now(); - CalcROI_i(); - if (auto_reanalyze) ReanalyzeImage_i(); @@ -168,11 +159,6 @@ void JFJochImageReadingWorker::SetROIBox(QRect box) { roi.reset(); roi = std::make_unique("roi1", box.left(), box.right(), box.bottom(), box.top()); - - if (current_image_ptr) { - current_image_ptr->CalcROI(roi.get()); - emit imageStatsUpdated(current_image_ptr); - } } void JFJochImageReadingWorker::SetROICircle(double x, double y, double radius) { @@ -182,11 +168,6 @@ void JFJochImageReadingWorker::SetROICircle(double x, double y, double radius) { roi.reset(); else roi = std::make_unique("roi1", x, y, radius); - - if (current_image_ptr) { - current_image_ptr->CalcROI(roi.get()); - emit imageStatsUpdated(current_image_ptr); - } } void JFJochImageReadingWorker::UpdateDataset_i(const std::optional &experiment) { @@ -212,7 +193,7 @@ void JFJochImageReadingWorker::UpdateDataset_i(const std::optional(current_image_ptr->ImageData(), dataset); - CalcROI_i(); + if (auto_reanalyze) ReanalyzeImage_i(); emit imageLoaded(current_image_ptr); diff --git a/viewer/JFJochImageReadingWorker.h b/viewer/JFJochImageReadingWorker.h index f929034d..0c70f1bf 100644 --- a/viewer/JFJochImageReadingWorker.h +++ b/viewer/JFJochImageReadingWorker.h @@ -69,7 +69,6 @@ private: int autoload_interval = 500; // milliseconds void LoadImage_i(int64_t image_number, int64_t summation); - void CalcROI_i(); void ReanalyzeImage_i(); void UpdateDataset_i(const std::optional& experiment); void UpdateAzint_i(const JFJochReaderDataset *dataset); diff --git a/viewer/JFJochViewerImageROIStatistics.cpp b/viewer/JFJochViewerImageROIStatistics.cpp index e7756223..2c1451f5 100644 --- a/viewer/JFJochViewerImageROIStatistics.cpp +++ b/viewer/JFJochViewerImageROIStatistics.cpp @@ -39,19 +39,24 @@ JFJochViewerImageROIStatistics::JFJochViewerImageROIStatistics(QWidget *parent) circle_settings->Disable(); box_radio->setChecked(true); - QHBoxLayout *label_row = new QHBoxLayout(); + QHBoxLayout *label_row_1 = new QHBoxLayout(); + QHBoxLayout *label_row_2 = new QHBoxLayout(); roi_sum = new QLabel("", this); - label_row->addWidget(roi_sum); + label_row_1->addWidget(roi_sum); roi_mean = new QLabel("", this); - label_row->addWidget(roi_mean); + label_row_1->addWidget(roi_mean); roi_var = new QLabel("", this); - label_row->addWidget(roi_var); + label_row_1->addWidget(roi_var); roi_max = new QLabel("", this); - label_row->addWidget(roi_max); + label_row_1->addWidget(roi_max); roi_npixel = new QLabel("", this); - label_row->addWidget(roi_npixel); - layout->addLayout(label_row); + label_row_2->addWidget(roi_npixel); + roi_masked = new QLabel("", this); + label_row_2->addWidget(roi_masked); + + layout->addLayout(label_row_1); + layout->addLayout(label_row_2); QPushButton *add_button = new QPushButton("Add ROI to user mask", this); connect(add_button, &QPushButton::clicked, [this]() { emit AddROIToUserMask(); }); @@ -68,36 +73,29 @@ JFJochViewerImageROIStatistics::JFJochViewerImageROIStatistics(QWidget *parent) } -void JFJochViewerImageROIStatistics::loadImage(std::shared_ptr image) { - if (!image) { +void JFJochViewerImageROIStatistics::SetROIResult(ROIMessage roi) { + if ( roi.pixels > 0) { + auto roi_npixel_val = static_cast(roi.pixels); + double roi_mean_val = static_cast(roi.sum) / roi_npixel_val; + double variance = static_cast(roi.sum_square) / roi_npixel_val - roi_mean_val * roi_mean_val; + + roi_sum->setText(QString("Sum %1").arg(roi.sum)); + roi_mean->setText(QString("Mean %1").arg(QString::number(roi_mean_val, 'f', 3))); + roi_var->setText(QString("Var %1").arg(QString::number(variance, 'f', 3))); + roi_max->setText(QString("Max %1").arg(roi.max_count)); + roi_npixel->setText(QString("Pixels %1").arg(roi.pixels)); + roi_masked->setText(QString("Masked %1").arg(roi.pixels_masked)); + } else { roi_sum->setText(""); roi_mean->setText(""); roi_var->setText(""); roi_max->setText(""); roi_npixel->setText(""); - } else { - auto roi = image->GetROI(); - - if (roi && roi->pixels > 0) { - auto roi_npixel_val = static_cast(roi->pixels); - double roi_mean_val = static_cast(roi->sum) / roi_npixel_val; - double variance = static_cast(roi->sum_square) / roi_npixel_val - roi_mean_val * roi_mean_val; - - roi_sum->setText(QString("Sum %1").arg(roi->sum)); - roi_mean->setText(QString("Mean %1").arg(QString::number(roi_mean_val, 'f', 3))); - roi_var->setText(QString("Var %1").arg(QString::number(variance, 'f', 3))); - roi_max->setText(QString("Max %1").arg(roi->max_count)); - roi_npixel->setText(QString("Pixels %1").arg(roi->pixels)); - } else { - roi_sum->setText(""); - roi_mean->setText(""); - roi_var->setText(""); - roi_max->setText(""); - roi_npixel->setText(""); - } + roi_masked->setText(""); } } + void JFJochViewerImageROIStatistics::SetROIBox(QRect box) { box_radio->setChecked(true); box_settings->ROIBoxConfigured(box); diff --git a/viewer/JFJochViewerImageROIStatistics.h b/viewer/JFJochViewerImageROIStatistics.h index e5ebed96..151fedae 100644 --- a/viewer/JFJochViewerImageROIStatistics.h +++ b/viewer/JFJochViewerImageROIStatistics.h @@ -30,15 +30,16 @@ class JFJochViewerImageROIStatistics : public QWidget { QLabel *roi_var; QLabel *roi_max; QLabel *roi_npixel; + QLabel *roi_masked; public: JFJochViewerImageROIStatistics(QWidget *parent); private slots: void BoxButtonClicked(); void CircleButtonClicked(); public slots: - void loadImage(std::shared_ptr image); void SetROIBox(QRect box); void SetROICircle(double x, double y, double radius); + void SetROIResult(ROIMessage msg); signals: void ROIBoxConfigured(QRect box); // Signal emitted when Box ROI is set void ROICircleConfigured(double center_x, double center_y, double radius); // Signal emitted when Circle ROI is set diff --git a/viewer/JFJochViewerSidePanel.cpp b/viewer/JFJochViewerSidePanel.cpp index 8a4b94c3..babf3348 100644 --- a/viewer/JFJochViewerSidePanel.cpp +++ b/viewer/JFJochViewerSidePanel.cpp @@ -125,7 +125,6 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) roi = new JFJochViewerImageROIStatistics(this); layout->addWidget(roi); - connect(this, &JFJochViewerSidePanel::imageLoaded, roi, &JFJochViewerImageROIStatistics::loadImage); connect(roi, &JFJochViewerImageROIStatistics::ROIBoxConfigured, [this] (QRect box) { emit ROIBoxConfigured(box); @@ -321,3 +320,7 @@ void JFJochViewerSidePanel::SetROIBox(QRect box) { void JFJochViewerSidePanel::SetROICircle(double x, double y, double radius) { roi->SetROICircle(x, y, radius); } + +void JFJochViewerSidePanel::SetROIResult(ROIMessage msg) { + roi->SetROIResult(msg); +} diff --git a/viewer/JFJochViewerSidePanel.h b/viewer/JFJochViewerSidePanel.h index 38a8d6a8..005db794 100644 --- a/viewer/JFJochViewerSidePanel.h +++ b/viewer/JFJochViewerSidePanel.h @@ -54,6 +54,7 @@ public slots: void loadImage(std::shared_ptr image); void SetROIBox(QRect box); void SetROICircle(double x, double y, double radius); + void SetROIResult(ROIMessage msg); private slots: void editingFinished(); void enableResRings(bool input); diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index f5966672..efd01417 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -183,6 +183,9 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString connect(viewer, &JFJochDiffractionImage::roiCircleUpdated, side_panel, &JFJochViewerSidePanel::SetROICircle); + connect(viewer, &JFJochDiffractionImage::roiCalculated, + side_panel, &JFJochViewerSidePanel::SetROIResult); + connect(side_panel, &JFJochViewerSidePanel::ROIBoxConfigured, reading_worker, &JFJochImageReadingWorker::SetROIBox); diff --git a/viewer/widgets/JFJochImage.cpp b/viewer/widgets/JFJochImage.cpp index 147eb8d8..a0f3dab9 100644 --- a/viewer/widgets/JFJochImage.cpp +++ b/viewer/widgets/JFJochImage.cpp @@ -23,6 +23,9 @@ JFJochImage::JFJochImage(QWidget *parent) : QGraphicsView(parent) { // Optional: a sensible default colormap color_scale.Select(ColorScaleEnum::Indigo); + + connect(this, &JFJochImage::roiBoxUpdated, this, &JFJochImage::CalcROIBox); + connect(this, &JFJochImage::roiCircleUpdated, this, &JFJochImage::CalcROICircle); } void JFJochImage::onScroll(int value) { @@ -575,7 +578,7 @@ void JFJochImage::writePixelLabels() { } void JFJochImage::updateOverlay() { - if (!scene() || W*H <= 0) return; + if (!scene() || W * H <= 0) return; scene()->clear(); scene()->addItem(new QGraphicsPixmapItem(pixmap)); @@ -583,10 +586,96 @@ void JFJochImage::updateOverlay() { if (scale_factor > 30.0) writePixelLabels(); - DrawROI(); addCustomOverlay(); } void JFJochImage::addCustomOverlay() {} + +ROIMessage JFJochImage::accumulateROI( + int64_t xmin, int64_t xmax, + int64_t ymin, int64_t ymax, + const std::function &inside) { + int64_t roi_val = 0; + uint64_t roi_val_2 = 0; + int64_t roi_max = INT64_MIN; + uint64_t roi_npixel = 0; + uint64_t roi_npixel_masked = 0; + float x_weighted = 0.0f; + float y_weighted = 0.0f; + + // Clamp bounds defensively to the image + xmin = std::max(0, xmin); + ymin = std::max(0, ymin); + xmax = std::min(W, xmax); + ymax = std::min(H, ymax); + + for (int64_t y = ymin; y < ymax; ++y) { + for (int64_t x = xmin; x < xmax; ++x) { + if (!inside(x, y)) continue; + + float val = image_fp[x + W * y]; + + if (std::isinf(val)) { + roi_npixel_masked++; + } else if (std::isfinite(val)) { + x_weighted += val * x; + y_weighted += val * y; + roi_val += val; + roi_val_2 += val * val; + if (val > roi_max) roi_max = val; + roi_npixel++; + } + } + } + + return ROIMessage{ + .sum = roi_val, + .sum_square = roi_val_2, + .max_count = roi_max, + .pixels = roi_npixel, + .pixels_masked = roi_npixel_masked, + .x_weighted = std::lroundf(x_weighted), + .y_weighted = std::lroundf(y_weighted), + }; +} + +void JFJochImage::CalcROIBox(QRect box) { + auto box_norm = box.normalized(); + + // Using the rectangle as-is; you can adjust inclusivity if needed + int64_t xmin = box_norm.left(); + int64_t xmax = box_norm.right(); + int64_t ymin = box_norm.top(); + int64_t ymax = box_norm.bottom(); + + ROIMessage msg = accumulateROI( + xmin, xmax, ymin, ymax, + [](int64_t, int64_t) { return true; } // everything in the rectangle + ); + emit roiCalculated(msg); +} + +void JFJochImage::CalcROICircle(double in_x, double in_y, double radius) { + const double margin = 1.0; + int64_t xmin = static_cast(std::floor(in_x - radius - margin)); + int64_t xmax = static_cast(std::ceil(in_x + radius + margin)); + int64_t ymin = static_cast(std::floor(in_y - radius - margin)); + int64_t ymax = static_cast(static_cast(std::ceil(in_y + radius + margin))); + + const float cx = static_cast(in_x); + const float cy = static_cast(in_y); + const float r2 = static_cast(radius * radius); + + ROIMessage msg = accumulateROI( + xmin, xmax, ymin, ymax, + [cx, cy, r2](int64_t x, int64_t y) { + const float dx = static_cast(x) - cx; + const float dy = static_cast(y) - cy; + const float dist2 = dx * dx + dy * dy; + return dist2 <= r2; + } + ); + emit roiCalculated(msg); +} diff --git a/viewer/widgets/JFJochImage.h b/viewer/widgets/JFJochImage.h index 57340932..846b3d1f 100644 --- a/viewer/widgets/JFJochImage.h +++ b/viewer/widgets/JFJochImage.h @@ -6,6 +6,9 @@ #include #include #include "../../common/ColorScale.h" +#include "../../common/JFJochMessages.h" + +// Q_DECLARE_METATYPE(ROIMessage) class JFJochImage : public QGraphicsView { Q_OBJECT @@ -20,6 +23,9 @@ class JFJochImage : public QGraphicsView { void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; + + ROIMessage accumulateROI(int64_t xmin, int64_t xmax, int64_t ymin, int64_t ymax, + const std::function& inside); protected: bool show_saturation = false; @@ -67,9 +73,11 @@ signals: void writeStatusBar(QString string, int timeout_ms = 0); void roiBoxUpdated(QRect box); void roiCircleUpdated(double x, double y, double radius); - + void roiCalculated(ROIMessage &output); private slots: void onScroll(int value); + void CalcROIBox(QRect box); + void CalcROICircle(double x, double y, double radius); public slots: void setFeatureColor(QColor input); void setColorMap(int color_map);