aceff23ce2
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>
269 lines
9.9 KiB
C++
269 lines
9.9 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include <QGridLayout>
|
|
#include <QPushButton>
|
|
|
|
#include "JFJochViewerDatasetInfo.h"
|
|
|
|
JFJochViewerDatasetInfo::JFJochViewerDatasetInfo(QWidget *parent) : QWidget(parent) {
|
|
auto layout = new QGridLayout(this);
|
|
combo_box = new QComboBox(this);
|
|
|
|
last_selection = 0;
|
|
|
|
layout->addWidget(combo_box, 0, 0);
|
|
|
|
auto reset_button = new QPushButton("Reset zoom", this);
|
|
reset_button->setFixedWidth(100);
|
|
|
|
grid_button = new QPushButton("Grid", this);
|
|
grid_button->setFixedWidth(100);
|
|
grid_button->setCheckable(true);
|
|
grid_button->setEnabled(false);
|
|
|
|
layout->addWidget(reset_button, 0, 2);
|
|
layout->addWidget(grid_button, 0, 3);
|
|
|
|
stack = new QStackedWidget(this);
|
|
chart_view = new JFJochDatasetInfoChartView(this);
|
|
chart_view->setMinimumHeight(200); // keep the plot readable even when the dock is small
|
|
grid_scan_image = new JFJochGridScanImage(this);
|
|
|
|
stack->addWidget(chart_view);
|
|
stack->addWidget(grid_scan_image);
|
|
|
|
layout->addWidget(stack, 1, 0, 1, 4);
|
|
|
|
connect(chart_view, &JFJochDatasetInfoChartView::imageSelected,
|
|
this, &JFJochViewerDatasetInfo::imageSelectedInChart);
|
|
|
|
connect(reset_button, &QPushButton::clicked,
|
|
this, &JFJochViewerDatasetInfo::resetZoomButtonPressed);
|
|
|
|
connect(combo_box, &QComboBox::currentIndexChanged,
|
|
this, &JFJochViewerDatasetInfo::comboBoxSelected);
|
|
|
|
connect(grid_scan_image, &JFJochGridScanImage::imageSelected,
|
|
this, &JFJochViewerDatasetInfo::imageSelectedInChart);
|
|
|
|
connect(chart_view, &JFJochDatasetInfoChartView::writeStatusBar,
|
|
[this](QString string, int timeout_ms) {
|
|
emit writeStatusBar(string, timeout_ms);
|
|
});
|
|
|
|
connect(grid_scan_image, &JFJochGridScanImage::writeStatusBar,
|
|
[this](QString string, int timeout_ms) {
|
|
emit writeStatusBar(string, timeout_ms);
|
|
});
|
|
|
|
connect(grid_button, &QPushButton::clicked,
|
|
[this]() {
|
|
if (grid_button->isChecked() && dataset && dataset->experiment.GetGridScan())
|
|
stack->setCurrentWidget(grid_scan_image);
|
|
else
|
|
stack->setCurrentWidget(chart_view);
|
|
});
|
|
setLayout(layout);
|
|
}
|
|
|
|
void JFJochViewerDatasetInfo::UpdateLabels() {
|
|
QSignalBlocker bl(combo_box);
|
|
|
|
if (combo_box->count() > 0)
|
|
last_selection = combo_box->currentIndex();
|
|
|
|
combo_box->clear();
|
|
|
|
if (this->dataset) {
|
|
combo_box->addItem("Background estimate", 0);
|
|
combo_box->addItem("Resolution estimate", 7);
|
|
combo_box->addItem("Spot count", 1);
|
|
combo_box->addItem("Spot count (indexed)", 2);
|
|
combo_box->addItem("Spot count (ice rings)", 3);
|
|
combo_box->addItem("Spot count (low res.)", 4);
|
|
|
|
// Offer indexing/scaling metrics if the active run, any overlay run, or the live run has them.
|
|
bool has_indexing = !dataset->indexing_result.empty();
|
|
bool has_scale = !dataset->image_scale_factor.empty();
|
|
for (const auto &r: runs_)
|
|
if (r.dataset) {
|
|
has_indexing = has_indexing || !r.dataset->indexing_result.empty();
|
|
has_scale = has_scale || !r.dataset->image_scale_factor.empty();
|
|
}
|
|
if (live_run_) {
|
|
has_indexing = has_indexing || !live_run_->indexing_result.empty();
|
|
has_scale = has_scale || !live_run_->image_scale_factor.empty();
|
|
}
|
|
|
|
if (has_indexing) {
|
|
combo_box->insertSeparator(1000);
|
|
combo_box->addItem("Indexing result", 5);
|
|
combo_box->addItem("Profile radius", 6);
|
|
combo_box->addItem("B-factor", 8);
|
|
combo_box->addItem("Mosaicity", 9);
|
|
combo_box->addItem("Integrated reflections", 12);
|
|
combo_box->addItem("Indexing lattice count", 13);
|
|
}
|
|
|
|
if (has_scale) {
|
|
combo_box->insertSeparator(1000);
|
|
combo_box->addItem("Scale factor", 10);
|
|
combo_box->addItem("CC", 11);
|
|
}
|
|
for (int i = 0; i < this->dataset->roi.size(); i++) {
|
|
std::string name = std::string("ROI ") + this->dataset->roi[i];
|
|
combo_box->insertSeparator(1000);
|
|
combo_box->addItem(QString::fromStdString(name + " mean"), 100 + i * 4);
|
|
combo_box->addItem(QString::fromStdString(name + " sum"), 100 + i * 4 + 1);
|
|
combo_box->addItem(QString::fromStdString(name + " weighted x"), 100 + i * 4 + 2);
|
|
combo_box->addItem(QString::fromStdString(name + " weighted y"), 100 + i * 4 + 3);
|
|
}
|
|
} else {
|
|
combo_box->clear();
|
|
}
|
|
}
|
|
|
|
|
|
void JFJochViewerDatasetInfo::datasetLoaded(std::shared_ptr<const JFJochReaderDataset> dataset) {
|
|
this->dataset = dataset;
|
|
if (dataset) {
|
|
UpdateLabels();
|
|
if (last_selection < combo_box->count())
|
|
combo_box->setCurrentIndex(last_selection);
|
|
else
|
|
combo_box->setCurrentIndex(0);
|
|
UpdatePlot();
|
|
} else {
|
|
chart_view->loadValues({}, 0, false);
|
|
grid_scan_image->clear();
|
|
UpdateLabels();
|
|
}
|
|
}
|
|
|
|
void JFJochViewerDatasetInfo::imageLoaded(std::shared_ptr<const JFJochReaderImage> image) {
|
|
this->image = image;
|
|
if (image) {
|
|
chart_view->setImage(image->ImageData().number);
|
|
grid_scan_image->setImage(image->ImageData().number);
|
|
}
|
|
}
|
|
|
|
void JFJochViewerDatasetInfo::imageSelectedInChart(int64_t number) {
|
|
emit imageSelected(number, 1);
|
|
}
|
|
|
|
std::vector<float> JFJochViewerDatasetInfo::ExtractMetric(const JFJochReaderDataset &ds, int val,
|
|
bool &one_over_d2) const {
|
|
one_over_d2 = false;
|
|
std::vector<float> data;
|
|
if (val == 0) data = ds.bkg_estimate;
|
|
else if (val == 1) data = ds.spot_count;
|
|
else if (val == 2) data = ds.spot_count_indexed;
|
|
else if (val == 3) data = ds.spot_count_ice_rings;
|
|
else if (val == 4) data = ds.spot_count_low_res;
|
|
else if (val == 5) data = ds.indexing_result;
|
|
else if (val == 6) data = ds.profile_radius;
|
|
else if (val == 7) { data = ds.resolution_estimate; one_over_d2 = true; }
|
|
else if (val == 8) data = ds.b_factor;
|
|
else if (val == 9) data = ds.mosaicity_deg;
|
|
else if (val == 10) data = ds.image_scale_factor;
|
|
else if (val == 11) data = ds.image_scale_cc;
|
|
else if (val == 12) data = ds.integrated_reflections;
|
|
else if (val == 13) data = ds.indexing_lattice_count;
|
|
else if (val >= 100) {
|
|
const int roi_index = (val - 100) / 4;
|
|
if (val % 4 == 0) {
|
|
if (roi_index < ds.roi_sum.size() && ds.roi_sum.size() == ds.roi_npixel.size())
|
|
for (int i = 0; i < ds.roi_sum[roi_index].size(); i++)
|
|
data.push_back(static_cast<float>(ds.roi_sum[roi_index][i])
|
|
/ static_cast<float>(ds.roi_npixel[roi_index][i]));
|
|
} else if (val % 4 == 1) {
|
|
if (roi_index < ds.roi_sum.size()) {
|
|
data.reserve(ds.roi_sum[roi_index].size());
|
|
for (auto &v: ds.roi_sum[roi_index])
|
|
data.push_back(v);
|
|
}
|
|
} else if (val % 4 == 2) {
|
|
if (roi_index < ds.roi_x.size()) data = ds.roi_x[roi_index];
|
|
} else if (val % 4 == 3) {
|
|
if (roi_index < ds.roi_y.size()) data = ds.roi_y[roi_index];
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
void JFJochViewerDatasetInfo::UpdatePlot() {
|
|
int index = combo_box->currentIndex();
|
|
if (combo_box->count() == 0 || index < 0)
|
|
return;
|
|
|
|
int val = combo_box->itemData(index).toInt();
|
|
|
|
if (!dataset) {
|
|
grid_button->setEnabled(false);
|
|
return;
|
|
}
|
|
|
|
const int64_t image_number = image ? image->ImageData().number : 0;
|
|
|
|
bool one_over_d2 = false;
|
|
std::vector<float> data = ExtractMetric(*dataset, val, one_over_d2);
|
|
|
|
// One overlay line per other run, plus the in-progress live run.
|
|
std::vector<JFJochDatasetInfoChartView::NamedSeries> overlays;
|
|
QString primary_name;
|
|
for (const auto &run: runs_) {
|
|
if (run.id == active_id_) { primary_name = run.label; continue; }
|
|
if (!run.dataset || run.dataset == dataset) continue; // never draw the active run twice
|
|
bool ignore = false;
|
|
overlays.push_back({run.label, ExtractMetric(*run.dataset, val, ignore)});
|
|
}
|
|
if (live_run_ && live_run_ != dataset) {
|
|
bool ignore = false;
|
|
overlays.push_back({QStringLiteral("Live"), ExtractMetric(*live_run_, val, ignore)});
|
|
}
|
|
|
|
chart_view->loadValues(data, image_number, one_over_d2, dataset.get(), primary_name, std::move(overlays));
|
|
|
|
if (dataset->experiment.GetGridScan()) {
|
|
stack->setCurrentWidget(grid_scan_image);
|
|
grid_scan_image->loadData(data, dataset->experiment.GetGridScan().value(), one_over_d2);
|
|
grid_button->setEnabled(true);
|
|
grid_button->setChecked(true);
|
|
} else {
|
|
grid_scan_image->clear();
|
|
stack->setCurrentWidget(chart_view);
|
|
grid_button->setEnabled(false);
|
|
}
|
|
}
|
|
|
|
void JFJochViewerDatasetInfo::runsChanged(QVector<RunData> runs, QString active_id) {
|
|
runs_ = std::move(runs);
|
|
active_id_ = std::move(active_id);
|
|
UpdateLabels();
|
|
if (last_selection < combo_box->count())
|
|
combo_box->setCurrentIndex(last_selection);
|
|
UpdatePlot();
|
|
}
|
|
|
|
void JFJochViewerDatasetInfo::liveRunUpdated(std::shared_ptr<const JFJochReaderDataset> in_dataset) {
|
|
live_run_ = std::move(in_dataset);
|
|
UpdatePlot(); // lightweight refresh; no combo rebuild on every live tick
|
|
}
|
|
|
|
void JFJochViewerDatasetInfo::comboBoxSelected(int index) {
|
|
UpdatePlot();
|
|
}
|
|
|
|
void JFJochViewerDatasetInfo::setColorMap(int color_map) {
|
|
grid_scan_image->setColorMap(color_map);
|
|
}
|
|
|
|
void JFJochViewerDatasetInfo::resetZoomButtonPressed() {
|
|
if (stack->currentWidget() == grid_scan_image)
|
|
grid_scan_image->fitToView();
|
|
else
|
|
chart_view->resetZoom();
|
|
}
|