viewer: draw azimuthal ROI wedges, with optional fill and labels
Azimuthal ROIs now render on the diffraction image as annular sectors (or full-ring annuli), sampled through DiffractionGeometry::ResPhiToPxl so the outline follows the ROI footprint, including wrap-around sectors. Add two side-panel toggles (both default off): a translucent fill for every ROI (helpful when outline colours clash with the image colour map, and with many ROIs) and ROI name labels (constant on-screen size). Wired side panel -> window -> diffraction image like the existing feature toggles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -113,6 +113,16 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent)
|
||||
|
||||
layout->addWidget(new TitleLabel("ROI", this));
|
||||
|
||||
auto roiLabelsCheckBox = new QCheckBox("Show ROI labels", this);
|
||||
roiLabelsCheckBox->setCheckState(Qt::CheckState::Unchecked);
|
||||
connect(roiLabelsCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::showROILabels);
|
||||
layout->addWidget(roiLabelsCheckBox);
|
||||
|
||||
auto roiFillCheckBox = new QCheckBox("Translucent ROI fill", this);
|
||||
roiFillCheckBox->setCheckState(Qt::CheckState::Unchecked);
|
||||
connect(roiFillCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::showROIFill);
|
||||
layout->addWidget(roiFillCheckBox);
|
||||
|
||||
roi = new JFJochViewerImageROIStatistics(this);
|
||||
layout->addWidget(roi);
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ signals:
|
||||
void showSaturatedPixels(bool input);
|
||||
void showPredictions(bool input);
|
||||
void highlightIceRings(bool input);
|
||||
void showROILabels(bool input);
|
||||
void showROIFill(bool input);
|
||||
void findBeamCenter(const UnitCell &input, bool guess);
|
||||
void analyze();
|
||||
|
||||
|
||||
@@ -281,6 +281,10 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString
|
||||
viewer, &JFJochDiffractionImage::showSpots);
|
||||
connect(side_panel, &JFJochViewerSidePanel::showPredictions,
|
||||
viewer, &JFJochDiffractionImage::showPredictions);
|
||||
connect(side_panel, &JFJochViewerSidePanel::showROILabels,
|
||||
viewer, &JFJochDiffractionImage::showROILabels);
|
||||
connect(side_panel, &JFJochViewerSidePanel::showROIFill,
|
||||
viewer, &JFJochDiffractionImage::showROIFill);
|
||||
|
||||
connect(side_panel, &JFJochViewerSidePanel::setFeatureColor,
|
||||
viewer, &JFJochDiffractionImage::setFeatureColor);
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
#include "JFJochDiffractionImage.h"
|
||||
#include "../../common/DiffractionGeometry.h"
|
||||
#include "../../common/JFJochMath.h"
|
||||
#include "../../common/ROIAzimuthal.h"
|
||||
|
||||
#include <QPainterPath>
|
||||
#include <QBrush>
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QGraphicsSimpleTextItem>
|
||||
#include <QGraphicsScene>
|
||||
@@ -332,31 +335,116 @@ void JFJochDiffractionImage::DrawROIs() {
|
||||
return;
|
||||
|
||||
const auto &rois = image->Dataset().experiment.ROI().GetROIDefinition();
|
||||
auto geom = image->Dataset().experiment.GetDiffractionGeometry();
|
||||
|
||||
// Distinct colours per ROI; loaded ROIs use solid lines (the interactively
|
||||
// drawn scratch ROI keeps its dashed feature_color).
|
||||
// TODO: align this palette with the ROI colours in the bottom-panel plots.
|
||||
static const QColor palette[] = {Qt::cyan, Qt::yellow, QColor(0xff, 0x57, 0x22),
|
||||
Qt::green, Qt::magenta, QColor(0x21, 0x96, 0xf3)};
|
||||
const int palette_size = sizeof(palette) / sizeof(palette[0]);
|
||||
int color_index = 0;
|
||||
auto roi_pen = [&]() {
|
||||
QPen pen(palette[color_index % palette_size], 2);
|
||||
pen.setCosmetic(true);
|
||||
color_index++;
|
||||
return pen;
|
||||
|
||||
auto fill_brush = [&](const QColor &c) {
|
||||
return show_roi_fill ? QBrush(QColor(c.red(), c.green(), c.blue(), 60)) : QBrush(Qt::NoBrush);
|
||||
};
|
||||
auto label = [&](const std::string &name, const QColor &c, float px, float py) {
|
||||
if (!show_roi_labels)
|
||||
return;
|
||||
auto *text = scene()->addText(QString::fromStdString(name));
|
||||
text->setDefaultTextColor(c);
|
||||
text->setFlag(QGraphicsItem::ItemIgnoresTransformations); // constant on-screen size
|
||||
text->setPos(px, py);
|
||||
addOverlayItem(text);
|
||||
};
|
||||
|
||||
for (const auto &b : rois.boxes) {
|
||||
auto *rect = scene()->addRect(b.GetXMin(), b.GetYMin(), b.GetWidth(), b.GetHeight(), roi_pen());
|
||||
addOverlayItem(rect);
|
||||
QColor c = palette[color_index++ % palette_size];
|
||||
QPen pen(c, 2); pen.setCosmetic(true);
|
||||
addOverlayItem(scene()->addRect(b.GetXMin(), b.GetYMin(), b.GetWidth(), b.GetHeight(), pen, fill_brush(c)));
|
||||
label(b.GetName(), c, b.GetXMin(), b.GetYMin());
|
||||
}
|
||||
|
||||
for (const auto &c : rois.circles) {
|
||||
const float r = c.GetRadius_pxl();
|
||||
auto *ell = scene()->addEllipse(c.GetX() - r, c.GetY() - r, 2 * r, 2 * r, roi_pen());
|
||||
addOverlayItem(ell);
|
||||
for (const auto &c_roi : rois.circles) {
|
||||
QColor c = palette[color_index++ % palette_size];
|
||||
QPen pen(c, 2); pen.setCosmetic(true);
|
||||
const float r = c_roi.GetRadius_pxl();
|
||||
addOverlayItem(scene()->addEllipse(c_roi.GetX() - r, c_roi.GetY() - r, 2 * r, 2 * r, pen, fill_brush(c)));
|
||||
label(c_roi.GetName(), c, c_roi.GetX(), c_roi.GetY());
|
||||
}
|
||||
// Azimuthal ROIs are drawn as wedges in a later step.
|
||||
|
||||
for (const auto &az : rois.azimuthal)
|
||||
DrawAzimuthalROI(az, palette[color_index++ % palette_size], geom);
|
||||
}
|
||||
|
||||
void JFJochDiffractionImage::DrawAzimuthalROI(const ROIAzimuthal &az, const QColor &color,
|
||||
const DiffractionGeometry &geom) {
|
||||
QPen pen(color, 2); pen.setCosmetic(true);
|
||||
QBrush brush = show_roi_fill ? QBrush(QColor(color.red(), color.green(), color.blue(), 60))
|
||||
: QBrush(Qt::NoBrush);
|
||||
|
||||
const float d_inner = az.GetDMax_A(); // larger d -> smaller radius
|
||||
const float d_outer = az.GetDMin_A();
|
||||
auto deg2rad = [](float d) { return d * static_cast<float>(PI) / 180.0f; };
|
||||
|
||||
// Sample the boundary through the geometry so the wedge matches the ROI footprint.
|
||||
// ResPhiToPxl throws when the resolution is too high for the wavelength; skip such ROIs.
|
||||
auto add_arc = [&](QPainterPath &path, float d, float phi_a, float phi_b, int steps) -> bool {
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
float phi = phi_a + (phi_b - phi_a) * static_cast<float>(i) / static_cast<float>(steps);
|
||||
try {
|
||||
auto [px, py] = geom.ResPhiToPxl(d, phi);
|
||||
if (path.elementCount() == 0)
|
||||
path.moveTo(px, py);
|
||||
else
|
||||
path.lineTo(px, py);
|
||||
} catch (...) { return false; }
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
QPainterPath path;
|
||||
if (az.HasPhi()) {
|
||||
float phi0 = deg2rad(az.GetPhiMin_deg());
|
||||
float phi1 = deg2rad(az.GetPhiMax_deg());
|
||||
if (phi1 < phi0) phi1 += 2.0f * static_cast<float>(PI); // unwrap the sector
|
||||
int steps = std::max(8, static_cast<int>((phi1 - phi0) * 180.0f / static_cast<float>(PI) / 2.0f));
|
||||
if (!add_arc(path, d_outer, phi0, phi1, steps)) return; // outer arc
|
||||
if (!add_arc(path, d_inner, phi1, phi0, steps)) return; // inner arc back
|
||||
path.closeSubpath();
|
||||
} else {
|
||||
path.setFillRule(Qt::OddEvenFill); // annulus
|
||||
const float two_pi = 2.0f * static_cast<float>(PI);
|
||||
if (!add_arc(path, d_outer, 0, two_pi, 180)) return;
|
||||
path.closeSubpath();
|
||||
QPainterPath inner;
|
||||
if (!add_arc(inner, d_inner, 0, two_pi, 180)) return;
|
||||
inner.closeSubpath();
|
||||
path.addPath(inner);
|
||||
}
|
||||
|
||||
addOverlayItem(scene()->addPath(path, pen, brush));
|
||||
|
||||
if (show_roi_labels) {
|
||||
try {
|
||||
auto [px, py] = geom.ResPhiToPxl(d_outer, az.HasPhi() ? deg2rad(az.GetPhiMin_deg()) : 0.0f);
|
||||
auto *text = scene()->addText(QString::fromStdString(az.GetName()));
|
||||
text->setDefaultTextColor(color);
|
||||
text->setFlag(QGraphicsItem::ItemIgnoresTransformations);
|
||||
text->setPos(px, py);
|
||||
addOverlayItem(text);
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
|
||||
void JFJochDiffractionImage::showROILabels(bool input) {
|
||||
show_roi_labels = input;
|
||||
updateOverlay();
|
||||
}
|
||||
|
||||
void JFJochDiffractionImage::showROIFill(bool input) {
|
||||
show_roi_fill = input;
|
||||
updateOverlay();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include "JFJochImage.h"
|
||||
#include "../../reader/JFJochReaderImage.h"
|
||||
|
||||
class ROIAzimuthal;
|
||||
class DiffractionGeometry;
|
||||
|
||||
class JFJochDiffractionImage : public JFJochImage {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -26,6 +29,7 @@ private:
|
||||
void LoadImageInternal();
|
||||
void DrawResolutionRings();
|
||||
void DrawROIs();
|
||||
void DrawAzimuthalROI(const ROIAzimuthal &az, const QColor &color, const DiffractionGeometry &geom);
|
||||
void DrawSpots();
|
||||
void DrawPredictions();
|
||||
void DrawBeamCenter();
|
||||
@@ -49,6 +53,9 @@ private:
|
||||
bool show_spots = false;
|
||||
bool show_predictions = false;
|
||||
|
||||
bool show_roi_labels = false;
|
||||
bool show_roi_fill = false;
|
||||
|
||||
bool highlight_ice_rings = true;
|
||||
|
||||
float ice_ring_width_Q_recipA = 0.01;
|
||||
@@ -64,6 +71,9 @@ public slots:
|
||||
void showSpots(bool input);
|
||||
void showPredictions(bool input);
|
||||
|
||||
void showROILabels(bool input);
|
||||
void showROIFill(bool input);
|
||||
|
||||
void setSpotColor(QColor input);
|
||||
void setPredictionColor(QColor input);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user