rugnux report: BEAM_CENTRE_OFF warning when the file's beam centre is wrong
The pre-scan's beam-centre check and the post-refinement both measure where the direct beam lands, but the report only carried the centre the run used. Section 1 now sets the file's direct beam (FILE_DIRECT_BEAM) beside the background's (BACKGROUND_DIRECT_BEAM, with its sigma), the shift of the refined centre from the file's (BEAM_CENTRE_SHIFT) and the tolerance the geometry asks for (BEAM_CENTRE_TOLERANCE: BeamCenterNeed_pxl, the number the log's check prints, here on the run's own cell). A shift above the tolerance is a BEAM_CENTRE_OFF warning naming the file's, refined and background centres, the difference in px and mm and the value to correct the beamline to; a run that refined no centre is warned on the background alone where its fit resolves the difference (3 sigma). The summary gains a Beam centre line. The refined centre, not the background, is the trigger wherever one exists: on a battery set where XDS's refined direct beam and rugnux's agree to 1 px, the background fit is 12 px away from both. Report-only; no result changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVmAWnzCmRKAXVUCdc4iNi
This commit is contained in:
+24
-2
@@ -128,6 +128,28 @@ JFJOCH_DATASET_SETTINGS= {"beam_x_pxl": 2078.24, "beam_y_pxl": 2233.92, "detecto
|
||||
grep '^JFJOCH_DATASET_SETTINGS=' out_report.txt | cut -d' ' -f2- > geometry.json
|
||||
```
|
||||
|
||||
**The beam centre in the file.** Section 1 sets the file's centre beside the two the run measured:
|
||||
**`FILE_DIRECT_BEAM=`** is where the input file puts the direct beam (its beam centre, under its own
|
||||
detector tilt), **`BACKGROUND_DIRECT_BEAM=`** where the scattered background puts it, with the fit's
|
||||
sigma (the pre-scan's beam-centre check), and **`BEAM_CENTRE_SHIFT=`** how far the centre the result was
|
||||
integrated at (`DIRECT_BEAM=`, the refined one on a rotation run) lies from the file's, in pixels.
|
||||
**`BEAM_CENTRE_TOLERANCE=`** is how far this crystal on this geometry lets the centre be wrong - the
|
||||
displacement at which the accumulated reflections of the longest cell axis smear to 70% of their
|
||||
height, the number the log's `Beam centre check` line states, here on the run's own cell. Where the
|
||||
shift is larger, a **`BEAM_CENTRE_OFF`** warning gives the file's, the refined and the background
|
||||
centre, the difference in pixels and millimetres, and the value to correct the beamline (or the
|
||||
header) to: the run itself integrated at the refined centre, but every other program given the same
|
||||
file starts from the wrong one. A run that refined no centre (stills) is warned on the background
|
||||
alone, where its fit resolves the difference (three sigma) and the difference exceeds the tolerance,
|
||||
worded as a check. The summary's `Beam centre` line states the outcome either way.
|
||||
|
||||
```
|
||||
FILE_DIRECT_BEAM= 1564.31 1702.91
|
||||
BACKGROUND_DIRECT_BEAM= 1571.30 1699.85 0.62
|
||||
BEAM_CENTRE_SHIFT= 7.15
|
||||
BEAM_CENTRE_TOLERANCE= 3.49
|
||||
```
|
||||
|
||||
**Which pass.** A rotation run integrates twice — once at the geometry in the input file (the first
|
||||
half of the sweep but at least 120°, `--prepass-fraction`), then the whole sweep again at the post-refined geometry — and can integrate a third time if a guard rejects the second pass.
|
||||
There is **one** report, for the pass that became the canonical output, and `PASS=` /
|
||||
@@ -156,13 +178,13 @@ and a run that produced a textbook data set read identically for their first thr
|
||||
`GONIO_SCALE`, `SPINDLE_CAP`, `ANISOTROPY`, `TWINNING`, `PSEUDO_TRANSLATION`,
|
||||
`LATTICE_TRANSLATION`, `MODEL_HAND`, `MODEL_NOT_VALIDATED`, `REFERENCE_MISMATCH`, `CANCELLED`, `RESOLUTION_FIT`,
|
||||
`FLIGHT_PATH`, `GEOMETRY_NOT_CONVERGED`, `SCALING_NOT_CONVERGED`, `HARMONIC_CONTAMINATION`,
|
||||
`SUPERCELL_POSSIBLE`, `MULTIPLE_LATTICES`, `ICE_RINGS`, `POWDER_RINGS`. `NONE` when nothing fired. A code appears if and only if its
|
||||
`SUPERCELL_POSSIBLE`, `MULTIPLE_LATTICES`, `ICE_RINGS`, `POWDER_RINGS`, `BEAM_CENTRE_OFF`. `NONE` when nothing fired. A code appears if and only if its
|
||||
warning fired, so the flags and the `WARNING:` lines are two renderings of one list. Thresholds are
|
||||
deliberately low: a warning is a prompt to check something, not a verdict, and some of them fire on
|
||||
data that turn out fine — the closed
|
||||
type for machinery, the open sentence for a person.
|
||||
- Below them, one line each, the facts a reader needs before reading further: space group, cell,
|
||||
mosaicity, the powder and ice rings, multiple lattices, resolution, completeness, signal, anomalous
|
||||
mosaicity, the beam centre, the powder and ice rings, multiple lattices, resolution, completeness, signal, anomalous
|
||||
signal, anisotropy, pseudo-symmetry, twinning, supercell, radiation damage and the sweep. A line
|
||||
states a finding that is not a condition too - weak powder rings, a possible supercell no stronger than
|
||||
correct cells show, an arbitrary indexing choice (`Indexing choice`, a rotation sweep whose cell
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace PathologyCode {
|
||||
constexpr const char *SCALING_NOT_CONVERGED = "SCALING_NOT_CONVERGED"; // the per-frame scales hit their cap still moving
|
||||
constexpr const char *POWDER_RINGS = "POWDER_RINGS"; // non-ice powder rings hold many spots
|
||||
constexpr const char *ICE_RINGS = "ICE_RINGS"; // hexagonal-ice rings hold many spots
|
||||
constexpr const char *BEAM_CENTRE_OFF = "BEAM_CENTRE_OFF"; // the file's beam centre is wrong for this geometry
|
||||
}
|
||||
|
||||
// A closed type plus an open description. The type is what machinery switches on; the text is what a
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace {
|
||||
// UNDECLARED_LATTICE_TRANSLATION_PCT and INDEXING_AMBIGUITY_OPERATORS; INDEXING_RATE is a
|
||||
// --developer key on rotation runs; POWDER_ICE_* / POWDER_NON_ICE_SPOT_FRACTION;
|
||||
// the ICE_RINGS and POWDER_RINGS flags.
|
||||
// 16: FILE_DIRECT_BEAM / BACKGROUND_DIRECT_BEAM / BEAM_CENTRE_SHIFT / BEAM_CENTRE_TOLERANCE in
|
||||
// section 1 and the BEAM_CENTRE_OFF flag - the file's beam centre against the refined one.
|
||||
// 8: every line that is not `KEY= value` and not blank starts with `#`, warnings included
|
||||
// (`# WARNING:`); the header gains RUGNUX_DOWNLOAD and BUILD_CXX_FLAGS, and RUGNUX_GIT is
|
||||
// the full build-time hash.
|
||||
@@ -408,6 +410,8 @@ ReportDocument BuildReportDocument(const std::string &output_prefix,
|
||||
const bool rotation = experiment.IsRotationIndexing();
|
||||
const bool merged = result.has_merge_statistics;
|
||||
doc.warnings = result.warnings;
|
||||
// SUMMARY rows filled by the sections that measure them; empty leaves the row out.
|
||||
std::string beam_centre_row;
|
||||
|
||||
// ------------------------------------------------------------------ header block
|
||||
{
|
||||
@@ -543,6 +547,67 @@ ReportDocument BuildReportDocument(const std::string &output_prefix,
|
||||
"integrated at is the best one reached, not a settled one (see PASS_DECISION)");
|
||||
}
|
||||
|
||||
// The beam centre against the file's. The file's is read off the experiment the run was
|
||||
// given - the header, with the header's tilt - and compared with where the result was
|
||||
// integrated (the refined geometry) and with where the scattered background put the beam. The
|
||||
// run corrects a wrong centre for itself; every other program reading the same file does not,
|
||||
// which is why this is worth a warning however well the run went. The tolerance is the one the
|
||||
// pre-scan's check prints (BeamCenterNeed_pxl), taken on the run's own cell.
|
||||
{
|
||||
const auto file_direct = experiment.GetDiffractionGeometry().GetDirectBeam_pxl();
|
||||
const double pixel_mm = experiment.GetPixelSize_mm();
|
||||
const double need = result.beam_centre_need_pxl;
|
||||
const double moved = std::hypot(result.used_direct_beam_x_pxl - file_direct.first,
|
||||
result.used_direct_beam_y_pxl - file_direct.second);
|
||||
const auto &bg = result.background_beam_centre;
|
||||
const double bg_moved = bg ? std::hypot(bg->beam_x_pxl - file_direct.first,
|
||||
bg->beam_y_pxl - file_direct.second) : NAN;
|
||||
Add(s, KeyText("FILE_DIRECT_BEAM", fmt::format("{:.2f} {:.2f}", file_direct.first,
|
||||
file_direct.second)));
|
||||
if (bg)
|
||||
Add(s, KeyText("BACKGROUND_DIRECT_BEAM", fmt::format("{:.2f} {:.2f} {:.2f}", bg->beam_x_pxl,
|
||||
bg->beam_y_pxl, bg->sigma_pxl)));
|
||||
Add(s, KeyReal("BEAM_CENTRE_SHIFT", moved, "{:.2f}"));
|
||||
if (std::isfinite(need))
|
||||
Add(s, KeyReal("BEAM_CENTRE_TOLERANCE", need, "{:.2f}"));
|
||||
// The refined geometry is the evidence wherever the run refined one: the background fit can
|
||||
// be pulled by an asymmetric background, and where both exist and disagree the spot
|
||||
// positions are what the result rests on. Only a run that refined nothing (stills, no
|
||||
// post-refinement) is warned on the background alone, and only where the fit resolves the
|
||||
// difference at all (three of its own sigma, as the pre-scan's check reads it).
|
||||
const bool bg_resolves = bg && bg_moved > 3.0 * bg->sigma_pxl;
|
||||
const std::string bg_text = bg ? fmt::format("the scattered background at ({:.2f}, {:.2f}) +- {:.2f} px",
|
||||
bg->beam_x_pxl, bg->beam_y_pxl, bg->sigma_pxl)
|
||||
: std::string();
|
||||
if (std::isfinite(need) && moved > need) {
|
||||
beam_centre_row = fmt::format("the file's is OFF by {:.1f} px ({:.2f} mm) - correct the "
|
||||
"beamline value", moved, moved * pixel_mm);
|
||||
Warn(doc, PathologyCode::BEAM_CENTRE_OFF, fmt::format(
|
||||
"the beam centre in the input file is {:.1f} px ({:.2f} mm) from where this run found "
|
||||
"the direct beam, more than the {:.1f} px this geometry needs it to be right to: the "
|
||||
"file puts the direct beam at ({:.2f}, {:.2f}), the refined geometry at ({:.2f}, "
|
||||
"{:.2f}){}. This run used the refined centre, but any other program given this file "
|
||||
"starts from the wrong one - correct the beam centre at the beamline (or in the "
|
||||
"file's header) to ({:.2f}, {:.2f})",
|
||||
moved, moved * pixel_mm, need, file_direct.first, file_direct.second,
|
||||
result.used_direct_beam_x_pxl, result.used_direct_beam_y_pxl,
|
||||
bg ? ", " + bg_text : std::string(),
|
||||
result.used_direct_beam_x_pxl, result.used_direct_beam_y_pxl));
|
||||
} else if (std::isfinite(need) && !result.post_refine && bg_resolves && bg_moved > need) {
|
||||
beam_centre_row = fmt::format("the background puts it {:.1f} px ({:.2f} mm) from the file's - "
|
||||
"check the beamline value", bg_moved, bg_moved * pixel_mm);
|
||||
Warn(doc, PathologyCode::BEAM_CENTRE_OFF, fmt::format(
|
||||
"the beam centre in the input file may be off: the file puts the direct beam at "
|
||||
"({:.2f}, {:.2f}), {} - {:.1f} px ({:.2f} mm) away, more than the {:.1f} px this "
|
||||
"geometry needs the centre to be right to. Nothing on this run refined the centre, so "
|
||||
"this is the background's word alone: check the beam centre at the beamline and "
|
||||
"correct it if this is confirmed",
|
||||
file_direct.first, file_direct.second, bg_text, bg_moved, bg_moved * pixel_mm, need));
|
||||
} else if (std::isfinite(need))
|
||||
beam_centre_row = fmt::format("the file's is within the {:.1f} px this geometry needs ({:.1f} px "
|
||||
"from the refined one)", need, moved);
|
||||
}
|
||||
|
||||
Add(s, Blank());
|
||||
// Where the geometry came from. This is the whole provenance story a user needs, because a
|
||||
// user acts on exactly one geometry: the one the result was produced with.
|
||||
@@ -2377,6 +2442,8 @@ ReportDocument BuildReportDocument(const std::string &output_prefix,
|
||||
row("Indexing choice", fmt::format("alternative indexing {} - one sweep is indexed consistently, "
|
||||
"so it matters only against other data: index against those "
|
||||
"with -z or --model", result.indexing_ambiguity_operators));
|
||||
if (!beam_centre_row.empty())
|
||||
checked_row("Beam centre", {PathologyCode::BEAM_CENTRE_OFF}, beam_centre_row);
|
||||
if (!result.powder.rings_q_recipA.empty() || result.consensus_cell) {
|
||||
const auto &pw = result.powder;
|
||||
checked_row("Powder", {PathologyCode::POWDER_RINGS}, pw.rings_q_recipA.empty() ? std::string("no rings detected")
|
||||
|
||||
@@ -434,8 +434,17 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) {
|
||||
// on the rotation two-pass that is under a third of what the run actually took, the pre-scan and the
|
||||
// first pass being invisible in it. Time the whole thing here, where every pass is inside.
|
||||
const auto run_start = std::chrono::steady_clock::now();
|
||||
DiffractionExperiment header_experiment = experiment_;
|
||||
ProcessResult result = RunAllPasses(observer);
|
||||
result.total_time_s = std::chrono::duration<double>(std::chrono::steady_clock::now() - run_start).count();
|
||||
// For the report's beam-centre warning: what the background measured, and what this crystal asks
|
||||
// of the centre - at the file's geometry, as the pre-scan's check asks it, but on the cell the run
|
||||
// determined rather than the long axis that check has to assume.
|
||||
JoinBeamCenterCapture();
|
||||
result.background_beam_centre = measured_beam_center_;
|
||||
if (result.consensus_cell)
|
||||
header_experiment.SetUnitCell(*result.consensus_cell);
|
||||
result.beam_centre_need_pxl = BeamCenterNeed_pxl(header_experiment);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -530,6 +530,13 @@ struct ProcessResult {
|
||||
// did not post-refine (stills, single pass, or the refinement did not converge).
|
||||
std::optional<PostRefineResult> post_refine;
|
||||
|
||||
// The beam centre the scattered background puts the direct beam at (the pre-scan's check), and
|
||||
// how far the centre may be wrong on this crystal (BeamCenterNeed_pxl, with the run's cell where
|
||||
// it has one). Report only: the file's centre is read off the experiment the report is given, the
|
||||
// refined one off used_direct_beam_*. Empty / NaN where the check did not run.
|
||||
std::optional<BeamCenterEstimate> background_beam_centre;
|
||||
float beam_centre_need_pxl = NAN;
|
||||
|
||||
// Which pass this result is (1-based) and how many ran, plus one line saying which pass became the
|
||||
// canonical output and on what evidence. pass_count == 1 outside the rotation two-pass.
|
||||
int pass_number = 1;
|
||||
|
||||
@@ -1200,3 +1200,39 @@ TEST_CASE("ResultReport_Checks", "[Diagnostics]") {
|
||||
CHECK(std::ranges::any_of(c.codes, [&](const std::string &code) {
|
||||
return std::ranges::find(doc.pathology_flags, code) != doc.pathology_flags.end(); }));
|
||||
}
|
||||
|
||||
TEST_CASE("ResultReport_BeamCentreOff", "[Diagnostics]") {
|
||||
DiffractionExperiment x(DetJF(1));
|
||||
x.BeamX_pxl(500.0f).BeamY_pxl(400.0f);
|
||||
const auto file = x.GetDiffractionGeometry().GetDirectBeam_pxl();
|
||||
ProcessResult result;
|
||||
result.post_refine = PostRefineResult{};
|
||||
result.beam_centre_need_pxl = 2.0f;
|
||||
result.background_beam_centre = BeamCenterEstimate{.beam_x_pxl = static_cast<float>(file.first) + 5.0f,
|
||||
.beam_y_pxl = static_cast<float>(file.second),
|
||||
.sigma_pxl = 0.5f};
|
||||
|
||||
// Refined within the tolerance: the background's word is not enough where the centre was refined.
|
||||
result.used_direct_beam_x_pxl = file.first + 1.0;
|
||||
result.used_direct_beam_y_pxl = file.second;
|
||||
auto text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("BEAM_CENTRE_OFF") == std::string::npos);
|
||||
CHECK(text.find("\nBEAM_CENTRE_SHIFT= 1.00\n") != std::string::npos);
|
||||
CHECK(text.find("\nBEAM_CENTRE_TOLERANCE= 2.00\n") != std::string::npos);
|
||||
|
||||
// Refined 4 px off the file's centre: warned, with both centres, px and mm, and the advice.
|
||||
result.used_direct_beam_x_pxl = file.first;
|
||||
result.used_direct_beam_y_pxl = file.second + 4.0;
|
||||
text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("\nPATHOLOGY_FLAGS= BEAM_CENTRE_OFF\n") != std::string::npos);
|
||||
CHECK(text.find("4.0 px (0.30 mm)") != std::string::npos);
|
||||
CHECK(text.find("correct the beam centre at the beamline") != std::string::npos);
|
||||
|
||||
// Nothing refined (stills): the background alone warns, worded as a check.
|
||||
result.post_refine.reset();
|
||||
result.used_direct_beam_x_pxl = file.first;
|
||||
result.used_direct_beam_y_pxl = file.second;
|
||||
text = RenderResultReport("p", "in.h5", x, result);
|
||||
CHECK(text.find("\nPATHOLOGY_FLAGS= BEAM_CENTRE_OFF\n") != std::string::npos);
|
||||
CHECK(text.find("the background's word alone") != std::string::npos);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user