Files
Jungfraujoch/common/AzimuthalIntegrationSettings.cpp
leonarski_fandClaude Opus 5 5830f78d57 Revert the azimuthal-integration sigma clip
Removes azim_int_settings.sigma_clip / rugnux --azim-sigma-clip and the clipping
machinery in AzIntEngine. This is a partial revert of a6be35ccd - the ice-ring-mask
removal that commit also carried stays. Sigma clipping remains where it started and
where it is needed: inside the adaptive spot finder, at a fixed 3 sigma on raw
counts, feeding the detection threshold and the ice score.

The option made the workflow harder to reason about than the quantity was worth. It
gave azimuthal integration two meanings behind one setting - the bin mean and the
background under the peaks - which the azimuthal-integration workflows do not need.
It also did not compose with the fused GPU engine, which supplies the profile from
its PLAIN pass: on the default rugnux, viewer and receiver path the setting was
silently doing nothing (measured, the profile came out identical to the unclipped
run to 1e-6 with identical per-bin pixel counts). Making it correct is not a matter
of gating that one shortcut - it means separating the workflows (azimuthal
integration, MX rotation, MX stills, geometry calibration) and deciding per workflow
what the profile is for, which is a larger change than the option earns.

The default path is unaffected: over 20 images of a rotation dataset the radial
profile, the per-bin pixel counts and the spot counts are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 15:31:19 +02:00

162 lines
5.7 KiB
C++

// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include <algorithm>
#include <cmath>
#include "AzimuthalIntegrationSettings.h"
#include "JFJochException.h"
#define check_max(param, val, max) if ((val) > (max)) throw JFJochException(JFJochExceptionCategory::InputParameterAboveMax, param)
#define check_min(param, val, min) if ((val) < (min)) throw JFJochException(JFJochExceptionCategory::InputParameterBelowMin, param)
#define check_finite(param, val) if (!std::isfinite(val)) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, param)
AzimuthalIntegrationSettings::AzimuthalIntegrationSettings() {
UpdateBinCount();
}
AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::SolidAngleCorrection(bool input) {
solid_angle_correction = input;
return *this;
}
AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::QRange_recipA(float low, std::optional<float> high) {
check_finite("Low Q for azimuthal integration", low);
check_min("Low Q for azimuthal integration", low, minQ_recipA);
// The low limit has to leave room for at least one bin below maxQ. This used to follow from the
// high limit being mandatory (high <= maxQ and high > low); once "unset" became legal, ResolveHighQ
// was left computing std::clamp(q, low + spacing, maxQ) with the lower bound above the upper one.
check_max("Low Q for azimuthal integration", low, maxQ_recipA - q_spacing);
if (high.has_value()) {
check_finite("High Q for azimuthal integration", *high);
check_max("High Q for azimuthal integration", *high, maxQ_recipA);
if (*high <= low)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"High Q must be higher than low Q");
}
requested_high_q_recipA = high;
low_q_recipA = low;
// Until ResolveHighQ runs, an unset limit keeps the value the bins were last built from.
high_q_recipA = high.value_or(high_q_recipA);
UpdateBinCount();
return *this;
}
void AzimuthalIntegrationSettings::ResolveHighQ(float detector_max_q_recipA) {
if (requested_high_q_recipA.has_value())
return;
high_q_recipA = std::clamp(detector_max_q_recipA, low_q_recipA + q_spacing, maxQ_recipA);
UpdateBinCount();
}
AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::QSpacing_recipA(float input) {
check_finite("Q spacing for azimuthal integration", input);
check_min("Q spacing for azimuthal integration", input, minQ_recipA);
q_spacing = input;
UpdateBinCount();
return *this;
}
bool AzimuthalIntegrationSettings::IsSolidAngleCorrection() const {
return solid_angle_correction;
}
float AzimuthalIntegrationSettings::GetHighQ_recipA() const {
return high_q_recipA;
}
std::optional<float> AzimuthalIntegrationSettings::GetRequestedHighQ_recipA() const {
return requested_high_q_recipA;
}
float AzimuthalIntegrationSettings::GetLowQ_recipA() const {
return low_q_recipA;
}
float AzimuthalIntegrationSettings::GetQSpacing_recipA() const {
return q_spacing;
}
AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::BkgEstimateQRange_recipA(float low, float high) {
check_finite("Low Q for background estimation", low);
check_finite("High Q for background estimation", high);
check_max("High Q for background estimation", high, maxQ_recipA);
check_min("Low Q for background estimation", low, minQ_recipA);
if (high <= low)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"High Q must be higher than low Q");
bkg_estimate_low_q_recipA = low;
bkg_estimate_high_q_recipA = high;
return *this;
}
float AzimuthalIntegrationSettings::GetBkgEstimateLowQ_recipA() const {
return bkg_estimate_low_q_recipA;
}
float AzimuthalIntegrationSettings::GetBkgEstimateHighQ_recipA() const {
return bkg_estimate_high_q_recipA;
}
AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::PolarizationCorrection(bool input) {
polarization_correction = input;
return *this;
}
bool AzimuthalIntegrationSettings::IsPolarizationCorrection() const {
return polarization_correction;
}
AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::AzimuthalBinCount(int32_t input) {
check_min("Azimuthal bin count", input, 1);
check_max("Azimuthal bin count", input, 512);
azim_bins = input;
UpdateBinCount();
return *this;
}
AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::ForceCPUinFPGAWorkflow(bool input) {
force_cpu_in_fpga_workflow = input;
return *this;
}
bool AzimuthalIntegrationSettings::IsForceCPUinFPGAWorkflow() const {
return force_cpu_in_fpga_workflow;
}
int32_t AzimuthalIntegrationSettings::GetQBinCount() const {
return q_bins;
}
int32_t AzimuthalIntegrationSettings::GetAzimuthalBinCount() const {
return azim_bins;
}
int32_t AzimuthalIntegrationSettings::GetBinCount() const {
return total_bins;
}
uint16_t AzimuthalIntegrationSettings::QToBin(float q) const {
return std::min<uint16_t>(GetBinCount() - 1, std::floor(std::max(0.0f, (q - GetLowQ_recipA()) / GetQSpacing_recipA())));
}
uint16_t AzimuthalIntegrationSettings::GetBin(float q, float phi_deg) const {
if (q < low_q_recipA || q >= high_q_recipA)
return UINT16_MAX;
if (phi_deg < 0.0 || phi_deg >= 360)
return UINT16_MAX;
int16_t q_bin = std::floor((q - low_q_recipA) / q_spacing);
int16_t phi_bin = std::floor(phi_deg / 360.0f * azim_bins);
return q_bin + phi_bin * GetQBinCount();
}
void AzimuthalIntegrationSettings::UpdateBinCount() {
q_bins = std::ceil((high_q_recipA - low_q_recipA) / q_spacing);
total_bins = q_bins * azim_bins;
}