marCCD frames are widened on the device
The marCCD reader read each frame's 16-bit scanlines into scratch and widened them to int32 on the worker thread (marccd::ReadInto, ~20 % of the image-loop worker CPU on 4nwv), and the 4-byte image was uploaded. ReadRawImage now reads the scanlines straight into the image's buffer and hands them over as a new read-only CompressionAlgorithm::UINT16_WIDENED (mode Int32): two bytes a pixel cross PCIe, from page-locked memory, and the preprocessing kernel widens them itself. It is not CompressedImageMode::Uint16, which calls 65535 an error pixel; a marCCD's 65535 is its saturation marker. So the kernel compares each pixel with int32's error marker and the int32 saturation cap - the comparisons are made on the pixel's value, in int64, rather than in the storage type - and decides every pixel exactly as for the int32 image the host would have widened it to. The host widening (JFJochDecompressPtr) is the CPU path. Test: ImagePreprocessorGPU_StoredEncodingsMatchCPU, with a frame carrying 65535 at a saturation of 65535, against the CPU preprocessor on the host-widened image. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EizBKhTqrAYago9KJG3dA3
This commit is contained in:
@@ -203,7 +203,18 @@ bool JFJochMarCCDReader::HasImage(int64_t image_number) const {
|
||||
bool JFJochMarCCDReader::ReadRawImage(int64_t image_number, JFJochReaderRawImage &image) {
|
||||
if (!HasImage(image_number))
|
||||
return false;
|
||||
image.image = DecodeInto(image_number, image.image_buffer, image.read_buffer);
|
||||
// The pixels as the file stores them, two bytes each: the consumer widens them, on the device where
|
||||
// there is one, so the host neither widens nor uploads a four-byte image.
|
||||
const size_t npixel = static_cast<size_t>(header0_.nx) * static_cast<size_t>(header0_.ny);
|
||||
image.image_buffer.resize(npixel * sizeof(uint16_t));
|
||||
const auto h = marccd::ReadRaw(files_[image_number], reinterpret_cast<uint16_t *>(image.image_buffer.data()),
|
||||
npixel);
|
||||
if (h.nx != header0_.nx || h.ny != header0_.ny)
|
||||
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
||||
"marCCD image size differs from the first image of the sweep");
|
||||
image.image = CompressedImage(image.image_buffer.data(), image.image_buffer.size(),
|
||||
static_cast<size_t>(header0_.nx), static_cast<size_t>(header0_.ny),
|
||||
CompressedImageMode::Int32, CompressionAlgorithm::UINT16_WIDENED);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+15
-4
@@ -299,7 +299,7 @@ Header ReadHeader(const std::string &path) {
|
||||
return Parse(h, path);
|
||||
}
|
||||
|
||||
Header ReadInto(const std::string &path, int32_t *out, size_t capacity, std::vector<uint8_t> &scratch) {
|
||||
Header ReadRaw(const std::string &path, uint16_t *out, size_t capacity) {
|
||||
const Header header = ReadHeader(path);
|
||||
const size_t npixel = static_cast<size_t>(header.nx) * static_cast<size_t>(header.ny);
|
||||
if (npixel > capacity)
|
||||
@@ -331,14 +331,25 @@ Header ReadInto(const std::string &path, int32_t *out, size_t capacity, std::vec
|
||||
if (scanline_bytes != static_cast<size_t>(TIFFScanlineSize(tiff)))
|
||||
throw JFJochException(JFJochExceptionCategory::TIFFGeneratorError,
|
||||
path + ": TIFFScanlineSize mismatch");
|
||||
scratch.resize(scanline_bytes * length);
|
||||
auto *dst = reinterpret_cast<uint8_t *>(out);
|
||||
for (uint32_t row = 0; row < length; row++) {
|
||||
if (TIFFReadScanline(tiff, scratch.data() + static_cast<size_t>(row) * scanline_bytes,
|
||||
row, 0) < 0)
|
||||
if (TIFFReadScanline(tiff, dst + static_cast<size_t>(row) * scanline_bytes, row, 0) < 0)
|
||||
throw JFJochException(JFJochExceptionCategory::TIFFGeneratorError,
|
||||
path + ": TIFFReadScanline error");
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
Header ReadInto(const std::string &path, int32_t *out, size_t capacity, std::vector<uint8_t> &scratch) {
|
||||
const Header header = ReadHeader(path);
|
||||
const size_t npixel = static_cast<size_t>(header.nx) * static_cast<size_t>(header.ny);
|
||||
if (npixel > capacity)
|
||||
throw JFJochException(JFJochExceptionCategory::ArrayOutOfBounds,
|
||||
path + ": image is larger than the buffer given for it");
|
||||
scratch.resize(npixel * sizeof(uint16_t));
|
||||
ReadRaw(path, reinterpret_cast<uint16_t *>(scratch.data()), npixel);
|
||||
|
||||
// The stored pixels are unsigned 16-bit; the rest of the code works in signed 32-bit, so they
|
||||
// are widened here and nothing downstream has to know what the file held.
|
||||
const auto *src = reinterpret_cast<const uint16_t *>(scratch.data());
|
||||
|
||||
@@ -58,6 +58,10 @@ Header ReadHeader(const std::string &path);
|
||||
// Header + pixels of one file. out is resized to nx * ny.
|
||||
Header Read(const std::string &path, std::vector<int32_t> &out);
|
||||
|
||||
// Header + the stored unsigned 16-bit pixels, as the file holds them, into memory the caller already
|
||||
// has. Throws if the image does not fit in capacity pixels.
|
||||
Header ReadRaw(const std::string &path, uint16_t *out, size_t capacity);
|
||||
|
||||
// The same, widening the stored uint16 into memory the caller already has. scratch carries the
|
||||
// 16-bit strip between frames, so a worker walking a sweep allocates once. Throws if the image
|
||||
// does not fit in capacity pixels.
|
||||
|
||||
Reference in New Issue
Block a user