Two more of the formats deposited data actually arrives in, found by processing a
corpus of it: every PETRA III EMBL set is .cbf.gz, and NSRRC and the whole ADSC
Quantum era are SMV. Both were previously "no native input".
SMV is an ASCII "KEY=value;" block between braces, then the pixels - no container,
no compression, nothing to decode by offset - so reader/SMV.{h,cpp} and
JFJochSMVReader are a smaller job than the marCCD pair they sit beside, and need no
new dependency at all. Two things the format does not give us, both said out loud
rather than papered over:
* It states no saturation value, so overloads are judged on the 16-bit container
alone. That can only fail to call a pixel saturated, never condemn a good one,
but a CCD at the top of its range does saturate, so the reader warns once.
* Its beam centre is in MILLIMETRES and which of X/Y is the fast direction is a
convention rather than a rule. Measured on one ALS ADSC sweep the file's value is
TRANSPOSED: as stated it indexes 2/60 frames, and the run's own beam-centre
measurement (which adopts the right one automatically) indexes 60/60. Swapping it
here would fit that writer and might break another, so the header is read as the
format defines it and the measurement stays the arbiter. Revisit with a second
vendor's SMV in hand.
.cbf.gz needed only Slurp() in MiniCBF.cpp, through which every read already passes:
it sniffs the two-byte gzip magic - not the file name - and takes a zlib path when it
is there, leaving the plain path free of zlib's buffer copy. zlib-ng is already in the
build, so this is a link line, not a dependency. The sweep template grew a suffix,
because ".cbf" and ".cbf.gz" are separate sweeps and std::filesystem cannot split the
double extension on its own.
The viewer's single cbf_reader becomes three, dispatched by CanRead() in the same
order as rugnux. Dispatch is by CONTENT in both: ".img" is used by miniCBF, marCCD
AND SMV depending on the writer, and a PDB detector label has now been wrong about
the format four times, so an extension decides nothing.
Measured, de novo, no flags: 9fcg (1800 gzipped frames) gives P4 and a cell 0.06%
from the deposited one at 1.37 A against a deposited 1.54; 6oel (ADSC SMV) gives
F4132 - 96 operations, the most a protein space group can have - and a cell 0.05%
out, 100% indexed. Tests cover both formats and the transposed-beam-centre case with
fixtures written byte for byte, so they need no external data.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
58 lines
2.4 KiB
C++
58 lines
2.4 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "JFJochReader.h"
|
|
#include "SMV.h"
|
|
|
|
// Reads a rotation sweep straight from a directory of SMV files - what ADSC Quantum detectors
|
|
// wrote and what Rayonix and others still write, so most archived CCD data from the 2000s is in
|
|
// this format.
|
|
//
|
|
// The same shape as JFJochMarCCDReader, which it is otherwise a sibling of: the sweep's geometry
|
|
// comes from the first file's header, the rotation angle of each image from its own, and images
|
|
// are decoded on demand. A raw SMV file carries no analysis results, so no spots and no
|
|
// reflections.
|
|
//
|
|
// SMV states no saturation value and marks no untrusted pixels, so nothing is masked and no pixel
|
|
// is called an overload from the file alone - unlike marCCD, which at least carries a saturated
|
|
// value. The beam centre it states is in millimetres and its convention varies between writers;
|
|
// see the note in smv::Header.
|
|
class JFJochSMVReader : public JFJochReader {
|
|
std::vector<std::string> files_;
|
|
std::shared_ptr<JFJochReaderDataset> dataset_;
|
|
smv::Header header0_;
|
|
|
|
bool LoadImage_i(std::shared_ptr<JFJochReaderDataset> &dataset,
|
|
DataMessage &message,
|
|
std::vector<uint8_t> &buffer,
|
|
int64_t image_number,
|
|
bool update_dataset) override;
|
|
|
|
// Decodes one image into the caller's byte buffer and returns the image that points at it. The
|
|
// stored 16-bit strip is read through scratch, which the caller keeps between frames.
|
|
template <class Buffer>
|
|
CompressedImage DecodeInto(int64_t image_number, Buffer &buffer, std::vector<uint8_t> &scratch) const;
|
|
|
|
public:
|
|
~JFJochSMVReader() override = default;
|
|
|
|
// True if the path names something this reader can open: a marCCD file, or a directory holding
|
|
// at least one. Cheap - it reads a few kB at most.
|
|
static bool CanRead(const std::string &path);
|
|
|
|
// path is a directory of marCCD frames, or one frame inside the sweep to take the whole sweep
|
|
// from.
|
|
void ReadFiles(const std::string &path);
|
|
|
|
[[nodiscard]] uint64_t GetNumberOfImages() const override;
|
|
void Close() override;
|
|
|
|
bool ReadRawImage(int64_t image_number, JFJochReaderRawImage &image) override;
|
|
[[nodiscard]] std::vector<SpotToSave> ReadSpots(int64_t image) const override;
|
|
};
|