diff --git a/viewer/JFJochImageReadingWorker.cpp b/viewer/JFJochImageReadingWorker.cpp index a22862d5..6a7e2f70 100644 --- a/viewer/JFJochImageReadingWorker.cpp +++ b/viewer/JFJochImageReadingWorker.cpp @@ -1035,6 +1035,16 @@ void JFJochImageReadingWorker::SetThumbnailColorMap(int color_map) { thumb_color_scale_.Select(static_cast(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 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); } } diff --git a/viewer/JFJochImageReadingWorker.h b/viewer/JFJochImageReadingWorker.h index 352739ca..94a982e7 100644 --- a/viewer/JFJochImageReadingWorker.h +++ b/viewer/JFJochImageReadingWorker.h @@ -11,6 +11,7 @@ #include #include #include +#include #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 current_image; @@ -224,4 +227,6 @@ public slots: // for each; non-disruptive (does not change the displayed image). void RenderThumbnails(QVector image_numbers, bool show_spots); void SetThumbnailColorMap(int color_map); + void SetThumbnailFeatureColor(QColor c); + void SetThumbnailSpotColor(QColor c); }; diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index 8983d93e..7e5d219a 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -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()) if (d->objectName().startsWith("datasetInfoDock")) d->setVisible(processing); diff --git a/viewer/widgets/JFJochViewerImageStrip.cpp b/viewer/widgets/JFJochViewerImageStrip.cpp index 8fc38461..607aaa34 100644 --- a/viewer/widgets/JFJochViewerImageStrip.cpp +++ b/viewer/widgets/JFJochViewerImageStrip.cpp @@ -14,6 +14,7 @@ #include #include +#include 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 JFJochViewerImageStrip::ComputeRepresentatives() const { result.push_back(N == 1 ? 0 : static_cast(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 &metric) { + std::vector> vals; + for (size_t i = 0; i < metric.size(); ++i) + if (std::isfinite(metric[i])) + vals.push_back({metric[i], static_cast(i)}); + if (vals.empty()) { evenly(); return; } + std::sort(vals.begin(), vals.end()); + const int n = static_cast(std::min(N, vals.size())); + for (int i = 0; i < n; ++i) + result.push_back(vals[n == 1 ? 0 : static_cast(i) * (vals.size() - 1) / (n - 1)].second); + }; + if (mode == 1 && !sc.empty()) { // most spots std::vector idx(sc.size()); std::iota(idx.begin(), idx.end(), 0); @@ -96,6 +113,10 @@ QVector JFJochViewerImageStrip::ComputeRepresentatives() const { for (int i = 0; i < n; ++i) result.push_back(indexed[n == 1 ? 0 : static_cast(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(); } diff --git a/viewer/windows/JFJochProcessingJobsWindow.cpp b/viewer/windows/JFJochProcessingJobsWindow.cpp index 0fc02790..6f5cf5be 100644 --- a/viewer/windows/JFJochProcessingJobsWindow.cpp +++ b/viewer/windows/JFJochProcessingJobsWindow.cpp @@ -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); } diff --git a/viewer/windows/JFJochProcessingJobsWindow.h b/viewer/windows/JFJochProcessingJobsWindow.h index 653dd3a8..dc65f74c 100644 --- a/viewer/windows/JFJochProcessingJobsWindow.h +++ b/viewer/windows/JFJochProcessingJobsWindow.h @@ -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 dataset); + void jobStarted(); // a reprocessing job was launched — reveal the (hidden-by-default) panel public slots: void onHttpConnectionChanged(bool connected, QString addr);