diff --git a/image_analysis/scale_merge/RotationScaleMerge.cpp b/image_analysis/scale_merge/RotationScaleMerge.cpp index bfde3cd48..d3597ca03 100644 --- a/image_analysis/scale_merge/RotationScaleMerge.cpp +++ b/image_analysis/scale_merge/RotationScaleMerge.cpp @@ -4841,9 +4841,8 @@ RotationScaleMerge::Result RotationScaleMerge::Run(bool for_search, bool full_st } // The total scale each frame's fulls were amplified by - partial scale, incident flux and the fulls' // own G (corr = 1/G here) - taken BEFORE the correction surfaces fold per-observation terms into - // corr, and before a collapsed frame's fulls are zeroed: the resident fulls only get that zero with - // the corrected corr, so a merge on the device before then still sees them, and must weigh them by - // their own scale. Read by MergeAndStats for the CC1/2 weight. + // corr, and before a collapsed frame's fulls are zeroed (a zeroed corr says nothing about the + // frame's scale). Read by MergeAndStats for the CC1/2 weight. frame_scale_fulls.assign(n_frames, NAN); for (const auto &o : fulls) if (std::isfinite(o.corr) && o.corr > 0.0f && !std::isfinite(frame_scale_fulls[o.frame])) { @@ -4852,6 +4851,22 @@ RotationScaleMerge::Result RotationScaleMerge::Run(bool for_search, bool full_st } const bool rejected_full_scales = scale_fulls && DropCollapsedFullScales(combined_on_gpu && scaled_fulls_on_gpu); +#ifdef JFJOCH_USE_CUDA + // The host fulls' corr is the truth from here on; a merge on the resident (GPU) fulls reads the + // device copy, so every change to it is pushed back before the next device merge. + auto upload_fulls_corr = [&]() { + std::vector fcorr(fulls.size()); + ParallelChunks(static_cast(fulls.size()), ThreadsForWork(fulls.size(), nthreads), + [&](int lo, int hi) { + for (int i = lo; i < hi; ++i) fcorr[i] = fulls[i].corr; + }); + gpu_->SetFullsCorr(fcorr.data()); + }; + // Right away, not with the corrections below: the merge just before the surfaces must see the + // collapsed frames' fulls gone on the device exactly as the host merge does. + if (rejected_full_scales && combined_on_gpu && scaled_fulls_on_gpu) + upload_fulls_corr(); +#endif // --- 4b. Optional correction surfaces (decay = resolution x time; absorption = goniometer-frame // diffracted-beam direction), each an alternating multiplicative fit of the fulls' corr against @@ -4917,16 +4932,10 @@ RotationScaleMerge::Result RotationScaleMerge::Run(bool for_search, bool full_st CountSweepDisposition(); } #ifdef JFJOCH_USE_CUDA - // The corrections mutate the host fulls' corr; when the merge runs on the resident (GPU) fulls, push - // the corrected corr back to the device so the merge reads it. - if ((corrections || rejected_full_scales || rejected_frames) && combined_on_gpu && scaled_fulls_on_gpu) { - std::vector fcorr(fulls.size()); - ParallelChunks(static_cast(fulls.size()), ThreadsForWork(fulls.size(), nthreads), - [&](int lo, int hi) { - for (int i = lo; i < hi; ++i) fcorr[i] = fulls[i].corr; - }); - gpu_->SetFullsCorr(fcorr.data()); - } + // The corrections and the frame rejection mutate the host fulls' corr; push it to the device so the + // merge reads it. (The collapsed-scale drop was pushed where it happened.) + if ((corrections || rejected_frames) && combined_on_gpu && scaled_fulls_on_gpu) + upload_fulls_corr(); #endif // --- 5. Error model + merge + statistics. ---