viewer: hit-feed polish — stacked docks, hidden processing, resolution mode
Follow-up to the image strip, addressing the review: - Composition: stack the plots and the thumbnail strip vertically (plots on top with more height, strip below) instead of sharing horizontal space — both benefit from width, and the strip needs less height. - Processing dock is hidden by default and narrower; it reveals itself only when a reprocessing job starts (new jobStarted signal), and the Processing perspective no longer force-shows it. - Thumbnail spot overlays now use the same feature (indexed) / spot colours as the main viewer, and follow the side-panel colour pickers. - New strip selection modes "Resolution" and "Background": pick images that span that metric's distribution (a quick histogram-representative selection). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1035,6 +1035,16 @@ void JFJochImageReadingWorker::SetThumbnailColorMap(int color_map) {
|
||||
thumb_color_scale_.Select(static_cast<ColorScaleEnum>(color_map));
|
||||
}
|
||||
|
||||
void JFJochImageReadingWorker::SetThumbnailFeatureColor(QColor c) {
|
||||
QMutexLocker locker(&m);
|
||||
thumb_feature_color_ = c;
|
||||
}
|
||||
|
||||
void JFJochImageReadingWorker::SetThumbnailSpotColor(QColor c) {
|
||||
QMutexLocker locker(&m);
|
||||
thumb_spot_color_ = c;
|
||||
}
|
||||
|
||||
void JFJochImageReadingWorker::RenderThumbnails(QVector<qint64> image_numbers, bool show_spots) {
|
||||
// File mode only; live HTTP cannot address arbitrary images cheaply.
|
||||
if (http_mode)
|
||||
@@ -1111,7 +1121,7 @@ QImage JFJochImageReadingWorker::RenderThumbnail_i(int64_t image_number, bool sh
|
||||
QPainter p(&out);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
for (const auto &sp : img->ImageData().spots) {
|
||||
p.setPen(QPen(sp.indexed ? QColor(0xFA, 0x72, 0x68) : QColor(0x2A, 0x9D, 0x8F), 1.0));
|
||||
p.setPen(QPen(sp.indexed ? thumb_feature_color_ : thumb_spot_color_, 1.0));
|
||||
p.drawEllipse(QPointF(sp.x * s, sp.y * s), 1.6, 1.6);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <QTimer>
|
||||
#include <QElapsedTimer>
|
||||
#include <QImage>
|
||||
#include <QColor>
|
||||
|
||||
#include "../common/ColorScale.h"
|
||||
|
||||
@@ -92,6 +93,8 @@ private:
|
||||
SpotFindingSettings spot_finding_settings;
|
||||
|
||||
ColorScale thumb_color_scale_; // colour map used for the image-strip thumbnails
|
||||
QColor thumb_feature_color_ = Qt::magenta; // indexed-spot overlay (matches the main viewer)
|
||||
QColor thumb_spot_color_ = Qt::green; // non-indexed spot overlay
|
||||
QImage RenderThumbnail_i(int64_t image_number, bool show_spots);
|
||||
|
||||
std::optional<int64_t> current_image;
|
||||
@@ -224,4 +227,6 @@ public slots:
|
||||
// for each; non-disruptive (does not change the displayed image).
|
||||
void RenderThumbnails(QVector<qint64> image_numbers, bool show_spots);
|
||||
void SetThumbnailColorMap(int color_map);
|
||||
void SetThumbnailFeatureColor(QColor c);
|
||||
void SetThumbnailSpotColor(QColor c);
|
||||
};
|
||||
|
||||
@@ -518,7 +518,8 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString
|
||||
connect(settingsPanel, &JFJochViewerSettingsDock::ringsFromCalibration,
|
||||
side_panel, &JFJochViewerSidePanel::SetRings);
|
||||
|
||||
// Dock the processing panel in the bottom-right corner, next to the dataset-info plots.
|
||||
// Processing panel: hidden by default and narrower; it reveals itself (to the right of the
|
||||
// plots) only when a reprocessing job starts.
|
||||
processingDock = new QDockWidget("Processing", this);
|
||||
processingDock->setObjectName("processingDock");
|
||||
processingDock->setAllowedAreas(Qt::BottomDockWidgetArea);
|
||||
@@ -528,19 +529,26 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString
|
||||
addDockWidget(Qt::BottomDockWidgetArea, processingDock);
|
||||
if (lastDatasetInfoDock) {
|
||||
splitDockWidget(lastDatasetInfoDock, processingDock, Qt::Horizontal);
|
||||
resizeDocks({lastDatasetInfoDock, processingDock}, {650, 350}, Qt::Horizontal);
|
||||
// Give the bottom area generous height: the per-dataset plot is a headline feature, so it
|
||||
// gets real room rather than a thin strip.
|
||||
resizeDocks({lastDatasetInfoDock, processingDock}, {420, 420}, Qt::Vertical);
|
||||
resizeDocks({lastDatasetInfoDock, processingDock}, {900, 280}, Qt::Horizontal);
|
||||
}
|
||||
processingDock->hide();
|
||||
menuBar->AddDockEntry(processingDock, "Processing");
|
||||
connect(processingJobsWindow, &JFJochProcessingJobsWindow::jobStarted, this, [this] {
|
||||
processingDock->show();
|
||||
processingDock->raise();
|
||||
});
|
||||
|
||||
// Image strip / hit feed: thumbnails of representative images, rendered off-thread; click opens.
|
||||
// Stacked below the plots — both want vertical room, and the strip needs less of it.
|
||||
auto *imageStrip = new JFJochViewerImageStrip(this);
|
||||
imageStripDock = new QDockWidget("Image strip", this);
|
||||
imageStripDock->setObjectName("imageStripDock");
|
||||
imageStripDock->setWidget(imageStrip);
|
||||
addDockWidget(Qt::BottomDockWidgetArea, imageStripDock);
|
||||
if (lastDatasetInfoDock) {
|
||||
splitDockWidget(lastDatasetInfoDock, imageStripDock, Qt::Vertical);
|
||||
resizeDocks({lastDatasetInfoDock, imageStripDock}, {340, 180}, Qt::Vertical);
|
||||
}
|
||||
menuBar->AddDockEntry(imageStripDock, "Image strip");
|
||||
connect(reading_worker, &JFJochImageReadingWorker::datasetLoaded,
|
||||
imageStrip, &JFJochViewerImageStrip::datasetLoaded);
|
||||
@@ -552,6 +560,10 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString
|
||||
reading_worker, &JFJochImageReadingWorker::LoadImage);
|
||||
connect(toolBarDisplay, &JFJochViewerToolbarDisplay::colorMapChanged,
|
||||
reading_worker, &JFJochImageReadingWorker::SetThumbnailColorMap);
|
||||
connect(side_panel, &JFJochViewerSidePanel::setFeatureColor,
|
||||
reading_worker, &JFJochImageReadingWorker::SetThumbnailFeatureColor);
|
||||
connect(side_panel, &JFJochViewerSidePanel::setSpotColor,
|
||||
reading_worker, &JFJochImageReadingWorker::SetThumbnailSpotColor);
|
||||
|
||||
connect(menuBar, &JFJochViewerMenu::imageLayoutSelected, this,
|
||||
[this] { ApplyPerspective(Perspective::Image); });
|
||||
@@ -583,8 +595,8 @@ void JFJochViewerWindow::ApplyPerspective(Perspective p) {
|
||||
const bool processing = (p == Perspective::Processing);
|
||||
if (inspectorDock) inspectorDock->setVisible(true);
|
||||
if (settingsDock) settingsDock->setVisible(processing);
|
||||
if (processingDock) processingDock->setVisible(processing);
|
||||
if (imageStripDock) imageStripDock->setVisible(processing);
|
||||
if (processingDock && !processing) processingDock->hide(); // shows only when a job starts
|
||||
for (auto *d : findChildren<QDockWidget *>())
|
||||
if (d->objectName().startsWith("datasetInfoDock"))
|
||||
d->setVisible(processing);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <cmath>
|
||||
|
||||
JFJochViewerImageStrip::JFJochViewerImageStrip(QWidget *parent) : QWidget(parent) {
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
@@ -25,6 +26,8 @@ JFJochViewerImageStrip::JFJochViewerImageStrip(QWidget *parent) : QWidget(parent
|
||||
mode_->addItem("Evenly spaced", 0);
|
||||
mode_->addItem("Most spots", 1);
|
||||
mode_->addItem("Indexed", 2);
|
||||
mode_->addItem("Resolution", 3); // representatives across the resolution range
|
||||
mode_->addItem("Background", 4); // representatives across the background range
|
||||
controls->addWidget(mode_);
|
||||
spots_ = new QCheckBox("Spots", this);
|
||||
spots_->setChecked(true);
|
||||
@@ -76,6 +79,20 @@ QVector<qint64> JFJochViewerImageStrip::ComputeRepresentatives() const {
|
||||
result.push_back(N == 1 ? 0 : static_cast<int64_t>(i) * (total - 1) / (N - 1));
|
||||
};
|
||||
|
||||
// Pick N images that span a metric's distribution (sort by value, sample evenly across it) —
|
||||
// a quick "histogram representative" selection.
|
||||
auto spread = [&](const std::vector<float> &metric) {
|
||||
std::vector<std::pair<float, int64_t>> vals;
|
||||
for (size_t i = 0; i < metric.size(); ++i)
|
||||
if (std::isfinite(metric[i]))
|
||||
vals.push_back({metric[i], static_cast<int64_t>(i)});
|
||||
if (vals.empty()) { evenly(); return; }
|
||||
std::sort(vals.begin(), vals.end());
|
||||
const int n = static_cast<int>(std::min<size_t>(N, vals.size()));
|
||||
for (int i = 0; i < n; ++i)
|
||||
result.push_back(vals[n == 1 ? 0 : static_cast<size_t>(i) * (vals.size() - 1) / (n - 1)].second);
|
||||
};
|
||||
|
||||
if (mode == 1 && !sc.empty()) { // most spots
|
||||
std::vector<int64_t> idx(sc.size());
|
||||
std::iota(idx.begin(), idx.end(), 0);
|
||||
@@ -96,6 +113,10 @@ QVector<qint64> JFJochViewerImageStrip::ComputeRepresentatives() const {
|
||||
for (int i = 0; i < n; ++i)
|
||||
result.push_back(indexed[n == 1 ? 0 : static_cast<size_t>(i) * (indexed.size() - 1) / (n - 1)]);
|
||||
}
|
||||
} else if (mode == 3 && !dataset_->resolution_estimate.empty()) { // resolution spread
|
||||
spread(dataset_->resolution_estimate);
|
||||
} else if (mode == 4 && !dataset_->bkg_estimate.empty()) { // background spread
|
||||
spread(dataset_->bkg_estimate);
|
||||
} else {
|
||||
evenly();
|
||||
}
|
||||
|
||||
@@ -295,6 +295,7 @@ void JFJochProcessingJobsWindow::newJob() {
|
||||
|
||||
job_timer_.start();
|
||||
controller_->start(inputs.file, experiment, inputs.pixel_mask, config);
|
||||
emit jobStarted();
|
||||
emit writeStatusBar("Started processing job " + label);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ signals:
|
||||
void writeStatusBar(QString message, int timeout_ms = 0);
|
||||
// Live per-image results while a job runs, for the dataset-info plots (nullptr clears it).
|
||||
void liveDataset(std::shared_ptr<const JFJochReaderDataset> dataset);
|
||||
void jobStarted(); // a reprocessing job was launched — reveal the (hidden-by-default) panel
|
||||
|
||||
public slots:
|
||||
void onHttpConnectionChanged(bool connected, QString addr);
|
||||
|
||||
Reference in New Issue
Block a user