Files
Jungfraujoch/viewer/JFJochViewerSidePanel.cpp
T
leonarski_f 84228bf8be
Build Packages / Create release (push) Successful in 24s
Build Packages / build:viewer:macos-arm64:nocuda (push) Successful in 3m29s
Build Packages / build:rugnux:macos-arm64:nocuda (push) Successful in 2m43s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 8m27s
Build Packages / build:rugnux:linux-x86_64:cuda (push) Successful in 9m53s
Build Packages / build:viewer:linux-x86_64:nocuda (push) Successful in 9m58s
Build Packages / build:viewer:linux-x86_64:cuda (push) Successful in 11m22s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 13m39s
Build Packages / build:viewer:windows-x86_64:nocuda (push) Successful in 18m37s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 16m32s
Build Packages / build:viewer:windows-x86_64:cuda (push) Successful in 24m11s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m30s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 19m3s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 20m23s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 19m41s
Build Packages / Generate python client (push) Successful in 50s
Build Packages / Build documentation (push) Successful in 1m16s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 21m0s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 18m38s
Build Packages / build:rugnux:windows-x86_64:cuda (push) Successful in 14m33s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 17m55s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 20m50s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 18m38s
Build Packages / Unit tests (push) Successful in 1h46m14s
v1.0.0-rc.173 (#83)
* jfjoch_broker: Optional per-dataset authentication - statistics, images and plots can require a bearer token, which jfjoch_viewer supports.
* jfjoch_viewer: Dark mode and a theme-matched colour scheme, a magnifier panel, and simpler contrast and background controls.
* Rugnux: Multiple performance improvements on GPU and CPU (CPU-only processing up to 40% faster, faster image decoding on ARM), with unchanged results.
* Rugnux: `--model` rigid-body refinement runs on the GPU, and the model-validation check is faster and more reliable.
* Rugnux: Improved scaling and merging - error model, outlier rejection, absorption correction and French-Wilson amplitudes now agree more closely with XDS and ctruncate.
* Rugnux: Improved integration - radial background on powder and ice rings, crowded rotation data keep their reflections, and CPU-only builds integrate large unit cells as GPU builds do.
* Rugnux: More robust detector geometry - measured beam centre, X-ray bandwidth and goniometer rate, and geometry refinement accepted only on significant evidence.
* Rugnux: Merged files are written in the standard setting, or in the setting of a reference MTZ, structure-factor mmCIF or model, with its free-R flags.
* Rugnux: Richer report - ice and powder rings, further lattices, superstructure candidates and mosaicity, with warnings worded as prompts to check.
* Rugnux: Clear error messages when a data set needs more GPU or host memory than is available.

Reviewed-on: #83
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-09-29 15:57:32 +02:00

249 lines
11 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/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);
// Open at start, but foldable, so a small screen can give its space to the magnifier below
auto *statsSection = new CollapsibleSection("Image statistics", this);
auto *statsLayout = new QVBoxLayout();
statsLayout->setContentsMargins(0, 0, 0, 0);
auto stats = new JFJochViewerImageStatistics(this);
statsLayout->addWidget(stats);
statsSection->setContentLayout(statsLayout);
statsSection->setExpanded(true);
layout->addWidget(statsSection);
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 hideUnindexedCheckBox = new QCheckBox("Hide non-indexed spots", this);
hideUnindexedCheckBox->setToolTip("Draw only the spots that were assigned to a lattice.");
connect(hideUnindexedCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::hideUnindexedSpotsToggled);
auto hideIceRingSpotsCheckBox = new QCheckBox("Hide spots on ice rings", this);
hideIceRingSpotsCheckBox->setToolTip("Leave the spots that fall on an ice ring out of the overlay.");
connect(hideIceRingSpotsCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::hideIceRingSpotsToggled);
auto outlineSpotsCheckBox = new QCheckBox("Outline spots in black", this);
outlineSpotsCheckBox->setCheckState(Qt::CheckState::Unchecked);
outlineSpotsCheckBox->setToolTip("Draw a black casing under each spot marker, so it stays visible "
"where the colour map is light.");
connect(outlineSpotsCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::outlineSpots);
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(hideUnindexedCheckBox, 1, 0);
image_feature_grid->addWidget(hideIceRingSpotsCheckBox, 1, 1);
image_feature_grid->addWidget(predictionsToggleCheckBox, 2, 0);
image_feature_grid->addWidget(beamStopCheckBox, 2, 1);
image_feature_grid->addWidget(saturatedPixelsCheckBox, 3, 0);
image_feature_grid->addWidget(outlineSpotsCheckBox, 3, 1);
image_feature_grid->addWidget(highestPixelsComboBox, 4, 0);
image_feature_grid->addWidget(colorSelectButton, 5, 0);
image_feature_grid->addWidget(spotColorSelectButton, 5, 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), 6, 0, 1, 2);
image_feature_grid->addLayout(legendLayout, 7, 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::hideUnindexedSpotsToggled(bool input) {
emit hideUnindexedSpots(input);
}
void JFJochViewerSidePanel::hideIceRingSpotsToggled(bool input) {
emit hideIceRingSpots(input);
}
void JFJochViewerSidePanel::selectROIInList(QString name) {
roi_list->setSelected(name);
}
void JFJochViewerSidePanel::SetRings(QVector<float> v) {
res_rings->setRings(v);
}