integration: carry the box-sum observed centroid through the profile path

Pass A (the box-sum) always computes an intensity-weighted centroid (the
observed spot position), but it was only emitted in BoxSum mode. Rotation
integration runs the profile path, so observed_x/y were left NAN there and no
positional (detector<->reciprocal) residual was possible downstream.

Emit the Pass-A centroid in the profile path too (CPU) and copy d_obs_x/d_obs_y
back in all modes (GPU) - Pass A fills them regardless of integrator mode.
observed_x/y are consumed only by the diagnostic HDF5 reflection table and the
viewer read-back; nothing in scaling/merge reads them, so merged output is
unchanged - the _process.h5 observed_x/y columns simply go from NAN to the real
centroid. This makes the observed position available to post-refinement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-18 10:23:17 +02:00
co-authored by Claude Opus 4.8
parent 7ceffde5a4
commit 1911875e1c
2 changed files with 10 additions and 8 deletions
@@ -345,8 +345,11 @@ std::vector<Reflection> BraggIntegrationEngineCPU::RunImpl(const Sampler &img,
I = rh.I;
sigma = rh.sigma;
}
// Carry the Pass-A box-sum intensity-weighted centroid (observed spot position) through the
// profile path too - post-refinement uses it as the observed position (beam-centre / distance).
results[i] = {static_cast<float>(I), static_cast<float>(sigma),
static_cast<float>(rh.bkg), 0.0f, 0.0f, true, false};
static_cast<float>(rh.bkg),
static_cast<float>(rh.obs_x), static_cast<float>(rh.obs_y), true, rh.has_obs};
}
return Finalize(predicted, npredicted, results, image_number);
@@ -518,12 +518,11 @@ std::vector<Reflection> BraggIntegrationEngineGPU::Run(const ImagePreprocessorBu
cuda_err(cudaMemcpyAsync(h_sigma.data(), d_sigma, sizeof(float) * npredicted, cudaMemcpyDeviceToHost, *stream));
cuda_err(cudaMemcpyAsync(h_bkg.data(), d_bkg, sizeof(float) * npredicted, cudaMemcpyDeviceToHost, *stream));
cuda_err(cudaMemcpyAsync(h_ok.data(), d_ok, sizeof(uint8_t) * npredicted, cudaMemcpyDeviceToHost, *stream));
const bool boxsum_mode = mode == IntegratorMode::BoxSum;
if (boxsum_mode) {
cuda_err(cudaMemcpyAsync(h_obs_x.data(), d_obs_x, sizeof(float) * npredicted, cudaMemcpyDeviceToHost, *stream));
cuda_err(cudaMemcpyAsync(h_obs_y.data(), d_obs_y, sizeof(float) * npredicted, cudaMemcpyDeviceToHost, *stream));
cuda_err(cudaMemcpyAsync(h_has_obs.data(), d_has_obs, sizeof(uint8_t) * npredicted, cudaMemcpyDeviceToHost, *stream));
}
// Pass A always fills the box-sum centroid (observed spot position), so copy it back in all modes -
// post-refinement uses it as the observed position (beam-centre / distance).
cuda_err(cudaMemcpyAsync(h_obs_x.data(), d_obs_x, sizeof(float) * npredicted, cudaMemcpyDeviceToHost, *stream));
cuda_err(cudaMemcpyAsync(h_obs_y.data(), d_obs_y, sizeof(float) * npredicted, cudaMemcpyDeviceToHost, *stream));
cuda_err(cudaMemcpyAsync(h_has_obs.data(), d_has_obs, sizeof(uint8_t) * npredicted, cudaMemcpyDeviceToHost, *stream));
cuda_err(cudaStreamSynchronize(*stream));
for (size_t i = 0; i < npredicted; ++i) {
@@ -532,7 +531,7 @@ std::vector<Reflection> BraggIntegrationEngineGPU::Run(const ImagePreprocessorBu
results[i].sigma = h_sigma[i];
results[i].bkg = h_bkg[i];
results[i].ok = true;
if (boxsum_mode && h_has_obs[i]) {
if (h_has_obs[i]) {
results[i].observed_x = h_obs_x[i];
results[i].observed_y = h_obs_y[i];
results[i].has_observed = true;