Build Packages / Create release (push) Successful in 21s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 9m40s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 9m49s
Build Packages / build:viewer-tgz:cpu (push) Successful in 11m37s
Build Packages / build:viewer-tgz:cuda (push) Successful in 12m40s
Build Packages / build:windows:nocuda (push) Successful in 17m44s
Build Packages / build:windows:cuda (push) Successful in 20m13s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 14m41s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m59s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 15m5s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 14m35s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 15m53s
Build Packages / build:rugnux:windows (push) Successful in 11m29s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 18m51s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 18m43s
Build Packages / Generate python client (push) Successful in 51s
Build Packages / build:rpm (rocky8) (push) Successful in 18m51s
Build Packages / Build documentation (push) Successful in 1m21s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 18m38s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 18m24s
Build Packages / build:rpm (rocky9) (push) Successful in 19m19s
Build Packages / Unit tests (push) Successful in 1h37m15s
* Building Jungfraujoch no longer needs zlib or Eigen installed on the machine, and the dependencies the build fetches are pinned and updated to current releases. * rugnux: improvements in indexing, lattice selection and geometry post-refinement, which index crystals that previously returned no lattice and keep the better of the two geometries a run measures. * rugnux: improvements in beam-centre measurement, beam-stop detection and space-group determination. * rugnux: the unit cell reported with a determined space group now obeys that group - a cell whose symmetry was confirmed from the intensities is re-refined under it, and a cell the group cannot describe is reported with a warning rather than as it stands. * rugnux drops the stretches of a rotation sweep whose removal measurably improves the merged intensities and reports what became of every frame, and decides the resolution cut on the crystal's own diffraction rather than on its ice rings. * The rugnux results report is machine-readable - every line that is not `KEY= value` data starts with `#` - and states the build it was written by, its authorship and its terms of use (`REPORT_VERSION= 8`). * `jfjoch_viewer`: improvements in the file manager (CBF frames beside HDF5 datasets, a remembered root), the dataset plots, the inspector and the image statistics, plus a settable font size, a view of the rugnux results report, usable performance over a remote display (`ssh -X`) and a reset of all settings to defaults; the reciprocal-space window is removed. * Broker fixes around DECTRIS collections and dark-mask calibration: re-initialising after a run that never started no longer freezes the broker, a cancelled calibration is abandoned instead of reported as done, and a collection whose start message never arrives ends by itself. Reviewed-on: #79 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
90 lines
5.3 KiB
C++
90 lines
5.3 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include "../../common/DiffractionSpot.h"
|
|
|
|
void GenerateSpotPlot(DataMessage &msg, const std::vector<SpotToSave> &spots, float d_min_A);
|
|
|
|
void CountSpots(DataMessage &msg,
|
|
const DiffractionExperiment& experiment,
|
|
const std::vector<DiffractionSpot> &spots,
|
|
float d_min_A);
|
|
|
|
void CountSpots(DataMessage &msg,
|
|
const std::vector<SpotToSave> &spots,
|
|
float d_min_A);
|
|
|
|
float CountIceRingControlSpots(const std::vector<SpotToSave> &spots, float half_width_q_recipA);
|
|
|
|
void MarkIceRings(std::vector<SpotToSave> &spots, float tolerance_q_recipA);
|
|
|
|
// Flag every spot sitting within tolerance of one of the given ring positions (q = 2*pi/d). The same
|
|
// flag MarkIceRings sets, because it means the same thing downstream: a spot that belongs to a powder
|
|
// ring and not to this crystal.
|
|
void MarkRings(std::vector<SpotToSave> &spots, const std::vector<float> &rings_q_recipA,
|
|
float tolerance_q_recipA);
|
|
|
|
// What a POOLED spot list says about powder contamination: a crystalline phase other than the
|
|
// crystal, diffracting as rings the crystal's own reflections sit among. Hexagonal ice has a fixed
|
|
// list of rings and MarkIceRings above; nothing else that powders does - a shower of microcrystals
|
|
// around the crystal, salt out of the cryoprotectant, a phase nobody expected - and on such a pattern
|
|
// the contaminant's spots can outnumber the crystal's several times over in every frame.
|
|
//
|
|
// A ring is a q bin holding far more spots than the smooth spot-density fall-off around it. The
|
|
// baseline is a running median over a window wide compared with a ring, so it follows that fall-off
|
|
// without following the rings themselves. Pool over frames spread across the sweep: a powder ring is
|
|
// on every frame and the crystal's own rows are not, so pooling sharpens the rings and flattens
|
|
// everything else.
|
|
struct PowderRings {
|
|
// Ring centres, q = 2*pi/d. Empty on a pattern that shows no rings, which is most of them.
|
|
std::vector<float> rings_q_recipA;
|
|
// The share of the pooled spots that the rings hold OVER the baseline - what the contaminant
|
|
// contributes, not what happens to lie in a ring band. 0 where there is nothing.
|
|
float spot_fraction = 0.0f;
|
|
// How far the rings can be told apart, in A. A powder's rings crowd together as q grows, and past
|
|
// the point where ring bins are the majority of bins the baseline is itself made of rings and a
|
|
// ring no longer measures as one. Nothing finer than this can be separated from the crystal, so
|
|
// it is the resolution an indexing pass can be asked to trust on such a pattern. Absent where the
|
|
// rings stay separable over the whole range, which is the ordinary case.
|
|
std::optional<float> resolved_to_d_A;
|
|
};
|
|
PowderRings MeasurePowderRings(const std::vector<float> &spot_q_recipA, float half_width_q_recipA);
|
|
|
|
// The resolution (A) that this fraction of a pooled spot list lies COARSER than. A first pass that
|
|
// stops here is given that share of the spots, whatever the detector reaches and whatever the
|
|
// crystal's fall-off looks like - so it scales to any dataset without a resolution written into the
|
|
// program. Returns nothing on a pool too small to have quantiles.
|
|
std::optional<float> SpotResolutionQuantile(const std::vector<float> &spot_q_recipA, float fraction);
|
|
|
|
// Keep the strongest `count` spots. With deprioritise_ice, spots on the hexagonal ice bands are ordered
|
|
// last and so are the first to go; pass false where the run has no measurable ice, in which case the
|
|
// flag marks ordinary reflections and ordering on it would discard good data.
|
|
void FilterSpotsByCount(std::vector<SpotToSave> &input, int64_t count, bool deprioritise_ice);
|
|
|
|
void FilterSpuriousHighResolutionSpots(std::vector<SpotToSave> &spots, float threshold);
|
|
|
|
// How far the SCALED AND MERGED data from a run of images like this one are expected to reach, in A,
|
|
// from the spots of this one image alone. Nothing else - no lattice, no integration, no merge.
|
|
//
|
|
// The spots' intensity-weighted resolution distribution falls off at the crystal's own rate, and a
|
|
// quantile taken near the middle of that fall-off measures that rate. The far end of the distribution
|
|
// does not: the highest-resolution spot found says where DETECTION stops, which moves with the
|
|
// exposure and with how many reflections the unit cell puts on the frame. Merging then reaches a fixed
|
|
// factor further in 1/d than the quantile, because averaging many observations goes on measuring
|
|
// intensities that one image cannot detect.
|
|
//
|
|
// The answer is deliberately NOT limited to what this detector records. The quantile sits in the
|
|
// middle of the fall-off, well inside the recorded range, so it goes on measuring the crystal when
|
|
// the detector stops before the diffraction does - which is the case the number is most wanted for.
|
|
// Returns nothing when the image has too few spots to have a fall-off at all.
|
|
std::optional<float> GetResolution(const std::vector<SpotToSave> &spots);
|
|
|
|
void SpotAnalyze(const DiffractionExperiment &experiment,
|
|
const SpotFindingSettings &settings,
|
|
const std::vector<DiffractionSpot> &spots,
|
|
DataMessage &message);
|
|
|
|
|