Files
Jungfraujoch/common/DiffractionExperiment.cpp
T
leonarski_fandClaude Opus 5 df9a9c2a2c
Build Packages / build:viewer-tgz:cpu (push) Successful in 19m32s
Build Packages / build:windows:nocuda (push) Successful in 19m57s
Build Packages / build:viewer-tgz:cuda (push) Successful in 22m45s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 22m38s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 23m24s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 28m8s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 28m9s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 28m18s
Build Packages / XDS test (durin plugin) (push) Successful in 11m10s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 20m21s
Build Packages / build:windows:cuda (push) Successful in 22m5s
Build Packages / build:rpm (rocky9) (push) Successful in 20m57s
Build Packages / Generate python client (push) Successful in 34s
Build Packages / Build documentation (push) Successful in 1m29s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (ubuntu2204) (push) Successful in 25m41s
Build Packages / DIALS test (push) Successful in 21m19s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 21m34s
Build Packages / build:rpm (rocky8) (push) Successful in 27m4s
Build Packages / XDS test (neggia plugin) (push) Successful in 10m19s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 10m58s
Build Packages / Unit tests (push) Successful in 1h17m36s
Fix the defects found reviewing the branch before merge
Image buffer: the per-image CBOR metadata headroom had been re-derived from the
online reflection cap alone, which cut it from 4 MiB to 2.55 MB while the measured
worst case - reflections plus the capped spot list plus the three azimuthal arrays -
is 2.9 MB, so the receiver dropped the frames with the most to say. Restore it and
give it a name that both the code and its guard test read: written down twice, the
two had drifted and the test kept passing against the value the code had left.

Spot finding: an unset low_resolution_limit means no limit at that end, as an unset
high_resolution_limit already did. An optional rather than a zero sentinel, because
zero is not a natural "no limit" here - every pixel lies above it, so the plain
comparison masked the whole image instead of none of it, and nothing validated the
zero. The API field is no longer required; a zero is folded into the unset case at
the boundary, where older clients still send it, so one spelling reaches the
analysis code. The FPGA takes its fixed-point ceiling instead, since ap_ufixed<16,9>
wraps above 512 A and would have masked everything.

image_preprocessing: check the CUDA calls on the fused decode path - the one new GPU
file with none, and the path fed by bytes we did not produce. An unchecked
synchronise returned the host-written sentinel as if it were a measurement, so the
decode looked successful and the fallback to the host decoder never fired.

rugnux: --stride no longer writes one past the end of the per-image arrays, whose
count floored where the worker loop ceils, and the written process file links the
images actually processed rather than the first N - each frame's picture now sits
next to its own analysis.

Powder calibration: the face-centred calibrants no longer list their systematically
absent rings, so the distance fit starts from a reflection that exists rather than
an extinct one; the triclinic calibrant covers both signs of h and k instead of a
single octant, which is only valid for a diagonal metric. The test asserted the old
behaviour - one ring formula for every cubic standard - and is rewritten.

CBOR: skip an unknown tagged value in the end block, as the other four blocks
already do. One advance lands on the tagged item rather than past it, so an older
reader fed a newer end message threw and never finalized its file.

Viewer: a settings value the setter rejects no longer escapes as an uncaught throw
from a worker slot, and the field offers only what the setter accepts.

Space-group search: judge stage B on the same "present" cut stage A already computes.
Merged sigma is floored so no reflection reads above ISa, so on a low-ISa merge the
fixed cut left both stage B tests unsatisfiable - every screw axis passed unchallenged
and the centering rescue switched itself off on exactly the weak data it exists for.
Where the fixed cut is the smaller of the two they are equal and this is inert: over
the 37-crystal rotation battery every crystal reports the identical space group and
identical merge statistics, so it is a no-op there and the low-ISa case it targets
remains unmeasured.

rugnux: --polarization reaches --mode azint, which parsed the flag and then dropped
it; that mode also applies the same polarization default as every other mode.

Acknowledge the ACTS/traccc project, whose sparse connected-component labelling both
spot extractors take their algorithm from, with its citation and its license.

The rc.161 change list is brought back to one line per entry, and the user-visible
changes that were missing from it added.

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

1747 lines
59 KiB
C++

// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include <algorithm>
#include <cmath>
#include <utility>
#include "NetworkAddressConvert.h"
#include "JFJochCompressor.h" // For ZSTD_USE_JFJOCH_RLE
#include "DiffractionExperiment.h"
#include "CUDAWrapper.h"
#include "JFJochException.h"
#include "RawToConvertedGeometry.h"
#include "../include/spdlog/fmt/fmt.h"
#include "GitInfo.h"
using namespace std::literals::chrono_literals;
#define check_max(param, val, max) if ((val) > (max)) throw JFJochException(JFJochExceptionCategory::InputParameterAboveMax, param)
#define check_min(param, val, min) if ((val) < (min)) throw JFJochException(JFJochExceptionCategory::InputParameterBelowMin, param)
#define check_finite(param, val) if (!std::isfinite(val)) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, param)
DiffractionExperiment::DiffractionExperiment() : DiffractionExperiment(DetJF4M()) {}
DiffractionExperiment::DiffractionExperiment(const DetectorSetup& det_setup)
: detector(det_setup) {
ndatastreams = 1;
series_id = 0;
mode = DetectorMode::Standard;
image_format_settings.Conv();
summation = 1;
cpu_summation = false;
}
// setter functions
DiffractionExperiment &DiffractionExperiment::Detector(const DetectorSetup &input) {
detector = input;
// Conversion is always default mode after switching detectors
mode = DetectorMode::Standard;
auto settings = detector.GetDefaultSettings();
if (settings)
detector_settings = *settings;
return *this;
}
DiffractionExperiment &DiffractionExperiment::Mode(DetectorMode input) {
// Handle allowed mode settings
switch (GetDetectorType()) {
case DetectorType::JUNGFRAU:
if (input == DetectorMode::DarkMask)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Dark mask is not supported for PSI JUNGFRAU detector");
break;
case DetectorType::DECTRIS:
if ((input == DetectorMode::PedestalG0) || (input == DetectorMode::PedestalG1) || (input == DetectorMode::PedestalG2))
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Pedestal data collection is not supported for DECTRIS detector");
break;
case DetectorType::EIGER:
if ((input == DetectorMode::PedestalG0) || (input == DetectorMode::PedestalG1) || (input == DetectorMode::PedestalG2) || (input == DetectorMode::DarkMask))
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Pedestal or dark mask data collection is not supported for PSI EIGER detector");
break;
}
mode = input;
return *this;
}
DiffractionExperiment &DiffractionExperiment::DataStreams(int64_t input) {
check_max("Number of data streams", input, 16);
check_min("Number of data streams", input, 1);
ndatastreams = input;
return *this;
}
DiffractionExperiment &DiffractionExperiment::ImagesPerTrigger(int64_t input) {
dataset.ImagesPerTrigger(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::NumTriggers(int64_t input) {
dataset.NumTriggers(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::FrameTime(std::chrono::nanoseconds in_frame_time,
std::chrono::nanoseconds in_count_time) {
DetectorSettings tmp = detector_settings;
if (in_count_time.count() == 0)
tmp.FrameTime(in_frame_time);
else
tmp.FrameTime(in_frame_time, in_count_time);
ImportDetectorSettings(tmp);
return *this;
}
DiffractionExperiment &DiffractionExperiment::PedestalG0Frames(int64_t input) {
detector_settings.PedestalG0Frames(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::PedestalG1Frames(int64_t input) {
detector_settings.PedestalG1Frames(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::PedestalG2Frames(int64_t input) {
detector_settings.PedestalG2Frames(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::IncidentEnergy_keV(float input) {
dataset.PhotonEnergy_keV(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::BeamX_pxl(float input) {
dataset.BeamX_pxl(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::BeamY_pxl(float input) {
dataset.BeamY_pxl(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::DetectorDistance_mm(float input) {
dataset.DetectorDistance_mm(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::FilePrefix(std::string input) {
dataset.FilePrefix(std::move(input));
return *this;
}
DiffractionExperiment &DiffractionExperiment::FilePrefixTrusted(std::string input) {
dataset.FilePrefixTrusted(std::move(input));
return *this;
}
DiffractionExperiment &DiffractionExperiment::UseInternalPacketGenerator(bool input) {
detector_settings.InternalGeneratorEnable(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::MaskModuleEdges(bool input) {
image_format_settings.MaskModuleEdges(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::Compression(CompressionAlgorithm input) {
dataset.Compression(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::MaskChipEdges(bool input) {
image_format_settings.MaskChipEdges(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::QRangeForAzimInt_recipA(float low, std::optional<float> high) {
az_integration_settings.QRange_recipA(low, high);
return *this;
}
DiffractionExperiment& DiffractionExperiment::BkgEstimateQRange_recipA(float low, float high) {
az_integration_settings.BkgEstimateQRange_recipA(low, high);
return *this;
}
DiffractionExperiment& DiffractionExperiment::QSpacingForAzimInt_recipA(float input) {
az_integration_settings.QSpacing_recipA(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::SetUnitCell(const std::optional<UnitCell> &cell) {
dataset.SetUnitCell(cell);
return *this;
}
DiffractionExperiment &DiffractionExperiment::ZMQPreviewPeriod(const std::optional<std::chrono::microseconds> &input) {
if (input.has_value()) {
check_min("Preview image generation period [ms]", input.value().count(), 0);
}
zmq_preview_period = input;
return *this;
}
DiffractionExperiment &DiffractionExperiment::SpaceGroupNumber(std::optional<int64_t> input) {
dataset.SpaceGroupNumber(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::StorageCells(int64_t input) {
detector_settings.StorageCells(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::StorageCellStart(int64_t input) {
detector_settings.StorageCellStart(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::SampleName(const std::string &input) {
dataset.SampleName(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::OverwriteExistingFiles(bool input) {
file_writer.OverwriteExistingFiles(input);
return *this;
}
// getter functions
int64_t DiffractionExperiment::GetNumTriggers() const {
if (GetDetectorMode() != DetectorMode::Standard)
// For pedestal and dark mask modes
return 1;
else if (IsPulsedSource())
// For pulsed source summation happens over multiple triggers
return dataset.GetNumTriggers() * GetSummation();
else
return dataset.GetNumTriggers();
}
DetectorMode DiffractionExperiment::GetDetectorMode() const {
return mode;
}
std::chrono::nanoseconds DiffractionExperiment::GetFrameTime() const {
switch (GetDetectorMode()) {
case DetectorMode::DarkMask:
return GetDarkMaskSettings().GetFrameTime();
case DetectorMode::PedestalG1:
case DetectorMode::PedestalG2:
return detector_settings.GetFrameTimePedestalG1G2();
default:
if ((GetDetectorType() != DetectorType::JUNGFRAU) && dataset.GetImageTime().has_value())
return dataset.GetImageTime().value();
return detector_settings.GetFrameTime();
}
}
std::chrono::nanoseconds DiffractionExperiment::GetDetectorPeriod() const {
// Without storage cells - this is just frame time
if (GetStorageCellNumber() == 1)
return GetFrameTime();
// With storage cells
// Do 100 Hz repetition for pedestal G1/G2
// Do 2 kHz repetition for conversion/raw/pedestal G0
switch (GetDetectorMode()) {
case DetectorMode::PedestalG1:
case DetectorMode::PedestalG2:
return detector_settings.GetFrameTimePedestalG1G2();
default:
return MIN_FRAME_TIME_JUNGFRAU_FULL_SPEED * GetStorageCellNumber();
}
}
std::chrono::nanoseconds DiffractionExperiment::GetImageTime() const {
switch (GetDetectorMode()) {
case DetectorMode::PedestalG1:
case DetectorMode::PedestalG2:
return detector_settings.GetFrameTimePedestalG1G2();
default:
return GetFrameTime() * GetSummation();
}
}
int64_t DiffractionExperiment::GetImageNum() const {
if (GetDetectorMode() == DetectorMode::Standard)
return GetFrameNum() / GetSummation();
return 0;
}
int64_t DiffractionExperiment::GetFrameNum() const {
return GetFrameNumPerTrigger() * GetNumTriggers();
}
int64_t DiffractionExperiment::GetFrameNumPerTrigger() const {
switch (GetDetectorMode()) {
case DetectorMode::DarkMask:
return GetDarkMaskSettings().GetNumberOfFrames();
case DetectorMode::PedestalG0:
return GetPedestalG0Frames() * GetStorageCellNumber();
case DetectorMode::PedestalG1:
return GetPedestalG1Frames() * GetStorageCellNumber();
case DetectorMode::PedestalG2:
return GetPedestalG2Frames()* GetStorageCellNumber();
default:
if (GetStorageCellNumber() > 1)
return GetStorageCellNumber();
else if (IsPulsedSource())
return 1;
else
return dataset.GetImageNumPerTrigger() * GetSummation();
}
}
std::chrono::nanoseconds DiffractionExperiment::GetFrameCountTime() const {
if ((GetDetectorType() != DetectorType::JUNGFRAU)
&& dataset.GetImageTime().has_value()
&& !detector_settings.GetCountTime().has_value())
return std::chrono::duration_cast<std::chrono::nanoseconds>(dataset.GetImageTime().value())
- detector.GetReadOutTime();
if (detector_settings.GetCountTime().has_value())
return detector_settings.GetCountTime().value();
return std::chrono::duration_cast<std::chrono::nanoseconds>(detector_settings.GetFrameTime())
- detector.GetReadOutTime();
}
bool DiffractionExperiment::GetFrameCountTimeAuto() const {
return !detector_settings.GetCountTime().has_value();
}
std::chrono::nanoseconds DiffractionExperiment::GetImageCountTime() const {
return GetFrameCountTime() * GetSummation();
}
int64_t DiffractionExperiment::GetPedestalG0Frames() const {
if (GetDetectorType() != DetectorType::JUNGFRAU)
return 0;
return detector_settings.GetPedestalG0Frames();
}
int64_t DiffractionExperiment::GetPedestalG1Frames() const {
if (GetDetectorType() != DetectorType::JUNGFRAU)
return 0;
return detector_settings.GetPedestalG1Frames();
}
int64_t DiffractionExperiment::GetPedestalG2Frames() const {
if (GetDetectorType() != DetectorType::JUNGFRAU)
return 0;
return detector_settings.GetPedestalG2Frames();
}
float DiffractionExperiment::GetIncidentEnergy_keV() const {
return dataset.GetPhotonEnergy_keV();
}
float DiffractionExperiment::GetWavelength_A() const {
if (instrument.IsElectronSource()) {
const double hc = WVL_1A_IN_KEV;
const double me_c2 = 511; // in keV
return static_cast<float>(hc / sqrt(dataset.GetPhotonEnergy_keV() * (dataset.GetPhotonEnergy_keV() + 2 * me_c2)));
}
return WVL_1A_IN_KEV / dataset.GetPhotonEnergy_keV();
}
float DiffractionExperiment::GetBeamX_pxl() const {
return dataset.GetBeamX_pxl();
}
float DiffractionExperiment::GetBeamY_pxl() const {
return dataset.GetBeamY_pxl();
}
float DiffractionExperiment::GetDetectorDistance_mm() const {
return dataset.GetDetectorDistance_mm();
}
Coord DiffractionExperiment::GetScatteringVector() const {
return dataset.GetScatteringVector();
}
std::string DiffractionExperiment::GetFilePrefix() const {
return dataset.GetFilePrefix();
}
CompressionAlgorithm DiffractionExperiment::GetCompressionAlgorithm() const {
return dataset.GetCompressionAlgorithm();
}
int64_t DiffractionExperiment::GetByteDepthImage() const {
if (detector.GetBitDepthImage())
return detector.GetBitDepthImage().value() / 8;
if (IsCPUSummation())
return 4;
auto bit_depth_image = image_format_settings.GetBitDepthImage();
if (!bit_depth_image.has_value()) {
if (GetBitDepthReadout() == 32)
return 4;
if (GetBitDepthReadout() == 8)
return 1;
return (GetSummation() > 2) ? 4 : 2;
}
return bit_depth_image.value() / 8;
}
bool DiffractionExperiment::IsPixelSigned() const {
auto pixel_signed = image_format_settings.IsPixelSigned();
if (!pixel_signed.has_value())
return IsJungfrauConvPhotonCnt() && (GetDetectorType() == DetectorType::JUNGFRAU);
else
return pixel_signed.value();
}
int64_t DiffractionExperiment::GetDataStreamsNum() const {
return std::min<int64_t>(ndatastreams, detector.GetModulesNum());
}
int64_t DiffractionExperiment::GetModulesNum(uint16_t data_stream) const {
if (data_stream >= GetDataStreamsNum())
throw JFJochException(JFJochExceptionCategory::ArrayOutOfBounds, "Non existing data stream");
return (detector.GetModulesNum() + (GetDataStreamsNum() - 1) - data_stream) / GetDataStreamsNum();
}
int64_t DiffractionExperiment::GetModulesNum() const {
return detector.GetModulesNum();
}
int64_t DiffractionExperiment::GetFirstModuleOfDataStream(uint16_t data_stream) const {
if (data_stream >= GetDataStreamsNum())
throw JFJochException(JFJochExceptionCategory::ArrayOutOfBounds, "Non exisiting data stream");
int64_t val = 0;
for (int i = 0; i < data_stream; i++) val += GetModulesNum(i);
return val;
}
int64_t DiffractionExperiment::GetMaxCompressedSize() const {
return MaxCompressedSize(GetCompressionAlgorithm(), GetPixelsNum(), GetByteDepthImage());
}
int64_t DiffractionExperiment::GetPixelsNum() const {
return GetXPixelsNum() * GetYPixelsNum();
}
int64_t DiffractionExperiment::GetXPixelsNum() const {
return detector.GetGeometry().GetWidth(IsGeometryTransformed());
}
int64_t DiffractionExperiment::GetYPixelsNum() const {
return detector.GetGeometry().GetHeight(IsGeometryTransformed());
}
int64_t DiffractionExperiment::GetPixelsNumConv() const {
return GetXPixelsNumConv() * GetYPixelsNumConv();
}
int64_t DiffractionExperiment::GetXPixelsNumConv() const {
return detector.GetGeometry().GetWidth(true);
}
int64_t DiffractionExperiment::GetYPixelsNumConv() const {
return detector.GetGeometry().GetHeight(true);
}
int64_t DiffractionExperiment::GetPixel0OfModuleConv(uint16_t module_number) const {
return detector.GetGeometry().GetPixel0(module_number, true);
}
int64_t DiffractionExperiment::GetOverflow() const {
switch (GetByteDepthImage()) {
case 4:
return IsPixelSigned() ? static_cast<int64_t>(INT32_MAX) : static_cast<int64_t>(UINT32_MAX);
case 2:
return IsPixelSigned() ? INT16_MAX : UINT16_MAX;
case 1:
return IsPixelSigned() ? INT8_MAX : UINT8_MAX;
default:
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Pixel depth unsupported");
}
}
int64_t DiffractionExperiment::GetSaturationLimit() const {
auto sat = detector.GetSaturationLimit();
auto overflow = GetOverflow();
if (!sat)
return overflow;
return std::min(sat.value(), overflow);
}
int64_t DiffractionExperiment::GetUnderflow() const {
if (!IsPixelSigned())
return -1;
switch (GetByteDepthImage()) {
case 1:
return INT8_MIN;
case 2:
return INT16_MIN;
case 4:
return INT32_MIN;
default:
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Pixel depth unsupported");
}
}
std::optional<std::chrono::microseconds> DiffractionExperiment::GetZMQPreviewPeriod() const {
if (IsPedestalRun())
return {};
else
return zmq_preview_period;
}
int64_t DiffractionExperiment::GetDefaultPlotBinning() const {
// If images are equal longer than 100 ms, don't bin images
// If data collection is shorter than 5s, don't bin
if ((GetImageTime() >= 100ms) || (GetImageNum() * GetImageTime() < 5s))
return 1;
return 500ms / GetImageTime(); // 1 bin == 500 ms
}
bool DiffractionExperiment::IsUsingInternalPacketGen() const {
return detector_settings.IsInternalGeneratorEnable();
}
uint32_t DiffractionExperiment::GetSrcIPv4Address(uint32_t data_stream, uint32_t half_module) const {
uint32_t host = half_module + detector.GetUDPInterfaceCount() * GetFirstModuleOfDataStream(data_stream);
return detector.GetSrcIPv4Addr(host);
}
bool DiffractionExperiment::GetMaskModuleEdges() const {
return image_format_settings.IsMaskModuleEdges();
}
bool DiffractionExperiment::GetMaskChipEdges() const {
return image_format_settings.IsMaskChipEdges();
}
std::optional<UnitCell> DiffractionExperiment::GetUnitCell() const {
return dataset.GetUnitCell();
}
std::string DiffractionExperiment::GetUnitCellString() const {
auto uc = dataset.GetUnitCell();
if (uc.has_value()) {
return fmt::format("{:.1f}, {:.1f}, {:.1f}, {:.1f}, {:.1f} {:.1f}",
uc.value().a,
uc.value().b,
uc.value().c,
uc.value().alpha,
uc.value().beta,
uc.value().gamma);
} else
return "-";
}
std::optional<int64_t> DiffractionExperiment::GetSpaceGroupNumber() const {
return dataset.GetSpaceGroupNumber();
}
int64_t DiffractionExperiment::GetStorageCellNumber() const {
auto storage_cells = detector_settings.GetStorageCells();
switch (GetDetectorMode()) {
case DetectorMode::PedestalG1:
if (IsFixedGainG1())
return storage_cells;
case DetectorMode::PedestalG2:
if (storage_cells > 1)
return 2;
else
return 1;
default:
return storage_cells;
}
}
int64_t DiffractionExperiment::GetStorageCellStart() const {
return detector_settings.GetStorageCellStart();
}
float DiffractionExperiment::GetLowQForAzimInt_recipA() const {
return az_integration_settings.GetLowQ_recipA();
}
float DiffractionExperiment::GetHighQForAzimInt_recipA() const {
return GetAzimuthalIntegrationSettings().GetHighQ_recipA();
}
float DiffractionExperiment::GetQSpacingForAzimInt_recipA() const {
return az_integration_settings.GetQSpacing_recipA();
}
DiffractionExperiment &DiffractionExperiment::MaxSpotCount(int64_t input) {
dataset.MaxSpotCount(input);
return *this;
}
int64_t DiffractionExperiment::GetMaxSpotCount() const {
if (!IsSpotFindingEnabled())
return 0;
return dataset.GetMaxSpotCount();
}
std::string DiffractionExperiment::GetSampleName() const {
return dataset.GetSampleName();
}
void DiffractionExperiment::CheckDataProcessingSettings(const SpotFindingSettings &settings) {
check_finite("Signal to noise threshold", settings.signal_to_noise_threshold);
check_min("Signal to noise threshold", settings.signal_to_noise_threshold, 1);
check_min("Photon count threshold", settings.photon_count_threshold, 0);
check_min("Minimum pixels per spot", settings.min_pix_per_spot.value_or(2), 1);
check_min("Maximum pixels per spot", settings.max_pix_per_spot, settings.min_pix_per_spot.value_or(2) + 1);
// An unset limit means "no limit at that end", so there is nothing to check. A value that is present
// still has to be finite - NaN fails every comparison below, so testing it inside the branch would
// let it through unchecked.
if (settings.high_resolution_limit.has_value())
check_finite("Spot finding high resolution limit", *settings.high_resolution_limit);
if (settings.low_resolution_limit.has_value())
check_finite("Spot finding low resolution limit", *settings.low_resolution_limit);
if (settings.high_resolution_limit.has_value()) {
check_min("Spot finding high resolution limit", *settings.high_resolution_limit, 0.5);
check_max("Spot finding high resolution limit", *settings.high_resolution_limit, 50.0);
if (settings.low_resolution_limit.has_value())
check_min("Spot finding low resolution limit", *settings.low_resolution_limit,
*settings.high_resolution_limit);
} else if (settings.low_resolution_limit.has_value()) {
check_min("Spot finding low resolution limit", *settings.low_resolution_limit, 1.0);
check_max("Spot finding low resolution limit", *settings.low_resolution_limit, 50.0);
}
check_min("Ice ring width in Q-space (A^-1)", settings.ice_ring_width_Q_recipA, 0.0);
check_max("Ice ring width in Q-space (A^-1)", settings.ice_ring_width_Q_recipA, 1.0);
check_finite("Ice ring width in Q-space (A^-1)", settings.ice_ring_width_Q_recipA);
if (settings.high_res_gap_Q_recipA.has_value()) {
check_min("High resolution gap (A^-1)", settings.high_res_gap_Q_recipA.value(), 0.1);
check_max("High resolution gap (A^-1)", settings.high_res_gap_Q_recipA.value(), 5.0);
check_finite("High resolution gap (A^-1)", settings.high_res_gap_Q_recipA.value());
}
}
SpotFindingSettings DiffractionExperiment::DefaultDataProcessingSettings() {
return {};
}
void DiffractionExperiment::FillMessage(StartMessage &message) const {
message.images_per_file = GetImagesPerFile();
message.beam_center_x = GetBeamX_pxl();
message.beam_center_y = GetBeamY_pxl();
message.detector_distance = GetDetectorDistance_mm() * 1e-3f;
message.incident_wavelength = GetWavelength_A();
// NXmx incident_wavelength_spread is the FWHM of the wavelength distribution,
// in wavelength units. We store bandwidth as a relative FWHM (dlambda/lambda),
// so convert to absolute: dlambda = (dlambda/lambda) * lambda.
if (const auto bw = GetBandwidthFWHM())
message.incident_wavelength_spread = bw.value() * GetWavelength_A();
message.incident_energy = GetIncidentEnergy_keV() * 1e3f;
message.image_size_x = GetXPixelsNum();
message.image_size_y = GetYPixelsNum();
message.saturation_value = GetSaturationLimit() - 1;
message.error_value = GetUnderflow();
message.frame_time = std::chrono::duration<float>(GetImageTime()).count();
message.count_time = std::chrono::duration<float>(GetImageCountTime()).count();
message.number_of_images = GetImageNum();
message.pixel_size_x = GetPixelSize_mm() * 1e-3f;
message.pixel_size_y = GetPixelSize_mm() * 1e-3f;
message.sensor_material = detector.GetSensorMaterial();
message.sensor_thickness = detector.GetSensorThickness_um() * 1e-6f;
message.bit_depth_image = GetByteDepthImage() * 8;
message.bit_depth_readout = GetBitDepthReadout();
message.indexing_algorithm = GetIndexingAlgorithm();
message.images_per_trigger = dataset.GetImageNumPerTrigger();
if (GetDetectorType() == DetectorType::JUNGFRAU) {
message.storage_cell_number = GetStorageCellNumber();
message.storage_cell_delay_ns = GetStorageCellDelay().count();
}
message.file_prefix = GetFilePrefix();
message.pixel_signed = IsPixelSigned();
message.sample_name = GetSampleName();
message.max_spot_count = GetMaxSpotCount();
message.max_extra_lattices = indexing.GetMaxExtraLattices();
message.pixel_mask_enabled = IsApplyPixelMask();
message.detector_description = GetDetectorDescription();
message.space_group_number = GetSpaceGroupNumber();
message.unit_cell = GetUnitCell();
message.total_flux = GetTotalFlux();
message.attenuator_transmission = GetAttenuatorTransmission();
message.detector_translation[0] = 0.0f;
message.detector_translation[1] = 0.0f;
message.detector_translation[2] = GetDetectorDistance_mm() * 1e-3f;
message.source_name = GetSourceName();
message.source_type = GetSourceType();
message.instrument_name = GetInstrumentName();
message.summation = GetSummation();
message.user_data = GetHeaderAppendix();
message.countrate_correction_enabled = false;
message.flatfield_enabled = false;
message.goniometer = dataset.GetGoniometer();
message.grid_scan = dataset.GetGridScan();
message.run_number = GetRunNumber();
message.run_name = GetRunName();
message.gain_file_names = detector.GetGainFileNames();
message.rois = roi_mask.ExportMetadata();
message.data_reduction_factor_serialmx = GetLossyCompressionSerialMX();
message.experiment_group = dataset.GetExperimentGroup();
message.jfjoch_release = jfjoch_version();
message.detector_serial_number = detector.GetSerialNumber();
message.write_master_file = dataset.IsWriteNXmxHDF5Master();
message.overwrite = file_writer.IsOverwriteExistingFiles();
message.file_format = file_writer.GetFileFormat();
message.ring_current_mA = dataset.GetRingCurrent_mA();
message.sample_temperature_K = dataset.GetSampleTemperature_K();
message.fluorescence_spectrum = dataset.GetFluorescenceSpectrum();
message.poni_rot1 = dataset.GetPoniRot1_rad();
message.poni_rot2 = dataset.GetPoniRot2_rad();
message.poni_rot3 = dataset.GetPoniRot3_rad();
message.detect_ice_rings = dataset.IsDetectIceRings();
message.channels = {"default"};
message.smargon_position = dataset.GetSmargonPosition();
switch (GetDetectorType()) {
case DetectorType::JUNGFRAU:
message.jungfrau_conversion_enabled = IsJungfrauConvPhotonCnt();
if (IsJungfrauConvPhotonCnt())
message.jungfrau_conversion_factor = GetPhotonEnergyForConversion_keV() * 1000;
break;
case DetectorType::EIGER:
case DetectorType::DECTRIS:
message.threshold_energy["default"] = GetEigerThreshold_keV() * 1000.0f; // threshold in CBOR is in eV
break;
}
message.geometry_transformation_enabled = IsGeometryTransformed();
if (GetSummation() == 1)
message.summation_mode = "none";
else {
if (IsCPUSummation())
message.summation_mode = "cpu";
else
message.summation_mode = "fpga";
}
}
float DiffractionExperiment::GetPixelSize_mm() const {
return detector.GetPixelSize_mm();
}
std::string DiffractionExperiment::GetSourceName() const {
return instrument.GetSourceName();
}
std::string DiffractionExperiment::GetSourceType() const {
return instrument.GetSourceType();
}
std::string DiffractionExperiment::GetInstrumentName() const {
return instrument.GetInstrumentName();
}
int64_t DiffractionExperiment::GetModuleFastDirectionStep(uint16_t module_number) const {
return detector.GetGeometry().GetFastDirectionStep(module_number);
}
int64_t DiffractionExperiment::GetModuleSlowDirectionStep(uint16_t module_number) const {
return detector.GetGeometry().GetSlowDirectionStep(module_number);
}
std::vector<std::string> DiffractionExperiment::GetDetectorModuleHostname() const {
return detector.GetDetectorModuleHostname();
}
std::string DiffractionExperiment::GetDetectorDescription() const {
return detector.GetDescription();
}
DiffractionExperiment &DiffractionExperiment::ApplySolidAngleCorr(bool input) {
az_integration_settings.SolidAngleCorrection(input);
return *this;
}
bool DiffractionExperiment::GetApplySolidAngleCorr() const {
return az_integration_settings.IsSolidAngleCorrection();
}
bool DiffractionExperiment::GetSaveCalibration() const {
auto val = dataset.IsSaveCalibration();
if (val.has_value())
return val.value();
// By default calibration is saved if more than 4 images
// to limit cases were size of the file is determined by the calibration
return (GetImageNum() > 4) && (GetDetectorType() == DetectorType::JUNGFRAU);
}
DiffractionExperiment &DiffractionExperiment::StorageCellDelay(std::chrono::nanoseconds input) {
detector_settings.StorageCellDelay(input);
return *this;
}
std::chrono::nanoseconds DiffractionExperiment::GetStorageCellDelay() const {
return detector_settings.GetStorageCellDelay();
}
DiffractionExperiment &DiffractionExperiment::Summation(int64_t input) {
check_min("Summation factor", input, 1);
summation = input;
return *this;
}
int64_t DiffractionExperiment::GetSummation() const {
if (GetAutoSummation())
return summation;
return 1;
}
int64_t DiffractionExperiment::GetFPGASummation() const {
if (IsCPUSummation())
return 1;
return GetSummation();
}
int64_t DiffractionExperiment::GetByteDepthFPGA() const {
if (IsCPUSummation())
return 2;
return GetByteDepthImage();
}
int64_t DiffractionExperiment::GetUDPInterfaceCount() const {
return detector.GetUDPInterfaceCount();
}
std::vector<DetectorModuleConfig>
DiffractionExperiment::GetDetectorModuleConfig(const std::vector<AcquisitionDeviceNetConfig> &net_config) const{
std::vector<DetectorModuleConfig> ret;
for (int d = 0; d < GetDataStreamsNum(); d++) {
for (int m = 0; m < GetModulesNum(d); m++) {
DetectorModuleConfig mod_cfg;
mod_cfg.data_stream = d;
mod_cfg.udp_dest_port_1 = 1024 + m;
mod_cfg.udp_dest_port_2 = 1024 + m;
if (detector.GetUDPInterfaceCount() == 2) {
mod_cfg.ipv4_src_addr_1 = IPv4AddressToStr(GetSrcIPv4Address(d, 2 * m));
mod_cfg.ipv4_src_addr_2 = IPv4AddressToStr(GetSrcIPv4Address(d, 2 * m + 1));
} else {
mod_cfg.ipv4_src_addr_1 = IPv4AddressToStr(GetSrcIPv4Address(d, m));
mod_cfg.ipv4_src_addr_2 = IPv4AddressToStr(GetSrcIPv4Address(d, m)); // not used, settings just in case
}
mod_cfg.ipv4_dest_addr_1 = net_config[d].ipv4_addr;
mod_cfg.ipv4_dest_addr_2 = net_config[d].ipv4_addr;
mod_cfg.mac_addr_dest_1 = net_config[d].mac_addr;
mod_cfg.mac_addr_dest_2 = net_config[d].mac_addr;
mod_cfg.module_id_in_data_stream = m;
ret.emplace_back(std::move(mod_cfg));
}
}
return ret;
}
float DiffractionExperiment::GetLowQForBkgEstimate_recipA() const {
return az_integration_settings.GetBkgEstimateLowQ_recipA();
}
float DiffractionExperiment::GetHighQForBkgEstimate_recipA() const {
return az_integration_settings.GetBkgEstimateHighQ_recipA();
}
DiffractionExperiment &DiffractionExperiment::AttenuatorTransmission(const std::optional<float> &input) {
dataset.AttenuatorTransmission(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::TotalFlux(const std::optional<float> &input) {
dataset.TotalFlux(input);
return *this;
}
std::optional<float> DiffractionExperiment::GetAttenuatorTransmission() const {
return dataset.GetAttenuatorTransmission();
}
std::optional<float> DiffractionExperiment::GetTotalFlux() const {
return dataset.GetTotalFlux();
}
DiffractionExperiment &DiffractionExperiment::Goniometer(const std::optional<GoniometerAxis> &input) {
dataset.Goniometer(input);
return *this;
}
std::optional<GoniometerAxis> DiffractionExperiment::GetGoniometer() const {
return dataset.GetGoniometer();
}
std::optional<GridScanSettings> DiffractionExperiment::GetGridScan() const {
return dataset.GetGridScan();
}
DiffractionExperiment &DiffractionExperiment::UsingGainHG0(bool input) {
detector_settings.UseGainHG0(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::FixedGainG1(bool input) {
detector_settings.FixGainG1(input);
return *this;
}
bool DiffractionExperiment::IsFixedGainG1() const {
return detector_settings.IsFixGainG1();
}
bool DiffractionExperiment::IsUsingGainHG0() const {
return detector_settings.IsUseGainHG0();
}
DiffractionExperiment &DiffractionExperiment::HeaderAppendix(const nlohmann::json &input) {
dataset.HeaderAppendix(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::ImageAppendix(const nlohmann::json &input) {
dataset.ImageAppendix(input);
return *this;
}
const nlohmann::json& DiffractionExperiment::GetHeaderAppendix() const {
return dataset.GetHeaderAppendix();
}
const nlohmann::json& DiffractionExperiment::GetImageAppendix() const {
return dataset.GetImageAppendix();
}
uint64_t DiffractionExperiment::GetRunNumber() const {
if (dataset.GetRunNumber())
return dataset.GetRunNumber().value();
return series_id;
}
std::string DiffractionExperiment::GetRunName() const {
auto run_name = dataset.GetRunName();
if (run_name)
return run_name.value();
else
return std::to_string(series_id) + ":" + dataset.GetFilePrefix();
}
DiffractionExperiment &DiffractionExperiment::IncrementRunNumber() {
series_id++;
return *this;
}
Coord DiffractionExperiment::GetModuleFastDirection(uint16_t module_number) const {
return detector.GetGeometry().GetFastDirection(module_number);
}
Coord DiffractionExperiment::GetModuleSlowDirection(uint16_t module_number) const {
return detector.GetGeometry().GetSlowDirection(module_number);
}
DiffractionExperiment &DiffractionExperiment::JungfrauConvPhotonCnt(bool input) {
image_format_settings.JungfrauConversion(input);
return *this;
}
bool DiffractionExperiment::IsJungfrauConvPhotonCnt() const {
if (!IsPedestalRun() && (GetDetectorType() == DetectorType::JUNGFRAU))
return image_format_settings.IsJungfrauConversion();
else
return false;
}
DiffractionExperiment &DiffractionExperiment::DetectorDelay(std::chrono::nanoseconds input) {
detector_settings.DetectorDelay(input);
return *this;
}
std::chrono::nanoseconds DiffractionExperiment::GetDetectorDelay() const {
return detector_settings.GetDetectorDelay();
}
const DetectorSetup &DiffractionExperiment::GetDetectorSetup() const {
return detector;
}
DetectorSetup &DiffractionExperiment::Detector() {
return detector;
}
DiffractionExperiment &DiffractionExperiment::PulsedSource(bool input) {
instrument.PulsedSource(input);
return *this;
}
bool DiffractionExperiment::IsPulsedSource() const {
return instrument.IsPulsedSource();
}
bool DiffractionExperiment::IsSpotFindingEnabled() const {
return dataset.IsSpotFindingEnabled() && !IsPedestalRun();
}
float DiffractionExperiment::GetPhotonEnergyForConversion_keV() const {
auto val = GetJungfrauConversionFactor_keV();
if (val.has_value())
return val.value();
else
return GetIncidentEnergy_keV();
}
DiffractionExperiment &DiffractionExperiment::InternalPacketGeneratorImages(int64_t input) {
detector_settings.InternalGeneratorImages(input);
return *this;
}
int64_t DiffractionExperiment::GetInternalPacketGeneratorImages() const {
return detector_settings.GetInternalGeneratorImages();
}
DiffractionExperiment &DiffractionExperiment::ImportDatasetSettings(const DatasetSettings &input) {
auto tmp = dataset;
dataset = input;
auto image_time = input.GetImageTime();
if (image_time) {
switch (GetDetectorType()) {
case DetectorType::EIGER:
case DetectorType::DECTRIS:
check_min("Image time [ns]", image_time.value().count(), detector.GetMinFrameTime().count());
summation = 1;
break;
case DetectorType::JUNGFRAU:
if (image_time->count() % GetFrameTime().count() != 0) {
dataset = tmp;
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Image time must be multiple of frame time");
}
if (GetFrameTime().count() == 0) {
dataset = tmp;
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Frame time cannot be zero");
}
if (image_time < GetFrameTime()) {
dataset = tmp;
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Image time cannot be less than base frame time");
}
this->Summation(image_time.value() / GetFrameTime());
break;
}
} else
summation = 1;
if (dataset.GridScan())
dataset.GridScan()->ImageNum(GetImageNum());
if (GetFrameNum() >= MAX_FRAMES) {
dataset = tmp;
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Frame number (summation * images_per_trigger * ntrigger) cannot exceed "
+ std::to_string(MAX_FRAMES));
}
if (GetImageNum() > MAX_IMAGE_NUMBER) {
dataset = tmp;
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Number of images cannot exceed " + std::to_string(MAX_IMAGE_NUMBER));
}
return *this;
}
DatasetSettings DiffractionExperiment::GetDatasetSettings() const {
return dataset;
}
ROIMap &DiffractionExperiment::ROI() {
return roi_mask;
}
const ROIMap &DiffractionExperiment::ROI() const {
return roi_mask;
}
std::vector<uint16_t> DiffractionExperiment::ExportROIMap() const {
return roi_mask.GetROIMap(GetDiffractionGeometry(), GetXPixelsNumConv(), GetYPixelsNumConv());
}
DiffractionExperiment &DiffractionExperiment::ImagesPerFile(int64_t input) {
dataset.ImagesPerFile(input);
return *this;
}
int64_t DiffractionExperiment::GetImagesPerFile() const {
auto tmp = dataset.GetImagesPerFile();
if (tmp == 0
|| file_writer.GetFileFormat() == FileWriterFormat::NXmxIntegrated)
return GetImageNum();
else
return tmp;
}
int64_t DiffractionExperiment::GetImageBufferLocationSize() const {
// A buffer slot holds the compressed image plus the per-image CBOR metadata (spot list, reflection
// list, azimuthal profile, ...). The reflection list is bounded by the ONLINE cap
// BraggPrediction::kOnlineMaxReflections at ~170 B each, but it is not the whole of it: the capped
// spot list and the three azimuthal arrays at up to 65534 bins add another megabyte, and the
// measured worst case is ~2.9 MB. A frame whose metadata still does not fit is dropped
// individually by the receiver rather than aborting the collection.
return GetMaxCompressedSize() + kImageMetadataHeadroom;
}
float DiffractionExperiment::GetLossyCompressionSerialMX() const {
return dataset.GetLossyCompressionSerialMX();
}
DiffractionExperiment &DiffractionExperiment::LossyCompressionSerialMX(float input) {
dataset.LossyCompressionSerialMX(input);
return *this;
}
std::optional<int64_t> DiffractionExperiment::GetLossyCompressionPoisson() const {
return dataset.GetLossyCompressionPoisson();
}
DiffractionExperiment &DiffractionExperiment::LossyCompressionPoisson(const std::optional<int64_t> &input) {
dataset.LossyCompressionPoisson(input);
return *this;
}
std::string DiffractionExperiment::GetExperimentGroup() const {
return dataset.GetExperimentGroup();
}
std::optional<int64_t> DiffractionExperiment::GetPixelValueLowThreshold() const {
return dataset.GetPixelValueLowThreshold();
}
DiffractionExperiment &DiffractionExperiment::PixelValueLowThreshold(const std::optional<int64_t> &input) {
dataset.PixelValueLowThreshold(input);
return *this;
}
std::optional<int64_t> DiffractionExperiment::GetPixelValueHighThreshold() const {
return dataset.GetPixelValueHighThreshold();
}
DiffractionExperiment &DiffractionExperiment::PixelValueHighThreshold(const std::optional<int64_t> &input) {
dataset.PixelValueHighThreshold(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::ImportInstrumentMetadata(const InstrumentMetadata &input) {
instrument = input;
return *this;
}
InstrumentMetadata DiffractionExperiment::GetInstrumentMetadata() const {
return instrument;
}
DiffractionExperiment &DiffractionExperiment::ImportFileWriterSettings(const FileWriterSettings &input) {
file_writer = input;
return *this;
}
FileWriterSettings DiffractionExperiment::GetFileWriterSettings() const {
return file_writer;
}
bool DiffractionExperiment::IsGeometryTransformed() const {
// For DECTRIS detectors always operate in transformed geometry
return (GetDetectorType() == DetectorType::DECTRIS) || image_format_settings.IsGeometryTransformed();
}
DiffractionExperiment &DiffractionExperiment::GeometryTransformation(bool input) {
image_format_settings.GeometryTransformed(input);
return *this;
}
int64_t DiffractionExperiment::GetImageFillValue() const {
switch (GetByteDepthImage()) {
case 1:
if (IsPixelSigned())
return INT8_MIN;
return UINT8_MAX;
case 2:
if (IsPixelSigned())
return INT16_MIN;
return UINT16_MAX;
case 4:
if (IsPixelSigned())
return INT32_MIN;
return UINT32_MAX;
default:
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Pixel depth unsupported");
}
}
int64_t DiffractionExperiment::GetBitDepthReadout() const {
if (GetDetectorType() == DetectorType::EIGER)
return GetEigerBitDepth();
auto det_value = detector.GetBitDepthReadout();
if (det_value)
return det_value.value();
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Bit depth readout not configured");
}
bool DiffractionExperiment::IsPedestalRun() const {
switch (GetDetectorMode()) {
case DetectorMode::PedestalG0:
case DetectorMode::PedestalG1:
case DetectorMode::PedestalG2:
return true;
default:
return false;
}
}
bool DiffractionExperiment::GetAutoSummation() const {
if (IsPedestalRun() || (GetStorageCellNumber() > 1))
return false; // for pedestal or more than 1 storage cell summation doesn't make sense and should be always turned off
else
return image_format_settings.IsAutoSummation();
}
DiffractionExperiment &DiffractionExperiment::AutoSummation(bool input) {
image_format_settings.AutoSummation(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::BitDepthImage(const std::optional<int64_t> &input) {
image_format_settings.BitDepthImage(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::PixelSigned(const std::optional<bool> &input) {
image_format_settings.PixelSigned(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::JungfrauConversionFactor_keV(const std::optional<float> &input) {
image_format_settings.JungfrauConvFactor_keV(input);
return *this;
}
std::optional<float> DiffractionExperiment::GetJungfrauConversionFactor_keV() const {
return image_format_settings.GetJungfrauConvFactor_keV();
}
DiffractionExperiment & DiffractionExperiment::Conversion() {
image_format_settings.Conv();
return *this;
}
DiffractionExperiment & DiffractionExperiment::Raw() {
image_format_settings.Raw();
return *this;
}
DiffractionExperiment &DiffractionExperiment::ImportDetectorSettings(const DetectorSettings &input) {
check_min("Frame time [ns]", input.GetFrameTime().count(), detector.GetMinFrameTime().count());
if (GetDetectorType() == DetectorType::JUNGFRAU) {
if (!input.GetCountTime().has_value()) {
// implicit count time
check_max("Frame time [ns]", input.GetFrameTime().count(),
(MAX_COUNT_TIME_JUNGFRAU + detector.GetReadOutTime()).count());
} else {
// explicit count time
check_max("Count time [ns]", input.GetCountTime().value().count(),
MAX_COUNT_TIME_JUNGFRAU.count());
}
if ((input.GetTiming() == DetectorTiming::Burst) ||
(input.GetTiming() == DetectorTiming::Gated))
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Burst and gated timing modes not supported in JUNGFRAU");
}
detector_settings = input;
return *this;
}
DetectorSettings DiffractionExperiment::GetDetectorSettings() const {
return detector_settings;
}
DiffractionExperiment &DiffractionExperiment::ImportImageFormatSettings(const ImageFormatSettings &input) {
image_format_settings = input;
return *this;
}
ImageFormatSettings DiffractionExperiment::GetImageFormatSettings() const {
return image_format_settings;
}
DiffractionExperiment &DiffractionExperiment::ImportAzimuthalIntegrationSettings(const AzimuthalIntegrationSettings &input) {
az_integration_settings = input;
return *this;
}
AzimuthalIntegrationSettings DiffractionExperiment::GetAzimuthalIntegrationSettings() const {
// An unset high q means "as far as the detector reaches", so resolve it here, where the geometry is
// known. Everyone reads the settings through this getter, so nobody sees an unresolved q range.
AzimuthalIntegrationSettings ret = az_integration_settings;
ret.ResolveHighQ(GetDetectorMaxQ_recipA());
return ret;
}
float DiffractionExperiment::GetDetectorMaxResolution_A() const {
const float q = GetDetectorMaxQ_recipA();
return q > 0.0f ? 2.0f * static_cast<float>(PI) / q : 0.0f;
}
float DiffractionExperiment::GetDetectorMaxQ_recipA() const {
const DiffractionGeometry geom = GetDiffractionGeometry();
const auto width = static_cast<float>(GetXPixelsNumConv());
const auto height = static_cast<float>(GetYPixelsNumConv());
// The largest scattering angle sits at one of the detector corners, wherever the beam centre is.
float q = 0.0f;
for (const float x: {0.0f, width})
for (const float y: {0.0f, height})
q = std::max(q, geom.PxlToQ(x, y));
return q;
}
DiffractionExperiment &DiffractionExperiment::PolarizationFactor(const std::optional<float> &input) {
dataset.PolarizationFactor(input);
return *this;
}
std::optional<float> DiffractionExperiment::GetPolarizationFactor() const {
return dataset.GetPolarizationFactor();
}
DiffractionExperiment &DiffractionExperiment::BandwidthFWHM(const std::optional<float> &input) {
dataset.BandwidthFWHM(input);
return *this;
}
std::optional<float> DiffractionExperiment::GetBandwidthFWHM() const {
return dataset.GetBandwidthFWHM();
}
DiffractionExperiment &DiffractionExperiment::SaveCalibration(const std::optional<bool> &input) {
dataset.SaveCalibration(input);
return *this;
}
float DiffractionExperiment::GetPedestalG0RMSLimit() const {
return image_format_settings.GetPedestalG0RMSLimit();
}
uint32_t DiffractionExperiment::GetPedestalMinImageCount() const {
return detector_settings.GetPedestalMinImageCount();
}
float DiffractionExperiment::GetEigerThreshold_keV() const {
float thr;
if (GetDetectorMode() == DetectorMode::DarkMask)
thr = GetDarkMaskSettings().GetThreshold_keV();
else {
auto val = detector_settings.GetEigerThreshold_keV();
if (val)
thr = val.value();
else
thr = GetIncidentEnergy_keV() / 2.0f;
}
if (thr < detector.GetMinThreshold_keV())
thr = detector.GetMinThreshold_keV();
return thr;
}
DetectorTiming DiffractionExperiment::GetDetectorTiming() const {
// For calibration use Auto timing mode
switch (GetDetectorMode()) {
case DetectorMode::DarkMask:
return DetectorTiming::Auto;
default:
return detector_settings.GetTiming();
}
}
bool DiffractionExperiment::IsDetectorModuleSync() const {
return detector.IsModuleSync();
}
int64_t DiffractionExperiment::GetEigerBitDepth() const {
auto tmp = detector_settings.GetEigerBitDepth();
if (tmp.has_value())
return tmp.value();
if (GetFrameTime() >= std::chrono::microseconds(2622) )
return 32;
if (GetFrameTime() < std::chrono::microseconds(500))
return 8;
return 16;
}
DiffractionExperiment &DiffractionExperiment::EigerBitDepth(const std::optional<int64_t> &input) {
detector_settings.EigerBitDepth(input);
return *this;
}
DetectorType DiffractionExperiment::GetDetectorType() const {
return detector.GetDetectorType();
}
bool DiffractionExperiment::IsMaskPixelsWithoutG0() const {
if (GetDetectorType() == DetectorType::JUNGFRAU)
return image_format_settings.IsMaskPixelsWithoutG0();
return false;
}
bool DiffractionExperiment::IsApplyPixelMask() const {
return image_format_settings.IsApplyPixelMask() && !IsPedestalRun();
}
DiffractionExperiment &DiffractionExperiment::CPUSummation(bool input) {
cpu_summation = input;
return *this;
}
DiffractionExperiment &DiffractionExperiment::ApplyPixelMask(bool input) {
image_format_settings.ApplyPixelMask(input);
return *this;
}
bool DiffractionExperiment::IsCPUSummation() const {
if (summation == 1)
return false;
if (summation >= MAX_FPGA_SUMMATION)
return true;
return cpu_summation;
}
DiffractionExperiment & DiffractionExperiment::ElectronSource(bool input) {
instrument.ElectronSource(input);
return *this;
}
bool DiffractionExperiment::IsElectronSource() const {
return instrument.IsElectronSource();
}
DiffractionExperiment &DiffractionExperiment::SetFileWriterFormat(FileWriterFormat input) {
file_writer.FileFormat(input);
return *this;
}
FileWriterFormat DiffractionExperiment::GetFileWriterFormat() const {
return file_writer.GetFileFormat();
}
DiffractionGeometry DiffractionExperiment::GetDiffractionGeometry() const {
DiffractionGeometry g;
g.Wavelength_A(GetWavelength_A())
.PixelSize_mm(GetPixelSize_mm())
.BeamX_pxl(dataset.GetBeamX_pxl())
.BeamY_pxl(dataset.GetBeamY_pxl())
.DetectorDistance_mm(dataset.GetDetectorDistance_mm())
.PoniRot1_rad(dataset.GetPoniRot1_rad())
.PoniRot2_rad(dataset.GetPoniRot2_rad())
.PoniRot3_rad(dataset.GetPoniRot3_rad())
.Rotation(dataset.GetGoniometer());
return g;
}
void DiffractionExperiment::CalcAzIntCorrRawCoord(float *output, size_t module_number) const {
if (module_number >= GetModulesNum())
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Wrong module number");
auto geom = GetDiffractionGeometry();
for (int i = 0; i < RAW_MODULE_SIZE; i++) {
auto [x,y] = RawToConvertedCoordinate(*this, module_number, i);
if (GetApplySolidAngleCorr())
output[i] /= geom.CalcAzIntSolidAngleCorr(static_cast<float>(x), static_cast<float>(y));
auto p = GetPolarizationFactor();
if (p.has_value())
output[i] /= geom.CalcAzIntPolarizationCorr(static_cast<float>(x), static_cast<float>(y), p.value());
}
}
void DiffractionExperiment::CalcSpotFinderResolutionMap(float *data, size_t module_number) const {
if (module_number >= GetModulesNum())
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Wrong module number");
auto geom = GetDiffractionGeometry();
for (int i = 0; i < RAW_MODULE_SIZE; i++) {
auto [x,y] = RawToConvertedCoordinate(*this, module_number, i);
data[i] = geom.PxlToRes(static_cast<float>(x), static_cast<float>(y));
}
}
CompressedImageMode DiffractionExperiment::GetImageMode() const {
switch (GetByteDepthImage()) {
case 1:
return (IsPixelSigned() ? CompressedImageMode::Int8 : CompressedImageMode::Uint8);
case 2:
return (IsPixelSigned() ? CompressedImageMode::Int16 : CompressedImageMode::Uint16);
case 4:
return (IsPixelSigned() ? CompressedImageMode::Int32 : CompressedImageMode::Uint32);
default:
throw (JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Bit depth not supported"));
}
}
DiffractionExperiment &DiffractionExperiment::IndexingAlgorithm(IndexingAlgorithmEnum input) {
indexing.Algorithm(input);
return *this;
}
IndexingAlgorithmEnum DiffractionExperiment::GetIndexingAlgorithm() const {
// Rotation indexing accumulates a dense multi-frame reciprocal-space cloud that needs a GLOBAL
// lattice finder (3D FFT). ffbidx is a single-still, known-cell ORIENTATION finder: it cannot
// recover the global orientation from that cloud (it locks onto a local wedge -> garbage). So
// rotation always uses FFT (GPU) or FFTW (CPU), regardless of the requested algorithm or whether a
// cell is known - a reference MTZ (-z) or -C supplies a cell and used to silently flip Auto->FFBIDX,
// switching the rotation indexer to the one solver that cannot handle its input.
if (IsRotationIndexing())
return get_gpu_count() == 0 ? IndexingAlgorithmEnum::FFTW : IndexingAlgorithmEnum::FFT;
auto cell = GetUnitCell().has_value();
switch (indexing.GetAlgorithm()) {
case IndexingAlgorithmEnum::FFBIDX:
if (!cell)
return IndexingAlgorithmEnum::None;
return IndexingAlgorithmEnum::FFBIDX;
case IndexingAlgorithmEnum::Auto:
if (get_gpu_count() == 0)
return IndexingAlgorithmEnum::FFTW;
if (!cell)
return IndexingAlgorithmEnum::FFT;
return IndexingAlgorithmEnum::FFBIDX;
case IndexingAlgorithmEnum::FFT:
return IndexingAlgorithmEnum::FFT;
case IndexingAlgorithmEnum::FFTW:
return IndexingAlgorithmEnum::FFTW;
default:
return IndexingAlgorithmEnum::None;
}
}
IndexingSettings DiffractionExperiment::GetIndexingSettings() const {
return indexing;
}
DiffractionExperiment &DiffractionExperiment::ImportIndexingSettings(const IndexingSettings &input) {
indexing = input;
return *this;
}
float DiffractionExperiment::GetIndexingTolerance() const {
return indexing.GetTolerance();
}
DiffractionExperiment &DiffractionExperiment::IndexingTolerance(float input) {
indexing.Tolerance(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::GridScan(const std::optional<GridScanSettings> &input) {
dataset.GridScan(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::SampleTemperature_K(const std::optional<float> &input) {
dataset.SampleTemperature_K(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::RingCurrent_mA(const std::optional<float> &input) {
dataset.RingCurrent_mA(input);
return *this;
}
std::optional<float> DiffractionExperiment::GetSampleTemperature_K() const {
return dataset.GetSampleTemperature_K();
}
std::optional<float> DiffractionExperiment::GetRingCurrent_mA() const {
return dataset.GetRingCurrent_mA();
}
DiffractionExperiment &DiffractionExperiment::ImportBraggIntegrationSettings(const BraggIntegrationSettings &input) {
bragg_integration_settings = input;
return *this;
}
BraggIntegrationSettings DiffractionExperiment::GetBraggIntegrationSettings() const {
return bragg_integration_settings;
}
DiffractionExperiment &DiffractionExperiment::ImportScalingSettings(const ScalingSettings &input) {
scaling_settings = input;
return *this;
}
ScalingSettings DiffractionExperiment::GetScalingSettings() const {
return scaling_settings;
}
DiffractionExperiment &DiffractionExperiment::PoniRot1_rad(float input) {
dataset.PoniRot1_rad(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::PoniRot2_rad(float input) {
dataset.PoniRot2_rad(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::PoniRot3_rad(float input) {
dataset.PoniRot3_rad(input);
return *this;
}
float DiffractionExperiment::GetPoniRot1_rad() const {
return dataset.GetPoniRot1_rad();
}
float DiffractionExperiment::GetPoniRot2_rad() const {
return dataset.GetPoniRot2_rad();
}
float DiffractionExperiment::GetPoniRot3_rad() const {
return dataset.GetPoniRot3_rad();
}
GeomRefinementAlgorithmEnum DiffractionExperiment::GetGeomRefinementAlgorithm() const {
return indexing.GetGeomRefinementAlgorithm();
}
DiffractionExperiment &DiffractionExperiment::GeomRefinementAlgorithm(GeomRefinementAlgorithmEnum input) {
indexing.GeomRefinementAlgorithm(input);
return *this;
}
gemmi::CrystalSystem DiffractionExperiment::GetCrystalSystem() const {
auto sg = GetGemmiSpaceGroup();
if (!sg)
return gemmi::CrystalSystem::Monoclinic;
return sg->crystal_system();
}
std::string DiffractionExperiment::GetSpaceGroupName() const {
auto sg = GetGemmiSpaceGroup();
if (!sg)
return "";
return sg->short_name();
}
std::optional<gemmi::SpaceGroup> DiffractionExperiment::GetGemmiSpaceGroup() const {
auto sg = GetSpaceGroupNumber();
if (!sg)
return std::nullopt;
const gemmi::SpaceGroup *g = gemmi::find_spacegroup_by_number(sg.value());
if (g == nullptr)
return std::nullopt;
return *g;
}
char DiffractionExperiment::GetCentering() const {
auto sg = GetGemmiSpaceGroup();
if (!sg)
return 'P';
return sg->centring_type();
}
DiffractionExperiment &DiffractionExperiment::FluorescenceSpectrum(const XrayFluorescenceSpectrum &input) {
dataset.FluorescenceSpectrum(input);
return *this;
}
DiffractionExperiment &DiffractionExperiment::DetectIceRings(bool input) {
dataset.DetectIceRings(input);
return *this;
}
bool DiffractionExperiment::IsDetectIceRings() const {
return dataset.IsDetectIceRings();
}
const XrayFluorescenceSpectrum &DiffractionExperiment::GetFluorescenceSpectrum() const {
return dataset.GetFluorescenceSpectrum();
}
DarkMaskSettings DiffractionExperiment::GetDarkMaskSettings() const {
return dark_mask_settings;
}
DiffractionExperiment &DiffractionExperiment::ImportDarkMaskSettings(const DarkMaskSettings &input) {
dark_mask_settings = input;
return *this;
}
int64_t DiffractionExperiment::GetDarkMaskNumberOfFrames() const {
if (GetDetectorType() == DetectorType::DECTRIS)
return GetDarkMaskSettings().GetNumberOfFrames();
return 0;
}
bool DiffractionExperiment::IsRotationIndexing() const {
return GetGoniometer().has_value() && indexing.GetRotationIndexing();
}
DiffractionExperiment &DiffractionExperiment::RunNumber(uint64_t input) {
series_id = input;
return *this;
}
std::optional<double> DiffractionExperiment::GetRotationWedgeForScaling() const {
return scaling_settings.GetRotationWedgeForScaling();
}
bool DiffractionExperiment::GetRefineRotationWedgeInScaling() const {
return scaling_settings.GetRotationWedgeForScaling().has_value() && scaling_settings.GetRefineWedge();
}