Files
Jungfraujoch/reader/XTC.cpp
T
leonarski_fandClaude Opus 5.5 ae4786d14f WIP: LCLS-I XTC reader for single-panel camera stills (Rayonix), geometry from a DIALS .expt
Not yet compiled or tested: work stopped after the feasibility study.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:10:10 +02:00

150 lines
5.4 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "XTC.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <memory>
#include "../common/JFJochException.h"
namespace {
constexpr size_t DGRAM_HEADER = 40; // clock time 8, timestamp 8, env 4, Xtc header 20
constexpr size_t XTC_HEADER = 20;
constexpr size_t FRAME_HEADER = 16; // Camera::FrameV1: width, height, depth, offset
constexpr uint32_t ID_XTC = 1;
constexpr uint32_t ID_FRAME = 2;
constexpr uint32_t TRANSITION_L1ACCEPT = 12;
constexpr uint32_t TRANSITION_LAST = 12;
struct FileCloser {
void operator()(FILE *f) const { if (f) fclose(f); }
};
using File = std::unique_ptr<FILE, FileCloser>;
File Open(const std::string &path) {
File f(fopen(path.c_str(), "rb"));
if (!f)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Cannot open " + path);
return f;
}
uint32_t Word(const uint8_t *p, size_t i) {
uint32_t v;
memcpy(&v, p + 4 * i, 4);
return v;
}
// The transition a datagram header names, from the top bits of its timestamp's first word.
uint32_t Transition(const uint8_t *dgram) {
return (Word(dgram, 2) >> 24) & 0xf;
}
// A first datagram is a Configure with a container as its outermost Xtc, whose extent covers at
// least its own header.
bool PlausibleFirstDatagram(const uint8_t *h) {
return Transition(h) <= TRANSITION_LAST && (Word(h, 8) & 0xffff) == ID_XTC && Word(h, 9) >= XTC_HEADER;
}
// Walks the Xtcs in [begin, end) of the file, looking for camera frames; the largest found is kept.
void FindFrame(FILE *f, uint64_t begin, uint64_t end, xtc::Frame &best) {
uint64_t pos = begin;
while (pos + XTC_HEADER <= end) {
uint8_t h[XTC_HEADER];
if (fseeko(f, static_cast<off_t>(pos), SEEK_SET) != 0 || fread(h, 1, XTC_HEADER, f) != XTC_HEADER)
return;
const uint32_t type = Word(h, 3) & 0xffff;
const uint32_t extent = Word(h, 4);
if (extent < XTC_HEADER || pos + extent > end)
return;
if (type == ID_XTC)
FindFrame(f, pos + XTC_HEADER, pos + extent, best);
else if (type == ID_FRAME && extent >= XTC_HEADER + FRAME_HEADER) {
uint8_t fh[FRAME_HEADER];
if (fread(fh, 1, FRAME_HEADER, f) == FRAME_HEADER) {
const uint32_t width = Word(fh, 0), height = Word(fh, 1), depth = Word(fh, 2);
const uint64_t npixel = static_cast<uint64_t>(width) * height;
if (depth > 8 && depth <= 16 && XTC_HEADER + FRAME_HEADER + 2 * npixel <= extent
&& npixel > static_cast<uint64_t>(best.width) * best.height)
best = {"", pos + XTC_HEADER + FRAME_HEADER, width, height};
}
}
pos += extent;
}
}
} // namespace
namespace xtc {
bool CanRead(const std::string &path) {
std::error_code ec;
if (std::filesystem::is_directory(path, ec))
return !CollectStreams(path).empty();
if (!std::filesystem::is_regular_file(path, ec) || !path.ends_with(".xtc") || path.ends_with(".smd.xtc"))
return false;
File f(fopen(path.c_str(), "rb"));
uint8_t h[DGRAM_HEADER];
return f && fread(h, 1, DGRAM_HEADER, f.get()) == DGRAM_HEADER && PlausibleFirstDatagram(h);
}
std::vector<std::string> CollectStreams(const std::string &path) {
std::error_code ec;
if (!std::filesystem::is_directory(path, ec))
return {path};
std::vector<std::string> out;
for (const auto &e: std::filesystem::directory_iterator(path, ec)) {
const std::string p = e.path().string();
if (e.is_regular_file() && p.ends_with(".xtc") && !p.ends_with(".smd.xtc"))
out.push_back(p);
}
std::sort(out.begin(), out.end());
return out;
}
std::vector<Frame> IndexFrames(const std::string &file) {
File f = Open(file);
std::vector<Frame> out;
uint64_t pos = 0;
uint8_t h[DGRAM_HEADER];
while (fseeko(f.get(), static_cast<off_t>(pos), SEEK_SET) == 0
&& fread(h, 1, DGRAM_HEADER, f.get()) == DGRAM_HEADER) {
const uint32_t extent = Word(h, 9);
if (extent < XTC_HEADER)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
file + ": a datagram's extent is shorter than its own header");
const uint64_t payload = pos + DGRAM_HEADER, end = pos + DGRAM_HEADER - XTC_HEADER + extent;
if (Transition(h) == TRANSITION_L1ACCEPT) {
Frame frame;
FindFrame(f.get(), payload, end, frame);
if (frame.width > 0) {
frame.file = file;
out.push_back(frame);
}
}
pos = end;
}
return out;
}
void ReadFrame(const Frame &frame, int32_t *out, std::vector<uint8_t> &scratch) {
const size_t npixel = static_cast<size_t>(frame.width) * frame.height;
scratch.resize(2 * npixel);
File f = Open(frame.file);
if (fseeko(f.get(), static_cast<off_t>(frame.offset), SEEK_SET) != 0
|| fread(scratch.data(), 1, scratch.size(), f.get()) != scratch.size())
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
frame.file + ": an image ends past the end of the file");
for (size_t i = 0; i < npixel; i++) {
uint16_t v;
memcpy(&v, scratch.data() + 2 * i, 2);
out[i] = v;
}
}
} // namespace xtc