viewer: click to select an ROI, with live move and a hand cursor

Clicking a box/circle on the image now selects it (syncing the side-panel
combobox) and grabs it for moving, with a closed-hand cursor during the
drag. The move recomputes ROI statistics live rather than only on release,
throttled to at most one in-flight recompute so the worker is not flooded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 13:56:09 +02:00
co-authored by Claude Opus 4.8
parent 308265c7b1
commit d9dfd36413
7 changed files with 54 additions and 20 deletions
+4
View File
@@ -189,6 +189,10 @@ void JFJochViewerSidePanel::highlightIceRingsToggled(bool input) {
emit highlightIceRings(input);
}
void JFJochViewerSidePanel::selectROIInList(QString name) {
roi_list->setSelected(name);
}
void JFJochViewerSidePanel::SetROIBox(QRect box) {
roi->SetROIBox(box);
}
+1
View File
@@ -56,6 +56,7 @@ public slots:
void SetROICircle(double x, double y, double radius);
void SetROIResult(ROIMessage msg);
void SetRings(QVector<float> v);
void selectROIInList(QString name);
private slots:
void spotsToggled(bool input);
+2
View File
@@ -210,6 +210,8 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString
viewer, &JFJochDiffractionImage::setSelectedROI);
connect(viewer, &JFJochDiffractionImage::roiGeometryEdited,
reading_worker, &JFJochImageReadingWorker::SetROIDefinition);
connect(viewer, &JFJochDiffractionImage::roiSelected,
side_panel, &JFJochViewerSidePanel::selectROIInList);
connect(side_panel, &JFJochViewerSidePanel::ROIBoxConfigured,
reading_worker, &JFJochImageReadingWorker::SetROIBox);
+37 -20
View File
@@ -466,35 +466,39 @@ void JFJochDiffractionImage::setSelectedROI(QString name) {
}
bool JFJochDiffractionImage::roiEditPress(const QPointF &scenePos) {
if (!image || selected_roi_.isEmpty())
if (!image)
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;
// Pick the first box/circle under the cursor, so an ROI can be grabbed by clicking it.
for (const auto &b : rois.boxes) {
const QRectF r(QPointF(b.GetXMin(), b.GetYMin()), QPointF(b.GetXMax(), b.GetYMax()));
if (r.contains(scenePos)) {
roi_edit_ = RoiEdit::MoveBox;
edit_name_ = selected_roi_;
edit_name_ = QString::fromStdString(b.GetName());
edit_box_ = r;
move_last_ = scenePos;
selected_roi_ = edit_name_;
emit roiSelected(edit_name_);
setCursor(Qt::ClosedHandCursor);
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;
}
for (const auto &c : rois.circles) {
const QPointF center(c.GetX(), c.GetY());
if (QLineF(center, scenePos).length() <= c.GetRadius_pxl()) {
roi_edit_ = RoiEdit::MoveCircle;
edit_name_ = selected_roi_;
edit_name_ = QString::fromStdString(c.GetName());
edit_center_ = center;
edit_radius_ = c.GetRadius_pxl();
move_last_ = scenePos;
selected_roi_ = edit_name_;
emit roiSelected(edit_name_);
setCursor(Qt::ClosedHandCursor);
return true;
}
}
return false; // azimuthal editing is not yet supported
}
@@ -506,15 +510,28 @@ void JFJochDiffractionImage::roiEditMove(const QPointF &scenePos) {
else if (roi_edit_ == RoiEdit::MoveCircle)
edit_center_ += delta;
updateOverlay();
// Live recompute, but keep at most one in flight (cleared in loadImage) so the
// worker is not flooded with edits faster than it can recompute them.
if (!live_pending_) {
live_pending_ = true;
emit roiGeometryEdited(BuildEditedROIDefinition());
}
}
void JFJochDiffractionImage::roiEditRelease() {
if (roi_edit_ == RoiEdit::None || !image) {
roi_edit_ = RoiEdit::None;
if (roi_edit_ == RoiEdit::None)
return;
}
const ROIDefinition rois = BuildEditedROIDefinition();
roi_edit_ = RoiEdit::None;
setCursor(Qt::ArrowCursor);
emit roiGeometryEdited(rois); // final, exact position
}
ROIDefinition rois = image->Dataset().experiment.ROI().GetROIDefinition();
ROIDefinition JFJochDiffractionImage::BuildEditedROIDefinition() const {
ROIDefinition rois;
if (image)
rois = image->Dataset().experiment.ROI().GetROIDefinition();
const std::string sel = edit_name_.toStdString();
if (roi_edit_ == RoiEdit::MoveBox) {
for (auto &b : rois.boxes)
@@ -530,8 +547,7 @@ void JFJochDiffractionImage::roiEditRelease() {
break;
}
}
roi_edit_ = RoiEdit::None;
emit roiGeometryEdited(rois);
return rois;
}
@@ -556,6 +572,7 @@ void JFJochDiffractionImage::setHDRMode(bool input) {
}
void JFJochDiffractionImage::loadImage(std::shared_ptr<const JFJochReaderImage> in_image) {
live_pending_ = false; // a live ROI edit (if any) has now been recomputed
if (in_image) {
image = in_image;
UpdateForeground();
@@ -37,6 +37,7 @@ private:
bool roiEditPress(const QPointF &scenePos) override;
void roiEditMove(const QPointF &scenePos) override;
void roiEditRelease() override;
[[nodiscard]] ROIDefinition BuildEditedROIDefinition() const; // current ROIs with the edit applied
QString selected_roi_;
enum class RoiEdit { None, MoveBox, MoveCircle };
@@ -46,6 +47,7 @@ private:
QPointF edit_center_;
double edit_radius_ = 0;
QPointF move_last_;
bool live_pending_ = false; // a live edit is being recomputed; throttles emissions
void DrawSpots();
void DrawPredictions();
@@ -81,6 +83,7 @@ private:
signals:
void roiGeometryEdited(ROIDefinition rois);
void roiSelected(QString name); // user picked an ROI by clicking it on the image
public slots:
void setSelectedROI(QString name);
void loadImage(std::shared_ptr<const JFJochReaderImage> image);
+6
View File
@@ -69,6 +69,12 @@ QString JFJochViewerROIList::SelectedName() const {
return combo_->currentData().toString();
}
void JFJochViewerROIList::setSelected(QString name) {
const int idx = combo_->findData(name);
if (idx >= 0 && idx != combo_->currentIndex())
combo_->setCurrentIndex(idx); // updates the stats and selection highlight
}
void JFJochViewerROIList::ShowSelectedResult() {
const QString name = SelectedName();
if (image_ && !name.isEmpty()) {
+1
View File
@@ -35,6 +35,7 @@ public:
explicit JFJochViewerROIList(QWidget *parent = nullptr);
public slots:
void loadImage(std::shared_ptr<const JFJochReaderImage> image);
void setSelected(QString name); // select an ROI by name (e.g. clicked on the canvas)
private slots:
void OnSelectionChanged();
void OnAdd();