// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #pragma once #include #include // Where the frames of a one-file-per-image sweep sit on the spindle. // // A deposited series of CBF / marCCD / SMV files is not always complete: frames go missing between // the beamline and the archive, and the numbers that are left are scattered through the range. Every // one of these formats writes each image's own start angle in its own header, so the sweep is fully // recoverable - but only if the frames are placed at the angles they state rather than laid out // end to end. Reading a gapped series as contiguous compresses the sweep: one deposited 179.8 degree // series of 1108 files out of 1800 came out as 111 degrees, and every frame past the first gap was // analysed at the wrong spindle angle. // // So the sweep is a grid of SLOTS, one per rotation step over the range the headers span, and a frame // occupies the slot its own angle puts it in. A slot with no file is a missing image, not a compressed // out one: the reader hands it back as "nothing to read", which every image loop already handles, and // the goniometer's start + increment * image_number is then the true angle of every image. namespace sweep { // What one frame's own header says about where it sits and how the instrument stood while it was // taken. The last four are not used to place the frame - they are what the whole series has to agree // on for it to be one sweep at all. struct Frame { std::string path; double angle_deg = 0; double increment_deg = 0; double distance_m = 0; double beam_x_px = 0; double beam_y_px = 0; double wavelength_A = 0; }; struct Layout { std::vector files; // one entry per slot; empty where the series has no frame double start_deg = 0; // angle of slot 0 double increment_deg = 0; // one slot, signed with the direction the sweep turns size_t present = 0; // files that are really there (files.size() - the gaps) }; // Place the frames on the sweep they describe. frames must be in collection order (the order the // file names sort in) and must not be empty. // // Throws JFJochException, naming the frames, when the series is not one sweep: when the headers // disagree about the instrument, when two frames claim the same angle, or when the angles do not sit // on a single rotation step - a folder of screening images taken at scattered angles is not a sweep, // and silently averaging it into one is how such a set comes out as an indexing failure instead of a // clear refusal. // // A series that does not turn at all - one angle repeated, a grid scan or a set of stills - is left // exactly as it came, one slot per file, with the header's nominal increment. // // logger_name is the reader's own logger, so the gap report names the format the user passed. Layout Place(const std::vector &frames, const std::string &logger_name); } // namespace sweep