Live stills: the q range stays fixed when the running refinement moves the geometry

An azimuthal q range with no high limit reaches the detector's edge, which depends on the distance.
When the running geometry refinement moved the distance, the rebuilt mapping could have a different
number of bins from the start message and the run's profile. Adding the next frame's profile then
threw "Error combining two radial integration profiles" and cancelled the collection.

The q range is now frozen at its current value when the geometry moves. The replay tool adds up the
frames' profiles as the receiver does, so this path is exercised there; it ignored them before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-11-06 16:32:14 +01:00
1 parent c407e690fd
commit df861de7fb
4 files changed
+51 -3

No files matched your search

+9 -2
View File
@@ -337,6 +337,13 @@ void StillsGPUAnalysis::AnalysisThread() {
}
}
void MoveStillsGeometry(DiffractionExperiment &experiment, float beam_x_pxl, float beam_y_pxl, float distance_mm) {
AzimuthalIntegrationSettings azint = experiment.GetAzimuthalIntegrationSettings();
azint.QRange_recipA(azint.GetLowQ_recipA(), azint.GetHighQ_recipA());
experiment.ImportAzimuthalIntegrationSettings(azint);
experiment.BeamX_pxl(beam_x_pxl).BeamY_pxl(beam_y_pxl).DetectorDistance_mm(distance_mm);
}
void StillsGPUAnalysis::SwitchGeometry() {
// One fit, in the analysis thread that offered the last frame it needed; the others go on meanwhile.
const auto t0 = std::chrono::steady_clock::now();
@@ -361,8 +368,8 @@ void StillsGPUAnalysis::SwitchGeometry() {
}
{
std::unique_lock ul(geometry_mutex);
setup.experiment.BeamX_pxl(static_cast<float>(r->beam_x_px)).BeamY_pxl(static_cast<float>(r->beam_y_px))
.DetectorDistance_mm(static_cast<float>(r->distance_mm));
MoveStillsGeometry(setup.experiment, static_cast<float>(r->beam_x_px), static_cast<float>(r->beam_y_px),
static_cast<float>(r->distance_mm));
retired_mappings.push_back(std::move(setup.mapping));
setup.mapping = std::make_unique<AzimuthalIntegrationMapping>(setup.experiment, setup.mask);
++geometry_epoch;
+4
View File
@@ -51,6 +51,10 @@
// switches to that geometry as each of GEOMETRY_ROUNDS fits is made. The frames analysed before the last
// switch are not merged (their reflections were predicted at an earlier geometry; the images are gone); where
// no fit can be made they are merged as analysed.
// Moves the analysis to a refined geometry during the run. The q range stays where it was, also one left to
// reach the detector's edge: the start message and the run's profile are binned on it.
void MoveStillsGeometry(DiffractionExperiment &experiment, float beam_x_pxl, float beam_y_pxl, float distance_mm);
class StillsGPUAnalysis {
public:
// Enough frames for the distance to be refined at a serial hit rate: of 100 frames of a collection indexed
+27
View File
@@ -125,3 +125,30 @@ TEST_CASE("StillsGPUAnalysis_FailingFrameDoesNotStopAnalysis", "[StillsGPUAnalys
CHECK(s.steps.back().duration_s);
CHECK(buffer.GetStatus().preparation_slots == 0);
}
TEST_CASE("StillsGPUAnalysis_MovedGeometryKeepsTheProfileBins", "[StillsGPUAnalysis]") {
DiffractionExperiment x = StillsExperiment();
const PixelMask mask(x);
const AzimuthalIntegrationMapping before(x, mask);
AzimuthalIntegrationProfile run_profile(before);
// Left to itself the high q follows the detector's edge, and with it the number of bins.
DiffractionExperiment unfrozen = x;
unfrozen.DetectorDistance_mm(120);
REQUIRE(AzimuthalIntegrationMapping(unfrozen, mask).GetBinNumber() != before.GetBinNumber());
MoveStillsGeometry(x, 510, 260, 120);
CHECK(x.GetDetectorDistance_mm() == Catch::Approx(120));
CHECK(x.GetBeamX_pxl() == Catch::Approx(510));
CHECK(x.GetBeamY_pxl() == Catch::Approx(260));
const AzimuthalIntegrationMapping after(x, mask);
CHECK(after.GetBinNumber() == before.GetBinNumber());
CHECK(after.GetBinToQ() == before.GetBinToQ());
const AzimuthalIntegrationProfile frame_profile(after);
CHECK_NOTHROW(run_profile += frame_profile);
// A second move stays on the same bins.
MoveStillsGeometry(x, 505, 255, 90);
CHECK(AzimuthalIntegrationMapping(x, mask).GetBinToQ() == before.GetBinToQ());
}
+11 -1
View File
@@ -14,6 +14,8 @@
#include <sstream>
#include <thread>
#include <getopt.h>
#include <memory>
#include <mutex>
#include "../common/ImageBuffer.h"
#include "../common/Logger.h"
@@ -142,9 +144,12 @@ int main(int argc, char **argv) {
const auto t0 = std::chrono::steady_clock::now();
{
// As the receiver: half the buffer for the frames waiting, the other half for those sent.
std::mutex run_profile_mutex;
std::unique_ptr<AzimuthalIntegrationProfile> run_profile;
StillsGPUAnalysis gpu(x, mask, start, &pool, nthreads, buffer.GetStatus().total_slots / 2, logger,
[&](const StillsGPUAnalysis::Setup &s) {
t_ready = std::chrono::steady_clock::now();
run_profile = std::make_unique<AzimuthalIntegrationProfile>(*s.mapping);
const auto &m = s.mask.GetMask();
std::ofstream f(prefix + "_mask.u32", std::ios::binary);
f.write(reinterpret_cast<const char *>(m.data()), m.size() * sizeof(uint32_t));
@@ -152,7 +157,12 @@ int main(int argc, char **argv) {
std::chrono::duration<double>(t_ready - t0).count(),
s.experiment.GetBeamX_pxl(), s.experiment.GetBeamY_pxl());
},
[&](DataMessage &m, const AzimuthalIntegrationProfile &) {
[&](DataMessage &m, const AzimuthalIntegrationProfile &profile) {
{
// As the receiver's plots do: the frames' profiles add up over the run.
std::unique_lock ul(run_profile_mutex);
*run_profile += profile;
}
++analysed;
spots += static_cast<int64_t>(m.spots.size());
processing_s += m.processing_time_s.value_or(0.0f);