diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index 7e5d219a..6d8daabb 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -496,10 +496,15 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString // into the running analysis so edits re-run on the current image / dataset. auto *settingsPanel = new JFJochViewerSettingsDock(spot_finding_settings, indexing_settings, experiment.GetAzimuthalIntegrationSettings(), this); + auto *settingsScroll = new QScrollArea(this); + settingsScroll->setWidget(settingsPanel); + settingsScroll->setWidgetResizable(true); + settingsScroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + settingsScroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); settingsDock = new QDockWidget("Settings", this); settingsDock->setObjectName("settingsDock"); settingsDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); - settingsDock->setWidget(settingsPanel); + settingsDock->setWidget(settingsScroll); // scrollable: don't force the window taller than the screen addDockWidget(Qt::LeftDockWidgetArea, settingsDock); menuBar->AddDockEntry(settingsDock, "Settings"); @@ -547,11 +552,13 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString addDockWidget(Qt::BottomDockWidgetArea, imageStripDock); if (lastDatasetInfoDock) { splitDockWidget(lastDatasetInfoDock, imageStripDock, Qt::Vertical); - resizeDocks({lastDatasetInfoDock, imageStripDock}, {340, 180}, Qt::Vertical); + resizeDocks({lastDatasetInfoDock, imageStripDock}, {260, 140}, Qt::Vertical); } menuBar->AddDockEntry(imageStripDock, "Image strip"); connect(reading_worker, &JFJochImageReadingWorker::datasetLoaded, imageStrip, &JFJochViewerImageStrip::datasetLoaded); + connect(reading_worker, &JFJochImageReadingWorker::fileOpened, + imageStrip, &JFJochViewerImageStrip::resetForNewFile); connect(imageStrip, &JFJochViewerImageStrip::requestThumbnails, reading_worker, &JFJochImageReadingWorker::RenderThumbnails); connect(reading_worker, &JFJochImageReadingWorker::thumbnailReady, diff --git a/viewer/charts/JFJochSimpleChartView.cpp b/viewer/charts/JFJochSimpleChartView.cpp index d964d821..d8622dd1 100644 --- a/viewer/charts/JFJochSimpleChartView.cpp +++ b/viewer/charts/JFJochSimpleChartView.cpp @@ -12,7 +12,7 @@ JFJochSimpleChartView::JFJochSimpleChartView(QWidget *parent) : QChartView(new QChart(), parent) { chart()->legend()->hide(); - setFixedHeight(300); + setMinimumHeight(120); // soft floor: let the host dock/window decide the height setRenderHint(QPainter::Antialiasing); setMouseTracking(true); //setRubberBand(QChartView::RubberBand::HorizontalRubberBand); diff --git a/viewer/widgets/JFJochViewerImageStrip.cpp b/viewer/widgets/JFJochViewerImageStrip.cpp index 607aaa34..785daf13 100644 --- a/viewer/widgets/JFJochViewerImageStrip.cpp +++ b/viewer/widgets/JFJochViewerImageStrip.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include @@ -33,8 +35,15 @@ JFJochViewerImageStrip::JFJochViewerImageStrip(QWidget *parent) : QWidget(parent spots_->setChecked(true); spots_->setToolTip("Overlay found spots — a real pattern reads like a constellation"); controls->addWidget(spots_); + auto *refresh = new QToolButton(this); + refresh->setIcon(style()->standardIcon(QStyle::SP_BrowserReload)); + refresh->setAutoRaise(true); + refresh->setCursor(Qt::PointingHandCursor); + refresh->setToolTip("Re-roll the representative selection"); + controls->addWidget(refresh); controls->addStretch(); layout->addLayout(controls); + connect(refresh, &QToolButton::clicked, this, [this] { Rebuild(); }); auto *scroll = new QScrollArea(this); scroll->setWidgetResizable(true); @@ -52,8 +61,14 @@ JFJochViewerImageStrip::JFJochViewerImageStrip(QWidget *parent) : QWidget(parent } void JFJochViewerImageStrip::datasetLoaded(std::shared_ptr dataset) { + // Store the latest dataset (metrics may update during live sync / re-analysis) but do NOT + // rebuild here — that would refetch thumbnails on every HTTP image update. Rebuilding happens + // on file open (resetForNewFile, which fires just after this) and on mode / Spots / Refresh. dataset_ = std::move(dataset); - Rebuild(); +} + +void JFJochViewerImageStrip::resetForNewFile() { + Rebuild(); // a new file/stream opened; dataset_ was just set by datasetLoaded } void JFJochViewerImageStrip::thumbnailReady(qint64 image_number, QImage thumb) { @@ -71,29 +86,43 @@ QVector JFJochViewerImageStrip::ComputeRepresentatives() const { return result; const int N = static_cast(std::min(8, total)); const int mode = mode_->currentIndex(); - const auto &sc = dataset_->spot_count; - const auto &sci = dataset_->spot_count_indexed; + auto *rng = QRandomGenerator::global(); - auto evenly = [&] { - for (int i = 0; i < N; ++i) - result.push_back(N == 1 ? 0 : static_cast(i) * (total - 1) / (N - 1)); + // Pick one random image from each of N equal bins over an ordered candidate list. Stochastic, + // so Refresh re-rolls a fresh set and deterministic-spacing artefacts are avoided. + auto binnedRandom = [&](const std::vector &candidates) { + const size_t M = candidates.size(); + if (M == 0) + return; + const int n = static_cast(std::min(N, M)); + for (int i = 0; i < n; ++i) { + const size_t lo = static_cast(i) * M / n; + size_t hi = static_cast(i + 1) * M / n; + if (hi <= lo) hi = lo + 1; + result.push_back(candidates[lo + rng->bounded(static_cast(hi - lo))]); + } }; - // 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) { + // Image indices ordered by a metric (finite values only), low to high. + auto orderedByMetric = [](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); + std::vector idx; + idx.reserve(vals.size()); + for (const auto &v : vals) idx.push_back(v.second); + return idx; }; - if (mode == 1 && !sc.empty()) { // most spots + std::vector allIndices(total); + std::iota(allIndices.begin(), allIndices.end(), 0); + + const auto &sc = dataset_->spot_count; + const auto &sci = dataset_->spot_count_indexed; + + if (mode == 1 && !sc.empty()) { // most spots (deterministic top-N) std::vector idx(sc.size()); std::iota(idx.begin(), idx.end(), 0); const size_t n = std::min(N, idx.size()); @@ -102,23 +131,17 @@ QVector JFJochViewerImageStrip::ComputeRepresentatives() const { idx.resize(n); std::sort(idx.begin(), idx.end()); for (int64_t i : idx) result.push_back(i); - } else if (mode == 2 && !sci.empty()) { // indexed images, evenly sampled + } else if (mode == 2 && !sci.empty()) { // indexed images, spaced random std::vector indexed; for (size_t i = 0; i < sci.size(); ++i) if (sci[i] > 0) indexed.push_back(static_cast(i)); - if (indexed.empty()) { - evenly(); - } else { - const int n = static_cast(std::min(N, indexed.size())); - 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(); + binnedRandom(indexed.empty() ? allIndices : indexed); + } else if (mode == 3 && !dataset_->resolution_estimate.empty()) { + binnedRandom(orderedByMetric(dataset_->resolution_estimate)); + } else if (mode == 4 && !dataset_->bkg_estimate.empty()) { + binnedRandom(orderedByMetric(dataset_->bkg_estimate)); + } else { // evenly spaced (random within each bin) + binnedRandom(allIndices); } return result; } @@ -140,7 +163,7 @@ void JFJochViewerImageStrip::Rebuild() { for (qint64 n : reps) { auto *btn = new QToolButton(this); btn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); - btn->setIconSize(QSize(120, 120)); + btn->setIconSize(QSize(96, 96)); btn->setText(QString::number(n + 1)); btn->setAutoRaise(true); btn->setCursor(Qt::PointingHandCursor); diff --git a/viewer/widgets/JFJochViewerImageStrip.h b/viewer/widgets/JFJochViewerImageStrip.h index fae90657..2a8df93d 100644 --- a/viewer/widgets/JFJochViewerImageStrip.h +++ b/viewer/widgets/JFJochViewerImageStrip.h @@ -25,6 +25,7 @@ public: public slots: void datasetLoaded(std::shared_ptr dataset); void thumbnailReady(qint64 image_number, QImage thumb); + void resetForNewFile(); // a new file/stream opened: rebuild once, then stay put on live updates signals: void requestThumbnails(QVector image_numbers, bool show_spots);