viewer: move the selected box/circle ROI on the canvas
Add base-class mouse hooks (roiEditPress/Move/Release) so the diffraction image can edit the persistent ROI selected in the side-panel combobox. The selected ROI is highlighted (dashed, thicker), and dragging its interior moves a box or circle; on release the new geometry is committed through SetROIDefinition, which recomputes only the ROIs. Resize, azimuthal handles and delete-key follow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -206,6 +206,10 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString
|
||||
|
||||
connect(side_panel, &JFJochViewerSidePanel::roisChanged,
|
||||
reading_worker, &JFJochImageReadingWorker::SetROIDefinition);
|
||||
connect(side_panel, &JFJochViewerSidePanel::selectedROIChanged,
|
||||
viewer, &JFJochDiffractionImage::setSelectedROI);
|
||||
connect(viewer, &JFJochDiffractionImage::roiGeometryEdited,
|
||||
reading_worker, &JFJochImageReadingWorker::SetROIDefinition);
|
||||
|
||||
connect(side_panel, &JFJochViewerSidePanel::ROIBoxConfigured,
|
||||
reading_worker, &JFJochImageReadingWorker::SetROIBox);
|
||||
|
||||
@@ -351,17 +351,31 @@ void JFJochDiffractionImage::DrawROIs() {
|
||||
|
||||
for (const auto &b : rois.boxes) {
|
||||
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)));
|
||||
AddROILabel(b.GetName(), c, b.GetXMin(), b.GetYMin());
|
||||
const bool selected = (QString::fromStdString(b.GetName()) == selected_roi_);
|
||||
QPen pen(c, selected ? 3 : 2);
|
||||
pen.setCosmetic(true);
|
||||
if (selected) pen.setStyle(Qt::DashLine); // highlight the editable ROI
|
||||
const QRectF rect = (roi_edit_ == RoiEdit::MoveBox && b.GetName() == edit_name_.toStdString())
|
||||
? edit_box_
|
||||
: QRectF(b.GetXMin(), b.GetYMin(), b.GetWidth(), b.GetHeight());
|
||||
addOverlayItem(scene()->addRect(rect, pen, fill_brush(c)));
|
||||
AddROILabel(b.GetName(), c, rect.left(), rect.top());
|
||||
}
|
||||
|
||||
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)));
|
||||
AddROILabel(c_roi.GetName(), c, c_roi.GetX(), c_roi.GetY());
|
||||
const bool selected = (QString::fromStdString(c_roi.GetName()) == selected_roi_);
|
||||
QPen pen(c, selected ? 3 : 2);
|
||||
pen.setCosmetic(true);
|
||||
if (selected) pen.setStyle(Qt::DashLine);
|
||||
QPointF center(c_roi.GetX(), c_roi.GetY());
|
||||
double r = c_roi.GetRadius_pxl();
|
||||
if (roi_edit_ == RoiEdit::MoveCircle && c_roi.GetName() == edit_name_.toStdString()) {
|
||||
center = edit_center_;
|
||||
r = edit_radius_;
|
||||
}
|
||||
addOverlayItem(scene()->addEllipse(center.x() - r, center.y() - r, 2 * r, 2 * r, pen, fill_brush(c)));
|
||||
AddROILabel(c_roi.GetName(), c, center.x(), center.y());
|
||||
}
|
||||
|
||||
for (const auto &az : rois.azimuthal)
|
||||
@@ -446,6 +460,80 @@ void JFJochDiffractionImage::showROIFill(bool input) {
|
||||
updateOverlay();
|
||||
}
|
||||
|
||||
void JFJochDiffractionImage::setSelectedROI(QString name) {
|
||||
selected_roi_ = name;
|
||||
updateOverlay();
|
||||
}
|
||||
|
||||
bool JFJochDiffractionImage::roiEditPress(const QPointF &scenePos) {
|
||||
if (!image || selected_roi_.isEmpty())
|
||||
return false;
|
||||
|
||||
const auto &rois = image->Dataset().experiment.ROI().GetROIDefinition();
|
||||
const std::string sel = selected_roi_.toStdString();
|
||||
|
||||
for (const auto &b : rois.boxes)
|
||||
if (b.GetName() == sel) {
|
||||
const QRectF r(QPointF(b.GetXMin(), b.GetYMin()), QPointF(b.GetXMax(), b.GetYMax()));
|
||||
if (!r.contains(scenePos))
|
||||
return false;
|
||||
roi_edit_ = RoiEdit::MoveBox;
|
||||
edit_name_ = selected_roi_;
|
||||
edit_box_ = r;
|
||||
move_last_ = scenePos;
|
||||
return true;
|
||||
}
|
||||
for (const auto &c : rois.circles)
|
||||
if (c.GetName() == sel) {
|
||||
const QPointF center(c.GetX(), c.GetY());
|
||||
if (QLineF(center, scenePos).length() > c.GetRadius_pxl())
|
||||
return false;
|
||||
roi_edit_ = RoiEdit::MoveCircle;
|
||||
edit_name_ = selected_roi_;
|
||||
edit_center_ = center;
|
||||
edit_radius_ = c.GetRadius_pxl();
|
||||
move_last_ = scenePos;
|
||||
return true;
|
||||
}
|
||||
return false; // azimuthal editing is not yet supported
|
||||
}
|
||||
|
||||
void JFJochDiffractionImage::roiEditMove(const QPointF &scenePos) {
|
||||
const QPointF delta = scenePos - move_last_;
|
||||
move_last_ = scenePos;
|
||||
if (roi_edit_ == RoiEdit::MoveBox)
|
||||
edit_box_.translate(delta);
|
||||
else if (roi_edit_ == RoiEdit::MoveCircle)
|
||||
edit_center_ += delta;
|
||||
updateOverlay();
|
||||
}
|
||||
|
||||
void JFJochDiffractionImage::roiEditRelease() {
|
||||
if (roi_edit_ == RoiEdit::None || !image) {
|
||||
roi_edit_ = RoiEdit::None;
|
||||
return;
|
||||
}
|
||||
|
||||
ROIDefinition rois = image->Dataset().experiment.ROI().GetROIDefinition();
|
||||
const std::string sel = edit_name_.toStdString();
|
||||
if (roi_edit_ == RoiEdit::MoveBox) {
|
||||
for (auto &b : rois.boxes)
|
||||
if (b.GetName() == sel) {
|
||||
b = ROIBox(sel, std::lround(edit_box_.left()), std::lround(edit_box_.right()),
|
||||
std::lround(edit_box_.top()), std::lround(edit_box_.bottom()));
|
||||
break;
|
||||
}
|
||||
} else if (roi_edit_ == RoiEdit::MoveCircle) {
|
||||
for (auto &c : rois.circles)
|
||||
if (c.GetName() == sel) {
|
||||
c = ROICircle(sel, edit_center_.x(), edit_center_.y(), edit_radius_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
roi_edit_ = RoiEdit::None;
|
||||
emit roiGeometryEdited(rois);
|
||||
}
|
||||
|
||||
|
||||
void JFJochDiffractionImage::UpdateForeground() {
|
||||
if (!image || !auto_fg)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "JFJochImage.h"
|
||||
#include "../../reader/JFJochReaderImage.h"
|
||||
#include "../../common/ROIDefinition.h"
|
||||
|
||||
class ROIAzimuthal;
|
||||
class DiffractionGeometry;
|
||||
@@ -31,6 +32,21 @@ private:
|
||||
void DrawROIs();
|
||||
void DrawAzimuthalROI(const ROIAzimuthal &az, const QColor &color, const DiffractionGeometry &geom);
|
||||
void AddROILabel(const std::string &name, const QColor &color, float px, float py);
|
||||
|
||||
// Interactive editing of the selected (named) ROI: move box/circle for now.
|
||||
bool roiEditPress(const QPointF &scenePos) override;
|
||||
void roiEditMove(const QPointF &scenePos) override;
|
||||
void roiEditRelease() override;
|
||||
|
||||
QString selected_roi_;
|
||||
enum class RoiEdit { None, MoveBox, MoveCircle };
|
||||
RoiEdit roi_edit_ = RoiEdit::None;
|
||||
QString edit_name_;
|
||||
QRectF edit_box_;
|
||||
QPointF edit_center_;
|
||||
double edit_radius_ = 0;
|
||||
QPointF move_last_;
|
||||
|
||||
void DrawSpots();
|
||||
void DrawPredictions();
|
||||
void DrawBeamCenter();
|
||||
@@ -63,7 +79,10 @@ private:
|
||||
|
||||
void mouseHover(QMouseEvent* event) override;
|
||||
|
||||
signals:
|
||||
void roiGeometryEdited(ROIDefinition rois);
|
||||
public slots:
|
||||
void setSelectedROI(QString name);
|
||||
void loadImage(std::shared_ptr<const JFJochReaderImage> image);
|
||||
void setAutoForeground(bool input);
|
||||
void setResolutionRing(QVector<float> v);
|
||||
|
||||
@@ -157,6 +157,13 @@ void JFJochImage::mousePressEvent(QMouseEvent *event) {
|
||||
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
const QPointF scenePos = mapToScene(event->pos());
|
||||
|
||||
if (roiEditPress(scenePos)) {
|
||||
mouse_event_type = MouseEventType::EditingExternalROI;
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
active_handle_ = hitTestROIHandle(scenePos, 4.0 / std::sqrt(std::max(1e-4, scale_factor)));
|
||||
|
||||
if (active_handle_ != ResizeHandle::None && active_handle_ != ResizeHandle::Inside) {
|
||||
@@ -193,6 +200,9 @@ void JFJochImage::mouseMoveEvent(QMouseEvent *event) {
|
||||
QPointF delta;
|
||||
|
||||
switch (mouse_event_type) {
|
||||
case MouseEventType::EditingExternalROI:
|
||||
roiEditMove(scenePos);
|
||||
return;
|
||||
case MouseEventType::Panning: {
|
||||
const QPoint viewDelta = event->pos() - lastMousePos;
|
||||
lastMousePos = event->pos();
|
||||
@@ -284,10 +294,13 @@ void JFJochImage::mouseReleaseEvent(QMouseEvent *event) {
|
||||
if (!scene()) return;
|
||||
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
if (mouse_event_type == MouseEventType::DrawingROI) {
|
||||
roiEndPos = RoundPoint(mapToScene(event->pos()));
|
||||
if (mouse_event_type == MouseEventType::EditingExternalROI) {
|
||||
roiEditRelease();
|
||||
} else {
|
||||
if (mouse_event_type == MouseEventType::DrawingROI)
|
||||
roiEndPos = RoundPoint(mapToScene(event->pos()));
|
||||
updateROI();
|
||||
}
|
||||
updateROI();
|
||||
}
|
||||
|
||||
mouse_event_type = MouseEventType::None;
|
||||
|
||||
@@ -75,8 +75,15 @@ protected:
|
||||
// Helper: add an overlay item to the scene and track it for selective removal
|
||||
void addOverlayItem(QGraphicsItem *item);
|
||||
|
||||
enum class MouseEventType {None, Panning, DrawingROI, MovingROI, ResizingROI};
|
||||
enum class MouseEventType {None, Panning, DrawingROI, MovingROI, ResizingROI, EditingExternalROI};
|
||||
MouseEventType mouse_event_type = MouseEventType::None;
|
||||
|
||||
// Hooks for editing a named, persistent ROI drawn by a subclass (the base only
|
||||
// owns the mouse events; the subclass knows the loaded ROIs). roiEditPress returns
|
||||
// true to claim the gesture for ROI editing.
|
||||
virtual bool roiEditPress(const QPointF &scenePos) { return false; }
|
||||
virtual void roiEditMove(const QPointF &scenePos) {}
|
||||
virtual void roiEditRelease() {}
|
||||
QPoint lastMousePos; // To track panning movement
|
||||
|
||||
// Resizing which edge/corner
|
||||
|
||||
Reference in New Issue
Block a user