From e49a9088625f6fb4fa15ac5968dfb9606ecf49f0 Mon Sep 17 00:00:00 2001 From: leonarski_f Date: Fri, 19 Jun 2026 13:16:16 +0200 Subject: [PATCH] =?UTF-8?q?viewer:=20ROI=20panel=20refinements=20=E2=80=94?= =?UTF-8?q?=20persist=20edits,=20combobox,=20compact=20toggles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Edited ROIs now override the file's for every subsequently loaded image and survive settings changes, until a new file is opened (previously a delete/add was lost on the next image). Tracked via an roi_override_ that is re-applied to curr_experiment and to each loaded image's dataset. - The ROI selector is a compact combobox instead of a list widget. - "Show labels" and "Translucent fill" share one row to save space. Co-Authored-By: Claude Opus 4.8 --- viewer/JFJochImageReadingWorker.cpp | 19 ++++++++++++- viewer/JFJochImageReadingWorker.h | 4 +++ viewer/JFJochViewerSidePanel.cpp | 11 +++++--- viewer/widgets/JFJochViewerROIList.cpp | 37 +++++++++++--------------- viewer/widgets/JFJochViewerROIList.h | 5 ++-- 5 files changed, 47 insertions(+), 29 deletions(-) diff --git a/viewer/JFJochImageReadingWorker.cpp b/viewer/JFJochImageReadingWorker.cpp index 0e8de2c5..a6ba76b2 100644 --- a/viewer/JFJochImageReadingWorker.cpp +++ b/viewer/JFJochImageReadingWorker.cpp @@ -271,6 +271,7 @@ void JFJochImageReadingWorker::LoadFile_i(const QString &filename, qint64 image_ current_file = filename; if (dataset) { curr_experiment = dataset->experiment; + roi_override_.reset(); // new file: use its ROIs, forget any earlier edits curr_experiment.ImportIndexingSettings(indexing_settings); curr_experiment.ImportAzimuthalIntegrationSettings(azint_settings); UpdateAzint_i(dataset.get()); @@ -378,6 +379,8 @@ void JFJochImageReadingWorker::LoadImage_i(int64_t image_number, int64_t summati return; } + ApplyROIOverrideToImage_i(); // edited ROIs win over the file's for every image + current_image = current_image_ptr->ImageData().number; current_summation = summation; @@ -466,6 +469,8 @@ void JFJochImageReadingWorker::UpdateDataset_i(const std::optionalexperiment; + if (roi_override_) + curr_experiment.ROI().SetROI(*roi_override_); // keep edited ROIs across settings changes curr_experiment.ImportIndexingSettings(indexing_settings); curr_experiment.ImportAzimuthalIntegrationSettings(azint_settings); UpdateAzint_i(dataset.get()); @@ -473,6 +478,7 @@ void JFJochImageReadingWorker::UpdateDataset_i(const std::optional(current_image_ptr->ImageData(), dataset); + ApplyROIOverrideToImage_i(); if (auto_reanalyze) ReanalyzeImage_i(); @@ -596,7 +602,9 @@ void JFJochImageReadingWorker::SetROIDefinition(const ROIDefinition &rois) { void JFJochImageReadingWorker::SetROIDefinition_i(const ROIDefinition &rois) { // The worker experiment is the source of truth; the analysis ROI engine is rebuilt - // for it so newly loaded images pick up the change automatically. + // for it so newly loaded images pick up the change automatically. From now on the + // edited ROIs override whatever the file carried. + roi_override_ = rois; curr_experiment.ROI().SetROI(rois); if (image_analysis) image_analysis->RebuildROI(); @@ -623,6 +631,15 @@ void JFJochImageReadingWorker::SetROIDefinition_i(const ROIDefinition &rois) { emit imageLoaded(current_image_ptr); } +void JFJochImageReadingWorker::ApplyROIOverrideToImage_i() { + if (!roi_override_ || !current_image_ptr) + return; + auto md = current_image_ptr->CreateMutableDataset(); + md->experiment.ROI().SetROI(*roi_override_); + current_image_ptr = std::make_shared( + current_image_ptr->ImageData(), std::shared_ptr(md)); +} + void JFJochImageReadingWorker::UpdateUserMask_i(const std::vector &mask) { std::shared_ptr dataset; if (http_mode) { diff --git a/viewer/JFJochImageReadingWorker.h b/viewer/JFJochImageReadingWorker.h index 3db9e495..b9e546a0 100644 --- a/viewer/JFJochImageReadingWorker.h +++ b/viewer/JFJochImageReadingWorker.h @@ -53,6 +53,9 @@ private: QString current_file; DiffractionExperiment curr_experiment; + // Once the user edits ROIs they override whatever the file carried, for this and + // every subsequently loaded image, until a new file is opened. + std::optional roi_override_; IndexingSettings indexing_settings; AzimuthalIntegrationSettings azint_settings; std::unique_ptr indexing; @@ -119,6 +122,7 @@ private: void UpdateAzint_i(const JFJochReaderDataset *dataset); void UpdateUserMask_i(const std::vector &mask); void SetROIDefinition_i(const ROIDefinition &rois); + void ApplyROIOverrideToImage_i(); // rewrite current_image_ptr's dataset with roi_override_ void setAutoLoadMode_i(AutoloadMode mode); signals: diff --git a/viewer/JFJochViewerSidePanel.cpp b/viewer/JFJochViewerSidePanel.cpp index bb260d64..c78fec8a 100644 --- a/viewer/JFJochViewerSidePanel.cpp +++ b/viewer/JFJochViewerSidePanel.cpp @@ -113,15 +113,18 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) layout->addWidget(new TitleLabel("ROI", this)); - auto roiLabelsCheckBox = new QCheckBox("Show ROI labels", this); + auto roiLabelsCheckBox = new QCheckBox("Show 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); + auto roiFillCheckBox = new QCheckBox("Translucent fill", this); roiFillCheckBox->setCheckState(Qt::CheckState::Unchecked); connect(roiFillCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::showROIFill); - layout->addWidget(roiFillCheckBox); + + auto roiToggleRow = new QHBoxLayout(); + roiToggleRow->addWidget(roiLabelsCheckBox); + roiToggleRow->addWidget(roiFillCheckBox); + layout->addLayout(roiToggleRow); roi_list = new JFJochViewerROIList(this); layout->addWidget(roi_list); diff --git a/viewer/widgets/JFJochViewerROIList.cpp b/viewer/widgets/JFJochViewerROIList.cpp index 24c5bbde..250da1b8 100644 --- a/viewer/widgets/JFJochViewerROIList.cpp +++ b/viewer/widgets/JFJochViewerROIList.cpp @@ -9,15 +9,13 @@ #include #include #include -#include JFJochViewerROIList::JFJochViewerROIList(QWidget *parent) : QWidget(parent) { auto *layout = new QVBoxLayout(this); - list_ = new QListWidget(this); - list_->setMaximumHeight(120); - layout->addWidget(list_); - connect(list_, &QListWidget::itemSelectionChanged, this, &JFJochViewerROIList::OnSelectionChanged); + combo_ = new QComboBox(this); + layout->addWidget(combo_); + connect(combo_, &QComboBox::currentIndexChanged, this, &JFJochViewerROIList::OnSelectionChanged); auto *buttons = new QHBoxLayout(); type_combo_ = new QComboBox(this); @@ -42,36 +40,33 @@ void JFJochViewerROIList::loadImage(std::shared_ptr ima image_ = image; const QString keep = SelectedName(); rois_ = image_ ? image_->Dataset().experiment.ROI().GetROIDefinition() : ROIDefinition{}; - RebuildList(keep); + RebuildCombo(keep); ShowSelectedResult(); } -void JFJochViewerROIList::RebuildList(const QString &keep_selected) { +void JFJochViewerROIList::RebuildCombo(const QString &keep_selected) { rebuilding_ = true; - list_->clear(); + combo_->clear(); auto add_item = [&](const std::string &name, const char *type) { - auto *item = new QListWidgetItem(QString("%1 · %2").arg(QString::fromStdString(name), type)); - item->setData(Qt::UserRole, QString::fromStdString(name)); // name independent of display text - list_->addItem(item); + // display "name · type", with the bare name kept as item data for lookups + combo_->addItem(QString("%1 · %2").arg(QString::fromStdString(name), type), + QString::fromStdString(name)); }; for (const auto &b : rois_.boxes) add_item(b.GetName(), "box"); for (const auto &c : rois_.circles) add_item(c.GetName(), "circle"); for (const auto &a : rois_.azimuthal) add_item(a.GetName(), "azim"); if (!keep_selected.isEmpty()) { - for (int i = 0; i < list_->count(); i++) - if (list_->item(i)->data(Qt::UserRole).toString() == keep_selected) { - list_->setCurrentRow(i); - break; - } + const int idx = combo_->findData(keep_selected); + if (idx >= 0) + combo_->setCurrentIndex(idx); } rebuilding_ = false; } QString JFJochViewerROIList::SelectedName() const { - auto *item = list_->currentItem(); - return item ? item->data(Qt::UserRole).toString() : QString(); + return combo_->currentData().toString(); } void JFJochViewerROIList::ShowSelectedResult() { @@ -121,7 +116,7 @@ void JFJochViewerROIList::OnAdd() { default: rois_.azimuthal.emplace_back(name, 2.0f, 4.0f); break; } emit roisChanged(rois_); - RebuildList(QString::fromStdString(name)); + RebuildCombo(QString::fromStdString(name)); ShowSelectedResult(); } @@ -137,7 +132,7 @@ void JFJochViewerROIList::OnDelete() { }; if (erase_by_name(rois_.boxes) || erase_by_name(rois_.circles) || erase_by_name(rois_.azimuthal)) { emit roisChanged(rois_); - RebuildList(QString()); + RebuildCombo(QString()); ShowSelectedResult(); } } @@ -168,6 +163,6 @@ void JFJochViewerROIList::OnRename() { } emit roisChanged(rois_); - RebuildList(new_name); + RebuildCombo(new_name); ShowSelectedResult(); } diff --git a/viewer/widgets/JFJochViewerROIList.h b/viewer/widgets/JFJochViewerROIList.h index 675c5721..b8332062 100644 --- a/viewer/widgets/JFJochViewerROIList.h +++ b/viewer/widgets/JFJochViewerROIList.h @@ -6,7 +6,6 @@ #include #include -#include #include #include "JFJochViewerROIResult.h" @@ -20,7 +19,7 @@ class JFJochViewerROIList : public QWidget { Q_OBJECT - QListWidget *list_; + QComboBox *combo_; QComboBox *type_combo_; JFJochViewerROIResult *result_; @@ -28,7 +27,7 @@ class JFJochViewerROIList : public QWidget { std::shared_ptr image_; bool rebuilding_ = false; - void RebuildList(const QString &keep_selected); + void RebuildCombo(const QString &keep_selected); void ShowSelectedResult(); [[nodiscard]] QString SelectedName() const; [[nodiscard]] std::string UniqueName() const;