The error/invalid pixel marker (the pixel type's extreme value, e.g. 0xFFFFFFFF for EIGER uint32) was only mapped to the internal error value for SIGNED types - the check was gated on std::is_signed<T>. For unsigned EIGER data a 0xFFFFFFFF pixel therefore fell through to the saturation test and, exceeding the (HDF5) saturation_value, was silently classified as saturated rather than invalid. Both are excluded from integration, so results are unchanged where such pixels are also in the static pixel mask (as in the JUNGFRAU test data - Thau_9 and lyso are bit-for-bit the same), but the classification/statistics were wrong and a stray 0xFFFFFFFF outside the static mask would be mis-accounted. Test the error marker before saturation and drop the is_signed gate in both the CPU and GPU preprocessors: the HDF5 saturation_value stays the saturation threshold, the pixel type's maximum is the invalid marker (priority masked > error > saturated). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
68 lines
2.9 KiB
C++
68 lines
2.9 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "ImagePreprocessorCPU.h"
|
|
|
|
ImagePreprocessorCPU::ImagePreprocessorCPU(const DiffractionExperiment &experiment, const PixelMask &mask)
|
|
: ImagePreprocessor(experiment),
|
|
mask_1bit(npixels, false) {
|
|
for (int i = 0; i < npixels; i++)
|
|
mask_1bit[i] = (mask.GetMask().at(i) != 0);
|
|
}
|
|
|
|
ImageStatistics ImagePreprocessorCPU::Analyze(ImagePreprocessorBuffer &processed_image, const uint8_t *image_ptr, CompressedImageMode image_mode) {
|
|
switch (image_mode) {
|
|
case CompressedImageMode::Int8:
|
|
return Analyze<int8_t>(processed_image, image_ptr, INT8_MIN, INT8_MAX);
|
|
case CompressedImageMode::Int16:
|
|
return Analyze<int16_t>(processed_image, image_ptr, INT16_MIN, INT16_MAX);
|
|
case CompressedImageMode::Int32:
|
|
return Analyze<int32_t>(processed_image, image_ptr, INT32_MIN, INT32_MAX);
|
|
case CompressedImageMode::Uint8:
|
|
return Analyze<uint8_t>(processed_image, image_ptr, UINT8_MAX, UINT8_MAX);
|
|
case CompressedImageMode::Uint16:
|
|
return Analyze<uint16_t>(processed_image, image_ptr, UINT16_MAX, UINT16_MAX);
|
|
case CompressedImageMode::Uint32:
|
|
return Analyze<uint32_t>(processed_image, image_ptr, UINT32_MAX, UINT32_MAX);
|
|
default:
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "RGB/float mode not supported");
|
|
}
|
|
}
|
|
|
|
template<class T>
|
|
ImageStatistics ImagePreprocessorCPU::Analyze(ImagePreprocessorBuffer &processed_image, const uint8_t *input, T err_pixel_val, T sat_pixel_val) {
|
|
|
|
if (processed_image.size() != npixels)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Processed image size mismatch");
|
|
|
|
auto image = reinterpret_cast<const T *>(input);
|
|
|
|
ImageStatistics ret{};
|
|
|
|
if (sat_pixel_val > saturation_limit)
|
|
sat_pixel_val = static_cast<T>(saturation_limit);
|
|
|
|
for (int i = 0; i < npixels; i++) {
|
|
if (mask_1bit[i] != 0) {
|
|
processed_image[i] = INT32_MIN;
|
|
++ret.masked_pixel_count;
|
|
} else if (image[i] == err_pixel_val) {
|
|
// Error/invalid marker = the pixel type's extreme value (0xFFFFFFFF for EIGER uint32).
|
|
// Tested before saturation, since for unsigned types the marker also exceeds sat_pixel_val
|
|
// (which is clipped above to the HDF5 saturation_value).
|
|
processed_image[i] = INT32_MIN;
|
|
++ret.error_pixel_count;
|
|
} else if (image[i] >= sat_pixel_val) {
|
|
processed_image[i] = INT32_MAX;
|
|
++ret.saturated_pixel_count;
|
|
} else {
|
|
processed_image[i] = static_cast<int32_t>(image[i]);
|
|
if (image[i] > ret.max_value)
|
|
ret.max_value = image[i];
|
|
if (image[i] < ret.min_value)
|
|
ret.min_value = image[i];
|
|
}
|
|
}
|
|
return ret;
|
|
}
|