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");
|
"Size of input dead pixel mask invalid");
|
||||||
for (size_t i = 0; i < mask.size(); i++) {
|
for (size_t i = 0; i < mask.size(); i++) {
|
||||||
if (in_mask[i] != 0)
|
if (in_mask[i] != 0)
|
||||||
mask[i] |= (1 << ErrorPixelBit);
|
mask[i] |= (1 << InactivePixelBit);
|
||||||
}
|
}
|
||||||
UpdateDerived(experiment);
|
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
|
// 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.
|
// many frames, or holding the error value on most of them.
|
||||||
constexpr static const uint8_t HotPixelBit = 10;
|
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 ChipGapPixelBit = 31;
|
||||||
constexpr static const uint8_t ModuleEdgePixelBit = 30;
|
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.
|
// 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.
|
// 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 BinaryMaskDevice = 1;
|
||||||
constexpr static const uint8_t BinaryMaskRunOnly = 2;
|
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.
|
// from a file that carries one starts clear. The user mask (bit 8) is deliberately left alone.
|
||||||
void ClearBeamStopMask(const DiffractionExperiment& experiment);
|
void ClearBeamStopMask(const DiffractionExperiment& experiment);
|
||||||
void LoadHotPixelMask(const DiffractionExperiment& experiment, const std::vector<uint32_t>& mask);
|
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 AddDeadPixelMask(const DiffractionExperiment& experiment, const std::vector<uint32_t>& mask);
|
||||||
void LoadDECTRISBadPixelMask(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);
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
`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
|
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
|
## Square root compression
|
||||||
Jungfraujoch FPGA includes lossy compression preserving counting statistic properties of X-ray image, while reducing bit width of an image.
|
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`.
|
`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
|
`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,
|
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.
|
EIGER) per image, on the assembled image, to the same definition.
|
||||||
|
|
||||||
### 4.6 `/entry/profiling` — per-image timing
|
### 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
|
[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.
|
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 30 - module edge (only for PSI systems)
|
||||||
|
|
||||||
Bit 31 - chip edge interpolated pixel (multipixel)
|
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 |
|
| 0 | module gap | yes | yes | yes | yes |
|
||||||
| 1–4 | error, noisy | 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 |
|
| 30 | module edge | yes | **no** | **no** | yes |
|
||||||
| 31 | chip edge | yes | yes | **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();
|
std::vector<uint32_t> m = pixel_mask_.GetMask();
|
||||||
for (size_t i = 0; i < m.size(); i++)
|
for (size_t i = 0; i < m.size(); i++)
|
||||||
if (config_.inactive_pixels[i] != 0)
|
if (config_.inactive_pixels[i] != 0)
|
||||||
m[i] &= ~(1u << PixelMask::ErrorPixelBit);
|
m[i] &= ~((1u << PixelMask::ErrorPixelBit) | (1u << PixelMask::InactivePixelBit));
|
||||||
beam_stop_mask = PixelMask(m);
|
beam_stop_mask = PixelMask(m);
|
||||||
}
|
}
|
||||||
std::optional<ShadowFinder> finder;
|
std::optional<ShadowFinder> finder;
|
||||||
|
|||||||
@@ -99,24 +99,29 @@ TEST_CASE("ImagePreprocessorCPU_PixelSum_NoOverflow", "[ImagePreprocessor]") {
|
|||||||
CHECK(stats.pixel_sum > INT32_MAX);
|
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]") {
|
TEST_CASE("ImagePreprocessorCPU_PixelSum_RunMask", "[ImagePreprocessor]") {
|
||||||
DiffractionExperiment x(DetDECTRIS(64, 4, "Test", {}).BitDepthImage(32));
|
DiffractionExperiment x(DetDECTRIS(64, 4, "Test", {}).BitDepthImage(32));
|
||||||
x.PixelSigned(true);
|
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
|
user_mask[1] = 1; // the device's
|
||||||
shadow[1] = 1; // and the run's: the device mask decides
|
shadow[1] = 1; // and the run's: the device mask decides
|
||||||
shadow[2] = 1;
|
shadow[2] = 1;
|
||||||
shadow[3] = 1; // error pixel under the shadow
|
shadow[3] = 1; // error pixel under the shadow
|
||||||
hot[4] = 1;
|
hot[4] = 1;
|
||||||
hot[5] = 1; // saturated pixel, hot
|
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);
|
PixelMask device(x);
|
||||||
device.LoadUserMask(x, user_mask);
|
device.LoadUserMask(x, user_mask);
|
||||||
PixelMask with_run = device;
|
PixelMask with_run = device;
|
||||||
with_run.LoadBeamStopMask(x, shadow);
|
with_run.LoadBeamStopMask(x, shadow);
|
||||||
with_run.LoadHotPixelMask(x, hot);
|
with_run.LoadHotPixelMask(x, hot);
|
||||||
|
with_run.AddDeadPixelMask(x, inactive);
|
||||||
|
|
||||||
std::vector<int32_t> image(x.GetPixelsNum(), 0);
|
std::vector<int32_t> image(x.GetPixelsNum(), 0);
|
||||||
image[0] = 11;
|
image[0] = 11;
|
||||||
@@ -125,18 +130,21 @@ TEST_CASE("ImagePreprocessorCPU_PixelSum_RunMask", "[ImagePreprocessor]") {
|
|||||||
image[3] = INT32_MIN;
|
image[3] = INT32_MIN;
|
||||||
image[4] = -33;
|
image[4] = -33;
|
||||||
image[5] = INT32_MAX;
|
image[5] = INT32_MAX;
|
||||||
|
image[6] = 44;
|
||||||
|
image[7] = INT32_MIN;
|
||||||
const auto input = reinterpret_cast<const uint8_t *>(image.data());
|
const auto input = reinterpret_cast<const uint8_t *>(image.data());
|
||||||
|
|
||||||
ImagePreprocessorBuffer buffer(x.GetPixelsNum());
|
ImagePreprocessorBuffer buffer(x.GetPixelsNum());
|
||||||
ImagePreprocessorCPU device_only(x, device);
|
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);
|
ImagePreprocessorCPU run(x, with_run);
|
||||||
const ImageStatistics stats = run.Analyze(buffer, input, CompressedImageMode::Int32);
|
const ImageStatistics stats = run.Analyze(buffer, input, CompressedImageMode::Int32);
|
||||||
CHECK(stats.pixel_sum == 11 + 22 - 33);
|
CHECK(stats.pixel_sum == 11 + 22 - 33 + 44);
|
||||||
CHECK(stats.masked_pixel_count == 5);
|
CHECK(stats.masked_pixel_count == 7);
|
||||||
CHECK(stats.max_value == 11);
|
CHECK(stats.max_value == 11);
|
||||||
CHECK(buffer[2] == INT32_MIN);
|
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.
|
// 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);
|
PixelMask mask(x);
|
||||||
mask.LoadUserMask(x, user_mask);
|
mask.LoadUserMask(x, user_mask);
|
||||||
mask.LoadBeamStopMask(x, shadow);
|
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.
|
// Mostly near the limit, so the sum needs far more than 32 bits; negative pixels and markers among them.
|
||||||
std::mt19937 rng(11);
|
std::mt19937 rng(11);
|
||||||
|
|||||||
@@ -4933,8 +4933,8 @@ TEST_CASE("RugnuxMeasureGain_ConstantPixels", "[portable]") {
|
|||||||
|
|
||||||
reader.AddDeadPixelMask(g.constant);
|
reader.AddDeadPixelMask(g.constant);
|
||||||
const auto &mask = reader.GetDataset()->pixel_mask->GetMask();
|
const auto &mask = reader.GetDataset()->pixel_mask->GetMask();
|
||||||
CHECK(mask[0] == (1u << PixelMask::ErrorPixelBit));
|
CHECK(mask[0] == (1u << PixelMask::InactivePixelBit));
|
||||||
CHECK(mask[5 * f.nx + 10] == (1u << PixelMask::ErrorPixelBit));
|
CHECK(mask[5 * f.nx + 10] == (1u << PixelMask::InactivePixelBit));
|
||||||
CHECK(mask[5 * f.nx + 11] == 0);
|
CHECK(mask[5 * f.nx + 11] == 0);
|
||||||
reader.Close();
|
reader.Close();
|
||||||
for (int frame = 1; frame <= frames; frame++) {
|
for (int frame = 1; frame <= frames; frame++) {
|
||||||
|
|||||||
Reference in new issue
Block a user