Inactive pixels get a pixel mask bit of their own (bit 11)
The pixels rugnux measures as outside a CCD's active area shared bit 1 with the detector's own bad pixels, so they could not be told from the device mask and pixel_sum left them out. They now have bit 11, a run-determined bit like the shadow and the hot pixels: they stay masked in the image and the other statistics, and pixel_sum counts them. The beam-stop finder still reads them as data. A file written with this mask no longer shows them as bit 1. The jfjoch XDS plugin and DIALS mask any bit, so they still mask them; Durin and Neggia, which ignore the bits from 8 up, no longer do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
15af14357d
commit
e6baecda58
10 files changed
+33
-15
No files matched your search
@@ -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);
|
||||
}
|
||||
|
||||
+5
-2
@@ -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<uint32_t>& 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<uint32_t>& mask);
|
||||
void LoadDECTRISBadPixelMask(const std::vector<uint32_t>& mask);
|
||||
void LoadDarkBadPixelMask(const DiffractionExperiment& experiment, const std::vector<uint32_t>& mask);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
@@ -349,7 +349,7 @@ void Rugnux::PreScan(int start_image, int images_to_process, int frame_count, Ru
|
||||
std::vector<uint32_t> 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<ShadowFinder> finder;
|
||||
|
||||
@@ -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<uint32_t> user_mask(x.GetPixelsNum(), 0), shadow(x.GetPixelsNum(), 0), hot(x.GetPixelsNum(), 0);
|
||||
std::vector<uint32_t> 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<int32_t> 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<const uint8_t *>(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<uint32_t> 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);
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
Reference in new issue
Block a user