viewer: overlay one plot line per processing run (with editable names)

The dataset-info plot now overlays every run as a separate named line instead of
replacing the plot when a snapshot is activated:

- Reader gains AllSnapshotDatasets() (every snapshot's dataset, Original first).
- The worker owns the run collection: a stable snapshot id plus an editable
  display label (RunData), emitted as runsChanged(runs, active_id) on file open,
  snapshot register, activate and rename. RenameRun(id, label) updates the legend
  label without touching the reader key (decoupled id vs label).
- The chart view draws the active run as the primary series (markers, hover,
  binning, axes) and the other runs as overlay lines sharing its axes, with a
  legend shown when overlaying (appendSeries factored out for both).
- The dataset-info widget holds the run list + the live run, extracts the selected
  metric from each (ExtractMetric), and unions the available metrics across runs.
- The live run is its own "Live" overlay (lightweight per-tick refresh, no combo
  rebuild); it is cleared on finish so the persisted snapshot takes over.
- The processing jobs table gets a "Started" column and an editable Name column;
  editing a name renames that run's legend label.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 11:28:47 +02:00
co-authored by Claude Opus 4.8
parent b735aec1c4
commit aceff23ce2
14 changed files with 358 additions and 163 deletions
+33 -4
View File
@@ -97,6 +97,8 @@ JFJochImageReadingWorker::JFJochImageReadingWorker(const SpotFindingSettings &se
qRegisterMetaType<ROIDefinition>("ROIDefinition");
qRegisterMetaType<BraggIntegrationSettings>("BraggIntegrationSettings");
qRegisterMetaType<ScalingSettings>("ScalingSettings");
qRegisterMetaType<RunData>("RunData");
qRegisterMetaType<QVector<RunData>>("QVector<RunData>");
spot_finding_settings = settings;
indexing = std::make_unique<IndexerThreadPool>(indexing_settings);
@@ -287,6 +289,12 @@ void JFJochImageReadingWorker::LoadFile_i(const QString &filename, qint64 image_
emit datasetLoaded(dataset);
// Reset the run collection for the freshly opened file (file mode only has snapshots).
run_labels_.clear();
if (!http_mode)
run_labels_["Original"] = "Original";
EmitRuns_i();
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
logger.Info(fmt::format("Loaded file {} in {} ms", filename.toStdString(), duration));
@@ -948,6 +956,7 @@ void JFJochImageReadingWorker::ActivateSnapshot_i(const QString &name) {
for (const auto &n: file_reader.SnapshotNames())
names << QString::fromStdString(n);
emit snapshotsChanged(names, QString::fromStdString(file_reader.ActiveSnapshot()));
EmitRuns_i();
if (current_image.has_value()) {
// A snapshot carries its own per-image results; just re-read the image against the new
@@ -959,17 +968,37 @@ void JFJochImageReadingWorker::ActivateSnapshot_i(const QString &name) {
}
}
void JFJochImageReadingWorker::RegisterProcessingSnapshot(QString name, QString master_path) {
void JFJochImageReadingWorker::EmitRuns_i() {
// Assumes m locked! Build the run list (every snapshot dataset + its display label).
QVector<RunData> runs;
if (!http_mode) {
for (const auto &[id, dataset]: file_reader.AllSnapshotDatasets()) {
const auto it = run_labels_.find(id);
const QString label = (it != run_labels_.end()) ? it->second : QString::fromStdString(id);
runs.push_back(RunData{QString::fromStdString(id), label, dataset});
}
}
emit runsChanged(runs, http_mode ? QString() : QString::fromStdString(file_reader.ActiveSnapshot()));
}
void JFJochImageReadingWorker::RenameRun(QString id, QString label) {
QMutexLocker ul(&m);
run_labels_[id.toStdString()] = label;
EmitRuns_i();
}
void JFJochImageReadingWorker::RegisterProcessingSnapshot(QString id, QString label, QString master_path) {
QMutexLocker ul(&m);
if (http_mode) {
logger.Error("Processing snapshots are only available for files");
return;
}
try {
file_reader.RegisterSnapshot(name.toStdString(), master_path.toStdString());
ActivateSnapshot_i(name);
file_reader.RegisterSnapshot(id.toStdString(), master_path.toStdString());
run_labels_[id.toStdString()] = label;
ActivateSnapshot_i(id); // activates + emits datasetLoaded / snapshotsChanged / runsChanged
} catch (const std::exception &e) {
logger.Error("Failed to register snapshot {}: {}", name.toStdString(), e.what());
logger.Error("Failed to register snapshot {}: {}", id.toStdString(), e.what());
emit fileLoadError("Snapshot error", QString::fromStdString(e.what()));
}
}