diff --git a/image_analysis/scale_merge/RotationScaleMerge.cpp b/image_analysis/scale_merge/RotationScaleMerge.cpp index b37f3237..c8e3f4ae 100644 --- a/image_analysis/scale_merge/RotationScaleMerge.cpp +++ b/image_analysis/scale_merge/RotationScaleMerge.cpp @@ -29,7 +29,8 @@ namespace { // These mirror the per-image ScaleOnTheFly / Merge rocking-curve physics verbatim so this flat // implementation is numerically identical - see the comments there for the details. - constexpr size_t MIN_REFLECTIONS = 20; // per-frame scale needs at least this many + constexpr size_t MIN_REFLECTIONS = 20; + constexpr int64_t MIN_REFLECTIONS_FOR_IMAGE_CC = 20; // below this a frame's CC means nothing // per-frame scale needs at least this many constexpr double SCALE_ROBUST_K = 3.0; // Cauchy loss scale (sigma units) for the per-frame G fit // A fitted per-frame scale below this fraction of the run's median is not a measurement of anything: @@ -175,6 +176,7 @@ RotationScaleMerge::RotationScaleMerge(const DiffractionExperiment &experiment, merge_friedel = s.GetMergeFriedel(); capture_uncertainty_coeff = s.GetCaptureUncertaintyCoeff(); min_captured_fraction = s.GetMinCapturedFraction(); + min_cc_for_image = s.GetMinCCForImage(); reject_nsigma = s.GetOutlierRejectNsigma(); reject_outliers = reject_nsigma > 0.0; rfree_fraction = s.GetRfreeFraction(); @@ -1993,6 +1995,35 @@ RotationScaleMerge::Result RotationScaleMerge::Run(bool for_search, } FinalizePerFrameScale(cc, cc_n, partial_scaled); + // --- 2b. Drop frames that do not agree with the merged reference (--min-image-cc). --- + if (min_cc_for_image > 0.0) { + std::vector reject(n_frames, 0); + int n_rejected = 0; + for (int f = 0; f < n_frames; ++f) + if (std::isfinite(cc[f]) && cc_n[f] >= MIN_REFLECTIONS_FOR_IMAGE_CC && cc[f] < min_cc_for_image) { + reject[f] = 1; + ++n_rejected; + } + if (n_rejected > 0) { + // Zeroing corr is how a frame leaves the pipeline: every consumer requires corr > 0, so the + // frame's partials stop being usable for the combine, the merge and the error model alike. + bool rejected_on_gpu = false; +#ifdef JFJOCH_USE_CUDA + if (gpu_active_) { + std::vector ratio(n_frames, 1.0); + for (int f = 0; f < n_frames; ++f) if (reject[f]) ratio[f] = 0.0; + gpu_->SmoothCorr(reject.data(), ratio.data()); + rejected_on_gpu = true; + } +#endif + if (!rejected_on_gpu) + for (auto &o : partials) + if (reject[o.frame]) o.corr = 0.0f; + logger.Info("Rejected {} of {} frames correlating below {:.2f} with the merged reference", + n_rejected, n_frames, min_cc_for_image); + } + } + // --- 3. 3D combine of per-frame partials into fulls (fulls inherit their ASU group here). --- bool combined_on_gpu = false; bool scaled_fulls_on_gpu = false; diff --git a/image_analysis/scale_merge/RotationScaleMerge.h b/image_analysis/scale_merge/RotationScaleMerge.h index 2dd3be38..adb37fa9 100644 --- a/image_analysis/scale_merge/RotationScaleMerge.h +++ b/image_analysis/scale_merge/RotationScaleMerge.h @@ -100,6 +100,12 @@ private: bool merge_friedel = true; double capture_uncertainty_coeff = 0.0; double min_captured_fraction = 0.0; + + // Drop a frame's observations entirely when the frame disagrees with the merged reference below this + // correlation (--min-image-cc). A mis-centred or off-crystal frame still produces spots, still + // indexes and still integrates - it just measures something that is not the crystal's diffraction, + // and nothing downstream removes it. 0 = off. + double min_cc_for_image = 0.0; double reject_nsigma = 0.0; bool reject_outliers = false; double rfree_fraction = 0.0; diff --git a/rugnux/rugnux_cli.cpp b/rugnux/rugnux_cli.cpp index 383625d9..d1e1e66f 100644 --- a/rugnux/rugnux_cli.cpp +++ b/rugnux/rugnux_cli.cpp @@ -1425,10 +1425,6 @@ int main(int argc, char **argv) { scaling_settings.CaptureUncertaintyCoeff(capture_uncertainty_arg.value_or(rotation_indexing ? 1.0 : 0.0)); scaling_settings.ForcedMosaicity(forced_mosaicity_arg); scaling_settings.MinCCForImage(min_image_cc / 100.0); // --min-image-cc is in percent; the setting is a fraction - // The per-image CC filter is consumed by the stills merge (MergeOnTheFly) only - RotationScaleMerge - // never reads it - so on rotation data it would otherwise be accepted and silently do nothing. - if (min_image_cc > 0.0 && rotation_indexing) - logger.Warning("--min-image-cc is ignored for rotation data: it filters the stills merge only"); scaling_settings.OutlierRejectNsigma( outlier_reject_nsigma.value_or(rotation_indexing ? REJECT_OUTLIERS_DEFAULT_NSIGMA : 0.0));