jfjoch_viewer: ROI calculated inside image viewer

This commit is contained in:
2025-11-14 20:20:19 +01:00
parent 8b809e5609
commit 6f3c7348ca
12 changed files with 140 additions and 107 deletions

View File

@@ -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;
};

View File

@@ -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<ROIMessage> &JFJochReaderImage::GetROI() const {
std::unique_lock ul(roi_mutex);
return roi;
}
std::vector<float> JFJochReaderImage::GetAzInt1D() const {
if (dataset->azimuthal_bins <= 1) {
return message.az_int_profile;

View File

@@ -25,8 +25,6 @@ class JFJochReaderImage {
std::unordered_set<int64_t> saturated_pixel;
std::unordered_set<int64_t> error_pixel;
std::vector<std::pair<int32_t, int32_t>> valid_pixel;
std::optional<ROIMessage> 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<std::pair<int32_t, int32_t>> &ValidPixels() const;
const JFJochReaderDataset &Dataset() const;
const std::optional<ROIMessage> &GetROI() const;
void AddImage(const JFJochReaderImage& other);
std::vector<float> GetAzInt1D() const;
std::vector<float> GetAzInt1D_BinToQ() const;
// Functions that change the content
void CalcROI(const ROIElement *input);
std::shared_ptr<JFJochReaderDataset> CreateMutableDataset();
int32_t GetAutoContrastValue() const;

View File

@@ -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<AzimuthalIntegration>(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<ROIBox>("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<ROICircle>("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<DiffractionExperiment> &experiment) {
@@ -212,7 +193,7 @@ void JFJochImageReadingWorker::UpdateDataset_i(const std::optional<DiffractionEx
emit datasetLoaded(dataset);
current_image_ptr = std::make_shared<JFJochReaderImage>(current_image_ptr->ImageData(), dataset);
CalcROI_i();
if (auto_reanalyze)
ReanalyzeImage_i();
emit imageLoaded(current_image_ptr);

View File

@@ -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<DiffractionExperiment>& experiment);
void UpdateAzint_i(const JFJochReaderDataset *dataset);

View File

@@ -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<const JFJochReaderImage> image) {
if (!image) {
void JFJochViewerImageROIStatistics::SetROIResult(ROIMessage roi) {
if ( roi.pixels > 0) {
auto roi_npixel_val = static_cast<double>(roi.pixels);
double roi_mean_val = static_cast<double>(roi.sum) / roi_npixel_val;
double variance = static_cast<double>(roi.sum_square) / roi_npixel_val - roi_mean_val * roi_mean_val;
roi_sum->setText(QString("Sum <b>%1</b>").arg(roi.sum));
roi_mean->setText(QString("Mean <b>%1</b>").arg(QString::number(roi_mean_val, 'f', 3)));
roi_var->setText(QString("Var <b>%1</b>").arg(QString::number(variance, 'f', 3)));
roi_max->setText(QString("Max <b>%1</b>").arg(roi.max_count));
roi_npixel->setText(QString("Pixels <b>%1</b>").arg(roi.pixels));
roi_masked->setText(QString("Masked <b>%1</b>").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<double>(roi->pixels);
double roi_mean_val = static_cast<double>(roi->sum) / roi_npixel_val;
double variance = static_cast<double>(roi->sum_square) / roi_npixel_val - roi_mean_val * roi_mean_val;
roi_sum->setText(QString("Sum <b>%1</b>").arg(roi->sum));
roi_mean->setText(QString("Mean <b>%1</b>").arg(QString::number(roi_mean_val, 'f', 3)));
roi_var->setText(QString("Var <b>%1</b>").arg(QString::number(variance, 'f', 3)));
roi_max->setText(QString("Max <b>%1</b>").arg(roi->max_count));
roi_npixel->setText(QString("Pixels <b>%1</b>").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);

View File

@@ -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<const JFJochReaderImage> 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

View File

@@ -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);
}

View File

@@ -54,6 +54,7 @@ public slots:
void loadImage(std::shared_ptr<const JFJochReaderImage> 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);

View File

@@ -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);

View File

@@ -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<bool(int64_t, int64_t)> &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<int64_t>(0, xmin);
ymin = std::max<int64_t>(0, ymin);
xmax = std::min<int64_t>(W, xmax);
ymax = std::min<int64_t>(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<int64_t>(std::floor(in_x - radius - margin));
int64_t xmax = static_cast<int64_t>(std::ceil(in_x + radius + margin));
int64_t ymin = static_cast<int64_t>(std::floor(in_y - radius - margin));
int64_t ymax = static_cast<int64_t>(static_cast<int64_t>(std::ceil(in_y + radius + margin)));
const float cx = static_cast<float>(in_x);
const float cy = static_cast<float>(in_y);
const float r2 = static_cast<float>(radius * radius);
ROIMessage msg = accumulateROI(
xmin, xmax, ymin, ymax,
[cx, cy, r2](int64_t x, int64_t y) {
const float dx = static_cast<float>(x) - cx;
const float dy = static_cast<float>(y) - cy;
const float dist2 = dx * dx + dy * dy;
return dist2 <= r2;
}
);
emit roiCalculated(msg);
}

View File

@@ -6,6 +6,9 @@
#include <QGraphicsView>
#include <QMouseEvent>
#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<bool(int64_t,int64_t)>& 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);