v1.0.0-rc.166 (#76)
Build Packages / Unit tests (push) Successful in 1h22m15s
Build Packages / build:windows:nocuda (push) Successful in 18m0s
Build Packages / build:windows:cuda (push) Successful in 20m30s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m32s
Build Packages / build:viewer-tgz:cuda (push) Successful in 11m39s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 8m55s
Build Packages / build:rugnux:windows (push) Successful in 11m25s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 20m6s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 16m27s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 20m19s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 15m34s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 20m25s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m36s
Build Packages / build:rpm (rocky8) (push) Successful in 17m43s
Build Packages / build:rpm (rocky9) (push) Successful in 13m34s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 21m28s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 18m19s
Build Packages / DIALS test (push) Successful in 12m36s
Build Packages / XDS test (durin plugin) (push) Successful in 6m56s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 6m48s
Build Packages / XDS test (neggia plugin) (push) Successful in 6m7s
Build Packages / Generate python client (push) Successful in 11s
Build Packages / Build documentation (push) Successful in 36s
Build Packages / Create release (push) Skipped
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 5m11s

* `rugnux --mode calibration` writes `<prefix>.json` beside the `.poni`, whose `dataset_settings` member is a `jfjoch_broker` `dataset_settings` body as it stands.
* `rugnux` and `jfjoch_viewer` read PILATUS miniCBF sweeps natively, without conversion.
* Masters written by other facilities open, including Eiger 1.x and third-party NXmx variants.
* `rugnux` measures the beam centre on every run, and indexes with it when the file's value indexes nothing.
* A detector swung out on a 2theta arm is placed where the file says it stands, and the calibration can hold the tilt fixed.
* `rugnux` writes the unmerged MTZ by default, and a P1 merge beside it, so a wrong space group can be re-merged without reprocessing.
* Significant improvements to symmetry handling in `rugnux`: the lattice, the point group, the setting and the systematic absences.
* The `rugnux` report gives the resolution the CC1/2 fit reached, beside the range the reflections were written to.
* The `rugnux` report gives the twinning statistics measured before the space group was decided, beside the ones measured after.
* 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, the measured tilt and the direct beam beside the beam centre, and a post-refined beam centre is judged against the run's own measurement rather than the file's.
* `--no-refine-tilt` holds the detector tilt at the value in the file, instead of zeroing it, when the calibration starts from the spots.
* 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.

Reviewed-on: #76
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
This commit was merged in pull request #76.
This commit is contained in:
2026-09-02 21:17:31 +02:00
committed by leonarski_f
parent 511be0c366
commit 680c36c20d
383 changed files with 20910 additions and 3936 deletions
+108
View File
@@ -98,6 +98,25 @@ TEST_CASE("HDF5DataSet_scalar", "[HDF5][Unit]") {
REQUIRE (H5Fget_obj_count(H5F_OBJ_ALL, H5F_OBJ_ALL) == 0);
}
// NXmx puts no rank on distance, saturation_value or an axis setting, and a writer outside the
// DECTRIS toolchain may store any of them as a length-1 array. That is the same one number.
TEST_CASE("HDF5DataSet_scalar_stored_rank1", "[HDF5][Unit]") {
{
HDF5File file("scratch1b.h5");
file.SaveVector("one", std::vector<double>{85.5});
file.SaveVector("two", std::vector<double>{85.5, 90.0});
}
{
HDF5ReadOnlyFile file("scratch1b.h5");
HDF5DataSet one(file, "one");
CHECK(HDF5DataSpace(one).GetNumOfDimensions() == 1);
CHECK(one.ReadScalar<double>() == 85.5);
REQUIRE_THROWS(HDF5DataSet(file, "two").ReadScalar<double>());
}
remove("scratch1b.h5");
REQUIRE (H5Fget_obj_count(H5F_OBJ_ALL, H5F_OBJ_ALL) == 0);
}
TEST_CASE("HDF5DataSet_string", "[HDF5][Unit]") {
std::string tmp_string = "HDF5Content";
@@ -1432,3 +1451,92 @@ TEST_CASE("HDF5FilePusher_finalize_failure_recovers", "[HDF5FilePusher][Repro]")
std::filesystem::remove(e.path());
REQUIRE(H5Fget_obj_count(H5F_OBJ_ALL, H5F_OBJ_ALL) == 0);
}
// A link that is written in the file and points at something not there is NOT an existing dataset.
// Every DECTRIS Eiger master links saturation_value, pixel_mask, bit_depth_readout and
// serial_number into a companion <prefix>_meta.h5, and that file is routinely not kept when a
// dataset is archived or deposited. Asking only whether the LINK exists then answers yes and the
// read that follows throws, which turns every optional-field guard in the reader into a hard
// failure - measured on a deposited Eiger 16M set that could not be opened at all.
TEST_CASE("HDF5Objects_dangling_external_link_does_not_exist", "[HDF5][Unit]") {
const std::string fname = "test_dangling_link.h5";
remove(fname.c_str());
{
HDF5File file(fname);
HDF5Group group(file, "/entry");
group.SaveScalar("present", static_cast<int64_t>(7));
// ...and a link into a file that does not exist, exactly as an orphaned Eiger master has.
REQUIRE(H5Lcreate_external("no_such_meta.h5", "/_dectris/whatever",
group.GetID(), "absent", H5P_DEFAULT, H5P_DEFAULT) >= 0);
}
{
HDF5ReadOnlyFile file(fname);
CHECK(file.Exists("/entry/present"));
CHECK(file.GetInt("/entry/present") == 7);
// The link is there, the object is not.
CHECK(H5Lexists(file.GetID(), "/entry/absent", H5P_DEFAULT) > 0);
CHECK_FALSE(file.Exists("/entry/absent"));
// ...so an optional read of it falls back instead of throwing.
CHECK(file.GetString("/entry/absent", "fallback") == "fallback");
}
remove(fname.c_str());
REQUIRE(H5Fget_obj_count(H5F_OBJ_ALL, H5F_OBJ_ALL) == 0);
}
// The direct beam - where the undeflected beam lands - is not the PONI stored as beam_center_x/y:
// on a tilted detector the two are D*tan(rot)/pixel apart, which is what a downstream program (XDS,
// say) needs and cannot read out of beam_center_x/y. Written into detectorSpecific for all three
// NXmx layouts, so check the value in all three and check it is really the tilted point.
TEST_CASE("HDF5Writer_DirectBeamInDetectorSpecific", "[HDF5][Full]") {
const std::array formats{FileWriterFormat::NXmxLegacy, FileWriterFormat::NXmxVDS,
FileWriterFormat::NXmxIntegrated};
for (const auto format : formats) {
const std::string prefix = "direct_beam_" + std::to_string(static_cast<int>(format));
DiffractionExperiment x(DetJF(1));
x.ImagesPerTrigger(2).ImagesPerFile(2).Compression(CompressionAlgorithm::NO_COMPRESSION)
.FilePrefix(prefix);
x.SetFileWriterFormat(format).OverwriteExistingFiles(true);
x.BeamX_pxl(500).BeamY_pxl(400).DetectorDistance_mm(150)
.PoniRot1_rad(0.031f).PoniRot2_rad(-0.047f).PoniRot3_rad(0.019f);
const auto expected = x.GetDiffractionGeometry().GetDirectBeam_pxl();
// The tilt must actually move the point, or the test would pass on a writer that stored the PONI.
REQUIRE(std::abs(expected.first - x.GetBeamX_pxl()) > 1.0f);
REQUIRE(std::abs(expected.second - x.GetBeamY_pxl()) > 1.0f);
{
RegisterHDF5Filter();
StartMessage start_message;
x.FillMessage(start_message);
REQUIRE(start_message.direct_beam_x);
REQUIRE(start_message.direct_beam_y);
CHECK(start_message.direct_beam_x.value() == Catch::Approx(expected.first));
CHECK(start_message.direct_beam_y.value() == Catch::Approx(expected.second));
EndMessage end_message;
end_message.max_image_number = x.GetImageNum();
FileWriter writer(start_message);
std::vector<int16_t> image(x.GetPixelsNum(), 42);
for (int i = 0; i < x.GetImageNum(); i++) {
DataMessage message{};
message.image = CompressedImage(image, x.GetXPixelsNum(), x.GetYPixelsNum());
message.number = i;
REQUIRE_NOTHROW(writer.Write(message));
}
writer.WriteHDF5(end_message);
writer.Finalize();
}
{
HDF5ReadOnlyFile file(prefix + "_master.h5");
CHECK(file.GetFloat("/entry/instrument/detector/beam_center_x") == Catch::Approx(500));
CHECK(file.GetFloat("/entry/instrument/detector/detectorSpecific/direct_beam_x")
== Catch::Approx(expected.first));
CHECK(file.GetFloat("/entry/instrument/detector/detectorSpecific/direct_beam_y")
== Catch::Approx(expected.second));
}
remove((prefix + "_master.h5").c_str());
remove((prefix + "_data_000001.h5").c_str());
REQUIRE(H5Fget_obj_count(H5F_OBJ_ALL, H5F_OBJ_ALL) == 0);
}
}