Files
Jungfraujoch/common/Definitions.h
T
leonarski_fandClaude Opus 5 1f4f77fe42 Choose images_per_file from the acquisition when it is not given
The value means three different things at once. It is the unit of writer
parallelism - whole files go round-robin to the writers,
(image_number / images_per_file) % socket.size() in ZMQStream2Pusher::SendImage
and TCPStreamPusher - it multiplies writer memory linearly, since every data-file
plugin reserves per file, and it decides whether a legacy master is readable at
all, because dxtbx follows only the first data file of one.

That last point is what makes a flat default wrong. Measured with DIALS on a
2500-image rotation sweep written as legacy: split into five files it reports
2500 images and then raises IndexError beyond image 499, so it half-works
silently; in one file all 2500 read. AutoPROC does not read VDS, so legacy has to
stay the default, which leaves the file count as the only lever.

So make it optional and resolve it from the acquisition. A rotation sweep of at
most 20000 images goes into one data file - rotation datasets are small enough,
and one writer keeps up with them. A grid scan splits on whole fast-axis rows, so
a file is a meaningful piece of the grid. Stills and serial keep 1000, where the
image count far exceeds it and the parallelism and the bounded writer memory are
what matter. An explicit value is always taken literally.

GetImagesPerFile is the single place this is resolved, and it must always return
a fixed non-zero number, because everything downstream - receiver, pusher, puller,
writer - requires one. That was already true of the old 0 = "one file" spelling;
0 is now gone from the API and omitting the field says the same thing better.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 21:57:12 +02:00

79 lines
3.4 KiB
C++

// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include "../fpga/pcie_driver/jfjoch_fpga.h"
#include <cstdint>
#include <cstddef>
#include <chrono>
#include <array>
#include <cmath>
constexpr float WVL_1A_IN_KEV = 12.39854f;
constexpr size_t CONVERTED_MODULE_LINES = 514;
constexpr size_t CONVERTED_MODULE_COLS = 1030;
constexpr size_t CONVERTED_MODULE_SIZE = CONVERTED_MODULE_LINES * CONVERTED_MODULE_COLS;
constexpr size_t JUNGFRAU_PACKET_SIZE_BYTES = 8192;
constexpr int MAX_IMAGE_NUMBER = 2*1024*1024;
// Defaults for images_per_file when the acquisition does not ask for a particular number; see
// DiffractionExperiment::GetImagesPerFile.
constexpr int64_t DEFAULT_IMAGES_PER_FILE = 1000;
// Above this, a rotation sweep is split rather than written to one file.
constexpr int64_t ROTATION_SINGLE_FILE_IMAGE_LIMIT = 20000;
constexpr std::chrono::nanoseconds MIN_COUNT_TIME = std::chrono::microseconds(3);
constexpr std::chrono::nanoseconds MIN_STORAGE_CELL_DELAY = std::chrono::nanoseconds(2100);
constexpr std::chrono::nanoseconds MIN_FRAME_TIME_JUNGFRAU_HALF_SPEED = std::chrono::microseconds(1000);
constexpr std::chrono::nanoseconds MIN_FRAME_TIME_JUNGFRAU_FULL_SPEED = std::chrono::microseconds(470);
constexpr std::chrono::nanoseconds MIN_FRAME_TIME_EIGER = std::chrono::microseconds(250);
constexpr std::chrono::nanoseconds MAX_COUNT_TIME_JUNGFRAU = std::chrono::microseconds(2000);
constexpr std::chrono::nanoseconds FRAME_TIME_PEDE_G1G2 = std::chrono::microseconds(10*1000);
constexpr std::chrono::nanoseconds PSI_JUNGFRAU_READOUT_TIME = std::chrono::microseconds(20);
constexpr std::chrono::nanoseconds PSI_EIGER_READOUT_TIME = std::chrono::microseconds(20);
constexpr std::chrono::nanoseconds DARK_MASK_FRAME_TIME = std::chrono::milliseconds(10);
constexpr float MIN_ENERGY_KEV = 0.001;
constexpr float MAX_ENERGY_KEV = 500.0;
constexpr float DEFAULT_G0_FACTOR = 41.0f;
constexpr float DEFAULT_G1_FACTOR = -1.439f;
constexpr float DEFAULT_G2_FACTOR = -0.1145f;
constexpr float DEFAULT_HG0_FACTOR = 100.0f;
constexpr int MAX_SPOT_COUNT = 64 * 1024;
constexpr uint32_t MASK_PEDESTAL_G0_RMS_LIMIT = (1U<<4);
constexpr size_t PEDESTAL_MIN_IMAGE_COUNT = 128;
constexpr uint16_t PEDESTAL_WRONG = (UINT16_MAX);
constexpr size_t PEDESTAL_G0_WRONG_GAIN_ALLOWED_COUNT = 2;
constexpr size_t MESSAGE_SIZE_FOR_START_END = (256*1024*1024); // pessimistic highest value
constexpr float LAB6_CELL_A = 4.156468f;
// Ice ring resolution taken from:
// Moreau, Atakisi, Thorne, Acta Cryst D77, 2021, 540,554
// https://journals.iucr.org/d/issues/2021/04/00/tz5104/index.html
constexpr std::array<float, 11> ICE_RING_RES_A = {3.895, 3.661, 3.438, 2.667, 2.249, 2.068, 1.947, 1.916, 1.882, 1.719, 1.522};
// True when resolution d (Angstrom) sits within half_width of a hexagonal-ice powder ring, in the
// q = 2*pi/d units the spot-finder uses (ice_ring_width_Q_recipA). Used to drop ice-contaminated
// reflections from scaling/merging when ice-ring handling is enabled.
inline bool IsOnIceRing(float d_A, float half_width_q_recipA) {
if (!(d_A > 0.0f))
return false;
constexpr float two_pi = 6.283185307f;
const float q = two_pi / d_A;
for (const float ice_d : ICE_RING_RES_A)
if (std::fabs(q - two_pi / ice_d) < half_width_q_recipA)
return true;
return false;
}