viewer: show the space group in the merge-statistics window
The de-novo space-group search (SearchSpaceGroup) already ran inside Rugnux but only its chosen number survived on ProcessResult, and the viewer dropped even that. Carry the structured SearchSpaceGroupResult (point group + ranked candidate scores) on ProcessResult and thread it into JFJochMergeStatsWindow, which now shows a 'Space group' hero card (the final group, searched or fixed) plus a compact table of candidate groups and their absence scores. The library no longer renders the search to text (it used to embed it in merge_statistics_text); rugnux_cli formats it for stdout instead, so the CLI keeps its text table while the viewer draws a proper table and does not spew it to stdout. Also surface a user-fixed space group on the result so the card shows it too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+4
-3
@@ -669,7 +669,6 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) {
|
||||
if (end_msg.rotation_lattice_type.has_value())
|
||||
sg_opts.lattice_system = end_msg.rotation_lattice_type->crystal_system;
|
||||
auto sg_search = SearchSpaceGroup(sm.merged, sg_opts);
|
||||
stats_text << SearchSpaceGroupResultToText(sg_search) << "\n\n";
|
||||
|
||||
// Miller-index reindex under a change of basis: (h,k,l)_conv = reindex * (h,k,l)_prim.
|
||||
const auto reindex_hkl = [](auto &r, const gemmi::Mat33 &m) {
|
||||
@@ -723,8 +722,6 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) {
|
||||
SearchSpaceGroupOptions o2 = sg_opts;
|
||||
o2.lattice_system = cand.system;
|
||||
const auto s2 = SearchSpaceGroup(merged_c, o2);
|
||||
stats_text << "Pseudo-symmetry re-test in the metric candidate's conventional setting:\n"
|
||||
<< SearchSpaceGroupResultToText(s2) << "\n\n";
|
||||
if (s2.best_space_group.has_value() && s2.best_space_group->number > 1) {
|
||||
// The centring cannot be confirmed from absences here (integrated in the primitive
|
||||
// cell, so the centring-absent reflections do not exist) - it is metric-determined.
|
||||
@@ -785,6 +782,10 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) {
|
||||
phase("Re-scaling in space group " + sg.short_name());
|
||||
sm = scale_and_merge(sg.short_name(), false);
|
||||
}
|
||||
result.space_group_search = sg_search;
|
||||
} else {
|
||||
// A space group was fixed by the user; surface it so the viewer/CLI can still show it.
|
||||
result.space_group_number = experiment_.GetSpaceGroupNumber();
|
||||
}
|
||||
|
||||
// Ice-ring CC1/2 mask: a hexagonal-ice ring whose merged half-set CC1/2 collapses well below its
|
||||
|
||||
+8
-2
@@ -17,6 +17,7 @@
|
||||
#include "../image_analysis/spot_finding/SpotFindingSettings.h"
|
||||
#include "../image_analysis/scale_merge/Merge.h" // MergeStatistics
|
||||
#include "../image_analysis/scale_merge/TwinningAnalysis.h" // TwinningAnalysisResult
|
||||
#include "../image_analysis/scale_merge/SearchSpaceGroup.h" // SearchSpaceGroupResult
|
||||
|
||||
class JFJochHDF5Reader;
|
||||
|
||||
@@ -93,9 +94,14 @@ struct ProcessResult {
|
||||
// Twinning analysis of the final merged intensities (l_test_pairs == 0 when not computed).
|
||||
TwinningAnalysisResult twinning;
|
||||
|
||||
// Space group determined by the search (when the user did not fix one), used for the final
|
||||
// re-scale/merge and written to the master file.
|
||||
// Space group used for the final re-scale/merge and written to the master file: determined by the
|
||||
// search when the user did not fix one, otherwise the fixed group.
|
||||
std::optional<int64_t> space_group_number;
|
||||
|
||||
// Structured result of the de-novo space-group search (point group + ranked candidate scores),
|
||||
// populated only when a search ran (no space group was fixed). The library no longer renders it to
|
||||
// text - the CLI formats it for stdout, the viewer draws it as a table.
|
||||
std::optional<SearchSpaceGroupResult> space_group_search;
|
||||
};
|
||||
|
||||
// Callbacks for progress and live results. Methods may be called from worker threads, so an
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "../image_analysis/scale_merge/RotationScaleMerge.h"
|
||||
#include "../image_analysis/scale_merge/ResolutionCutoff.h"
|
||||
#include "../image_analysis/scale_merge/TwinningAnalysis.h"
|
||||
#include "../image_analysis/scale_merge/SearchSpaceGroup.h"
|
||||
#include "../rugnux/Rugnux.h"
|
||||
|
||||
// Default rot3d per-frame scale-G smoothing range (XDS DELPHI-like), in degrees of rotation.
|
||||
@@ -1291,6 +1292,11 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
g_active_process = nullptr;
|
||||
|
||||
// The space-group search is rendered here (not in the library) so the viewer does not emit it on
|
||||
// stdout and the CLI owns the format.
|
||||
if (result.space_group_search.has_value())
|
||||
std::cout << std::endl << SearchSpaceGroupResultToText(*result.space_group_search) << std::endl;
|
||||
|
||||
if (!result.merge_statistics_text.empty())
|
||||
std::cout << std::endl << result.merge_statistics_text << std::endl;
|
||||
|
||||
|
||||
@@ -12,12 +12,15 @@
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QStackedWidget>
|
||||
#include <QStringList>
|
||||
#include <QTableWidget>
|
||||
#include <QToolButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtCharts/QChart>
|
||||
#include <QtCharts/QValueAxis>
|
||||
|
||||
#include "gemmi/symmetry.hpp" // find_spacegroup_by_number
|
||||
|
||||
#include "../charts/JFJochSimpleChartView.h"
|
||||
#include "../widgets/ToolbarIcons.h"
|
||||
|
||||
@@ -62,7 +65,10 @@ namespace {
|
||||
|
||||
JFJochMergeStatsWindow::JFJochMergeStatsWindow(const QString &title, const MergeStatistics &stats,
|
||||
double isa, bool has_reference,
|
||||
const TwinningAnalysisResult &twinning, QWidget *parent)
|
||||
const TwinningAnalysisResult &twinning,
|
||||
std::optional<int64_t> space_group_number,
|
||||
const std::optional<SearchSpaceGroupResult> &space_group_search,
|
||||
QWidget *parent)
|
||||
: QWidget(parent, Qt::Window), stats_(stats), has_reference_(has_reference) {
|
||||
setWindowTitle("Merge statistics — " + title);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
@@ -87,8 +93,57 @@ JFJochMergeStatsWindow::JFJochMergeStatsWindow(const QString &title, const Merge
|
||||
hero->addWidget(MakeCard(num(multiplicity, 1), "Multiplicity", this));
|
||||
if (has_reference_)
|
||||
hero->addWidget(MakeCard(pct(o.cc_ref * 100.0), "CCref", this));
|
||||
if (space_group_number.has_value()) {
|
||||
const gemmi::SpaceGroup *sg = gemmi::find_spacegroup_by_number(static_cast<int>(*space_group_number));
|
||||
const QString sg_name = sg ? QString::fromStdString(sg->short_name())
|
||||
: QString::number(*space_group_number);
|
||||
hero->addWidget(MakeCard(sg_name, "Space group", this));
|
||||
}
|
||||
layout->addLayout(hero);
|
||||
|
||||
// De-novo space-group search: the point group + the ranked candidates it scored (only present when
|
||||
// the space group was not fixed by the user). A summary line above a compact table.
|
||||
if (space_group_search.has_value() && !space_group_search->candidates.empty()) {
|
||||
const auto &sgs = *space_group_search;
|
||||
QString summary = "Point group " + QString::fromStdString(sgs.point_group_hm.empty() ? "?" : sgs.point_group_hm);
|
||||
if (!sgs.alternatives.empty()) {
|
||||
QStringList alts;
|
||||
for (const auto &alt : sgs.alternatives)
|
||||
alts << QString::fromStdString(alt.short_name());
|
||||
summary += " · indistinguishable from these data: " + alts.join(", ");
|
||||
}
|
||||
auto *sgLabel = new QLabel(summary, this);
|
||||
sgLabel->setStyleSheet("color: gray;");
|
||||
layout->addWidget(sgLabel);
|
||||
|
||||
auto *sgTable = new QTableWidget(this);
|
||||
sgTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
sgTable->verticalHeader()->setVisible(false);
|
||||
const QStringList sgHeaders{"Space group", "Absent", "Viol.", "⟨I/σ⟩ abs", "⟨I/σ⟩ pres", "Consistent"};
|
||||
sgTable->setColumnCount(sgHeaders.size());
|
||||
sgTable->setHorizontalHeaderLabels(sgHeaders);
|
||||
sgTable->setRowCount(static_cast<int>(sgs.candidates.size()));
|
||||
for (int i = 0; i < static_cast<int>(sgs.candidates.size()); ++i) {
|
||||
const auto &c = sgs.candidates[i];
|
||||
int col = 0;
|
||||
auto set = [&](const QString &t, bool leftAlign = false) {
|
||||
auto *it = new QTableWidgetItem(t);
|
||||
it->setTextAlignment((leftAlign ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignVCenter);
|
||||
if (c.selected) { QFont f = it->font(); f.setBold(true); it->setFont(f); }
|
||||
sgTable->setItem(i, col++, it);
|
||||
};
|
||||
set(QString::fromStdString(c.space_group.short_name()) + (c.selected ? " *" : ""), true);
|
||||
set(QString::number(c.absent_observed));
|
||||
set(QString::number(c.absent_violations));
|
||||
set(QString::number(c.absent_mean_i_over_sigma, 'f', 2));
|
||||
set(QString::number(c.present_mean_i_over_sigma, 'f', 2));
|
||||
set(c.consistent ? QStringLiteral("yes") : QStringLiteral("no"));
|
||||
}
|
||||
sgTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
sgTable->setMaximumHeight(160); // a secondary detail — keep the shells plot the focus
|
||||
layout->addWidget(sgTable);
|
||||
}
|
||||
|
||||
// --- Twinning test (Padilla-Yeates L-test + second moment) ---
|
||||
// A red-outlined banner flags suspected twinning; otherwise a quiet grey "all clear".
|
||||
if (twinning.l_test_pairs > 0) {
|
||||
|
||||
@@ -5,8 +5,12 @@
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "../../image_analysis/scale_merge/Merge.h" // MergeStatistics
|
||||
#include "../../image_analysis/scale_merge/TwinningAnalysis.h" // TwinningAnalysisResult
|
||||
#include "../../image_analysis/scale_merge/SearchSpaceGroup.h" // SearchSpaceGroupResult
|
||||
|
||||
class QComboBox;
|
||||
class QLabel;
|
||||
@@ -23,7 +27,10 @@ class JFJochMergeStatsWindow : public QWidget {
|
||||
public:
|
||||
JFJochMergeStatsWindow(const QString &title, const MergeStatistics &stats,
|
||||
double isa, bool has_reference,
|
||||
const TwinningAnalysisResult &twinning, QWidget *parent = nullptr);
|
||||
const TwinningAnalysisResult &twinning,
|
||||
std::optional<int64_t> space_group_number,
|
||||
const std::optional<SearchSpaceGroupResult> &space_group_search,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
private:
|
||||
MergeStatistics stats_;
|
||||
|
||||
@@ -364,7 +364,8 @@ void JFJochProcessingJobsWindow::showStats(const QString &id) {
|
||||
for (const auto &j: jobs_) {
|
||||
if (j.id == id && j.has_merge_stats) {
|
||||
auto *win = new JFJochMergeStatsWindow(j.label, j.merge_stats, j.isa, j.merge_has_reference,
|
||||
j.twinning, window());
|
||||
j.twinning, j.space_group_number, j.space_group_search,
|
||||
window());
|
||||
win->show();
|
||||
return;
|
||||
}
|
||||
@@ -518,6 +519,8 @@ void JFJochProcessingJobsWindow::onFinished(ProcessResult result) {
|
||||
jobs_[row].isa = result.error_model_isa;
|
||||
jobs_[row].merge_has_reference = result.has_reference;
|
||||
jobs_[row].twinning = result.twinning;
|
||||
jobs_[row].space_group_number = result.space_group_number;
|
||||
jobs_[row].space_group_search = result.space_group_search;
|
||||
if (jobs_[row].graph_btn)
|
||||
jobs_[row].graph_btn->setEnabled(true);
|
||||
showStats(jobs_[row].id);
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <QElapsedTimer>
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "../JFJochProcessController.h"
|
||||
@@ -64,6 +66,8 @@ private:
|
||||
double isa = 0.0;
|
||||
bool merge_has_reference = false;
|
||||
TwinningAnalysisResult twinning; // twinning test of the merged intensities
|
||||
std::optional<int64_t> space_group_number; // final space group (searched or fixed)
|
||||
std::optional<SearchSpaceGroupResult> space_group_search; // ranked candidates, when a search ran
|
||||
QToolButton *graph_btn = nullptr; // per-row "show statistics" button (enabled once stats exist)
|
||||
};
|
||||
struct JobSpec {
|
||||
|
||||
Reference in New Issue
Block a user