// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #pragma once #include #include #include #include "../common/CompressedImage.h" #include "../common/DiffractionExperiment.h" #include "../common/PixelMask.h" #include "ImagePreprocessorBuffer.h" struct alignas(8) ImageStatistics { unsigned long long error_pixel_count = 0; unsigned long long saturated_pixel_count = 0; unsigned long long masked_pixel_count = 0; long long max_value = INT64_MIN; long long min_value = INT64_MAX; }; class ImagePreprocessor { protected: const size_t npixels; const DiffractionExperiment &experiment; const int64_t saturation_limit; public: ImagePreprocessor(const DiffractionExperiment &experiment); virtual ~ImagePreprocessor() = default; virtual ImageStatistics Analyze(ImagePreprocessorBuffer &processed_image, const uint8_t *decompressed_image, CompressedImageMode image_mode) = 0; // Analyze straight from the COMPRESSED image, decompressing wherever the implementation prefers. // The GPU implementation uploads the compressed chunk and decodes it on the device, so only a few // MB cross PCIe instead of the whole frame and the host never decompresses at all. // Returns false when this implementation cannot handle the image - the CPU preprocessor always, // and the GPU one for any algorithm without a device decoder - and the caller then decompresses // on the host and calls Analyze() as before. Keeping the fallback explicit means a format we // cannot decode on the device is a slower path, never a wrong answer. virtual bool AnalyzeCompressed(ImagePreprocessorBuffer &processed_image, const CompressedImage &image, ImageStatistics &stats) { return false; } // Device time the last AnalyzeCompressed() spent getting the chunk across and decompressing it, // so the caller can still report a decompression cost once the host no longer does the work. // Meaningless unless the previous call returned true. [[nodiscard]] virtual float GetLastDecompressionTime_s() const { return 0.0f; } // Resize the buffer an image will be decompressed into and page-lock it, so that the host->device // copy of Analyze() is a real DMA. Without page-locking the driver stages the copy through its own // pinned pool, which is a host-side copy on the calling thread: it does not overlap and it degrades // badly with the number of workers. Nothing to do on the CPU. virtual void PinInputBuffer(std::vector &buffer, size_t size) {} };