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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user