diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b181deb0b..e816510db 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -14,6 +14,7 @@ * The `rugnux` report gives the strong-direction diffraction limit, and warns when CC1/2 is not monotone with resolution. * `rugnux` ranks screw axes on the evidence their absences carry, rather than on how many control reflections a candidate happens to have. * Twinning is no longer reported when the L-test contradicts it. +* The `rugnux` report gives the detector tilt and the direct beam beside the beam centre, and carries the tilt in the `dataset_settings` block. * The `jfjoch_viewer` grid scan view draws the cells in the proportion of the scan steps, so the map has the shape of the scanned area. ### 1.0.0-rc.165 diff --git a/rugnux/ResultReport.cpp b/rugnux/ResultReport.cpp index 79b4e78e7..6bee76691 100644 --- a/rugnux/ResultReport.cpp +++ b/rugnux/ResultReport.cpp @@ -91,9 +91,25 @@ std::string RenderResultReport(const std::string &output_prefix, } Key(os, "DETECTOR_DISTANCE", fmt::format("{:.3f}", result.used_distance_mm)); Key(os, "BEAM_CENTRE", fmt::format("{:.2f} {:.2f}", result.used_beam_x_pxl, result.used_beam_y_pxl)); + // The tilt this run integrated at, and the two points it separates. BEAM_CENTRE is the PONI - the + // foot of the perpendicular from the sample - so on a tilted detector it is NOT where the direct + // beam lands, and the two were previously indistinguishable in this report because only one of + // them was printed. Degrees here; the JSON below carries radians, as the API spells it. + { + const auto &g = experiment.GetDiffractionGeometry(); + constexpr double DEG = 180.0 / M_PI; + const auto direct = g.GetDirectBeam_pxl(); + Key(os, "DETECTOR_TILT", fmt::format("{:.4f} {:.4f} {:.4f}", g.GetPoniRot1_rad() * DEG, + g.GetPoniRot2_rad() * DEG, g.GetPoniRot3_rad() * DEG)); + Key(os, "DIRECT_BEAM", fmt::format("{:.2f} {:.2f}", direct.first, direct.second)); + } os << "\n" << " The distance and beam centre above are the ones this result was integrated at, which on\n" - << " a rotation run is the post-refined geometry rather than the values in the input file.\n"; + << " a rotation run is the post-refined geometry rather than the values in the input file.\n" + << " DETECTOR_TILT is rot1/rot2/rot3 in degrees, as the run used them - it is not refined here.\n" + << " BEAM_CENTRE is the PONI and DIRECT_BEAM is where the beam actually lands; they differ by\n" + << " distance*tan(tilt)/pixel and are identical only when the tilt is zero. Quote whichever the\n" + << " program you are feeding expects, and check which one it means.\n"; // The same geometry once more, as the object jfjoch_broker takes it in: the four required // properties of dataset_settings in broker/jfjoch_api.yaml, spelled the way the API spells them. @@ -101,14 +117,27 @@ std::string RenderResultReport(const std::string &output_prefix, // this the only way back into the instrument is to read two numbers off this report by eye and // retype them. One line, valid JSON, so a script can lift it with a grep and POST it. os << "\n"; - Key(os, "JFJOCH_DATASET_SETTINGS", - fmt::format(R"({{"beam_x_pxl": {:.2f}, "beam_y_pxl": {:.2f}, "detector_distance_mm": {:.3f}, )" - R"("incident_energy_keV": {:.4f}}})", - result.used_beam_x_pxl, result.used_beam_y_pxl, result.used_distance_mm, - experiment.GetDatasetSettings().GetPhotonEnergy_keV())); + { + // The rotations belong here whenever they are not zero: dataset_settings carries + // poni_rot1/2/3_rad, and a block that omits them describes a FLAT detector - a different + // geometry from the one this run used, silently, on every tilted setup. Omitted when zero + // because the API's own default is 0.0, so the shorter block means the same thing. + const auto &g = experiment.GetDiffractionGeometry(); + std::string rot; + if (g.GetPoniRot1_rad() != 0.0f || g.GetPoniRot2_rad() != 0.0f || g.GetPoniRot3_rad() != 0.0f) + rot = fmt::format(R"(, "poni_rot1_rad": {:.6f}, "poni_rot2_rad": {:.6f}, )" + R"("poni_rot3_rad": {:.6f})", + g.GetPoniRot1_rad(), g.GetPoniRot2_rad(), g.GetPoniRot3_rad()); + Key(os, "JFJOCH_DATASET_SETTINGS", + fmt::format(R"({{"beam_x_pxl": {:.2f}, "beam_y_pxl": {:.2f}, "detector_distance_mm": {:.3f}, )" + R"("incident_energy_keV": {:.4f}{}}})", + result.used_beam_x_pxl, result.used_beam_y_pxl, result.used_distance_mm, + experiment.GetDatasetSettings().GetPhotonEnergy_keV(), rot)); + } os << "\n" << " The geometry above as jfjoch_broker's dataset_settings, to carry a refined beam centre and\n" - << " distance back to the instrument for the next collection.\n"; + << " distance back to the instrument for the next collection. The PONI rotations ride with it\n" + << " when they are non-zero, because without them the block describes a flat detector.\n"; if (result.spot_resolution_estimate_A.has_value()) { os << "\n";