// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include "JFJochCalibrationResultWindow.h" #include #include #include #include #include #include #include "../../common/JFJochMath.h" // PI namespace { constexpr double RAD_TO_DEG = 180.0 / PI; // A big-number "card" for the hero row, as in the merge-statistics window. QWidget *MakeCard(const QString &value, const QString &caption, QWidget *parent) { auto *card = new QFrame(parent); card->setFrameShape(QFrame::StyledPanel); auto *l = new QVBoxLayout(card); l->setContentsMargins(12, 8, 12, 8); l->setSpacing(0); auto *v = new QLabel(value, card); QFont f = v->font(); f.setPointSizeF(f.pointSizeF() * 2.2); f.setBold(true); v->setFont(f); v->setStyleSheet("color: #1F3A5F;"); v->setAlignment(Qt::AlignCenter); auto *c = new QLabel(caption, card); c->setStyleSheet("color: gray;"); c->setAlignment(Qt::AlignCenter); l->addWidget(v); l->addWidget(c); return card; } } JFJochCalibrationResultWindow::JFJochCalibrationResultWindow(const QString &title, const CalibrationResult &calibration, const DiffractionGeometry &header, const QString &poni_path, QWidget *parent) : QWidget(parent, Qt::Window) { setWindowTitle("Detector calibration — " + title); setAttribute(Qt::WA_DeleteOnClose); resize(640, 380); const DiffractionGeometry &g = calibration.geometry; const double pxl_mm = g.GetPixelSize_mm(); auto *layout = new QVBoxLayout(this); auto *hero = new QHBoxLayout(); hero->addWidget(MakeCard(QString::number(calibration.rms_radial_pxl, 'f', 2), "Radial rms [px]", this)); hero->addWidget(MakeCard(QString::number(calibration.beam_sigma_pxl, 'f', 3), "Beam σ [px]", this)); hero->addWidget(MakeCard(QString::number(calibration.ring_points), "Ring points", this)); layout->addLayout(hero); auto *table = new QTableWidget(6, 4, this); table->setHorizontalHeaderLabels({"Quantity", "Fitted", "From header", "Change"}); table->setEditTriggers(QAbstractItemView::NoEditTriggers); table->verticalHeader()->setVisible(false); table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); const struct { QString name; double fitted; double header; int digits; } rows[] = { {"PONI x [px]", g.GetBeamX_pxl(), header.GetBeamX_pxl(), 3}, {"PONI y [px]", g.GetBeamY_pxl(), header.GetBeamY_pxl(), 3}, {"Rot1 [°]", g.GetPoniRot1_rad() * RAD_TO_DEG, header.GetPoniRot1_rad() * RAD_TO_DEG, 4}, {"Rot2 [°]", g.GetPoniRot2_rad() * RAD_TO_DEG, header.GetPoniRot2_rad() * RAD_TO_DEG, 4}, {"Distance [mm]", g.GetDetectorDistance_mm(), header.GetDetectorDistance_mm(), 4}, }; for (int i = 0; i < 5; i++) { const double change = rows[i].fitted - rows[i].header; table->setItem(i, 0, new QTableWidgetItem(rows[i].name)); table->setItem(i, 1, new QTableWidgetItem(QString::number(rows[i].fitted, 'f', rows[i].digits))); table->setItem(i, 2, new QTableWidgetItem(QString::number(rows[i].header, 'f', rows[i].digits))); table->setItem(i, 3, new QTableWidgetItem( QStringLiteral("%1%2").arg(change >= 0 ? "+" : "").arg(change, 0, 'f', rows[i].digits))); } // The PONI is the point of normal incidence, which is what the .poni file stores; the direct beam // is where the beam actually lands, which is what every other program calls the beam centre. They // part company by distance*tan(rot) as soon as the detector is tilted, so show both. const auto [beam_x, beam_y] = g.GetDirectBeam_pxl(); table->setItem(5, 0, new QTableWidgetItem("Direct beam [px]")); table->setItem(5, 1, new QTableWidgetItem(QStringLiteral("%1, %2") .arg(beam_x, 0, 'f', 3).arg(beam_y, 0, 'f', 3))); table->setItem(5, 2, new QTableWidgetItem("—")); table->setItem(5, 3, new QTableWidgetItem("—")); layout->addWidget(table); auto *note = new QLabel(this); note->setWordWrap(true); note->setStyleSheet("color: gray;"); note->setText(QStringLiteral("PONI x = %1 mm, PONI y = %2 mm.%3") .arg(g.GetBeamX_pxl() * pxl_mm, 0, 'f', 4) .arg(g.GetBeamY_pxl() * pxl_mm, 0, 'f', 4) .arg(poni_path.isEmpty() ? QString() : " Written to " + poni_path)); layout->addWidget(note); }