// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include "BraggIntegrate2D.h" #include #include #include namespace { template float Median(std::vector &values) { if (values.empty()) return 0.0f; const size_t middle = values.size() / 2; std::nth_element(values.begin(), values.begin() + middle, values.end()); if (values.size() % 2 == 1) return static_cast(values[middle]); const T upper = values[middle]; std::nth_element(values.begin(), values.begin() + middle - 1, values.begin() + middle); const T lower = values[middle - 1]; return 0.5f * static_cast(lower + upper); } void MarkReflectionMask(std::vector &mask, size_t xpixel, size_t ypixel, const Reflection &r, float r_2, float r_2_sq) { int64_t x0 = std::floor(r.predicted_x - r_2 - 1.0f); int64_t x1 = std::ceil(r.predicted_x + r_2 + 1.0f); int64_t y0 = std::floor(r.predicted_y - r_2 - 1.0f); int64_t y1 = std::ceil(r.predicted_y + r_2 + 1.0f); if (x0 < 0) x0 = 0; if (y0 < 0) y0 = 0; if (x1 >= static_cast(xpixel)) x1 = static_cast(xpixel) - 1; if (y1 >= static_cast(ypixel)) y1 = static_cast(ypixel) - 1; for (int64_t y = y0; y <= y1; y++) { for (int64_t x = x0; x <= x1; x++) { const float dist_sq = (x - r.predicted_x) * (x - r.predicted_x) + (y - r.predicted_y) * (y - r.predicted_y); if (dist_sq < r_2_sq) mask[y * xpixel + x] = 1; } } } std::vector BuildReflectionMask(const std::vector &predicted, size_t npredicted, size_t xpixel, size_t ypixel, float r_2, float r_2_sq) { std::vector mask(xpixel * ypixel, 0); for (size_t i = 0; i < npredicted; i++) MarkReflectionMask(mask, xpixel, ypixel, predicted.at(i), r_2, r_2_sq); return mask; } } // namespace template void IntegrateReflection(Reflection &r, const T *image, const std::vector &reflection_mask, size_t xpixel, size_t ypixel, int64_t special_value, int64_t saturation, float r_3, float r_1_sq, float r_2_sq, float r_3_sq) { int64_t x0 = std::floor(r.predicted_x - r_3 - 1.0); int64_t x1 = std::ceil(r.predicted_x + r_3 + 1.0); int64_t y0 = std::floor(r.predicted_y - r_3 - 1.0); int64_t y1 = std::ceil(r.predicted_y + r_3 + 1.0); if (x0 < 0) x0 = 0; if (y0 < 0) y0 = 0; if (x1 >= xpixel) x1 = xpixel - 1; if (y1 >= ypixel) y1 = ypixel - 1; int64_t I_sum = 0; int64_t I_npixel_inner = 0; int64_t I_npixel_integrated = 0; std::vector bkg_values; bkg_values.reserve(static_cast((x1 - x0 + 1) * (y1 - y0 + 1))); for (int64_t y = y0; y <= y1; y++) { for (int64_t x = x0; x <= x1; x++) { const float dist_sq = (x - r.predicted_x) * (x - r.predicted_x) + (y - r.predicted_y) * (y - r.predicted_y); const auto pixel = image[y * xpixel + x]; if (dist_sq < r_1_sq) I_npixel_inner++; if (pixel == special_value || pixel == saturation) continue; if (dist_sq < r_1_sq) { I_sum += pixel; I_npixel_integrated++; } else if (dist_sq >= r_2_sq && dist_sq < r_3_sq) { if (reflection_mask[y * xpixel + x] != 0) continue; bkg_values.emplace_back(pixel); } } } if ((I_npixel_integrated == I_npixel_inner) && (bkg_values.size() > 5)) { r.bkg = Median(bkg_values); r.I = static_cast(I_sum) - static_cast(I_npixel_integrated) * r.bkg; // minimum sigma is 1! if (I_sum >= 1) r.sigma = std::sqrt(static_cast(I_sum)); else r.sigma = 1; r.observed = true; } else { r.I = 0; r.bkg = 0; r.observed = false; } } template std::vector IntegrateInternal(const DiffractionExperiment &experiment, const CompressedImage &image, const std::vector &predicted, size_t npredicted, int64_t special_value, int64_t saturation, int64_t image_number) { std::vector ret; ret.reserve(npredicted); auto settings = experiment.GetBraggIntegrationSettings(); auto geom = experiment.GetDiffractionGeometry(); std::vector buffer; auto ptr = reinterpret_cast(image.GetUncompressedPtr(buffer)); const float r_3 = settings.GetR3(); const float r_1_sq = settings.GetR1() * settings.GetR1(); const float r_2 = settings.GetR2(); const float r_2_sq = settings.GetR2() * settings.GetR2(); const float r_3_sq = settings.GetR3() * settings.GetR3(); const auto reflection_mask = BuildReflectionMask(predicted, npredicted, image.GetWidth(), image.GetHeight(), r_2, r_2_sq); for (int i = 0; i < npredicted; i++) { auto r = predicted.at(i); IntegrateReflection(r, ptr, reflection_mask, image.GetWidth(), image.GetHeight(), special_value, saturation, r_3, r_1_sq, r_2_sq, r_3_sq); if (r.observed) { if (experiment.GetPolarizationFactor()) r.rlp /= geom.CalcAzIntPolarizationCorr(r.predicted_x, r.predicted_y, experiment.GetPolarizationFactor().value()); r.image_number = static_cast(image_number); ret.emplace_back(r); } } return ret; } std::vector BraggIntegrate2D(const DiffractionExperiment &experiment, const CompressedImage &image, const std::vector &predicted, size_t npredicted, int64_t image_number) { if (image.GetCompressedSize() == 0 || predicted.empty()) return {}; switch (image.GetMode()) { case CompressedImageMode::Int8: return IntegrateInternal(experiment, image, predicted, npredicted, INT8_MIN, INT8_MAX, image_number); case CompressedImageMode::Int16: return IntegrateInternal(experiment, image, predicted, npredicted, INT16_MIN, INT16_MAX, image_number); case CompressedImageMode::Int32: return IntegrateInternal(experiment, image, predicted, npredicted, INT32_MIN, INT32_MAX, image_number); case CompressedImageMode::Uint8: return IntegrateInternal(experiment, image, predicted, npredicted, UINT8_MAX, UINT8_MAX, image_number); case CompressedImageMode::Uint16: return IntegrateInternal(experiment, image, predicted, npredicted, UINT16_MAX, UINT16_MAX, image_number); case CompressedImageMode::Uint32: return IntegrateInternal(experiment, image, predicted, npredicted, UINT32_MAX, UINT32_MAX, image_number); default: throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Image mode not supported"); } }