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>
This commit is contained in:
1 parent
acf6b3604f
commit
ae4786d14f
6 files changed
+455
-3
No files matched your search
+149
@@ -0,0 +1,149 @@
|
||||
// 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
|
||||
Reference in new issue
Block a user