// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #include #include #include #include #include #include #include "JFJochViewerSidePanel.h" #include "JFJochViewerImageROIStatistics.h" #include "widgets/TitleLabel.h" #include "JFJochViewerImageStatistics.h" #include "../image_analysis/geom_refinement/AssignSpotsToRings.h" 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); layout->addWidget(new TitleLabel("Image features", this)); // Image features... auto spotToggleCheckBox = new QCheckBox("Show spots", this); spotToggleCheckBox->setCheckState(Qt::CheckState::Checked); 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); resRingsCheckBox = new QCheckBox("Show resolution rings", this); resRingsCheckBox->setCheckState(Qt::Unchecked); autoResRingsCheckBox = new QCheckBox("Resolution rings auto.", this); res_rings_edit = new QLineEdit(this); res_rings_edit->setPlaceholderText("Ring positions, e.g., 1.0,2.5,3.7"); res_rings_edit->setEnabled(false); // Initially disabled as "Auto Res Rings" is checked by default 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); } }); 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); } }); 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(resRingsCheckBox, 2, 0); image_feature_grid->addWidget(autoResRingsCheckBox, 2, 1); image_feature_grid->addWidget(res_rings_edit, 3, 0, 1, 2); image_feature_grid->addWidget(saturatedPixelsCheckBox, 4, 0); image_feature_grid->addWidget(highestPixelsComboBox, 4, 1); image_feature_grid->addWidget(colorSelectButton, 5, 0); image_feature_grid->addWidget(spotColorSelectButton, 5, 1); layout->addLayout(image_feature_grid); connect(autoResRingsCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::enableAutoResRings); connect(res_rings_edit, &QLineEdit::editingFinished, this, &JFJochViewerSidePanel::editingFinished); connect(resRingsCheckBox, &QCheckBox::toggled, this, &JFJochViewerSidePanel::enableResRings); layout->addWidget(new TitleLabel("Image statistics plot", this)); chart = new JFJochViewerSidePanelChart(this); layout->addWidget(chart); connect(this, &JFJochViewerSidePanel::imageLoaded, chart, &JFJochViewerSidePanelChart::loadImage); layout->addWidget(new TitleLabel("ROI", this)); roi = new JFJochViewerImageROIStatistics(this); layout->addWidget(roi); connect(this, &JFJochViewerSidePanel::imageLoaded, roi, &JFJochViewerImageROIStatistics::loadImage); connect(roi, &JFJochViewerImageROIStatistics::ROIBoxConfigured, [this] (QRect box) { emit ROIBoxConfigured(box); }); connect(roi, &JFJochViewerImageROIStatistics::ROICircleConfigured, [this] (double x, double y, double radius) { emit ROICircleConfigured(x, y, radius); }); connect(roi, &JFJochViewerImageROIStatistics::AddROIToUserMask, [this]() { emit AddROIToUserMask(); }); connect(roi, &JFJochViewerImageROIStatistics::SubtractROIFromUserMask, [this]() { emit SubtractROIFromUserMask(); }); layout->addWidget(new TitleLabel("Data analysis", this)); auto analyzeButton = new QPushButton("Full analysis", this); connect(analyzeButton, &QPushButton::clicked,[this] {emit analyze();}); layout->addWidget(analyzeButton); // Calibrant selection combo box layout->addWidget(new TitleLabel("Powder geometry calibration", this)); calibrantCombo = new QComboBox(this); updateCalibrantList(); auto findBeamCenterButton = new QPushButton("Guess detector calibration", this); connect(findBeamCenterButton, &QPushButton::clicked,this, &JFJochViewerSidePanel::findBeamCenterClicked); auto optimizeBeamCenterButton = new QPushButton("Refine detector calibration", this); connect(optimizeBeamCenterButton, &QPushButton::clicked,this, &JFJochViewerSidePanel::optimizeBeamCenterClicked); auto calibrantRingsButton = new QPushButton("Display calibrant rings", this); connect(calibrantRingsButton, &QPushButton::clicked, this, [this]() { std::vector rings = CalculateXtalRings(GetCalibrant(), 10); QVector q_rings; for (float ring : rings) { q_rings.append(2 * M_PI / ring); } setRings(q_rings); }); // Add preset ice rings button below LaB6 calibration auto iceRingsButton = new QPushButton("Display ice rings", this); connect(iceRingsButton, &QPushButton::clicked, this, [this]() { // Set manual rings and enable display const QVector ice_rings(ICE_RING_RES_A.begin(), ICE_RING_RES_A.end()); setRings(ice_rings); }); auto refine_row = new QGridLayout(); refine_row->setSpacing(12); refine_row->addWidget(new QLabel("Calibrant:"),0,0); refine_row->addWidget(calibrantCombo,0,1); refine_row->addWidget(findBeamCenterButton,1, 0); refine_row->addWidget(optimizeBeamCenterButton,1, 1); refine_row->addWidget(calibrantRingsButton,2, 0); refine_row->addWidget(iceRingsButton,2, 1); layout->addLayout(refine_row); layout->addStretch(); setLayout(layout); // Set the layout to the widget } UnitCell JFJochViewerSidePanel::GetCalibrant() const { UnitCell uc(LAB6_CELL_A, LAB6_CELL_A, LAB6_CELL_A, 90, 90, 90); switch (calibrantCombo->currentIndex()) { case 1: // T. C. Huang , H. Toraya, T. N. Blanton, Y. Wu, J. Appl. Cryst. 26 (1993), 180-184. uc = UnitCell(5.1769, 4.7218, 58.380, 89.440, 89.634, 75.854); break; case 2: if (sample_cell) uc = sample_cell.value(); break; default: break; } return uc; } void JFJochViewerSidePanel::updateCalibrantList() { calibrantCombo->clear(); calibrantCombo->addItem("LaB6"); calibrantCombo->addItem("Silver Behenate"); if (sample_cell) calibrantCombo->addItem(QString("Current sample (%1 %2 %3 %4 %5 %6)") .arg(QString::number(sample_cell->a, 'f', 1)) .arg(QString::number(sample_cell->b, 'f', 1)) .arg(QString::number(sample_cell->c, 'f', 1)) .arg(QString::number(sample_cell->alpha, 'f', 1)) .arg(QString::number(sample_cell->beta, 'f', 1)) .arg(QString::number(sample_cell->gamma, 'f', 1))); calibrantCombo->setCurrentIndex(0); } void JFJochViewerSidePanel::optimizeBeamCenterClicked() { emit findBeamCenter(GetCalibrant(), false); } void JFJochViewerSidePanel::findBeamCenterClicked() { emit findBeamCenter(GetCalibrant(), true); } void JFJochViewerSidePanel::setRings(const QVector &v) { QString txt; for (float i : v) { txt += QString::number(i, 'f', 4) + ","; } resRingsCheckBox->setChecked(true); autoResRingsCheckBox->setChecked(false); res_rings_edit->setEnabled(true); res_rings_edit->setText(txt); emit setResRings(v); } void JFJochViewerSidePanel::enableAutoResRings(bool input) { res_rings_edit->setEnabled(!input); // Enable line edit only if "Auto Res Rings" is unchecked if (input) { emit autoResRings(); } else { editingFinished(); } } void JFJochViewerSidePanel::spotsToggled(bool input) { emit showSpots(input); } void JFJochViewerSidePanel::predictionsToggled(bool input) { emit showPredictions(input); } void JFJochViewerSidePanel::editingFinished() { QString text = res_rings_edit->text(); QStringList stringList = text.split(',', Qt::SkipEmptyParts); QVector manualResRings; bool validInput = true; for (const QString &str: stringList) { bool ok; float value = str.toFloat(&ok); if (ok && value >= 0) { manualResRings.append(value); } else { validInput = false; break; } } if (!validInput) { res_rings_edit->setStyleSheet("border: 1px solid red;"); // Highlight error } else { res_rings_edit->setStyleSheet(""); // Reset to default style emit setResRings(manualResRings); // Update resolution ring vector } } void JFJochViewerSidePanel::loadImage(std::shared_ptr image) { if (image) sample_cell = image->Dataset().experiment.GetUnitCell(); updateCalibrantList(); emit imageLoaded(image); } void JFJochViewerSidePanel::enableResRings(bool input) { if (!input) { autoResRingsCheckBox->setEnabled(false); res_rings_edit->setEnabled(false); emit setResRings({}); } else { autoResRingsCheckBox->setEnabled(true); enableAutoResRings(autoResRingsCheckBox->isChecked()); } } void JFJochViewerSidePanel::saturatedPixelsToggled(bool input) { emit showSaturatedPixels(input); } void JFJochViewerSidePanel::highlightIceRingsToggled(bool input) { emit highlightIceRings(input); } void JFJochViewerSidePanel::SetROIBox(QRect box) { roi->SetROIBox(box); } void JFJochViewerSidePanel::SetROICircle(double x, double y, double radius) { roi->SetROICircle(x, y, radius); }