diff --git a/rugnux/ReportDocument.h b/rugnux/ReportDocument.h index 4031ac619..429a16d03 100644 --- a/rugnux/ReportDocument.h +++ b/rugnux/ReportDocument.h @@ -64,6 +64,19 @@ struct ReportWarning { bool developer_only = false; }; +// One line of the SUMMARY's pathology checklist, for a rendering that lights an indicator per +// pathology rather than reading the summary's prose. PRESENT exactly when one of `codes` is in +// PATHOLOGY_FLAGS; ABSENT when it was looked for and did not fire; UNDETERMINED when it could not be +// measured on this run. `text` is the summary row's own words, `detail` the warnings that fired. +struct ReportCheck { + enum class Status { Present, Absent, Undetermined }; + std::string label; + std::vector codes; + Status status = Status::Undetermined; + std::string text; + std::string detail; +}; + // A value with its type kept, so a rendering other than text does not have to parse the string back. // `text` is always the exact spelling the text report prints, because that spelling is the interface. struct ReportValue { @@ -115,4 +128,5 @@ struct ReportDocument { std::string verdict; // closed: OK | WARNINGS | UNUSABLE | FAILED std::string verdict_text; // one to three sentences, free text std::vector pathology_flags; // PathologyCode::*, deduplicated, in first-seen order + std::vector checks; // the SUMMARY's pathologies, in the order it lists them }; diff --git a/rugnux/ResultReport.cpp b/rugnux/ResultReport.cpp index 189295c7b..31a125663 100644 --- a/rugnux/ResultReport.cpp +++ b/rugnux/ResultReport.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -2266,6 +2267,23 @@ ReportDocument BuildReportDocument(const std::string &output_prefix, if (!value.empty()) f += fmt::format(" {:<18} {}\n", label, value); }; + // The pathology rows are also recorded as checks, with their status read off the flags. + const auto check = [&](const char *label, std::vector codes, const std::string &value) { + ReportCheck c{label, std::move(codes), ReportCheck::Status::Absent, value, {}}; + for (const auto &w : doc.warnings) + if (!w.developer_only && std::ranges::find(c.codes, w.code) != c.codes.end()) { + c.status = ReportCheck::Status::Present; + c.detail += (c.detail.empty() ? "" : "\n") + w.text; + } + doc.checks.push_back(std::move(c)); + }; + const auto checked_row = [&](const char *label, std::vector codes, const std::string &value) { + row(label, value); + check(label, std::move(codes), value); + }; + const auto untested = [&](const char *label, std::vector codes) { + doc.checks.push_back({label, std::move(codes), ReportCheck::Status::Undetermined, "not measured", {}}); + }; if (result.space_group) row("Space group", group); if (result.consensus_cell) @@ -2281,7 +2299,7 @@ ReportDocument BuildReportDocument(const std::string &output_prefix, "with -z or --model", result.indexing_ambiguity_operators)); if (!result.powder.rings_q_recipA.empty() || result.consensus_cell) { const auto &pw = result.powder; - row("Powder", pw.rings_q_recipA.empty() ? std::string("no rings detected") + checked_row("Powder", {PathologyCode::POWDER_RINGS}, pw.rings_q_recipA.empty() ? std::string("no rings detected") : fmt::format("{} rings holding {:.0f}% of the spots found - {} ice ({:.0f}%), {} other " "({:.0f}%){}", pw.rings_q_recipA.size(), 100.0f * pw.spot_fraction, pw.ice_ring_count, 100.0f * pw.ice_spot_fraction, @@ -2289,17 +2307,24 @@ ReportDocument BuildReportDocument(const std::string &output_prefix, result.powder_excluded_from_indexing ? "; left out of indexing" : "")); } if (result.ice_ring_score || result.ice_spot_ratio) - row("Ice", result.ice_rings_detected + checked_row("Ice", {PathologyCode::ICE_RINGS}, result.ice_rings_detected ? fmt::format("rings DETECTED: the {:.1f}% of the reflections on them were left out of " "scaling, kept in the merge", 100.0 * result.ice_flagged_fraction) : std::string("no rings detected")); + else + untested("Ice", {PathologyCode::ICE_RINGS}); if (result.harmonic.detected) - row("Beam", fmt::format("higher harmonic INDICATED ({:.0f} % of the unindexed spots " + checked_row("Beam", {PathologyCode::HARMONIC_CONTAMINATION}, + fmt::format("higher harmonic INDICATED ({:.0f} % of the unindexed spots " "are lattice points at lambda/3)", result.harmonic.fraction)); + else if (result.harmonic.measurable) + check("Beam", {PathologyCode::HARMONIC_CONTAMINATION}, "no higher harmonic indicated"); + else + untested("Beam", {PathologyCode::HARMONIC_CONTAMINATION}); if (result.leftover_lattices) { const auto &c = *result.leftover_lattices; const auto strongest = std::ranges::max_element(c.lattices, {}, &LeftoverLattice::intensity_pct); - row("Multiple lattices", strongest == c.lattices.end() ? std::string("none found") + checked_row("Multiple lattices", {PathologyCode::MULTIPLE_LATTICES}, strongest == c.lattices.end() ? std::string("none found") : fmt::format("{}{}: {:.0f}% of spot intensity (main lattice {:.0f}%){}", c.lattices.size() > 1 ? fmt::format("{} further lattices, the strongest: ", c.lattices.size()) : "", @@ -2307,6 +2332,8 @@ ReportDocument BuildReportDocument(const std::string &output_prefix, c.primary_intensity_pct, DomainIntensityPct(c) >= MULTIPLE_LATTICES_INTENSITY_PCT ? " - check the crystal" : "")); + } else { + untested("Multiple lattices", {PathologyCode::MULTIPLE_LATTICES}); } if (merged) { std::string res = fmt::format("{:.2f} A written", o.d_min); @@ -2319,7 +2346,7 @@ ReportDocument BuildReportDocument(const std::string &output_prefix, if (completeness_narrower) c = fmt::format("{:.1f} % to {:.2f} A; {}", completeness_fit.percent, completeness_fit.d_min, c); - row("Completeness", c); + checked_row("Completeness", {PathologyCode::LOW_COMPLETENESS}, c); } std::string sig; if (std::isfinite(o.mean_i_over_sigma)) @@ -2338,9 +2365,12 @@ ReportDocument BuildReportDocument(const std::string &output_prefix, ? fmt::format("signal present (CC_anom {:.2f})", o.cc_anom) : fmt::format("no usable signal (CC_anom {:.2f})", o.cc_anom)); const auto &an = result.merge_statistics.anisotropy; - if (an.n_reflections > 0) { + if (an.n_reflections == 0) + untested("Anisotropy", {PathologyCode::ANISOTROPY}); + else { if (an.verdict != AnisotropyVerdict::Detected) - row("Anisotropy", "not established above this data set's own noise"); + checked_row("Anisotropy", {PathologyCode::ANISOTROPY}, + "not established above this data set's own noise"); else { const double *lo = nullptr, *hi = nullptr; for (const double *p = an.d_min_axis; p != an.d_min_axis + 3; ++p) { @@ -2351,7 +2381,7 @@ ReportDocument BuildReportDocument(const std::string &output_prefix, } const char *sev = an.d_min_spread < 0.25 ? "small" : an.d_min_spread < 0.5 ? "noticeable" : "STRONG"; - row("Anisotropy", lo && hi + checked_row("Anisotropy", {PathologyCode::ANISOTROPY}, lo && hi ? fmt::format("{}: deltaB {:.1f} A^2, d_min {:.2f}-{:.2f} A by direction", sev, an.delta_b, *lo, *hi) : fmt::format("{}: deltaB {:.1f} A^2", sev, an.delta_b)); @@ -2376,10 +2406,15 @@ ReportDocument BuildReportDocument(const std::string &output_prefix, t.peak_percent >= PSEUDO_TRANSLATION_WORDING_PCT ? "INDICATED" : "INDICATED, weak - check whether molecular replacement needs it", t.peak_percent, t.vector_length_A); - row("Pseudo-symmetry", ps.empty() ? std::string("no indication") : ps); + checked_row("Pseudo-symmetry", {PathologyCode::PSEUDO_TRANSLATION, PathologyCode::LATTICE_TRANSLATION}, + ps.empty() ? std::string("no indication") : ps); + } else { + untested("Pseudo-symmetry", {PathologyCode::PSEUDO_TRANSLATION, PathologyCode::LATTICE_TRANSLATION}); } if (result.twinning.l_test_pairs > 0) - row("Twinning", TwinVerdict(result)); + checked_row("Twinning", {PathologyCode::TWINNING}, TwinVerdict(result)); + else + untested("Twinning", {PathologyCode::TWINNING}); if (result.supercell) { const auto &sc = *result.supercell; const std::string doubled = fmt::format( @@ -2389,14 +2424,16 @@ ReportDocument BuildReportDocument(const std::string &output_prefix, // Spots of further domains of the crystal can land on the half-integer positions. const bool domains = result.leftover_lattices && DomainIntensityPct(*result.leftover_lattices) >= 5.0; if (!SupercellPossible(sc)) - row("Supercell", "no indication"); + checked_row("Supercell", {PathologyCode::SUPERCELL_POSSIBLE}, "no indication"); else - row("Supercell", fmt::format("POSSIBLE, check - class {} {} {} rocks like Bragg reflections " + checked_row("Supercell", {PathologyCode::SUPERCELL_POSSIBLE}, fmt::format("POSSIBLE, check - class {} {} {} rocks like Bragg reflections " "(rocking part {:.1f}%); correct cells with weak ordered intensity " "read the same{}; {}", sc.h, sc.k, sc.l, sc.rock_pct, domains ? ", and the further lattice domains found may put " "spots there" : "", doubled)); + } else { + untested("Supercell", {PathologyCode::SUPERCELL_POSSIBLE}); } const double db = result.merge_statistics.radiation_damage_delta_b; if (std::isfinite(db)) @@ -2404,7 +2441,7 @@ ReportDocument BuildReportDocument(const std::string &output_prefix, const auto &sq = result.merge_statistics.sweep_quality; if (sq.measured) { const int n_sweep = sq.frames_merged + sq.frames_downgraded + sq.frames_rejected; - row("Sweep", sq.frames_rejected > 0 + checked_row("Sweep", {PathologyCode::SWEEP_GAPS}, sq.frames_rejected > 0 ? fmt::format("{} of {} frames rejected ({:.1f} deg of {:.1f}), " "{} degraded range(s)", sq.frames_rejected, n_sweep, sq.rejected_deg, sq.sweep_deg, sq.ranges.size()) @@ -2412,8 +2449,22 @@ ReportDocument BuildReportDocument(const std::string &output_prefix, ? fmt::format("no degraded ranges in {:.1f} deg", sq.sweep_deg) : fmt::format("{} degraded range(s) in {:.1f} deg, none rejected", sq.ranges.size(), sq.sweep_deg))); + } else { + untested("Sweep", {PathologyCode::SWEEP_GAPS}); } } + // A flag no row above stands for is still a pathology that fired: its own check, named + // after its code, so the checklist never shows less than PATHOLOGY_FLAGS. + for (const auto &code : doc.pathology_flags) + if (std::ranges::none_of(doc.checks, [&](const ReportCheck &c) { + return std::ranges::find(c.codes, code) != c.codes.end(); })) { + std::string label = code; + std::ranges::replace(label, '_', ' '); + std::transform(label.begin() + 1, label.end(), label.begin() + 1, + [](unsigned char ch) { return std::tolower(ch); }); + check(label.c_str(), {code}, ""); + doc.checks.back().text = doc.checks.back().detail.substr(0, doc.checks.back().detail.find('\n')); + } if (!f.empty()) { f.pop_back(); Add(s, Prose(f)); diff --git a/tests/ResultReportTest.cpp b/tests/ResultReportTest.cpp index b5f26dd21..1f964ed5c 100644 --- a/tests/ResultReportTest.cpp +++ b/tests/ResultReportTest.cpp @@ -1165,3 +1165,38 @@ TEST_CASE("ResultReport_SecondCrystal", "[Diagnostics]") { CHECK(text.find("second crystal, 38.9 deg from the main lattice") != std::string::npos); CHECK(text.find("split crystal, ") == std::string::npos); } + +// The summary's checklist: a status per pathology, PRESENT exactly where its code is flagged, and no +// flag left without a check of its own. +TEST_CASE("ResultReport_Checks", "[Diagnostics]") { + DiffractionExperiment x(DetJF(1)); + ProcessResult result; + result.consensus_cell = UnitCell{.a = 79.0f, .b = 79.0f, .c = 38.0f, + .alpha = 90.0f, .beta = 90.0f, .gamma = 90.0f}; + result.pass_number = 10; + result.pass_count = 10; + result.geometry_not_converged = true; + result.harmonic.measurable = true; + + const auto doc = BuildReportDocument("p", "in.h5", x, result); + const auto find = [&](const std::string &label) { + return std::ranges::find(doc.checks, label, &ReportCheck::label); + }; + REQUIRE(find("Beam") != doc.checks.end()); + CHECK(find("Beam")->status == ReportCheck::Status::Absent); + REQUIRE(find("Multiple lattices") != doc.checks.end()); + CHECK(find("Multiple lattices")->status == ReportCheck::Status::Undetermined); + // GEOMETRY_NOT_CONVERGED has no summary row: it gets a check named after its code. + REQUIRE(find("Geometry not converged") != doc.checks.end()); + CHECK(find("Geometry not converged")->status == ReportCheck::Status::Present); + CHECK(!find("Geometry not converged")->text.empty()); + + for (const auto &code : doc.pathology_flags) + CHECK(std::ranges::any_of(doc.checks, [&](const ReportCheck &c) { + return c.status == ReportCheck::Status::Present + && std::ranges::find(c.codes, code) != c.codes.end(); })); + for (const auto &c : doc.checks) + if (c.status == ReportCheck::Status::Present) + CHECK(std::ranges::any_of(c.codes, [&](const std::string &code) { + return std::ranges::find(doc.pathology_flags, code) != doc.pathology_flags.end(); })); +} diff --git a/viewer/JFJochProcessController.cpp b/viewer/JFJochProcessController.cpp index 84c31c05e..699c82e09 100644 --- a/viewer/JFJochProcessController.cpp +++ b/viewer/JFJochProcessController.cpp @@ -16,6 +16,7 @@ JFJochProcessController::JFJochProcessController(QObject *parent) : QObject(parent) { qRegisterMetaType("ProcessResult"); qRegisterMetaType>("std::shared_ptr"); + qRegisterMetaType>("std::vector"); } JFJochProcessController::~JFJochProcessController() { @@ -98,19 +99,22 @@ void JFJochProcessController::run_(QString file_path, DiffractionExperiment expe // with WriteResultReport, a run that produced results must not be lost to a report // failure, so a throw here leaves the report empty rather than failing the job. QString report; + std::vector checks; if (!result.cancelled) { try { RunProvenance provenance; provenance.wall_time_s = run_timer.elapsed() / 1000.0; - report = QString::fromStdString(RenderResultReport( - output_prefix, file_path.toStdString(), report_experiment, result, provenance)); + ReportDocument doc = BuildReportDocument(output_prefix, file_path.toStdString(), + report_experiment, result, provenance); + report = QString::fromStdString(RenderReportText(doc, provenance.developer)); + checks = std::move(doc.checks); } catch (...) { } } active_ = nullptr; running_ = false; - emit finished(result, report); + emit finished(result, report, checks); } catch (const std::exception &e) { active_ = nullptr; running_ = false; diff --git a/viewer/JFJochProcessController.h b/viewer/JFJochProcessController.h index 8f16f4462..c603dc324 100644 --- a/viewer/JFJochProcessController.h +++ b/viewer/JFJochProcessController.h @@ -18,6 +18,7 @@ #include "../reader/JFJochReaderDataset.h" Q_DECLARE_METATYPE(ProcessResult) +Q_DECLARE_METATYPE(std::vector) // Runs one Rugnux job off the GUI thread and reports back via queued Qt signals. The job // opens its own private reader on the file (HDF5 access is globally serialized, so this @@ -44,8 +45,9 @@ signals: void phaseChanged(QString phase); void progress(quint64 done, quint64 total); // `report` is the rugnux result report (the _report.txt text), rendered from the run's - // own results so the viewer can show it without a file; empty for a cancelled run. - void finished(ProcessResult result, QString report); + // own results so the viewer can show it without a file; empty for a cancelled run. `checks` is + // the same report's pathology checklist. + void finished(ProcessResult result, QString report, std::vector checks); void failed(QString error); // Per-image results accumulated so far, for live dataset-info plots while a job runs (throttled). void liveDataset(std::shared_ptr dataset); diff --git a/viewer/windows/JFJochMergeStatsWindow.cpp b/viewer/windows/JFJochMergeStatsWindow.cpp index 85fb84f49..a49f009ac 100644 --- a/viewer/windows/JFJochMergeStatsWindow.cpp +++ b/viewer/windows/JFJochMergeStatsWindow.cpp @@ -11,7 +11,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -54,6 +56,7 @@ namespace { l->setContentsMargins(12, 8, 12, 8); l->setSpacing(0); auto *v = new QLabel(value, card); + v->setTextFormat(Qt::RichText); // the space group carries QFont f = v->font(); f.setPointSizeF(f.pointSizeF() * 2.2); f.setBold(true); @@ -67,6 +70,53 @@ namespace { l->addWidget(c); return card; } + + // The short Hermann-Mauguin symbol as rich text, screw axes subscripted and rotoinversions + // overlined: "P 1 21 1" -> P21, "P 41 21 2" -> P41212. Built from + // the spaced symbol, where short_name()'s run-together digits are ambiguous (P622 vs P6_2 2). + QString SpaceGroupHtml(const gemmi::SpaceGroup &sg) { + std::string hm = sg.hm; + // the monoclinic shortening of short_name(): "P 1 21 1" -> "P 21" + if (hm.size() > 6 && hm[2] == '1' && hm[hm.size() - 2] == ' ' && hm.back() == '1') + hm = hm[0] + hm.substr(3, hm.size() - 3 - 2); + if (sg.ext == 'H') + hm[0] = 'H'; + QString out; + for (const auto &token : QString::fromStdString(hm).split(' ', Qt::SkipEmptyParts)) { + if (token.size() >= 2 && token[0] == '-') + out += "" + token.mid(1, 1) + "" + token.mid(2); + else if (token.size() >= 2 && token[0].isDigit() && token[1].isDigit()) + out += token.left(1) + "" + token.mid(1, 1) + "" + token.mid(2); + else + out += token; + } + return out; + } + + // Status light of a pathology check: green tick absent, red cross present, grey dash undetermined. + QPixmap CheckIcon(ReportCheck::Status status, int size, qreal dpr) { + QPixmap pm(QSize(size, size) * dpr); + pm.setDevicePixelRatio(dpr); + pm.fill(Qt::transparent); + QPainter p(&pm); + p.setRenderHint(QPainter::Antialiasing); + const QColor fill = status == ReportCheck::Status::Present ? QColor(200, 40, 40) + : status == ReportCheck::Status::Absent ? QColor(40, 150, 70) + : QColor(150, 150, 150); + p.setPen(Qt::NoPen); + p.setBrush(fill); + p.drawEllipse(QRectF(0.5, 0.5, size - 1.0, size - 1.0)); + p.setPen(QPen(Qt::white, size / 8.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + const double u = size / 16.0; + if (status == ReportCheck::Status::Absent) + p.drawPolyline(QPolygonF({QPointF(4.5 * u, 8.5 * u), QPointF(7 * u, 11 * u), QPointF(11.5 * u, 5.5 * u)})); + else if (status == ReportCheck::Status::Present) { + p.drawLine(QPointF(5 * u, 5 * u), QPointF(11 * u, 11 * u)); + p.drawLine(QPointF(11 * u, 5 * u), QPointF(5 * u, 11 * u)); + } else + p.drawLine(QPointF(4.5 * u, 8 * u), QPointF(11.5 * u, 8 * u)); + return pm; + } } JFJochMergeStatsWindow::JFJochMergeStatsWindow(const QString &title, const MergeStatistics &stats, @@ -76,6 +126,7 @@ JFJochMergeStatsWindow::JFJochMergeStatsWindow(const QString &title, const Merge const std::optional &space_group_search, const std::vector> &merged_i_sigma, const QString &report, + const std::vector &checks, QWidget *parent) : QWidget(parent, Qt::Window), stats_(stats), has_reference_(has_reference) { setWindowTitle("Merge statistics — " + title); @@ -114,9 +165,45 @@ JFJochMergeStatsWindow::JFJochMergeStatsWindow(const QString &title, const Merge if (has_reference_) hero->addWidget(MakeCard(pct(o.cc_ref * 100.0), "CCref", this)); if (space_group.has_value()) - hero->addWidget(MakeCard(QString::fromStdString(space_group->short_name()), "Space group", this)); + hero->addWidget(MakeCard(SpaceGroupHtml(*space_group), "Space group", this)); layout->addLayout(hero); + // --- Pathology checklist, as phenix.xtriage lists its tests: one status light per pathology the + // report's summary looks for, its words and the warnings it raised in the tooltip. + if (!checks.empty()) { + auto *grid = new QGridLayout(); + grid->setHorizontalSpacing(16); + grid->setVerticalSpacing(2); + constexpr int columns = 4; + for (int i = 0; i < static_cast(checks.size()); ++i) { + const auto &c = checks[i]; + auto *item = new QWidget(this); + auto *l = new QHBoxLayout(item); + l->setContentsMargins(0, 0, 0, 0); + l->setSpacing(6); + auto *icon = new QLabel(item); + icon->setPixmap(CheckIcon(c.status, 14, devicePixelRatioF())); + auto *label = new QLabel(QString::fromStdString(c.label), item); + if (c.status == ReportCheck::Status::Present) { + QFont f = label->font(); + f.setBold(true); + label->setFont(f); + } else if (c.status == ReportCheck::Status::Undetermined) { + label->setStyleSheet("color: gray;"); + } + l->addWidget(icon); + l->addWidget(label); + l->addStretch(); + QString tip = "" + QString::fromStdString(c.label).toHtmlEscaped() + ": " + + QString::fromStdString(c.text).toHtmlEscaped(); + if (!c.detail.empty() && c.detail != c.text) + tip += "

" + QString::fromStdString(c.detail).toHtmlEscaped().replace('\n', "

"); + item->setToolTip("" + tip + ""); + grid->addWidget(item, i / columns, i % columns); + } + layout->addLayout(grid); + } + // 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()) { @@ -125,10 +212,11 @@ JFJochMergeStatsWindow::JFJochMergeStatsWindow(const QString &title, const Merge if (!sgs.alternatives.empty()) { QStringList alts; for (const auto &alt : sgs.alternatives) - alts << QString::fromStdString(alt.short_name()); + alts << SpaceGroupHtml(alt); summary += " · indistinguishable from these data: " + alts.join(", "); } auto *sgLabel = new QLabel(summary, this); + sgLabel->setTextFormat(Qt::RichText); sgLabel->setStyleSheet("color: gray;"); layout->addWidget(sgLabel); diff --git a/viewer/windows/JFJochMergeStatsWindow.h b/viewer/windows/JFJochMergeStatsWindow.h index 199fb3169..7c06c2c41 100644 --- a/viewer/windows/JFJochMergeStatsWindow.h +++ b/viewer/windows/JFJochMergeStatsWindow.h @@ -13,6 +13,7 @@ #include "../../image_analysis/scale_merge/Merge.h" // MergeStatistics #include "../../image_analysis/scale_merge/TwinningAnalysis.h" // TwinningAnalysisResult #include "../../image_analysis/scale_merge/SearchSpaceGroup.h" // SearchSpaceGroupResult +#include "../../rugnux/ReportDocument.h" // ReportCheck class QComboBox; class QLabel; @@ -22,7 +23,7 @@ class JFJochSimpleChartView; // Pop-up analysis window for a finished scaling/merging run: a row of eye-catching overall numbers // (ISa, CC1/2, R-meas, completeness, multiplicity, the Wilson B and the radiation-damage Delta-B, and -// CCref when a reference was used) above a per-resolution-shell plot whose metric is chosen from a +// CCref when a reference was used) and the report's pathology checklist above a per-resolution-shell plot whose metric is chosen from a // combo, a per-shell table, and an ISa diagnostic scatter. Opened automatically when a merge job // finishes and on demand from the processing-jobs dock. Modeless; deletes on close. class JFJochMergeStatsWindow : public QWidget { @@ -36,6 +37,7 @@ public: const std::optional &space_group_search, const std::vector> &merged_i_sigma, const QString &report, + const std::vector &checks, QWidget *parent = nullptr); private: diff --git a/viewer/windows/JFJochProcessingJobsWindow.cpp b/viewer/windows/JFJochProcessingJobsWindow.cpp index 0e0aa6f3a..d66b0b7bf 100644 --- a/viewer/windows/JFJochProcessingJobsWindow.cpp +++ b/viewer/windows/JFJochProcessingJobsWindow.cpp @@ -599,7 +599,7 @@ void JFJochProcessingJobsWindow::showStats(const QString &id) { 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, j.space_group, j.space_group_search, - j.merged_i_sigma, j.report, window()); + j.merged_i_sigma, j.report, j.checks, window()); win->show(); return; } @@ -719,7 +719,7 @@ void JFJochProcessingJobsWindow::onProgress(quint64 done, quint64 total) { } } -void JFJochProcessingJobsWindow::onFinished(ProcessResult result, QString report) { +void JFJochProcessingJobsWindow::onFinished(ProcessResult result, QString report, std::vector checks) { emit liveDataset(nullptr); // the finished run takes over from the live overlay const int row = running_row_; running_row_ = -1; @@ -766,6 +766,7 @@ void JFJochProcessingJobsWindow::onFinished(ProcessResult result, QString report if (!result.cancelled && result.has_merge_statistics) { jobs_[row].has_merge_stats = true; jobs_[row].report = report; + jobs_[row].checks = std::move(checks); jobs_[row].merge_stats = result.merge_statistics; jobs_[row].isa = result.error_model_isa; jobs_[row].merge_has_reference = result.has_reference; diff --git a/viewer/windows/JFJochProcessingJobsWindow.h b/viewer/windows/JFJochProcessingJobsWindow.h index 08d8eb692..817c05f8b 100644 --- a/viewer/windows/JFJochProcessingJobsWindow.h +++ b/viewer/windows/JFJochProcessingJobsWindow.h @@ -54,7 +54,7 @@ private slots: void viewResults(); void onPhase(QString phase); void onProgress(quint64 done, quint64 total); - void onFinished(ProcessResult result, QString report); + void onFinished(ProcessResult result, QString report, std::vector checks); void onFailed(QString error); private: @@ -73,6 +73,7 @@ private: std::optional space_group_search; // ranked candidates, when a search ran std::vector> merged_i_sigma; // ISa diagnostic scatter QString report; // the rugnux result report text (a section of the window) + std::vector checks; // the report's pathology checklist // Detector calibration result (Calibration mode): the fit, the header geometry it is compared // against, and where the .poni was written. std::optional calibration;