Files
Jungfraujoch/viewer/JFJochViewerSidePanel.cpp
T
leonarski_fandClaude Opus 5 93093c5f21
Build Packages / build:viewer-tgz:cpu (push) Successful in 19m17s
Build Packages / build:viewer-tgz:cuda (push) Successful in 20m58s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 21m57s
Build Packages / build:windows:nocuda (push) Successful in 23m25s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 24m14s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 28m25s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 28m26s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 29m26s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 20m52s
Build Packages / XDS test (durin plugin) (push) Successful in 12m1s
Build Packages / build:rpm (rocky9) (push) Successful in 21m4s
Build Packages / Generate python client (push) Successful in 40s
Build Packages / Build documentation (push) Successful in 1m12s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (rocky8) (push) Successful in 25m9s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 20m53s
Build Packages / DIALS test (push) Successful in 21m14s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 25m44s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 10m32s
Build Packages / XDS test (neggia plugin) (push) Successful in 9m13s
Build Packages / build:windows:cuda (push) Successful in 32m13s
Build Packages / build:viewer-tgz:cpu (pull_request) Successful in 16m9s
Build Packages / Unit tests (push) Successful in 1h30m48s
Build Packages / build:viewer-tgz:cuda (pull_request) Successful in 18m19s
Build Packages / build:rpm (ubuntu2404_nocuda) (pull_request) Successful in 20m42s
Build Packages / build:rpm (rocky9_nocuda) (pull_request) Successful in 21m5s
Build Packages / build:rpm (rocky8_nocuda) (pull_request) Successful in 24m42s
Build Packages / build:rpm (ubuntu2204_nocuda) (pull_request) Successful in 24m50s
Build Packages / build:rpm (rocky9_sls9) (pull_request) Successful in 22m6s
Build Packages / build:rpm (rocky8_sls9) (pull_request) Successful in 27m44s
Build Packages / build:rpm (rocky9) (pull_request) Successful in 24m45s
Build Packages / DIALS test (pull_request) Successful in 21m17s
Build Packages / build:rpm (rocky8) (pull_request) Successful in 28m21s
Build Packages / Generate python client (pull_request) Successful in 39s
Build Packages / Create release (pull_request) Skipped
Build Packages / Build documentation (pull_request) Successful in 1m38s
Build Packages / build:rpm (ubuntu2404) (pull_request) Successful in 23m38s
Build Packages / build:rpm (ubuntu2204) (pull_request) Successful in 27m43s
Build Packages / XDS test (durin plugin) (pull_request) Successful in 10m5s
Build Packages / XDS test (JFJoch plugin) (pull_request) Successful in 8m48s
Build Packages / XDS test (neggia plugin) (pull_request) Successful in 7m39s
Build Packages / Unit tests (pull_request) Successful in 1h55m24s
Build Packages / build:windows:nocuda (pull_request) Successful in 21m3s
Build Packages / build:windows:cuda (pull_request) Successful in 21m25s
Viewer: every panel section starts folded, and calibration carries the azimuthal settings
A CollapsibleSection was born expanded and each caller folded it again, so the three that
never got round to it - geometry, unit cell, goniometer - greeted every start with three
open accordions and the rest of the settings pushed below the fold. The default is now
folded, both panels open as a list of headers, and the ten setExpanded(false) calls that
existed only to undo the old default are gone. The one remaining call is the ROI section
opening itself when ROIs appear, which is a real behaviour and still works.

Azimuthal integration was a page of the MX/AzInt/Calib stack, which left a calibration run
unable to reach the settings it depends on: calibrating from powder rings integrates the run
in azimuthal sectors and over the same Q range and spacing as any other integration, and the
Calib page could only report what the AzInt page had been set to. The section now lives
beside geometry, outside the stack, shown for both pages and hidden on MX - one set of
widgets over one AzimuthalIntegrationSettings, so there is nothing to keep in sync. The
too-few-sectors note moves into the powder section and appears only when the count is below
the four the rings fit needs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 15:21:41 +02:00

220 lines
9.4 KiB
C++

// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include <QHBoxLayout>
#include <QPushButton>
#include <QColorDialog>
#include <QGridLayout>
#include "JFJochViewerSidePanel.h"
#include "widgets/TitleLabel.h"
#include "widgets/CollapsibleSection.h"
#include "widgets/JFJochViewerImageStatistics.h"
namespace {
// A small square filled with the given colour, used as a legend/key swatch.
QLabel *MakeColorSwatch(QWidget *parent, const QColor &color) {
auto *swatch = new QLabel(parent);
swatch->setFixedSize(13, 13);
swatch->setStyleSheet(QString("background-color: %1; border: 1px solid #666;").arg(color.name()));
return swatch;
}
}
JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) {
auto layout = new QVBoxLayout(this);
layout->addWidget(new TitleLabel("Image statistics", this));
auto stats = new JFJochViewerImageStatistics(this);
layout->addWidget(stats);
connect(this, &JFJochViewerSidePanel::imageLoaded, stats, &JFJochViewerImageStatistics::loadImage);
auto *featuresSection = new CollapsibleSection("Image features", this);
auto spotToggleCheckBox = new QCheckBox("Show spots", this);
spotToggleCheckBox->setCheckState(Qt::CheckState::Unchecked);
connect(spotToggleCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::spotsToggled);
auto highlightIceRingToggleCheckBox = new QCheckBox("Highlight spots on ice rings", this);
highlightIceRingToggleCheckBox->setCheckState(Qt::CheckState::Checked);
connect(highlightIceRingToggleCheckBox, &QCheckBox::toggled, this,&JFJochViewerSidePanel::highlightIceRingsToggled);
auto predictionsToggleCheckBox = new QCheckBox("Show predictions", this);
predictionsToggleCheckBox->setCheckState(Qt::CheckState::Unchecked);
connect(predictionsToggleCheckBox, &QCheckBox::toggled, this,
&JFJochViewerSidePanel::predictionsToggled);
auto highestPixelsComboBox = new QComboBox(this);
highestPixelsComboBox->addItem("Show 0 highest pixels", 0);
highestPixelsComboBox->addItem("Show 1 highest pixel", 1);
highestPixelsComboBox->addItem("Show 2 highest pixels", 2);
highestPixelsComboBox->addItem("Show 3 highest pixels", 3);
highestPixelsComboBox->addItem("Show 5 highest pixels", 5);
highestPixelsComboBox->addItem("Show 10 highest pixels", 10);
highestPixelsComboBox->setCurrentIndex(0);
connect(highestPixelsComboBox, &QComboBox::currentIndexChanged, this, [this, highestPixelsComboBox](int index) {
int value = highestPixelsComboBox->itemData(index).toInt();
emit showHighestPixels(value);
});
auto saturatedPixelsCheckBox = new QCheckBox("Show saturated pixels", this);
connect(saturatedPixelsCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::saturatedPixelsToggled);
auto beamStopCheckBox = new QCheckBox("Show beam stop", this);
beamStopCheckBox->setChecked(true);
beamStopCheckBox->setToolTip("Draw the detected beam-stop shadow (pixel mask bit 9) in coral. "
"Unchecked, it is drawn like any other masked pixel.");
connect(beamStopCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::showBeamStop);
auto colorSelectButton = new QPushButton("Select feature color", this);
connect(colorSelectButton, &QPushButton::clicked, this, [this]() {
QColor color = QColorDialog::getColor(Qt::magenta, this, "Select Feature Color");
if (color.isValid()) {
emit setFeatureColor(color);
if (legend_indexed_swatch_)
legend_indexed_swatch_->setStyleSheet(
QString("background-color: %1; border: 1px solid #666;").arg(color.name()));
}
});
auto spotColorSelectButton = new QPushButton("Select spot color", this);
connect(spotColorSelectButton, &QPushButton::clicked, this, [this]() {
QColor color = QColorDialog::getColor(Qt::green, this, "Select Spot Color");
if (color.isValid()) {
emit setSpotColor(color);
if (legend_spot_swatch_)
legend_spot_swatch_->setStyleSheet(
QString("background-color: %1; border: 1px solid #666;").arg(color.name()));
}
});
auto image_feature_grid = new QGridLayout();
image_feature_grid->addWidget(spotToggleCheckBox, 0, 0);
image_feature_grid->addWidget(highlightIceRingToggleCheckBox, 0, 1);
image_feature_grid->addWidget(predictionsToggleCheckBox, 1, 0);
image_feature_grid->addWidget(beamStopCheckBox, 1, 1);
image_feature_grid->addWidget(saturatedPixelsCheckBox, 2, 0);
image_feature_grid->addWidget(highestPixelsComboBox, 2, 1);
image_feature_grid->addWidget(colorSelectButton, 3, 0);
image_feature_grid->addWidget(spotColorSelectButton, 3, 1);
// Legend: what each overlay colour means (matches JFJochDiffractionImage draw colours).
legend_spot_swatch_ = MakeColorSwatch(this, Qt::green);
legend_indexed_swatch_ = MakeColorSwatch(this, Qt::magenta);
auto *legendLayout = new QGridLayout();
legendLayout->setContentsMargins(0, 4, 0, 0);
legendLayout->setHorizontalSpacing(6);
legendLayout->setVerticalSpacing(2);
struct LegendRow { QLabel *swatch; const char *text; };
const LegendRow legend_rows[] = {
{legend_spot_swatch_, "Spots (not indexed)"},
{legend_indexed_swatch_, "Indexed spots"},
{MakeColorSwatch(this, Qt::cyan), "Spots on ice ring"},
{MakeColorSwatch(this, QColor(0xFA, 0x72, 0x68)), "Secondary lattice"},
{MakeColorSwatch(this, Qt::darkRed), "Predictions"},
};
int legend_row = 0;
for (const auto &r : legend_rows) {
legendLayout->addWidget(r.swatch, legend_row, 0, Qt::AlignVCenter);
legendLayout->addWidget(new QLabel(tr(r.text), this), legend_row, 1);
legend_row++;
}
legendLayout->setColumnStretch(1, 1);
image_feature_grid->addWidget(new QLabel(tr("Legend"), this), 4, 0, 1, 2);
image_feature_grid->addLayout(legendLayout, 5, 0, 1, 2);
featuresSection->setContentLayout(image_feature_grid);
layout->addWidget(featuresSection);
auto *ringsSection = new CollapsibleSection("Resolution rings", this);
auto *ringsLayout = new QVBoxLayout();
ringsLayout->setContentsMargins(0, 0, 0, 0);
res_rings = new ResolutionRingWidget(this);
connect(res_rings, &ResolutionRingWidget::resRingsSet, [this] (const QVector<float> &v) {
emit resRingsSet(v);
});
connect(res_rings, &ResolutionRingWidget::ringModeSet, [this] (JFJochDiffractionImage::RingMode mode) {
emit ringModeSet(mode);
});
ringsLayout->addWidget(res_rings);
ringsSection->setContentLayout(ringsLayout);
layout->addWidget(ringsSection);
// The per-image statistics plot now lives in the dataset-info dock (its "Per-image" toggle),
// and powder calibration / full analysis live in the settings dock and the hero buttons,
// so they are no longer duplicated here.
roiSection = new CollapsibleSection("ROI", this);
auto *roiLayout = new QVBoxLayout();
roiLayout->setContentsMargins(0, 0, 0, 0);
auto roiLabelsCheckBox = new QCheckBox("Show labels", this);
roiLabelsCheckBox->setCheckState(Qt::CheckState::Unchecked);
connect(roiLabelsCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::showROILabels);
auto roiFillCheckBox = new QCheckBox("Translucent fill", this);
roiFillCheckBox->setCheckState(Qt::CheckState::Unchecked);
connect(roiFillCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::showROIFill);
auto roiToggleRow = new QHBoxLayout();
roiToggleRow->addWidget(roiLabelsCheckBox);
roiToggleRow->addWidget(roiFillCheckBox);
roiLayout->addLayout(roiToggleRow);
roi_list = new JFJochViewerROIList(this);
roiLayout->addWidget(roi_list);
connect(this, &JFJochViewerSidePanel::imageLoaded, roi_list, &JFJochViewerROIList::loadImage);
connect(roi_list, &JFJochViewerROIList::roisChanged, this, &JFJochViewerSidePanel::roisChanged);
connect(roi_list, &JFJochViewerROIList::selectedROIChanged, this, &JFJochViewerSidePanel::selectedROIChanged);
connect(roi_list, &JFJochViewerROIList::downloadROIs, this, &JFJochViewerSidePanel::downloadROIs);
connect(roi_list, &JFJochViewerROIList::uploadROIs, this, &JFJochViewerSidePanel::uploadROIs);
connect(roi_list, &JFJochViewerROIList::maskFromROI, this, &JFJochViewerSidePanel::maskFromROI);
connect(roi_list, &JFJochViewerROIList::roisChanged, this, [this] { roiSection->setExpanded(true); });
roiSection->setContentLayout(roiLayout);
layout->addWidget(roiSection);
layout->addStretch();
setLayout(layout); // Set the layout to the widget
}
void JFJochViewerSidePanel::spotsToggled(bool input) {
emit showSpots(input);
}
void JFJochViewerSidePanel::predictionsToggled(bool input) {
emit showPredictions(input);
}
void JFJochViewerSidePanel::loadImage(std::shared_ptr<const JFJochReaderImage> image) {
emit imageLoaded(image);
}
void JFJochViewerSidePanel::saturatedPixelsToggled(bool input) {
emit showSaturatedPixels(input);
}
void JFJochViewerSidePanel::highlightIceRingsToggled(bool input) {
emit highlightIceRings(input);
}
void JFJochViewerSidePanel::selectROIInList(QString name) {
roi_list->setSelected(name);
}
void JFJochViewerSidePanel::SetRings(QVector<float> v) {
res_rings->setRings(v);
}