From 0dfaf0a58a3a33156a94e435fa7957f29fb2efbf Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sat, 11 Jul 2026 22:56:28 +0200 Subject: [PATCH] rugnux: don't reuse absent spots on plain DECTRIS datasets reuse_rotation_spots defaults on, so the two-pass first phase read stored spots from /entry/MX. A standard DECTRIS dataset has no spot-finding results, so every frame came back with zero spots and indexing failed ("Two-pass rotation indexing failed") instead of falling back to finding spots. Add HasStoredSpots() to the reader (HDF5MetadataSource::HasSpots): spots exist if /entry/MX/nPeaks is present in the master (integrated _process.h5) or the per-image data file (VDS/legacy) - the same master-first-then-source path ReadSpots uses. Rugnux reuses stored spots only when they exist, otherwise it runs spot finding (and logs it); --redo-rotation-spots already forced finding. Verified: a true no-/entry/MX rotation dataset fails on the old code and indexes (P21) on the new; all jungfraujoch files (spots in master or in data files) still reuse unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- reader/HDF5MetadataSource.cpp | 13 +++++++++++++ reader/HDF5MetadataSource.h | 2 ++ reader/JFJochHDF5Reader.cpp | 5 +++++ reader/JFJochHDF5Reader.h | 3 +++ rugnux/Rugnux.cpp | 9 ++++++++- 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/reader/HDF5MetadataSource.cpp b/reader/HDF5MetadataSource.cpp index 2be83fc4..61076f08 100644 --- a/reader/HDF5MetadataSource.cpp +++ b/reader/HDF5MetadataSource.cpp @@ -1180,3 +1180,16 @@ std::vector HDF5MetadataSource::ReadSpots(int64_t requested_image) c return tmp_message.spots; } + +bool HDF5MetadataSource::HasSpots() const { + // Stored spots (jungfraujoch spot finding) live under /entry/MX; a plain DECTRIS file has none, + // so ReadSpots would silently return nothing and the caller must find them itself. ReadSpots + // reads /entry/MX/nPeaks master-first-then-source, so check both: the integrated _process.h5 + // keeps it in the master, while a VDS/legacy dataset keeps the per-image arrays in the data file. + if (!master_file || number_of_images == 0) + return false; + if (master_file->Exists("/entry/MX/nPeaks")) + return true; + const auto loc = ResolveMeta(0); + return loc.file && loc.file->Exists("/entry/MX/nPeaks"); +} diff --git a/reader/HDF5MetadataSource.h b/reader/HDF5MetadataSource.h index 7731e4e5..3db7ed28 100644 --- a/reader/HDF5MetadataSource.h +++ b/reader/HDF5MetadataSource.h @@ -52,6 +52,8 @@ public: const std::shared_ptr &dataset) const; std::vector ReadSpots(int64_t image) const; + // True if the file stores spot-finding results (/entry/MX); a plain DECTRIS file has none. + [[nodiscard]] bool HasSpots() const; std::vector ReadReflections(size_t start_image, std::optional end_image) const; CompressedImage ReadCalibration(std::vector &tmp, const std::string &name) const; diff --git a/reader/JFJochHDF5Reader.cpp b/reader/JFJochHDF5Reader.cpp index 511f9f87..ba8c879a 100644 --- a/reader/JFJochHDF5Reader.cpp +++ b/reader/JFJochHDF5Reader.cpp @@ -123,6 +123,11 @@ std::vector JFJochHDF5Reader::ReadSpots(int64_t image) const { return active_metadata_->ReadSpots(image); } +bool JFJochHDF5Reader::HasStoredSpots() const { + std::unique_lock ul(hdf5_mutex); + return active_metadata_ && active_metadata_->HasSpots(); +} + CompressedImage JFJochHDF5Reader::ReadCalibration(std::vector &tmp, const std::string &name) const { std::unique_lock ul(hdf5_mutex); diff --git a/reader/JFJochHDF5Reader.h b/reader/JFJochHDF5Reader.h index 985ace03..b5d51e5f 100644 --- a/reader/JFJochHDF5Reader.h +++ b/reader/JFJochHDF5Reader.h @@ -52,6 +52,9 @@ public: std::vector ReadReflections(size_t start_image = 0, std::optional end_image = {}) const; std::vector ReadSpots(int64_t image) const override; + // True if the loaded file stores spot-finding results; false for a plain DECTRIS dataset (no + // /entry/MX), so callers can find spots instead of reusing absent ones. + [[nodiscard]] bool HasStoredSpots() const; CompressedImage ReadCalibration(std::vector &tmp, const std::string &name) const; diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index 81078b25..728ba367 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -200,6 +200,13 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) { return std::nullopt; }; + // Reuse stored spots only if the file actually has them: a plain DECTRIS dataset carries no + // spot-finding results, so "reusing" would leave every frame with zero spots and fail + // indexing - find them instead. (--redo-rotation-spots already forces finding.) + const bool reuse_spots = config_.reuse_rotation_spots && reader_.HasStoredSpots(); + if (config_.reuse_rotation_spots && !reuse_spots) + logger.Info("Dataset has no stored spots - running spot finding for first-pass rotation indexing"); + // Spots for a first-pass image ordinal, cached (a frame is reused across schemes and the // validation set; re-finding is expensive on the --redo-rotation-spots path). Accessed only // from single-threaded sections (the scheme feed and the validation loop), so no locking. @@ -211,7 +218,7 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) { const int image_idx = start_image + ordinal * config_.stride; std::vector spots; try { - if (config_.reuse_rotation_spots) { + if (reuse_spots) { spots = reader_.ReadSpots(image_idx); } else if (auto img = reader_.GetRawImage(image_idx)) { DataMessage m{};