reader: take the stored image bit depth from the file, not the DECTRIS default
DetectorSetup hardcodes bit_depth_image = 16 for every DECTRIS detector (DetectorSetup.cpp:78), and GetByteDepthImage() consults that BEFORE the depth the reader takes from the file - so a file storing 32-bit images had its overflow computed as a 16-bit one. With the reader also declaring the images signed, GetOverflow() returned INT16_MAX and GetSaturationLimit() became min(file value, 32767). Every count above 32767 was therefore marked saturated, and because the integration accept gate requires ALL inner pixels valid, the whole reflection was discarded. That silently removes the strongest reflections of a strong crystal - the low-resolution ones that anchor scaling - while the file itself declares saturation at 105000-133000. Measured on a lysozyme rotation set (200 frames), before -> after: saturated pixels per frame 0.815 -> 0.000 brightest accepted pixel 32738 -> 87633 mean per-frame maximum 26218 -> 37390 i.e. the ceiling was exactly INT16_MAX and nothing genuine reached it. Which datasets this touches depends on how bright they are: measured pixels above the old ceiling range from 0.0 per frame on some rotation sets to 6.1 on others, so the fix is a no-op on weak data and only ever adds reflections. Rotation battery, 33 crystals: no point group changed (30/33 before and after) and no run failed. Four crystals move on quality, in both directions - ISa 1.90 -> 2.40 and 3.29 -> 4.80 on two, 2.97 -> 1.85 and 20.83 -> 18.47 on two others; three of the four are the battery's known weak or run-to-run-unstable crystals. The one strong crystal that moves gains 136 observations out of 1.9 million and loses 2.4 ISa: the reflections restored are by construction the brightest ones, and they carry the systematic error that the strongest reflections always carry. That is a real cost, but it is the cost of MEASURING them rather than discarding them unseen, and a lower asymptotic I/sigma on data that are now complete is preferable to a flattering one on data that quietly are not. Only the offline file reader is affected; the online path builds its detector setup from configuration. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -661,6 +661,15 @@ HDF5MetadataSource::OpenResult HDF5MetadataSource::Open(const std::string &filen
|
||||
if (master_file->Exists("/entry/instrument/detector/sensor_material"))
|
||||
detector.SensorMaterial(master_file->GetString("/entry/instrument/detector/sensor_material"));
|
||||
detector.SaturationLimit(master_file->GetInt("/entry/instrument/detector/saturation_value"));
|
||||
// The container depth of the stored images, NOT the detector's counter depth. DetectorSetup
|
||||
// defaults DECTRIS to 16 bits, and GetByteDepthImage() prefers that over the value the image
|
||||
// format carries - so without this an EIGER2 file that stores 32-bit images has its overflow
|
||||
// computed as a 16-bit one and every count above 32767 is called saturated. The integration
|
||||
// accept gate then drops the WHOLE reflection, which silently removes the strongest
|
||||
// reflections of a strong crystal (measured on a lysozyme set: max accepted pixel 32738
|
||||
// against a declared saturation of 108833).
|
||||
if (master_file->Exists("/entry/instrument/detector/bit_depth_image"))
|
||||
detector.BitDepthImage(master_file->GetInt("/entry/instrument/detector/bit_depth_image"));
|
||||
detector.MinFrameTime(std::chrono::microseconds(0));
|
||||
detector.MinCountTime(std::chrono::microseconds(0));
|
||||
detector.ReadOutTime(std::chrono::nanoseconds(0));
|
||||
|
||||
Reference in New Issue
Block a user