- Colour indexed spots from a secondary (or further) lattice in coral (#FA7268), the viewer's finishing accent, in the diffraction-image overlay. - Draw resolution rings by sweeping ResPhiToPxl into a QPainterPath (like the azimuthal ROI arcs), replacing the 4-point bounding-box ellipse and the poni_rot_3=0 hack, so rings follow the geometry on tilted detectors. - Add a spot/prediction colour legend to the Image-features side panel; the two user-adjustable swatches track the colour picker. - Show a colour swatch matching the on-image annotation on each ROI-list item, via a shared ROIAnnotationColor palette used by both the overlay and the list. - Add "Save image as JPEG..." / "...with overlay as JPEG..." to the image right-click menu, reusing a factored renderToImage() helper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
216 lines
9.1 KiB
C++
216 lines
9.1 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 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(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);
|
|
featuresSection->setExpanded(false);
|
|
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);
|
|
ringsSection->setExpanded(false);
|
|
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);
|
|
roiSection->setExpanded(false);
|
|
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);
|
|
}
|