diff --git a/common/PixelMask.cpp b/common/PixelMask.cpp index 34db40f6b..faaa60039 100644 --- a/common/PixelMask.cpp +++ b/common/PixelMask.cpp @@ -275,7 +275,7 @@ void PixelMask::AddDeadPixelMask(const DiffractionExperiment& experiment, const "Size of input dead pixel mask invalid"); for (size_t i = 0; i < mask.size(); i++) { if (in_mask[i] != 0) - mask[i] |= (1 << ErrorPixelBit); + mask[i] |= (1 << InactivePixelBit); } UpdateDerived(experiment); } diff --git a/common/PixelMask.h b/common/PixelMask.h index 2b2df286a..af74463a5 100644 --- a/common/PixelMask.h +++ b/common/PixelMask.h @@ -49,10 +49,13 @@ public: // Found bad on the run's own frames (rugnux pre-scan, HotPixelFinder): lit above its ring on too // many frames, or holding the error value on most of them. constexpr static const uint8_t HotPixelBit = 10; + // Outside a CCD's active area, measured on the run's own frames (RugnuxMeasuredGain::constant). + constexpr static const uint8_t InactivePixelBit = 11; constexpr static const uint8_t ChipGapPixelBit = 31; constexpr static const uint8_t ModuleEdgePixelBit = 30; // What a run determines on its own frames, as against the detector's and the calibration's mask. - constexpr static const uint32_t RunMaskBits = (1u << BeamStopPixelBit) | (1u << HotPixelBit); + constexpr static const uint32_t RunMaskBits = (1u << BeamStopPixelBit) | (1u << HotPixelBit) + | (1u << InactivePixelBit); // The values of GetBinaryMask(): masked by the device mask, or only by what the run determined. constexpr static const uint8_t BinaryMaskDevice = 1; constexpr static const uint8_t BinaryMaskRunOnly = 2; @@ -70,7 +73,7 @@ public: // from a file that carries one starts clear. The user mask (bit 8) is deliberately left alone. void ClearBeamStopMask(const DiffractionExperiment& experiment); void LoadHotPixelMask(const DiffractionExperiment& experiment, const std::vector& mask); - // Pixels that record nothing (NXmx bit 1, "dead"), added to whatever the bit already holds. + // Pixels that record nothing (InactivePixelBit), added to whatever the bit already holds. void AddDeadPixelMask(const DiffractionExperiment& experiment, const std::vector& mask); void LoadDECTRISBadPixelMask(const std::vector& mask); void LoadDarkBadPixelMask(const DiffractionExperiment& experiment, const std::vector& mask); diff --git a/docs/CPU_DATA_ANALYSIS_IMAGE.md b/docs/CPU_DATA_ANALYSIS_IMAGE.md index 40d55dfb2..7eaada1ff 100644 --- a/docs/CPU_DATA_ANALYSIS_IMAGE.md +++ b/docs/CPU_DATA_ANALYSIS_IMAGE.md @@ -454,7 +454,7 @@ many pixels were masked and why. CCD, an image plate or another integrating detector read through an amplifier) has read noise on every live pixel, so no live pixel reads the same value on all 16 frames the detector gain is measured on. A pixel that does lies outside the active area - the corners of a round CCD, a border or seam -the detector's corrections leave at zero - and is masked as dead (bit 1) before the run starts. A test that +the detector's corrections leave at zero - and is masked as inactive (bit 11) before the run starts. A test that compares a pixel with its own resolution ring cannot see such an area: beyond the circle of a round CCD the whole ring reads zero. It is not made where the pixels that do vary read zero on 1 % or more of their readings (counted photons, or a file cut at a threshold), since a pixel at zero on every diff --git a/docs/FPGA_DATA_ANALYSIS.md b/docs/FPGA_DATA_ANALYSIS.md index fcb0ba202..23738a0af 100644 --- a/docs/FPGA_DATA_ANALYSIS.md +++ b/docs/FPGA_DATA_ANALYSIS.md @@ -80,7 +80,7 @@ Valid pixels are not masked, not saturated, not error pixels. These are not specific to the FPGA path: the CPU and GPU image preprocessors of the shared `image_analysis/` library collect the same statistics per image, for detectors driven through DECTRIS SIMPLON (e.g. EIGER) and for rugnux. There the sum excludes only the device mask (detector and -calibration), not the hot pixels and the beam-stop shadow a run finds on its own frames. +calibration), not the hot pixels, the beam-stop shadow and the inactive pixels a run finds on its own frames. ## Square root compression Jungfraujoch FPGA includes lossy compression preserving counting statistic properties of X-ray image, while reducing bit width of an image. diff --git a/docs/HDF5.md b/docs/HDF5.md index 40f7f221a..19d1eb4fa 100644 --- a/docs/HDF5.md +++ b/docs/HDF5.md @@ -544,7 +544,7 @@ every pixel belonging to that ROI, so an ROI's footprint can be recovered exactl `error_pixels`, `saturated_pixels`, `pixel_sum`. Surfaced in the master file under `/entry/image`. `pixel_sum` is the signed sum of the pixels that are not error, not saturated and not under the device mask (detector and calibration); a mask rugnux determines on the run's own frames (hot pixels, -beam-stop shadow) does not change it. The FPGA computes it per module, and CPU/GPU analysis (e.g. +beam-stop shadow, inactive pixels) does not change it. The FPGA computes it per module, and CPU/GPU analysis (e.g. EIGER) per image, on the assembled image, to the same definition. ### 4.6 `/entry/profiling` — per-image timing diff --git a/docs/PIXEL_MASK.md b/docs/PIXEL_MASK.md index 22f5200a4..efd1dc077 100644 --- a/docs/PIXEL_MASK.md +++ b/docs/PIXEL_MASK.md @@ -25,6 +25,9 @@ Bit 10 - defective pixel found on the run's own frames (rotation data from a cou [CPU data analysis §1.6](CPU_DATA_ANALYSIS_IMAGE.md)): lit above its resolution ring on more frames than one reflection or chance explains, or holding the detector's error value on most frames. +Bit 11 - outside a CCD's active area, measured on the run's own frames (see +[CPU data analysis §1.6](CPU_DATA_ANALYSIS_IMAGE.md)). + Bit 30 - module edge (only for PSI systems) Bit 31 - chip edge interpolated pixel (multipixel) diff --git a/docs/SOFTWARE_INTEGRATION.md b/docs/SOFTWARE_INTEGRATION.md index 4268e4bca..5e9536ba9 100644 --- a/docs/SOFTWARE_INTEGRATION.md +++ b/docs/SOFTWARE_INTEGRATION.md @@ -74,7 +74,7 @@ Measured with the Durin and Neggia builds used in Jungfraujoch CI: |---|---|---|---|---|---| | 0 | module gap | yes | yes | yes | yes | | 1–4 | error, noisy | yes | yes | yes | yes | -| 8, 9, 10 | user mask, beam stop, defective | yes | **no** | **no** | yes | +| 8, 9, 10, 11 | user mask, beam stop, defective, inactive | yes | **no** | **no** | yes | | 30 | module edge | yes | **no** | **no** | yes | | 31 | chip edge | yes | yes | **no** | yes | diff --git a/rugnux/RugnuxPreScan.cpp b/rugnux/RugnuxPreScan.cpp index 8575cdc4c..4b74efcda 100644 --- a/rugnux/RugnuxPreScan.cpp +++ b/rugnux/RugnuxPreScan.cpp @@ -349,7 +349,7 @@ void Rugnux::PreScan(int start_image, int images_to_process, int frame_count, Ru std::vector m = pixel_mask_.GetMask(); for (size_t i = 0; i < m.size(); i++) if (config_.inactive_pixels[i] != 0) - m[i] &= ~(1u << PixelMask::ErrorPixelBit); + m[i] &= ~((1u << PixelMask::ErrorPixelBit) | (1u << PixelMask::InactivePixelBit)); beam_stop_mask = PixelMask(m); } std::optional finder; diff --git a/tests/ImagePreprocessorPixelSumTest.cpp b/tests/ImagePreprocessorPixelSumTest.cpp index c723a159c..be4ed761a 100644 --- a/tests/ImagePreprocessorPixelSumTest.cpp +++ b/tests/ImagePreprocessorPixelSumTest.cpp @@ -99,24 +99,29 @@ TEST_CASE("ImagePreprocessorCPU_PixelSum_NoOverflow", "[ImagePreprocessor]") { CHECK(stats.pixel_sum > INT32_MAX); } -// A mask a run determines (a beam-stop shadow, hot pixels) takes pixels out of the image, not out of the sum. +// A mask a run determines (a beam-stop shadow, hot pixels, inactive pixels) takes pixels out of the image, not out of the sum. TEST_CASE("ImagePreprocessorCPU_PixelSum_RunMask", "[ImagePreprocessor]") { DiffractionExperiment x(DetDECTRIS(64, 4, "Test", {}).BitDepthImage(32)); x.PixelSigned(true); - std::vector user_mask(x.GetPixelsNum(), 0), shadow(x.GetPixelsNum(), 0), hot(x.GetPixelsNum(), 0); + std::vector user_mask(x.GetPixelsNum(), 0), shadow(x.GetPixelsNum(), 0), hot(x.GetPixelsNum(), 0), + inactive(x.GetPixelsNum(), 0); user_mask[1] = 1; // the device's shadow[1] = 1; // and the run's: the device mask decides shadow[2] = 1; shadow[3] = 1; // error pixel under the shadow hot[4] = 1; hot[5] = 1; // saturated pixel, hot + inactive[6] = 1; + inactive[7] = 1; // error pixel, inactive + inactive[1] = 1; // the device mask decides here too PixelMask device(x); device.LoadUserMask(x, user_mask); PixelMask with_run = device; with_run.LoadBeamStopMask(x, shadow); with_run.LoadHotPixelMask(x, hot); + with_run.AddDeadPixelMask(x, inactive); std::vector image(x.GetPixelsNum(), 0); image[0] = 11; @@ -125,18 +130,21 @@ TEST_CASE("ImagePreprocessorCPU_PixelSum_RunMask", "[ImagePreprocessor]") { image[3] = INT32_MIN; image[4] = -33; image[5] = INT32_MAX; + image[6] = 44; + image[7] = INT32_MIN; const auto input = reinterpret_cast(image.data()); ImagePreprocessorBuffer buffer(x.GetPixelsNum()); ImagePreprocessorCPU device_only(x, device); - CHECK(device_only.Analyze(buffer, input, CompressedImageMode::Int32).pixel_sum == 11 + 22 - 33); + CHECK(device_only.Analyze(buffer, input, CompressedImageMode::Int32).pixel_sum == 11 + 22 - 33 + 44); ImagePreprocessorCPU run(x, with_run); const ImageStatistics stats = run.Analyze(buffer, input, CompressedImageMode::Int32); - CHECK(stats.pixel_sum == 11 + 22 - 33); - CHECK(stats.masked_pixel_count == 5); + CHECK(stats.pixel_sum == 11 + 22 - 33 + 44); + CHECK(stats.masked_pixel_count == 7); CHECK(stats.max_value == 11); CHECK(buffer[2] == INT32_MIN); + CHECK(buffer[6] == INT32_MIN); } // CPU or GPU, whichever this machine runs, from the image to the vector the master file is written from. @@ -210,6 +218,10 @@ TEST_CASE("ImagePreprocessorGPU_PixelSum", "[ImagePreprocessorGPU]") { PixelMask mask(x); mask.LoadUserMask(x, user_mask); mask.LoadBeamStopMask(x, shadow); + std::vector inactive(npixels, 0); + for (size_t i = 0; i < npixels; i += 1003) + inactive[i] = 1; + mask.AddDeadPixelMask(x, inactive); // Mostly near the limit, so the sum needs far more than 32 bits; negative pixels and markers among them. std::mt19937 rng(11); diff --git a/tests/JFJochReaderTest.cpp b/tests/JFJochReaderTest.cpp index 79eed81b1..a751f14a0 100644 --- a/tests/JFJochReaderTest.cpp +++ b/tests/JFJochReaderTest.cpp @@ -4933,8 +4933,8 @@ TEST_CASE("RugnuxMeasureGain_ConstantPixels", "[portable]") { reader.AddDeadPixelMask(g.constant); const auto &mask = reader.GetDataset()->pixel_mask->GetMask(); - CHECK(mask[0] == (1u << PixelMask::ErrorPixelBit)); - CHECK(mask[5 * f.nx + 10] == (1u << PixelMask::ErrorPixelBit)); + CHECK(mask[0] == (1u << PixelMask::InactivePixelBit)); + CHECK(mask[5 * f.nx + 10] == (1u << PixelMask::InactivePixelBit)); CHECK(mask[5 * f.nx + 11] == 0); reader.Close(); for (int frame = 1; frame <= frames; frame++) {