383072ac80
Make the inline settings dock the single home for processing settings and retire the separate Processing-settings window (and the dock<->window sync): - "Analyze image" / "Analyze dataset" move to the top of the panel; the MX/AzInt toggle decides the dataset-job kind, so the job dialog drops its mode combo. - The panel gains the most-used spot-finding (max spots, high-resolution, min pixels), Bragg (Gaussian profile-fit, r1/r2/r3) and scaling (partiality, "3D rotation scaling" = rot3d combine + scale-fulls, merge Friedel, refine B, resolution limit) handles, a live indexing-algorithm description line, and now owns the Bragg/Scaling settings. The now-unused window tab classes are deleted. - Complete the PixelRefine removal on the viewer side (the "Pixel refinement" option + profile-multiplier widget), fixing the transient HEAD breakage. New JFJochMergeStatsWindow: an analysis pop-up for a finished merge (hero numbers over a per-resolution plot / per-shell table), auto-opened on completion and reopenable from the processing-jobs dock. Fixes: disable tear-off dock floating (a floated dock is a dead off-screen window under WSLg, which has no window manager); version the saved dock layout so a stale arrangement is discarded instead of restoring a broken one; keep the Analyze-button icons; right-align and equal-width the stats table; line-plot / table toggle icons (ToolbarIcons gains linePlot + table). Add ScalingSettings::HighResolutionLimit_A(optional) so the panel can clear the resolution limit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
217 lines
9.2 KiB
C++
217 lines
9.2 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "JFJochMergeStatsWindow.h"
|
|
|
|
#include <cmath>
|
|
|
|
#include <QButtonGroup>
|
|
#include <QComboBox>
|
|
#include <QFrame>
|
|
#include <QHBoxLayout>
|
|
#include <QHeaderView>
|
|
#include <QLabel>
|
|
#include <QStackedWidget>
|
|
#include <QTableWidget>
|
|
#include <QToolButton>
|
|
#include <QVBoxLayout>
|
|
#include <QtCharts/QChart>
|
|
#include <QtCharts/QValueAxis>
|
|
|
|
#include "../charts/JFJochSimpleChartView.h"
|
|
#include "../widgets/ToolbarIcons.h"
|
|
|
|
namespace {
|
|
// Per-shell metric value (NaN -> dropped by the chart). Keep in sync with the combo entries.
|
|
double ShellMetric(const MergeStatisticsShell &s, int metric) {
|
|
switch (metric) {
|
|
case 0: return s.cc_half * 100.0; // CC1/2 (%)
|
|
case 1: return s.r_meas * 100.0; // R-meas (%)
|
|
case 2: return s.mean_i_over_sigma; // <I/sigma>
|
|
case 3: return s.possible_unique_reflections > 0 // Completeness (%)
|
|
? 100.0 * s.unique_reflections / s.possible_unique_reflections : NAN;
|
|
case 4: return s.unique_reflections > 0 // Multiplicity
|
|
? static_cast<double>(s.total_observations) / s.unique_reflections : NAN;
|
|
case 5: return s.cc_ref * 100.0; // CCref (%)
|
|
default: return NAN;
|
|
}
|
|
}
|
|
|
|
// A big-number "card" for the hero row: large navy value over a small grey caption.
|
|
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;
|
|
}
|
|
}
|
|
|
|
JFJochMergeStatsWindow::JFJochMergeStatsWindow(const QString &title, const MergeStatistics &stats,
|
|
double isa, bool has_reference, QWidget *parent)
|
|
: QWidget(parent, Qt::Window), stats_(stats), has_reference_(has_reference) {
|
|
setWindowTitle("Merge statistics — " + title);
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
resize(740, 560);
|
|
|
|
auto *layout = new QVBoxLayout(this);
|
|
|
|
// --- Hero row: overall numbers ---
|
|
const auto &o = stats_.overall;
|
|
const double completeness = o.possible_unique_reflections > 0
|
|
? 100.0 * o.unique_reflections / o.possible_unique_reflections : NAN;
|
|
const double multiplicity = o.unique_reflections > 0
|
|
? static_cast<double>(o.total_observations) / o.unique_reflections : NAN;
|
|
auto pct = [](double v) { return std::isfinite(v) ? QString::number(v, 'f', 1) + "%" : QStringLiteral("—"); };
|
|
auto num = [](double v, int d) { return std::isfinite(v) ? QString::number(v, 'f', d) : QStringLiteral("—"); };
|
|
|
|
auto *hero = new QHBoxLayout();
|
|
hero->addWidget(MakeCard(num(isa, 1), "ISa", this));
|
|
hero->addWidget(MakeCard(pct(o.cc_half * 100.0), "CC½", this));
|
|
hero->addWidget(MakeCard(pct(o.r_meas * 100.0), "R-meas", this));
|
|
hero->addWidget(MakeCard(pct(completeness), "Completeness", this));
|
|
hero->addWidget(MakeCard(num(multiplicity, 1), "Multiplicity", this));
|
|
if (has_reference_)
|
|
hero->addWidget(MakeCard(pct(o.cc_ref * 100.0), "CCref", this));
|
|
layout->addLayout(hero);
|
|
|
|
// --- Controls: plot/table view toggle (icon buttons) + per-resolution metric selector ---
|
|
auto *row = new QHBoxLayout();
|
|
auto *plotBtn = new QToolButton(this);
|
|
plotBtn->setIcon(ToolbarIcons::linePlot());
|
|
plotBtn->setCheckable(true);
|
|
plotBtn->setChecked(true);
|
|
plotBtn->setToolTip("Per-resolution plot");
|
|
plotBtn->setStyleSheet(ToolbarIcons::buttonStyle());
|
|
auto *tableBtn = new QToolButton(this);
|
|
tableBtn->setIcon(ToolbarIcons::table());
|
|
tableBtn->setCheckable(true);
|
|
tableBtn->setToolTip("Per-shell table");
|
|
tableBtn->setStyleSheet(ToolbarIcons::buttonStyle());
|
|
auto *viewGroup = new QButtonGroup(this);
|
|
viewGroup->setExclusive(true);
|
|
viewGroup->addButton(plotBtn, 0);
|
|
viewGroup->addButton(tableBtn, 1);
|
|
row->addWidget(plotBtn);
|
|
row->addWidget(tableBtn);
|
|
row->addSpacing(12);
|
|
metricLabel_ = new QLabel("Metric:", this);
|
|
row->addWidget(metricLabel_);
|
|
metric_ = new QComboBox(this);
|
|
metric_->addItem("CC½ (%)", 0);
|
|
metric_->addItem("R-meas (%)", 1);
|
|
metric_->addItem("⟨I/σ⟩", 2);
|
|
metric_->addItem("Completeness (%)", 3);
|
|
metric_->addItem("Multiplicity", 4);
|
|
if (has_reference_)
|
|
metric_->addItem("CCref (%)", 5);
|
|
row->addWidget(metric_);
|
|
row->addStretch();
|
|
layout->addLayout(row);
|
|
|
|
// --- Stacked plot / table ---
|
|
stack_ = new QStackedWidget(this);
|
|
chart_ = new JFJochSimpleChartView(this);
|
|
table_ = new QTableWidget(this);
|
|
table_->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
|
table_->verticalHeader()->setVisible(false);
|
|
stack_->addWidget(chart_); // page 0
|
|
stack_->addWidget(table_); // page 1
|
|
layout->addWidget(stack_, 1);
|
|
|
|
connect(metric_, &QComboBox::currentIndexChanged, this, [this](int) { updatePlot(); });
|
|
connect(viewGroup, &QButtonGroup::idClicked, this, [this](int idx) {
|
|
stack_->setCurrentIndex(idx);
|
|
metric_->setVisible(idx == 0); // the metric selector + its label only apply to the plot
|
|
metricLabel_->setVisible(idx == 0);
|
|
});
|
|
|
|
buildTable();
|
|
updatePlot();
|
|
}
|
|
|
|
void JFJochMergeStatsWindow::updatePlot() {
|
|
const int metric = metric_->currentData().toInt();
|
|
std::vector<float> x, y;
|
|
double dmax = 0.0;
|
|
x.reserve(stats_.shells.size());
|
|
y.reserve(stats_.shells.size());
|
|
for (const auto &s: stats_.shells) {
|
|
x.push_back(s.mean_one_over_d2);
|
|
const double v = ShellMetric(s, metric);
|
|
y.push_back(static_cast<float>(v));
|
|
if (std::isfinite(v))
|
|
dmax = std::max(dmax, v);
|
|
}
|
|
chart_->UpdateData(x, y, "Resolution [Å]", metric_->currentText(), true);
|
|
|
|
// Absolute y-axis so positions are comparable: CC1/2 + CCref are 0..100, completeness 0..(>=100),
|
|
// multiplicity / R-meas / I-sigma start at 0. Show the labels (UpdateData hides them by default).
|
|
double ymin = 0.0, ymax = 1.0;
|
|
switch (metric) {
|
|
case 0: case 5: ymax = 100.0; break; // CC1/2, CCref
|
|
case 3: ymax = std::max(100.0, dmax * 1.05); break; // Completeness
|
|
default: ymax = dmax > 0.0 ? dmax * 1.05 : 1.0; break; // R-meas, I/sigma, multiplicity
|
|
}
|
|
const auto axes = chart_->chart()->axes(Qt::Vertical);
|
|
if (!axes.isEmpty()) {
|
|
if (auto *vax = qobject_cast<QValueAxis *>(axes.first())) {
|
|
vax->setRange(ymin, ymax);
|
|
vax->setLabelsVisible(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void JFJochMergeStatsWindow::buildTable() {
|
|
QStringList headers{"Res. [Å]", "N_obs", "Unique", "Compl. %", "Mult.", "⟨I/σ⟩", "R-meas %", "CC½ %"};
|
|
if (has_reference_)
|
|
headers << "CCref %";
|
|
table_->setColumnCount(headers.size());
|
|
table_->setHorizontalHeaderLabels(headers);
|
|
table_->setRowCount(static_cast<int>(stats_.shells.size()) + 1); // shells + overall
|
|
|
|
auto fillRow = [&](int rowIdx, const MergeStatisticsShell &s, const QString &resLabel) {
|
|
int c = 0;
|
|
auto set = [&](const QString &t) {
|
|
auto *it = new QTableWidgetItem(t);
|
|
it->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); // numbers right-aligned
|
|
table_->setItem(rowIdx, c++, it);
|
|
};
|
|
const double compl_ = s.possible_unique_reflections > 0
|
|
? 100.0 * s.unique_reflections / s.possible_unique_reflections : NAN;
|
|
const double mult = s.unique_reflections > 0
|
|
? static_cast<double>(s.total_observations) / s.unique_reflections : NAN;
|
|
set(resLabel);
|
|
set(QString::number(s.total_observations));
|
|
set(QString::number(s.unique_reflections));
|
|
set(std::isfinite(compl_) ? QString::number(compl_, 'f', 1) : QStringLiteral("—"));
|
|
set(std::isfinite(mult) ? QString::number(mult, 'f', 1) : QStringLiteral("—"));
|
|
set(QString::number(s.mean_i_over_sigma, 'f', 1));
|
|
set(QString::number(s.r_meas * 100.0, 'f', 1));
|
|
set(QString::number(s.cc_half * 100.0, 'f', 1));
|
|
if (has_reference_)
|
|
set(QString::number(s.cc_ref * 100.0, 'f', 1));
|
|
};
|
|
|
|
int rowIdx = 0;
|
|
for (const auto &s: stats_.shells)
|
|
fillRow(rowIdx++, s, QString::number(s.d_min, 'f', 2));
|
|
fillRow(rowIdx, stats_.overall, QStringLiteral("Overall"));
|
|
|
|
// Equal-width columns that fill the table (no super-wide last column).
|
|
table_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
|
}
|