rugnux live: a cancelled rotation run reports CANCELLED, counted in images received
A cancelled collection showed verdict FAILED, "No crystal lattice was found in these frames" and "cancelled after 0 images", with step titles carrying the planned image counts. The live status now has verdict CANCELLED, says how many images came before the cancel, and a step title's ", N images" is capped at the images received once the run is cut by a cancel. The report of a rugnux CLI run is unchanged. The frontend shows CANCELLED as info, not as an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK
This commit is contained in:
1 parent
2978a9f928
commit
8f3ba8d389
6 files changed
+72
-3
No files matched your search
@@ -11,6 +11,8 @@ export const verdictSeverity = (verdict: string) => {
|
||||
return "success";
|
||||
case "WARNINGS":
|
||||
return "warning";
|
||||
case "CANCELLED":
|
||||
return "info";
|
||||
default:
|
||||
return "error";
|
||||
}
|
||||
|
||||
@@ -254,12 +254,24 @@ void JFJochReceiverRugnuxRotation::RugnuxThread() {
|
||||
// The verdict and summary of the report a `rugnux` run writes, without writing it.
|
||||
const ReportDocument doc = BuildReportDocument(experiment.GetFilePrefix(), experiment.GetFilePrefix(),
|
||||
setup.experiment, result);
|
||||
// A cancelled collection is no failed analysis, and the run stops at the images that came
|
||||
const int64_t images_received = live_status.Get().images_received;
|
||||
std::string verdict = doc.verdict;
|
||||
std::string verdict_text = doc.verdict_text;
|
||||
if (result.cancelled) {
|
||||
verdict = "CANCELLED";
|
||||
verdict_text = fmt::format("The collection was cancelled after {} images - these results describe "
|
||||
"an incomplete sweep.", images_received);
|
||||
}
|
||||
std::vector<std::string> warnings;
|
||||
for (const auto &warning: doc.warnings)
|
||||
if (!warning.developer_only)
|
||||
if (result.cancelled && warning.code == PathologyCode::CANCELLED)
|
||||
warnings.push_back(fmt::format("Processing was cancelled after {} images - this report describes "
|
||||
"an incomplete run", images_received));
|
||||
else if (!warning.developer_only)
|
||||
warnings.push_back(warning.text);
|
||||
const double cc_anom = result.merge_statistics.overall.cc_anom;
|
||||
live_status.Result(doc.verdict, doc.verdict_text, doc.summary_rows, warnings,
|
||||
live_status.Result(verdict, verdict_text, doc.summary_rows, warnings,
|
||||
std::isfinite(cc_anom) && cc_anom > CC_ANOM_SIGNAL);
|
||||
if (const auto live_model = LiveModel(result))
|
||||
live_status.Model(*live_model);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "RugnuxLiveStatus.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
|
||||
#include "../rugnux/Rugnux.h"
|
||||
@@ -16,6 +17,19 @@ double RugnuxLiveStatus::Now() const {
|
||||
return Since(std::chrono::steady_clock::now());
|
||||
}
|
||||
|
||||
// A step of the run, "..., N images", covers no more images than came: after a cancel the run goes on over those.
|
||||
void RugnuxLiveStatus::CapImages(std::string &title) const {
|
||||
const std::string suffix = " images";
|
||||
const size_t pos = title.rfind(", ");
|
||||
if (snapshot.cut != "cancel" || pos == std::string::npos || !title.ends_with(suffix))
|
||||
return;
|
||||
const std::string count = title.substr(pos + 2, title.size() - pos - 2 - suffix.size());
|
||||
if (count.empty() || !std::all_of(count.begin(), count.end(), ::isdigit))
|
||||
return;
|
||||
if (std::stoll(count) > snapshot.images_received)
|
||||
title = title.substr(0, pos + 2) + std::to_string(snapshot.images_received) + suffix;
|
||||
}
|
||||
|
||||
void RugnuxLiveStatus::CloseStep() {
|
||||
if (!snapshot.steps.empty() && !snapshot.steps.back().duration_s)
|
||||
snapshot.steps.back().duration_s = Now() - snapshot.steps.back().started_s;
|
||||
@@ -47,6 +61,7 @@ void RugnuxLiveStatus::Step(const std::string &title) {
|
||||
std::unique_lock ul(status_mutex);
|
||||
CloseStep();
|
||||
snapshot.steps.push_back(RugnuxLiveStep{.title = title, .started_s = Now()});
|
||||
CapImages(snapshot.steps.back().title);
|
||||
snapshot.step_progress.reset();
|
||||
}
|
||||
|
||||
@@ -117,6 +132,8 @@ void RugnuxLiveStatus::Cut(const std::string &reason) {
|
||||
std::unique_lock ul(status_mutex);
|
||||
snapshot.cut = reason;
|
||||
snapshot.cut_s = Now();
|
||||
for (auto &step: snapshot.steps)
|
||||
CapImages(step.title);
|
||||
}
|
||||
|
||||
void RugnuxLiveStatus::Probe(const std::string &text) {
|
||||
|
||||
@@ -111,7 +111,7 @@ struct RugnuxLiveSnapshot {
|
||||
std::string cut;
|
||||
std::optional<double> cut_s;
|
||||
std::optional<double> last_image_to_written_s;
|
||||
std::string verdict; // OK | WARNINGS | UNUSABLE | FAILED
|
||||
std::string verdict; // OK | WARNINGS | UNUSABLE | FAILED | CANCELLED
|
||||
std::string verdict_text;
|
||||
std::vector<std::pair<std::string, std::string>> summary;
|
||||
std::vector<std::string> warnings;
|
||||
@@ -136,6 +136,7 @@ class RugnuxLiveStatus {
|
||||
double Since(std::chrono::steady_clock::time_point t) const;
|
||||
double Now() const;
|
||||
void CloseStep();
|
||||
void CapImages(std::string &title) const;
|
||||
public:
|
||||
// start_received, received: when the message came off the socket; expected_collection_s: none if not known
|
||||
void Start(int64_t images_expected, std::optional<double> expected_collection_s,
|
||||
|
||||
@@ -698,6 +698,14 @@ TEST_CASE("JFJochReceiverRugnux_Rotation_Cancel", "[JFJochReceiverRugnux]") {
|
||||
const auto live = service.GetRugnuxStatus();
|
||||
CHECK(live.cut == "cancel");
|
||||
CHECK(live.state != RugnuxLiveState::Running);
|
||||
// A cancelled collection, not a failed analysis, and counted in the images that came
|
||||
CHECK(live.verdict == "CANCELLED");
|
||||
CHECK(live.verdict_text.find("after 2 images") != std::string::npos);
|
||||
for (const auto &step: live.steps) {
|
||||
const size_t pos = step.title.rfind(", ");
|
||||
if (pos != std::string::npos && step.title.ends_with(" images"))
|
||||
CHECK(std::stoll(step.title.substr(pos + 2)) <= 2);
|
||||
}
|
||||
}
|
||||
SECTION("A silent cancel lets the collection finish") {
|
||||
Stream stream(x, images);
|
||||
|
||||
@@ -322,3 +322,32 @@ TEST_CASE("RugnuxLiveModel_NonFiniteScores", "[RugnuxLive]") {
|
||||
CHECK(!j["model"]["refinement"]["blobs"][0].contains("nearest_atom_distance_A"));
|
||||
CHECK(j.dump().find("null") == std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("RugnuxLiveStatus_StepsAfterCancel", "[RugnuxLive]") {
|
||||
// A step covers the images that came: after a cancel, not the ones it was planned for
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
RugnuxLiveStatus status;
|
||||
status.Start(1000, 10.0, start);
|
||||
status.Step("Integration (geometry pre-pass), 600 images");
|
||||
for (int i = 0; i < 384; i++)
|
||||
status.ImageReceived(start + i * 10ms);
|
||||
status.Step("Pre-scan");
|
||||
status.Cut("cancel");
|
||||
status.Step("Integration, 1000 images");
|
||||
status.Step("Integration, 200 images");
|
||||
|
||||
const auto s = status.Get();
|
||||
REQUIRE(s.steps.size() == 4);
|
||||
CHECK(s.steps[0].title == "Integration (geometry pre-pass), 384 images");
|
||||
CHECK(s.steps[1].title == "Pre-scan");
|
||||
CHECK(s.steps[2].title == "Integration, 384 images");
|
||||
CHECK(s.steps[3].title == "Integration, 200 images");
|
||||
}
|
||||
|
||||
TEST_CASE("RugnuxLiveStatus_StepsAfterDeadline", "[RugnuxLive]") {
|
||||
RugnuxLiveStatus status;
|
||||
status.Start(1000, 10.0, std::chrono::steady_clock::now());
|
||||
status.Step("Integration, 1000 images");
|
||||
status.Cut("deadline");
|
||||
CHECK(status.Get().steps[0].title == "Integration, 1000 images");
|
||||
}
|
||||
Reference in new issue
Block a user