From 3c001eb7a3f50d6ac29f18070dbd4a489e4a187a Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 11 Nov 2026 11:56:17 +0100 Subject: [PATCH 01/96] RESULT-CHANGING: LatticeSearch takes the Bravais class from the metric two-folds where the table misses it The 44-character table is read on the single Niggli-reduced cell. A lattice whose shortest vectors are degenerate has several reduced cells: an F-centred cubic one reduces to the 60/60/60 rhombohedral cell or to a 60/60/90 one (also the reduced cell of an I-tetragonal lattice with c = a*sqrt(2)) according to the sign of its strain, and only the first carries character 1. On an F-cubic small-molecule crystal the indexing therefore named the lattice I-tetragonal, the space-group search never got the cubic operators, -C with the cubic cell ended in P1 and -S Fm-3m was refused as "no setting of that lattice carries the group's". LatticeSearch now runs LePageLattice (basis-independent, already used by the space-group stage) on the same lattice and takes its class where it is richer than the table's and its conventional cell meets that class's metric within the caller's own length and angle tolerances (obliquity at most min(angle tolerance, 1 deg)). A tetragonal or cubic class refused only on its length equality is returned as the orthorhombic lattice of the same centring, so the demoted hypothesis (dist_tolerance 0) gets oF/oI/oP rather than triclinic. Measured on synthetic lattices, 300 per Bravais type, strain 0.05-0.5%: cF is now right 296-300/300 (was 155-159); hP 291-300 (105-108); oC, oI, tI, hR also gain, the monoclinic under-calls of centred orthorhombic lattices mostly go. Costs: a few more promotions within the existing tolerances (mP -> oC 39 -> 50 of 300), which the first pass's constrained-vs-free guard and the metric-symmetry check judge on the data. ci tier vs the rc184 full run: 29/29 pass, the null passes, 27 sets identical; myob_x10sa changes slightly (its first-pass candidate is pseudo-oC at 0.3 deg obliquity, now promoted and then dropped by the guard; P21, R_free 0.246 -> 0.245), 6iu5 d_min 2.12 -> 2.11. LatticePointGroupOrder moves from RotationIndexer to LatticeSearch; the class metric departure is shared with MetricDeviation. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK --- .../lattice_search/LatticeSearch.cpp | 99 +++++++++++++++---- image_analysis/lattice_search/LatticeSearch.h | 4 + .../rotation_indexer/RotationIndexer.cpp | 14 --- tests/LatticeSearchTest.cpp | 25 +++++ 4 files changed, 107 insertions(+), 35 deletions(-) diff --git a/image_analysis/lattice_search/LatticeSearch.cpp b/image_analysis/lattice_search/LatticeSearch.cpp index 7e5624a3f..b60c3c388 100644 --- a/image_analysis/lattice_search/LatticeSearch.cpp +++ b/image_analysis/lattice_search/LatticeSearch.cpp @@ -3,6 +3,7 @@ #include "../../common/JFJochMath.h" #include "LatticeSearch.h" +#include "LePageLattice.h" #include #include @@ -35,6 +36,27 @@ struct NiggliClass { }; namespace { +// How far a cell is from the metric its crystal system imposes: the largest angle departure (degrees) +// and the largest relative length difference among the axes the system makes equal. +std::pair ClassMetricDeparture(const UnitCell &uc, gemmi::CrystalSystem system) { + double angle = 0.0, length = 0.0; + auto a_dev = [&](double x, double ideal) { angle = std::max(angle, std::fabs(x - ideal)); }; + auto l_dev = [&](double x, double y) { length = std::max(length, std::fabs(x - y) / (0.5 * (x + y))); }; + switch (system) { + case gemmi::CrystalSystem::Monoclinic: a_dev(uc.alpha, 90); a_dev(uc.gamma, 90); break; + case gemmi::CrystalSystem::Orthorhombic: a_dev(uc.alpha, 90); a_dev(uc.beta, 90); a_dev(uc.gamma, 90); break; + case gemmi::CrystalSystem::Tetragonal: a_dev(uc.alpha, 90); a_dev(uc.beta, 90); a_dev(uc.gamma, 90); + l_dev(uc.a, uc.b); break; + case gemmi::CrystalSystem::Trigonal: + case gemmi::CrystalSystem::Hexagonal: a_dev(uc.alpha, 90); a_dev(uc.beta, 90); a_dev(uc.gamma, 120); + l_dev(uc.a, uc.b); break; + case gemmi::CrystalSystem::Cubic: a_dev(uc.alpha, 90); a_dev(uc.beta, 90); a_dev(uc.gamma, 90); + l_dev(uc.a, uc.b); l_dev(uc.b, uc.c); break; + default: break; // triclinic: nothing is asserted, nothing is violated + } + return {angle, length}; +} + // The body of both entry points. only_class, when given, keeps just the characters of that Bravais // class - see LatticeSearchForClass. With no filter this is the original walk unchanged, and the // triclinic character fits every metric, so it always returns a result. @@ -417,8 +439,6 @@ std::optional SearchCharacters(const CrystalLattice &L, dou // Character 44 fits any cell, so a match is always found - "nothing fits" is reported as triclinic. auto found = match(L_niggli, D, E, F); - if (found && found->system != gemmi::CrystalSystem::Triclinic) - return *found; // A reduced cell with an angle 90 to within NIGGLI_TYPE_BOUNDARY_DEG sits ON the boundary between the // two Niggli types: the same lattice reduces to an all-acute cell or an all-obtuse one according to @@ -433,7 +453,7 @@ std::optional SearchCharacters(const CrystalLattice &L, dou // run) and on an I-centred orthorhombic one whose reduced gamma sits 0.1 deg from 90. Beta is tried // first, so a cell the earlier beta-only retry already rescued is answered exactly as before. const UnitCell uc_niggli = L_niggli.GetUnitCell(); - if (D > 0 && E > 0 && F > 0) { + if ((!found || found->system == gemmi::CrystalSystem::Triclinic) && D > 0 && E > 0 && F > 0) { struct Flip { double kept_angle; gemmi::Mat33 basis; double d, e, f; }; const Flip flips[3] = { {uc_niggli.beta, gemmi::Mat33(-1, 0, 0, 0, 1, 0, 0, 0, -1), -D, E, -F}, @@ -444,8 +464,48 @@ std::optional SearchCharacters(const CrystalLattice &L, dou if (flip.kept_angle < 90 - NIGGLI_TYPE_BOUNDARY_DEG) continue; const auto flipped = match(L_niggli.Multiply(flip.basis), flip.d, flip.e, flip.f); - if (flipped && flipped->system != gemmi::CrystalSystem::Triclinic) - return *flipped; + if (flipped && flipped->system != gemmi::CrystalSystem::Triclinic) { + found = flipped; + break; + } + } + } + + // The characters are read on ONE reduced cell, and a lattice whose shortest vectors are degenerate + // has several: an F-centred cubic one reduces to the 60/60/60 rhombohedral cell or to a 60/60/90 + // one according to the sign of its strain, and only the first carries character 1 - the second + // reads as I-centred tetragonal or orthorhombic. Measured: of F-cubic lattices strained by 0.1 to + // 1%, 45% came back tI or oI at every strain, while the two-fold search, which does not depend on + // the basis the reduction picked, named 399 of 400 cubic F up to 0.3%. Where it finds a richer + // class than the table, take it - held to this call's own tolerances, so a search that grants no + // length equality still grants none. + // + // A tetragonal or cubic cell refused only on its length equality is, on its angles alone, the + // orthorhombic lattice of the same centring in the same basis - the class a search granting no + // length equality has to return, as the table did for the settings it could read. + if (auto lp = LePageLattice(L, std::min(angle_tolerance, LATTICE_MAX_OBLIQUITY_DEG))) { + const auto [angle, length] = ClassMetricDeparture(lp->conventional.GetUnitCell(), lp->system); + if (length > dist_tolerance && (lp->system == gemmi::CrystalSystem::Tetragonal + || lp->system == gemmi::CrystalSystem::Cubic)) + lp->system = gemmi::CrystalSystem::Orthorhombic; + if (angle <= angle_tolerance + && ClassMetricDeparture(lp->conventional.GetUnitCell(), lp->system).second <= dist_tolerance + && LatticePointGroupOrder(lp->system) > (found ? LatticePointGroupOrder(found->system) : 0) + && (!only_class || (lp->system == only_class->first && lp->centering == only_class->second))) { + int number = 0; + for (const auto &c: make_classes(D, E, F)) + if (c.system == lp->system && c.centering == lp->centering) { + number = c.number; + break; + } + return LatticeSearchResult{ + .niggli_class = number, + .primitive_reduced = lp->primitive_reduced, + .conventional = lp->conventional, + .system = lp->system, + .centering = lp->centering, + .reindex = lp->reindex, + }; } } @@ -478,6 +538,18 @@ std::optional LatticeSearchForClass(const CrystalLattice &L } +int LatticePointGroupOrder(gemmi::CrystalSystem system) { + switch (system) { + case gemmi::CrystalSystem::Monoclinic: return 2; + case gemmi::CrystalSystem::Orthorhombic: return 4; + case gemmi::CrystalSystem::Trigonal: return 6; + case gemmi::CrystalSystem::Tetragonal: return 8; + case gemmi::CrystalSystem::Hexagonal: return 12; + case gemmi::CrystalSystem::Cubic: return 24; + default: return 1; // Triclinic + } +} + bool ClassImposesLengthEquality(gemmi::CrystalSystem system) { switch (system) { case gemmi::CrystalSystem::Tetragonal: @@ -543,22 +615,7 @@ double AngleEqualityDeparture_deg(const LatticeSearchResult &sr, const CrystalLa } double MetricDeviation(const LatticeSearchResult &sr) { - const UnitCell uc = sr.conventional.GetUnitCell(); - double angle = 0.0, length = 0.0; - auto a_dev = [&](double x, double ideal) { angle = std::max(angle, std::fabs(x - ideal)); }; - auto l_dev = [&](double x, double y) { length = std::max(length, std::fabs(x - y) / (0.5 * (x + y))); }; - switch (sr.system) { - case gemmi::CrystalSystem::Monoclinic: a_dev(uc.alpha, 90); a_dev(uc.gamma, 90); break; - case gemmi::CrystalSystem::Orthorhombic: a_dev(uc.alpha, 90); a_dev(uc.beta, 90); a_dev(uc.gamma, 90); break; - case gemmi::CrystalSystem::Tetragonal: a_dev(uc.alpha, 90); a_dev(uc.beta, 90); a_dev(uc.gamma, 90); - l_dev(uc.a, uc.b); break; - case gemmi::CrystalSystem::Trigonal: - case gemmi::CrystalSystem::Hexagonal: a_dev(uc.alpha, 90); a_dev(uc.beta, 90); a_dev(uc.gamma, 120); - l_dev(uc.a, uc.b); break; - case gemmi::CrystalSystem::Cubic: a_dev(uc.alpha, 90); a_dev(uc.beta, 90); a_dev(uc.gamma, 90); - l_dev(uc.a, uc.b); l_dev(uc.b, uc.c); break; - default: break; // triclinic: nothing is asserted, nothing is violated - } + const auto [angle, length] = ClassMetricDeparture(sr.conventional.GetUnitCell(), sr.system); return std::max(angle / LATTICE_SEARCH_ANGLE_TOLERANCE_DEG, length / LATTICE_SEARCH_DIST_TOLERANCE); } diff --git a/image_analysis/lattice_search/LatticeSearch.h b/image_analysis/lattice_search/LatticeSearch.h index 7d9ef851a..5cf6588b6 100644 --- a/image_analysis/lattice_search/LatticeSearch.h +++ b/image_analysis/lattice_search/LatticeSearch.h @@ -48,6 +48,10 @@ std::optional LatticeSearchForClass(const CrystalLattice& L double dist_tolerance = LATTICE_SEARCH_DIST_TOLERANCE, double angle_tolerance_deg = LATTICE_SEARCH_ANGLE_TOLERANCE_DEG); +// Order of the lattice point group, so "lower symmetry" is a well-defined comparison +// (gemmi's enum orders Trigonal after Tetragonal, which have 6 and 8 rotations). +int LatticePointGroupOrder(gemmi::CrystalSystem system); + // Whether the conventional metric of this class asserts an equality between cell LENGTHS (a = b, and // for cubic a = b = c). Those are the promotions the relative tolerance above admits far more widely // than the spot positions resolve; every other class is carried by its angles alone. The walk run diff --git a/image_analysis/rotation_indexer/RotationIndexer.cpp b/image_analysis/rotation_indexer/RotationIndexer.cpp index 4a67037e1..6720f93d2 100644 --- a/image_analysis/rotation_indexer/RotationIndexer.cpp +++ b/image_analysis/rotation_indexer/RotationIndexer.cpp @@ -49,20 +49,6 @@ namespace { // default iteration limit, so it only bites where the clock was biting before. constexpr int ROT_REFINE_ITERATIONS = 50; - // Order of the lattice point group, so "lower symmetry" is a well-defined comparison - // (gemmi's enum orders Trigonal after Tetragonal, which have 6 and 8 rotations). - int LatticePointGroupOrder(gemmi::CrystalSystem s) { - switch (s) { - case gemmi::CrystalSystem::Monoclinic: return 2; - case gemmi::CrystalSystem::Orthorhombic: return 4; - case gemmi::CrystalSystem::Trigonal: return 6; - case gemmi::CrystalSystem::Tetragonal: return 8; - case gemmi::CrystalSystem::Hexagonal: return 12; - case gemmi::CrystalSystem::Cubic: return 24; - default: return 1; // Triclinic - } - } - // Re-express a primitive hexagonal/trigonal lattice in the conventional hexagonal setting // (a = b, gamma = 120). The Niggli-reduced primitive cell carries the two equal-length axes // at gamma = 60; replacing b with b - a opens that angle to 120 without changing the lattice. diff --git a/tests/LatticeSearchTest.cpp b/tests/LatticeSearchTest.cpp index 69e5a4ac6..72bafb701 100644 --- a/tests/LatticeSearchTest.cpp +++ b/tests/LatticeSearchTest.cpp @@ -76,6 +76,31 @@ TEST_CASE("LatticeSearch - cubic F") { CHECK(uc.gamma == Catch::Approx(90.0)); } +// An F-cubic lattice has several equally short primitive bases (60/60/60, and the 60/60/90 form that is +// also the reduced cell of an I-tetragonal lattice with c = a*sqrt(2)). Strained by a few parts per +// thousand, the reduction picks one by the sign of the strain, and the character table read on the +// 60/60/90 one named it I-centred tetragonal. +TEST_CASE("LatticeSearch - a strained cubic F lattice is cubic F in each of its primitive bases") { + const Coord a(40.0, 0.0, 0.0), b(0.05, 40.08, 0.0), c(0.0, -0.04, 39.94); + const CrystalLattice bases[] = { + CrystalLattice((b + c) * 0.5, (a + c) * 0.5, (a + b) * 0.5), + CrystalLattice((a + b) * 0.5, (a - b) * 0.5, (a + c) * 0.5), + CrystalLattice((a + b) * 0.5, (b - a) * 0.5, (a + c) * 0.5), + }; + for (const auto &L : bases) { + const auto res = LatticeSearch(L); + CHECK(res.system == gemmi::CrystalSystem::Cubic); + CHECK(res.centering == 'F'); + const auto uc = res.conventional.GetUnitCell(); + CHECK(uc.a == Catch::Approx(40.0).epsilon(0.01)); + CHECK(uc.b == Catch::Approx(40.0).epsilon(0.01)); + CHECK(uc.c == Catch::Approx(40.0).epsilon(0.01)); + CHECK(uc.alpha == Catch::Approx(90.0).margin(0.5)); + CHECK(uc.beta == Catch::Approx(90.0).margin(0.5)); + CHECK(uc.gamma == Catch::Approx(90.0).margin(0.5)); + } +} + TEST_CASE("LatticeSearch - cubic P") { // Simple cubic P, a=30 const double a = 30.0; -- 2.54.0 From 896953af6e55c70556cecdb2b8a5b545d4378b83 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 11 Nov 2026 12:06:21 +0100 Subject: [PATCH 02/96] RESULT-CHANGING: a harmonic pair of beam centres is carried to the merges instead of kept at the file's Where the file's centre and the measured one index lattices whose primitive volumes differ by an integer factor (2-4), the pooled excess over chance decided between them, and where it did not prefer the measured centre the file's was kept outright. On a harmonic pair that excess is often a near tie - the larger cell's extra nodes catch the spots a wrong centre displaces and its own null grows with them. Measured on an F-cubic small-molecule crystal whose file centre sits 4 px out across the spindle: 12.8x over chance on the doubled cell at the file's centre against 13.9x on the true cell at the measured one; the file's centre was kept, the first pass fell to the triclinic cell, the distance refined 2.9 mm long and the run ended on a cell 1.6 % long. Now, when the pooled spots do not adopt the measured centre, it is carried as beam_center_alternative_ - the mechanism the same-cell case already uses - and RunAllPasses runs the first pass again there and adopts whichever arm merges better. On that crystal: CC1/2 before corrections 0.913 at the measured centre against 0.564, cubic F at the true cell; R_meas 16.2 %, CC1/2 0.998. Costs one more first pass on runs whose two centres give a harmonic pair. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK --- rugnux/RugnuxFirstPassRescues.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/rugnux/RugnuxFirstPassRescues.cpp b/rugnux/RugnuxFirstPassRescues.cpp index e3baace32..0c7b4b0c7 100644 --- a/rugnux/RugnuxFirstPassRescues.cpp +++ b/rugnux/RugnuxFirstPassRescues.cpp @@ -855,7 +855,19 @@ auto Rugnux::FirstPassRun::CentresDisagree(FirstPass best, const FirstPass &alt, RestoreBeamCenter(header_x, header_y); } } - if (!adopted_measured) + // The pooled excess is a near tie on a harmonic pair more often than not: the larger cell's extra + // nodes catch the spots a wrong centre displaces, and its own null grows with them. Measured on a + // small-molecule crystal whose file centre sat 4 px out across the spindle: 12.8x over chance at + // the file's centre on the doubled cell against 13.9x at the measured one on the true cell, so the + // file's centre was kept and the run ended on a cell 1.6 % long. Rather than decide on that, carry + // the measured centre forward and judge the two arms on their merges (RunAllPasses), as the + // same-cell case above does. + if (!adopted_measured && integer_harmonic && geometry_prepass) { + rx.beam_center_alternative_ = std::array{measured_x, measured_y}; + logger.Warning("Beam centre check: the pooled spots do not settle which of the two " + "cells is the centre error, so the run keeps the file's centre for now " + "and carries the measured one forward, adopting whichever merges better"); + } else if (!adopted_measured) logger.Warning("Beam centre check: the run keeps the file's centre. The " "frame counts cannot arbitrate this - a cell twice as long " "must place every spot twice as accurately to score the " -- 2.54.0 From 82029ebc0e82f6d2c26b6a9ebeb4147c4e691a04 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 11 Nov 2026 12:41:38 +0100 Subject: [PATCH 03/96] RESULT-CHANGING: a harmonic pair of beam centres is decided on the point-symmetry capture first Where the file's centre and the background-walk centre index lattices whose primitive volumes differ by an integer factor (2-4), the decision was made on the pooled excess of spots over chance - a comparison of two working indexings, which on a harmonic pair can favour the wrong centre: on the battery it chose the centre holding the larger, false cell in 3 of 9 such runs, and on an F-centred cubic small-molecule crystal the file centre (4 px out across the spindle) was kept, the first pass fell to triclinic and the cell ended 1.6 % long. The pre-scan already measures the centre a second way, as the image's strongest point-symmetry centre, and only stills used it. Against each run's own final refined beam over the rotation runs of the battery its median error is 0.26 px (walk 1.17, file 2.89), and "the centre nearer the capture" picks the better of file and walk in 167 of 180 runs where they differ. It is now asked first on a harmonic pair: where its margin is at least 1.5 % (below ~1 % its surface is flat and it is uninformative) the centre nearer it is taken with its cell; the long- axis and sub-lattice hypotheses then test that cell on its own spots as before. Without an informative capture the pooled spots decide, and where they keep the file's centre the measured one is still carried to the merge (previous commit). Forced-centre runs of the ten harmonic-pair sets: the measured centre is the one that merges in five (four collapse at the file's centre: ISa 0.6-2.6 against 13-31), the file's in two - one where the capture is uninformative, unchanged, and one unscored P1 set that merges poorly at either centre, where the capture now takes the measured one - and the centre makes no difference in the rest. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK --- docs/CPU_DATA_ANALYSIS_IMAGE.md | 16 +++++++++---- docs/RUGNUX_CLI.md | 2 +- rugnux/Rugnux.cpp | 1 + rugnux/Rugnux.h | 4 +++- rugnux/RugnuxFirstPassRescues.cpp | 40 ++++++++++++++++++++++++++++--- rugnux/RugnuxPreScan.cpp | 4 +++- 6 files changed, 56 insertions(+), 11 deletions(-) diff --git a/docs/CPU_DATA_ANALYSIS_IMAGE.md b/docs/CPU_DATA_ANALYSIS_IMAGE.md index eb91d0157..ca528af07 100644 --- a/docs/CPU_DATA_ANALYSIS_IMAGE.md +++ b/docs/CPU_DATA_ANALYSIS_IMAGE.md @@ -232,10 +232,16 @@ within 2 %. The outcomes: the file's centre and its class are kept. 4. **Both index, on different lattices.** Both cells are reported in a warning. Where the primitive volumes differ by an integer factor of 2 to 4 — an axis harmonic, which a centre error along the - spindle produces — the choice is made on the **pooled validation spots**: the measured centre is - adopted where its lattice, larger or smaller, beats chance and puts a share of the spots on itself - over its own wrong-spindle null that exceeds the file's by more than the binomial noise of the two - (z = 3.29). Otherwise the file's centre is kept, with a warning. + spindle produces — the choice is made first on the pre-scan's **point-symmetry capture**, a second + reading of the same background by an estimator that sees neither lattice: where its margin is at + least 1.5 % (below ~1 % its surface is flat and its peak can be anywhere), the centre nearer the + capture is taken with its cell. Otherwise it is made on the **pooled validation spots**: the + measured centre is adopted where its lattice, larger or smaller, beats chance and puts a share of + the spots on itself over its own wrong-spindle null that exceeds the file's by more than the + binomial noise of the two (z = 3.29). Where that does not adopt it, the file's centre is kept for + the first pass and, on a two-pass run, both centres are carried to the merge (next paragraph). + Whichever cell a centre brings, the long-axis and sub-lattice hypotheses then test it on its own + spots, so a centre is not refused for the harmonic it first indexed. 5. **The file's centre does not index.** Nothing defends it, so: - where the measured centre indexes, it is **adopted**; - where it does not, the rotation-axis sign is flipped **at the measured centre**, since each of the @@ -249,7 +255,7 @@ within 2 %. The outcomes: file's centre is put back, so a run that fails for some other reason fails at the geometry it was given. -**Both centres judged on the merge (rotation, two-pass).** Where case 3 or 5 carried both centres, the +**Both centres judged on the merge (rotation, two-pass).** Where case 3, 4 or 5 carried both centres, the whole first pass is run again at the measured centre — with its own post-refinement — and the two arms are compared on their search merges ($P1$, whole range, before the correction surfaces). Where case 2 held a hypothesis, this is done only if the file's arm merges diff --git a/docs/RUGNUX_CLI.md b/docs/RUGNUX_CLI.md index 1e502d430..6718cd50d 100644 --- a/docs/RUGNUX_CLI.md +++ b/docs/RUGNUX_CLI.md @@ -207,7 +207,7 @@ The model is taken as placed in the data's cell, and refined in the data's space | Option | Description | | --- | --- | -| `--beam-center-check[=off]` | Measure the beam centre from the isotropy of the scattered background on **every** run, report how far the file's value is from it (against how right this geometry needs it to be), and on rotation data index a **second first pass** at the measured centre. The fit reads the projection `--detect-beam-stop` already builds, so it costs no extra frames. **On by default**; `=off` disables it, and with it the measured centre's part in the post-refinement bound. The measured centre is adopted where the file's centre indexes nothing and the measured one indexes a majority; where the two return cells related by an integer volume factor (2 to 4) and the measured centre's cell, larger or smaller, carries materially more of the pooled validation spots against its own chance level; and, on a two-pass run, where the first pass is run at both centres and the measured one merges better — done where the two give the same cell in different metric symmetries, where they agree only once less of each frame is read, and where they agree but lie further apart than the geometry absorbs and the file's centre merges inconsistently in the lowest-resolution shell. Otherwise the file's centre is kept. On stills, where there is no first pass, the file's centre, the measured one and the point-symmetry capture's are each indexed on a sample of frames and the one putting materially more spots on the lattice is taken; with `-C`, or after the geometry refinement has fitted a cell de novo, the detector distance is searched too (30-3000 mm, the file's value one hypothesis). Still runs when `--beam-x`/`--beam-y` are given. See [§1.4](CPU_DATA_ANALYSIS_IMAGE.md) | +| `--beam-center-check[=off]` | Measure the beam centre from the isotropy of the scattered background on **every** run, report how far the file's value is from it (against how right this geometry needs it to be), and on rotation data index a **second first pass** at the measured centre. The fit reads the projection `--detect-beam-stop` already builds, so it costs no extra frames. **On by default**; `=off` disables it, and with it the measured centre's part in the post-refinement bound. The measured centre is adopted where the file's centre indexes nothing and the measured one indexes a majority; where the two return cells related by an integer volume factor (2 to 4) and the pre-scan's point-symmetry capture (where its margin is at least 1.5 %) lies nearer the measured centre - without an informative capture, where the measured centre's cell, larger or smaller, carries materially more of the pooled validation spots against its own chance level, and otherwise the two centres are judged on the merge; and, on a two-pass run, where the first pass is run at both centres and the measured one merges better — done where the two give the same cell in different metric symmetries, where they agree only once less of each frame is read, and where they agree but lie further apart than the geometry absorbs and the file's centre merges inconsistently in the lowest-resolution shell. Otherwise the file's centre is kept. On stills, where there is no first pass, the file's centre, the measured one and the point-symmetry capture's are each indexed on a sample of frames and the one putting materially more spots on the lattice is taken; with `-C`, or after the geometry refinement has fitted a cell de novo, the detector distance is searched too (30-3000 mm, the file's value one hypothesis). Still runs when `--beam-x`/`--beam-y` are given. See [§1.4](CPU_DATA_ANALYSIS_IMAGE.md) | ### Spot finding diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index 1bd222a86..48fb8ed89 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -750,6 +750,7 @@ void Rugnux::JoinBeamCenterCapture() { const BackgroundCentres c = background_center_capture_.get(); background_center_ = c.measured; point_symmetry_center_ = c.point_symmetry; + point_symmetry_margin_ = c.point_symmetry_margin; measured_beam_center_ = background_center_; background_center_capture_ = {}; } diff --git a/rugnux/Rugnux.h b/rugnux/Rugnux.h index c1f18ef09..720dba98f 100644 --- a/rugnux/Rugnux.h +++ b/rugnux/Rugnux.h @@ -1337,9 +1337,11 @@ class Rugnux { struct BackgroundCentres { std::optional measured; std::optional> point_symmetry; + float point_symmetry_margin = 0.0f; // (best - runner-up) / best of the capture's peaks }; std::shared_future background_center_capture_; - std::optional> point_symmetry_center_; + std::optional> point_symmetry_center_; // a direct beam, not a PONI + float point_symmetry_margin_ = 0.0f; void JoinBeamCenterCapture(); // The centre the background measured, kept when the two centres index the SAME cell but a // different metric symmetry off it and the disagreement is by itself large enough to have decided diff --git a/rugnux/RugnuxFirstPassRescues.cpp b/rugnux/RugnuxFirstPassRescues.cpp index 0c7b4b0c7..54eeacc0f 100644 --- a/rugnux/RugnuxFirstPassRescues.cpp +++ b/rugnux/RugnuxFirstPassRescues.cpp @@ -827,8 +827,42 @@ auto Rugnux::FirstPassRun::CentresDisagree(FirstPass best, const FirstPass &alt, const double nearest_n = std::round(ratio); const bool integer_harmonic = nearest_n >= 2.0 && nearest_n <= 4.0 && std::abs(ratio - nearest_n) < 0.15; + // First asked of the THIRD reading of the centre, the pre-scan's point-symmetry capture: the + // image's centrosymmetry about the point the beam lands on, read off the same background by a + // different estimator than the isotropy walk that gave the measured centre, and touching neither + // lattice. Both statistics below compare two working indexings, and on a harmonic pair both + // can point at the wrong centre - which is why the capture is asked first. The two centres are + // judged by which one the capture lies nearer. Measured on the rotation runs of the battery + // against each run's own final refined beam: median error 0.26 px for the capture, 1.17 for the + // walk, 2.89 for the file; "the one nearer the capture" picked the better of file and walk in + // 167 of 180 runs where they differed, and on the harmonic pairs every case where the centre + // decided the result (four adoptions of the measured centre, and the small-molecule crystal of + // the merge-arm comment below, where the pooled spots chose wrong). The capture's margin is its + // own confidence: a margin under ~1 % is a flat surface and its peak can be anywhere (median + // 6.9 px off there, against 0.2-0.3 px from 1.5 % up), and then the pooled spots decide as before. + constexpr float MIN_CAPTURE_MARGIN = 0.015f; bool adopted_measured = false; - if (integer_harmonic) { + bool decided_by_capture = false; + if (integer_harmonic && rx.point_symmetry_center_ && rx.point_symmetry_margin_ >= MIN_CAPTURE_MARGIN) { + // The capture is a direct beam, the two centres are PONIs: put it in their frame. + const auto [direct_x, direct_y] = rx.experiment_.GetDiffractionGeometry().GetDirectBeam_pxl(); + const float capture_x = rx.point_symmetry_center_->first - (direct_x - header_x); + const float capture_y = rx.point_symmetry_center_->second - (direct_y - header_y); + const float to_file = std::hypot(capture_x - header_x, capture_y - header_y); + const float to_measured = std::hypot(capture_x - measured_x, capture_y - measured_y); + decided_by_capture = true; + adopted_measured = to_measured < to_file; + logger.Warning("Beam centre check: the point-symmetry capture puts the beam at ({:.2f},{:.2f}), " + "margin {:.1f}%, {:.2f} px from the measured centre and {:.2f} px from the file's - " + "the run takes the {} centre and its {} cell", + capture_x, capture_y, 100.0f * rx.point_symmetry_margin_, to_measured, to_file, + adopted_measured ? "measured" : "file's", + (adopted_measured ? alt.vol > best.vol : best.vol > alt.vol) ? "larger" : "smaller"); + if (adopted_measured) { + TryBeamCenter(measured_x, measured_y); + best = alt; + } + } else if (integer_harmonic) { const PooledEvidence at_header = MeasurePooled(*indexer, *best.result); TryBeamCenter(measured_x, measured_y); const PooledEvidence at_measured = MeasurePooled(*indexer, *alt.result); @@ -862,12 +896,12 @@ auto Rugnux::FirstPassRun::CentresDisagree(FirstPass best, const FirstPass &alt, // file's centre was kept and the run ended on a cell 1.6 % long. Rather than decide on that, carry // the measured centre forward and judge the two arms on their merges (RunAllPasses), as the // same-cell case above does. - if (!adopted_measured && integer_harmonic && geometry_prepass) { + if (!adopted_measured && !decided_by_capture && integer_harmonic && geometry_prepass) { rx.beam_center_alternative_ = std::array{measured_x, measured_y}; logger.Warning("Beam centre check: the pooled spots do not settle which of the two " "cells is the centre error, so the run keeps the file's centre for now " "and carries the measured one forward, adopting whichever merges better"); - } else if (!adopted_measured) + } else if (!adopted_measured && !decided_by_capture) logger.Warning("Beam centre check: the run keeps the file's centre. The " "frame counts cannot arbitrate this - a cell twice as long " "must place every spot twice as accurately to score the " diff --git a/rugnux/RugnuxPreScan.cpp b/rugnux/RugnuxPreScan.cpp index 248a018ff..00ce896dd 100644 --- a/rugnux/RugnuxPreScan.cpp +++ b/rugnux/RugnuxPreScan.cpp @@ -1291,8 +1291,10 @@ void Rugnux::PreScan(int start_image, int images_to_process, int frame_count, Ru : "this is not a rotation sweep, so there is no spindle to resolve it on"); } BackgroundCentres centres{background_center, std::nullopt}; - if (!capture.point.empty()) + if (!capture.point.empty()) { centres.point_symmetry = std::make_pair(capture.point[0].beam_x_pxl, capture.point[0].beam_y_pxl); + centres.point_symmetry_margin = capture.margin_point; + } return centres; }).share(); -- 2.54.0 From 6df0bd3a6a2cfbf08c1723da912cc60938ce5665 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 11 Nov 2026 13:07:20 +0100 Subject: [PATCH 04/96] Stream receivers, shared pieces: puller drop count, image slots kept free, analysis of a stream image Taken from rc186 (merge 2250e4019, net of 7f2ebd820) as the stream receivers need them: - ImagePuller::GetDroppedImages (15266b21e), and the puller comments as rc186 has them (0d26ecd13, 3047ccd13); ReceiveRingBuffer's mutex renamed (0d26ecd13). - ImageBuffer::GetImageSlot(keep_free), for JFJochReceiverRugnuxStills, and the unused slots_idle counter dropped (8bad85f9f, b0db94658). The default keeps every existing call as it was. - MXAnalysisWithoutFPGA::AnalyzeStreamImage, which keeps the saturated and error counts a stream states (4c3a7852e). - DarkMaskAnalysis::AnalyzeImage takes its buffer by reference (5d1fb1a48). - The device memory the indexers hold: Indexer/FFTIndexerGPU/IndexerThreadPool::DeviceBytes (0df766328), read by the live stills model step. Left out: nothing of these files; their FPGA-side callers keep their calls and behaviour. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK --- common/ImageBuffer.cpp | 8 ++--- common/ImageBuffer.h | 4 +-- image_analysis/MXAnalysisWithoutFPGA.cpp | 13 +++++++++ image_analysis/MXAnalysisWithoutFPGA.h | 8 +++-- .../dark_mask_analysis/DarkMaskAnalysis.cpp | 2 +- .../dark_mask_analysis/DarkMaskAnalysis.h | 2 +- image_analysis/indexing/FFTIndexerGPU.cu | 10 +++++++ image_analysis/indexing/FFTIndexerGPU.h | 1 + image_analysis/indexing/Indexer.h | 2 ++ image_analysis/indexing/IndexerThreadPool.cpp | 14 +++++++++ image_analysis/indexing/IndexerThreadPool.h | 9 ++++++ image_puller/ImagePuller.h | 23 +++++++-------- image_puller/ReceiveRingBuffer.cpp | 29 ++++++++----------- image_puller/ReceiveRingBuffer.h | 23 ++------------- image_puller/TCPImagePuller.cpp | 27 +++++------------ image_puller/TCPImagePuller.h | 3 +- image_puller/ZMQImagePuller.cpp | 19 ++++-------- image_puller/ZMQImagePuller.h | 3 +- 18 files changed, 99 insertions(+), 101 deletions(-) diff --git a/common/ImageBuffer.cpp b/common/ImageBuffer.cpp index ee496f880..faa52b817 100644 --- a/common/ImageBuffer.cpp +++ b/common/ImageBuffer.cpp @@ -69,7 +69,6 @@ void ImageBuffer::StartMeasurement(size_t in_location_size) { slots_sending = 0; slots_preparation = 0; slots_in_preview = 0; - slots_idle = 0; // Clear queue and vectors while (!free_slots.empty()) @@ -109,15 +108,13 @@ bool ImageBuffer::CheckIfBufferReturned(std::chrono::microseconds timeout) { return (slots_preparation == 0) && (slots_sending == 0); } -ZeroCopyReturnValue *ImageBuffer::GetImageSlot() { +ZeroCopyReturnValue *ImageBuffer::GetImageSlot(int64_t keep_free) { std::unique_lock ul(m); - if (!disable_requests && !free_slots.empty()) { + if (!disable_requests && static_cast(free_slots.size()) > keep_free) { auto i = free_slots.front(); free_slots.pop(); - if (v[i].status == ImageBufferEntryStatus::Idle) - --slots_idle; v[i].status = ImageBufferEntryStatus::InPreparation; v[i].image_number = INT64_MIN; v[i].image_size = 0; @@ -181,7 +178,6 @@ void ImageBuffer::ReleaseSlot(uint32_t location) { if (v[location].status != ImageBufferEntryStatus::Sending) return; - ++slots_idle; --slots_sending; v[location].status = ImageBufferEntryStatus::Idle; free_slots.push(location); diff --git a/common/ImageBuffer.h b/common/ImageBuffer.h index 0e856a446..1f87eca9d 100644 --- a/common/ImageBuffer.h +++ b/common/ImageBuffer.h @@ -47,7 +47,6 @@ class ImageBuffer { int64_t slots_preparation = 0; int64_t slots_sending = 0; int64_t slots_in_preview = 0; - int64_t slots_idle = 0; bool disable_requests = true; // All requests fail in this state int64_t counter = 0; // Counter is increased when something changes in the image buffer @@ -76,7 +75,8 @@ public: void StartMeasurement(size_t buffer_location_size); void StartMeasurement(const DiffractionExperiment& experiment); - ZeroCopyReturnValue *GetImageSlot(); + // Nullptr where no more than `keep_free` slots are free. + ZeroCopyReturnValue *GetImageSlot(int64_t keep_free = 0); void ReadyToSend(uint32_t location, int64_t image_number, size_t image_size, bool indexed); void ReleaseSlot(uint32_t location); diff --git a/image_analysis/MXAnalysisWithoutFPGA.cpp b/image_analysis/MXAnalysisWithoutFPGA.cpp index 010d895c3..1a68b6481 100644 --- a/image_analysis/MXAnalysisWithoutFPGA.cpp +++ b/image_analysis/MXAnalysisWithoutFPGA.cpp @@ -446,6 +446,19 @@ void MXAnalysisWithoutFPGA::RebuildROI() { roi = std::make_unique(experiment); } +void MXAnalysisWithoutFPGA::AnalyzeStreamImage(DataMessage &output, + AzimuthalIntegrationProfile &profile, + const SpotFindingSettings &spot_finding_settings) { + // In an unsigned image from the card saturated and error pixels have the same value: only the card knows which is which + const auto saturated_pixel_count = output.saturated_pixel_count; + const auto error_pixel_count = output.error_pixel_count; + Analyze(output, profile, spot_finding_settings); + if (saturated_pixel_count) + output.saturated_pixel_count = saturated_pixel_count; + if (error_pixel_count) + output.error_pixel_count = error_pixel_count; +} + void MXAnalysisWithoutFPGA::AnalyzeROIOnly(DataMessage &output) { if ((output.image.GetWidth() != xpixels) || (output.image.GetWidth() * output.image.GetHeight() != npixels)) diff --git a/image_analysis/MXAnalysisWithoutFPGA.h b/image_analysis/MXAnalysisWithoutFPGA.h index 8d3e0708a..63e7d2eb4 100644 --- a/image_analysis/MXAnalysisWithoutFPGA.h +++ b/image_analysis/MXAnalysisWithoutFPGA.h @@ -60,9 +60,9 @@ class MXAnalysisWithoutFPGA { ImageSpotFinder &FixedThresholdFinder(); // Self-calibrating finder, used when spot settings request adaptive detection. Kept alongside the // default finder because the choice arrives with the per-image settings, not at construction. It is - // an AdaptiveSpotFinderCPU by default; on the GPU path, when the fused engine is enabled (rugnux - // offline only), it is instead an AdaptiveSpotFinderGPU that also computes the azimuthal profile, - // aliased through fused_adaptive so Analyze() can take that profile and skip the separate azint pass. + // an AdaptiveSpotFinderCPU by default; on the GPU path, when the fused engine is enabled (rugnux, + // the viewer and the receivers), it is instead an AdaptiveSpotFinderGPU that also computes the + // azimuthal profile, aliased through fused_adaptive so Analyze() can take that profile and skip the separate azint pass. std::unique_ptr adaptiveSpotFinder; AdaptiveSpotFinderGPU *fused_adaptive = nullptr; // The CPU finder, where it gives the profile too (the azimuthal integration being on the CPU). @@ -117,6 +117,8 @@ public: MXAnalysisWithoutFPGA(const DiffractionExperiment &experiment, const AzimuthalIntegrationMapping &integration, const PixelMask &mask, IndexAndRefine &indexer, bool enable_fused_adaptive_gpu = false); void Analyze(DataMessage &output, AzimuthalIntegrationProfile &profile, const SpotFindingSettings &spot_finding_settings); + // Analyze for an image from a stream: saturated and error counts the stream states stay, a DECTRIS message has none. + void AnalyzeStreamImage(DataMessage &output, AzimuthalIntegrationProfile &profile, const SpotFindingSettings &spot_finding_settings); // Surgical ROI-only paths used when a full re-analysis is not wanted: rebuild the // ROI engine after the ROI set changes, recompute ROIs after preprocessing a new diff --git a/image_analysis/dark_mask_analysis/DarkMaskAnalysis.cpp b/image_analysis/dark_mask_analysis/DarkMaskAnalysis.cpp index 3a53bc3d3..f12cc17b1 100644 --- a/image_analysis/dark_mask_analysis/DarkMaskAnalysis.cpp +++ b/image_analysis/dark_mask_analysis/DarkMaskAnalysis.cpp @@ -19,7 +19,7 @@ void DarkMaskAnalysis::check(const T *ptr) { } } -void DarkMaskAnalysis::AnalyzeImage(const DataMessage &data, std::vector buffer) { +void DarkMaskAnalysis::AnalyzeImage(const DataMessage &data, std::vector &buffer) { if (data.image.GetWidth() * data.image.GetHeight() != mask.size()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Invalid size of input image"); diff --git a/image_analysis/dark_mask_analysis/DarkMaskAnalysis.h b/image_analysis/dark_mask_analysis/DarkMaskAnalysis.h index c9e8bb4e3..90e1a9eaf 100644 --- a/image_analysis/dark_mask_analysis/DarkMaskAnalysis.h +++ b/image_analysis/dark_mask_analysis/DarkMaskAnalysis.h @@ -21,7 +21,7 @@ class DarkMaskAnalysis { template void check(const T *ptr); public: DarkMaskAnalysis(const DarkMaskSettings& settings, size_t det_size); - void AnalyzeImage(const DataMessage &msg, std::vector buffer); + void AnalyzeImage(const DataMessage &msg, std::vector &buffer); std::vector GetMask() const; }; diff --git a/image_analysis/indexing/FFTIndexerGPU.cu b/image_analysis/indexing/FFTIndexerGPU.cu index 234820928..d048e093f 100644 --- a/image_analysis/indexing/FFTIndexerGPU.cu +++ b/image_analysis/indexing/FFTIndexerGPU.cu @@ -263,6 +263,16 @@ FFTIndexerGPU::FFTIndexerGPU(const IndexingSettings &settings) } +size_t FFTIndexerGPU::DeviceBytes() const { + const size_t batch = std::min(nDirections, FFT_BATCH_DIRECTIONS); + const size_t out_len = histogram_size / 2 + 1; + const size_t directions = nDirections; + return batch * histogram_size * sizeof(float) + batch * out_len * sizeof(cufftComplex) + + directions * out_len * sizeof(float) + directions * sizeof(FFTResult) + + 3 * FFT_MAX_SPOTS * sizeof(float) + 3 * directions * sizeof(float) + + plan.WorkBytes() + (nDirections % batch != 0 ? plan_last.WorkBytes() : 0); +} + void FFTIndexerGPU::DirectionsChanged() { std::vector dir_x(nDirections), dir_y(nDirections), dir_z(nDirections); for (int i = 0; i < nDirections; i++) { diff --git a/image_analysis/indexing/FFTIndexerGPU.h b/image_analysis/indexing/FFTIndexerGPU.h index 99cf0c3e5..03658bacf 100644 --- a/image_analysis/indexing/FFTIndexerGPU.h +++ b/image_analysis/indexing/FFTIndexerGPU.h @@ -58,6 +58,7 @@ public: FFTIndexerGPU(const FFTIndexerGPU &i) = delete; const FFTIndexerGPU &operator=(const FFTIndexerGPU &i) = delete; ~FFTIndexerGPU() override = default; + size_t DeviceBytes() const override; }; diff --git a/image_analysis/indexing/Indexer.h b/image_analysis/indexing/Indexer.h index 903a38737..0ecb7ec57 100644 --- a/image_analysis/indexing/Indexer.h +++ b/image_analysis/indexing/Indexer.h @@ -86,6 +86,8 @@ protected: virtual std::optional RunSeverityOnly(const std::vector &coord) { return {}; } public: virtual ~Indexer() = default; + // The device memory the indexer holds for as long as it lives; 0 for one on the CPU. + virtual size_t DeviceBytes() const { return 0; } void Setup(const DiffractionExperiment& experiment); // severity_only = true runs RunSeverityOnly instead of indexing: no lattice comes back and // `executed` stays false, since nothing was an indexing attempt. Used when the seed escalation diff --git a/image_analysis/indexing/IndexerThreadPool.cpp b/image_analysis/indexing/IndexerThreadPool.cpp index d849acb01..013ea4e51 100644 --- a/image_analysis/indexing/IndexerThreadPool.cpp +++ b/image_analysis/indexing/IndexerThreadPool.cpp @@ -259,6 +259,10 @@ void IndexerThread::Worker(int threadid) { { std::unique_lock lock(m); + device_ = get_gpu(); + for (const auto *i : {fft_indexer.get(), ffbidx_indexer.get(), fftw_indexer.get()}) + if (i) + device_bytes_ += i->DeviceBytes(); state = TaskState::IDLE; } c_running.notify_all(); @@ -405,6 +409,16 @@ IndexerThreadPool::IndexerThreadPool(const IndexingSettings &settings, IndexerCo tasks.emplace_back(std::make_unique(std::cref(settings), i, construction, beside)); } +size_t IndexerThreadPool::DeviceBytes() const { + std::map by_card; + for (const auto &t : tasks) + by_card[t->Device()] += t->DeviceBytes(); + size_t most = 0; + for (const auto &[card, bytes] : by_card) + most = std::max(most, bytes); + return most; +} + void IndexerThreadPool::ReleaseIndexers() { for (auto &t : tasks) t->Release(); diff --git a/image_analysis/indexing/IndexerThreadPool.h b/image_analysis/indexing/IndexerThreadPool.h index 1a61be816..3f2f62b48 100644 --- a/image_analysis/indexing/IndexerThreadPool.h +++ b/image_analysis/indexing/IndexerThreadPool.h @@ -101,6 +101,9 @@ class IndexerThread { std::unique_ptr result = nullptr; std::unique_ptr task_input = nullptr; std::thread worker_thread; + // The card the worker is pinned to, and the device memory of the indexers it built before it was ready + int device_ = 0; + size_t device_bytes_ = 0; void Worker(int threadid); public: @@ -112,6 +115,8 @@ public: // them again on the next frame that needs one. Returns when they are gone. void Release(); void Finalize(); + [[nodiscard]] int Device() const { return device_; } + [[nodiscard]] size_t DeviceBytes() const { return device_bytes_; } }; class IndexerThreadPool { @@ -140,6 +145,10 @@ public: // 0.6-1.2 GB of it - for a stretch with nothing to index. A frame that comes after all builds its // indexer again, as on first use, and the indexer it gets computes what the dropped one would. void ReleaseIndexers(); + // The device memory the indexers built with the pool (IndexerConstruction::Preconstruct) hold, on the + // card they hold most on: what is resident beside anything else that plans its GPU memory on a card while + // the pool lives (model validation, molecular replacement, refinement). + [[nodiscard]] size_t DeviceBytes() const; }; diff --git a/image_puller/ImagePuller.h b/image_puller/ImagePuller.h index b399fb63e..fec35d311 100644 --- a/image_puller/ImagePuller.h +++ b/image_puller/ImagePuller.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "../common/ZMQWrappers.h" #include "../frame_serialize/CBORStream2Deserializer.h" @@ -32,14 +33,11 @@ struct RawFrame { }; struct ImagePullerOutput { - // The message as it came off the socket, in the puller's ring buffer. It stays there for as long as - // any copy of this struct is alive - the deserialised image in cbor points into it, and republish - // sends it as is. + // The message as it came off the socket, in the ring buffer; held there as long as any copy of this struct lives std::shared_ptr raw; std::shared_ptr tcp_msg; std::shared_ptr cbor; - // When the message came off the socket - before it was queued or deserialised, so a consumer that - // falls behind does not shift it. + // When the message came off the socket, before it was queued or deserialised std::chrono::steady_clock::time_point received{}; }; @@ -50,16 +48,12 @@ struct ImagePullerStatistics { class ImagePuller { protected: ThreadSafeFIFO outside_fifo; // assuming 1 file is <1000 files, this should be enough to buffer - // Every received message is held here until consumed; the queues count messages, this bounds bytes. - // Size 0 means no ring: messages are held on the heap, bounded by the queues alone, as they always - // were - the broker's own DECTRIS puller runs that way, jfjoch_writer sets a size. + // Holds every received message until it is consumed. The queues count messages, the ring bounds bytes. + // With size 0 there is no ring: messages are held on the heap, bounded by the queues alone. ReceiveRingBuffer ring; - // Bytes republish holds right now. Republish keeps the OLDEST not yet sent images, and the ring is - // reclaimed in arrival order, so an image waiting for a slow republish consumer pins everything - // received after it - the writer would then run at the republish rate. What republish may hold is - // therefore capped at a share of the ring; an image over the cap is not republished, exactly as one - // that finds the republish queue full. Without a ring there is no cap. + // Bytes republish holds now. Republish keeps the oldest unsent images and the ring is reclaimed in arrival order, + // so a slow republish consumer would pin everything received after them. That is why it is capped at a share of the ring. std::atomic repub_bytes{0}; bool RepubCanHold(size_t bytes) const { return ring.GetCapacity() == 0 || repub_bytes.load() + bytes <= ring.GetCapacity() / 4; @@ -81,6 +75,9 @@ public: virtual bool SendAck(const PullerAckMessage &) { return false; } virtual void Disconnect() = 0; + + // Images the puller had no room for, which the receiver therefore never gets. + virtual size_t GetDroppedImages() const { return 0; } }; diff --git a/image_puller/ReceiveRingBuffer.cpp b/image_puller/ReceiveRingBuffer.cpp index c01b86c1e..f8533e608 100644 --- a/image_puller/ReceiveRingBuffer.cpp +++ b/image_puller/ReceiveRingBuffer.cpp @@ -7,24 +7,21 @@ #include struct ReceiveRingBuffer::State { - // Uninitialised on purpose: a ring that is never filled should not cost its capacity in - // resident memory. + // Uninitialised: a ring that is never filled should not cost its capacity in resident memory std::unique_ptr memory; const size_t capacity; - std::mutex m; + std::mutex state_mutex; std::condition_variable cv; - // Live regions in arrival order. Only the oldest can give back space, so a region released out of - // turn is marked and waits. + // Live regions in arrival order; only the oldest can give back space struct Slot { size_t offset; size_t size; bool released = false; }; std::deque slots; - // Next byte to hand out. The ring is empty when slots is empty; the oldest live byte is then - // slots.front().offset. + // Next byte to hand out; the oldest live byte is slots.front().offset size_t tail = 0; size_t utilization = 0; @@ -35,14 +32,12 @@ struct ReceiveRingBuffer::State { : memory(in_capacity > 0 ? std::make_unique_for_overwrite(in_capacity) : nullptr), capacity(in_capacity) {} - // Where a region of `size` bytes would start, or nothing if it does not fit right now. Called - // under the mutex. + // Where a region of `size` bytes would start, nothing if it does not fit now; under the mutex std::optional FindSpace(size_t size) const { if (slots.empty()) return 0; const size_t head = slots.front().offset; - // tail has come round to head: treated as full. (After a wrap the piece between the old tail - // and the end is free, but it is behind the live data and not worth tracking.) + // tail has come round to head: treated as full if (tail == head) return std::nullopt; if (tail > head) { @@ -60,7 +55,7 @@ struct ReceiveRingBuffer::State { } void Release(size_t offset) { - std::unique_lock ul(m); + std::unique_lock ul(state_mutex); for (auto &s: slots) { if (s.offset == offset) { s.released = true; @@ -77,7 +72,7 @@ struct ReceiveRingBuffer::State { } void ReleaseHeap(size_t size) { - std::unique_lock ul(m); + std::unique_lock ul(state_mutex); utilization -= size; } }; @@ -89,7 +84,7 @@ std::shared_ptr ReceiveRingBuffer::Allocate(size_t size) { if (size == 0) return std::make_shared(); - std::unique_lock ul(state->m); + std::unique_lock ul(state->state_mutex); if (size > state->capacity) { // Heap fallback: counted, not bounded. @@ -126,7 +121,7 @@ std::shared_ptr ReceiveRingBuffer::Allocate(size_t size) { } void ReceiveRingBuffer::Stop() { - std::unique_lock ul(state->m); + std::unique_lock ul(state->state_mutex); state->stopped = true; state->cv.notify_all(); } @@ -136,11 +131,11 @@ size_t ReceiveRingBuffer::GetCapacity() const { } size_t ReceiveRingBuffer::GetUtilization() const { - std::unique_lock ul(state->m); + std::unique_lock ul(state->state_mutex); return state->utilization; } size_t ReceiveRingBuffer::GetMaxUtilization() const { - std::unique_lock ul(state->m); + std::unique_lock ul(state->state_mutex); return state->max_utilization; } diff --git a/image_puller/ReceiveRingBuffer.h b/image_puller/ReceiveRingBuffer.h index c1831117a..55eaad63d 100644 --- a/image_puller/ReceiveRingBuffer.h +++ b/image_puller/ReceiveRingBuffer.h @@ -9,37 +9,20 @@ #include #include -// A received message lives here from the moment it comes off the socket until the last consumer is -// done with it - the writer has stored the image and the republish thread has sent it. Both consumers -// hold a shared_ptr to the region, and the region's destructor is what gives the bytes back. +// A received message, from the socket until the last consumer lets go of the shared_ptr to it struct ReceiveBufferRegion { uint8_t *data = nullptr; size_t size = 0; }; -// Bounds the memory a puller holds in received, not yet consumed, messages - by bytes, which is the -// quantity that matters, where the queues in front of and behind it count messages. One image of a -// large detector is tens of megabytes, so a queue of a thousand of them is a quantity of memory nobody -// chose. -// -// Regions are handed out in arrival order and the space is taken back in arrival order too, which is -// what makes it a ring: the writer and the republish thread may finish with messages out of order, so -// a region released early is only remembered, and the space is reclaimed once everything received -// before it is released as well. Republish holds at most its queue's worth of messages, so that is as -// far as the reclaim can lag. -// -// A message larger than the whole ring goes on the heap instead, so an oversized START or END message -// cannot wedge the receiver; everything else waits for space, which is the back-pressure that keeps the -// sender from running ahead. +// Bounds the bytes a puller holds in received messages, handed out and taken back in arrival order class ReceiveRingBuffer { struct State; std::shared_ptr state; public: explicit ReceiveRingBuffer(size_t capacity); - // Blocks until the bytes are available; returns nullptr once Stop() has been called. A request - // larger than the capacity (so every request of a ring of capacity 0) is served from the heap, - // at once and whether stopped or not. + // Blocks for the bytes; nullptr once stopped. A request larger than the capacity is served from the heap, at once std::shared_ptr Allocate(size_t size); // Releases every waiter in Allocate; the regions already handed out stay valid. diff --git a/image_puller/TCPImagePuller.cpp b/image_puller/TCPImagePuller.cpp index 2f27b2ec9..d85fa29c7 100644 --- a/image_puller/TCPImagePuller.cpp +++ b/image_puller/TCPImagePuller.cpp @@ -134,8 +134,7 @@ void TCPImagePuller::CBORThread() { } else { ret.cbor = CBORStream2Deserialize(ret.raw ? ret.raw->data : nullptr, ret.raw ? ret.raw->size : 0); outside_fifo.PutBlocking(ret); - // A PREFLIGHT carries a StartMessage payload but starts nothing, so republishing it - // would announce a run to downstream consumers that is not going to happen. + // A PREFLIGHT starts nothing, so it is not republished if (repub_socket && (type != TCPFrameType::PREFLIGHT)) { if ((ret.cbor->msg_type == CBORImageType::START) || (ret.cbor->msg_type == CBORImageType::END)) @@ -145,8 +144,7 @@ void TCPImagePuller::CBORThread() { } } } catch (const std::exception &e) { - // std::exception: the deserialiser allocates a vector per per-image array of an END message, - // and a bad_alloc leaving this thread would end the process. + // std::exception: a bad_alloc in the deserialiser leaving this thread would end the process logger.ErrorException(e); } ret = cbor_fifo.GetBlocking(); @@ -236,10 +234,7 @@ bool TCPImagePuller::EnsureConnected() { if (receive_buffer_size) setsockopt(new_fd, SOL_SOCKET, SO_RCVBUF, &receive_buffer_size.value(), sizeof(int32_t)); - // OS-level TCP keep-alive: detect a silently-dead pusher (no RST, e.g. crash or - // network partition) even while recv() is blocked in a long inter-image gap. Mirror - // the pusher's settings so detection is symmetric (~60s) and stays well clear of the - // 250 ms app-level BUSY heartbeat. + // Detects a silently dead pusher while recv() blocks; the pusher's settings, well clear of the 250 ms heartbeat int one = 1; setsockopt(new_fd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one)); int idle = 30; @@ -380,10 +375,7 @@ void TCPImagePuller::ReceiverThread() { out.tcp_msg->header = frame.header; if (frame.header.payload_size > 0) { - // Read straight into the ring (or, with no ring, onto the heap). A full ring holds the - // read here, the socket buffer fills, and the pusher's send blocks - its BUSY heartbeats - // tell it apart from a dead writer, and it gives up only after a minute in which not one - // image was written. + // A full ring holds the read here: the socket buffer fills and the pusher's send blocks out.raw = ring.Allocate(frame.header.payload_size); if (!out.raw) break; // Stop() - the puller is being disconnected. @@ -411,10 +403,7 @@ void TCPImagePuller::ReceiverThread() { } void TCPImagePuller::HeartbeatThread() { - // While connected, periodically tell the pusher we are alive even if the - // consuming pipeline is stalled (e.g. blocked on a slow filesystem). This lets - // the pusher distinguish a busy-but-healthy writer from a dead one and keep - // waiting through arbitrarily long backpressure instead of dropping the run. + // Tells the pusher we are alive even if the consuming pipeline is stalled while (!disconnect) { // Sleep in small slices so shutdown stays prompt. for (int i = 0; i < 5 && !disconnect; i++) @@ -434,8 +423,7 @@ void TCPImagePuller::HeartbeatThread() { h.ack_fifo_occupancy = cbor_fifo.GetCurrentUtilization(); h.ack_fifo_max_occupancy = cbor_fifo.Size(); - // Best effort: a failure here just means the socket is gone, which - // ReceiverThread will detect and reconnect on its own. + // Best effort: ReceiverThread notices a socket that is gone std::lock_guard lg(send_mutex); SendAll(&h, sizeof(h)); } @@ -447,8 +435,7 @@ void TCPImagePuller::Disconnect() { CloseSocket(); - // Same reason as in ZMQImagePuller: once the puller is going away nobody drains outside_fifo, - // so a full queue would leave CBORThread blocked in PutBlocking and the joins below hanging. + // As in ZMQImagePuller: nobody drains outside_fifo any more, and the joins below would hang cbor_fifo.Stop(); repub_fifo.Stop(); outside_fifo.Stop(); diff --git a/image_puller/TCPImagePuller.h b/image_puller/TCPImagePuller.h index 151e931e1..ff229c11c 100644 --- a/image_puller/TCPImagePuller.h +++ b/image_puller/TCPImagePuller.h @@ -31,8 +31,7 @@ class TCPImagePuller : public ImagePuller { std::thread repub_thread; std::thread heartbeat_thread; - // Serializes all writer->pusher sends (ACKs, keepalive pongs, busy heartbeats) - // so that concurrently-sent frames cannot interleave on the socket. + // Frames sent to the pusher (ACKs, pongs, busy heartbeats) must not interleave on the socket std::mutex send_mutex; Logger logger{"TCPImagePuller"}; diff --git a/image_puller/ZMQImagePuller.cpp b/image_puller/ZMQImagePuller.cpp index e606d4d4d..377e88906 100644 --- a/image_puller/ZMQImagePuller.cpp +++ b/image_puller/ZMQImagePuller.cpp @@ -40,10 +40,7 @@ ZMQImagePuller::~ZMQImagePuller() { void ZMQImagePuller::Disconnect() { disconnect = 1; - // The threads below hand messages on through bounded blocking queues, and by the time a puller - // is disconnected nothing drains outside_fifo any more - the receiver that did is gone. A full - // queue would then park CBORThread in PutBlocking for ever, back up cbor_fifo and park - // PullerThread too, and the joins below would never return. Stop() releases them. + // Nothing drains outside_fifo any more: without Stop() a full queue would leave the joins below hanging cbor_fifo.Stop(); repub_fifo.Stop(); outside_fifo.Stop(); @@ -87,11 +84,8 @@ void ZMQImagePuller::PullerThread() { ret.raw = std::shared_ptr(new ReceiveBufferRegion{const_cast(msg->data()), msg->size()}, [msg](ReceiveBufferRegion *r) { delete r; }); } else { - // Copied into the ring, and the ZeroMQ message is released with the next receive. When the - // ring is full this waits and the socket is not read: its queue fills to the watermark and - // the sender's send blocks - for as long as the sender is prepared to wait. The broker's - // ZeroMQ pusher waits one second, then drops the image and sends without waiting for the - // rest of the run, so a ring that is too small for the writer's stalls costs images. + // When the ring is full this waits and the socket is not read, so the sender's send blocks. + // The broker's ZeroMQ pusher waits one second, then drops the image, so a ring too small for the writer's stalls costs images. ret.raw = ring.Allocate(msg->size()); if (!ret.raw) { // Stop() - the puller is being disconnected. @@ -111,9 +105,7 @@ void ZMQImagePuller::CBORThread() { while (ret.raw) { try { ret.cbor = CBORStream2Deserialize(ret.raw->data, ret.raw->size); - // Even if we suspend consuming of the messages by the receiver, - // it is still reasonable to have them republished - // so republish functionality is not affected by Suspend() + // Suspend() of the receiver does not affect republish if (!suspend) outside_fifo.PutBlocking(ret); if (repub_socket) { @@ -124,8 +116,7 @@ void ZMQImagePuller::CBORThread() { repub_bytes += ret.raw->size; } } catch (const std::exception &e) { - // std::exception: the deserialiser allocates a vector per per-image array of an END message, - // and a bad_alloc leaving this thread would end the process. + // std::exception: a bad_alloc in the deserialiser leaving this thread would end the process logger.ErrorException(e); } ret = cbor_fifo.GetBlocking(); diff --git a/image_puller/ZMQImagePuller.h b/image_puller/ZMQImagePuller.h index 84770ab38..ed8b83c98 100644 --- a/image_puller/ZMQImagePuller.h +++ b/image_puller/ZMQImagePuller.h @@ -15,8 +15,7 @@ class ZMQImagePuller : public ImagePuller { - // Messages, not bytes: what the socket holds beyond the ring buffer. Kept small for that reason - - // the ring is where the memory is accounted for. + // Messages, not bytes: small, as the ring is where the memory is accounted for const uint32_t default_receive_watermark = 10; const uint32_t default_repub_watermark = 220; -- 2.54.0 From 3ba8ba1420fc4da231f4ad3e8440885bb5f82b2b Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 11 Nov 2026 13:07:26 +0100 Subject: [PATCH 05/96] Model step: one set of parts for rotation and live stills Taken from rc186 (270ae432d, net of merge 2250e4019): rugnux/ModelStep, the rotation pipeline's model validation and refinement calls moved onto its parts (RugnuxScaleMerge.cpp), StillsLiveMerge ::SpaceGroup, SecondsSince in common/time_utc.h, and the two ModelStep cases of ModelValidationTest. 270ae432d states rotation output with a model is unchanged by the move (identical maps, models and blobs end to end). Left out: nothing of these files. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK --- common/time_utc.h | 4 + rugnux/CMakeLists.txt | 2 + rugnux/ModelStep.cpp | 256 ++++++++++++++++++++++++++++++++++ rugnux/ModelStep.h | 101 ++++++++++++++ rugnux/RugnuxScaleMerge.cpp | 52 ++----- rugnux/StillsLiveMerge.cpp | 9 +- rugnux/StillsLiveMerge.h | 2 + tests/ModelValidationTest.cpp | 145 +++++++++++++++++++ 8 files changed, 526 insertions(+), 45 deletions(-) create mode 100644 rugnux/ModelStep.cpp create mode 100644 rugnux/ModelStep.h diff --git a/common/time_utc.h b/common/time_utc.h index bec55fd75..2dfaedb20 100644 --- a/common/time_utc.h +++ b/common/time_utc.h @@ -12,6 +12,10 @@ #include #include +inline double SecondsSince(std::chrono::steady_clock::time_point t0) { + return std::chrono::duration(std::chrono::steady_clock::now() - t0).count(); +} + inline std::string time_UTC(const std::chrono::time_point &input) { auto time_ms = std::chrono::duration_cast(input.time_since_epoch()).count(); diff --git a/rugnux/CMakeLists.txt b/rugnux/CMakeLists.txt index bd1462509..55379b405 100644 --- a/rugnux/CMakeLists.txt +++ b/rugnux/CMakeLists.txt @@ -25,6 +25,8 @@ ADD_LIBRARY(JFJochRugnux STATIC RugnuxLive.h StillsLiveMerge.cpp StillsLiveMerge.h + ModelStep.cpp + ModelStep.h RugnuxRefine.cpp RugnuxRefine.h Rugnux.h diff --git a/rugnux/ModelStep.cpp b/rugnux/ModelStep.cpp new file mode 100644 index 000000000..4d3d338bb --- /dev/null +++ b/rugnux/ModelStep.cpp @@ -0,0 +1,256 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "ModelStep.h" + +#include +#include + +#include "spdlog/fmt/fmt.h" + +#include "../common/time_utc.h" +#include "../image_analysis/scale_merge/CrystalSetting.h" +#include "../image_analysis/scale_merge/ReindexAmbiguity.h" +#include "../image_analysis/scale_merge/RfreeFlags.h" + +namespace { + bool Cut(const ModelStepSettings &settings) { + return settings.cut != nullptr && settings.cut->load(); + } + + void Step(const ModelStepSettings &settings, const std::string &title) { + if (settings.step) + settings.step(title); + } +} + +bool MergeIntoBasis(std::vector &merged, UnitCell &cell, gemmi::SpaceGroup &space_group, + const gemmi::Op &cob, bool merge_friedel, double rfree_fraction, size_t nthreads) { + const gemmi::SpaceGroup *moved = SpaceGroupInBasis(space_group, cob); + if (moved == nullptr) + return false; + merged = ReindexMergedIntoAsu(merged, HklOperator(cob), *moved, merge_friedel); + cell = CellInBasis(cell, cob); + AssignRfreeFlags(merged, *moved, rfree_fraction, 500, cell, nthreads); + space_group = *moved; + return true; +} + +ModelValidationResult ValidatePlacement(const std::vector &merged, const UnitCell &cell, + const gemmi::SpaceGroup &space_group, const RugnuxModel &model, + const gemmi::Structure &placed, double wavelength_A, + const std::vector &report_shell_d_min, size_t nthreads, + size_t gpu_resident_bytes, const ModelValidationResult *rigid_body, + Logger &logger) { + ModelValidationSchedule placed_elsewhere; + placed_elsewhere.fit_test = false; + placed_elsewhere.gpu_resident_bytes = gpu_resident_bytes; + ModelValidationResult v = ValidateAgainstModel(merged, cell, model, logger, &space_group, + /*probe_indexing_ambiguity=*/false, nthreads, wavelength_A, + report_shell_d_min, placed_elsewhere, nullptr, &placed); + if (v.ok && rigid_body != nullptr) { + v.fit_tested = rigid_body->fit_tested; + v.model_fits = rigid_body->model_fits; + v.null_r_work_mean = rigid_body->null_r_work_mean; + v.null_r_work_sd = rigid_body->null_r_work_sd; + v.r_work_sigma = rigid_body->r_work_sigma; + v.null_replicates = rigid_body->null_replicates; + } + return v; +} + +RefinementResult RefinePlacedModel(const gemmi::Structure &placed, const std::vector &merged, + const UnitCell &cell, const gemmi::SpaceGroup &space_group, double wavelength_A, + const RefinementSettings &settings, Logger &logger) { + try { + return RefineModel(placed, merged, gemmi::UnitCell(cell), space_group, wavelength_A, logger, settings); + } catch (const std::exception &e) { + RefinementResult failed; + failed.failure_reason = std::string("refinement failed: ") + e.what(); + return failed; + } +} + +ModelStepGroup ModelStepSpaceGroup(const std::optional &collection, const UnitCell &consensus, + const RugnuxModel &model) { + if (collection) + return {collection, ""}; + const gemmi::Structure &st = model.structure; + const gemmi::SpaceGroup *sg = st.find_spacegroup(); + if (sg == nullptr || !st.cell.is_crystal()) + return {std::nullopt, "the collection states no space group, and the model gives no crystal's"}; + if (!CellsCorrespond(st.cell, gemmi::UnitCell(consensus))) + return {std::nullopt, fmt::format("the collection states no space group, and the model's cell {:.2f} {:.2f} " + "{:.2f} {:.1f} {:.1f} {:.1f} ({}) is not the run's {:.2f} {:.2f} {:.2f} " + "{:.1f} {:.1f} {:.1f}", st.cell.a, st.cell.b, st.cell.c, st.cell.alpha, + st.cell.beta, st.cell.gamma, sg->xhm(), consensus.a, consensus.b, + consensus.c, consensus.alpha, consensus.beta, consensus.gamma)}; + return {*sg, ""}; +} + +ModelStepResult RunModelStep(const std::vector &merged, const UnitCell &cell, + const gemmi::SpaceGroup &space_group, const RugnuxModel &model, + double wavelength_A, bool merge_friedel, + const std::vector &report_shell_d_min, + const ModelStepSettings &settings, const ModelStepResult *earlier, + Logger &logger) { + const auto t0 = std::chrono::steady_clock::now(); + ModelStepResult out; + out.merged = merged; + out.cell = cell; + out.space_group = space_group; + + // Warm: in the earlier step's frame, from where it placed the model + if (earlier && earlier->placed_model && earlier->validation) { + std::vector moved = out.merged; + UnitCell moved_cell = cell; + gemmi::SpaceGroup moved_group = space_group; + const bool setting_moved = !(earlier->to_model_setting == gemmi::Op::identity()); + if (setting_moved) + MergeIntoBasis(moved, moved_cell, moved_group, earlier->to_model_setting, merge_friedel, + settings.rfree_fraction, settings.nthreads); + const ReindexChoice c = ChooseReindex(moved, moved_cell, moved_group, + [&](const std::vector &m) { + const double cc = ReferenceIntensityCC(m, earlier->merged, moved_group); + return std::isfinite(cc) ? cc : -1.0; + }); + if (!c.is_identity) { + moved = ReindexMergedIntoAsu(moved, c.op, moved_group, merge_friedel); + moved_cell = CellInBasis(moved_cell, c.op); + } + Step(settings, "Model validation"); + ModelValidationResult v = ValidatePlacement(moved, moved_cell, earlier->space_group, model, + *earlier->placed_model->model, wavelength_A, report_shell_d_min, + settings.nthreads, settings.gpu_resident_bytes, nullptr, logger); + if (v.ok && v.placed_model) { + logger.Info("Model: placed as on the earlier merge{} - R-work {:.3f}, R-free {:.3f}", + c.is_identity ? "" : ", the merge reindexed into its frame", v.r_work, v.r_free); + KeepModelVerdict(v, *earlier->validation); + out.merged = std::move(moved); + out.cell = moved_cell; + out.space_group = earlier->space_group; + out.to_model_setting = earlier->to_model_setting; + out.placement = earlier->placement; + out.mr = earlier->mr; + out.warm = true; + out.maps = std::move(v.maps); + out.placed_model = PlacedModelOutput{v.placed_model, out.cell, out.space_group}; + out.validation = std::move(v); + } else + logger.Warning("Model: the earlier placement does not validate on this merge ({}) - placed again", + v.failure_reason); + } + + if (!out.warm) { + const bool predicted = PrepareSearchModel(model).predicted; + // An experimental structure as given, by its rigid body; a prediction has no placement of its own + if (!predicted) { + Step(settings, "Model validation"); + ModelValidationSchedule schedule; + schedule.gpu_resident_bytes = settings.gpu_resident_bytes; + ModelValidationResult v = ValidateAgainstModel(out.merged, cell, model, logger, &space_group, + /*probe_indexing_ambiguity=*/true, settings.nthreads, + wavelength_A, report_shell_d_min, schedule); + if (v.ok && v.model_fits) { + out.space_group = *AdoptModelFrame(v, out.merged, space_group, merge_friedel, logger); + // The cell moves with an indexing that is only a near-symmetry, and a model on other axes takes the merge + // to them; the maps and the placed model are then made again, there. As a rotation run does. + bool moved = false; + if (!(v.indexing_op == gemmi::Op::identity())) { + const UnitCell before = out.cell; + out.cell = CellInBasis(out.cell, HklOperator(v.indexing_op)); + moved = !(out.cell == before); + } + gemmi::Op to_model = v.change_of_basis_op; + to_model.tran = {0, 0, 0}; + if (!(to_model == gemmi::Op::identity()) + && MergeIntoBasis(out.merged, out.cell, out.space_group, to_model.inverse(), merge_friedel, + settings.rfree_fraction, settings.nthreads)) { + out.to_model_setting = to_model.inverse(); + moved = true; + } + if (moved) { + ModelValidationSchedule kept_verdict; + kept_verdict.fit_test = false; + kept_verdict.gpu_resident_bytes = settings.gpu_resident_bytes; + ModelValidationResult remade = ValidateAgainstModel(out.merged, out.cell, model, logger, &out.space_group, + /*probe_indexing_ambiguity=*/false, settings.nthreads, + wavelength_A, report_shell_d_min, kept_verdict); + if (remade.ok) { + KeepModelVerdict(remade, v); + v = std::move(remade); + } + } + out.placement = "RIGID_BODY"; + out.maps = std::move(v.maps); + out.placed_model = PlacedModelOutput{v.placed_model, out.cell, out.space_group}; + } + v.maps.reset(); + out.validation = std::move(v); + } + // Molecular replacement, where the rigid body did not place it + if (out.placement == "NOT_PLACED" && Cut(settings)) + out.left_out.emplace_back("molecular replacement"); + else if (out.placement == "NOT_PLACED") { + Step(settings, "Molecular replacement"); + MolecularReplacementResult mr = RunMolecularReplacement( + model, {{"MERGE", &out.merged, cell, space_group}}, logger, settings.nthreads, + settings.gpu_resident_bytes); + if (mr.chosen >= 0) { + const MRCandidate &c = mr.candidates[mr.chosen]; + if (mr.decision == "ENANTIOMORPH") + CheckEnantiomorphHand(mr, out.merged, space_group, logger, settings.nthreads, + settings.gpu_resident_bytes); + // The merge labelled in the group the model chose, where it chose one of those the merge cannot + // tell apart (the enantiomorph past its anomalous veto, or another screw) + if (mr.decision == "ENANTIOMORPH" || mr.decision == "SPACE_GROUP") { + out.space_group = c.space_group; + const gemmi::GroupOps ops = c.space_group.operations(); + std::erase_if(out.merged, [&](const MergedReflection &r) { + return ops.is_systematically_absent({{r.h, r.k, r.l}}); + }); + } + ModelValidationResult v = ValidatePlacement(out.merged, cell, out.space_group, model, *c.placed, + wavelength_A, report_shell_d_min, settings.nthreads, + settings.gpu_resident_bytes, + out.validation ? &*out.validation : nullptr, logger); + if (v.ok && v.placed_model) { + out.placement = "MR"; + out.maps = std::move(v.maps); + out.placed_model = PlacedModelOutput{v.placed_model, cell, out.space_group}; + out.validation = std::move(v); + } + } + out.mr = std::move(mr); + } + } + out.placement_s = SecondsSince(t0); + + if (!settings.refine || !out.placed_model) + return out; + if (Cut(settings)) { + out.left_out.emplace_back("refinement"); + return out; + } + Step(settings, "Refinement"); + const auto t1 = std::chrono::steady_clock::now(); + RefinementSettings rs; + rs.nthreads = settings.nthreads; + rs.gpu_resident_bytes = settings.gpu_resident_bytes; + rs.cancel = settings.cut; + // The free set is drawn by the merge: a model placed as given has most likely been refined against all of it + rs.as_given_competes = out.placement != "RIGID_BODY"; + out.refinement = RefinePlacedModel(*out.placed_model->model, out.merged, out.cell, out.space_group, wavelength_A, rs, + logger); + out.refinement_s = SecondsSince(t1); + return out; +} + +ProcessResult ModelStepReport(const ModelStepResult &result) { + ProcessResult r; + r.model_validation = result.validation; + r.model_prior = result.mr; + r.model_placement = result.placement; + r.refinement = result.refinement; + return r; +} diff --git a/rugnux/ModelStep.h b/rugnux/ModelStep.h new file mode 100644 index 000000000..1c0319eef --- /dev/null +++ b/rugnux/ModelStep.h @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include +#include +#include +#include +#include + +#include "Rugnux.h" +#include "../image_analysis/structure_refinement/MolecularReplacement.h" +#include "../image_analysis/structure_refinement/Refinement.h" + +// The model step: a model placed against a merge - by its rigid body where it fits, by molecular replacement where +// it does not - then its maps and its refinement. No file is read or written here. +// +// A rotation run takes these parts one at a time, in Rugnux::ScaleMergeAndSymmetry, because each decision there moves what the +// run writes (the integrated observations, the twin hypothesis' merge, a rival refined beside it) and a space group, +// free set or merge it may search beside is its own. RunModelStep takes them in one go on a merge that is all there is +// of the run: the live stills of the broker, which make the step at each provisional merge and after the last frame. +// What to reuse from an earlier step and when to stop is the caller's. + +// The merge moved to the setting `cob` takes its group to: the indices, the cell, the group and, as the free set is a +// function of the written index (RfreeFlags.h), the free set. False, with nothing moved, where it reaches no setting. +bool MergeIntoBasis(std::vector &merged, UnitCell &cell, gemmi::SpaceGroup &space_group, + const gemmi::Op &cob, bool merge_friedel, double rfree_fraction, size_t nthreads); + +// The placement `placed` validated against the merge it was chosen on, for its maps, R-factors and CC(model, data), with +// nothing left for the validation to decide. `rigid_body`, where there is one, keeps its verdict on the model as given. +ModelValidationResult ValidatePlacement(const std::vector &merged, const UnitCell &cell, + const gemmi::SpaceGroup &space_group, const RugnuxModel &model, + const gemmi::Structure &placed, double wavelength_A, + const std::vector &report_shell_d_min, size_t nthreads, + size_t gpu_resident_bytes, const ModelValidationResult *rigid_body, + Logger &logger); + +// The refinement of a placed model. It decides nothing for the run, so one that fails is reported, in the result. +RefinementResult RefinePlacedModel(const gemmi::Structure &placed, const std::vector &merged, + const UnitCell &cell, const gemmi::SpaceGroup &space_group, double wavelength_A, + const RefinementSettings &settings, Logger &logger); + +// The group a merge is merged in where a model is given and no determination of the group was made (serial frames): the collection's, where it states +// one; else the model's own, where the model's cell describes the run's consensus cell on the same axes +// (CellsCorrespond); else none, and `reason` says why the model is left out. +struct ModelStepGroup { + std::optional space_group; + std::string reason; +}; +ModelStepGroup ModelStepSpaceGroup(const std::optional &collection, const UnitCell &consensus, + const RugnuxModel &model); + +struct ModelStepSettings { + size_t nthreads = 1; + size_t gpu_resident_bytes = 0; // held on the card meanwhile (the analysis's indexers) + double rfree_fraction = 0.05; // of a free set drawn again in the model's setting + bool refine = true; + // Set from any thread: the step is cut - a refinement that runs is cut short, keeping the trials that ran + // (RefinementSettings::cancel), and a molecular replacement or a refinement that has not started is left out. + const std::atomic *cut = nullptr; + // Called as each part starts: "Model validation", "Molecular replacement", "Refinement" + std::function step; +}; + +struct ModelStepResult { + // The merge in the model's frame - the indexing the model chose (AdoptModelFrame) on the axes the model is + // written on, labelled in the group the model was placed in (its enantiomorph, where it was taken) - and its cell. + std::vector merged; + UnitCell cell{}; + gemmi::SpaceGroup space_group; + gemmi::Op to_model_setting = gemmi::Op::identity(); // the change of basis that took the merge to those axes + std::string placement = "NOT_PLACED"; // RIGID_BODY | MR | NOT_PLACED + bool warm = false; // placed from an earlier step's placement + std::optional validation; // its maps moved to `maps` + std::optional mr; + // Where the model was placed: the maps of the placement and the model as placed, in the merge's frame + std::shared_ptr maps; + std::optional placed_model; + std::optional refinement; // its model, maps and blobs where it ran + std::vector left_out; // what a cut left out + double placement_s = 0, refinement_s = 0; +}; + +// The model placed against `merged` (in `space_group`, the cell `cell`): by its rigid body where it fits these +// data as given, its indexing ambiguity probed and the merge put in the model's indexing and on its axes; by molecular +// replacement where it does not (or is a prediction); then refined, where placed. `earlier`, the result of a +// step on an earlier merge of the same collection, is the warm start: the merge is put in its frame (the +// indexing a later merge happened to take is decided against it, ChooseReindex), the model goes in where it was +// placed there, and the verdict, the indexing and the molecular replacement's choice are its - no null, probe +// or search is made again. Where the warm placement does not validate, the step is made cold. +ModelStepResult RunModelStep(const std::vector &merged, const UnitCell &cell, + const gemmi::SpaceGroup &space_group, const RugnuxModel &model, + double wavelength_A, bool merge_friedel, + const std::vector &report_shell_d_min, + const ModelStepSettings &settings, const ModelStepResult *earlier, + Logger &logger); + +// The step's outcome in the terms of a ProcessResult (model_validation, model_prior, model_placement and +// refinement), for what reports one (LiveModel). +ProcessResult ModelStepReport(const ModelStepResult &result); diff --git a/rugnux/RugnuxScaleMerge.cpp b/rugnux/RugnuxScaleMerge.cpp index dbf89d206..b1646a36a 100644 --- a/rugnux/RugnuxScaleMerge.cpp +++ b/rugnux/RugnuxScaleMerge.cpp @@ -3,6 +3,7 @@ #include #include "Rugnux.h" +#include "ModelStep.h" #include "../image_analysis/structure_refinement/ModelValidation.h" #include "../image_analysis/indexing/SpindleCuspLoss.h" #include "../image_analysis/bragg_integration/SpotWidth.h" @@ -4379,13 +4380,7 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) { gemmi::Op to_model = ahead.change_of_basis_op; to_model.tran = {0, 0, 0}; const gemmi::Op cob = to_model.inverse(); - const gemmi::SpaceGroup *in = &sg; - if (const gemmi::SpaceGroup *moved = SpaceGroupInBasis(sg, cob)) { - merged = ReindexMergedIntoAsu(merged, HklOperator(cob), *moved, friedel); - cell = CellInBasis(cell, cob); - AssignRfreeFlags(merged, *moved, rfree_fraction, 500, cell, config_.nthreads); - in = moved; - } + MergeIntoBasis(merged, cell, sg, cob, friedel, rfree_fraction, config_.nthreads); ModelValidationSchedule schedule; schedule.maps_gate = gate; schedule.fit_test = false; // the verdict is the first validation's (KeepModelVerdict) @@ -4393,7 +4388,7 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) { schedule.gpu_beside_bytes = beside; ahead.merged = std::move(merged); return ValidateAgainstModel(ahead.merged, cell, *config_.model, ahead.log, - in, /*probe_indexing_ambiguity=*/false, + &sg, /*probe_indexing_ambiguity=*/false, static_cast(config_.nthreads), wavelength, report_shell_d_min, schedule); }); @@ -5482,27 +5477,15 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) { const ModelPriorFile *own = model_prior_file ? &*model_prior_file : nullptr; const gemmi::SpaceGroup data_sg = own ? own->space_group : experiment_.GetSpaceGroupOrP1(); const UnitCell cell = own ? own->cell : *result.consensus_cell; - ModelValidationSchedule placed_elsewhere; - placed_elsewhere.fit_test = false; - placed_elsewhere.gpu_resident_bytes = merge_on_card; phase("Validating against model (molecular replacement)"); - ModelValidationResult v = ValidateAgainstModel( - own ? own->merged : sm.merged, cell, *config_.model, logger, &data_sg, - /*probe_indexing_ambiguity=*/false, static_cast(config_.nthreads), - experiment_.GetWavelength_A(), report_shell_d_min, placed_elsewhere, nullptr, c.placed.get()); + ModelValidationResult v = ValidatePlacement( + own ? own->merged : sm.merged, cell, data_sg, *config_.model, *c.placed, + experiment_.GetWavelength_A(), report_shell_d_min, static_cast(config_.nthreads), + merge_on_card, result.model_validation ? &*result.model_validation : nullptr, logger); // Its maps go beside the files of the merge it was validated against. take_maps(own ? result.outputs->model_prior_maps : result.outputs->maps, v); v.maps_suffix = result.model_prior_file_suffix; if (v.ok) { - if (result.model_validation) { - const ModelValidationResult &rb = *result.model_validation; - v.fit_tested = rb.fit_tested; - v.model_fits = rb.model_fits; - v.null_r_work_mean = rb.null_r_work_mean; - v.null_r_work_sd = rb.null_r_work_sd; - v.r_work_sigma = rb.r_work_sigma; - v.null_replicates = rb.null_replicates; - } // Refined where it was chosen: in the group of the model's choice, against that choice's merge. if (own) { model_prior_file->placed = v.placed_model; @@ -5672,15 +5655,8 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) { refine_merged.push_back(r); } // processing-inert: a refinement that fails is reported, and the run goes on to its other outputs - RefinementResult refined; - try { - refined = RefineModel(*p.model, refine_merged.empty() ? *placed_merged : refine_merged, - gemmi::UnitCell(p.cell), refine_sg, experiment_.GetWavelength_A(), logger, - settings); - } catch (const std::exception &e) { - refined = RefinementResult(); - refined.failure_reason = std::string("refinement failed: ") + e.what(); - } + RefinementResult refined = RefinePlacedModel(*p.model, refine_merged.empty() ? *placed_merged : refine_merged, + p.cell, refine_sg, experiment_.GetWavelength_A(), settings, logger); // The rival hypothesis (see RivalPlacement), refined the same way, and the two decided on the refined // R-free: each refinement has made its own twin decision against its own null, so what is compared is // the best each hypothesis can do. The validation's choice stands unless the rival beats it by @@ -5689,14 +5665,8 @@ bool Rugnux::ScaleMergeAndSymmetry(PipelineLocals &p) { phase("Refining the model (rival symmetry hypothesis)"); const RivalPlacement &rv = *rival; const std::vector &rv_merged = rv.prior ? rv.prior->merged : *rv.merged; - RefinementResult other; - try { - other = RefineModel(*rv.placed.model, rv_merged, gemmi::UnitCell(rv.placed.cell), - rv.placed.space_group, experiment_.GetWavelength_A(), logger, settings); - } catch (const std::exception &e) { - other = RefinementResult(); - other.failure_reason = std::string("refinement failed: ") + e.what(); - } + RefinementResult other = RefinePlacedModel(*rv.placed.model, rv_merged, rv.placed.cell, rv.placed.space_group, + experiment_.GetWavelength_A(), settings, logger); ProcessResult::RivalRefinement rr; rr.space_group = rv.placed.space_group.xhm(); rr.merge = rv.prior ? "TWIN_SUBGROUP_MERGE" : "MERGE"; diff --git a/rugnux/StillsLiveMerge.cpp b/rugnux/StillsLiveMerge.cpp index b4a6c68cb..5f9de2a3b 100644 --- a/rugnux/StillsLiveMerge.cpp +++ b/rugnux/StillsLiveMerge.cpp @@ -7,6 +7,7 @@ #include #include "../common/ParallelFor.h" +#include "../common/time_utc.h" #include "../image_analysis/scale_merge/Merge.h" #include "../image_analysis/scale_merge/ReferenceFreeReindex.h" #include "../image_analysis/scale_merge/ReindexAmbiguity.h" @@ -14,10 +15,6 @@ #include "../image_analysis/scale_merge/SublatticeRelabel.h" namespace { - double SecondsSince(std::chrono::steady_clock::time_point t0) { - return std::chrono::duration(std::chrono::steady_clock::now() - t0).count(); - } - size_t ImagesWithReflections(const std::vector &outcomes) { size_t n = 0; for (const auto &o : outcomes) @@ -95,6 +92,10 @@ void StillsLiveMerge::Cell(const UnitCell &cell) { experiment.SetUnitCell(cell); } +void StillsLiveMerge::SpaceGroup(const gemmi::SpaceGroup &space_group) { + experiment.SetSpaceGroup(space_group); +} + void StillsLiveMerge::Add(int64_t image, IntegrationOutcome &&outcome) { ++added; if (outcome.reflections.empty()) diff --git a/rugnux/StillsLiveMerge.h b/rugnux/StillsLiveMerge.h index 43f2f3a46..ee8f841b9 100644 --- a/rugnux/StillsLiveMerge.h +++ b/rugnux/StillsLiveMerge.h @@ -50,6 +50,8 @@ public: // The cell merged in, where the experiment had none; before the first checkpoint. void Cell(const UnitCell &cell); + // The group merged in, where the experiment had none; before the first checkpoint. + void SpaceGroup(const gemmi::SpaceGroup &space_group); // Frame `image` as analysed: without reflections where it was not indexed. void Add(int64_t image, IntegrationOutcome &&outcome); // Frames added so far diff --git a/tests/ModelValidationTest.cpp b/tests/ModelValidationTest.cpp index e8986a690..60e676d4c 100644 --- a/tests/ModelValidationTest.cpp +++ b/tests/ModelValidationTest.cpp @@ -34,6 +34,7 @@ #include "../writer/WriteModel.h" #include "../writer/RugnuxModelFile.h" #include "../image_analysis/scale_merge/ReindexAmbiguity.h" +#include "../rugnux/ModelStep.h" namespace { // A synthetic P1 cell with two carbon atoms - enough for a reader to produce a Structure with @@ -2068,3 +2069,147 @@ TEST_CASE("ModelStructureFactorsGPU_MapMatchesCPU", "[ModelValidation][gpu]") { #endif + +// A model on other axes than the merge's takes the merge to them: its indices, cell, group and free set, as a rotation run +// takes its files. A later merge of the collection follows it there. +TEST_CASE("ModelStep_MergeTakesTheModelsAxes", "[ModelValidation][RugnuxLive]") { + Logger logger("ModelStep_MergeTakesTheModelsAxes"); + const auto data_model = WriteTemp("step_axes_data_test.pdb", ClusterPdb().c_str()); + auto obs = ModelReferenceIntensities(ReadModel(data_model), {}, {}, 2.5, logger); + REQUIRE(obs.size() > 500); + for (size_t i = 0; i < obs.size(); i++) { + obs[i].F = std::sqrt(std::max(0.0f, obs[i].I)); + obs[i].sigma = 0.02f * obs[i].I + 1.0f; + obs[i].sigmaF = 0.02f * obs[i].F + 0.1f; + obs[i].rfree_flag = (i % 20) == 0; + } + const UnitCell data_cell{.a = 30, .b = 34, .c = 38, .alpha = 90, .beta = 90, .gamma = 90}; + const gemmi::SpaceGroup data_sg = *gemmi::find_spacegroup_by_name("P 21 21 21"); + + // a' = b, b' = c, c' = a + std::string permuted = "CRYST1 34.000 38.000 30.000 90.00 90.00 90.00 P 21 21 21 4\n"; + std::istringstream in(ClusterPdb()); + std::string line; + char buf[96]; + while (std::getline(in, line)) { + if (line.compare(0, 4, "ATOM") != 0) + continue; + std::snprintf(buf, sizeof buf, "%s%8.3f%8.3f%8.3f%s", line.substr(0, 30).c_str(), std::stod(line.substr(38, 8)), + std::stod(line.substr(46, 8)), std::stod(line.substr(30, 8)), line.substr(54).c_str()); + permuted += buf; + permuted += "\n"; + } + permuted += "END\n"; + const auto path = WriteTemp("step_axes_model_test.pdb", permuted.c_str()); + const RugnuxModel model = ReadModel(path); + + ModelStepSettings settings; + settings.refine = false; + const ModelStepResult first = RunModelStep(obs, data_cell, data_sg, model, 1.0, true, {}, settings, nullptr, logger); + CHECK(first.placement == "RIGID_BODY"); + CHECK_FALSE(first.to_model_setting == gemmi::Op::identity()); + CHECK(first.cell.a == Catch::Approx(34.0)); + CHECK(first.cell.b == Catch::Approx(38.0)); + CHECK(first.cell.c == Catch::Approx(30.0)); + REQUIRE(first.placed_model); + CHECK(first.placed_model->cell.a == Catch::Approx(34.0)); + REQUIRE(first.validation); + CHECK(first.validation->r_work < 0.15); + CHECK(first.validation->model_fits); + + const ModelStepResult second = RunModelStep(obs, data_cell, data_sg, model, 1.0, true, {}, settings, &first, logger); + CHECK(second.warm); + CHECK(second.to_model_setting == first.to_model_setting); + CHECK(second.cell.a == Catch::Approx(34.0)); + REQUIRE(second.validation); + CHECK(second.validation->r_work < 0.15); + + std::filesystem::remove(data_model); + std::filesystem::remove(path); +} + +// The model step of a live serial merge (ModelStep.h). A merge in the other indexing of a P4 crystal is put in the +// model's by the probe; a later merge of the same collection, again in the other indexing, is put in the earlier +// step's frame and placed from where the model was, with no null or probe made again; a cut leaves out what has not +// started. The group a merge without one is merged in is the model's where its cell is the run's. +TEST_CASE("ModelStep_PlacesWarmStartsAndCuts", "[ModelValidation][RugnuxLive]") { + Logger logger("ModelStep_PlacesWarmStartsAndCuts"); + const auto path = WriteTemp("stills_model_test.pdb", + ClusterPdb("CRYST1 34.000 34.000 38.000 90.00 90.00 90.00 P 4 4\n").c_str()); + const RugnuxModel model = ReadModel(path); + auto own = ModelReferenceIntensities(model, {}, {}, 2.5, logger); + REQUIRE(own.size() > 500); + for (size_t i = 0; i < own.size(); i++) { + own[i].F = std::sqrt(std::max(0.0f, own[i].I)); + own[i].sigma = 0.02f * own[i].I + 1.0f; + own[i].sigmaF = 0.02f * own[i].F + 0.1f; + own[i].rfree_flag = (i % 20) == 0; + } + const UnitCell cell{.a = 34, .b = 34, .c = 38, .alpha = 90, .beta = 90, .gamma = 90}; + const gemmi::SpaceGroup p4 = *gemmi::find_spacegroup_by_name("P 4"); + const auto laws = ReindexAmbiguityOperators(cell, p4); + REQUIRE(laws.size() == 1); + const auto other = ReindexMergedIntoAsu(own, laws.front(), p4, true); + + // The group: the collection's, else the model's on the run's cell, else none + CHECK(ModelStepSpaceGroup(*gemmi::find_spacegroup_by_name("P 4 2 2"), cell, model).space_group->xhm() == "P 4 2 2"); + CHECK(ModelStepSpaceGroup(std::nullopt, cell, model).space_group->xhm() == "P 4"); + const UnitCell longer{.a = 34, .b = 34, .c = 45, .alpha = 90, .beta = 90, .gamma = 90}; + const auto none = ModelStepSpaceGroup(std::nullopt, longer, model); + CHECK_FALSE(none.space_group); + CHECK_FALSE(none.reason.empty()); + + ModelStepSettings settings; + settings.refine = false; + const ModelStepResult first = RunModelStep(other, cell, p4, model, 1.0, true, {}, settings, nullptr, + logger); + REQUIRE(first.validation); + CHECK(first.placement == "RIGID_BODY"); + CHECK_FALSE(first.warm); + CHECK(first.validation->indexing_op == laws.front()); + REQUIRE(first.maps); + REQUIRE(first.placed_model); + // In the model's indexing: the model's own intensities + CHECK(ReferenceIntensityCC(first.merged, own, p4) > 0.99); + CHECK(first.validation->r_work < 0.15); + + // A later merge, in the other indexing again, and refined + settings.refine = true; + const ModelStepResult second = RunModelStep(other, cell, p4, model, 1.0, true, {}, settings, &first, + logger); + REQUIRE(second.validation); + CHECK(second.warm); + CHECK(second.placement == "RIGID_BODY"); + CHECK(second.validation->model_fits); + CHECK(ReferenceIntensityCC(second.merged, own, p4) > 0.99); + REQUIRE(second.refinement); + CHECK(second.refinement->ok); + CHECK(second.refinement->r_free < 0.2); + + // Cut before anything: the refinement is left out; and for data the model does not describe, the molecular + // replacement too + std::atomic cut{true}; + settings.cut = &cut; + const ModelStepResult cut_placed = RunModelStep(other, cell, p4, model, 1.0, true, {}, settings, + &first, logger); + CHECK_FALSE(cut_placed.refinement); + CHECK(cut_placed.left_out == std::vector{"refinement"}); + const auto unrelated_path = WriteTemp("stills_model_unrelated_test.pdb", + ClusterPdb("CRYST1 34.000 34.000 38.000 90.00 90.00 90.00 P 4 4\n", 7).c_str()); + auto unrelated = ModelReferenceIntensities(ReadModel(unrelated_path), {}, {}, 2.5, logger); + for (size_t i = 0; i < unrelated.size(); i++) { + unrelated[i].F = std::sqrt(std::max(0.0f, unrelated[i].I)); + unrelated[i].sigma = 0.02f * unrelated[i].I + 1.0f; + unrelated[i].sigmaF = 0.02f * unrelated[i].F + 0.1f; + unrelated[i].rfree_flag = (i % 20) == 0; + } + const ModelStepResult cut_unplaced = RunModelStep(unrelated, cell, p4, model, 1.0, true, {}, + settings, nullptr, logger); + CHECK(cut_unplaced.placement == "NOT_PLACED"); + CHECK_FALSE(cut_unplaced.mr); + CHECK(cut_unplaced.left_out == std::vector{"molecular replacement"}); + CHECK_FALSE(cut_unplaced.placed_model); + + std::filesystem::remove(path); + std::filesystem::remove(unrelated_path); +} -- 2.54.0 From 1f8be9a259290c2439130f325d4488ae81bf1458 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 11 Nov 2026 13:07:42 +0100 Subject: [PATCH 06/96] JFJochReceiverFPGA keeps its live stills: StillsGPUAnalysis as at rc.185 becomes StillsGPUAnalysisFPGA rc186 reworks StillsGPUAnalysis for the stream receivers (3b09e721a: a sink instead of four callbacks, no bounded Hold, a model step, every frame integrated) and takes live stills out of JFJochReceiverFPGA (8bad85f9f). On this branch the FPGA path stays as it is at rc.185, so its analysis is kept unchanged under a name of its own: the class, its tests and the replay tool are renamed, nothing else. The rc186 StillsGPUAnalysis comes with the stream receivers in the next commit. The FPGA-side step that follows later removes this copy with the FPGA live stills. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK --- receiver/CMakeLists.txt | 2 +- receiver/JFJochReceiverFPGA.cpp | 4 +- receiver/JFJochReceiverFPGA.h | 6 +-- ...Analysis.cpp => StillsGPUAnalysisFPGA.cpp} | 44 +++++++++---------- ...sGPUAnalysis.h => StillsGPUAnalysisFPGA.h} | 7 +-- tests/CMakeLists.txt | 2 +- ...Test.cpp => StillsGPUAnalysisFPGATest.cpp} | 26 +++++------ tools/jfjoch_stills_gpu_replay.cpp | 10 ++--- 8 files changed, 51 insertions(+), 50 deletions(-) rename receiver/{StillsGPUAnalysis.cpp => StillsGPUAnalysisFPGA.cpp} (93%) rename receiver/{StillsGPUAnalysis.h => StillsGPUAnalysisFPGA.h} (96%) rename tests/{StillsGPUAnalysisTest.cpp => StillsGPUAnalysisFPGATest.cpp} (81%) diff --git a/receiver/CMakeLists.txt b/receiver/CMakeLists.txt index 9096e604c..6dd94d1ba 100644 --- a/receiver/CMakeLists.txt +++ b/receiver/CMakeLists.txt @@ -13,7 +13,7 @@ ADD_LIBRARY(JFJochReceiver STATIC JFJochReceiverLite.h JFJochReceiverLite.cpp JFJochReceiverRugnux.h JFJochReceiverRugnux.cpp StillsPrePass.h StillsPrePass.cpp - StillsGPUAnalysis.h StillsGPUAnalysis.cpp + StillsGPUAnalysisFPGA.h StillsGPUAnalysisFPGA.cpp RugnuxLiveStatus.h RugnuxLiveStatus.cpp RugnuxStructure.h RugnuxStructure.cpp JFJochReceiver.cpp diff --git a/receiver/JFJochReceiverFPGA.cpp b/receiver/JFJochReceiverFPGA.cpp index 340e47591..dd2fdd55d 100644 --- a/receiver/JFJochReceiverFPGA.cpp +++ b/receiver/JFJochReceiverFPGA.cpp @@ -130,12 +130,12 @@ JFJochReceiverFPGA::JFJochReceiverFPGA(const DiffractionExperiment &in_experimen } else if (experiment.GetImageNum() > 0) { if (gpu_analysis) { // The start message and the calibration go out once the pre-pass is done. - gpu = std::make_unique( + gpu = std::make_unique( experiment, pixel_mask, MakeStartMessage(experiment, pixel_mask, *az_int_mapping), indexing_thread_pool, std::min(16, frame_transformation_nthreads), // Half the image buffer for the frames waiting, the other half for those sent. image_buffer.GetStatus().total_slots / 2, logger, - [this](const StillsGPUAnalysis::Setup &s) { + [this](const StillsGPUAnalysisFPGA::Setup &s) { // The profiles are binned on the pre-pass's geometry. plots.Setup(s.experiment, *s.mapping); SendStartMessage(MakeStartMessage(s.experiment, s.mask, *s.mapping)); diff --git a/receiver/JFJochReceiverFPGA.h b/receiver/JFJochReceiverFPGA.h index 167fe50af..08c839647 100644 --- a/receiver/JFJochReceiverFPGA.h +++ b/receiver/JFJochReceiverFPGA.h @@ -40,7 +40,7 @@ #include "../image_analysis/MXAnalysisAfterFPGA.h" #include "../common/MovingAverage.h" #include "JFJochReceiverOutput.h" -#include "StillsGPUAnalysis.h" +#include "StillsGPUAnalysisFPGA.h" class JFJochReceiverFPGA : public JFJochReceiver { const JFCalibration *calibration; @@ -87,11 +87,11 @@ class JFJochReceiverFPGA : public JFJochReceiver { void Cancel(const JFJochException &e) override; void FinalizeMeasurement(); - // Serial data with rugnux on: the converted frames are analysed on the GPU (StillsGPUAnalysis), and + // Serial data with rugnux on: the converted frames are analysed on the GPU (StillsGPUAnalysisFPGA), and // the start message waits for its pre-pass. const bool gpu_analysis; RugnuxLiveStatus *const rugnux_status; - std::unique_ptr gpu; + std::unique_ptr gpu; void GPUSink(DataMessage &message, const AzimuthalIntegrationProfile &profile); void AddToEndMessage(EndMessage &message) override; diff --git a/receiver/StillsGPUAnalysis.cpp b/receiver/StillsGPUAnalysisFPGA.cpp similarity index 93% rename from receiver/StillsGPUAnalysis.cpp rename to receiver/StillsGPUAnalysisFPGA.cpp index 8d2daf488..41b4dc7c0 100644 --- a/receiver/StillsGPUAnalysis.cpp +++ b/receiver/StillsGPUAnalysisFPGA.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only -#include "StillsGPUAnalysis.h" +#include "StillsGPUAnalysisFPGA.h" #include @@ -10,7 +10,7 @@ #include "../image_analysis/MXAnalysisWithoutFPGA.h" #include "../rugnux/RugnuxDefaults.h" -StillsGPUAnalysis::StillsGPUAnalysis(const DiffractionExperiment &in_experiment, const PixelMask &in_mask, +StillsGPUAnalysisFPGA::StillsGPUAnalysisFPGA(const DiffractionExperiment &in_experiment, const PixelMask &in_mask, const StartMessage &in_start, IndexerThreadPool *in_indexing_thread_pool, int64_t nthreads, int64_t in_max_held, Logger &in_logger, Ready in_ready, Sink in_sink, Settings in_settings, Failed in_failed, @@ -31,13 +31,13 @@ StillsGPUAnalysis::StillsGPUAnalysis(const DiffractionExperiment &in_experiment, status->Step("Pre-pass"); Publish(); } - prepass_future = std::async(std::launch::async, &StillsGPUAnalysis::PrePassThread, this); + prepass_future = std::async(std::launch::async, &StillsGPUAnalysisFPGA::PrePassThread, this); for (int64_t i = 0; i < nthreads; i++) - analysis_futures.emplace_back(std::async(std::launch::async, &StillsGPUAnalysis::AnalysisThread, this)); + analysis_futures.emplace_back(std::async(std::launch::async, &StillsGPUAnalysisFPGA::AnalysisThread, this)); logger.Info("GPU analysis: {} threads, at least the first {} images held for the pre-pass", nthreads, PREPASS_IMAGES); } -StillsGPUAnalysis::~StillsGPUAnalysis() { +StillsGPUAnalysisFPGA::~StillsGPUAnalysisFPGA() { try { if (!finished) Finish(); @@ -48,14 +48,14 @@ StillsGPUAnalysis::~StillsGPUAnalysis() { status->Finish(outcome->first, outcome->second); } -void StillsGPUAnalysis::AnalysisError(const std::string &text) { +void StillsGPUAnalysisFPGA::AnalysisError(const std::string &text) { logger.Error("GPU analysis: {}", text); std::unique_lock ul(error_mutex); if (analysis_error.empty()) analysis_error = text; } -void StillsGPUAnalysis::Publish() { +void StillsGPUAnalysisFPGA::Publish() { std::unique_lock ul(live_mutex); if (live.prepass == RugnuxLivePrePass::Done || live.prepass == RugnuxLivePrePass::Failed) { const double t = std::chrono::duration(std::chrono::steady_clock::now() - ready_time).count(); @@ -65,7 +65,7 @@ void StillsGPUAnalysis::Publish() { status->Stills(live); } -bool StillsGPUAnalysis::Hold(const std::shared_ptr &image) { +bool StillsGPUAnalysisFPGA::Hold(const std::shared_ptr &image) { if (status) status->ImageReceived(std::chrono::steady_clock::now()); if (!queue.Put(image)) { @@ -87,7 +87,7 @@ bool StillsGPUAnalysis::Hold(const std::shared_ptr &image) { return true; } -void StillsGPUAnalysis::StartPrePass() { +void StillsGPUAnalysisFPGA::StartPrePass() { { std::unique_lock ul(prepass_mutex); prepass_last = true; @@ -95,7 +95,7 @@ void StillsGPUAnalysis::StartPrePass() { prepass_cv.notify_all(); } -void StillsGPUAnalysis::Finish() { +void StillsGPUAnalysisFPGA::Finish() { finished = true; StartPrePass(); prepass_future.get(); @@ -135,7 +135,7 @@ void StillsGPUAnalysis::Finish() { std::rethrow_exception(setup_error); } -void StillsGPUAnalysis::Written() { +void StillsGPUAnalysisFPGA::Written() { if (!status || !outcome) return; if (last_image) @@ -144,7 +144,7 @@ void StillsGPUAnalysis::Written() { outcome.reset(); } -void StillsGPUAnalysis::PrePassThread() { +void StillsGPUAnalysisFPGA::PrePassThread() { const auto masked = [](const PixelMask &m) { return std::count_if(m.GetMask().begin(), m.GetMask().end(), [](uint32_t v) { return v != 0; }); }; @@ -250,7 +250,7 @@ void StillsGPUAnalysis::PrePassThread() { setup.experiment.GetUnitCell(), std::nullopt, setup.experiment.IsDetectIceRings(), SpotFindingSettings().ice_ring_width_Q_recipA, logger); - merge_future = std::async(std::launch::async, &StillsGPUAnalysis::MergeThread, this); + merge_future = std::async(std::launch::async, &StillsGPUAnalysisFPGA::MergeThread, this); } ready(setup); } catch (const std::exception &e) { @@ -279,7 +279,7 @@ void StillsGPUAnalysis::PrePassThread() { prepass_done.count_down(); } -void StillsGPUAnalysis::AnalysisThread() { +void StillsGPUAnalysisFPGA::AnalysisThread() { try { pin_gpu(); } catch (const std::exception &e) { @@ -341,31 +341,31 @@ void StillsGPUAnalysis::AnalysisThread() { } } -std::optional StillsGPUAnalysis::GetConsensusUnitCell() const { +std::optional StillsGPUAnalysisFPGA::GetConsensusUnitCell() const { return indexer->GetConsensusUnitCell(); } -float StillsGPUAnalysis::GetBeamX_pxl() const { +float StillsGPUAnalysisFPGA::GetBeamX_pxl() const { return setup.experiment.GetBeamX_pxl(); } -float StillsGPUAnalysis::GetBeamY_pxl() const { +float StillsGPUAnalysisFPGA::GetBeamY_pxl() const { return setup.experiment.GetBeamY_pxl(); } -float StillsGPUAnalysis::GetDetectorDistance_mm() const { +float StillsGPUAnalysisFPGA::GetDetectorDistance_mm() const { return setup.experiment.GetDetectorDistance_mm(); } -const std::optional &StillsGPUAnalysis::GetMerged() const { +const std::optional &StillsGPUAnalysisFPGA::GetMerged() const { return merged; } -const DiffractionExperiment &StillsGPUAnalysis::GetMergeExperiment() const { +const DiffractionExperiment &StillsGPUAnalysisFPGA::GetMergeExperiment() const { return merge_experiment; } -void StillsGPUAnalysis::MergeThread() { +void StillsGPUAnalysisFPGA::MergeThread() { constexpr int64_t PREPARE_EVERY = 200; // frames between two Prepare calls after a checkpoint const int64_t images = experiment.GetImageNum(); // The cell merged in, as rugnux merges it: the consensus of the frames indexed by the first merge (the @@ -405,7 +405,7 @@ void StillsGPUAnalysis::MergeThread() { } } -void StillsGPUAnalysis::PublishMerge(const StillsMergeOutput &out, const std::string &title) { +void StillsGPUAnalysisFPGA::PublishMerge(const StillsMergeOutput &out, const std::string &title) { const auto &o = out.merge.statistics.overall; const std::string text = fmt::format("Merge of {}: {} crystals, {:.2f} A, CC1/2 {:.3f}, R_split {:.3f}, " "completeness {:.1f}%, {:.1f} ({:.1f} s)", title, out.images, diff --git a/receiver/StillsGPUAnalysis.h b/receiver/StillsGPUAnalysisFPGA.h similarity index 96% rename from receiver/StillsGPUAnalysis.h rename to receiver/StillsGPUAnalysisFPGA.h index 9d8c5b477..0773af0af 100644 --- a/receiver/StillsGPUAnalysis.h +++ b/receiver/StillsGPUAnalysisFPGA.h @@ -25,6 +25,7 @@ #include "../image_analysis/IndexAndRefine.h" #include "../rugnux/StillsLiveMerge.h" +// JFJochReceiverFPGA's live stills, kept as they are at rc.185 while the stream receivers take StillsGPUAnalysis. // Serial frames analysed on the GPU as the DECTRIS ones are (MXAnalysisWithoutFPGA), after a pre-pass on // the first PREPASS_IMAGES (RunStillsPrePass) has decided the beam centre and the mask they are analysed // with. Every frame given before the pre-pass is done is held; `ready` is called once it is, with the @@ -48,7 +49,7 @@ // not refine it (too few of the frames it had indexed: a small image buffer, a serial hit rate, a chip whose first // frames are empty; or no cell given), the header's distance stays for the whole run, with a warning in the log // and the status. -class StillsGPUAnalysis { +class StillsGPUAnalysisFPGA { public: // Enough frames for the distance to be refined at a serial hit rate: of 100 frames of a collection indexed // at 18%, 7 indexed at a distance 2.6% off and the geometry refinement did not run; of 500, 34 did and @@ -69,11 +70,11 @@ public: using Failed = std::function; // `start`: the collection's start message, as it would be sent without the pre-pass. - StillsGPUAnalysis(const DiffractionExperiment &experiment, const PixelMask &mask, const StartMessage &start, + StillsGPUAnalysisFPGA(const DiffractionExperiment &experiment, const PixelMask &mask, const StartMessage &start, IndexerThreadPool *indexing_thread_pool, int64_t nthreads, int64_t max_held, Logger &logger, Ready ready, Sink sink, Settings settings, Failed failed = {}, RugnuxLiveStatus *status = nullptr, bool merge = true); - ~StillsGPUAnalysis(); + ~StillsGPUAnalysisFPGA(); // False where `max_held` frames already wait: the frame is not taken. bool Hold(const std::shared_ptr &image); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 27d47e41a..dfd1a22cb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -127,7 +127,7 @@ ADD_EXECUTABLE(jfjoch_test ModuleRefinementTest.cpp LiveCollectionTest.cpp RugnuxLiveStatusTest.cpp - StillsGPUAnalysisTest.cpp + StillsGPUAnalysisFPGATest.cpp RugnuxStructureTest.cpp SweepLayoutTest.cpp BrukerReaderTest.cpp diff --git a/tests/StillsGPUAnalysisTest.cpp b/tests/StillsGPUAnalysisFPGATest.cpp similarity index 81% rename from tests/StillsGPUAnalysisTest.cpp rename to tests/StillsGPUAnalysisFPGATest.cpp index ef803a2e7..a2129bf4e 100644 --- a/tests/StillsGPUAnalysisTest.cpp +++ b/tests/StillsGPUAnalysisFPGATest.cpp @@ -6,12 +6,12 @@ #include #include "../common/ImageBuffer.h" -#include "../receiver/StillsGPUAnalysis.h" +#include "../receiver/StillsGPUAnalysisFPGA.h" namespace { DiffractionExperiment StillsExperiment() { DiffractionExperiment x(DetJF(1)); - x.ImagesPerTrigger(StillsGPUAnalysis::PREPASS_IMAGES + 200).NumTriggers(1).DetectorDistance_mm(100).BeamX_pxl(500).BeamY_pxl(250) + x.ImagesPerTrigger(StillsGPUAnalysisFPGA::PREPASS_IMAGES + 200).NumTriggers(1).DetectorDistance_mm(100).BeamX_pxl(500).BeamY_pxl(250) .IncidentEnergy_keV(12.4); return x; } @@ -21,7 +21,7 @@ namespace { auto held = std::make_shared(); held->slot = buffer.GetImageSlot(); REQUIRE(held->slot != nullptr); - held->message.number = StillsGPUAnalysis::PREPASS_IMAGES + number; + held->message.number = StillsGPUAnalysisFPGA::PREPASS_IMAGES + number; held->message.image_collection_efficiency = 1.0f; return held; } @@ -33,7 +33,7 @@ namespace { } } -TEST_CASE("StillsGPUAnalysis_StartFailureReleasesThreads", "[StillsGPUAnalysis]") { +TEST_CASE("StillsGPUAnalysisFPGA_StartFailureReleasesThreads", "[StillsGPUAnalysisFPGA]") { Logger logger(Catch::getResultCapture().getCurrentTestName()); const DiffractionExperiment x = StillsExperiment(); const PixelMask mask(x); @@ -43,8 +43,8 @@ TEST_CASE("StillsGPUAnalysis_StartFailureReleasesThreads", "[StillsGPUAnalysis]" std::atomic failed = 0, sunk = 0; { - StillsGPUAnalysis gpu(x, mask, Start(x), nullptr, 4, 100, logger, - [](const StillsGPUAnalysis::Setup &) { + StillsGPUAnalysisFPGA gpu(x, mask, Start(x), nullptr, 4, 100, logger, + [](const StillsGPUAnalysisFPGA::Setup &) { throw JFJochException(JFJochExceptionCategory::ZeroMQ, "Timeout on pushing start message"); }, [&](DataMessage &, const AzimuthalIntegrationProfile &) { ++sunk; }, @@ -61,7 +61,7 @@ TEST_CASE("StillsGPUAnalysis_StartFailureReleasesThreads", "[StillsGPUAnalysis]" CHECK(buffer.GetAvailSlots() == buffer.GetStatus().total_slots); } -TEST_CASE("StillsGPUAnalysis_HoldIsBounded", "[StillsGPUAnalysis]") { +TEST_CASE("StillsGPUAnalysisFPGA_HoldIsBounded", "[StillsGPUAnalysisFPGA]") { Logger logger(Catch::getResultCapture().getCurrentTestName()); const DiffractionExperiment x = StillsExperiment(); const PixelMask mask(x); @@ -69,8 +69,8 @@ TEST_CASE("StillsGPUAnalysis_HoldIsBounded", "[StillsGPUAnalysis]") { buffer.StartMeasurement(x); REQUIRE(buffer.GetStatus().total_slots >= 4); - StillsGPUAnalysis gpu(x, mask, Start(x), nullptr, 2, 3, logger, - [](const StillsGPUAnalysis::Setup &) { + StillsGPUAnalysisFPGA gpu(x, mask, Start(x), nullptr, 2, 3, logger, + [](const StillsGPUAnalysisFPGA::Setup &) { throw JFJochException(JFJochExceptionCategory::ZeroMQ, "no writer"); }, [](DataMessage &, const AzimuthalIntegrationProfile &) {}, @@ -87,7 +87,7 @@ TEST_CASE("StillsGPUAnalysis_HoldIsBounded", "[StillsGPUAnalysis]") { CHECK(buffer.GetStatus().preparation_slots == 0); } -TEST_CASE("StillsGPUAnalysis_FailingFrameDoesNotStopAnalysis", "[StillsGPUAnalysis]") { +TEST_CASE("StillsGPUAnalysisFPGA_FailingFrameDoesNotStopAnalysis", "[StillsGPUAnalysisFPGA]") { Logger logger(Catch::getResultCapture().getCurrentTestName()); const DiffractionExperiment x = StillsExperiment(); const PixelMask mask(x); @@ -96,11 +96,11 @@ TEST_CASE("StillsGPUAnalysis_FailingFrameDoesNotStopAnalysis", "[StillsGPUAnalys RugnuxLiveStatus status; std::atomic sunk = 0; - StillsGPUAnalysis gpu(x, mask, Start(x), nullptr, 1, 100, logger, - [](const StillsGPUAnalysis::Setup &) {}, + StillsGPUAnalysisFPGA gpu(x, mask, Start(x), nullptr, 1, 100, logger, + [](const StillsGPUAnalysisFPGA::Setup &) {}, [&](DataMessage &m, const AzimuthalIntegrationProfile &) { ++sunk; - if (m.number == StillsGPUAnalysis::PREPASS_IMAGES) + if (m.number == StillsGPUAnalysisFPGA::PREPASS_IMAGES) throw std::runtime_error("not a JFJochException"); }, [] { return DiffractionExperiment::DefaultDataProcessingSettings(); }, diff --git a/tools/jfjoch_stills_gpu_replay.cpp b/tools/jfjoch_stills_gpu_replay.cpp index 2af39ef5b..396c1fd8c 100644 --- a/tools/jfjoch_stills_gpu_replay.cpp +++ b/tools/jfjoch_stills_gpu_replay.cpp @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only // Replays the frames of a stored serial collection through the broker's GPU analysis of converted -// JUNGFRAU frames (StillsGPUAnalysis): the pre-pass on the first images, then every frame analysed with the +// JUNGFRAU frames (StillsGPUAnalysisFPGA): the pre-pass on the first images, then every frame analysed with the // beam centre and mask it decided. Reports the pre-pass, the indexed fraction and the sustained rate, and // writes the mask the frames were analysed with (_mask.u32, uint32, row by row). @@ -21,7 +21,7 @@ #include "../common/Logger.h" #include "../image_analysis/indexing/IndexerThreadPool.h" #include "../reader/JFJochHDF5Reader.h" -#include "../receiver/StillsGPUAnalysis.h" +#include "../receiver/StillsGPUAnalysisFPGA.h" #include "../writer/HDF5Objects.h" void print_usage() { @@ -37,7 +37,7 @@ void print_usage() { std::cout << " -F the broker's default fixed-threshold spot finder (default: adaptive, as the receiver)" << std::endl; std::cout << " -c leave the file's unit cell out of the collection" << std::endl; std::cout << " -S space group of the collection (the merge's)" << std::endl; - std::cout << " -M no merge (StillsGPUAnalysis merge = false)" << std::endl; + std::cout << " -M no merge (StillsGPUAnalysisFPGA merge = false)" << std::endl; std::cout << " -e the first images given as empty frames (no diffraction)" << std::endl; } @@ -147,8 +147,8 @@ int main(int argc, char **argv) { // As the receiver: half the buffer for the frames waiting, the other half for those sent. std::mutex run_profile_mutex; std::unique_ptr run_profile; - StillsGPUAnalysis gpu(x, mask, start, &pool, nthreads, buffer.GetStatus().total_slots / 2, logger, - [&](const StillsGPUAnalysis::Setup &s) { + StillsGPUAnalysisFPGA gpu(x, mask, start, &pool, nthreads, buffer.GetStatus().total_slots / 2, logger, + [&](const StillsGPUAnalysisFPGA::Setup &s) { t_ready = std::chrono::steady_clock::now(); run_profile = std::make_unique(*s.mapping); const auto &m = s.mask.GetMask(); -- 2.54.0 From 48ae84416d64596af32cbd3fd209ba00861b06e1 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 11 Nov 2026 13:08:04 +0100 Subject: [PATCH 07/96] Stream receivers and Rugnux live stills for DECTRIS, from rc186; the FPGA path as at rc.185 The stream-receiver side of rc186 (merge 2250e4019, net of 7f2ebd820), for testing on a DECTRIS system first: - JFJochReceiverStream, one parent for the receivers that take a collection from an ImagePuller (d87508c6b, c2a20a222, 659b5da52, 16f3b0270, 3047ccd13, 0d26ecd13). - JFJochReceiverLite on it: indexing only, no integration (5f8e26e3d, d279827a8), its own indexer (97cfae1fd), its thread-count rule (89fb963bd), the stream's saturated/error counts kept (4c3a7852e), stream calibration messages no longer forwarded (25ac0071b). - JFJochReceiverDarkMask for the DECTRIS mask sequence, sending nothing on the preview and metadata sockets (5d1fb1a48, 72310c2ee). - JFJochReceiverRugnuxRotation and JFJochReceiverRugnuxStills replace JFJochReceiverRugnux (1ea363876, 8bad85f9f, af5850090): DECTRIS serial collections get the live stills analysis; a cancelled rotation run reports CANCELLED (8f3ba8d38); live stills integrate every frame (11eb76093). - StillsGPUAnalysis as rc186 has it (3b09e721a, 270ae432d), with StillsPrePass, RugnuxLiveStatus (CANCELLED, steps capped after a cancel, LiveModel moved here) and RugnuxStructure (shared END helpers) - 8f3ba8d38, 270ae432d, 0d26ecd13. - JFJochReceiver base: one send block, CountImage/StampImage/AddToAverages/ForwardImage for the stream receivers (c2a20a222), images_dropped (15266b21e), the indexer moved out of the base into the receivers that index (97cfae1fd); dead members removed (51b2ee880, receiver side). rc.185's "a given cell is what the master file records" (2b284e11b) is kept, now in AddIndexingToEndMessage. - JFJochReceiverService: the DECTRIS dispatch of rc186 - dark mask, rotation, stills, Lite - and a rotation run the image buffer cannot hold gets the per-image analysis with a warning (2978a9f92, 29afe06a7). - Tests: JFJochReceiverRugnuxTest (59a0afaf8, 1a39d6f29 and the receivers' own), the rc186 StillsGPUAnalysisTest and RugnuxLiveStatusTest; the replay tool on the new StillsGPUAnalysis. The FPGA path is left as at rc.185: JFJochReceiverFPGA still takes every PSI run (pedestal included) and its live stills (StillsGPUAnalysisFPGA). Its only changes are the ones the base move forces, as 97cfae1fd makes them: it owns its IndexAndRefine, built with the same arguments the base used, and adds the consensus cell and indexer errors through AddIndexingToEndMessage before its live stills overwrite the cell, as before. Left out (FPGA side, a later step): FPGAImagePuller, JFJochReceiverPedestal, MockAcquisitionDevice and the tests on it, "FPGA receiver: index only" (68ec8a7f9), MXAnalysisAfterFPGA/ImageMetadata cleanups (b0db94658, 51b2ee880 FPGA part), the internal-generator memory check (25542a9af), JFJochReceiverService::UseFPGAImagePuller, the JFJochHLSSimulation link removal, and JFJochReceiverFPGA_RugnuxOnStaysPerImage (rugnux live still analyses FPGA serial runs here). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK --- receiver/CMakeLists.txt | 6 +- receiver/JFJochReceiver.cpp | 156 ++++- receiver/JFJochReceiver.h | 34 +- receiver/JFJochReceiverCurrentStatus.cpp | 6 - receiver/JFJochReceiverCurrentStatus.h | 2 +- receiver/JFJochReceiverDarkMask.cpp | 68 ++ receiver/JFJochReceiverDarkMask.h | 31 + receiver/JFJochReceiverFPGA.cpp | 8 +- receiver/JFJochReceiverFPGA.h | 2 + receiver/JFJochReceiverLite.cpp | 454 ++---------- receiver/JFJochReceiverLite.h | 78 +-- receiver/JFJochReceiverOutput.h | 5 +- receiver/JFJochReceiverRugnux.cpp | 733 ------------------- receiver/JFJochReceiverRugnux.h | 93 --- receiver/JFJochReceiverRugnuxRotation.cpp | 441 ++++++++++++ receiver/JFJochReceiverRugnuxRotation.h | 70 ++ receiver/JFJochReceiverRugnuxStills.cpp | 200 ++++++ receiver/JFJochReceiverRugnuxStills.h | 56 ++ receiver/JFJochReceiverService.cpp | 121 ++-- receiver/JFJochReceiverService.h | 7 +- receiver/JFJochReceiverStream.cpp | 253 +++++++ receiver/JFJochReceiverStream.h | 78 +++ receiver/RugnuxLiveStatus.cpp | 226 ++++-- receiver/RugnuxLiveStatus.h | 41 +- receiver/RugnuxStructure.cpp | 55 +- receiver/RugnuxStructure.h | 27 +- receiver/StillsGPUAnalysis.cpp | 588 ++++++++++++++++ receiver/StillsGPUAnalysis.h | 160 +++++ receiver/StillsPrePass.cpp | 3 +- receiver/StillsPrePass.h | 9 +- tests/CMakeLists.txt | 2 + tests/JFJochReceiverRugnuxTest.cpp | 814 ++++++++++++++++++++++ tests/RugnuxLiveStatusTest.cpp | 32 +- tests/RugnuxResultFilesTest.cpp | 4 +- tests/StillsGPUAnalysisTest.cpp | 192 +++++ tools/CMakeLists.txt | 2 +- tools/jfjoch_stills_gpu_replay.cpp | 125 +++- 37 files changed, 3643 insertions(+), 1539 deletions(-) create mode 100644 receiver/JFJochReceiverDarkMask.cpp create mode 100644 receiver/JFJochReceiverDarkMask.h delete mode 100644 receiver/JFJochReceiverRugnux.cpp delete mode 100644 receiver/JFJochReceiverRugnux.h create mode 100644 receiver/JFJochReceiverRugnuxRotation.cpp create mode 100644 receiver/JFJochReceiverRugnuxRotation.h create mode 100644 receiver/JFJochReceiverRugnuxStills.cpp create mode 100644 receiver/JFJochReceiverRugnuxStills.h create mode 100644 receiver/JFJochReceiverStream.cpp create mode 100644 receiver/JFJochReceiverStream.h create mode 100644 receiver/StillsGPUAnalysis.cpp create mode 100644 receiver/StillsGPUAnalysis.h create mode 100644 tests/JFJochReceiverRugnuxTest.cpp create mode 100644 tests/StillsGPUAnalysisTest.cpp diff --git a/receiver/CMakeLists.txt b/receiver/CMakeLists.txt index 6dd94d1ba..53da392dd 100644 --- a/receiver/CMakeLists.txt +++ b/receiver/CMakeLists.txt @@ -10,9 +10,13 @@ ADD_LIBRARY(JFJochReceiver STATIC JFJochReceiverCurrentStatus.cpp JFJochReceiverCurrentStatus.h JFJochReceiverOutput.h + JFJochReceiverStream.h JFJochReceiverStream.cpp JFJochReceiverLite.h JFJochReceiverLite.cpp - JFJochReceiverRugnux.h JFJochReceiverRugnux.cpp + JFJochReceiverDarkMask.h JFJochReceiverDarkMask.cpp + JFJochReceiverRugnuxRotation.h JFJochReceiverRugnuxRotation.cpp + JFJochReceiverRugnuxStills.h JFJochReceiverRugnuxStills.cpp StillsPrePass.h StillsPrePass.cpp + StillsGPUAnalysis.h StillsGPUAnalysis.cpp StillsGPUAnalysisFPGA.h StillsGPUAnalysisFPGA.cpp RugnuxLiveStatus.h RugnuxLiveStatus.cpp RugnuxStructure.h RugnuxStructure.cpp diff --git a/receiver/JFJochReceiver.cpp b/receiver/JFJochReceiver.cpp index 022348707..fdd4092c4 100644 --- a/receiver/JFJochReceiver.cpp +++ b/receiver/JFJochReceiver.cpp @@ -2,9 +2,9 @@ // SPDX-License-Identifier: GPL-3.0-only #include "JFJochReceiver.h" -#include "../common/CUDAWrapper.h" #include "../common/time_utc.h" -#include "JFJochCompressor.h" +#include "../frame_serialize/CBORStream2Serializer.h" +#include "../image_analysis/IndexAndRefine.h" JFJochReceiver::JFJochReceiver(const DiffractionExperiment &in_experiment, ImageBuffer &in_image_buffer, @@ -15,8 +15,7 @@ JFJochReceiver::JFJochReceiver(const DiffractionExperiment &in_experiment, Logger &logger, const PixelMask &in_pixel_mask, ZMQPreviewSocket *in_zmq_preview_socket, - ZMQMetadataSocket *in_zmq_metadata_socket, - IndexerThreadPool *indexing_thread_pool) + ZMQMetadataSocket *in_zmq_metadata_socket) : logger(logger), experiment(in_experiment), spot_finding_settings(spot_finding_settings), @@ -28,10 +27,7 @@ JFJochReceiver::JFJochReceiver(const DiffractionExperiment &in_experiment, plots(in_plots), scan_result(in_experiment), serialmx_filter(in_experiment), - pixel_mask(in_pixel_mask), - // retain_outcomes=false for now: online scaling/merge at the end is not enabled yet, so the - // whole-run integration_outcome is unused here. Flip back to true when online scaling lands. - indexer(experiment, indexing_thread_pool, false, /*real_time=*/true) { + pixel_mask(in_pixel_mask) { logger.Debug("Initializing receiver"); // Ensure there is nothing running for now if (!image_buffer.Finalize(std::chrono::seconds(1))) @@ -40,8 +36,7 @@ JFJochReceiver::JFJochReceiver(const DiffractionExperiment &in_experiment, logger.Debug("Image buffer from previous run finalized"); current_status.SetProgress(0); - current_status.SetEfficiency({}); - current_status.SetStatus(JFJochReceiverStatus{}); // GetStatus() is virtual function and cannot be called yet! + current_status.SetStatus(JFJochReceiverStatus{}); // GetStatus() calls virtual functions and cannot be called yet auto start_time_point = std::chrono::steady_clock::now(); az_int_mapping = std::make_unique(experiment, pixel_mask); @@ -61,6 +56,12 @@ SpotFindingSettings JFJochReceiver::GetSpotFindingSettings() { return spot_finding_settings; } +void JFJochReceiver::SetSpotFindingSettings(const SpotFindingSettings &in_spot_finding_settings) { + std::unique_lock ul(spot_finding_settings_mutex); + DiffractionExperiment::CheckDataProcessingSettings(in_spot_finding_settings); + spot_finding_settings = in_spot_finding_settings; +} + void JFJochReceiver::UpdateMaxImageSent(int64_t image_number) { std::unique_lock ul(max_image_number_sent_mutex); if (image_number + 1 > max_image_number_sent) @@ -100,6 +101,7 @@ JFJochReceiverStatus JFJochReceiver::GetStatus() const { ret.images_collected = images_collected; ret.images_sent = images_sent; ret.images_skipped = images_skipped; + ret.images_dropped = GetImagesDropped(); ret.images_written = image_pusher.GetImagesWritten(); ret.cancelled = cancelled; ret.efficiency = GetEfficiency(); @@ -107,10 +109,10 @@ JFJochReceiverStatus JFJochReceiver::GetStatus() const { } -StartMessage JFJochReceiver::MakeStartMessage(const DiffractionExperiment &x, const PixelMask &mask, +StartMessage JFJochReceiver::MakeStartMessage(const DiffractionExperiment &in_experiment, const PixelMask &mask, const AzimuthalIntegrationMapping &mapping) { StartMessage message{}; - x.FillMessage(message); + in_experiment.FillMessage(message); message.arm_date = time_UTC(std::chrono::system_clock::now()); message.az_int_q_bin_count = mapping.GetQBinCount(); message.az_int_bin_to_q = mapping.GetBinToQ(); @@ -121,11 +123,11 @@ StartMessage JFJochReceiver::MakeStartMessage(const DiffractionExperiment &x, co message.az_int_map = mapping.GetPixelToBin(); } message.writer_notification_zmq_addr = image_pusher.GetWriterNotificationSocketAddress(); - message.rois = x.ROI().ExportMetadata(); - if (!x.ROI().empty()) - message.roi_map = x.ExportROIMap(); - message.max_spot_count = x.GetMaxSpotCount(); - message.pixel_mask["default"] = mask.GetMask(x); + message.rois = in_experiment.ROI().ExportMetadata(); + if (!in_experiment.ROI().empty()) + message.roi_map = in_experiment.ExportROIMap(); + message.max_spot_count = in_experiment.GetMaxSpotCount(); + message.pixel_mask["default"] = mask.GetMask(in_experiment); return message; } @@ -154,6 +156,94 @@ void JFJochReceiver::SaveStartMessageToImageBuffer(const StartMessage &msg) { image_buffer.SaveStartMessage(buffer); } +// A frame whose metadata does not fit its slot is dropped, without aborting the collection +bool JFJochReceiver::SerializeImageOrDrop(CBORStream2Serializer &serializer, DataMessage &msg, + ZeroCopyReturnValue &loc, size_t slot_size, Logger &logger) { + try { + serializer.SerializeImage(msg); + return true; + } catch (const JFJochException &) { + // The metadata alone fits if it is the image that overflowed; if not, the exception propagates + const auto image = msg.image; + msg.image = CompressedImage(nullptr, 0, image.GetWidth(), image.GetHeight(), + image.GetMode(), image.GetCompressionAlgorithm(), + image.GetChannel()); + CBORStream2Serializer meas(static_cast(loc.GetImage()), slot_size); + meas.SerializeImage(msg); + const size_t metadata_size = meas.GetImageAppendOffset(); + msg.image = image; + + logger.Error("Dropping image {} - CBOR metadata too large to store image: " + "metadata {} B + image {} B do not fit in buffer slot {} B", + msg.number, metadata_size, image.GetCompressedSize(), slot_size); + loc.release(); + return false; + } +} + +void JFJochReceiver::CountImage(const DataMessage &message) { + ++images_collected; + collection_efficiency_sum += message.image_collection_efficiency.value_or(1.0f); + compressed_size += message.image.GetCompressedSize(); + uncompressed_size += message.image.GetUncompressedSize(); + UpdateMaxImageReceived(message.number); +} + +void JFJochReceiver::StampImage(DataMessage &message, size_t fifo_delay) { + message.original_number = message.number; + message.user_data = experiment.GetImageAppendix(); + message.run_number = experiment.GetRunNumber(); + message.run_name = experiment.GetRunName(); + message.receiver_buf_available = image_buffer.GetAvailSlots(); + message.receiver_aq_dev_delay = fifo_delay; + // Uncompressed/compressed (e.g. 7x), as JFJochReceiverFPGA and JFJochReceiver::GetStatus + if (message.image.GetCompressedSize() > 0) + message.compression_ratio = static_cast(message.image.GetUncompressedSize()) + / static_cast(message.image.GetCompressedSize()); +} + +void JFJochReceiver::AddToAverages(const DataMessage &message) { + // A stream that does not state it (DECTRIS) delivers whole images + if (message.image_collection_efficiency.value_or(1.0f) != 1.0f) + return; + saturated_pixels.Add(message.saturated_pixel_count); + error_pixels.Add(message.error_pixel_count); + if (message.roi.contains("beam")) { + roi_beam_npixel.Add(message.roi.at("beam").pixels); + roi_beam_sum.Add(message.roi.at("beam").sum); + } +} + +bool JFJochReceiver::ForwardImage(DataMessage &message, ZeroCopyReturnValue *loc) { + if (loc == nullptr) { + writer_queue_full = true; + return false; + } + auto writer_buffer = (uint8_t *) loc->GetImage(); + CBORStream2Serializer serializer(writer_buffer, experiment.GetImageBufferLocationSize()); + if (!SerializeImageOrDrop(serializer, message, *loc, experiment.GetImageBufferLocationSize(), logger)) + return false; + loc->SetImageNumber(message.number); + loc->SetImageSize(serializer.GetBufferSize()); + loc->SetIndexed(message.indexing_result.value_or(false)); + loc->ReadyToSend(); + + if (zmq_preview_socket != nullptr) + zmq_preview_socket->SendImage(writer_buffer, serializer.GetBufferSize()); + + if (zmq_metadata_socket != nullptr) + zmq_metadata_socket->AddDataMessage(message); + + if (push_images_to_writer) { + // Only count images the pusher accepted; TCP refuses them on a broken/absent connection + if (image_pusher.SendImage(*loc)) + ++images_sent; + } else + loc->release(); + UpdateMaxImageSent(message.number); + return true; +} + void JFJochReceiver::SendEndMessage() { EndMessage message{}; message.max_image_number = max_image_number_sent; @@ -173,21 +263,6 @@ void JFJochReceiver::SendEndMessage() { message.az_int_result["dataset"] = plots.GetAzIntProfile(); - // A given cell is what the master file records; the consensus of the refined cells only stands in - // for one that was not given. - if (!experiment.GetUnitCell()) - message.unit_cell = indexer.GetConsensusUnitCell(); - // The collection does not stop for an indexer that failed - the images are written regardless - but - // its indexing rate is then not a statement about the crystal, and the log has to say so. - if (const int64_t n = indexer.GetIndexerErrorCount(); n > 0) { - try { - indexer.RethrowIndexerError(); - } catch (const std::exception &e) { - logger.Error("Indexing failed {} times in this collection, first with: {}. Those attempts are " - "left out of the indexing rate", n, e.what()); - } - } - for (int i = 0; i < adu_histogram_module.size(); i++) message.adu_histogram["module" + std::to_string(i)] = adu_histogram_module[i]->GetHistogram(); @@ -216,6 +291,22 @@ void JFJochReceiver::SendEndMessage() { zmq_preview_socket->EndDataCollection(message); } +void JFJochReceiver::AddIndexingToEndMessage(EndMessage &message, const IndexAndRefine &indexer) { + // A given cell is what the master file records; the consensus of the refined cells only stands in + // for one that was not given. + if (!experiment.GetUnitCell()) + message.unit_cell = indexer.GetConsensusUnitCell(); + // The images are written regardless, but the indexing rate is then not a statement about the crystal + if (const int64_t error_count = indexer.GetIndexerErrorCount(); error_count > 0) { + try { + indexer.RethrowIndexerError(); + } catch (const std::exception &e) { + logger.Error("Indexing failed {} times in this collection, first with: {}. Those attempts are " + "left out of the indexing rate", error_count, e.what()); + } + } +} + JFJochReceiverOutput JFJochReceiver::GetFinalStatistics() const { JFJochReceiverOutput ret; ret.efficiency = GetEfficiency(); @@ -225,7 +316,6 @@ JFJochReceiverOutput JFJochReceiver::GetFinalStatistics() const { ret.status = GetStatus(); ret.writer_err = writer_error; ret.scan_result = scan_result.GetResult(); - ret.images_written = images_written; ret.processing_time = plots.GetMeanProcessingTime(); return ret; } diff --git a/receiver/JFJochReceiver.h b/receiver/JFJochReceiver.h index 559b43a3b..693d52a83 100644 --- a/receiver/JFJochReceiver.h +++ b/receiver/JFJochReceiver.h @@ -10,18 +10,16 @@ #include "../common/DiffractionExperiment.h" #include "../image_analysis/spot_finding/SpotFindingSettings.h" #include "JFJochReceiverOutput.h" -#include "../image_puller/ZMQImagePuller.h" #include "../common/ImageBuffer.h" #include "../image_pusher/ImagePusher.h" #include "../preview/ZMQPreviewSocket.h" #include "../preview/ZMQMetadataSocket.h" -#include "../preview/PreviewImage.h" #include "../common/JFJochReceiverPlots.h" #include "LossyFilter.h" #include "../common/MovingAverage.h" #include "../common/ScanResultGenerator.h" -#include "../image_analysis/indexing/IndexerThreadPool.h" -#include "../image_analysis/IndexAndRefine.h" + +class IndexAndRefine; class JFJochReceiver { protected: @@ -41,6 +39,7 @@ protected: std::atomic uncompressed_size{0}; std::atomic images_sent{0}; std::atomic images_collected{0}; + std::atomic collection_efficiency_sum{0.0}; // a partial image counts as its fraction; a stream without the key counts each as whole std::atomic images_skipped{0}; std::chrono::time_point start_time; @@ -75,8 +74,6 @@ protected: MovingAverage roi_beam_npixel{1000}; MovingAverage roi_beam_sum{1000}; - std::optional images_written; - std::vector> adu_histogram_module; PixelMask pixel_mask; @@ -85,22 +82,32 @@ protected: std::optional max_delay; std::mutex max_delay_mutex; - IndexAndRefine indexer; - std::string writer_error; void UpdateMaxDelay(uint64_t delay); JFJochReceiverStatus GetStatus() const; // The start message of this collection, stating that experiment, mask and azimuthal mapping. - StartMessage MakeStartMessage(const DiffractionExperiment &x, const PixelMask &mask, + StartMessage MakeStartMessage(const DiffractionExperiment &experiment, const PixelMask &mask, const AzimuthalIntegrationMapping &mapping); void SendStartMessage(); void SendStartMessage(StartMessage message); void SendEndMessage(); void SaveStartMessageToImageBuffer(const StartMessage &msg); - // The last word on the end message before it is sent: a receiver whose analysis is not the - // per-image one fills in what it found. + + static bool SerializeImageOrDrop(CBORStream2Serializer &serializer, DataMessage &msg, + ZeroCopyReturnValue &loc, size_t slot_size, Logger &logger); + void CountImage(const DataMessage &message); + // What the receiver states about an image from a stream + void StampImage(DataMessage &message, size_t fifo_delay); + void AddToAverages(const DataMessage &message); + // Serializes into the slot and sends it on; false if there is no slot or the image does not fit + bool ForwardImage(DataMessage &message, ZeroCopyReturnValue *loc); + + // The last word on the end message: a receiver with an analysis of its own fills in what it found virtual void AddToEndMessage(EndMessage &message) {} + // For the receivers that index: the consensus cell, and how often the indexer failed + void AddIndexingToEndMessage(EndMessage &message, const IndexAndRefine &indexer); + virtual uint64_t GetImagesDropped() const { return 0; } virtual void Cancel(const JFJochException &e); public: JFJochReceiver(const DiffractionExperiment& experiment, @@ -112,8 +119,7 @@ public: Logger &logger, const PixelMask &pixel_mask, ZMQPreviewSocket *zmq_preview_socket = nullptr, - ZMQMetadataSocket *zmq_metadata_socket = nullptr, - IndexerThreadPool *indexing_thread_pool = nullptr); + ZMQMetadataSocket *zmq_metadata_socket = nullptr); virtual ~JFJochReceiver(); virtual void StopReceiver() = 0; @@ -122,7 +128,7 @@ public: virtual void Cancel(bool silent); virtual float GetProgress() const = 0; - virtual void SetSpotFindingSettings(const SpotFindingSettings &spot_finding_settings) = 0; + virtual void SetSpotFindingSettings(const SpotFindingSettings &spot_finding_settings); virtual JFJochReceiverOutput GetFinalStatistics() const; }; diff --git a/receiver/JFJochReceiverCurrentStatus.cpp b/receiver/JFJochReceiverCurrentStatus.cpp index 1ec646f21..b4f350b6f 100644 --- a/receiver/JFJochReceiverCurrentStatus.cpp +++ b/receiver/JFJochReceiverCurrentStatus.cpp @@ -19,12 +19,6 @@ void JFJochReceiverCurrentStatus::SetStatus(const JFJochReceiverStatus &in_statu status = in_status; } -void JFJochReceiverCurrentStatus::SetEfficiency(const std::optional &e) { - std::unique_lock ul(m); - if (status.has_value()) - status->efficiency = e; -} - std::optional JFJochReceiverCurrentStatus::GetProgress() const { float tmp = progress; if (std::isnan(tmp)) diff --git a/receiver/JFJochReceiverCurrentStatus.h b/receiver/JFJochReceiverCurrentStatus.h index 030bbf110..b9b0e4190 100644 --- a/receiver/JFJochReceiverCurrentStatus.h +++ b/receiver/JFJochReceiverCurrentStatus.h @@ -23,6 +23,7 @@ struct JFJochReceiverStatus { uint64_t images_collected; uint64_t images_sent; uint64_t images_skipped; + uint64_t images_dropped = 0; // no room for them before the receiver: they never reached it std::optional images_written; bool cancelled; }; @@ -35,7 +36,6 @@ public: std::optional GetStatus() const; void Clear(); void SetStatus(const JFJochReceiverStatus &status); - void SetEfficiency(const std::optional &e); void SetProgress(std::optional input); std::optional GetProgress() const; }; diff --git a/receiver/JFJochReceiverDarkMask.cpp b/receiver/JFJochReceiverDarkMask.cpp new file mode 100644 index 000000000..527cd77f0 --- /dev/null +++ b/receiver/JFJochReceiverDarkMask.cpp @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "JFJochReceiverDarkMask.h" +#include "../frame_serialize/CBORStream2Serializer.h" + +JFJochReceiverDarkMask::JFJochReceiverDarkMask(const DiffractionExperiment &in_experiment, + const PixelMask &in_pixel_mask, + ImagePuller &in_image_puller, + ImagePusher &in_image_pusher, + Logger &in_logger, + int64_t forward_and_sum_nthreads, + const SpotFindingSettings &in_spot_finding_settings, + JFJochReceiverCurrentStatus &in_current_status, + JFJochReceiverPlots &in_plots, + ImageBuffer &in_image_buffer) + : JFJochReceiverStream(in_experiment, + in_pixel_mask, + in_image_puller, + in_image_pusher, + in_logger, + forward_and_sum_nthreads, + in_spot_finding_settings, + in_current_status, + in_plots, + in_image_buffer), + mask_buffer(nthreads), + dark_mask_analysis(in_experiment.GetDarkMaskSettings(), in_experiment.GetPixelsNum()) { + StartThreads(); +} + +JFJochReceiverDarkMask::~JFJochReceiverDarkMask() { + cancelled = true; + JoinThreads(); +} + +void JFJochReceiverDarkMask::OnImage(uint32_t id, DataMessage &data_msg, std::chrono::steady_clock::time_point received) { + CountImage(data_msg); + + dark_mask_analysis.AnalyzeImage(data_msg, mask_buffer[id]); + + auto loc = image_buffer.GetImageSlot(); + + if (loc == nullptr) + writer_queue_full = true; + else { + auto writer_buffer = (uint8_t *) loc->GetImage(); + CBORStream2Serializer serializer(writer_buffer, experiment.GetImageBufferLocationSize()); + if (SerializeImageOrDrop(serializer, data_msg, *loc, + experiment.GetImageBufferLocationSize(), logger)) { + loc->SetImageNumber(data_msg.number); + loc->SetImageSize(serializer.GetBufferSize()); + loc->SetIndexed(false); + loc->release(); + } + } +} + +// A dark mask run has frames and no images. +int64_t JFJochReceiverDarkMask::ExpectedImages() const { + return experiment.GetFrameNum(); +} + +JFJochReceiverOutput JFJochReceiverDarkMask::GetFinalStatistics() const { + JFJochReceiverOutput ret = JFJochReceiver::GetFinalStatistics(); + ret.dark_mask_result = dark_mask_analysis.GetMask(); + return ret; +} diff --git a/receiver/JFJochReceiverDarkMask.h b/receiver/JFJochReceiverDarkMask.h new file mode 100644 index 000000000..d0706f88a --- /dev/null +++ b/receiver/JFJochReceiverDarkMask.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include + +#include "JFJochReceiverStream.h" +#include "../image_analysis/dark_mask_analysis/DarkMaskAnalysis.h" + +// The dark frames of the mask sequence: each goes into DarkMaskAnalysis and the image buffer, nothing goes out on any socket. +class JFJochReceiverDarkMask : public JFJochReceiverStream { + std::vector> mask_buffer; // per image thread + DarkMaskAnalysis dark_mask_analysis; + + void OnImage(uint32_t id, DataMessage &message, std::chrono::steady_clock::time_point received) override; + int64_t ExpectedImages() const override; +public: + JFJochReceiverDarkMask(const DiffractionExperiment &experiment, + const PixelMask &pixel_mask, + ImagePuller &image_puller, + ImagePusher &image_pusher, + Logger &logger, int64_t forward_and_sum_nthreads, + const SpotFindingSettings &spot_finding_settings, + JFJochReceiverCurrentStatus ¤t_status, + JFJochReceiverPlots &plots, + ImageBuffer &image_buffer); + ~JFJochReceiverDarkMask() override; + + JFJochReceiverOutput GetFinalStatistics() const override; +}; diff --git a/receiver/JFJochReceiverFPGA.cpp b/receiver/JFJochReceiverFPGA.cpp index dd2fdd55d..163eaa553 100644 --- a/receiver/JFJochReceiverFPGA.cpp +++ b/receiver/JFJochReceiverFPGA.cpp @@ -36,9 +36,10 @@ JFJochReceiverFPGA::JFJochReceiverFPGA(const DiffractionExperiment &in_experimen in_logger, in_pixel_mask, in_zmq_preview_socket, - in_zmq_metadata_socket, - indexing_thread_pool), + in_zmq_metadata_socket), calibration(nullptr), + // retain_outcomes=false: the receiver indexes only, there is no scaling or merging at the end + indexer(experiment, indexing_thread_pool, false, /*real_time=*/true), acquisition_device(in_aq_device), ndatastreams(experiment.GetDataStreamsNum()), frame_transformation_nthreads( @@ -857,6 +858,7 @@ void JFJochReceiverFPGA::GPUSink(DataMessage &message, const AzimuthalIntegratio } void JFJochReceiverFPGA::AddToEndMessage(EndMessage &message) { + AddIndexingToEndMessage(message, indexer); if (!gpu) return; message.unit_cell = gpu->GetConsensusUnitCell(); @@ -870,7 +872,7 @@ void JFJochReceiverFPGA::AddToEndMessage(EndMessage &message) { if (!message.merged) return; // Every pusher serialises END into a buffer of MESSAGE_SIZE_FOR_START_END, and an END that does not fit there - // fails the collection. So it is serialised here first, as JFJochReceiverRugnux does; one that does not fit + // fails the collection. So it is serialised here first, as JFJochReceiverRugnuxRotation does; one that does not fit // goes without the merged data. const auto scratch = std::make_unique_for_overwrite(MESSAGE_SIZE_FOR_START_END); CBORStream2Serializer serializer(scratch.get(), MESSAGE_SIZE_FOR_START_END); diff --git a/receiver/JFJochReceiverFPGA.h b/receiver/JFJochReceiverFPGA.h index 08c839647..d5afc4c73 100644 --- a/receiver/JFJochReceiverFPGA.h +++ b/receiver/JFJochReceiverFPGA.h @@ -45,6 +45,8 @@ class JFJochReceiverFPGA : public JFJochReceiver { const JFCalibration *calibration; + IndexAndRefine indexer; + std::vector> pedestal; std::vector roi_map; diff --git a/receiver/JFJochReceiverLite.cpp b/receiver/JFJochReceiverLite.cpp index 03b0114f8..a91e0bcf4 100644 --- a/receiver/JFJochReceiverLite.cpp +++ b/receiver/JFJochReceiverLite.cpp @@ -2,46 +2,12 @@ // SPDX-License-Identifier: GPL-3.0-only #include "JFJochReceiverLite.h" -#include "../image_analysis/indexing/IndexerFactory.h" #include "../common/CUDAWrapper.h" using namespace std::chrono_literals; -// Serialize a processed image into its buffer slot. The slot holds the compressed -// image plus the per-image CBOR metadata (spot list, reflection list, ...); if an -// unusually rich frame makes that metadata too large to fit alongside the image, -// drop just this frame (log + recycle the slot) instead of letting the serializer -// throw, which would abort the whole data collection. Returns true when the image -// was serialized and the slot is ready for the caller to commit. -bool JFJochReceiverLite::SerializeImageOrDrop(CBORStream2Serializer &serializer, DataMessage &msg, - ZeroCopyReturnValue &loc, size_t slot_size, Logger &logger) { - try { - serializer.SerializeImage(msg); - return true; - } catch (const JFJochException &) { - // Confirm this is a slot-overflow (not some other CBOR error) by re-serializing - // the metadata alone with an empty image placeholder: that always fits when the - // image is what overflowed, and gives the exact metadata size for the log. If it - // throws too, it is not a simple overflow and the exception propagates. - const auto image = msg.image; - msg.image = CompressedImage(nullptr, 0, image.GetWidth(), image.GetHeight(), - image.GetMode(), image.GetCompressionAlgorithm(), - image.GetChannel()); - CBORStream2Serializer meas(static_cast(loc.GetImage()), slot_size); - meas.SerializeImage(msg); - const size_t metadata_size = meas.GetImageAppendOffset(); - msg.image = image; - - logger.Error("Dropping image {} - CBOR metadata too large to store image: " - "metadata {} B + image {} B do not fit in buffer slot {} B", - msg.number, metadata_size, image.GetCompressedSize(), slot_size); - loc.release(); - return false; - } -} - -int64_t JFJochReceiverLite::NumberOfDataAnalysisThreads(int64_t requested_thread_number, - const DiffractionExperiment& in_experiment) { +int64_t JFJochReceiverLite::NumberOfThreads(int64_t requested_thread_number, + const DiffractionExperiment &in_experiment) { int64_t number_of_images = in_experiment.GetImageNum(); auto image_time = in_experiment.GetImageTime(); @@ -75,217 +41,30 @@ JFJochReceiverLite::JFJochReceiverLite(const DiffractionExperiment &in_experimen ZMQPreviewSocket *in_zmq_preview_socket, ZMQMetadataSocket *in_zmq_metadata_socket, IndexerThreadPool *indexing_thread_pool) - : JFJochReceiver(in_experiment, - in_image_buffer, - in_image_pusher, - in_current_status, - in_plots, - in_spot_finding_settings, - in_logger, - in_pixel_mask, - in_zmq_preview_socket, - in_zmq_metadata_socket, - indexing_thread_pool), - image_puller(in_image_puller), - data_analysis_nthreads(NumberOfDataAnalysisThreads(forward_and_sum_nthreads, in_experiment)), - data_analysis_started(data_analysis_nthreads), - measurement_started(1), - dark_mask_analysis(in_experiment.GetDarkMaskSettings(), in_experiment.GetPixelsNum()) { - logger.Debug("Starting {} data analysis threads", data_analysis_nthreads); - - if (experiment.GetDetectorMode() == DetectorMode::DarkMask) { - for (int i = 0; i < data_analysis_nthreads; i++) - data_analysis_futures.emplace_back( - std::async(std::launch::async, &JFJochReceiverLite::MaskThread, this, i) - ); - } else { - // Start frame transformation threads - for (int i = 0; i < data_analysis_nthreads; i++) - data_analysis_futures.emplace_back( - std::async(std::launch::async, &JFJochReceiverLite::DataAnalysisThread, this, i) - ); - } - - measurement = std::async(std::launch::async, &JFJochReceiverLite::MeasurementThread, this); - - data_analysis_started.wait(); - logger.Debug("Data analysis threads ready"); + : JFJochReceiverStream(in_experiment, + in_pixel_mask, + in_image_puller, + in_image_pusher, + in_logger, + NumberOfThreads(forward_and_sum_nthreads, in_experiment), + in_spot_finding_settings, + in_current_status, + in_plots, + in_image_buffer, + in_zmq_preview_socket, + in_zmq_metadata_socket), + // retain_outcomes=false: the receiver indexes only, there is no scaling or merging at the end + indexer(experiment, indexing_thread_pool, false, /*real_time=*/true), + analysis(nthreads) { + StartThreads(); } JFJochReceiverLite::~JFJochReceiverLite() { cancelled = true; - try { - if (measurement.valid()) - measurement.get(); - } catch (const std::exception& e) { - logger.Error("ReceiverLite teardown failed: {}", e.what()); - } catch (...) { - logger.Error("ReceiverLite teardown failed with unknown exception"); - } + JoinThreads(); } -void JFJochReceiverLite::MeasurementThread() { - try { - // Wait for start message to arrive. Once the detector has reported it is done, nothing - // more is coming: allow a start message that is still in flight to land, then give up. - // Without this the poll runs for ever - a detector that never streamed leaves Stop() - // waiting on this thread with no way out other than a cancel from the operator. - std::optional give_up_at; - auto msg = image_puller.PollImage(); - - while (!cancelled && (!msg.has_value() || !msg->cbor || !msg->cbor->start_message)) { - if (detector_finished && !give_up_at) - give_up_at = std::chrono::steady_clock::now() + StartMessageGrace; - if (give_up_at && (std::chrono::steady_clock::now() > *give_up_at)) { - logger.Error("Detector finished, but no start message arrived - abandoning measurement"); - cancelled = true; - break; - } - msg = image_puller.PollImage(); - } - - if (cancelled) { - current_status.SetProgress({}); - current_status.SetStatus(GetStatus()); - measurement_started.count_down(); - return; // just quit the function - no measurement, no results - } - - ApplyStartMessage(experiment, msg->cbor->start_message.value()); - image_buffer.StartMeasurement(experiment); // Only at this point we know bit-depth of the images - start_time = std::chrono::system_clock::now(); - // Send new start message out - SendStartMessage(); - measurement_started.count_down(); - - } catch (const JFJochException &e) { - logger.ErrorException(e); - Cancel(e); - measurement_started.count_down(); - throw; - } - - logger.Info("Receiving started"); - - // Analysis is running - // ... - // Till it is done. - - // Combine frame transformation threads - for (auto &f: data_analysis_futures) - f.get(); - - logger.Debug("Data analysis threads finished"); - - current_status.SetProgress(1.0); - current_status.SetStatus(GetStatus()); - - // Send end message out - SendEndMessage(); - - if (!image_buffer.CheckIfBufferReturned(SendFinalizeTimeout)) { - logger.Error("Send commands not finalized in {} seconds", SendFinalizeTimeout.count()); - throw JFJochException(JFJochExceptionCategory::ZeroMQ, "Send commands not finalized in " - + std::to_string(SendFinalizeTimeout.count()) + " seconds"); - } - - logger.Debug("All images sent through ZeroMQ"); - - if (push_images_to_writer) - writer_error = image_pusher.Finalize(); - - logger.Debug("Writing process finalized"); - - end_time = std::chrono::system_clock::now(); - - current_status.SetProgress({}); - current_status.SetStatus(GetStatus()); -} - -void JFJochReceiverLite::ApplyStartMessage(DiffractionExperiment &experiment, const StartMessage &msg) { - if ((experiment.GetXPixelsNum() != msg.image_size_x) - || (experiment.GetYPixelsNum() != msg.image_size_y)) - throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, - "Mismatch in detector size"); - if (fabs(experiment.GetPixelSize_mm() - msg.pixel_size_x * 1e3) > 1e-7) - throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, - "Mismatch in pixel size"); - - experiment.Detector().Description(msg.detector_description); - experiment.Detector().SerialNumber(msg.detector_serial_number); - experiment.Detector().SensorMaterial(msg.sensor_material); - experiment.Detector().SensorThickness_um(msg.sensor_thickness * 1e6); - experiment.Detector().SaturationLimit(SaturationLimitFromValue(msg.saturation_value)); - // The detector decides the corrections applied to the pixels (DECTRIS enables them by default); - // the outgoing start message is rebuilt from the experiment, so without copying them NXmx would - // record them as not applied. - experiment.Detector().CountRateCorrectionApplied(msg.countrate_correction_enabled); - experiment.Detector().CountRateCorrectionLookupTable(msg.countrate_correction_lookup_table); - experiment.Detector().FlatfieldApplied(msg.flatfield_enabled); - experiment.Detector().VirtualPixelInterpolationApplied(msg.virtual_pixel_interpolation_enabled); - // The mask written with the data is ours (the detector's plus user masking); it is never - // uploaded to the detector, so the pixels arrive without it applied. - experiment.ApplyPixelMask(false); - // Images are forwarded byte-for-byte, so the stream's own image_dtype - not anything configured - // locally - decides both the width and the sign the outgoing metadata must declare. Taking only - // the width used to leave a signed stream declared unsigned in NXmx. - experiment.Detector().BitDepthImage(msg.bit_depth_image); - experiment.PixelSigned(msg.pixel_signed); -} - -void JFJochReceiverLite::MaskThread(uint32_t id) { - std::vector buffer; - data_analysis_started.count_down(); - measurement_started.wait(); - - while (!cancelled && !end_message_received) { - try { - auto msg = image_puller.PollImage(std::chrono::milliseconds(5)); - - if (!msg.has_value() || !msg->cbor) { - // Message not received or not parsed - continue; - } else if (msg->cbor->end_message) { - end_message_received = true; - logger.Debug("Thread {} end message received in JFJochReceiverLite", id); - } else if (msg->cbor->data_message) { - ++images_collected; - DataMessage data_msg = msg->cbor->data_message.value(); - - dark_mask_analysis.AnalyzeImage(data_msg, buffer); - - compressed_size += data_msg.image.GetCompressedSize(); - uncompressed_size += data_msg.image.GetUncompressedSize(); - UpdateMaxImageReceived(data_msg.number); - - auto loc = image_buffer.GetImageSlot(); - - if (loc == nullptr) - writer_queue_full = true; - else { - auto writer_buffer = (uint8_t *) loc->GetImage(); - CBORStream2Serializer serializer(writer_buffer, experiment.GetImageBufferLocationSize()); - if (SerializeImageOrDrop(serializer, data_msg, *loc, - experiment.GetImageBufferLocationSize(), logger)) { - loc->SetImageNumber(data_msg.number); - loc->SetImageSize(serializer.GetBufferSize()); - loc->SetIndexed(false); - loc->release(); - } - } - } - current_status.SetProgress(GetProgress()); - current_status.SetStatus(GetStatus()); - } catch (const JFJochException &e) { - logger.ErrorException(e); - Cancel(e); - } - } -} - -void JFJochReceiverLite::DataAnalysisThread(uint32_t id) { - std::unique_ptr analysis; - +void JFJochReceiverLite::ImageThread(uint32_t id) { logger.Debug("Thread {} started", id); try { @@ -294,170 +73,61 @@ void JFJochReceiverLite::DataAnalysisThread(uint32_t id) { logger.Warning("Error pinning GPU {}", e.what()); } - data_analysis_started.count_down(); - measurement_started.wait(); + WaitForImages(); try { - analysis = std::make_unique(experiment, *az_int_mapping, pixel_mask, indexer, - /*enable_fused_adaptive_gpu=*/true); + // The analysis works on the assembled image; an image in raw module layout is only forwarded. + if (experiment.IsGeometryTransformed()) + analysis[id] = std::make_unique(experiment, *az_int_mapping, pixel_mask, indexer, + /*enable_fused_adaptive_gpu=*/true); } catch (const JFJochException &e) { Cancel(e); return; } - while (!cancelled && !end_message_received) { - try { - auto msg = image_puller.PollImage(std::chrono::milliseconds(5)); - - if (!msg.has_value() || !msg->cbor) { - // Message not received or not parsed - continue; - } else if (msg->cbor->end_message) { - // Message is end message - end_message_received = true; - logger.Debug("Thread {} End message received in JFJochReceiverLite", id); - } else if (msg->cbor->calibration) { - // Calibration messages are just forwarded - if (push_images_to_writer) - image_pusher.SendCalibration(msg->cbor->calibration.value()); - } else if (msg->cbor->data_message) { - auto start = std::chrono::high_resolution_clock::now(); - DataMessage data_msg = msg->cbor->data_message.value(); - - ++images_collected; - - auto compressed_size_img = data_msg.image.GetCompressedSize(); - auto uncompressed_size_img = data_msg.image.GetUncompressedSize(); - - compressed_size += compressed_size_img; - uncompressed_size += uncompressed_size_img; - - UpdateMaxImageReceived(data_msg.number); - - auto image_start_time = std::chrono::high_resolution_clock::now(); - - AzimuthalIntegrationProfile profile(*az_int_mapping); - // Indexing only: integration is rugnux's (live or offline), not the Lite receiver's. - SpotFindingSettings settings = GetSpotFindingSettings(); - settings.quick_integration = false; - analysis->Analyze(data_msg, profile, settings); - - auto image_end_time = std::chrono::high_resolution_clock::now(); - std::chrono::duration image_duration = image_end_time - image_start_time; - - data_msg.processing_time_s = image_duration.count(); - data_msg.original_number = data_msg.number; - data_msg.user_data = experiment.GetImageAppendix(); - data_msg.run_number = experiment.GetRunNumber(); - data_msg.run_name = experiment.GetRunName(); - data_msg.receiver_buf_available = image_buffer.GetAvailSlots(); - data_msg.receiver_aq_dev_delay = image_puller.GetCurrentFifoUtilization(); - - // Compression ratio is uncompressed/compressed (e.g. 7x), matching the FPGA path - // (JFJochReceiverFPGA) and the aggregate (JFJochReceiver::GetFinalStatistics) - not the - // reciprocal. - if (compressed_size_img > 0) - data_msg.compression_ratio = static_cast(uncompressed_size_img) - / static_cast(compressed_size_img); - - saturated_pixels.Add(data_msg.saturated_pixel_count); - error_pixels.Add(data_msg.error_pixel_count); - - if (data_msg.roi.contains("beam")) { - roi_beam_npixel.Add(data_msg.roi["beam"].pixels); - roi_beam_sum.Add(data_msg.roi["beam"].sum); - } - - plots.Add(data_msg, profile); - scan_result.Add(data_msg); - - if (!serialmx_filter.ApplyFilter(data_msg)) - ++images_skipped; - else { - auto loc = image_buffer.GetImageSlot(); - - if (loc == nullptr) - writer_queue_full = true; - else { - auto writer_buffer = (uint8_t *) loc->GetImage(); - CBORStream2Serializer serializer(writer_buffer, experiment.GetImageBufferLocationSize()); - if (SerializeImageOrDrop(serializer, data_msg, *loc, - experiment.GetImageBufferLocationSize(), logger)) { - loc->SetImageNumber(data_msg.number); - loc->SetImageSize(serializer.GetBufferSize()); - loc->SetIndexed(data_msg.indexing_result.value_or(false)); - loc->ReadyToSend(); - - if (zmq_preview_socket != nullptr) - zmq_preview_socket->SendImage(writer_buffer, serializer.GetBufferSize()); - - if (zmq_metadata_socket != nullptr) - zmq_metadata_socket->AddDataMessage(data_msg); - - if (push_images_to_writer) { - // Only count images the pusher accepted; TCP refuses them on a broken/absent - // connection. Images actually written are GetImagesWritten() (ACK-based). - if (image_pusher.SendImage(*loc)) - ++images_sent; - } else - loc->release(); - UpdateMaxImageSent(data_msg.number); - } - } - } - - auto end = std::chrono::high_resolution_clock::now(); - std::chrono::duration duration_total = end - start; - logger.Debug("Thread {} Image {:6d} processing {:8.04f} s analysis {:8.04f} s", - id, data_msg.number, duration_total.count(), image_duration.count()); - } - UpdateMaxDelay(image_puller.GetCurrentFifoUtilization()); - current_status.SetProgress(GetProgress()); - current_status.SetStatus(GetStatus()); - } catch (const JFJochException &e) { - logger.ErrorException(e); - Cancel(e); - } - } + PollImages(id); logger.Debug("Thread {} finished", id); } -void JFJochReceiverLite::Cancel(bool silent) { - JFJochReceiver::Cancel(silent); - // A silent cancel is not a request to abort - it says the detector has finished - so it must - // not set cancelled. It does bound the wait for the start message: see MeasurementThread. - detector_finished = true; +void JFJochReceiverLite::AddToEndMessage(EndMessage &message) { + JFJochReceiverStream::AddToEndMessage(message); + AddIndexingToEndMessage(message, indexer); } -void JFJochReceiverLite::StopReceiver() { - if (measurement.valid()) { - measurement.get(); - logger.Debug("Receiver stopped"); +void JFJochReceiverLite::OnImage(uint32_t id, DataMessage &data_msg, std::chrono::steady_clock::time_point received) { + auto start = std::chrono::high_resolution_clock::now(); + + CountImage(data_msg); + + auto image_start_time = std::chrono::high_resolution_clock::now(); + + AzimuthalIntegrationProfile profile(*az_int_mapping); + if (analysis[id]) { + // Indexing only: integration is rugnux's (live or offline), not the Lite receiver's. + SpotFindingSettings settings = GetSpotFindingSettings(); + settings.quick_integration = false; + analysis[id]->AnalyzeStreamImage(data_msg, profile, settings); } + + auto image_end_time = std::chrono::high_resolution_clock::now(); + std::chrono::duration image_duration = image_end_time - image_start_time; + + data_msg.processing_time_s = image_duration.count(); + StampImage(data_msg, image_puller.GetCurrentFifoUtilization()); + AddToAverages(data_msg); + + plots.Add(data_msg, profile); + scan_result.Add(data_msg); + + if (!serialmx_filter.ApplyFilter(data_msg)) + ++images_skipped; + else + ForwardImage(data_msg, image_buffer.GetImageSlot()); + + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration duration_total = end - start; + logger.Debug("Thread {} Image {:6d} processing {:8.04f} s analysis {:8.04f} s", + id, data_msg.number, duration_total.count(), image_duration.count()); } -float JFJochReceiverLite::GetEfficiency() const { - if (experiment.GetFrameNum() == 0) - return 0; - return static_cast(images_collected) / static_cast(experiment.GetFrameNum()); -} - -float JFJochReceiverLite::GetProgress() const { - if (experiment.GetFrameNum() == 0) - return 0.0; - return static_cast(max_image_number_received) / static_cast(experiment.GetFrameNum()); -} - -void JFJochReceiverLite::SetSpotFindingSettings(const SpotFindingSettings &in_spot_finding_settings) { - std::unique_lock ul(spot_finding_settings_mutex); - DiffractionExperiment::CheckDataProcessingSettings(in_spot_finding_settings); - spot_finding_settings = in_spot_finding_settings; -} - -JFJochReceiverOutput JFJochReceiverLite::GetFinalStatistics() const { - JFJochReceiverOutput ret = JFJochReceiver::GetFinalStatistics(); - if (experiment.GetDetectorMode() == DetectorMode::DarkMask) - ret.dark_mask_result = dark_mask_analysis.GetMask(); - return ret; -} diff --git a/receiver/JFJochReceiverLite.h b/receiver/JFJochReceiverLite.h index 2b4248310..31099484f 100644 --- a/receiver/JFJochReceiverLite.h +++ b/receiver/JFJochReceiverLite.h @@ -3,68 +3,23 @@ #pragma once -#include -#include +#include +#include -#include "JFJochReceiver.h" -#include "../common/DiffractionExperiment.h" -#include "../common/PixelMask.h" -#include "../common/ImageBuffer.h" -#include "../common/Logger.h" -#include "../image_pusher/ImagePusher.h" -#include "../preview/PreviewImage.h" -#include "../preview/ZMQPreviewSocket.h" -#include "../preview/ZMQMetadataSocket.h" -#include "LossyFilter.h" -#include "../common/MovingAverage.h" -#include "JFJochReceiverCurrentStatus.h" -#include "../common/JFJochReceiverPlots.h" -#include "JFJochReceiverOutput.h" -#include "../image_puller/ZMQImagePuller.h" +#include "JFJochReceiverStream.h" #include "../image_analysis/MXAnalysisWithoutFPGA.h" -#include "../image_analysis/dark_mask_analysis/DarkMaskAnalysis.h" -class JFJochReceiverLite : public JFJochReceiver { - // Overriding Cancel(bool) below would otherwise hide the Cancel(const JFJochException &) - // the analysis threads call. - using JFJochReceiver::Cancel; +class JFJochReceiverLite : public JFJochReceiverStream { + IndexAndRefine indexer; + std::vector> analysis; // per image thread - ImagePuller &image_puller; + // Each thread builds a GPU analysis of its own + static int64_t NumberOfThreads(int64_t requested_thread_number, const DiffractionExperiment &experiment); - std::atomic end_message_received{false}; - - // Set when the caller reports the run is over - JFJochServices::Stop cancels the receiver - // silently once the detector reads back idle. Only consulted while waiting for the start - // message; a silent cancel must not interrupt images that are still being processed. - std::atomic detector_finished{false}; - - // How long a start message may still be in flight after the detector reported it had finished. - // The detector can also read back as idle for a moment just after it was armed, so a run is not - // abandoned the instant the silent cancel arrives. - constexpr static auto StartMessageGrace = std::chrono::seconds(5); - - const int64_t data_analysis_nthreads; - - std::vector> data_analysis_futures; - std::future measurement; - - std::latch measurement_started; - std::latch data_analysis_started; - - DarkMaskAnalysis dark_mask_analysis; - - void MaskThread(uint32_t id); - void MeasurementThread(); - void DataAnalysisThread(uint32_t id); + void ImageThread(uint32_t id) override; + void OnImage(uint32_t id, DataMessage &message, std::chrono::steady_clock::time_point received) override; + void AddToEndMessage(EndMessage &message) override; public: - // Shared with JFJochReceiverRugnux, which forwards a collection the same way. - static bool SerializeImageOrDrop(CBORStream2Serializer &serializer, DataMessage &msg, - ZeroCopyReturnValue &loc, size_t slot_size, Logger &logger); - // Takes what the detector's start message decides into the experiment. - static void ApplyStartMessage(DiffractionExperiment &experiment, const StartMessage &message); - - static int64_t NumberOfDataAnalysisThreads(int64_t requested_thread_number, const DiffractionExperiment& experiment); - JFJochReceiverLite(const DiffractionExperiment& experiment, const PixelMask &pixel_mask, ImagePuller &image_puller, @@ -78,15 +33,4 @@ public: ZMQMetadataSocket *zmq_metadata_socket = nullptr, IndexerThreadPool *indexing_thread_pool = nullptr); ~JFJochReceiverLite() override; - - void StopReceiver() override; - void Cancel(bool silent) override; - - float GetEfficiency() const override; - float GetProgress() const override; - - void SetSpotFindingSettings(const SpotFindingSettings &spot_finding_settings) override; - JFJochReceiverOutput GetFinalStatistics() const override; }; - - diff --git a/receiver/JFJochReceiverOutput.h b/receiver/JFJochReceiverOutput.h index f6ce317b2..adc35aebf 100644 --- a/receiver/JFJochReceiverOutput.h +++ b/receiver/JFJochReceiverOutput.h @@ -17,17 +17,14 @@ struct JFJochReceiverOutput { ScanResult scan_result; std::vector received_packets; std::vector expected_packets; - std::string master_file_name; uint64_t start_time_ms; uint64_t end_time_ms; float efficiency; bool writer_queue_full_warning; JFJochReceiverStatus status; std::string writer_err; - std::optional images_written; MeanProcessingTime processing_time; - // A problem with the analysis alone (the images are all there), for the broker's status: why a live - // rugnux run was skipped. Empty otherwise. + // Why a live rugnux run was skipped, for the broker's status std::string analysis_warning; }; diff --git a/receiver/JFJochReceiverRugnux.cpp b/receiver/JFJochReceiverRugnux.cpp deleted file mode 100644 index eb0bd65a5..000000000 --- a/receiver/JFJochReceiverRugnux.cpp +++ /dev/null @@ -1,733 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute -// SPDX-License-Identifier: GPL-3.0-only - -#include "JFJochReceiverRugnux.h" - -#include -#include - -#include "spdlog/fmt/fmt.h" - -#include "JFJochReceiverLite.h" -#include "../common/HostMemory.h" -#include "../frame_serialize/CBORStream2Deserializer.h" -#include "../frame_serialize/CBORStream2Serializer.h" -#include "../image_analysis/indexing/IndexerThreadPool.h" -#include "../rugnux/RugnuxLive.h" -#include "../rugnux/ResultReport.h" -#include "../writer/RugnuxResultBlocks.h" -#include "RugnuxStructure.h" - -using namespace std::chrono_literals; - -namespace { - // Kept for the rest of the broker - the receiver, the pushers, the preview - when the host memory - // rugnux plans against is decided. - constexpr size_t RUGNUX_HOST_MEMORY_MARGIN = 16ULL * 1024 * 1024 * 1024; - - // The host memory rugnux plans against (HostMemory.h) is what the machine has beside what the broker - // already holds: the image buffer, which has to keep the whole collection and so is most of the - // machine, and a margin - at most the machine's budget. Both come off the machine's total, not off the - // budget: the budget's own fraction is the reserve for the rest of the machine, which the margin already - // keeps for the broker, and taken off twice it left nothing (1 byte) with a buffer sized to most of the - // machine. A tenth of the machine stays out of it for the system and the other services on the host (a - // writer, for one). Never 0, which would take the whole machine's budget: at worst what could run side by side - // runs one part after the other. - size_t RugnuxHostBudget(const ImageBuffer &buffer) { - const size_t total = get_host_total_memory(); - const size_t taken = buffer.GetBufferSize() + RUGNUX_HOST_MEMORY_MARGIN; - const size_t usable = total / 10 * 9; - return usable > taken ? std::min(DefaultHostMemoryBudget(), usable - taken) : 1; - } - - double SecondsSince(std::chrono::steady_clock::time_point t) { - return std::chrono::duration(std::chrono::steady_clock::now() - t).count(); - } - - // A run reachable for Cut while it exists: registered in `slot` (under `m`, cancelled at once where the - // cut came first) and taken off again before it is destroyed, an exception included. - class Reachable { - std::mutex &m; - Rugnux *&slot; - public: - Reachable(std::mutex &m, Rugnux *&slot, Rugnux &run, const bool &cut, const std::atomic &cancelled) - : m(m), slot(slot) { - std::unique_lock ul(m); - slot = &run; - if (cut || cancelled) - run.Cancel(); - } - ~Reachable() { - std::unique_lock ul(m); - slot = nullptr; - } - }; - - // What rugnux reports as it goes: the per-image results of its canonical pass into the receiver's - // plots and scan result - the plots accumulate, so an image reported by the pre-pass as well would be - // counted twice - and its steps into the live status (not the log: they are processing results). - class LiveObserver : public RugnuxObserver { - JFJochReceiverPlots &plots; - ScanResultGenerator &scan_result; - const AzimuthalIntegrationMapping &mapping; - RugnuxLiveStatus &status; - std::atomic canonical{false}; - public: - LiveObserver(JFJochReceiverPlots &plots, ScanResultGenerator &scan_result, - const AzimuthalIntegrationMapping &mapping, RugnuxLiveStatus &status) - : plots(plots), scan_result(scan_result), mapping(mapping), status(status) {} - - void OnImagePass(bool c) override { canonical = c; } - void OnImageProcessed(const DataMessage &msg) override { - if (!canonical) - return; - plots.Add(msg, AzimuthalIntegrationProfile(mapping)); - scan_result.Add(msg); - } - void OnProgress(uint64_t done, uint64_t total) override { status.Progress(done, total); } - void OnStep(const std::string &title) override { status.Step(title); } - void OnNote(const std::string &text) override { status.Note(text); } - }; - - std::string StateName(RugnuxLiveState state) { - switch (state) { - case RugnuxLiveState::Idle: return "idle"; - case RugnuxLiveState::Running: return "running"; - case RugnuxLiveState::Done: return "done"; - case RugnuxLiveState::Failed: return "failed"; - case RugnuxLiveState::Cancelled: return "cancelled"; - case RugnuxLiveState::Skipped: return "skipped"; - } - return "unknown"; - } -} - -namespace { - // A score the status carries only where it is a number: JSON has no NaN or infinity (nlohmann writes null) - void SetIfFinite(std::optional &out, double v) { - if (std::isfinite(v)) - out = v; - } -} - -std::optional LiveModel(const ProcessResult &result) { - if (!result.model_validation && !result.model_prior) - return std::nullopt; - RugnuxLiveModel m; - m.placement = result.model_placement; - if (result.model_prior) { - const MolecularReplacementResult &mr = *result.model_prior; - m.model_name = mr.model_name; - m.predicted = mr.predicted_model; - m.mr_decision = mr.decision; - if (!mr.ok) - m.reason = mr.failure_reason; - if (mr.chosen >= 0) { - const MRCandidate &c = mr.candidates[mr.chosen]; - m.mr_space_group = c.space_group.xhm(); - m.copies = c.copies; - SetIfFinite(m.tf_z, c.tf_z); - SetIfFinite(m.rf_z, c.rf_z); - SetIfFinite(m.llg_z, c.llg_z); - SetIfFinite(m.fit_sigma, c.null_sigma); - SetIfFinite(m.mr_d_min, mr.d_min_search); - } - } - if (result.model_validation) { - const ModelValidationResult &v = *result.model_validation; - if (!v.model_name.empty()) - m.model_name = v.model_name; - if (!v.ok) { - if (m.reason.empty()) - m.reason = v.failure_reason; - } else { - SetIfFinite(m.r_work, v.r_work); - SetIfFinite(m.r_free, v.r_free); - SetIfFinite(m.cc_model, v.cc_model_overall); - if (v.fit_tested) - SetIfFinite(m.fit_sigma, v.r_work_sigma); - SetIfFinite(m.map_atom_sigma, v.mean_atom_density_sigma); - if (v.rigid_body_applied) { - SetIfFinite(m.rigid_body_angle_deg, v.rigid_body_angle_deg); - SetIfFinite(m.rigid_body_shift_A, v.rigid_body_shift_A); - } - if (!v.anomalous_sites.empty()) { - m.anomalous_site = v.anomalous_sites.front().label; - SetIfFinite(m.anomalous_site_sigma, v.anomalous_sites.front().sigma); - } - } - } - if (result.refinement) { - const RefinementResult &rf = *result.refinement; - RugnuxLiveRefinement r; - r.performed = rf.ok; - r.cancelled = rf.cancelled; - r.reason = rf.failure_reason; - r.on_gpu = rf.on_gpu; - r.seconds = rf.seconds; - if (rf.ok) { - SetIfFinite(r.r_work, rf.restrained ? rf.r_work : rf.r_work_placement); - SetIfFinite(r.r_free, rf.restrained ? rf.r_free : rf.r_free_placement); - if (rf.restrained) { - SetIfFinite(r.rmsd_bonds_A, rf.geometry.rmsd_bonds); - SetIfFinite(r.rmsd_angles_deg, rf.geometry.rmsd_angles); - } - } - r.blob_count = static_cast(rf.blobs.size()); - for (size_t i = 0; i < std::min(rf.blobs.size(), RugnuxLiveRefinement::MAX_BLOBS); i++) { - const DensityBlob &b = rf.blobs[i]; - RugnuxLiveBlob blob{DensityBlobKindName(b.kind), b.centroid.x, b.centroid.y, b.centroid.z, b.peak_sigma, - b.volume, b.nearest_atom, std::nullopt}; - SetIfFinite(blob.nearest_atom_distance_A, b.nearest_atom_distance); // infinite: no atom near - r.blobs.push_back(blob); - } - m.refinement = std::move(r); - } - return m; -} - -JFJochReceiverRugnux::JFJochReceiverRugnux(const DiffractionExperiment &in_experiment, - const PixelMask &in_pixel_mask, - ImagePuller &in_image_puller, - ImagePusher &in_image_pusher, - Logger &in_logger, - int64_t in_forward_nthreads, - int in_rugnux_nthreads, - std::shared_ptr in_model, - double in_deadline_s, - RugnuxLiveStatus &in_live_status, - const SpotFindingSettings &in_spot_finding_settings, - JFJochReceiverCurrentStatus &in_current_status, - JFJochReceiverPlots &in_plots, - ImageBuffer &in_image_buffer, - ZMQPreviewSocket *in_zmq_preview_socket, - ZMQMetadataSocket *in_zmq_metadata_socket) - : JFJochReceiver(in_experiment, in_image_buffer, in_image_pusher, in_current_status, in_plots, - in_spot_finding_settings, in_logger, in_pixel_mask, in_zmq_preview_socket, - in_zmq_metadata_socket, nullptr), - image_puller(in_image_puller), - reader(in_image_buffer), - forward_nthreads(JFJochReceiverLite::NumberOfDataAnalysisThreads(in_forward_nthreads, in_experiment)), - rugnux_nthreads(in_rugnux_nthreads), - host_budget(RugnuxHostBudget(in_image_buffer)), - model(std::move(in_model)), - deadline_s(in_deadline_s), - live_status(in_live_status), - measurement_started(1), - forward_started(forward_nthreads) { - logger.Info("rugnux live: starting {} forward threads; rugnux runs on {} threads while the images arrive, " - "planning against {:.1f} GB of host memory", forward_nthreads, rugnux_nthreads, - static_cast(host_budget) / 1e9); - for (int i = 0; i < forward_nthreads; i++) - forward_futures.emplace_back(std::async(std::launch::async, &JFJochReceiverRugnux::ForwardThread, this, i)); - measurement = std::async(std::launch::async, &JFJochReceiverRugnux::MeasurementThread, this); - forward_started.wait(); -} - -JFJochReceiverRugnux::~JFJochReceiverRugnux() { - cancelled = true; - reader.Ended(); - { - std::unique_lock ul(rugnux_m); - if (rugnux != nullptr) - rugnux->Cancel(); - if (probe != nullptr) - probe->Cancel(); - } - try { - if (measurement.valid()) - measurement.get(); - } catch (const std::exception &e) { - logger.Error("ReceiverRugnux teardown failed: {}", e.what()); - } -} - -void JFJochReceiverRugnux::MeasurementThread() { - std::future rugnux_done, rugnux_out, probe_done; - try { - // Waiting for the start message, as JFJochReceiverLite::MeasurementThread. - std::optional give_up_at; - auto msg = image_puller.PollImage(); - while (!cancelled && (!msg.has_value() || !msg->cbor || !msg->cbor->start_message)) { - if (detector_finished && !give_up_at) - give_up_at = std::chrono::steady_clock::now() + StartMessageGrace; - if (give_up_at && (std::chrono::steady_clock::now() > *give_up_at)) { - logger.Error("Detector finished, but no start message arrived - abandoning measurement"); - cancelled = true; - break; - } - msg = image_puller.PollImage(); - } - if (cancelled) { - current_status.SetProgress({}); - current_status.SetStatus(GetStatus()); - measurement_started.count_down(); - return; - } - - JFJochReceiverLite::ApplyStartMessage(experiment, msg->cbor->start_message.value()); - image_buffer.StartMeasurement(experiment); - start_time = std::chrono::system_clock::now(); - SendStartMessage(); - - // Rugnux reads the images back from the buffer, so it needs one slot per image: a slot taken - // again would hand it a later image. Without that the collection is only forwarded. - const auto slots = image_buffer.GetStatus().total_slots; - // The image time as the detector produces images, summation included - const double image_time_s = std::chrono::duration(experiment.GetImageTime()).count(); - std::optional expected_collection_s; - if (image_time_s > 0) - expected_collection_s = static_cast(experiment.GetImageNum()) * image_time_s; - live_status.Start(experiment.GetImageNum(), expected_collection_s, msg->received); - if (slots < experiment.GetImageNum()) { - skipped_warning = fmt::format("no rugnux analysis: the image buffer holds {} images, the collection " - "has {}", slots, experiment.GetImageNum()); - logger.Error("{}", skipped_warning); - live_status.Finish(RugnuxLiveState::Skipped, "the image buffer cannot hold the whole collection"); - } else - StartRugnux(rugnux_done, rugnux_out, probe_done); - measurement_started.count_down(); - } catch (const JFJochException &e) { - logger.ErrorException(e); - Cancel(e); - measurement_started.count_down(); - throw; - } catch (const std::exception &e) { - logger.Error("Cancelling data collection due to exception: {}", e.what()); - cancelled = true; - measurement_started.count_down(); - throw; - } - - logger.Info("Receiving started"); - for (auto &f: forward_futures) - f.get(); - reader.Ended(); - images_done = true; - // The latency of the demonstration runs from the last image in (as the puller took it off the socket) - // to every result in place. - const auto last_image = live_status.LastImage(); - logger.Info("Receiving finished, {} images", images_collected.load()); - - std::optional done_s; - if (rugnux_out.valid()) { - // The fixed cutoff: the analysis gets deadline_s after the last image, then gives what it has. - if (deadline_s > 0 && rugnux_out.wait_for(std::chrono::duration(deadline_s)) - == std::future_status::timeout) - Cut("deadline"); - rugnux_out.wait(); - if (last_image) - done_s = SecondsSince(last_image.value()); - } - if (probe_done.valid()) - probe_done.wait(); - - current_status.SetProgress(1.0); - current_status.SetStatus(GetStatus()); - - SendEndMessage(); - const double end_sent_s = last_image ? SecondsSince(last_image.value()) : 0.0; - - if (!image_buffer.CheckIfBufferReturned(SendFinalizeTimeout)) { - logger.Error("Send commands not finalized in {} seconds", SendFinalizeTimeout.count()); - throw JFJochException(JFJochExceptionCategory::ZeroMQ, "Send commands not finalized in " - + std::to_string(SendFinalizeTimeout.count()) + " seconds"); - } - if (push_images_to_writer) - writer_error = image_pusher.Finalize(); - if (rugnux_out.valid()) { - // The run's outcome, in one line: its results are in the live status, the end message and the files. - const auto live = live_status.Get(); - std::string outcome = "rugnux live: " + StateName(live.state); - if (!live.message.empty()) - outcome += " (" + live.message + ")"; - if (!live.cut.empty()) - outcome += " (cut: " + live.cut + ")"; - if (live.start_to_first_image_s && live.last_image_s && live.images_received > 1) - outcome += fmt::format("; START -> first image {:.2f} s, {} images in {:.2f} s ({:.1f} Hz)", - live.start_to_first_image_s.value(), live.images_received, - live.last_image_s.value(), - static_cast(live.images_received - 1) / live.last_image_s.value()); - if (done_s) { - const double written_s = SecondsSince(last_image.value()); - live_status.Latency(written_s); - outcome += fmt::format("; done {:.2f} s after the last image, last image -> all written {:.2f} s " - "(end message sent at {:.2f} s)", done_s.value(), written_s, end_sent_s); - } - logger.Info("{}", outcome); - } - // The run's teardown, which went on beside the end message and the writer, and then the pinned buffers its - // engines kept: given back after the end message, as each cudaFreeHost takes the process's memory-map lock. - if (rugnux_done.valid()) { - rugnux_done.wait(); - Rugnux::ReleasePinnedPool(); - } - - end_time = std::chrono::system_clock::now(); - current_status.SetProgress({}); - current_status.SetStatus(GetStatus()); -} - -void JFJochReceiverRugnux::StartRugnux(std::future &rugnux_done, std::future &rugnux_out, - std::future &probe_done) { - // A failure here costs the analysis only: the images go on to the writer as they would without it. - try { - // The start message as the writer gets it, so rugnux sees the dataset an HDF5 master states. - std::vector start_cbor; - image_buffer.GetStartMessage(start_cbor); - reader.Open(CBORStream2Deserialize(start_cbor)->start_message.value()); - rugnux_done = std::async(std::launch::async, &JFJochReceiverRugnux::RugnuxThread, this); - rugnux_out = rugnux_published.get_future(); - probe_done = std::async(std::launch::async, &JFJochReceiverRugnux::ProbeThread, this); - } catch (const std::exception &e) { - logger.Error("rugnux live: cannot start - {}", e.what()); - live_status.Finish(RugnuxLiveState::Failed, e.what()); - } -} - -void JFJochReceiverRugnux::RugnuxThread() { - bool published = false; - try { - const auto dataset = reader.GetDataset(); - RugnuxLiveSetup setup = MakeRugnuxLiveSetup(*dataset, rugnux_nthreads, model); - setup.config.host_memory_budget_bytes = host_budget; - // The cards are idle until the first images arrive: the indexers and plans the passes will - // need are built then, beside the run, which meanwhile waits for its first frames. Waited for - // when the run ends. - auto gpu_warm_up = std::async(std::launch::async, [this, setup] { - try { - const auto t0 = std::chrono::steady_clock::now(); - WarmUpRugnuxLiveGPU(setup); - logger.Debug("rugnux live: FFT indexers and plans ready in {:.2f} s", std::chrono::duration( - std::chrono::steady_clock::now() - t0).count()); - } catch (const std::exception &e) { - logger.Warning("rugnux live: building the FFT indexers ahead failed, they are built when used - {}", - e.what()); - } - }); - LiveObserver observer(plots, scan_result, *az_int_mapping, live_status); - // The run goes once its results are out (below), beside the end message, which reads only the result: a - // value of its own, no part of the run. The pinned buffers its engines kept go after the end message. - std::optional process; - process.emplace(reader, setup.experiment, *dataset->pixel_mask, setup.config); - std::optional reachable; - reachable.emplace(rugnux_m, rugnux, *process, cut, cancelled); - ProcessResult result = process->Run(&observer); - // The verdict and summary of the report a `rugnux` run writes, without writing it. - const ReportDocument doc = BuildReportDocument(experiment.GetFilePrefix(), experiment.GetFilePrefix(), - setup.experiment, result); - std::vector warnings; - for (const auto &w: doc.warnings) - if (!w.developer_only) - warnings.push_back(w.text); - const double cc_anom = result.merge_statistics.overall.cc_anom; - live_status.Result(doc.verdict, doc.verdict_text, doc.summary_rows, warnings, - std::isfinite(cc_anom) && cc_anom > CC_ANOM_SIGNAL); - if (const auto m = LiveModel(result)) - live_status.Model(*m); - // The numbers the summary's Space group, Resolution and Signal rows give - std::optional d_min, isa; - if (result.has_merge_statistics) - SetIfFinite(d_min, result.merge_statistics.overall.d_min); - if (!result.merge_statistics.stills && result.error_model_isa_resolved && result.error_model_isa > 0.0) - isa = result.error_model_isa; - live_status.Headline(result.space_group ? result.space_group->xhm() : "", d_min, isa); - // The placed model and a copy of its map coefficients (the end message takes the originals), and the - // refined model with its own, for the broker's viewer routes. - if (result.outputs && result.outputs->placed_model) { - const RugnuxOutputs &o = *result.outputs; - const PlacedModelOutput &p = *o.placed_model; - std::optional coefficients; - if (o.maps) - coefficients = o.maps->coefficients; - std::shared_ptr refined; - if (o.refined_model) { - std::optional refined_coefficients; - if (o.refined_maps) - refined_coefficients = o.refined_maps->coefficients; - refined = std::make_shared(o.refined_model->model, o.refined_model->cell, - o.refined_model->space_group, - std::move(refined_coefficients)); - } - live_status.Structure(std::make_shared(p.model, p.cell, p.space_group, - std::move(coefficients)), - std::move(refined)); - } - live_status.Finish(result.cancelled ? RugnuxLiveState::Cancelled : RugnuxLiveState::Done); - reachable.reset(); - { - std::unique_lock ul(rugnux_m); - rugnux_result = std::move(result); - } - published = true; - rugnux_published.set_value(); - process.reset(); - } catch (const std::exception &e) { - logger.Error("rugnux live: {}", e.what()); - live_status.Finish(RugnuxLiveState::Failed, e.what()); - } - if (!published) - rugnux_published.set_value(); -} - -void JFJochReceiverRugnux::ProbeThread() { - // Rugnux waits for half the sweep before it decides on a lattice. In the meantime the machine is - // idle, so a small run of its own indexes the first 60 degrees and says at once whether there is - // a crystal - quick feedback for the person at the beamline, above all where there is none. It - // decides nothing for the run, which goes on as it would without it. - constexpr double PROBE_DEG = 60.0; - try { - const auto dataset = reader.GetDataset(); - const auto gonio = dataset->experiment.GetGoniometer(); - if (!gonio || gonio->GetIncrement_deg() == 0.0f) - return; - const int64_t frames = std::lround(PROBE_DEG / std::abs(gonio->GetIncrement_deg())); - if (2 * frames > static_cast(reader.GetNumberOfImages())) - return; // a sweep this short is decided on soon enough by the run itself - // A quarter of the threads: it runs beside the main run, and each of its workers holds GPU memory too. - RugnuxLiveSetup setup = MakeRugnuxLiveSetup(*dataset, std::max(2, rugnux_nthreads / 4), nullptr); - setup.config.host_memory_budget_bytes = host_budget; // the same as the main run's: one pool for both - setup.config.end_image = static_cast(frames); - setup.config.rotation_postrefine_geometry = false; - setup.config.run_scaling = false; - // The file's centre only: the check captures the centre on the GPUs and runs a second first pass, - // both beside the main run's pre-scan, for a centre the probe does not report. - setup.config.beam_center_check = false; - ProcessResult r; - { - Rugnux probe_run(reader, setup.experiment, *dataset->pixel_mask, setup.config); - Reachable reachable(rugnux_m, probe, probe_run, cut, cancelled); - r = probe_run.Run(nullptr); - } - if (r.consensus_cell && r.rotation_lattice_type) { - const auto &c = *r.consensus_cell; - live_status.Probe(fmt::format("lattice on the first {:.0f} deg: {}-centred {}, {:.1f} {:.1f} {:.1f} " - "{:.1f} {:.1f} {:.1f}, {:.0f}% of the images indexed", PROBE_DEG, - r.rotation_lattice_type->centering, - gemmi::crystal_system_str(r.rotation_lattice_type->crystal_system), - c.a, c.b, c.c, c.alpha, c.beta, c.gamma, - 100.0 * r.indexing_rate.value_or(0.0f))); - } else - live_status.Probe(fmt::format("no lattice on the first {:.0f} deg", PROBE_DEG)); - } catch (const NoLatticeFound &e) { - live_status.Probe(fmt::format("no lattice on the first {:.0f} deg: {}", PROBE_DEG, e.reason)); - } catch (const std::exception &e) { - logger.Warning("rugnux live: the lattice probe failed: {}", e.what()); - } - logger.Debug("rugnux live: lattice probe - {}", live_status.Get().probe); -} - -void JFJochReceiverRugnux::AddToEndMessage(EndMessage &message) { - // A failure here costs the end message the analysis only: it goes out as the receiver made it. - try { - std::unique_lock ul(rugnux_m); - if (!rugnux_result) - return; - const EndMessage &r = rugnux_result->end_message; - message.indexing_rate = r.indexing_rate; - message.bkg_estimate = r.bkg_estimate; - message.spindle_blind_fraction = r.spindle_blind_fraction; - message.ice_ring_score_mean = r.ice_ring_score_mean; - message.az_int_result = r.az_int_result; - message.rotation_lattice = r.rotation_lattice; - message.rotation_lattice_type = r.rotation_lattice_type; - message.rotation_extra_lattices = r.rotation_extra_lattices; - message.unit_cell = r.unit_cell; - message.unit_cell_esd = r.unit_cell_esd; - message.space_group_name = r.space_group_name; - message.space_group_number = r.space_group_number; - message.reindex_matrix = r.reindex_matrix; - message.refined_beam_center_x = r.refined_beam_center_x; - message.refined_beam_center_y = r.refined_beam_center_y; - message.refined_poni_rot1 = r.refined_poni_rot1; - message.refined_poni_rot2 = r.refined_poni_rot2; - message.refined_poni_rot3 = r.refined_poni_rot3; - message.refined_rotation_axis = r.refined_rotation_axis; - // Merged data, map coefficients and placed model, for the writer to write beside the master. Moved: - // the result is not read again. - if (!rugnux_result->outputs) - return; - RugnuxOutputs &o = *rugnux_result->outputs; - if (o.merged) - message.merged = std::move(o.merged->mtz); - if (o.maps) - message.map_coefficients = std::move(o.maps->coefficients); - if (o.placed_model) - message.placed_model = PlacedModelFromStructure(*o.placed_model->model, o.placed_model->cell, - o.placed_model->space_group.xhm()); - // And the refinement's, as the CLI writes them (_refined_model.cif, _refined_maps.mtz, _blobs.json). - if (o.refined_model) - message.refined_model = PlacedModelFromStructure(*o.refined_model->model, o.refined_model->cell, - o.refined_model->space_group.xhm()); - if (o.refined_maps) - message.refined_map_coefficients = std::move(o.refined_maps->coefficients); - if (o.blobs) - message.blobs = DensityBlobsToMessage(*o.blobs); - - // Every pusher serialises END into a buffer of MESSAGE_SIZE_FOR_START_END, and an END that does - // not fit there fails the collection. So it is serialised here first, into a buffer of the same - // size (uninitialised: only the bytes written are touched); one that does not fit goes without - // the blocks. - const auto scratch = std::make_unique_for_overwrite(MESSAGE_SIZE_FOR_START_END); - CBORStream2Serializer serializer(scratch.get(), MESSAGE_SIZE_FOR_START_END); - try { - serializer.SerializeSequenceEnd(message); - } catch (const JFJochException &e) { - const std::string text = fmt::format("the merged data, map coefficients and models do not " - "fit in the end message ({} MiB) and are not written", - MESSAGE_SIZE_FOR_START_END / (1024 * 1024)); - logger.Error("rugnux live: {} - {}", text, e.what()); - live_status.Warning(text); - message.merged.reset(); - message.map_coefficients.reset(); - message.placed_model.reset(); - message.refined_model.reset(); - message.refined_map_coefficients.reset(); - message.blobs.reset(); - } - } catch (const std::exception &e) { - logger.Error("rugnux live: the end message goes without the analysis results - {}", e.what()); - message.merged.reset(); - message.map_coefficients.reset(); - message.placed_model.reset(); - message.refined_model.reset(); - message.refined_map_coefficients.reset(); - message.blobs.reset(); - } -} - -void JFJochReceiverRugnux::ForwardThread(uint32_t id) { - forward_started.count_down(); - measurement_started.wait(); - - while (!cancelled && !end_message_received) { - try { - auto msg = image_puller.PollImage(std::chrono::milliseconds(5)); - if (!msg.has_value() || !msg->cbor) - continue; - if (msg->cbor->end_message) { - end_message_received = true; - } else if (msg->cbor->calibration) { - if (push_images_to_writer) - image_pusher.SendCalibration(msg->cbor->calibration.value()); - } else if (msg->cbor->data_message) { - live_status.ImageReceived(msg->received); - DataMessage data_msg = msg->cbor->data_message.value(); - ++images_collected; - - const auto compressed_size_img = data_msg.image.GetCompressedSize(); - const auto uncompressed_size_img = data_msg.image.GetUncompressedSize(); - compressed_size += compressed_size_img; - uncompressed_size += uncompressed_size_img; - UpdateMaxImageReceived(data_msg.number); - - data_msg.original_number = data_msg.number; - data_msg.user_data = experiment.GetImageAppendix(); - data_msg.run_number = experiment.GetRunNumber(); - data_msg.run_name = experiment.GetRunName(); - data_msg.receiver_buf_available = image_buffer.GetAvailSlots(); - data_msg.receiver_aq_dev_delay = image_puller.GetCurrentFifoUtilization(); - if (compressed_size_img > 0) - data_msg.compression_ratio = static_cast(uncompressed_size_img) - / static_cast(compressed_size_img); - - auto loc = image_buffer.GetImageSlot(); - if (loc == nullptr) - writer_queue_full = true; - else { - auto writer_buffer = (uint8_t *) loc->GetImage(); - CBORStream2Serializer serializer(writer_buffer, experiment.GetImageBufferLocationSize()); - if (JFJochReceiverLite::SerializeImageOrDrop(serializer, data_msg, *loc, - experiment.GetImageBufferLocationSize(), logger)) { - loc->SetImageNumber(data_msg.number); - loc->SetImageSize(serializer.GetBufferSize()); - loc->SetIndexed(false); - loc->ReadyToSend(); - - if (zmq_preview_socket != nullptr) - zmq_preview_socket->SendImage(writer_buffer, serializer.GetBufferSize()); - if (zmq_metadata_socket != nullptr) - zmq_metadata_socket->AddDataMessage(data_msg); - if (push_images_to_writer) { - if (image_pusher.SendImage(*loc)) - ++images_sent; - } else - loc->release(); - UpdateMaxImageSent(data_msg.number); - } - } - } - UpdateMaxDelay(image_puller.GetCurrentFifoUtilization()); - current_status.SetProgress(GetProgress()); - current_status.SetStatus(GetStatus()); - } catch (const JFJochException &e) { - logger.ErrorException(e); - Cancel(e); - } catch (const std::exception &e) { - logger.Error("Cancelling data collection due to exception: {}", e.what()); - cancelled = true; - } - } -} - -void JFJochReceiverRugnux::Cancel(bool silent) { - detector_finished = true; - // The silent cancel is the detector reading idle at the end of every collection: rugnux goes on. - if (silent) - return; - // /cancel is the one way in for the beamline, which cannot tell the collection from its analysis: it - // ends the collection while the images still arrive, as JFJochReceiverLite does, and in every case - // cuts the analysis, so the end message goes out at once with what rugnux has established. - if (!images_done) - JFJochReceiver::Cancel(silent); - Cut("cancel"); -} - -void JFJochReceiverRugnux::Cut(const std::string &reason) { - std::unique_lock ul(rugnux_m); - if (cut || rugnux_result) - return; - cut = true; - reader.Ended(); // no image is waited for any more - if (rugnux != nullptr) - rugnux->Cancel(); - if (probe != nullptr) - probe->Cancel(); - live_status.Cut(reason); - logger.Debug("rugnux live: analysis cut ({}) - the end message carries what is established", reason); -} - -void JFJochReceiverRugnux::StopReceiver() { - if (measurement.valid()) { - measurement.get(); - logger.Debug("Receiver stopped"); - } -} - -float JFJochReceiverRugnux::GetEfficiency() const { - if (experiment.GetFrameNum() == 0) - return 0; - return static_cast(images_collected) / static_cast(experiment.GetFrameNum()); -} - -JFJochReceiverOutput JFJochReceiverRugnux::GetFinalStatistics() const { - JFJochReceiverOutput ret = JFJochReceiver::GetFinalStatistics(); - ret.analysis_warning = skipped_warning; - // The rotation lattice is rugnux's: the receivers run no rotation indexer of their own. - std::unique_lock ul(rugnux_m); - if (rugnux_result) { - const EndMessage &r = rugnux_result->end_message; - ret.scan_result.rotation_lattice = r.rotation_lattice; - if (r.rotation_lattice_type) { - ret.scan_result.rotation_crystal_system = r.rotation_lattice_type->crystal_system; - ret.scan_result.rotation_centering = r.rotation_lattice_type->centering; - } - } - return ret; -} - -float JFJochReceiverRugnux::GetProgress() const { - if (experiment.GetFrameNum() == 0) - return 0.0; - return static_cast(max_image_number_received) / static_cast(experiment.GetFrameNum()); -} diff --git a/receiver/JFJochReceiverRugnux.h b/receiver/JFJochReceiverRugnux.h deleted file mode 100644 index dabd7f6e5..000000000 --- a/receiver/JFJochReceiverRugnux.h +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute -// SPDX-License-Identifier: GPL-3.0-only - -#pragma once - -#include -#include - -#include "JFJochReceiver.h" -#include "../image_puller/ImagePuller.h" -#include "../reader/JFJochImageBufferReader.h" -#include "../rugnux/Rugnux.h" -#include "RugnuxLiveStatus.h" - -// What the run made of the model it was given, for the live status; none where no model was given. -std::optional LiveModel(const ProcessResult &result); - -// A DECTRIS rotation collection forwarded to the writer as JFJochReceiverLite does, but analysed by -// rugnux while it arrives instead of image by image: the forward threads put each image into the -// image buffer and send it on, and one rugnux thread reads the collection back from that buffer. The -// end message waits for rugnux, and carries what it found - the lattice, refined geometry, per-image -// results and its merged data, map coefficients and placed model (see AddToEndMessage). -class JFJochReceiverRugnux : public JFJochReceiver { - using JFJochReceiver::Cancel; - - ImagePuller &image_puller; - JFJochImageBufferReader reader; - - std::atomic end_message_received{false}; - std::atomic detector_finished{false}; // see JFJochReceiverLite - constexpr static auto StartMessageGrace = std::chrono::seconds(5); - - const int64_t forward_nthreads; - const int rugnux_nthreads; - const size_t host_budget; // what the main run and the probe plan their host memory against - const std::shared_ptr model; - const double deadline_s; // after the last image, the analysis is cut (0 = never) - RugnuxLiveStatus &live_status; - std::string skipped_warning; // why the collection was not analysed, for the broker's status - - std::vector> forward_futures; - std::future measurement; - std::latch measurement_started; - std::latch forward_started; - - mutable std::mutex rugnux_m; - Rugnux *rugnux = nullptr; // while it runs, for Cut - Rugnux *probe = nullptr; // the same for the lattice probe - std::optional rugnux_result; - std::promise rugnux_published; // rugnux_result is in place, or the run has failed (RugnuxThread) - bool cut = false; - std::atomic images_done{false}; // the forward threads have ended - - void MeasurementThread(); - // Opens the reader on the collection and starts the rugnux and probe threads; a failure is the - // analysis's alone (live status Failed), never the collection's. rugnux_out is ready once the run's - // result is in place, rugnux_done once the run is gone as well. - void StartRugnux(std::future &rugnux_done, std::future &rugnux_out, std::future &probe_done); - void ForwardThread(uint32_t id); - void RugnuxThread(); - void ProbeThread(); - // Stops the analysis where it is: rugnux returns what it has established (see Rugnux::Cancel), and - // the end message goes out with that. Reason: "deadline" or "cancel". - void Cut(const std::string &reason); - void AddToEndMessage(EndMessage &message) override; -public: - JFJochReceiverRugnux(const DiffractionExperiment &experiment, - const PixelMask &pixel_mask, - ImagePuller &image_puller, - ImagePusher &image_pusher, - Logger &logger, - int64_t forward_nthreads, - int rugnux_nthreads, - std::shared_ptr model, - double deadline_s, - RugnuxLiveStatus &live_status, - const SpotFindingSettings &spot_finding_settings, - JFJochReceiverCurrentStatus ¤t_status, - JFJochReceiverPlots &plots, - ImageBuffer &image_buffer, - ZMQPreviewSocket *zmq_preview_socket = nullptr, - ZMQMetadataSocket *zmq_metadata_socket = nullptr); - ~JFJochReceiverRugnux() override; - - void StopReceiver() override; - void Cancel(bool silent) override; - - float GetEfficiency() const override; - float GetProgress() const override; - JFJochReceiverOutput GetFinalStatistics() const override; - - void SetSpotFindingSettings(const SpotFindingSettings &spot_finding_settings) override {} -}; diff --git a/receiver/JFJochReceiverRugnuxRotation.cpp b/receiver/JFJochReceiverRugnuxRotation.cpp new file mode 100644 index 000000000..07a9eaf4e --- /dev/null +++ b/receiver/JFJochReceiverRugnuxRotation.cpp @@ -0,0 +1,441 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "JFJochReceiverRugnuxRotation.h" + +#include +#include + +#include "spdlog/fmt/fmt.h" + +#include "../common/HostMemory.h" +#include "../common/time_utc.h" +#include "../frame_serialize/CBORStream2Deserializer.h" +#include "../rugnux/RugnuxDefaults.h" +#include "../rugnux/RugnuxLive.h" +#include "../rugnux/ResultReport.h" +#include "../writer/RugnuxResultBlocks.h" +#include "RugnuxStructure.h" + +namespace { + // Kept for the rest of the broker when the host memory rugnux plans against is decided + constexpr size_t RUGNUX_HOST_MEMORY_MARGIN = 16ULL * 1024 * 1024 * 1024; + + // Nine tenths of the machine's total less the image buffer and the margin, at most the machine's budget + // (HostMemory.h): both come off the total, as the budget's own reserve taken off twice left nothing. A tenth + // stays out for the system and the other services on the host. Never 0, the whole machine's budget. + size_t RugnuxHostBudget(const ImageBuffer &buffer) { + const size_t total = get_host_total_memory(); + const size_t taken = buffer.GetBufferSize() + RUGNUX_HOST_MEMORY_MARGIN; + const size_t usable = total / 10 * 9; + return usable > taken ? std::min(DefaultHostMemoryBudget(), usable - taken) : 1; + } + + // A run reachable for Cut while it exists: registered in `slot`, cancelled at once where the cut came first + class Reachable { + std::mutex &slot_mutex; + Rugnux *&slot; + public: + Reachable(std::mutex &in_mutex, Rugnux *&slot, Rugnux &run, const bool &cut, const std::atomic &cancelled) + : slot_mutex(in_mutex), slot(slot) { + std::unique_lock ul(slot_mutex); + slot = &run; + if (cut || cancelled) + run.Cancel(); + } + ~Reachable() { + std::unique_lock ul(slot_mutex); + slot = nullptr; + } + }; + + // Per-image results of rugnux's canonical pass to the plots and scan result, its steps to the live status + class LiveObserver : public RugnuxObserver { + JFJochReceiverPlots &plots; + ScanResultGenerator &scan_result; + const AzimuthalIntegrationMapping &mapping; + RugnuxLiveStatus &status; + std::atomic canonical{false}; + public: + LiveObserver(JFJochReceiverPlots &plots, ScanResultGenerator &scan_result, + const AzimuthalIntegrationMapping &mapping, RugnuxLiveStatus &status) + : plots(plots), scan_result(scan_result), mapping(mapping), status(status) {} + + void OnImagePass(bool input) override { canonical = input; } + void OnImageProcessed(const DataMessage &msg) override { + if (!canonical) + return; + plots.Add(msg, AzimuthalIntegrationProfile(mapping)); + scan_result.Add(msg); + } + void OnProgress(uint64_t done, uint64_t total) override { status.Progress(done, total); } + void OnStep(const std::string &title) override { status.Step(title); } + void OnNote(const std::string &text) override { status.Note(text); } + }; + + std::string StateName(RugnuxLiveState state) { + switch (state) { + case RugnuxLiveState::Idle: return "idle"; + case RugnuxLiveState::Running: return "running"; + case RugnuxLiveState::Done: return "done"; + case RugnuxLiveState::Failed: return "failed"; + case RugnuxLiveState::Cancelled: return "cancelled"; + case RugnuxLiveState::Skipped: return "skipped"; + } + return "unknown"; + } +} + +JFJochReceiverRugnuxRotation::JFJochReceiverRugnuxRotation(const DiffractionExperiment &in_experiment, + const PixelMask &in_pixel_mask, + ImagePuller &in_image_puller, + ImagePusher &in_image_pusher, + Logger &in_logger, + int64_t in_forward_nthreads, + int in_rugnux_nthreads, + std::shared_ptr in_model, + double in_deadline_s, + RugnuxLiveStatus &in_live_status, + const SpotFindingSettings &in_spot_finding_settings, + JFJochReceiverCurrentStatus &in_current_status, + JFJochReceiverPlots &in_plots, + ImageBuffer &in_image_buffer, + ZMQPreviewSocket *in_zmq_preview_socket, + ZMQMetadataSocket *in_zmq_metadata_socket) + : JFJochReceiverStream(in_experiment, in_pixel_mask, in_image_puller, in_image_pusher, in_logger, + in_forward_nthreads, in_spot_finding_settings, in_current_status, in_plots, + in_image_buffer, in_zmq_preview_socket, in_zmq_metadata_socket), + reader(in_image_buffer), + rugnux_nthreads(in_rugnux_nthreads), + host_budget(RugnuxHostBudget(in_image_buffer)), + model(std::move(in_model)), + deadline_s(in_deadline_s), + live_status(in_live_status) { + logger.Info("rugnux live: starting {} forward threads; rugnux runs on {} threads while the images arrive, " + "planning against {:.1f} GB of host memory", nthreads, rugnux_nthreads, + static_cast(host_budget) / 1e9); + StartThreads(); +} + +JFJochReceiverRugnuxRotation::~JFJochReceiverRugnuxRotation() { + cancelled = true; + reader.Ended(); + { + std::unique_lock ul(rugnux_mutex); + if (rugnux != nullptr) + rugnux->Cancel(); + if (probe != nullptr) + probe->Cancel(); + } + JoinThreads(); +} + +void JFJochReceiverRugnuxRotation::OnStart(std::chrono::steady_clock::time_point received) { + SendStartMessage(); + + // Rugnux reads the images back from the buffer: without one slot per image the collection is only forwarded + const auto slots = image_buffer.GetStatus().total_slots; + // The image time as the detector produces images, summation included + const double image_time_s = std::chrono::duration(experiment.GetImageTime()).count(); + std::optional expected_collection_s; + if (image_time_s > 0) + expected_collection_s = static_cast(experiment.GetImageNum()) * image_time_s; + live_status.Start(experiment.GetImageNum(), expected_collection_s, received); + if (slots < experiment.GetImageNum()) { + skipped_warning = fmt::format("no rugnux analysis: the image buffer holds {} images, the collection " + "has {}", slots, experiment.GetImageNum()); + logger.Error("{}", skipped_warning); + live_status.Finish(RugnuxLiveState::Skipped, "the image buffer cannot hold the whole collection"); + } else + StartRugnux(); +} + +void JFJochReceiverRugnuxRotation::OnImage(uint32_t id, DataMessage &message, std::chrono::steady_clock::time_point received) { + live_status.ImageReceived(received); + CountImage(message); + StampImage(message, image_puller.GetCurrentFifoUtilization()); + ForwardImage(message, image_buffer.GetImageSlot()); +} + +void JFJochReceiverRugnuxRotation::FinishCollection() { + reader.Ended(); + // The latency runs from the last image in to every result in place + const auto last_image = live_status.LastImage(); + logger.Info("Receiving finished, {} images", images_collected.load()); + + std::optional done_s; + if (rugnux_out.valid()) { + // The fixed cutoff: the analysis gets deadline_s after the last image, then gives what it has. + if (deadline_s > 0 && rugnux_out.wait_for(std::chrono::duration(deadline_s)) + == std::future_status::timeout) + Cut("deadline"); + rugnux_out.wait(); + if (last_image) + done_s = SecondsSince(last_image.value()); + } + if (probe_done.valid()) + probe_done.wait(); + + current_status.SetProgress(1.0); + current_status.SetStatus(GetStatus()); + + SendEndMessage(); + const double end_sent_s = last_image ? SecondsSince(last_image.value()) : 0.0; + + CheckIfImagesSent(); + WriterFinalize(); + if (rugnux_out.valid()) { + // The run's outcome, in one line: its results are in the live status, the end message and the files. + const auto live = live_status.Get(); + std::string outcome = "rugnux live: " + StateName(live.state); + if (!live.message.empty()) + outcome += " (" + live.message + ")"; + if (!live.cut.empty()) + outcome += " (cut: " + live.cut + ")"; + if (live.start_to_first_image_s && live.last_image_s && live.images_received > 1) + outcome += fmt::format("; START -> first image {:.2f} s, {} images in {:.2f} s ({:.1f} Hz)", + live.start_to_first_image_s.value(), live.images_received, + live.last_image_s.value(), + static_cast(live.images_received - 1) / live.last_image_s.value()); + if (done_s) { + const double written_s = SecondsSince(last_image.value()); + live_status.Latency(written_s); + outcome += fmt::format("; done {:.2f} s after the last image, last image -> all written {:.2f} s " + "(end message sent at {:.2f} s)", done_s.value(), written_s, end_sent_s); + } + logger.Info("{}", outcome); + } + // The run's teardown, which went on beside the end message and the writer, then the pinned buffers its engines + // kept: given back after the end message, as each cudaFreeHost takes the process's memory-map lock. + if (rugnux_done.valid()) { + rugnux_done.wait(); + Rugnux::ReleasePinnedPool(); + } + + end_time = std::chrono::system_clock::now(); + current_status.SetProgress({}); + current_status.SetStatus(GetStatus()); +} + +void JFJochReceiverRugnuxRotation::StartRugnux() { + // A failure here costs the analysis only: the images go on to the writer as they would without it. + try { + // The start message as the writer gets it, so rugnux sees the dataset an HDF5 master states. + std::vector start_cbor; + image_buffer.GetStartMessage(start_cbor); + reader.Open(CBORStream2Deserialize(start_cbor)->start_message.value()); + rugnux_done = std::async(std::launch::async, &JFJochReceiverRugnuxRotation::RugnuxThread, this); + rugnux_out = rugnux_published.get_future(); + probe_done = std::async(std::launch::async, &JFJochReceiverRugnuxRotation::ProbeThread, this); + } catch (const std::exception &e) { + logger.Error("rugnux live: cannot start - {}", e.what()); + live_status.Finish(RugnuxLiveState::Failed, e.what()); + } +} + +void JFJochReceiverRugnuxRotation::RugnuxThread() { + bool published = false; + try { + const auto dataset = reader.GetDataset(); + RugnuxLiveSetup setup = MakeRugnuxLiveSetup(*dataset, rugnux_nthreads, model); + setup.config.host_memory_budget_bytes = host_budget; + // Built beside the run, which waits for its first frames meanwhile; waited for when the run ends + auto gpu_warm_up = std::async(std::launch::async, [this, setup] { + try { + const auto t0 = std::chrono::steady_clock::now(); + WarmUpRugnuxLiveGPU(setup); + logger.Debug("rugnux live: FFT indexers and plans ready in {:.2f} s", std::chrono::duration( + std::chrono::steady_clock::now() - t0).count()); + } catch (const std::exception &e) { + logger.Warning("rugnux live: building the FFT indexers ahead failed, they are built when used - {}", + e.what()); + } + }); + LiveObserver observer(plots, scan_result, *az_int_mapping, live_status); + // The run goes once its results are out (below), beside the end message; its pinned buffers after that + std::optional process; + process.emplace(reader, setup.experiment, *dataset->pixel_mask, setup.config); + std::optional reachable; + reachable.emplace(rugnux_mutex, rugnux, *process, cut, cancelled); + ProcessResult result = process->Run(&observer); + // The verdict and summary of the report a `rugnux` run writes, without writing it. + const ReportDocument doc = BuildReportDocument(experiment.GetFilePrefix(), experiment.GetFilePrefix(), + setup.experiment, result); + // A cancelled collection is no failed analysis, and the run stops at the images that came + const int64_t images_received = live_status.Get().images_received; + std::string verdict = doc.verdict; + std::string verdict_text = doc.verdict_text; + if (result.cancelled) { + verdict = "CANCELLED"; + verdict_text = fmt::format("The collection was cancelled after {} images - these results describe " + "an incomplete sweep.", images_received); + } + std::vector warnings; + for (const auto &warning: doc.warnings) + if (result.cancelled && warning.code == PathologyCode::CANCELLED) + warnings.push_back(fmt::format("Processing was cancelled after {} images - this report describes " + "an incomplete run", images_received)); + else if (!warning.developer_only) + warnings.push_back(warning.text); + const double cc_anom = result.merge_statistics.overall.cc_anom; + live_status.Result(verdict, verdict_text, doc.summary_rows, warnings, + std::isfinite(cc_anom) && cc_anom > CC_ANOM_SIGNAL); + if (const auto live_model = LiveModel(result)) + live_status.Model(*live_model); + // The numbers the summary's Space group, Resolution and Signal rows give + std::optional d_min, isa; + if (result.has_merge_statistics) + if (std::isfinite(result.merge_statistics.overall.d_min)) + d_min = result.merge_statistics.overall.d_min; + if (!result.merge_statistics.stills && result.error_model_isa_resolved && result.error_model_isa > 0.0) + isa = result.error_model_isa; + live_status.Headline(result.space_group ? result.space_group->xhm() : "", d_min, isa); + // For the broker's viewer routes; a copy of the map coefficients, as the end message takes the originals + if (result.outputs && result.outputs->placed_model) { + const RugnuxOutputs &outputs = *result.outputs; + PublishStructures(live_status, *outputs.placed_model, outputs.maps.get(), + outputs.refined_model ? &*outputs.refined_model : nullptr, outputs.refined_maps.get()); + } + live_status.Finish(result.cancelled ? RugnuxLiveState::Cancelled : RugnuxLiveState::Done); + reachable.reset(); + { + std::unique_lock ul(rugnux_mutex); + rugnux_result = std::move(result); + } + published = true; + rugnux_published.set_value(); + process.reset(); + } catch (const std::exception &e) { + logger.Error("rugnux live: {}", e.what()); + live_status.Finish(RugnuxLiveState::Failed, e.what()); + } + if (!published) + rugnux_published.set_value(); +} + +void JFJochReceiverRugnuxRotation::ProbeThread() { + // A small run of its own on the first degrees says early whether there is a crystal; it decides nothing + constexpr double PROBE_DEG = 60.0; + try { + const auto dataset = reader.GetDataset(); + const auto gonio = dataset->experiment.GetGoniometer(); + if (!gonio || gonio->GetIncrement_deg() == 0.0f) + return; + const int64_t frames = std::lround(PROBE_DEG / std::abs(gonio->GetIncrement_deg())); + if (2 * frames > static_cast(reader.GetNumberOfImages())) + return; // a sweep this short is decided on soon enough by the run itself + // A quarter of the threads: it runs beside the main run, and each of its workers holds GPU memory too. + RugnuxLiveSetup setup = MakeRugnuxLiveSetup(*dataset, std::max(2, rugnux_nthreads / 4), nullptr); + setup.config.host_memory_budget_bytes = host_budget; // the same as the main run's: one pool for both + setup.config.end_image = static_cast(frames); + setup.config.rotation_postrefine_geometry = false; + setup.config.run_scaling = false; + // The file's centre only: the probe does not report one + setup.config.beam_center_check = false; + ProcessResult result; + { + Rugnux probe_run(reader, setup.experiment, *dataset->pixel_mask, setup.config); + Reachable reachable(rugnux_mutex, probe, probe_run, cut, cancelled); + result = probe_run.Run(nullptr); + } + if (result.consensus_cell && result.rotation_lattice_type) { + const auto &cell = *result.consensus_cell; + live_status.Probe(fmt::format("lattice on the first {:.0f} deg: {}-centred {}, {:.1f} {:.1f} {:.1f} " + "{:.1f} {:.1f} {:.1f}, {:.0f}% of the images indexed", PROBE_DEG, + result.rotation_lattice_type->centering, + gemmi::crystal_system_str(result.rotation_lattice_type->crystal_system), + cell.a, cell.b, cell.c, cell.alpha, cell.beta, cell.gamma, + 100.0 * result.indexing_rate.value_or(0.0f))); + } else + live_status.Probe(fmt::format("no lattice on the first {:.0f} deg", PROBE_DEG)); + } catch (const NoLatticeFound &e) { + live_status.Probe(fmt::format("no lattice on the first {:.0f} deg: {}", PROBE_DEG, e.reason)); + } catch (const std::exception &e) { + logger.Warning("rugnux live: the lattice probe failed: {}", e.what()); + } + logger.Debug("rugnux live: lattice probe - {}", live_status.Get().probe); +} + +void JFJochReceiverRugnuxRotation::AddToEndMessage(EndMessage &message) { + JFJochReceiverStream::AddToEndMessage(message); + // A failure here costs the end message the analysis only: it goes out as the receiver made it. + try { + std::unique_lock ul(rugnux_mutex); + if (!rugnux_result) + return; + const EndMessage &rugnux_end = rugnux_result->end_message; + message.indexing_rate = rugnux_end.indexing_rate; + message.bkg_estimate = rugnux_end.bkg_estimate; + message.spindle_blind_fraction = rugnux_end.spindle_blind_fraction; + message.ice_ring_score_mean = rugnux_end.ice_ring_score_mean; + message.az_int_result = rugnux_end.az_int_result; + message.rotation_lattice = rugnux_end.rotation_lattice; + message.rotation_lattice_type = rugnux_end.rotation_lattice_type; + message.rotation_extra_lattices = rugnux_end.rotation_extra_lattices; + message.unit_cell = rugnux_end.unit_cell; + message.unit_cell_esd = rugnux_end.unit_cell_esd; + message.space_group_name = rugnux_end.space_group_name; + message.space_group_number = rugnux_end.space_group_number; + message.reindex_matrix = rugnux_end.reindex_matrix; + message.refined_beam_center_x = rugnux_end.refined_beam_center_x; + message.refined_beam_center_y = rugnux_end.refined_beam_center_y; + message.refined_poni_rot1 = rugnux_end.refined_poni_rot1; + message.refined_poni_rot2 = rugnux_end.refined_poni_rot2; + message.refined_poni_rot3 = rugnux_end.refined_poni_rot3; + message.refined_rotation_axis = rugnux_end.refined_rotation_axis; + // For the writer to write beside the master; moved, the result is not read again + if (!rugnux_result->outputs) + return; + RugnuxOutputs &outputs = *rugnux_result->outputs; + if (outputs.merged) + message.merged = std::move(outputs.merged->mtz); + if (outputs.maps) + message.map_coefficients = std::move(outputs.maps->coefficients); + if (outputs.placed_model) + message.placed_model = PlacedModelFromStructure(*outputs.placed_model->model, outputs.placed_model->cell, + outputs.placed_model->space_group.xhm()); + // And the refinement's, as the CLI writes them (_refined_model.cif, _refined_maps.mtz, _blobs.json). + if (outputs.refined_model) + message.refined_model = PlacedModelFromStructure(*outputs.refined_model->model, outputs.refined_model->cell, + outputs.refined_model->space_group.xhm()); + if (outputs.refined_maps) + message.refined_map_coefficients = std::move(outputs.refined_maps->coefficients); + if (outputs.blobs) + message.blobs = DensityBlobsToMessage(*outputs.blobs); + + DropResultBlocksThatDoNotFit(message, &live_status, logger); + } catch (const std::exception &e) { + logger.Error("rugnux live: the end message goes without the analysis results - {}", e.what()); + DropResultBlocks(message); + } +} + +void JFJochReceiverRugnuxRotation::Cut(const std::string &reason) { + std::unique_lock ul(rugnux_mutex); + if (cut || rugnux_result) + return; + cut = true; + reader.Ended(); // no image is waited for any more + if (rugnux != nullptr) + rugnux->Cancel(); + if (probe != nullptr) + probe->Cancel(); + live_status.Cut(reason); + logger.Debug("rugnux live: analysis cut ({}) - the end message carries what is established", reason); +} + +JFJochReceiverOutput JFJochReceiverRugnuxRotation::GetFinalStatistics() const { + JFJochReceiverOutput ret = JFJochReceiver::GetFinalStatistics(); + ret.analysis_warning = skipped_warning; + // The rotation lattice is rugnux's: the receivers run no rotation indexer of their own. + std::unique_lock ul(rugnux_mutex); + if (rugnux_result) { + const EndMessage &rugnux_end = rugnux_result->end_message; + ret.scan_result.rotation_lattice = rugnux_end.rotation_lattice; + if (rugnux_end.rotation_lattice_type) { + ret.scan_result.rotation_crystal_system = rugnux_end.rotation_lattice_type->crystal_system; + ret.scan_result.rotation_centering = rugnux_end.rotation_lattice_type->centering; + } + } + return ret; +} diff --git a/receiver/JFJochReceiverRugnuxRotation.h b/receiver/JFJochReceiverRugnuxRotation.h new file mode 100644 index 000000000..2fbf4fc60 --- /dev/null +++ b/receiver/JFJochReceiverRugnuxRotation.h @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include +#include +#include +#include +#include + +#include "JFJochReceiverStream.h" +#include "RugnuxLiveStatus.h" +#include "../reader/JFJochImageBufferReader.h" +#include "../rugnux/Rugnux.h" + +// A rotation collection forwarded to the writer and analysed by rugnux, which reads it back from the image buffer +class JFJochReceiverRugnuxRotation : public JFJochReceiverStream { + JFJochImageBufferReader reader; + + const int rugnux_nthreads; + const size_t host_budget; // what the main run and the probe plan their host memory against + const std::shared_ptr model; + const double deadline_s; // after the last image, the analysis is cut (0 = never) + RugnuxLiveStatus &live_status; + std::string skipped_warning; // why the collection was not analysed, for the broker's status + + mutable std::mutex rugnux_mutex; + Rugnux *rugnux = nullptr; // while it runs, for Cut + Rugnux *probe = nullptr; // the same for the lattice probe + std::optional rugnux_result; + std::promise rugnux_published; // rugnux_result is in place, or the run has failed (RugnuxThread) + bool cut = false; + + // rugnux_out is ready once the run's result is in place, rugnux_done once the run is gone as well. + std::future rugnux_out; + std::future probe_done; + std::future rugnux_done; + + void OnStart(std::chrono::steady_clock::time_point received) override; + void OnImage(uint32_t id, DataMessage &message, std::chrono::steady_clock::time_point received) override; + void FinishCollection() override; + // A failure here is the analysis's alone (live status Failed), never the collection's + void StartRugnux(); + void RugnuxThread(); + void ProbeThread(); + // Stops rugnux where it is, the end message goes out with what it established; reason "deadline" or "cancel" + void Cut(const std::string &reason) override; + void AddToEndMessage(EndMessage &message) override; +public: + JFJochReceiverRugnuxRotation(const DiffractionExperiment &experiment, + const PixelMask &pixel_mask, + ImagePuller &image_puller, + ImagePusher &image_pusher, + Logger &logger, + int64_t forward_nthreads, + int rugnux_nthreads, + std::shared_ptr model, + double deadline_s, + RugnuxLiveStatus &live_status, + const SpotFindingSettings &spot_finding_settings, + JFJochReceiverCurrentStatus ¤t_status, + JFJochReceiverPlots &plots, + ImageBuffer &image_buffer, + ZMQPreviewSocket *zmq_preview_socket = nullptr, + ZMQMetadataSocket *zmq_metadata_socket = nullptr); + ~JFJochReceiverRugnuxRotation() override; + + JFJochReceiverOutput GetFinalStatistics() const override; +}; diff --git a/receiver/JFJochReceiverRugnuxStills.cpp b/receiver/JFJochReceiverRugnuxStills.cpp new file mode 100644 index 000000000..e8d8148a5 --- /dev/null +++ b/receiver/JFJochReceiverRugnuxStills.cpp @@ -0,0 +1,200 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "JFJochReceiverRugnuxStills.h" + +#include +#include + +#include "../common/time_utc.h" + +JFJochReceiverRugnuxStills::JFJochReceiverRugnuxStills(const DiffractionExperiment &in_experiment, + const PixelMask &in_pixel_mask, + ImagePuller &in_image_puller, + ImagePusher &in_image_pusher, + Logger &in_logger, + int64_t in_forward_nthreads, + std::shared_ptr in_model, + double in_deadline_s, + RugnuxLiveStatus &in_live_status, + const SpotFindingSettings &in_spot_finding_settings, + JFJochReceiverCurrentStatus &in_current_status, + JFJochReceiverPlots &in_plots, + ImageBuffer &in_image_buffer, + ZMQPreviewSocket *in_zmq_preview_socket, + ZMQMetadataSocket *in_zmq_metadata_socket, + IndexerThreadPool *in_indexing_thread_pool) + : JFJochReceiverStream(in_experiment, in_pixel_mask, in_image_puller, in_image_pusher, in_logger, + in_forward_nthreads, in_spot_finding_settings, in_current_status, in_plots, + in_image_buffer, in_zmq_preview_socket, in_zmq_metadata_socket), + model(std::move(in_model)), + deadline_s(in_deadline_s), + live_status(in_live_status), + indexing_thread_pool(in_indexing_thread_pool), + stills_nthreads(std::min(16, in_forward_nthreads)) { + StartThreads(); +} + +JFJochReceiverRugnuxStills::~JFJochReceiverRugnuxStills() { + cancelled = true; + JoinThreads(); +} + +void JFJochReceiverRugnuxStills::OnStart(std::chrono::steady_clock::time_point received) { + const int64_t slots = image_buffer.GetStatus().total_slots; + stills_keep_free = std::min(stills_nthreads, slots / 2); + auto analysis = std::make_unique( + experiment, pixel_mask, MakeStartMessage(experiment, pixel_mask, *az_int_mapping), + indexing_thread_pool, stills_nthreads, + // What the pre-pass holds and fits on; the frames beyond it wait in the image buffer as well. + slots / 2, logger, + *this, live_status, true, model, deadline_s); + std::unique_lock ul(stills_mutex); + stills = std::move(analysis); + if (cut) + stills->Cut("cancel"); +} + +void JFJochReceiverRugnuxStills::OnImage(uint32_t id, DataMessage &message, std::chrono::steady_clock::time_point received) { + // The analysis keeps per-image results by number, for the images the collection declares; the stream is not ours. + if (message.number < 0 || message.number >= experiment.GetImageNum()) { + logger.Error("Dropping image {} - the collection has {} images", message.number, experiment.GetImageNum()); + return; + } + CountImage(message); + const size_t size = message.image.GetCompressedSize(); + + // Waits for the analysis in a slot of the image buffer; a slot is kept free for each analysis thread to send with + auto held = std::make_shared(); + held->slot = image_buffer.GetImageSlot(stills_keep_free); + if (held->slot == nullptr) { + writer_queue_full = true; + return; + } + if (size > static_cast(experiment.GetImageBufferLocationSize())) { + logger.Error("Dropping image {} - {} B do not fit in buffer slot {} B", message.number, size, + experiment.GetImageBufferLocationSize()); + return; + } + memcpy(held->slot->GetImage(), message.image.GetCompressed(), size); + held->message = message; + held->message.image = CompressedImage(held->slot->GetImage(), size, message.image.GetWidth(), + message.image.GetHeight(), message.image.GetMode(), + message.image.GetCompressionAlgorithm(), message.image.GetChannel()); + StampImage(held->message, image_puller.GetCurrentFifoUtilization()); + stills->Hold(held); +} + +void JFJochReceiverRugnuxStills::FinishCollection() { + logger.Info("Receiving finished, {} images", images_collected.load()); + + // With no start message there is no end message either, and the error is the collection's result + std::exception_ptr error; + try { + stills->Finish(); + } catch (const std::exception &) { + error = std::current_exception(); + } + + current_status.SetProgress(1.0); + current_status.SetStatus(GetStatus()); + + if (!error) + SendEndMessage(); + + end_time = std::chrono::system_clock::now(); + + CheckIfImagesSent(); + if (error) { + current_status.SetProgress({}); + current_status.SetStatus(GetStatus()); + std::rethrow_exception(error); + } + WriterFinalize(); + if (const auto last_image = live_status.LastImage()) + live_status.Latency(SecondsSince(last_image.value())); + stills->ReportOutcome(); + + current_status.SetProgress({}); + current_status.SetStatus(GetStatus()); +} + +void JFJochReceiverRugnuxStills::StillsReady(const StillsSetup &setup) { + // The profiles are binned on the pre-pass's geometry. + plots.Setup(setup.experiment, *setup.mapping); + SendStartMessage(MakeStartMessage(setup.experiment, setup.mask, *setup.mapping)); +} + +SpotFindingSettings JFJochReceiverRugnuxStills::AnalysisSettings() { + // The broker's settings, with the spot finder of the pre-pass: adaptive threshold, single-pixel spots kept. + // Every frame is integrated: integration is rugnux's, not the broker's to switch off. + SpotFindingSettings settings = GetSpotFindingSettings(); + settings.adaptive_threshold = true; + settings.min_pix_per_spot = std::nullopt; + settings.quick_integration = true; + return settings; +} + +// The start message did not go out (or the analysis could not be set up): nothing can be written, so the collection stops. +void JFJochReceiverRugnuxStills::StillsSetupFailed(const JFJochException &error) { + Cancel(error); +} + +ZeroCopyReturnValue *JFJochReceiverRugnuxStills::AnalysedImageSlot() { + // An analysed frame waits for the writer, which lags behind the catch-up from a full buffer; this long at most + constexpr auto WRITER_WAIT = std::chrono::seconds(2); + const auto t0 = std::chrono::steady_clock::now(); + auto loc = image_buffer.GetImageSlot(); + while (loc == nullptr && std::chrono::steady_clock::now() - t0 < WRITER_WAIT) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + loc = image_buffer.GetImageSlot(); + } + return loc; +} + +void JFJochReceiverRugnuxStills::SendAnalysedImage(DataMessage &message, const AzimuthalIntegrationProfile &profile) { + try { + const auto status = image_buffer.GetSlotStatus(); + message.receiver_buf_available = status.available_slots; + message.receiver_buf_in_preparation = status.preparation_slots; + message.receiver_buf_in_sending = status.sending_slots; + AddToAverages(message); + scan_result.Add(message); + + if (!serialmx_filter.ApplyFilter(message)) + ++images_skipped; + else + ForwardImage(message, AnalysedImageSlot()); + + plots.Add(message, profile); + current_status.SetProgress(GetProgress()); + current_status.SetStatus(GetStatus()); + } catch (const JFJochException &e) { + logger.ErrorException(e); + Cancel(e); + } catch (const std::exception &e) { + const JFJochException error(JFJochExceptionCategory::WrongDAQState, + fmt::format("Image {} not written - {}", message.number, e.what())); + logger.ErrorException(error); + Cancel(error); + } +} + +void JFJochReceiverRugnuxStills::AddToEndMessage(EndMessage &message) { + JFJochReceiverStream::AddToEndMessage(message); + if (stills) + stills->FillEndMessage(message); +} + +void JFJochReceiverRugnuxStills::Cut(const std::string &reason) { + std::unique_lock ul(stills_mutex); + if (cut) + return; + cut = true; + if (stills) { + stills->Cut(reason); + return; + } + live_status.Cut(reason); + logger.Debug("rugnux live: analysis cut ({}) - the end message carries what is established", reason); +} diff --git a/receiver/JFJochReceiverRugnuxStills.h b/receiver/JFJochReceiverRugnuxStills.h new file mode 100644 index 000000000..c36de3f9e --- /dev/null +++ b/receiver/JFJochReceiverRugnuxStills.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include +#include +#include + +#include "JFJochReceiverStream.h" +#include "RugnuxLiveStatus.h" +#include "StillsGPUAnalysis.h" + +// A serial collection analysed frame by frame at the geometry of a pre-pass, which the start message waits for +class JFJochReceiverRugnuxStills : public JFJochReceiverStream, public StillsSink { + const std::shared_ptr model; + const double deadline_s; // after the last image, the model step is cut (0 = never) + RugnuxLiveStatus &live_status; + + IndexerThreadPool *const indexing_thread_pool; + const int64_t stills_nthreads; + int64_t stills_keep_free = 0; // slots frames do not take, for the analysis threads to send with + + std::mutex stills_mutex; + bool cut = false; + std::unique_ptr stills; // under stills_mutex until the frames are in, for Cut + + void OnStart(std::chrono::steady_clock::time_point received) override; + void OnImage(uint32_t id, DataMessage &message, std::chrono::steady_clock::time_point received) override; + void FinishCollection() override; + ZeroCopyReturnValue *AnalysedImageSlot(); + void StillsReady(const StillsSetup &setup) override; + void SendAnalysedImage(DataMessage &message, const AzimuthalIntegrationProfile &profile) override; + SpotFindingSettings AnalysisSettings() override; + void StillsSetupFailed(const JFJochException &error) override; + void Cut(const std::string &reason) override; + void AddToEndMessage(EndMessage &message) override; +public: + JFJochReceiverRugnuxStills(const DiffractionExperiment &experiment, + const PixelMask &pixel_mask, + ImagePuller &image_puller, + ImagePusher &image_pusher, + Logger &logger, + int64_t forward_nthreads, + std::shared_ptr model, + double deadline_s, + RugnuxLiveStatus &live_status, + const SpotFindingSettings &spot_finding_settings, + JFJochReceiverCurrentStatus ¤t_status, + JFJochReceiverPlots &plots, + ImageBuffer &image_buffer, + ZMQPreviewSocket *zmq_preview_socket = nullptr, + ZMQMetadataSocket *zmq_metadata_socket = nullptr, + IndexerThreadPool *indexing_thread_pool = nullptr); + ~JFJochReceiverRugnuxStills() override; +}; diff --git a/receiver/JFJochReceiverService.cpp b/receiver/JFJochReceiverService.cpp index 868369b08..6755570a9 100644 --- a/receiver/JFJochReceiverService.cpp +++ b/receiver/JFJochReceiverService.cpp @@ -2,11 +2,13 @@ // SPDX-License-Identifier: GPL-3.0-only #include "JFJochReceiverService.h" -#include "JFJochReceiverRugnux.h" -#include "../common/ThreadAffinity.h" +#include "JFJochReceiverRugnuxRotation.h" +#include "JFJochReceiverRugnuxStills.h" #include "../rugnux/RugnuxDefaults.h" +#include "../common/ThreadAffinity.h" #include "JFJochReceiverFPGA.h" #include "JFJochReceiverLite.h" +#include "JFJochReceiverDarkMask.h" #include "../preview/JFJochJPEG.h" #include "../preview/JFJochTIFF.h" @@ -91,56 +93,83 @@ void JFJochReceiverService::Start(const DiffractionExperiment &experiment, throw JFJochException(JFJochExceptionCategory::WrongDAQState, "Receiver not idle, cannot start"); try { - auto nthreads_local = nthreads; - if (experiment.IsCPUSummation()) - nthreads_local = 4; - // First clean-up old measurement receiver.reset(); + analysis_warning.clear(); preview_image.Configure(experiment, pixel_mask); - switch (experiment.GetDetectorType()) { - case DetectorType::EIGER: - case DetectorType::JUNGFRAU: - receiver = std::make_unique(experiment, pixel_mask, - calibration, - aq_devices, image_pusher, - logger, - nthreads_local, - spot_finding_settings, - receiver_status, - plots, - image_buffer, - zmq_preview_socket.get(), - zmq_metadata_socket.get(), - indexer_thread_pool.get(), - // Serial data with rugnux on: the frames are - // analysed on the GPU, after a pre-pass. - rugnux_enable && !RugnuxRotationData(experiment), - &rugnux_status); - break; - case DetectorType::DECTRIS: - if (puller) - image_puller = puller; - else { - image_puller = std::make_shared( - experiment.GetDetectorSetup().GetDECTRISStream2Addr()); - } - if (rugnux_enable && RugnuxRotationData(experiment)) { - receiver = std::make_unique( - experiment, pixel_mask, *image_puller, image_pusher, logger, nthreads_local, - std::max(1, static_cast(AvailableCpus()) - rugnux_reserved_cpus), - rugnux_model, rugnux_deadline_s, rugnux_status, spot_finding_settings, receiver_status, plots, image_buffer, - zmq_preview_socket.get(), zmq_metadata_socket.get()); - break; - } + if (experiment.GetDetectorType() != DetectorType::DECTRIS) { + // Summation on the host runs threads of its own for each image + auto fpga_nthreads = nthreads; + if (experiment.IsCPUSummation()) + fpga_nthreads = 4; + receiver = std::make_unique(experiment, pixel_mask, + calibration, + aq_devices, image_pusher, + logger, + fpga_nthreads, + spot_finding_settings, + receiver_status, + plots, + image_buffer, + zmq_preview_socket.get(), + zmq_metadata_socket.get(), + indexer_thread_pool.get(), + // Serial data with rugnux on: the frames are + // analysed on the GPU, after a pre-pass. + rugnux_enable && !RugnuxRotationData(experiment), + &rugnux_status); + } else { + if (puller) + image_puller = puller; + else + image_puller = std::make_shared(experiment.GetDetectorSetup().GetDECTRISStream2Addr()); + + bool rugnux_rotation = rugnux_enable && (experiment.GetDetectorMode() == DetectorMode::Standard) + && RugnuxRotationData(experiment); + // Rugnux reads the images back from the buffer: a collection that does not fit gets the per-image analysis instead + const int64_t slots = image_buffer.GetBufferSize() / experiment.GetImageBufferLocationSize(); + if (rugnux_rotation && (slots < experiment.GetImageNum())) { + analysis_warning = fmt::format("no rugnux analysis: the image buffer holds {} images, the collection " + "has {}", slots, experiment.GetImageNum()); + logger.Warning("{}", analysis_warning); + rugnux_status.Start(experiment.GetImageNum(), {}, std::chrono::steady_clock::now()); + rugnux_status.Finish(RugnuxLiveState::Skipped, "the image buffer cannot hold the whole collection"); + rugnux_rotation = false; + } + + // The dark frames of the mask sequence have a receiver of their own, with rugnux on as well. + if (experiment.GetDetectorMode() == DetectorMode::DarkMask) + receiver = std::make_unique(experiment, + pixel_mask, + *image_puller, + image_pusher, + logger, + nthreads, + spot_finding_settings, + receiver_status, + plots, + image_buffer); + else if (rugnux_rotation) + receiver = std::make_unique( + experiment, pixel_mask, *image_puller, image_pusher, logger, nthreads, + std::max(1, static_cast(AvailableCpus()) - rugnux_reserved_cpus), + rugnux_model, rugnux_deadline_s, rugnux_status, spot_finding_settings, receiver_status, plots, + image_buffer, zmq_preview_socket.get(), zmq_metadata_socket.get()); + else if (rugnux_enable && (experiment.GetDetectorMode() == DetectorMode::Standard) + && !RugnuxRotationData(experiment)) + receiver = std::make_unique( + experiment, pixel_mask, *image_puller, image_pusher, logger, nthreads, + rugnux_model, rugnux_deadline_s, rugnux_status, spot_finding_settings, receiver_status, plots, + image_buffer, zmq_preview_socket.get(), zmq_metadata_socket.get(), indexer_thread_pool.get()); + else receiver = std::make_unique(experiment, pixel_mask, *image_puller, image_pusher, logger, - nthreads_local, + nthreads, spot_finding_settings, receiver_status, plots, @@ -148,12 +177,11 @@ void JFJochReceiverService::Start(const DiffractionExperiment &experiment, zmq_preview_socket.get(), zmq_metadata_socket.get(), indexer_thread_pool.get()); - break; - } measurement = std::async(std::launch::async, &JFJochReceiverService::FinalizeMeasurement, this); state = ReceiverState::Running; } catch (const std::exception &e) { + receiver.reset(); // its threads may be polling the puller // The receiver never started, so drop the status its base constructor had already reset - // otherwise /status and /statistics keep reporting a zero-progress run that never happened, // until the next start overwrites it. @@ -190,7 +218,10 @@ JFJochReceiverOutput JFJochReceiverService::Stop() { logger.Warning("Request to stop while receiver not running"); throw JFJochException(JFJochExceptionCategory::WrongReceiverState, "Receiver idle, cannot stop"); } - return receiver->GetFinalStatistics(); + auto ret = receiver->GetFinalStatistics(); + if (!analysis_warning.empty()) + ret.analysis_warning = analysis_warning; + return ret; } void JFJochReceiverService::SetSpotFindingSettings(const SpotFindingSettings &settings) { diff --git a/receiver/JFJochReceiverService.h b/receiver/JFJochReceiverService.h index d0c07e8c8..1d27be7f9 100644 --- a/receiver/JFJochReceiverService.h +++ b/receiver/JFJochReceiverService.h @@ -9,6 +9,9 @@ #include #include "JFJochReceiver.h" +#include "../preview/PreviewImage.h" +#include "../image_puller/ZMQImagePuller.h" +#include "../image_analysis/indexing/IndexerThreadPool.h" #include "../acquisition_device/AcquisitionDeviceGroup.h" #include "../preview/ZMQMetadataSocket.h" @@ -25,13 +28,13 @@ class JFJochReceiverService { std::shared_ptr image_puller; int64_t nthreads = 8; - // Rotation collections from a DECTRIS detector analysed by rugnux while they arrive - // (JFJochReceiverRugnux), instead of image by image. Off unless asked for. + // Collections analysed by rugnux while they arrive, instead of image by image. Off unless asked for. bool rugnux_enable = false; std::shared_ptr rugnux_model; int rugnux_reserved_cpus = 4; // left to the forward threads and the pusher double rugnux_deadline_s = 60; // the analysis is cut this long after the last image (0 = never) RugnuxLiveStatus rugnux_status; // of the last collection, kept after it ends + std::string analysis_warning; // why the collection was not analysed as asked, for the broker's status enum class ReceiverState {Idle, Running}; ReceiverState state = ReceiverState::Idle; diff --git a/receiver/JFJochReceiverStream.cpp b/receiver/JFJochReceiverStream.cpp new file mode 100644 index 000000000..0e176ee8f --- /dev/null +++ b/receiver/JFJochReceiverStream.cpp @@ -0,0 +1,253 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "JFJochReceiverStream.h" + +void JFJochReceiverStream::ApplyStartMessage(DiffractionExperiment &experiment, const StartMessage &msg) { + // A PSI detector's images are what the experiment set the card up to produce: nothing to learn here. + if (experiment.GetDetectorType() != DetectorType::DECTRIS) + return; + + if ((experiment.GetXPixelsNum() != msg.image_size_x) + || (experiment.GetYPixelsNum() != msg.image_size_y)) + throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, + "Mismatch in detector size"); + if (fabs(experiment.GetPixelSize_mm() - msg.pixel_size_x * 1e3) > 1e-7) + throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, + "Mismatch in pixel size"); + + experiment.Detector().Description(msg.detector_description); + experiment.Detector().SerialNumber(msg.detector_serial_number); + experiment.Detector().SensorMaterial(msg.sensor_material); + experiment.Detector().SensorThickness_um(msg.sensor_thickness * 1e6); + experiment.Detector().SaturationLimit(SaturationLimitFromValue(msg.saturation_value)); + // The outgoing start message is rebuilt from the experiment, NXmx would record these as not applied + experiment.Detector().CountRateCorrectionApplied(msg.countrate_correction_enabled); + experiment.Detector().CountRateCorrectionLookupTable(msg.countrate_correction_lookup_table); + experiment.Detector().FlatfieldApplied(msg.flatfield_enabled); + experiment.Detector().VirtualPixelInterpolationApplied(msg.virtual_pixel_interpolation_enabled); + // The mask written with the data is ours, never uploaded to the detector + experiment.ApplyPixelMask(false); + // Images are forwarded byte-for-byte, so the stream decides both width and sign + experiment.Detector().BitDepthImage(msg.bit_depth_image); + experiment.PixelSigned(msg.pixel_signed); +} + +JFJochReceiverStream::JFJochReceiverStream(const DiffractionExperiment &in_experiment, + const PixelMask &in_pixel_mask, + ImagePuller &in_image_puller, + ImagePusher &in_image_pusher, + Logger &in_logger, + int64_t in_nthreads, + const SpotFindingSettings &in_spot_finding_settings, + JFJochReceiverCurrentStatus &in_current_status, + JFJochReceiverPlots &in_plots, + ImageBuffer &in_image_buffer, + ZMQPreviewSocket *in_zmq_preview_socket, + ZMQMetadataSocket *in_zmq_metadata_socket) + : JFJochReceiver(in_experiment, + in_image_buffer, + in_image_pusher, + in_current_status, + in_plots, + in_spot_finding_settings, + in_logger, + in_pixel_mask, + in_zmq_preview_socket, + in_zmq_metadata_socket), + image_puller(in_image_puller), + nthreads(in_nthreads), + measurement_started(1), + images_started(nthreads) { +} + +void JFJochReceiverStream::StartThreads() { + logger.Debug("Starting {} image threads", nthreads); + for (int i = 0; i < nthreads; i++) + image_futures.emplace_back(std::async(std::launch::async, &JFJochReceiverStream::ImageThread, this, i)); + measurement = std::async(std::launch::async, &JFJochReceiverStream::MeasurementThread, this); + images_started.wait(); + logger.Debug("Image threads ready"); +} + +void JFJochReceiverStream::JoinFuture(std::future &future) { + try { + if (future.valid()) + future.get(); + } catch (const std::exception &e) { + logger.Error("Receiver teardown failed: {}", e.what()); + } catch (...) { + logger.Error("Receiver teardown failed with unknown exception"); + } +} + +void JFJochReceiverStream::JoinThreads() { + JoinFuture(measurement); + // The measurement thread joins them, unless it left with an error + for (auto &f: image_futures) + JoinFuture(f); +} + +void JFJochReceiverStream::MeasurementThread() { + try { + // Once the detector has reported it is done, a start message in flight may still land, then give up + std::optional give_up_at; + auto msg = image_puller.PollImage(); + + while (!cancelled && (!msg.has_value() || !msg->cbor || !msg->cbor->start_message)) { + if (detector_finished && !give_up_at) + give_up_at = std::chrono::steady_clock::now() + StartMessageGrace; + if (give_up_at && (std::chrono::steady_clock::now() > *give_up_at)) { + logger.Error("Detector finished, but no start message arrived - abandoning measurement"); + cancelled = true; + break; + } + msg = image_puller.PollImage(); + } + + if (cancelled) { + current_status.SetProgress({}); + current_status.SetStatus(GetStatus()); + measurement_started.count_down(); + return; // just quit the function - no measurement, no results + } + + ApplyStartMessage(experiment, msg->cbor->start_message.value()); + image_buffer.StartMeasurement(experiment); // Only at this point we know bit-depth of the images + start_time = std::chrono::system_clock::now(); + OnStart(msg->received); + measurement_started.count_down(); + } catch (const JFJochException &e) { + logger.ErrorException(e); + Cancel(e); + measurement_started.count_down(); + throw; + } catch (const std::exception &e) { + logger.Error("Cancelling data collection due to exception: {}", e.what()); + cancelled = true; + measurement_started.count_down(); + throw; + } + + logger.Info("Receiving started"); + + for (auto &f: image_futures) + f.get(); + + logger.Debug("Image threads finished"); + + FinishCollection(); +} + +void JFJochReceiverStream::OnStart(std::chrono::steady_clock::time_point received) { + SendStartMessage(); +} + +void JFJochReceiverStream::ImageThread(uint32_t id) { + WaitForImages(); + PollImages(id); +} + +void JFJochReceiverStream::WaitForImages() { + images_started.count_down(); + measurement_started.wait(); +} + +void JFJochReceiverStream::PollImages(uint32_t id) { + while (!cancelled && !end_message_received) { + try { + auto msg = image_puller.PollImage(std::chrono::milliseconds(5)); + + if (!msg.has_value() || !msg->cbor) { + // Message not received or not parsed + continue; + } else if (msg->cbor->end_message) { + end_adu_histogram = msg->cbor->end_message->adu_histogram; + end_message_received = true; + logger.Debug("Thread {} end message received", id); + } else if (msg->cbor->data_message) { + DataMessage message = msg->cbor->data_message.value(); + OnImage(id, message, msg->received); + } + UpdateMaxDelay(image_puller.GetCurrentFifoUtilization()); + current_status.SetProgress(GetProgress()); + current_status.SetStatus(GetStatus()); + } catch (const JFJochException &e) { + logger.ErrorException(e); + Cancel(e); + } catch (const std::exception &e) { + logger.Error("Cancelling data collection due to exception: {}", e.what()); + cancelled = true; + } + } +} + +void JFJochReceiverStream::CheckIfImagesSent() { + if (!image_buffer.CheckIfBufferReturned(SendFinalizeTimeout)) { + logger.Error("Send commands not finalized in {} seconds", SendFinalizeTimeout.count()); + throw JFJochException(JFJochExceptionCategory::ZeroMQ, "Send commands not finalized in " + + std::to_string(SendFinalizeTimeout.count()) + " seconds"); + } +} + +void JFJochReceiverStream::WriterFinalize() { + if (push_images_to_writer) + writer_error = image_pusher.Finalize(); +} + +void JFJochReceiverStream::FinishCollection() { + current_status.SetProgress(1.0); + current_status.SetStatus(GetStatus()); + + SendEndMessage(); + CheckIfImagesSent(); + logger.Debug("All images sent"); + + WriterFinalize(); + logger.Debug("Writing process finalized"); + + end_time = std::chrono::system_clock::now(); + + current_status.SetProgress({}); + current_status.SetStatus(GetStatus()); +} + +void JFJochReceiverStream::StopReceiver() { + if (measurement.valid()) { + measurement.get(); + logger.Debug("Receiver stopped"); + } +} + +void JFJochReceiverStream::Cancel(bool silent) { + // A silent cancel is not a request to abort, it says the detector has finished. It bounds the wait for the start message. + detector_finished = true; + if (silent) + return; + // Once the end message is in, the images are all there: a cancel only cuts the analysis that goes on after them. + if (!end_message_received) + JFJochReceiver::Cancel(silent); + Cut("cancel"); +} + +void JFJochReceiverStream::AddToEndMessage(EndMessage &message) { + if (!end_adu_histogram.empty()) + message.adu_histogram = end_adu_histogram; +} + +// The collection is counted in images, not frames. When the card sums frames, one image is many frames. +int64_t JFJochReceiverStream::ExpectedImages() const { + return experiment.GetImageNum(); +} + +float JFJochReceiverStream::GetEfficiency() const { + if (ExpectedImages() == 0) + return 0; + return static_cast(collection_efficiency_sum / static_cast(ExpectedImages())); +} + +float JFJochReceiverStream::GetProgress() const { + if (ExpectedImages() == 0) + return 0.0; + return static_cast(max_image_number_received) / static_cast(ExpectedImages()); +} diff --git a/receiver/JFJochReceiverStream.h b/receiver/JFJochReceiverStream.h new file mode 100644 index 000000000..448c49044 --- /dev/null +++ b/receiver/JFJochReceiverStream.h @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include +#include +#include +#include +#include + +#include "JFJochReceiver.h" +#include "../image_puller/ImagePuller.h" + +// A collection from an ImagePuller; the most-derived class starts the threads last and joins them first thing +class JFJochReceiverStream : public JFJochReceiver { +protected: + using JFJochReceiver::Cancel; + + ImagePuller &image_puller; + std::atomic end_message_received{false}; + + // A silent cancel: the detector reads back idle. Images still being processed go on. + std::atomic detector_finished{false}; + + // How long a start message may still be in flight after the detector reported it had finished + constexpr static auto StartMessageGrace = std::chrono::seconds(5); + + const int64_t nthreads; + std::latch measurement_started; +private: + std::latch images_started; + std::vector> image_futures; + std::future measurement; + std::map> end_adu_histogram; + + void JoinFuture(std::future &future); + void MeasurementThread(); +protected: + void StartThreads(); + void JoinThreads(); + void WaitForImages(); // what an image thread does before it takes images + void PollImages(uint32_t id); // the rest of an image thread, until the end message + void CheckIfImagesSent(); // throws if the pusher did not return every image buffer it held + void WriterFinalize(); + + virtual void ImageThread(uint32_t id); + virtual void OnStart(std::chrono::steady_clock::time_point received); + virtual void OnImage(uint32_t id, DataMessage &message, std::chrono::steady_clock::time_point received) = 0; + virtual void FinishCollection(); // all images are in: end message, writer, status + virtual int64_t ExpectedImages() const; + virtual void Cut(const std::string &reason) {} // the analysis that goes on after the images, if there is one + + void AddToEndMessage(EndMessage &message) override; + uint64_t GetImagesDropped() const override { return image_puller.GetDroppedImages(); } +public: + // Takes what the detector's start message decides into the experiment. + static void ApplyStartMessage(DiffractionExperiment &experiment, const StartMessage &message); + + JFJochReceiverStream(const DiffractionExperiment &experiment, + const PixelMask &pixel_mask, + ImagePuller &image_puller, + ImagePusher &image_pusher, + Logger &logger, + int64_t nthreads, + const SpotFindingSettings &spot_finding_settings, + JFJochReceiverCurrentStatus ¤t_status, + JFJochReceiverPlots &plots, + ImageBuffer &image_buffer, + ZMQPreviewSocket *zmq_preview_socket = nullptr, + ZMQMetadataSocket *zmq_metadata_socket = nullptr); + + void StopReceiver() override; + void Cancel(bool silent) override; + + float GetEfficiency() const override; + float GetProgress() const override; +}; diff --git a/receiver/RugnuxLiveStatus.cpp b/receiver/RugnuxLiveStatus.cpp index 755700609..9569af83f 100644 --- a/receiver/RugnuxLiveStatus.cpp +++ b/receiver/RugnuxLiveStatus.cpp @@ -3,6 +3,12 @@ #include "RugnuxLiveStatus.h" +#include +#include +#include + +#include "../rugnux/Rugnux.h" + double RugnuxLiveStatus::Since(std::chrono::steady_clock::time_point t) const { return std::chrono::duration(t - start).count(); } @@ -11,141 +17,157 @@ double RugnuxLiveStatus::Now() const { return Since(std::chrono::steady_clock::now()); } +// A step of the run, "..., N images", covers no more images than came: after a cancel the run goes on over those. +void RugnuxLiveStatus::CapImages(std::string &title) const { + const std::string suffix = " images"; + const size_t pos = title.rfind(", "); + if (snapshot.cut != "cancel" || pos == std::string::npos || !title.ends_with(suffix)) + return; + const std::string count = title.substr(pos + 2, title.size() - pos - 2 - suffix.size()); + if (count.empty() || !std::all_of(count.begin(), count.end(), ::isdigit)) + return; + if (std::stoll(count) > snapshot.images_received) + title = title.substr(0, pos + 2) + std::to_string(snapshot.images_received) + suffix; +} + void RugnuxLiveStatus::CloseStep() { - if (!s.steps.empty() && !s.steps.back().duration_s) - s.steps.back().duration_s = Now() - s.steps.back().started_s; + if (!snapshot.steps.empty() && !snapshot.steps.back().duration_s) + snapshot.steps.back().duration_s = Now() - snapshot.steps.back().started_s; } void RugnuxLiveStatus::Start(int64_t images_expected, std::optional expected_collection_s, std::chrono::steady_clock::time_point start_received) { - std::unique_lock ul(m); - s = RugnuxLiveSnapshot{}; - s.state = RugnuxLiveState::Running; - s.images_expected = images_expected; - s.expected_collection_s = expected_collection_s; + std::unique_lock ul(status_mutex); + snapshot = RugnuxLiveSnapshot{}; + snapshot.state = RugnuxLiveState::Running; + snapshot.images_expected = images_expected; + snapshot.expected_collection_s = expected_collection_s; start = start_received; last_image.reset(); } void RugnuxLiveStatus::ImageReceived(std::chrono::steady_clock::time_point received) { - std::unique_lock ul(m); - ++s.images_received; + std::unique_lock ul(status_mutex); + ++snapshot.images_received; // Several forward threads report, so the images need not come here in the order they arrived. const double t = Since(received); - if (!s.start_to_first_image_s || t < s.start_to_first_image_s.value()) - s.start_to_first_image_s = t; + if (!snapshot.start_to_first_image_s || t < snapshot.start_to_first_image_s.value()) + snapshot.start_to_first_image_s = t; if (!last_image || received > last_image.value()) last_image = received; } void RugnuxLiveStatus::Step(const std::string &title) { - std::unique_lock ul(m); + std::unique_lock ul(status_mutex); CloseStep(); - s.steps.push_back(RugnuxLiveStep{.title = title, .started_s = Now()}); - s.step_progress.reset(); + snapshot.steps.push_back(RugnuxLiveStep{.title = title, .started_s = Now()}); + CapImages(snapshot.steps.back().title); + snapshot.step_progress.reset(); } void RugnuxLiveStatus::Note(const std::string &text) { - std::unique_lock ul(m); - if (s.steps.empty()) - s.steps.push_back(RugnuxLiveStep{.title = "", .started_s = Now()}); - s.steps.back().notes.push_back(text); + std::unique_lock ul(status_mutex); + if (snapshot.steps.empty()) + snapshot.steps.push_back(RugnuxLiveStep{.title = "", .started_s = Now()}); + snapshot.steps.back().notes.push_back(text); } void RugnuxLiveStatus::Progress(uint64_t done, uint64_t total) { - std::unique_lock ul(m); + std::unique_lock ul(status_mutex); if (total > 0) - s.step_progress = static_cast(done) / static_cast(total); + snapshot.step_progress = static_cast(done) / static_cast(total); } void RugnuxLiveStatus::Finish(RugnuxLiveState state, const std::string &message) { - std::unique_lock ul(m); + std::unique_lock ul(status_mutex); CloseStep(); - s.elapsed_s = Now(); - s.state = state; - s.message = message; - s.step_progress.reset(); + snapshot.elapsed_s = Now(); + snapshot.state = state; + snapshot.message = message; + snapshot.step_progress.reset(); } void RugnuxLiveStatus::Result(const std::string &verdict, const std::string &verdict_text, const std::vector> &summary, const std::vector &warnings, bool anomalous_signal) { - std::unique_lock ul(m); - s.verdict = verdict; - s.verdict_text = verdict_text; - s.summary = summary; - s.warnings = warnings; - s.anomalous_signal = anomalous_signal; + std::unique_lock ul(status_mutex); + snapshot.verdict = verdict; + snapshot.verdict_text = verdict_text; + snapshot.summary = summary; + snapshot.warnings = warnings; + snapshot.anomalous_signal = anomalous_signal; } void RugnuxLiveStatus::Model(const RugnuxLiveModel &model) { - std::unique_lock ul(m); - s.model = model; + std::unique_lock ul(status_mutex); + snapshot.model = model; } void RugnuxLiveStatus::Headline(const std::string &space_group, std::optional d_min, std::optional isa) { - std::unique_lock ul(m); - s.space_group = space_group; - s.d_min = d_min; - s.isa = isa; + std::unique_lock ul(status_mutex); + snapshot.space_group = space_group; + snapshot.d_min = d_min; + snapshot.isa = isa; } void RugnuxLiveStatus::Stills(const RugnuxLiveStills &stills) { - std::unique_lock ul(m); - s.stills = stills; + std::unique_lock ul(status_mutex); + snapshot.stills = stills; } void RugnuxLiveStatus::Latency(double last_image_to_written_s) { - std::unique_lock ul(m); - s.last_image_to_written_s = last_image_to_written_s; + std::unique_lock ul(status_mutex); + snapshot.last_image_to_written_s = last_image_to_written_s; } std::optional RugnuxLiveStatus::LastImage() { - std::unique_lock ul(m); + std::unique_lock ul(status_mutex); if (last_image) - s.last_image_s = Since(last_image.value()); + snapshot.last_image_s = Since(last_image.value()); return last_image; } void RugnuxLiveStatus::Cut(const std::string &reason) { - std::unique_lock ul(m); - s.cut = reason; - s.cut_s = Now(); + std::unique_lock ul(status_mutex); + snapshot.cut = reason; + snapshot.cut_s = Now(); + for (auto &step: snapshot.steps) + CapImages(step.title); } void RugnuxLiveStatus::Probe(const std::string &text) { - std::unique_lock ul(m); - s.probe = text; - s.probe_s = Now(); + std::unique_lock ul(status_mutex); + snapshot.probe = text; + snapshot.probe_s = Now(); } void RugnuxLiveStatus::Warning(const std::string &text) { - std::unique_lock ul(m); - s.warnings.push_back(text); + std::unique_lock ul(status_mutex); + snapshot.warnings.push_back(text); } void RugnuxLiveStatus::Structure(std::shared_ptr structure, std::shared_ptr refined_structure) { - std::unique_lock ul(m); - s.structure = std::move(structure); - s.refined_structure = std::move(refined_structure); + std::unique_lock ul(status_mutex); + snapshot.structure = std::move(structure); + snapshot.refined_structure = std::move(refined_structure); } void RugnuxLiveStatus::Clear() { - std::unique_lock ul(m); - s = RugnuxLiveSnapshot{}; + std::unique_lock ul(status_mutex); + snapshot = RugnuxLiveSnapshot{}; last_image.reset(); } RugnuxLiveSnapshot RugnuxLiveStatus::Get() const { - std::unique_lock ul(m); - RugnuxLiveSnapshot ret = s; - if (s.state == RugnuxLiveState::Running) + std::unique_lock ul(status_mutex); + RugnuxLiveSnapshot ret = snapshot; + if (snapshot.state == RugnuxLiveState::Running) ret.elapsed_s = Now(); // Held since START, reported since the first image - or, before it has come, since now. - const double zero = s.start_to_first_image_s.value_or(s.state == RugnuxLiveState::Running ? ret.elapsed_s : 0.0); + const double zero = snapshot.start_to_first_image_s.value_or(snapshot.state == RugnuxLiveState::Running ? ret.elapsed_s : 0.0); ret.elapsed_s -= zero; for (auto &step: ret.steps) step.started_s -= zero; @@ -157,3 +179,87 @@ RugnuxLiveSnapshot RugnuxLiveStatus::Get() const { *ret.cut_s -= zero; return ret; } + +namespace { + // A score the status carries only where it is a number: JSON has no NaN or infinity (nlohmann writes null) + void SetIfFinite(std::optional &out, double v) { + if (std::isfinite(v)) + out = v; + } +} + +std::optional LiveModel(const ProcessResult &result) { + if (!result.model_validation && !result.model_prior) + return std::nullopt; + RugnuxLiveModel m; + m.placement = result.model_placement; + if (result.model_prior) { + const MolecularReplacementResult &mr = *result.model_prior; + m.model_name = mr.model_name; + m.predicted = mr.predicted_model; + m.mr_decision = mr.decision; + if (!mr.ok) + m.reason = mr.failure_reason; + if (mr.chosen >= 0) { + const MRCandidate &c = mr.candidates[mr.chosen]; + m.mr_space_group = c.space_group.xhm(); + m.copies = c.copies; + SetIfFinite(m.tf_z, c.tf_z); + SetIfFinite(m.rf_z, c.rf_z); + SetIfFinite(m.llg_z, c.llg_z); + SetIfFinite(m.fit_sigma, c.null_sigma); + SetIfFinite(m.mr_d_min, mr.d_min_search); + } + } + if (result.model_validation) { + const ModelValidationResult &v = *result.model_validation; + if (!v.model_name.empty()) + m.model_name = v.model_name; + if (!v.ok) { + if (m.reason.empty()) + m.reason = v.failure_reason; + } else { + SetIfFinite(m.r_work, v.r_work); + SetIfFinite(m.r_free, v.r_free); + SetIfFinite(m.cc_model, v.cc_model_overall); + if (v.fit_tested) + SetIfFinite(m.fit_sigma, v.r_work_sigma); + SetIfFinite(m.map_atom_sigma, v.mean_atom_density_sigma); + if (v.rigid_body_applied) { + SetIfFinite(m.rigid_body_angle_deg, v.rigid_body_angle_deg); + SetIfFinite(m.rigid_body_shift_A, v.rigid_body_shift_A); + } + if (!v.anomalous_sites.empty()) { + m.anomalous_site = v.anomalous_sites.front().label; + SetIfFinite(m.anomalous_site_sigma, v.anomalous_sites.front().sigma); + } + } + } + if (result.refinement) { + const RefinementResult &rf = *result.refinement; + RugnuxLiveRefinement r; + r.performed = rf.ok; + r.cancelled = rf.cancelled; + r.reason = rf.failure_reason; + r.on_gpu = rf.on_gpu; + r.seconds = rf.seconds; + if (rf.ok) { + SetIfFinite(r.r_work, rf.restrained ? rf.r_work : rf.r_work_placement); + SetIfFinite(r.r_free, rf.restrained ? rf.r_free : rf.r_free_placement); + if (rf.restrained) { + SetIfFinite(r.rmsd_bonds_A, rf.geometry.rmsd_bonds); + SetIfFinite(r.rmsd_angles_deg, rf.geometry.rmsd_angles); + } + } + r.blob_count = static_cast(rf.blobs.size()); + for (size_t i = 0; i < std::min(rf.blobs.size(), RugnuxLiveRefinement::MAX_BLOBS); i++) { + const DensityBlob &b = rf.blobs[i]; + RugnuxLiveBlob blob{DensityBlobKindName(b.kind), b.centroid.x, b.centroid.y, b.centroid.z, b.peak_sigma, + b.volume, b.nearest_atom, std::nullopt}; + SetIfFinite(blob.nearest_atom_distance_A, b.nearest_atom_distance); // infinite: no atom near + r.blobs.push_back(blob); + } + m.refinement = std::move(r); + } + return m; +} diff --git a/receiver/RugnuxLiveStatus.h b/receiver/RugnuxLiveStatus.h index a076f9225..5f7417282 100644 --- a/receiver/RugnuxLiveStatus.h +++ b/receiver/RugnuxLiveStatus.h @@ -12,15 +12,11 @@ #include class RugnuxStructure; // RugnuxStructure.h +struct ProcessResult; // rugnux/Rugnux.h -// Where the live rugnux run of the last collection is, for the broker's status route: the steps it has -// taken with what each found ("lattice found", "space group P 21 21 21"), the step it is on and how far -// into its images, and - once it is over - its verdict and summary as the report states them, and the -// latency from the last image to every result written. -// -// Times are those of the image puller - when a message came off the socket - and every time reported is -// in seconds since the first image arrived; until it has, since now. The START message comes earlier, by -// start_to_first_image_s. The collection's frame rate is (images_received - 1) / last_image_s. +// Where the live rugnux run of the last collection is. +// Times come from the image puller (when a message came off the socket) and are in seconds since the first image arrived. +// The START message comes earlier, by start_to_first_image_s. The frame rate is (images_received - 1) / last_image_s. enum class RugnuxLiveState { Idle, Running, Done, Failed, Cancelled, Skipped }; struct RugnuxLiveStep { @@ -56,9 +52,7 @@ struct RugnuxLiveRefinement { std::vector blobs; // strongest first }; -// What the run made of the model it was given (ProcessResult::model_validation and ::model_prior), in the -// numbers the report's MODEL VALIDATION section gives. Unset values were not computed: the model was not -// placed, or the placement was not by molecular replacement. +// What the run made of the model it was given, as the report's MODEL VALIDATION; unset: not computed struct RugnuxLiveModel { std::string placement; // RIGID_BODY | MR | NOT_PLACED std::string model_name; @@ -82,8 +76,7 @@ struct RugnuxLiveModel { std::optional refinement; }; -// Serial frames analysed on the GPU after a pre-pass (StillsGPUAnalysis): the pre-pass and what it decided, -// and the analysis since. +// Serial frames analysed on the GPU after a pre-pass (StillsGPUAnalysis) enum class RugnuxLivePrePass { Holding, Running, Done, Failed }; struct RugnuxLiveStills { @@ -109,18 +102,16 @@ struct RugnuxLiveSnapshot { double elapsed_s = 0.0; std::optional step_progress; // fraction of the current pass's images std::vector steps; - // The lattice probe on the first 60 degrees (JFJochReceiverRugnux::ProbeThread): an early answer - // to "is there a crystal", seconds into the collection; the run's own answer comes later. + // The lattice probe on the first 60 degrees (JFJochReceiverRugnuxRotation::ProbeThread) std::string probe; std::optional probe_s; // when it answered std::optional start_to_first_image_s; // from the START message to the first image std::optional last_image_s; // when the last image arrived; set once the collection is over - // The analysis was cut - "deadline" (the broker's deadline_s after the last image) or "cancel" - at - // cut_s; what it had established went out, the steps after it were left out. + // The analysis was cut, "deadline" or "cancel", at cut_s std::string cut; std::optional cut_s; std::optional last_image_to_written_s; - std::string verdict; // OK | WARNINGS | UNUSABLE | FAILED + std::string verdict; // OK | WARNINGS | UNUSABLE | FAILED | CANCELLED std::string verdict_text; std::vector> summary; std::vector warnings; @@ -130,25 +121,24 @@ struct RugnuxLiveSnapshot { std::string space_group; std::optional d_min; // of the merge std::optional isa; // where the error model determined it - // The placed model and its maps, once the run has placed one, and the refined model and its maps, once the - // run has refined it; for the viewer routes. + // The placed and refined models and their maps, for the viewer routes std::shared_ptr structure; std::shared_ptr refined_structure; std::optional stills; }; class RugnuxLiveStatus { - mutable std::mutex m; - RugnuxLiveSnapshot s; + mutable std::mutex status_mutex; + RugnuxLiveSnapshot snapshot; // Kept since the START message, shifted to the first image by Get(). std::chrono::steady_clock::time_point start; std::optional last_image; double Since(std::chrono::steady_clock::time_point t) const; double Now() const; void CloseStep(); + void CapImages(std::string &title) const; public: - // start_received, received: when the START message and the image came off the socket (ImagePullerOutput); - // expected_collection_s: how long the collection is set up to take, none if not known + // start_received, received: when the message came off the socket; expected_collection_s: none if not known void Start(int64_t images_expected, std::optional expected_collection_s, std::chrono::steady_clock::time_point start_received); void ImageReceived(std::chrono::steady_clock::time_point received); @@ -174,3 +164,6 @@ public: void Clear(); RugnuxLiveSnapshot Get() const; }; + +// What the run made of the model it was given, for the live status; none where no model was given. +std::optional LiveModel(const ProcessResult &result); diff --git a/receiver/RugnuxStructure.cpp b/receiver/RugnuxStructure.cpp index 88493686e..e15927242 100644 --- a/receiver/RugnuxStructure.cpp +++ b/receiver/RugnuxStructure.cpp @@ -3,6 +3,14 @@ #include "RugnuxStructure.h" +#include + +#include "spdlog/fmt/fmt.h" + +#include "../common/JFJochException.h" +#include "../frame_serialize/CBORStream2Serializer.h" +#include "../rugnux/Rugnux.h" + #include #include #include @@ -43,7 +51,7 @@ bool RugnuxStructure::HasAnomalousMap() const { } std::string RugnuxStructure::ModelCif() const { - std::unique_lock ul(m); + std::unique_lock ul(structure_mutex); if (!model_cif) model_cif = PlacedModelMmcif(*model, cell, space_group); return *model_cif; @@ -52,7 +60,7 @@ std::string RugnuxStructure::ModelCif() const { std::string RugnuxStructure::Map(RugnuxMapType type) const { if (!coefficients || (type == RugnuxMapType::Anomalous && !HasAnomalousMap())) return {}; - std::unique_lock ul(m); + std::unique_lock ul(structure_mutex); std::optional &cached = type == RugnuxMapType::TwoFoFc ? map_2fofc : type == RugnuxMapType::FoFc ? map_fofc : map_anom; if (cached) @@ -101,3 +109,46 @@ std::string RugnuxStructure::Map(RugnuxMapType type) const { cached = std::move(out); return *cached; } + +void PublishStructures(RugnuxLiveStatus &status, const PlacedModelOutput &placed, const ModelMaps *maps, + const PlacedModelOutput *refined, const ModelMaps *refined_maps) { + std::optional coefficients; + if (maps) + coefficients = maps->coefficients; + std::shared_ptr refined_structure; + if (refined) { + std::optional refined_coefficients; + if (refined_maps) + refined_coefficients = refined_maps->coefficients; + refined_structure = std::make_shared(refined->model, refined->cell, refined->space_group, + std::move(refined_coefficients)); + } + status.Structure(std::make_shared(placed.model, placed.cell, placed.space_group, + std::move(coefficients)), + std::move(refined_structure)); +} + +void DropResultBlocks(EndMessage &message) { + message.merged.reset(); + message.map_coefficients.reset(); + message.placed_model.reset(); + message.refined_model.reset(); + message.refined_map_coefficients.reset(); + message.blobs.reset(); +} + +void DropResultBlocksThatDoNotFit(EndMessage &message, RugnuxLiveStatus *status, Logger &logger) { + const auto scratch = std::make_unique_for_overwrite(MESSAGE_SIZE_FOR_START_END); + CBORStream2Serializer serializer(scratch.get(), MESSAGE_SIZE_FOR_START_END); + try { + serializer.SerializeSequenceEnd(message); + } catch (const JFJochException &e) { + const std::string text = fmt::format("the merged data, map coefficients and models do not fit in the end " + "message ({} MiB) and are not written", + MESSAGE_SIZE_FOR_START_END / (1024 * 1024)); + logger.Error("rugnux live: {} - {}", text, e.what()); + if (status) + status->Warning(text); + DropResultBlocks(message); + } +} diff --git a/receiver/RugnuxStructure.h b/receiver/RugnuxStructure.h index e44b18a32..39dd82183 100644 --- a/receiver/RugnuxStructure.h +++ b/receiver/RugnuxStructure.h @@ -11,21 +11,25 @@ #include "gemmi/model.hpp" #include "gemmi/symmetry.hpp" +#include "../common/JFJochMessages.h" +#include "../common/Logger.h" #include "../common/MtzTable.h" #include "../common/UnitCell.h" +#include "../image_analysis/structure_refinement/ModelValidation.h" +#include "RugnuxLiveStatus.h" + +struct PlacedModelOutput; enum class RugnuxMapType { TwoFoFc, FoFc, Anomalous }; -// The placed model and the map coefficients of a live rugnux run, for the broker to serve to a viewer: the -// model as mmCIF, the same text as _model.cif (PlacedModelMmcif), and the maps as CCP4 map files - made in memory when -// first asked for and kept, never written to disk. +// The placed model and map coefficients of a live run for a viewer: the maps made in memory when first asked for class RugnuxStructure { std::shared_ptr model; UnitCell cell; gemmi::SpaceGroup space_group; std::optional coefficients; // FWT/PHWT and DELFWT/PHDELWT, as _maps.mtz holds them - mutable std::mutex m; + mutable std::mutex structure_mutex; mutable std::optional model_cif; mutable std::optional map_2fofc, map_fofc, map_anom; public: @@ -38,9 +42,16 @@ public: // The coefficients include the anomalous difference map (ANOM/PHANOM): the merge kept the Bijvoet pairs. bool HasAnomalousMap() const; std::string ModelCif() const; - // A CCP4 map (mode 2), sampled every d_min/3, on one unit cell centred on the model - along an axis the model - // reaches further than the cell, on the model plus MAP_MARGIN_A instead, so that all of it has density. The - // header's mean and RMS are those of the cell. Empty where there are no map coefficients, or for the anomalous - // map none of its coefficients. + // A CCP4 map sampled every d_min/3, on one unit cell centred on the model. + // Along an axis where the model reaches further than the cell, the map covers the model plus MAP_MARGIN_A instead. + // The mean and RMS in the header are those of the cell. Empty where there are no coefficients. std::string Map(RugnuxMapType type) const; }; + +// The model step's structures, to the live status for the broker's viewer routes +void PublishStructures(RugnuxLiveStatus &status, const PlacedModelOutput &placed, const ModelMaps *maps, + const PlacedModelOutput *refined, const ModelMaps *refined_maps); + +// An END that does not fit MESSAGE_SIZE_FOR_START_END fails the collection: it goes without them, with a warning +void DropResultBlocks(EndMessage &message); +void DropResultBlocksThatDoNotFit(EndMessage &message, RugnuxLiveStatus *status, Logger &logger); diff --git a/receiver/StillsGPUAnalysis.cpp b/receiver/StillsGPUAnalysis.cpp new file mode 100644 index 000000000..130ef5342 --- /dev/null +++ b/receiver/StillsGPUAnalysis.cpp @@ -0,0 +1,588 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "StillsGPUAnalysis.h" + +#include + +#include "../common/CUDAWrapper.h" +#include "../common/ThreadAffinity.h" +#include "../image_analysis/MXAnalysisWithoutFPGA.h" +#include "../image_analysis/WriteReflections.h" +#include "../rugnux/RugnuxDefaults.h" +#include "../writer/RugnuxResultBlocks.h" +#include "RugnuxStructure.h" + +StillsGPUAnalysis::StillsGPUAnalysis(const DiffractionExperiment &in_experiment, const PixelMask &in_mask, + const StartMessage &in_start, IndexerThreadPool *in_indexing_thread_pool, + int64_t nthreads, int64_t max_held, Logger &in_logger, StillsSink &in_sink, + RugnuxLiveStatus &in_status, bool in_merge, + std::shared_ptr in_model, double in_deadline_s) + : experiment(in_experiment), mask(in_mask), start(in_start), indexing_thread_pool(in_indexing_thread_pool), + logger(in_logger), sink(in_sink), status(in_status), merge_frames(in_merge), + hold_limit(std::max(1, max_held)), model(std::move(in_model)), deadline_s(in_deadline_s) { + live.images_needed = std::min({PREPASS_IMAGES, experiment.GetImageNum(), hold_limit}); + live.file_beam_x_pxl = experiment.GetBeamX_pxl(); + live.file_beam_y_pxl = experiment.GetBeamY_pxl(); + live.file_distance_mm = experiment.GetDetectorDistance_mm(); + status.Start(experiment.GetImageNum(), + static_cast(experiment.GetImageNum()) + * std::chrono::duration(experiment.GetImageTime()).count(), + std::chrono::steady_clock::now()); + status.Step("Pre-pass"); + Publish(); + prepass_future = std::async(std::launch::async, &StillsGPUAnalysis::PrePassThread, this); + for (int64_t i = 0; i < nthreads; i++) + analysis_futures.emplace_back(std::async(std::launch::async, &StillsGPUAnalysis::AnalysisThread, this)); + logger.Info("GPU analysis: {} threads, at least the first {} images held for the pre-pass", nthreads, PREPASS_IMAGES); +} + +StillsGPUAnalysis::~StillsGPUAnalysis() { + try { + if (prepass_future.valid()) + Finish(); + } catch (const std::exception &e) { + logger.Error("GPU analysis: {}", e.what()); + } + StopModel(true); + ReportOutcome(); +} + +void StillsGPUAnalysis::AnalysisError(const std::string &text) { + logger.Error("GPU analysis: {}", text); + std::unique_lock ul(error_mutex); + if (analysis_error.empty()) + analysis_error = text; +} + +void StillsGPUAnalysis::Publish() { + std::unique_lock ul(live_mutex); + if (live.prepass == RugnuxLivePrePass::Done || live.prepass == RugnuxLivePrePass::Failed) { + const double t = std::chrono::duration(std::chrono::steady_clock::now() - ready_time).count(); + if (t > 0 && live.images_analysed > 0) + live.analysis_rate_hz = static_cast(live.images_analysed) / t; + } + status.Stills(live); +} + +void StillsGPUAnalysis::Hold(const std::shared_ptr &image) { + status.ImageReceived(std::chrono::steady_clock::now()); + queue.PutBlocking(image); + { + std::unique_lock ul(prepass_mutex); + if (prepass_state != PrePassState::Over && static_cast(prepass_images.size()) < hold_limit) { + prepass_images[image->message.number] = image; + std::unique_lock ul_live(live_mutex); + live.images_held = static_cast(prepass_images.size()); + } + } + prepass_cv.notify_all(); + Publish(); +} + +void StillsGPUAnalysis::StartPrePass() { + { + std::unique_lock ul(prepass_mutex); + if (prepass_state == PrePassState::Holding) + prepass_state = PrePassState::Closing; + } + prepass_cv.notify_all(); +} + +void StillsGPUAnalysis::Finish() { + const auto finish_start = std::chrono::steady_clock::now(); + StartPrePass(); + prepass_future.get(); + for (size_t i = 0; i < analysis_futures.size(); i++) + queue.PutBlocking(nullptr); + for (auto &f: analysis_futures) + f.get(); + const auto last_image = status.LastImage(); + Publish(); + if (merge_future.valid()) { + status.Step("Merge"); + merge_queue.Put(-1); + merge_future.get(); + if (merged) { + PublishMerge(*merged, "the collection"); + status.Headline(merge_experiment.GetSpaceGroupOrP1().xhm(), merged->merge.statistics.overall.d_min, + std::nullopt); + } + } + if (model_future.valid()) { + // The step on the merge of the collection, until the deadline; the provisional one running is cut, this starts from it + if (merged && model_left_out.empty()) { + const auto deadline = last_image.value_or(finish_start) + std::chrono::duration_cast< + std::chrono::steady_clock::duration>(std::chrono::duration(deadline_s)); + SubmitModel(*merged, "the collection", true); + StopModel(false); + if (deadline_s > 0 && model_future.wait_until(deadline) == std::future_status::timeout) { + { + std::unique_lock ul(model_mutex); + step_cut = true; + if (model_next) + model_next->cut = true; + } + logger.Warning("Model step cut at the deadline, {:.1f} s after the last image", deadline_s); + status.Cut("deadline"); + } + model_future.get(); + } else + StopModel(true); + std::unique_lock ul(model_mutex); + model_over = true; + } + if (setup_error) { + status.Finish(RugnuxLiveState::Failed, "GPU analysis setup failed"); + std::rethrow_exception(setup_error); + } + // Reported by ReportOutcome, once everything is written - the end message and the writer go on meanwhile. + status.Step("Writing"); + std::unique_lock ul(error_mutex); + if (analysis_error.empty()) + outcome.emplace(RugnuxLiveState::Done, ""); + else + outcome.emplace(RugnuxLiveState::Failed, analysis_error); +} + +void StillsGPUAnalysis::ReportOutcome() { + if (!outcome) + return; + status.Finish(outcome->first, outcome->second); + outcome.reset(); +} + +void StillsGPUAnalysis::PrePassThread() { + const auto masked = [](const PixelMask &pixel_mask) { + return std::count_if(pixel_mask.GetMask().begin(), pixel_mask.GetMask().end(), [](uint32_t v) { return v != 0; }); + }; + setup.experiment = experiment; + setup.mask = mask; + const int64_t all_images = experiment.GetImageNum(); + std::optional result; + std::chrono::steady_clock::time_point prepass_start = std::chrono::steady_clock::now(); + // With a cell, the pre-pass runs again on all that is held once it has doubled, until the fit has its frames, the + // end, or PREPASS_MAX_IMAGES + const int64_t most_held = std::min({all_images, hold_limit, PREPASS_MAX_IMAGES}); + for (int64_t needed = std::min(PREPASS_IMAGES, most_held); ;) { + std::map> sample; + bool last; + { + std::unique_lock ul(prepass_mutex); + prepass_cv.wait(ul, [&] { + return prepass_state != PrePassState::Holding || static_cast(prepass_images.size()) >= needed; + }); + // Frames that arrived during the previous run do not make this one larger than the bound + for (const auto &[n, image]: prepass_images) { + if (static_cast(sample.size()) >= most_held) + break; + sample.emplace(n, image); + } + last = prepass_state != PrePassState::Holding || static_cast(sample.size()) >= most_held; + } + if (sample.empty()) + break; + { + std::unique_lock ul(live_mutex); + live.prepass = RugnuxLivePrePass::Running; + } + Publish(); + try { + const auto t0 = std::chrono::steady_clock::now(); + auto ret = RunStillsPrePass(start, sample, static_cast(std::thread::hardware_concurrency())); + logger.Info("Pre-pass on {} images ({:.1f} s): beam centre ({:.2f}, {:.2f}) -> ({:.2f}, {:.2f}) px, " + "distance {:.2f} -> {:.2f} mm on {} frames, masked pixels {} -> {}", ret.images, + std::chrono::duration(std::chrono::steady_clock::now() - t0).count(), + experiment.GetBeamX_pxl(), experiment.GetBeamY_pxl(), ret.beam_x_pxl, ret.beam_y_pxl, + experiment.GetDetectorDistance_mm(), ret.distance_mm, ret.geometry_frames, + masked(mask), masked(ret.mask)); + result = std::move(ret); + } catch (const std::exception &e) { + logger.Warning("Pre-pass on {} images failed - {}", sample.size(), e.what()); + } + if (last || !experiment.GetUnitCell() + || (result && result->geometry_frames >= RUGNUX_REFINE_GEOMETRY_FRAMES)) + break; + needed = std::min(2 * static_cast(sample.size()), most_held); + std::unique_lock ul(live_mutex); + live.prepass = RugnuxLivePrePass::Holding; + live.images_needed = needed; + } + { + // The frames are analysed from here on: their slots go back to the buffer once they are sent. + std::unique_lock ul(prepass_mutex); + prepass_state = PrePassState::Over; + prepass_images.clear(); + } + const bool failed = !result; + int geometry_frames = 0; + if (result) { + setup.experiment.BeamX_pxl(result->beam_x_pxl).BeamY_pxl(result->beam_y_pxl).DetectorDistance_mm(result->distance_mm); + setup.mask = result->mask; + geometry_frames = result->geometry_frames; + const auto &result_mask = result->mask.GetMask(); + std::unique_lock ul(live_mutex); + live.beam_x_pxl = result->beam_x_pxl; + live.beam_y_pxl = result->beam_y_pxl; + live.distance_mm = result->distance_mm; + live.shadow_pixels = std::count_if(result_mask.begin(), result_mask.end(), [](uint32_t v) { + return (v & (1u << PixelMask::BeamStopPixelBit)) != 0; + }); + live.bad_pixels = std::count_if(result_mask.begin(), result_mask.end(), [](uint32_t v) { + return (v & (1u << PixelMask::HotPixelBit)) != 0; + }); + } else + logger.Warning("Pre-pass failed, the images are analysed with the stated beam centre and mask"); + try { + setup.mapping = std::make_unique(setup.experiment, setup.mask); + // Retained for the merge, which takes each image's reflections as soon as it is analysed. + indexer = std::make_unique(setup.experiment, indexing_thread_pool, merge_frames, true); + if (geometry_frames == 0) { + const std::string text = fmt::format( + "The pre-pass could not refine the geometry (too few frames indexed in what the buffer holds, or no unit cell given): the " + "whole run is analysed at the distance in the header, {:.2f} mm, beam centre ({:.2f}, {:.2f}) px", + setup.experiment.GetDetectorDistance_mm(), setup.experiment.GetBeamX_pxl(), + setup.experiment.GetBeamY_pxl()); + logger.Warning("{}", text); + status.Note(text); + } + if (merge_frames) { + merge_experiment = setup.experiment; + merge_experiment.ImportScalingSettings(RugnuxDefaultScalingSettings(false)); + merge = std::make_unique(merge_experiment, experiment.GetImageNum(), + setup.experiment.GetUnitCell(), std::nullopt, + setup.experiment.IsDetectIceRings(), + SpotFindingSettings().ice_ring_width_Q_recipA, logger); + // The model's thread first: the merge thread hands it its merges + if (model) + model_future = std::async(std::launch::async, &StillsGPUAnalysis::ModelThread, this); + merge_future = std::async(std::launch::async, &StillsGPUAnalysis::MergeThread, this); + } + sink.StillsReady(setup); + } catch (const std::exception &e) { + // Nothing can be written without the start message: the frames are dropped, the collection is stopped + const auto *jfjoch_error = dynamic_cast(&e); + const JFJochException error = jfjoch_error ? *jfjoch_error : JFJochException(JFJochExceptionCategory::WrongDAQState, + std::string("GPU analysis setup failed: ") + e.what()); + logger.ErrorException(error); + setup_error = jfjoch_error ? std::current_exception() : std::make_exception_ptr(error); + sink.StillsSetupFailed(error); + prepass_done.count_down(); + return; + } + { + std::unique_lock ul(live_mutex); + live.prepass = failed ? RugnuxLivePrePass::Failed : RugnuxLivePrePass::Done; + live.prepass_s = std::chrono::duration(std::chrono::steady_clock::now() - prepass_start).count(); + ready_time = std::chrono::steady_clock::now(); + } + status.Step("GPU analysis"); + Publish(); + prepass_done.count_down(); +} + +void StillsGPUAnalysis::AnalysisThread() { + try { + pin_gpu(); + } catch (const std::exception &e) { + logger.Warning("Error pinning GPU {}", e.what()); + } + + prepass_done.wait(); + + if (setup_error) { + while (queue.GetBlocking()) {} + return; + } + + // A thread whose analysis cannot be built still passes its frames on, unanalysed. + std::unique_ptr analysis; + try { + analysis = std::make_unique(setup.experiment, *setup.mapping, setup.mask, + *indexer, /*enable_fused_adaptive_gpu=*/true); + } catch (const std::exception &e) { + AnalysisError(std::string("analysis thread could not start, its frames are written unanalysed - ") + + e.what()); + } + + while (auto held = queue.GetBlocking()) { + DataMessage &message = held->message; + std::optional profile; + const auto start_time = std::chrono::steady_clock::now(); + profile.emplace(*setup.mapping); + try { + if (analysis) + analysis->AnalyzeStreamImage(message, *profile, sink.AnalysisSettings()); + } catch (const JFJochException &e) { + // The frame is still written, without its analysis. + logger.ErrorException(e); + } catch (const std::exception &e) { + AnalysisError(fmt::format("image {} written unanalysed - {}", message.number, e.what())); + } + message.processing_time_s = std::chrono::duration(std::chrono::steady_clock::now() + - start_time).count(); + try { + sink.SendAnalysedImage(message, *profile); + if (merge) + merge_queue.Put(message.number); + int64_t analysed; + { + std::unique_lock ul(live_mutex); + analysed = ++live.images_analysed; + if (message.indexing_result.value_or(false)) + ++live.images_indexed; + } + status.Progress(analysed, experiment.GetImageNum()); + Publish(); + } catch (const std::exception &e) { + // The thread goes on with the next frame, so that the queue is drained. + AnalysisError(fmt::format("image {} lost - {}", message.number, e.what())); + } + } +} + +std::optional StillsGPUAnalysis::GetConsensusUnitCell() const { + return indexer->GetConsensusUnitCell(); +} + +float StillsGPUAnalysis::GetBeamX_pxl() const { + return setup.experiment.GetBeamX_pxl(); +} + +float StillsGPUAnalysis::GetBeamY_pxl() const { + return setup.experiment.GetBeamY_pxl(); +} + +float StillsGPUAnalysis::GetDetectorDistance_mm() const { + return setup.experiment.GetDetectorDistance_mm(); +} + +const std::optional &StillsGPUAnalysis::GetMerged() const { + return merged; +} + +const DiffractionExperiment &StillsGPUAnalysis::GetMergeExperiment() const { + return merge_experiment; +} + +const std::optional &StillsGPUAnalysis::GetModel() const { + return model_final; +} + +void StillsGPUAnalysis::MergeThread() { + constexpr int64_t PREPARE_EVERY = 200; // frames between two Prepare calls after a checkpoint + const int64_t images = experiment.GetImageNum(); + // The cell merged in, as rugnux merges it: the consensus of the frames indexed by the first merge + bool cell_set = false; + const auto have_cell = [&] { + if (!cell_set) + if (const auto cell = indexer->GetConsensusUnitCell()) { + merge_experiment.SetUnitCell(*cell); + merge->Cell(*cell); + cell_set = true; + if (model) { + const ModelStepGroup group = ModelStepSpaceGroup(merge_experiment.GetGemmiSpaceGroup(), *cell, *model); + if (!group.space_group) { + model_left_out = "the model is left out: " + group.reason; + logger.Warning("{}", model_left_out); + status.Warning(model_left_out); + } else if (!merge_experiment.GetGemmiSpaceGroup()) { + merge_experiment.SetSpaceGroup(*group.space_group); + merge->SpaceGroup(*group.space_group); + logger.Info("Merged in the model's space group {}", group.space_group->xhm()); + } + } + } + return cell_set; + }; + size_t next = 0; + try { + const auto add = [&](int64_t n, IntegrationOutcome &&outcome) { + merge->Add(n, std::move(outcome)); + if (next < CHECKPOINTS.size() + && merge->Added() >= std::llround(CHECKPOINTS[next] * static_cast(images))) { + if (have_cell()) { + StillsMergeOutput checkpoint = merge->Checkpoint(MERGE_THREADS); + const std::string title = fmt::format("{:.0f}% of the images (provisional)", + 100.0 * CHECKPOINTS[next]); + PublishMerge(checkpoint, title); + if (model_future.valid() && model_left_out.empty()) + SubmitModel(checkpoint, title, false); + } + ++next; + } else if (next > 0 && merge->Added() % PREPARE_EVERY == 0) + merge->Prepare(MERGE_THREADS); + }; + for (int64_t n = merge_queue.GetBlocking(); n >= 0; n = merge_queue.GetBlocking()) + add(n, indexer->TakeIntegrationOutcome(n)); + if (have_cell()) { + merged = merge->Final(static_cast(AvailableCpus())); + logger.Info("Stills merge after the last image: {:.2f} s", merged->seconds); + } + // The frames' reflections are not read again: what the model step and the end message need is the merge + merge.reset(); + } catch (const std::exception &e) { + logger.Warning("Stills merge failed: {}", e.what()); + merged.reset(); + } +} + +void StillsGPUAnalysis::PublishMerge(const StillsMergeOutput &out, const std::string &title) { + const auto &overall = out.merge.statistics.overall; + const std::string text = fmt::format("Merge of {}: {} crystals, {:.2f} A, CC1/2 {:.3f}, R_split {:.3f}, " + "completeness {:.1f}%, {:.1f} ({:.1f} s)", title, out.images, + overall.d_min, overall.cc_half, overall.r_split, + overall.possible_unique_reflections > 0 + ? 100.0 * overall.unique_reflections / overall.possible_unique_reflections : 0.0, + overall.mean_i_over_sigma, out.seconds); + logger.Info("{}", text); + status.Note(text); +} + +void StillsGPUAnalysis::SubmitModel(StillsMergeOutput &out, const std::string &title, bool final) { + ModelJob job; + // The merge of the collection is kept for the end message + job.merged = final ? out.merge.merged : std::move(out.merge.merged); + for (const auto &sh : out.merge.statistics.shells) + job.shells.push_back(sh.d_min); + job.title = title; + job.final = final; + { + std::unique_lock ul(model_mutex); + step_cut = true; + if (model_next) + logger.Info("Model step on the merge of {} left out: the merge of {} is there", model_next->title, title); + model_next = std::move(job); + } + model_cv.notify_all(); +} + +void StillsGPUAnalysis::Cut(const std::string &reason) { + if (!model) + return; + { + std::unique_lock ul(model_mutex); + if (model_cut || model_over) + return; + model_cut = true; + step_cut = true; + } + logger.Info("Model step cut: {}", reason); + status.Cut(reason); +} + +void StillsGPUAnalysis::StopModel(bool cut) { + if (!model_future.valid()) + return; + { + std::unique_lock ul(model_mutex); + model_stop = true; + if (cut) { + model_next.reset(); + step_cut = true; + } + } + model_cv.notify_all(); + if (cut) + model_future.get(); +} + +void StillsGPUAnalysis::ModelThread() { + try { + pin_gpu(); + } catch (const std::exception &e) { + logger.Warning("Error pinning GPU {}", e.what()); + } + while (true) { + ModelJob job; + { + std::unique_lock ul(model_mutex); + model_cv.wait(ul, [this] { return model_next || model_stop; }); + if (!model_next) + return; + job = std::move(*model_next); + model_next.reset(); + step_cut = model_cut || job.cut; + } + try { + ModelStepSettings settings; + // Beside the analysis on as many threads as a provisional merge, on every thread after the last image + settings.nthreads = job.final ? AvailableCpus() : MERGE_THREADS; + // The analysis's indexers stay on the card for the broker's life + settings.gpu_resident_bytes = indexing_thread_pool ? indexing_thread_pool->DeviceBytes() : 0; + settings.rfree_fraction = merge_experiment.GetScalingSettings().GetRfreeFraction(); + settings.cut = &step_cut; + if (job.final) + settings.step = [this](const std::string &title) { status.Step(title); }; + ModelStepResult result = RunModelStep(job.merged, merge_experiment.GetUnitCell().value(), + merge_experiment.GetSpaceGroupOrP1(), *model, + merge_experiment.GetWavelength_A(), + merge_experiment.GetScalingSettings().GetMergeFriedel(), job.shells, + settings, model_placed ? &*model_placed : nullptr, logger); + PublishModel(result, job.title); + if (result.placed_model) + model_placed = result; + if (job.final) + model_final = std::move(result); + } catch (const std::exception &e) { + logger.Warning("Model step on the merge of {} failed: {}", job.title, e.what()); + } + } +} + +void StillsGPUAnalysis::PublishModel(const ModelStepResult &result, const std::string &title) { + std::string text = fmt::format("Model on the merge of {}: ", title); + if (result.placement == "NOT_PLACED") + text += "not placed"; + else + text += fmt::format("placed by {}{}", result.placement == "MR" ? "molecular replacement" : "its rigid body", + result.warm ? " (from the earlier placement)" : ""); + if (result.validation && result.validation->ok) + text += fmt::format(", R-work {:.3f}, R-free {:.3f}", result.validation->r_work, result.validation->r_free); + if (result.refinement && result.refinement->ok) + text += fmt::format("; refined{} R-work {:.3f}, R-free {:.3f}, {} blobs", result.refinement->cancelled + ? " (cut short)" : "", result.refinement->r_work, result.refinement->r_free, + result.refinement->blobs.size()); + for (const auto &left_out : result.left_out) + text += "; " + left_out + " left out"; + text += fmt::format(" ({:.1f} s placement, {:.1f} s refinement)", result.placement_s, result.refinement_s); + logger.Info("{}", text); + status.Note(text); + if (const auto live_model = LiveModel(ModelStepReport(result))) + status.Model(*live_model); + if (!result.placed_model) + return; + std::optional refined; + if (result.refinement && result.refinement->ok && result.refinement->model) + refined = PlacedModelOutput{result.refinement->model, result.placed_model->cell, result.placed_model->space_group}; + PublishStructures(status, *result.placed_model, result.maps.get(), refined ? &*refined : nullptr, + refined && result.refinement->maps ? result.refinement->maps.get() : nullptr); +} + +void StillsGPUAnalysis::FillEndMessage(EndMessage &message) const { + message.unit_cell = GetConsensusUnitCell(); + message.refined_beam_center_x = GetBeamX_pxl(); + message.refined_beam_center_y = GetBeamY_pxl(); + message.refined_detector_distance_mm = GetDetectorDistance_mm(); + // In the model's frame where a model was placed against them + if (const auto &model_step = GetModel()) { + DiffractionExperiment model_experiment(GetMergeExperiment()); + model_experiment.SetSpaceGroup(model_step->space_group); + message.merged = MergedMtzTable(model_step->merged, model_step->cell, model_experiment, OutputProvenance{}); + if (model_step->maps) + message.map_coefficients = model_step->maps->coefficients; + if (model_step->placed_model) + message.placed_model = PlacedModelFromStructure(*model_step->placed_model->model, model_step->placed_model->cell, + model_step->placed_model->space_group.xhm()); + if (const auto &refinement = model_step->refinement; refinement && refinement->ok && refinement->model) { + message.refined_model = PlacedModelFromStructure(*refinement->model, model_step->cell, model_step->space_group.xhm()); + if (refinement->maps) + message.refined_map_coefficients = refinement->maps->coefficients; + message.blobs = DensityBlobsToMessage(refinement->blobs); + } + } else if (const auto &merge_output = GetMerged(); merge_output && GetMergeExperiment().GetUnitCell()) + message.merged = MergedMtzTable(merge_output->merge.merged, *GetMergeExperiment().GetUnitCell(), + GetMergeExperiment(), OutputProvenance{}); + if (message.merged) + DropResultBlocksThatDoNotFit(message, &status, logger); +} diff --git a/receiver/StillsGPUAnalysis.h b/receiver/StillsGPUAnalysis.h new file mode 100644 index 000000000..1a6536fcd --- /dev/null +++ b/receiver/StillsGPUAnalysis.h @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "StillsPrePass.h" +#include "RugnuxLiveStatus.h" +#include "../common/DiffractionExperiment.h" +#include "../common/JFJochException.h" +#include "../common/AzimuthalIntegrationMapping.h" +#include "../common/AzimuthalIntegrationProfile.h" +#include "../common/Logger.h" +#include "../common/ThreadSafeFIFO.h" +#include "../image_analysis/IndexAndRefine.h" +#include "../rugnux/StillsLiveMerge.h" +#include "../rugnux/ModelStep.h" + +struct StillsSetup { + DiffractionExperiment experiment; + PixelMask mask; + std::unique_ptr mapping; +}; + +// What StillsGPUAnalysis needs from its owner (JFJochReceiverRugnuxStills). +class StillsSink { +public: + virtual ~StillsSink() = default; + // The pre-pass is over, no frame is analysed before this returns: the owner sends the start message. + virtual void StillsReady(const StillsSetup &setup) = 0; + // A frame is analysed; called from the analysis threads. + virtual void SendAnalysedImage(DataMessage &message, const AzimuthalIntegrationProfile &profile) = 0; + virtual SpotFindingSettings AnalysisSettings() = 0; + // The setup after the pre-pass failed, StillsReady included. + virtual void StillsSetupFailed(const JFJochException &error) = 0; +}; + +// Serial frames analysed on the GPU at the geometry of a pre-pass (fixed from there on), and merged beside the analysis +class StillsGPUAnalysis { +public: + // Enough frames for the distance to be refined at a serial hit rate; they wait in the image buffer + constexpr static int64_t PREPASS_IMAGES = 500; + // The most a pre-pass run on held frames takes: the last run stays short, so the frames arriving meanwhile flow + constexpr static int64_t PREPASS_MAX_IMAGES = 4 * PREPASS_IMAGES; + constexpr static std::array CHECKPOINTS = {0.5, 0.75}; + constexpr static int MERGE_THREADS = 8; + + // `start`: the collection's start message, as it would be sent without the pre-pass. + StillsGPUAnalysis(const DiffractionExperiment &experiment, const PixelMask &mask, const StartMessage &start, + IndexerThreadPool *indexing_thread_pool, int64_t nthreads, int64_t max_held, Logger &logger, + StillsSink &sink, RugnuxLiveStatus &status, bool merge = true, + std::shared_ptr model = nullptr, double deadline_s = 0); + ~StillsGPUAnalysis(); + + void Hold(const std::shared_ptr &image); + // No frame comes after this: every frame is analysed before it returns. Throws if the setup after the pre-pass failed. + void Finish(); + // After Finish, once everything is written; without it the outcome is reported when the analysis goes + void ReportOutcome(); + // POST /cancel: the model step stops where it is, as at the deadline, and the frames are still analysed and merged. + void Cut(const std::string &reason); + + // After Finish. + [[nodiscard]] std::optional GetConsensusUnitCell() const; + [[nodiscard]] float GetBeamX_pxl() const; + [[nodiscard]] float GetBeamY_pxl() const; + [[nodiscard]] float GetDetectorDistance_mm() const; + // The merge of the collection, where one was made. + [[nodiscard]] const std::optional &GetMerged() const; + [[nodiscard]] const DiffractionExperiment &GetMergeExperiment() const; + // The model step on the merge of the collection, where one was made. + [[nodiscard]] const std::optional &GetModel() const; + // The cell, geometry and merged data, in the model's frame where a model was placed + void FillEndMessage(EndMessage &message) const; + +private: + const DiffractionExperiment &experiment; + const PixelMask &mask; + const StartMessage start; + IndexerThreadPool *indexing_thread_pool; + Logger &logger; + StillsSink &sink; + RugnuxLiveStatus &status; + const bool merge_frames; + + std::mutex live_mutex; + RugnuxLiveStills live; + std::chrono::steady_clock::time_point ready_time; + void Publish(); + std::optional> outcome; // kept by Finish for ReportOutcome + + StillsSetup setup; + std::unique_ptr indexer; + + std::mutex prepass_mutex; + std::condition_variable prepass_cv; + enum class PrePassState {Holding, Closing, Over}; // Closing: what is held is all there will be + PrePassState prepass_state = PrePassState::Holding; + std::map> prepass_images; + std::latch prepass_done{1}; + std::exception_ptr setup_error; // set before prepass_done + + std::mutex error_mutex; + std::string analysis_error; // the first, where an analysis thread failed + void AnalysisError(const std::string &text); + + const int64_t hold_limit; // the frames the pre-pass holds and fits on, at most + ThreadSafeFIFO> queue; + std::future prepass_future; + std::vector> analysis_futures; + + // The frames analysed, by number, for the merge (-1 after the last); it takes their outcomes from the indexer. + ThreadSafeFIFO merge_queue; + std::unique_ptr merge; // after the indexer: its reflections are in the indexer's arena + DiffractionExperiment merge_experiment; + std::optional merged; + std::future merge_future; + + // Placed against each merge, mapped and refined on a thread of its own; Finish waits until deadline_s, then cuts it + const std::shared_ptr model; + const double deadline_s; + std::string model_left_out; // why the model is left out; set by the merge thread before any step + struct ModelJob { + std::vector merged; + std::vector shells; // the merge statistics' shell bounds + std::string title; + bool final = false; + bool cut = false; + }; + std::mutex model_mutex; + std::condition_variable model_cv; + std::optional model_next; + std::atomic step_cut{false}; // the step running is cut + bool model_stop = false; + bool model_cut = false; // the steps from here on are cut too + bool model_over = false; // the step on the merge of the collection has ended: nothing is left to cut + std::optional model_placed; // the last step that placed the model, for the next to start from + std::optional model_final; + std::future model_future; + // The step on `out`, the merge of `title` (its reflections moved out, but for the final one). + void SubmitModel(StillsMergeOutput &out, const std::string &title, bool final); + void StopModel(bool cut); + void ModelThread(); + void PublishModel(const ModelStepResult &result, const std::string &title); + + void StartPrePass(); + void PrePassThread(); + void AnalysisThread(); + void MergeThread(); + void PublishMerge(const StillsMergeOutput &out, const std::string &title); +}; diff --git a/receiver/StillsPrePass.cpp b/receiver/StillsPrePass.cpp index c3989b6ce..ed02abaa2 100644 --- a/receiver/StillsPrePass.cpp +++ b/receiver/StillsPrePass.cpp @@ -54,8 +54,7 @@ StillsPrePassResult RunStillsPrePass(const StartMessage &start, const std::map(result.used_direct_beam_x_pxl), .beam_y_pxl = static_cast(result.used_direct_beam_y_pxl), .distance_mm = result.used_distance_mm, diff --git a/receiver/StillsPrePass.h b/receiver/StillsPrePass.h index 634171360..afd12816a 100644 --- a/receiver/StillsPrePass.h +++ b/receiver/StillsPrePass.h @@ -11,9 +11,7 @@ #include "../common/PixelMask.h" #include "../common/ZeroCopyReturnValue.h" -// An image the receiver holds until it is analysed: its message, with the image pointing into `slot`, the -// image buffer slot the compressed frame was written to. The slot goes back to the buffer, unsent, with the -// last copy of the image held. +// An image held until analysed, pointing into `slot`, which goes back unsent with the last copy struct HeldImage { DataMessage message; ZeroCopyReturnValue *slot = nullptr; @@ -40,9 +38,6 @@ struct StillsPrePassResult { int geometry_frames = 0; // the indexed frames the distance and centre were refined on; 0: kept as stated }; -// The first frames of a serial collection through rugnux, as `rugnux --no-merge` on them: the beam centre -// it decides (from the scattered background, and from the frames that index where there are any), the -// distance (refined where a cell is given) and the mask it ends with (the collection's, plus the beam-stop -// shadow and the defective pixels). `images` by number; the start message is the collection's. +// The first frames of a serial collection through rugnux: beam centre, distance (with a cell) and mask it decides StillsPrePassResult RunStillsPrePass(const StartMessage &start, const std::map> &images, int nthreads); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dfd1a22cb..3bb067d40 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -127,6 +127,7 @@ ADD_EXECUTABLE(jfjoch_test ModuleRefinementTest.cpp LiveCollectionTest.cpp RugnuxLiveStatusTest.cpp + StillsGPUAnalysisTest.cpp StillsGPUAnalysisFPGATest.cpp RugnuxStructureTest.cpp SweepLayoutTest.cpp @@ -149,6 +150,7 @@ ADD_EXECUTABLE(jfjoch_test MovingAverageTest.cpp ImageMetadataTest.cpp JFJochReceiverLiteTest.cpp + JFJochReceiverRugnuxTest.cpp GridScanSettingsTest.cpp JFJochReceiverPlotsTest.cpp GoniometerAxisTest.cpp diff --git a/tests/JFJochReceiverRugnuxTest.cpp b/tests/JFJochReceiverRugnuxTest.cpp new file mode 100644 index 000000000..8cea5dc7b --- /dev/null +++ b/tests/JFJochReceiverRugnuxTest.cpp @@ -0,0 +1,814 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include + +#include + +#include "../compression/JFJochCompressor.h" +#include "../image_analysis/structure_refinement/RugnuxModel.h" +#include "../image_puller/TestImagePuller.h" +#include "../image_puller/ZMQImagePuller.h" +#include "../image_pusher/ImagePusher.h" +#include "../image_pusher/ZMQStream2Pusher.h" +#include "../receiver/JFJochReceiverDarkMask.h" +#include "../receiver/JFJochReceiverLite.h" +#include "../receiver/JFJochReceiverRugnuxRotation.h" +#include "../receiver/JFJochReceiverRugnuxStills.h" +#include "../receiver/JFJochReceiverService.h" +#include "../writer/HDF5Objects.h" +#include "../writer/StreamWriter.h" + +namespace { + // What the writer would be given, in order. + class RecordingPusher : public ImagePusher { + mutable std::mutex m; + std::vector events; + std::optional start; + std::optional end; + std::vector images; + public: + void StartDataCollection(StartMessage &message) override { + std::unique_lock ul(m); + events.emplace_back("start"); + start = message; + } + bool EndDataCollection(const EndMessage &message) override { + std::unique_lock ul(m); + events.emplace_back("end"); + end = message; + return true; + } + bool SendImage(const uint8_t *, size_t, int64_t number) override { + std::unique_lock ul(m); + if (events.empty() || events.back() != "image") + events.emplace_back("image"); + images.push_back(number); + return true; + } + bool SendCalibration(const CompressedImage &) override { + std::unique_lock ul(m); + events.emplace_back("calibration"); + return true; + } + std::string PrintSetup() const override { return "recording"; } + ImagePusherType GetType() const override { return ImagePusherType::Test; } + + std::vector Events() const { std::unique_lock ul(m); return events; } + std::vector Images() const { std::unique_lock ul(m); return images; } + std::optional Start() const { std::unique_lock ul(m); return start; } + std::optional End() const { std::unique_lock ul(m); return end; } + }; + + DiffractionExperiment StillsExperiment(int64_t images) { + DiffractionExperiment x(DetDECTRIS(2068, 2164, "Test", {})); + // As JFJochReceiverLite's test: the frames are signed int16, and the stream's start message says so. + x.ImagesPerTrigger(images).NumTriggers(1).ImagesPerFile(2).FilePrefix("rugnux_stills").JungfrauConvPhotonCnt(false) + .PixelSigned(true).DetectorDistance_mm(75).BeamY_pxl(1136).BeamX_pxl(1090).IncidentEnergy_keV(12.4) + .SetUnitCell(UnitCell{.a = 36.9, .b = 78.95, .c = 78.95, .alpha = 90, .beta = 90, .gamma = 90}); + return x; + } + + // The frames of the test data, as a stream delivers them: compressed, one DataMessage each, no more. + class Stream { + std::vector> frames; + size_t width, height; + public: + const std::shared_ptr puller = std::make_shared(); + + // With same_frame >= 0, every frame is that frame of the test data. + Stream(const DiffractionExperiment &x, int64_t nframes, bool blank = false, int64_t same_frame = -1) + : width(x.GetXPixelsNum()), height(x.GetYPixelsNum()) { + HDF5ReadOnlyFile data("../../tests/test_data/compression_benchmark.h5"); + HDF5DataSet dataset(data, "/entry/data/data"); + HDF5DataSpace file_space(dataset); + std::vector image(file_space.GetDimensions()[1] * file_space.GetDimensions()[2]); + JFJochBitShuffleCompressor compressor(CompressionAlgorithm::BSHUF_LZ4); + for (int64_t i = 0; i < nframes; i++) { + const int64_t frame = (same_frame >= 0) ? same_frame : i; + const std::vector start = {static_cast(frame % file_space.GetDimensions()[0]), 0, 0}; + const std::vector size = {1, file_space.GetDimensions()[1], file_space.GetDimensions()[2]}; + if (blank) + std::fill(image.begin(), image.end(), 0); + else + dataset.ReadVector(image, start, size); + frames.push_back(compressor.Compress(image)); + } + StartMessage start_msg; + x.FillMessage(start_msg); + puller->Put(ImagePullerOutput{.cbor = std::make_shared(start_msg)}); + } + + void Frames(int64_t first, int64_t count) { + for (int64_t i = first; i < first + count; i++) { + DataMessage message; + message.number = i; + message.image = CompressedImage(frames[i].data(), frames[i].size(), width, height, + CompressedImageMode::Int16, CompressionAlgorithm::BSHUF_LZ4); + puller->Put(ImagePullerOutput{.cbor = std::make_shared(message)}); + } + } + + void End() { + puller->Put(ImagePullerOutput{.cbor = std::make_shared(EndMessage{})}); + } + }; + + void WaitForStatus(JFJochReceiverService &service, const std::function &what) { + const auto give_up = std::chrono::steady_clock::now() + std::chrono::minutes(5); + while (!what(service.GetRugnuxStatus())) { + REQUIRE(std::chrono::steady_clock::now() < give_up); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } + + bool HasNote(const RugnuxLiveSnapshot &s, const std::string &text) { + for (const auto &step: s.steps) + for (const auto ¬e: step.notes) + if (note.find(text) != std::string::npos) + return true; + return false; + } +} + +TEST_CASE("JFJochReceiverRugnux_Stills", "[JFJochReceiverRugnux]") { + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + const int64_t images = 12; + DiffractionExperiment x = StillsExperiment(images); + PixelMask mask(x); + + Stream stream(x, images); + stream.Frames(0, images); + stream.End(); + + RecordingPusher pusher; + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher); + service.NumThreads(4); + service.Indexing(x.GetIndexingSettings()); + service.RugnuxLive(true, nullptr, 4, 60); + // Integration in live stills is rugnux's own, whatever the broker's spot-finding settings say. + SpotFindingSettings spot_finding; + spot_finding.quick_integration = false; + service.SetSpotFindingSettings(spot_finding); + + service.Start(x, mask, nullptr, stream.puller); + const auto out = service.Stop(); + + CHECK(out.efficiency == 1.0f); + CHECK(!out.status.cancelled); + CHECK(out.status.images_collected == images); + CHECK(out.status.images_sent == images); + REQUIRE(out.status.indexing_rate); + CHECK(out.status.indexing_rate.value() > 0.5f); + CHECK(out.scan_result.images.size() == images); + + // The writer sees the start message before the frames, the end message last. + CHECK(pusher.Events() == std::vector{"start", "image", "end"}); + auto sent = pusher.Images(); + std::sort(sent.begin(), sent.end()); + REQUIRE(sent.size() == images); + CHECK(sent.front() == 0); + CHECK(sent.back() == images - 1); + + // The merged data travel in the end message, with the geometry the frames were analysed at. + const auto end = pusher.End(); + REQUIRE(end); + CHECK(end->merged.has_value()); + CHECK(end->unit_cell.has_value()); + CHECK(end->refined_beam_center_x.has_value()); + CHECK(end->refined_detector_distance_mm.has_value()); + REQUIRE(pusher.Start()); + CHECK(pusher.Start()->beam_center_x == Catch::Approx(end->refined_beam_center_x.value())); + + const auto live = service.GetRugnuxStatus(); + CHECK(live.state == RugnuxLiveState::Done); + CHECK(live.cut.empty()); + CHECK(HasNote(live, "Merge of 50% of the images (provisional)")); + CHECK(HasNote(live, "Merge of 75% of the images (provisional)")); + CHECK(HasNote(live, "Merge of the collection")); + REQUIRE(live.stills); + CHECK(live.stills->prepass == RugnuxLivePrePass::Done); + CHECK(live.stills->images_analysed == images); + CHECK(live.stills->images_indexed > images / 2); + REQUIRE(live.last_image_to_written_s); + CHECK(!live.space_group.empty()); + + REQUIRE(!service.GetProgress().has_value()); +} + +TEST_CASE("JFJochReceiverRugnux_Stills_CancelDuringPrePass", "[JFJochReceiverRugnux]") { + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + const int64_t images = 8; + DiffractionExperiment x = StillsExperiment(images); + PixelMask mask(x); + Stream stream(x, images); + + RecordingPusher pusher; + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher); + service.NumThreads(4); + service.Indexing(x.GetIndexingSettings()); + service.RugnuxLive(true, nullptr, 4, 60); + + int64_t given = 0; + SECTION("while it holds frames") { + given = 3; + stream.Frames(0, given); + service.Start(x, mask, nullptr, stream.puller); + WaitForStatus(service, [&](const RugnuxLiveSnapshot &s) { + return s.stills && s.stills->images_held == given; + }); + } + SECTION("while it runs") { + given = images; + stream.Frames(0, given); + service.Start(x, mask, nullptr, stream.puller); + WaitForStatus(service, [&](const RugnuxLiveSnapshot &s) { + return s.stills && s.stills->prepass == RugnuxLivePrePass::Running; + }); + } + service.Cancel(false); + const auto out = service.Stop(); + + // The frames that came are still analysed and written, and the collection ends properly. + CHECK(out.status.cancelled); + CHECK(out.status.images_sent == given); + CHECK(pusher.Events() == std::vector{"start", "image", "end"}); + const auto live = service.GetRugnuxStatus(); + CHECK(live.state == RugnuxLiveState::Done); + REQUIRE(live.stills); + CHECK(live.stills->images_analysed == given); + REQUIRE(!service.GetProgress().has_value()); +} + +TEST_CASE("JFJochReceiverRugnux_Stills_CancelDuringRun", "[JFJochReceiverRugnux]") { + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + const int64_t images = 40; + DiffractionExperiment x = StillsExperiment(images); + PixelMask mask(x); + Stream stream(x, images); + + // Twelve slots: the pre-pass ends on six frames, and the analysis starts with most of the run to come. + const size_t buffer_MiB = 12 * x.GetImageBufferLocationSize() / (1024 * 1024) + 1; + const int64_t given = 8; + stream.Frames(0, given); + + RecordingPusher pusher; + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher, buffer_MiB); + service.NumThreads(2); + service.Indexing(x.GetIndexingSettings()); + // A model only so that there is a model step for the cancel to cut. + service.RugnuxLive(true, std::make_shared(), 4, 60); + + service.Start(x, mask, nullptr, stream.puller); + WaitForStatus(service, [](const RugnuxLiveSnapshot &s) { + return s.stills && s.stills->prepass == RugnuxLivePrePass::Done && s.stills->images_analysed > 0; + }); + service.Cancel(false); + const auto out = service.Stop(); + + CHECK(out.status.cancelled); + CHECK(out.status.images_sent == given); + CHECK(pusher.Events() == std::vector{"start", "image", "end"}); + const auto live = service.GetRugnuxStatus(); + CHECK(live.state == RugnuxLiveState::Done); + CHECK(live.cut == "cancel"); + REQUIRE(!service.GetProgress().has_value()); +} + +TEST_CASE("JFJochReceiverRugnux_Stills_ShortRun", "[JFJochReceiverRugnux]") { + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + DiffractionExperiment x = StillsExperiment(8); + PixelMask mask(x); + Stream stream(x, 3); + stream.Frames(0, 3); + stream.End(); + + RecordingPusher pusher; + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher); + service.NumThreads(4); + service.Indexing(x.GetIndexingSettings()); + service.RugnuxLive(true, nullptr, 4, 60); + + service.Start(x, mask, nullptr, stream.puller); + const auto out = service.Stop(); + + // The pre-pass runs on what came. + CHECK(out.efficiency == Catch::Approx(3.0 / 8.0)); + CHECK(!out.status.cancelled); + CHECK(out.status.images_sent == 3); + CHECK(pusher.Events() == std::vector{"start", "image", "end"}); + const auto live = service.GetRugnuxStatus(); + CHECK(live.state == RugnuxLiveState::Done); + REQUIRE(live.stills); + CHECK(live.stills->images_analysed == 3); +} + +TEST_CASE("JFJochReceiverRugnux_Stills_FramesBeyondTheCollection", "[JFJochReceiverRugnux]") { + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + DiffractionExperiment x = StillsExperiment(3); + PixelMask mask(x); + Stream stream(x, 5); + stream.Frames(0, 5); + stream.End(); + + RecordingPusher pusher; + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher); + service.NumThreads(4); + service.Indexing(x.GetIndexingSettings()); + service.RugnuxLive(true, nullptr, 4, 60); + + service.Start(x, mask, nullptr, stream.puller); + const auto out = service.Stop(); + + CHECK(out.status.images_collected == 3); + CHECK(out.status.images_sent == 3); +} + +TEST_CASE("JFJochReceiverRugnux_Stills_NoFrames", "[JFJochReceiverRugnux]") { + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + DiffractionExperiment x = StillsExperiment(8); + PixelMask mask(x); + Stream stream(x, 0); + stream.End(); + + RecordingPusher pusher; + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher); + service.NumThreads(4); + service.Indexing(x.GetIndexingSettings()); + service.RugnuxLive(true, nullptr, 4, 60); + + service.Start(x, mask, nullptr, stream.puller); + const auto out = service.Stop(); + + CHECK(out.status.images_sent == 0); + CHECK(pusher.Events() == std::vector{"start", "end"}); + CHECK(service.GetRugnuxStatus().state == RugnuxLiveState::Done); +} + +TEST_CASE("JFJochReceiverRugnux_Stills_NoIndexableFrames", "[JFJochReceiverRugnux]") { + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + const int64_t images = 6; + DiffractionExperiment x = StillsExperiment(images); + PixelMask mask(x); + Stream stream(x, images, true); + stream.Frames(0, images); + stream.End(); + + RecordingPusher pusher; + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher); + service.NumThreads(4); + service.Indexing(x.GetIndexingSettings()); + service.RugnuxLive(true, nullptr, 4, 60); + + service.Start(x, mask, nullptr, stream.puller); + const auto out = service.Stop(); + + // Written, with no merge, at the geometry in the header. + CHECK(!out.status.cancelled); + CHECK(out.status.images_sent == images); + CHECK(pusher.Events() == std::vector{"start", "image", "end"}); + REQUIRE(pusher.End()); + CHECK(!pusher.End()->merged.has_value()); + CHECK(pusher.End()->refined_beam_center_x == Catch::Approx(x.GetBeamX_pxl())); + const auto live = service.GetRugnuxStatus(); + CHECK(live.state == RugnuxLiveState::Done); + CHECK(HasNote(live, "could not refine the geometry")); + REQUIRE(live.stills); + CHECK(live.stills->images_analysed == images); + CHECK(live.stills->images_indexed == 0); +} + +TEST_CASE("JFJochReceiverRugnux_Stills_WriterDown", "[JFJochReceiverRugnux]") { + // The start message goes out after the pre-pass: where it fails, the collection ends with the error. + class NoWriter : public RecordingPusher { + public: + bool down = true; + void StartDataCollection(StartMessage &message) override { + if (down) + throw JFJochException(JFJochExceptionCategory::ZeroMQ, "no writer"); + RecordingPusher::StartDataCollection(message); + } + }; + + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + const int64_t images = 4; + DiffractionExperiment x = StillsExperiment(images); + PixelMask mask(x); + Stream stream(x, images); + stream.Frames(0, images); + stream.End(); + + NoWriter pusher; + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher); + service.NumThreads(4); + service.Indexing(x.GetIndexingSettings()); + service.RugnuxLive(true, nullptr, 4, 60); + + service.Start(x, mask, nullptr, stream.puller); + REQUIRE_THROWS_AS(service.Stop(), JFJochException); + CHECK(pusher.Events().empty()); + CHECK(service.GetRugnuxStatus().state == RugnuxLiveState::Failed); + + // And the next one runs. + pusher.down = false; + Stream next(x, images); + next.Frames(0, images); + next.End(); + service.Start(x, mask, nullptr, next.puller); + CHECK(service.Stop().status.images_sent == images); + CHECK(pusher.Events() == std::vector{"start", "image", "end"}); +} + +TEST_CASE("JFJochReceiverRugnux_Stills_ZMQWriter", "[JFJochReceiverRugnux]") { + // As the removed GPU-stills test of the FPGA receiver: five copies of one frame, through ZeroMQ to the writer. + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + const int64_t images = 5; + DiffractionExperiment x = StillsExperiment(images); + x.FilePrefix("rugnux_stills_zmq").SetFileWriterFormat(FileWriterFormat::NXmxVDS).OverwriteExistingFiles(true); + PixelMask mask(x); + + Stream stream(x, images, false, 4); + stream.Frames(0, images); + stream.End(); + + ZMQStream2Pusher pusher({"ipc://*"}); + pusher.WriterNotificationSocket("ipc://*"); + + ZMQImagePuller puller(pusher.GetAddress()[0]); + StreamWriter writer(logger, puller); + auto writer_future = std::async(std::launch::async, &StreamWriter::Run, &writer); + + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher); + service.NumThreads(4); + service.Indexing(x.GetIndexingSettings()); + service.RugnuxLive(true, nullptr, 4, 60); + + REQUIRE(!service.GetProgress().has_value()); + + SpotFindingSettings settings = DiffractionExperiment::DefaultDataProcessingSettings(); + settings.signal_to_noise_threshold = 2.5; + settings.photon_count_threshold = 5; + settings.min_pix_per_spot = 1; + settings.max_pix_per_spot = 200; + settings.high_resolution_limit = 2.0; + settings.low_resolution_limit = 50.0; + service.SetSpotFindingSettings(settings); + + service.Start(x, mask, nullptr, stream.puller); + const auto out = service.Stop(); + + CHECK(out.efficiency == 1.0f); + REQUIRE(out.status.indexing_rate); + CHECK(out.status.indexing_rate.value() == 1.0f); + CHECK(out.status.images_sent == images); + CHECK(!out.status.cancelled); + + // Done once everything is written, with the latency from the last image to then. + const auto live = service.GetRugnuxStatus(); + CHECK(live.state == RugnuxLiveState::Done); + REQUIRE(live.last_image_to_written_s); + REQUIRE(live.last_image_s); + CHECK(live.elapsed_s >= live.last_image_s.value() + live.last_image_to_written_s.value() - 1e-6); + + REQUIRE(!service.GetProgress().has_value()); + REQUIRE_NOTHROW(writer_future.get()); +} + +TEST_CASE("JFJochReceiverRugnux_Stills_ZMQWriterDown", "[JFJochReceiverRugnux]") { + // With no writer listening the collection ends with the error, and the next one runs. + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + const int64_t images = 5; + DiffractionExperiment x = StillsExperiment(images); + x.FilePrefix("rugnux_stills_zmq_writer_down").SetFileWriterFormat(FileWriterFormat::NXmxVDS) + .OverwriteExistingFiles(true); + PixelMask mask(x); + + ZMQStream2Pusher pusher({"ipc://*"}); + pusher.WriterNotificationSocket("ipc://*"); + + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher); + service.NumThreads(4); + service.Indexing(x.GetIndexingSettings()); + service.RugnuxLive(true, nullptr, 4, 60); + + Stream stream(x, images, false, 4); + stream.Frames(0, images); + stream.End(); + service.Start(x, mask, nullptr, stream.puller); + REQUIRE_THROWS_AS(service.Stop(), JFJochException); + REQUIRE(!service.GetProgress().has_value()); + + ZMQImagePuller puller(pusher.GetAddress()[0]); + StreamWriter writer(logger, puller); + auto writer_future = std::async(std::launch::async, &StreamWriter::Run, &writer); + + Stream next(x, images, false, 4); + next.Frames(0, images); + next.End(); + service.Start(x, mask, nullptr, next.puller); + const auto out = service.Stop(); + CHECK(out.status.images_sent == images); + CHECK(!out.status.cancelled); + REQUIRE_NOTHROW(writer_future.get()); +} + +TEST_CASE("JFJochReceiverRugnux_Stills_BufferFullDuringPrePass", "[JFJochReceiverRugnux]") { + // Forty frames, all there before the pre-pass ends, and twelve slots in the image buffer. + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + const int64_t images = 40; + DiffractionExperiment x = StillsExperiment(images); + PixelMask mask(x); + Stream stream(x, images); + stream.Frames(0, images); + stream.End(); + + const size_t buffer_MiB = 12 * x.GetImageBufferLocationSize() / (1024 * 1024) + 1; + + RecordingPusher pusher; + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher, buffer_MiB); + service.NumThreads(2); + service.Indexing(x.GetIndexingSettings()); + service.RugnuxLive(true, nullptr, 4, 60); + + service.Start(x, mask, nullptr, stream.puller); + const auto out = service.Stop(); + + // No deadlock, and the end message goes out. + CHECK(!out.status.cancelled); + CHECK(pusher.Events() == std::vector{"start", "image", "end"}); + + // A frame that finds no free slot is not written, and the collection says so. + CHECK(out.status.images_collected == images); + CHECK(out.efficiency == 1.0f); + CHECK(out.writer_queue_full_warning == (out.status.images_sent < images)); + CHECK(out.scan_result.images.size() == out.status.images_sent); + REQUIRE(pusher.End()); + CHECK(pusher.End()->images_collected_count == images); + CHECK(pusher.End()->images_sent_to_write_count == out.status.images_sent); + CHECK(pusher.Images().size() == out.status.images_sent); + + const auto live = service.GetRugnuxStatus(); + CHECK(live.state == RugnuxLiveState::Done); + REQUIRE(live.stills); + CHECK(live.stills->images_analysed == out.status.images_sent); +} + +TEST_CASE("JFJochReceiverRugnux_Rotation", "[JFJochReceiverRugnux]") { + // A scan stays with rugnux's own run: the start message goes out at once, and there are no stills results. + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + const int64_t images = 5; + DiffractionExperiment x = StillsExperiment(images); + x.Goniometer(GoniometerAxis("omega", 0.0f, 0.1f, {1, 0, 0}, {})); + PixelMask mask(x); + Stream stream(x, images); + stream.Frames(0, images); + stream.End(); + + RecordingPusher pusher; + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher); + service.NumThreads(4); + service.Indexing(x.GetIndexingSettings()); + service.RugnuxLive(true, nullptr, 4, 60); + + service.Start(x, mask, nullptr, stream.puller); + const auto out = service.Stop(); + + CHECK(out.status.images_sent == images); + CHECK(pusher.Events() == std::vector{"start", "image", "end"}); + const auto live = service.GetRugnuxStatus(); + CHECK(!live.stills); + CHECK(live.images_received == images); + CHECK(live.state != RugnuxLiveState::Idle); + CHECK(live.state != RugnuxLiveState::Running); +} + +TEST_CASE("JFJochReceiverRugnux_Rotation_DoesNotFit", "[JFJochReceiverRugnux]") { + // A scan the image buffer cannot hold is analysed image by image, and the live status says why rugnux did not run. + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + const int64_t images = 5; + DiffractionExperiment x = StillsExperiment(images); + x.Goniometer(GoniometerAxis("omega", 0.0f, 0.1f, {1, 0, 0}, {})); + PixelMask mask(x); + Stream stream(x, images); + stream.Frames(0, images); + stream.End(); + + // Three slots + const size_t buffer_MiB = (3 * x.GetImageBufferLocationSize()) / (1024 * 1024) + 1; + REQUIRE(buffer_MiB * 1024 * 1024 / x.GetImageBufferLocationSize() < images); + + RecordingPusher pusher; + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher, buffer_MiB); + service.NumThreads(4); + service.Indexing(x.GetIndexingSettings()); + service.RugnuxLive(true, nullptr, 4, 60); + + service.Start(x, mask, nullptr, stream.puller); + const auto out = service.Stop(); + + CHECK(out.status.images_collected == images); + CHECK(out.scan_result.images.size() == images); + CHECK(pusher.Events() == std::vector{"start", "image", "end"}); + CHECK(out.analysis_warning.find("image buffer") != std::string::npos); + const auto live = service.GetRugnuxStatus(); + CHECK(live.state == RugnuxLiveState::Skipped); + CHECK(live.message.find("image buffer") != std::string::npos); +} + +TEST_CASE("JFJochReceiverRugnux_Rotation_Cancel", "[JFJochReceiverRugnux]") { + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + const int64_t images = 5; + DiffractionExperiment x = StillsExperiment(images); + x.Goniometer(GoniometerAxis("omega", 0.0f, 0.1f, {1, 0, 0}, {})); + PixelMask mask(x); + + RecordingPusher pusher; + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher); + service.NumThreads(4); + service.Indexing(x.GetIndexingSettings()); + service.RugnuxLive(true, nullptr, 4, 60); + + SECTION("Before the start message") { + service.Start(x, mask, nullptr, std::make_shared()); + service.Cancel(false); + const auto out = service.Stop(); + CHECK(out.status.cancelled); + CHECK(pusher.Events().empty()); + } + SECTION("Detector finished, no start message") { + service.Start(x, mask, nullptr, std::make_shared()); + service.Cancel(true); + const auto out = service.Stop(); + CHECK(out.status.cancelled); + CHECK(pusher.Events().empty()); + } + SECTION("During the collection") { + Stream stream(x, images); + stream.Frames(0, 2); + service.Start(x, mask, nullptr, stream.puller); + WaitForStatus(service, [](const RugnuxLiveSnapshot &s) { return s.images_received == 2; }); + service.Cancel(false); + const auto out = service.Stop(); + CHECK(out.status.cancelled); + CHECK(out.status.images_sent == 2); + CHECK(pusher.Events() == std::vector{"start", "image", "end"}); + const auto live = service.GetRugnuxStatus(); + CHECK(live.cut == "cancel"); + CHECK(live.state != RugnuxLiveState::Running); + // A cancelled collection, not a failed analysis, and counted in the images that came + CHECK(live.verdict == "CANCELLED"); + CHECK(live.verdict_text.find("after 2 images") != std::string::npos); + for (const auto &step: live.steps) { + const size_t pos = step.title.rfind(", "); + if (pos != std::string::npos && step.title.ends_with(" images")) + CHECK(std::stoll(step.title.substr(pos + 2)) <= 2); + } + } + SECTION("A silent cancel lets the collection finish") { + Stream stream(x, images); + stream.Frames(0, 2); + service.Start(x, mask, nullptr, stream.puller); + WaitForStatus(service, [](const RugnuxLiveSnapshot &s) { return s.images_received == 2; }); + service.Cancel(true); + stream.Frames(2, images - 2); + stream.End(); + const auto out = service.Stop(); + CHECK(!out.status.cancelled); + CHECK(out.status.images_sent == images); + CHECK(pusher.Events() == std::vector{"start", "image", "end"}); + CHECK(service.GetRugnuxStatus().cut.empty()); + } +} + +// A leaf joins its threads in its own destructor, with no one having asked for the end of the collection. +TEST_CASE("JFJochReceiverRugnux_DestroyedMidRun", "[JFJochReceiverRugnux]") { + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + const int64_t images = 5; + DiffractionExperiment x = StillsExperiment(images); + PixelMask mask(x); + Stream stream(x, images); + stream.Frames(0, 2); + + RecordingPusher pusher; + JFJochReceiverCurrentStatus status; + JFJochReceiverPlots plots; + RugnuxLiveStatus live; + ImageBuffer buffer(512 * 1024 * 1024); + + SECTION("JFJochReceiverLite") { + JFJochReceiverLite receiver(x, mask, *stream.puller, pusher, logger, 4, DiffractionExperiment::DefaultDataProcessingSettings(), + status, plots, buffer); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + SECTION("JFJochReceiverDarkMask") { + DiffractionExperiment dark = x; + dark.Mode(DetectorMode::DarkMask); + JFJochReceiverDarkMask receiver(dark, mask, *stream.puller, pusher, logger, 4, DiffractionExperiment::DefaultDataProcessingSettings(), + status, plots, buffer); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + SECTION("JFJochReceiverRugnuxRotation") { + DiffractionExperiment rotation = x; + rotation.Goniometer(GoniometerAxis("omega", 0.0f, 0.1f, {1, 0, 0}, {})); + JFJochReceiverRugnuxRotation receiver(rotation, mask, *stream.puller, pusher, logger, 4, 4, nullptr, 60, live, + DiffractionExperiment::DefaultDataProcessingSettings(), status, plots, buffer); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + SECTION("JFJochReceiverRugnuxStills") { + JFJochReceiverRugnuxStills receiver(x, mask, *stream.puller, pusher, logger, 4, nullptr, 60, live, + DiffractionExperiment::DefaultDataProcessingSettings(), status, plots, buffer); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + SUCCEED("the destructor joined the threads"); +} + +TEST_CASE("JFJochReceiverRugnux_DarkMaskStaysOut", "[JFJochReceiverRugnux]") { + // The mask sequence of a later POST /pedestal, with rugnux on: the state machine's experiment still has the + // last /start's goniometer, or none. + Logger logger(Catch::getResultCapture().getCurrentTestName()); + RegisterHDF5Filter(); + + DiffractionExperiment x(DetDECTRIS(2068, 2164, "Test", {})); + SECTION("after a serial collection") {} + SECTION("after a rotation collection") { + x.Goniometer(GoniometerAxis("omega", 0.0f, 0.1f, {1, 0, 0}, {})); + } + DarkMaskSettings mask_settings; + mask_settings.NumberOfFrames(10).MaxCounts(1).MaxFramesWithCounts(5); + x.Mode(DetectorMode::DarkMask).ImportDarkMaskSettings(mask_settings); + PixelMask mask(x); + + auto puller = std::make_shared(); + StartMessage start_msg; + x.FillMessage(start_msg); + puller->Put(ImagePullerOutput{.cbor = std::make_shared(start_msg)}); + + std::vector image(x.GetPixelsNum(), 0); + image[2] = 2; + for (int i = 0; i < x.GetFrameNum(); i++) { + DataMessage message; + message.image = CompressedImage(image, x.GetXPixelsNum(), x.GetYPixelsNum()); + message.number = i; + puller->Put(ImagePullerOutput{.cbor = std::make_shared(message)}); + } + puller->Put(ImagePullerOutput{.cbor = std::make_shared(EndMessage{})}); + + RecordingPusher pusher; + AcquisitionDeviceGroup group; + JFJochReceiverService service(group, logger, pusher); + service.NumThreads(4); + service.RugnuxLive(true, nullptr, 4, 60); + + service.Start(x, mask, nullptr, puller); + const auto out = service.Stop(); + + REQUIRE(out.dark_mask_result.size() == x.GetPixelsNum()); + CHECK(out.dark_mask_result[2] != 0); + CHECK(out.dark_mask_result[0] == 0); + CHECK(out.status.images_sent == 0); + CHECK(!service.GetRugnuxStatus().stills); +} diff --git a/tests/RugnuxLiveStatusTest.cpp b/tests/RugnuxLiveStatusTest.cpp index b57e19dca..cdb258357 100644 --- a/tests/RugnuxLiveStatusTest.cpp +++ b/tests/RugnuxLiveStatusTest.cpp @@ -6,7 +6,8 @@ #include #include "../receiver/RugnuxLiveStatus.h" -#include "../receiver/JFJochReceiverRugnux.h" +#include "../receiver/RugnuxLiveStatus.h" +#include "../rugnux/Rugnux.h" #include "../broker/OpenAPIConvert.h" #include "../image_puller/TestImagePuller.h" @@ -321,3 +322,32 @@ TEST_CASE("RugnuxLiveModel_NonFiniteScores", "[RugnuxLive]") { CHECK(!j["model"]["refinement"]["blobs"][0].contains("nearest_atom_distance_A")); CHECK(j.dump().find("null") == std::string::npos); } + +TEST_CASE("RugnuxLiveStatus_StepsAfterCancel", "[RugnuxLive]") { + // A step covers the images that came: after a cancel, not the ones it was planned for + const auto start = std::chrono::steady_clock::now(); + RugnuxLiveStatus status; + status.Start(1000, 10.0, start); + status.Step("Integration (geometry pre-pass), 600 images"); + for (int i = 0; i < 384; i++) + status.ImageReceived(start + i * 10ms); + status.Step("Pre-scan"); + status.Cut("cancel"); + status.Step("Integration, 1000 images"); + status.Step("Integration, 200 images"); + + const auto s = status.Get(); + REQUIRE(s.steps.size() == 4); + CHECK(s.steps[0].title == "Integration (geometry pre-pass), 384 images"); + CHECK(s.steps[1].title == "Pre-scan"); + CHECK(s.steps[2].title == "Integration, 384 images"); + CHECK(s.steps[3].title == "Integration, 200 images"); +} + +TEST_CASE("RugnuxLiveStatus_StepsAfterDeadline", "[RugnuxLive]") { + RugnuxLiveStatus status; + status.Start(1000, 10.0, std::chrono::steady_clock::now()); + status.Step("Integration, 1000 images"); + status.Cut("deadline"); + CHECK(status.Get().steps[0].title == "Integration, 1000 images"); +} diff --git a/tests/RugnuxResultFilesTest.cpp b/tests/RugnuxResultFilesTest.cpp index 13e66436b..b61576637 100644 --- a/tests/RugnuxResultFilesTest.cpp +++ b/tests/RugnuxResultFilesTest.cpp @@ -134,7 +134,7 @@ TEST_CASE("RugnuxResultFiles: rugnux's outputs -> END blocks -> CBOR -> writer, provenance.command_line = "rugnux -N 8 input_master.h5"; provenance.input_file = "/some/where/input_master.h5"; // The files as rugnux writes them from its outputs (RugnuxOutputFiles.cpp), and the same outputs as the - // broker puts them in the END message (JFJochReceiverRugnux::AddToEndMessage). + // broker puts them in the END message (JFJochReceiverRugnuxRotation::AddToEndMessage). EndMessage end{}; end.max_image_number = 100; end.merged = MergedMtzTable(reflections, TEST_CELL, experiment, provenance); @@ -369,7 +369,7 @@ TEST_CASE("RugnuxResultFiles: the refinement's model, maps and blobs reach the w .polar_contacts = 3, .metal_contacts = 1, .anomalous_sigma = 1.25}; { std::ofstream f(original + "_blobs.json"); f << DensityBlobsJson(DensityBlobsToMessage(blobs)); } - // As the broker puts them in the END message (JFJochReceiverRugnux::AddToEndMessage). + // As the broker puts them in the END message (JFJochReceiverRugnuxRotation::AddToEndMessage). EndMessage end{}; end.refined_model = PlacedModelFromStructure(TestModel(), TEST_CELL, sg.xhm()); end.refined_map_coefficients = maps; diff --git a/tests/StillsGPUAnalysisTest.cpp b/tests/StillsGPUAnalysisTest.cpp new file mode 100644 index 000000000..23c44e234 --- /dev/null +++ b/tests/StillsGPUAnalysisTest.cpp @@ -0,0 +1,192 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include + +#include +#include +#include + +#include "../common/ImageBuffer.h" +#include "../common/time_utc.h" +#include "../image_analysis/structure_refinement/RugnuxModel.h" +#include "../receiver/StillsGPUAnalysis.h" + +namespace { + struct TestSink : public StillsSink { + std::function ready = [](const StillsSetup &) {}; + std::function analysed = [](DataMessage &) {}; + std::function failed = [] {}; + + void StillsReady(const StillsSetup &setup) override { ready(setup); } + void SendAnalysedImage(DataMessage &message, const AzimuthalIntegrationProfile &) override { analysed(message); } + SpotFindingSettings AnalysisSettings() override { return DiffractionExperiment::DefaultDataProcessingSettings(); } + void StillsSetupFailed(const JFJochException &) override { failed(); } + }; + + DiffractionExperiment StillsExperiment() { + DiffractionExperiment x(DetJF(1)); + x.ImagesPerTrigger(StillsGPUAnalysis::PREPASS_IMAGES + 200).NumTriggers(1).DetectorDistance_mm(100).BeamX_pxl(500).BeamY_pxl(250) + .IncidentEnergy_keV(12.4); + return x; + } + + // A frame after the pre-pass's sample (numbers from PREPASS_IMAGES on are never in it), in a slot of `buffer`. + std::shared_ptr Frame(ImageBuffer &buffer, int64_t number) { + auto held = std::make_shared(); + held->slot = buffer.GetImageSlot(); + REQUIRE(held->slot != nullptr); + held->message.number = StillsGPUAnalysis::PREPASS_IMAGES + number; + held->message.image_collection_efficiency = 1.0f; + return held; + } + + StartMessage Start(const DiffractionExperiment &x) { + StartMessage start; + x.FillMessage(start); + return start; + } +} + +TEST_CASE("StillsGPUAnalysis_StartFailureReleasesThreads", "[StillsGPUAnalysis]") { + Logger logger(Catch::getResultCapture().getCurrentTestName()); + const DiffractionExperiment x = StillsExperiment(); + const PixelMask mask(x); + ImageBuffer buffer(64 * 1024 * 1024); + buffer.StartMeasurement(x); + RugnuxLiveStatus status; + + std::atomic failed = 0, sunk = 0; + TestSink sink; + sink.ready = [](const StillsSetup &) { + throw JFJochException(JFJochExceptionCategory::ZeroMQ, "Timeout on pushing start message"); + }; + sink.analysed = [&](DataMessage &) { ++sunk; }; + sink.failed = [&] { ++failed; }; + { + StillsGPUAnalysis gpu(x, mask, Start(x), nullptr, 4, 100, logger, sink, status); + for (int i = 0; i < 3; i++) + gpu.Hold(Frame(buffer, i)); + REQUIRE_THROWS_AS(gpu.Finish(), JFJochException); + } + CHECK(failed == 1); + CHECK(sunk == 0); + CHECK(status.Get().state == RugnuxLiveState::Failed); + // Every slot the frames were held in is back. + CHECK(buffer.GetAvailSlots() == buffer.GetStatus().total_slots); +} + +TEST_CASE("StillsGPUAnalysis_HoldIsBounded", "[StillsGPUAnalysis]") { + Logger logger(Catch::getResultCapture().getCurrentTestName()); + const DiffractionExperiment x = StillsExperiment(); + const PixelMask mask(x); + ImageBuffer buffer(64 * 1024 * 1024); + buffer.StartMeasurement(x); + REQUIRE(buffer.GetStatus().total_slots >= 4); + + RugnuxLiveStatus status; + TestSink sink; + sink.ready = [](const StillsSetup &) { throw JFJochException(JFJochExceptionCategory::ZeroMQ, "no writer"); }; + StillsGPUAnalysis gpu(x, mask, Start(x), nullptr, 2, 3, logger, sink, status); + // Past what the pre-pass holds, frames are still taken: only the image buffer bounds them. + for (int i = 0; i < 4; i++) + gpu.Hold(Frame(buffer, i)); + CHECK(buffer.GetStatus().preparation_slots == 4); + CHECK(buffer.GetImageSlot(buffer.GetStatus().available_slots) == nullptr); + REQUIRE_THROWS(gpu.Finish()); + CHECK(buffer.GetStatus().preparation_slots == 0); +} + +TEST_CASE("StillsGPUAnalysis_FailingFrameDoesNotStopAnalysis", "[StillsGPUAnalysis]") { + Logger logger(Catch::getResultCapture().getCurrentTestName()); + const DiffractionExperiment x = StillsExperiment(); + const PixelMask mask(x); + ImageBuffer buffer(64 * 1024 * 1024); + buffer.StartMeasurement(x); + RugnuxLiveStatus status; + + std::atomic sunk = 0; + TestSink sink; + sink.analysed = [&](DataMessage &m) { + ++sunk; + if (m.number == StillsGPUAnalysis::PREPASS_IMAGES) + throw std::runtime_error("not a JFJochException"); + }; + StillsGPUAnalysis gpu(x, mask, Start(x), nullptr, 1, 100, logger, sink, status); + // The frames carry no image, so their analysis fails too - they are passed on all the same. + for (int i = 0; i < 3; i++) + gpu.Hold(Frame(buffer, i)); + REQUIRE_NOTHROW(gpu.Finish()); + CHECK(sunk == 3); + // The outcome waits for everything to be written, and comes after the latency. + CHECK(status.Get().state == RugnuxLiveState::Running); + CHECK(!status.Get().last_image_to_written_s); + status.Latency(SecondsSince(status.LastImage().value())); + gpu.ReportOutcome(); + const auto s = status.Get(); + CHECK(s.state == RugnuxLiveState::Failed); + CHECK(!s.message.empty()); + REQUIRE(s.last_image_to_written_s); + REQUIRE(s.last_image_s); + CHECK(s.elapsed_s >= s.last_image_s.value() + s.last_image_to_written_s.value() - 1e-6); + REQUIRE(!s.steps.empty()); + CHECK(s.steps.back().title == "Writing"); + CHECK(s.steps.back().duration_s); + CHECK(buffer.GetStatus().preparation_slots == 0); +} + +TEST_CASE("StillsGPUAnalysis_CancelCutsTheModelStep", "[StillsGPUAnalysis]") { + Logger logger(Catch::getResultCapture().getCurrentTestName()); + const DiffractionExperiment x = StillsExperiment(); + const PixelMask mask(x); + TestSink sink; + const auto run = [&](RugnuxLiveStatus &status, std::shared_ptr model) { + return std::make_unique(x, mask, Start(x), nullptr, 1, 100, logger, sink, status, true, + std::move(model), 60.0); + }; + + RugnuxLiveStatus during; + auto a = run(during, std::make_shared()); + a->Cut("cancel"); + CHECK(during.Get().cut == "cancel"); + REQUIRE_NOTHROW(a->Finish()); + + // Nothing is left to cut once the model step has ended, and nothing runs without a model. + RugnuxLiveStatus after; + auto b = run(after, std::make_shared()); + b->Finish(); + b->Cut("cancel"); + CHECK(after.Get().cut.empty()); + + RugnuxLiveStatus no_model; + auto c = run(no_model, nullptr); + c->Cut("cancel"); + CHECK(no_model.Get().cut.empty()); + c->Finish(); +} + +TEST_CASE("StillsGPUAnalysis_FramesDuringThePrePassAreAnalysed", "[StillsGPUAnalysis]") { + Logger logger(Catch::getResultCapture().getCurrentTestName()); + const DiffractionExperiment x = StillsExperiment(); + const PixelMask mask(x); + ImageBuffer buffer(64 * 1024 * 1024); + buffer.StartMeasurement(x); + const int64_t frames = 3 + 5; + REQUIRE(buffer.GetStatus().total_slots >= frames); + + std::atomic sunk = 0; + std::latch offered(1); + RugnuxLiveStatus status; + TestSink sink; + // The pre-pass is not over until every frame has been offered. + sink.ready = [&](const StillsSetup &) { offered.wait(); }; + sink.analysed = [&](DataMessage &) { ++sunk; }; + StillsGPUAnalysis gpu(x, mask, Start(x), nullptr, 2, 3, logger, sink, status); + CHECK(status.Get().stills->images_needed == 3); + for (int i = 0; i < frames; i++) + gpu.Hold(Frame(buffer, i)); + offered.count_down(); + REQUIRE_NOTHROW(gpu.Finish()); + CHECK(sunk == frames); + CHECK(buffer.GetStatus().preparation_slots == 0); +} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 18abcf779..a2cc71c45 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -48,7 +48,7 @@ IF(NOT JFJOCH_PORTABLE_ONLY) # Not installed: replays a stored serial dataset through the GPU analysis of converted JUNGFRAU frames. ADD_EXECUTABLE(jfjoch_stills_gpu_replay jfjoch_stills_gpu_replay.cpp) - TARGET_LINK_LIBRARIES(jfjoch_stills_gpu_replay JFJochReceiver JFJochWriter JFJochReader) + TARGET_LINK_LIBRARIES(jfjoch_stills_gpu_replay JFJochReceiver JFJochWriter JFJochReader JFJochModelWriter JFJochRugnuxFiles) ADD_EXECUTABLE(jfjoch_pcie_status jfjoch_pcie_status.cpp) TARGET_LINK_LIBRARIES(jfjoch_pcie_status JFJochDevice JFJochCommon) diff --git a/tools/jfjoch_stills_gpu_replay.cpp b/tools/jfjoch_stills_gpu_replay.cpp index 396c1fd8c..ed9747660 100644 --- a/tools/jfjoch_stills_gpu_replay.cpp +++ b/tools/jfjoch_stills_gpu_replay.cpp @@ -1,15 +1,19 @@ // SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only -// Replays the frames of a stored serial collection through the broker's GPU analysis of converted -// JUNGFRAU frames (StillsGPUAnalysisFPGA): the pre-pass on the first images, then every frame analysed with the +// Replays the frames of a stored serial collection through the broker's live analysis of serial +// frames (StillsGPUAnalysis): the pre-pass on the first images, then every frame analysed with the // beam centre and mask it decided. Reports the pre-pass, the indexed fraction and the sustained rate, and -// writes the mask the frames were analysed with (_mask.u32, uint32, row by row). +// writes the mask the frames were analysed with (_mask.u32, uint32, row by row). With a model (-m), the +// model step of the merges as the broker makes it, and the files the end message would carry: .mtz (the +// merge, in the model's frame), _maps.mtz, _model.cif, _refined_model.cif, +// _refined_maps.mtz. #include #include #include #include +#include #include #include #include @@ -21,8 +25,23 @@ #include "../common/Logger.h" #include "../image_analysis/indexing/IndexerThreadPool.h" #include "../reader/JFJochHDF5Reader.h" -#include "../receiver/StillsGPUAnalysisFPGA.h" +#include "../receiver/StillsGPUAnalysis.h" #include "../writer/HDF5Objects.h" +#include "../writer/RugnuxModelFile.h" +#include "../writer/WriteModel.h" +#include "../writer/WriteRugnuxFiles.h" +#include "../image_analysis/WriteReflections.h" + +struct ReplaySink : public StillsSink { + std::function ready; + std::function analysed; + SpotFindingSettings settings; + + void StillsReady(const StillsSetup &setup) override { ready(setup); } + void SendAnalysedImage(DataMessage &message, const AzimuthalIntegrationProfile &profile) override { analysed(message, profile); } + SpotFindingSettings AnalysisSettings() override { return settings; } + void StillsSetupFailed(const JFJochException &) override {} +}; void print_usage() { std::cout << "Usage: jfjoch_stills_gpu_replay [options] " << std::endl; @@ -37,8 +56,10 @@ void print_usage() { std::cout << " -F the broker's default fixed-threshold spot finder (default: adaptive, as the receiver)" << std::endl; std::cout << " -c leave the file's unit cell out of the collection" << std::endl; std::cout << " -S space group of the collection (the merge's)" << std::endl; - std::cout << " -M no merge (StillsGPUAnalysisFPGA merge = false)" << std::endl; + std::cout << " -M no merge (StillsGPUAnalysis merge = false)" << std::endl; std::cout << " -e the first images given as empty frames (no diffraction)" << std::endl; + std::cout << " -m model (PDB/mmCIF) placed and refined against the merges" << std::endl; + std::cout << " -D deadline of the model step after the last image (default 60, 0 = none)" << std::endl; } int main(int argc, char **argv) { @@ -58,9 +79,11 @@ int main(int argc, char **argv) { std::optional space_group; bool merge = true; int64_t empty_frames = 0; + std::shared_ptr model; + double deadline_s = 60; int opt; - while ((opt = getopt(argc, argv, "o:s:n:r:N:b:x:y:C:cFS:Me:")) != -1) { + while ((opt = getopt(argc, argv, "o:s:n:r:N:b:x:y:C:cFS:Me:m:D:")) != -1) { switch (opt) { case 'o': prefix = optarg; break; case 's': first = atoll(optarg); break; @@ -75,6 +98,8 @@ int main(int argc, char **argv) { case 'S': space_group = optarg; break; case 'M': merge = false; break; case 'e': empty_frames = atoll(optarg); break; + case 'm': model = std::make_shared(ReadRugnuxModelFile(optarg)); break; + case 'D': deadline_s = atof(optarg); break; case 'C': { UnitCell c{}; if (sscanf(optarg, "%f,%f,%f,%f,%f,%f", &c.a, &c.b, &c.c, &c.alpha, &c.beta, &c.gamma) != 6) { @@ -140,6 +165,7 @@ int main(int argc, char **argv) { std::atomic analysed = 0, indexed = 0, spots = 0, given = 0; std::atomic caught_up = false; + double buffer_full_s = 0.0; std::atomic processing_s = 0.0; std::chrono::steady_clock::time_point t_ready; const auto t0 = std::chrono::steady_clock::now(); @@ -147,34 +173,37 @@ int main(int argc, char **argv) { // As the receiver: half the buffer for the frames waiting, the other half for those sent. std::mutex run_profile_mutex; std::unique_ptr run_profile; - StillsGPUAnalysisFPGA gpu(x, mask, start, &pool, nthreads, buffer.GetStatus().total_slots / 2, logger, - [&](const StillsGPUAnalysisFPGA::Setup &s) { - t_ready = std::chrono::steady_clock::now(); - run_profile = std::make_unique(*s.mapping); - const auto &m = s.mask.GetMask(); - std::ofstream f(prefix + "_mask.u32", std::ios::binary); - f.write(reinterpret_cast(m.data()), m.size() * sizeof(uint32_t)); - logger.Info("Ready after {:.1f} s: beam centre {:.2f} {:.2f}", - std::chrono::duration(t_ready - t0).count(), - s.experiment.GetBeamX_pxl(), s.experiment.GetBeamY_pxl()); - }, - [&](DataMessage &m, const AzimuthalIntegrationProfile &profile) { - { - // As the receiver's plots do: the frames' profiles add up over the run. - std::unique_lock ul(run_profile_mutex); - *run_profile += profile; - } - // The analysis has caught up with the frames given. - if (++analysed + nthreads >= given && !caught_up.exchange(true)) - logger.Info("Analysis caught up {:.1f} s after it started, {} frames analysed", - std::chrono::duration(std::chrono::steady_clock::now() - - t_ready).count(), analysed.load()); - spots += static_cast(m.spots.size()); - processing_s += m.processing_time_s.value_or(0.0f); - if (m.indexing_result.value_or(false)) - ++indexed; - }, - [&] { return spot_finding; }, {}, nullptr, merge); + RugnuxLiveStatus status; // nobody reads it here + ReplaySink sink; + sink.ready = [&](const StillsSetup &setup) { + t_ready = std::chrono::steady_clock::now(); + run_profile = std::make_unique(*setup.mapping); + const auto &m = setup.mask.GetMask(); + std::ofstream f(prefix + "_mask.u32", std::ios::binary); + f.write(reinterpret_cast(m.data()), m.size() * sizeof(uint32_t)); + logger.Info("Ready after {:.1f} s: beam centre {:.2f} {:.2f}", + std::chrono::duration(t_ready - t0).count(), + setup.experiment.GetBeamX_pxl(), setup.experiment.GetBeamY_pxl()); + }; + sink.analysed = [&](DataMessage &m, const AzimuthalIntegrationProfile &profile) { + { + // As the receiver's plots do: the frames' profiles add up over the run. + std::unique_lock ul(run_profile_mutex); + *run_profile += profile; + } + // The analysis has caught up with the frames given. + if (++analysed + nthreads >= given && !caught_up.exchange(true)) + logger.Info("Analysis caught up {:.1f} s after it started, {} frames analysed", + std::chrono::duration(std::chrono::steady_clock::now() + - t_ready).count(), analysed.load()); + spots += static_cast(m.spots.size()); + processing_s += m.processing_time_s.value_or(0.0f); + if (m.indexing_result.value_or(false)) + ++indexed; + }; + sink.settings = spot_finding; + StillsGPUAnalysis gpu(x, mask, start, &pool, nthreads, buffer.GetStatus().total_slots / 2, logger, sink, status, + merge, model, deadline_s); JFJochReaderRawImage raw; std::vector empty; @@ -190,20 +219,24 @@ int main(int argc, char **argv) { } auto held = std::make_shared(); // Unlike the receiver, the replay waits for a slot rather than lose the frame. + const auto t_slot = std::chrono::steady_clock::now(); while ((held->slot = buffer.GetImageSlot()) == nullptr) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + buffer_full_s += std::chrono::duration(std::chrono::steady_clock::now() - t_slot).count(); memcpy(held->slot->GetImage(), raw.image.GetCompressed(), raw.image.GetCompressedSize()); held->message.number = i; held->message.image = CompressedImage(held->slot->GetImage(), raw.image.GetCompressedSize(), raw.image.GetWidth(), raw.image.GetHeight(), raw.image.GetMode(), raw.image.GetCompressionAlgorithm()); held->message.image_collection_efficiency = 1.0f; - while (!gpu.Hold(held)) - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + gpu.Hold(held); ++given; } - logger.Info("Gave {} images in {:.1f} s", n, - std::chrono::duration(std::chrono::steady_clock::now() - t0).count()); + const auto t_given = std::chrono::steady_clock::now(); + // The replay waits for a slot where a receiver would drop the frame. + logger.Info("Buffer full for {:.1f} s: at -r {} Hz about {:.0f} frames would be dropped", buffer_full_s, rate, + buffer_full_s * rate); + logger.Info("Gave {} images in {:.1f} s", n, std::chrono::duration(t_given - t0).count()); gpu.Finish(); if (const auto c = gpu.GetConsensusUnitCell()) logger.Info("Consensus cell {:.3f} {:.3f} {:.3f} {:.2f} {:.2f} {:.2f}", c->a, c->b, c->c, c->alpha, c->beta, @@ -217,6 +250,22 @@ int main(int argc, char **argv) { os << m->merge.statistics; logger.Info("Merging statistics:\n{}", os.str()); } + logger.Info("Last image given -> merge and model step done: {:.2f} s", std::chrono::duration( + std::chrono::steady_clock::now() - t_given).count()); + if (const auto &mx = gpu.GetModel()) { + DiffractionExperiment mx_x(gpu.GetMergeExperiment()); + mx_x.SetSpaceGroup(mx->space_group); + WriteMtz(MergedMtzTable(mx->merged, mx->cell, mx_x, OutputProvenance{}), prefix + ".mtz"); + if (mx->maps) + WriteMtz(mx->maps->coefficients, prefix + "_maps.mtz"); + if (mx->placed_model) + WritePlacedModel(*mx->placed_model->model, mx->cell, mx->space_group, prefix, logger); + if (const auto &rf = mx->refinement; rf && rf->ok && rf->model) { + WritePlacedModel(*rf->model, mx->cell, mx->space_group, prefix + "_refined", logger); + if (rf->maps) + WriteMtz(rf->maps->coefficients, prefix + "_refined_maps.mtz"); + } + } } const auto t1 = std::chrono::steady_clock::now(); const double after_ready = std::chrono::duration(t1 - t_ready).count(); -- 2.54.0 From 8a7f46a4e277e0582f19fe3639c4b1e8b01f85ed Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 11 Nov 2026 13:08:24 +0100 Subject: [PATCH 08/96] API: the live rugnux verdict can be CANCELLED; rugnux live covers DECTRIS serial collections From rc186 b86b99691 and 8f3ba8d38: the verdict's description lists CANCELLED, in jfjoch_api.yaml and, as the generator writes them, broker/gen/model/Rugnux_live_status.h, frontend/src/client/{types,zod}.gen.ts and docs/python_client/docs/RugnuxLiveStatus.md. The frontend shows a CANCELLED verdict as info, and for serial data shows the model section while the collection runs (RugnuxParts/RugnuxPage/RugnuxStills.tsx as in rc186). Adapted, not copied: rc186's rugnux_settings text says the FPGA workflow is not affected by the mode, which is not true here - JFJochReceiverFPGA still analyses serial runs live. The descriptions of rugnux_settings, its `enable` and rugnux_live_stills say instead that serial collections from a DECTRIS detector or converted by the FPGA are analysed; the generated files carry the same text (edited by hand, as only comments change; VERSION stays 1.0.0-rc.185). The frontend switch reads "Process collections with rugnux live". Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK --- broker/gen/model/Rugnux_live_status.h | 2 +- broker/gen/model/Rugnux_live_stills.h | 4 ++-- broker/gen/model/Rugnux_settings.h | 6 +++--- broker/jfjoch_api.yaml | 11 ++++++----- docs/python_client/docs/RugnuxLiveStatus.md | 2 +- docs/python_client/docs/RugnuxLiveStills.md | 2 +- docs/python_client/docs/RugnuxSettings.md | 4 ++-- frontend/src/client/types.gen.ts | 11 ++++++----- frontend/src/client/zod.gen.ts | 7 ++++--- frontend/src/components/RugnuxPage.tsx | 9 +++++++-- frontend/src/components/RugnuxParts.tsx | 2 ++ frontend/src/components/RugnuxSettings.tsx | 2 +- frontend/src/components/RugnuxStills.tsx | 2 +- 13 files changed, 37 insertions(+), 27 deletions(-) diff --git a/broker/gen/model/Rugnux_live_status.h b/broker/gen/model/Rugnux_live_status.h index 58ccc5a36..45e42dea7 100644 --- a/broker/gen/model/Rugnux_live_status.h +++ b/broker/gen/model/Rugnux_live_status.h @@ -210,7 +210,7 @@ public: bool lastImageToWrittenSIsSet() const; void unsetLast_image_to_written_s(); /// - /// OK, WARNINGS, UNUSABLE or FAILED; empty until the run is over + /// OK, WARNINGS, UNUSABLE, FAILED or CANCELLED; empty until the run is over /// std::string getVerdict() const; void setVerdict(std::string const& value); diff --git a/broker/gen/model/Rugnux_live_stills.h b/broker/gen/model/Rugnux_live_stills.h index e7021606b..18bed6ef1 100644 --- a/broker/gen/model/Rugnux_live_stills.h +++ b/broker/gen/model/Rugnux_live_stills.h @@ -12,7 +12,7 @@ /* * Rugnux_live_stills.h * - * Serial frames converted by the FPGA and analysed on the GPU: the pre-pass on the first images (the beam centre, distance and mask the whole collection is analysed with), and the analysis since. + * Serial frames analysed on the GPU: the pre-pass on the first images (the beam centre, distance and mask the whole collection is analysed with), and the analysis since. */ #ifndef Rugnux_live_stills_H_ @@ -26,7 +26,7 @@ namespace org::openapitools::server::model { /// -/// Serial frames converted by the FPGA and analysed on the GPU: the pre-pass on the first images (the beam centre, distance and mask the whole collection is analysed with), and the analysis since. +/// Serial frames analysed on the GPU: the pre-pass on the first images (the beam centre, distance and mask the whole collection is analysed with), and the analysis since. /// class Rugnux_live_stills { diff --git a/broker/gen/model/Rugnux_settings.h b/broker/gen/model/Rugnux_settings.h index def16b489..8719ca61e 100644 --- a/broker/gen/model/Rugnux_settings.h +++ b/broker/gen/model/Rugnux_settings.h @@ -12,7 +12,7 @@ /* * Rugnux_settings.h * - * Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux inside the broker while they arrive, and the merged result is sent to the writer. Serial collections converted by the FPGA (JUNGFRAU) are analysed on the GPU after a pre-pass on their first images that decides the beam centre, distance and mask (`stills` in `/rugnux/status`). + * Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux inside the broker while they arrive, and the merged result is sent to the writer. Serial collections, from a DECTRIS detector or converted by the FPGA (JUNGFRAU), are analysed on the GPU after a pre-pass on their first images that decides the beam centre, distance and mask (`stills` in `/rugnux/status`), and merged as they go. */ #ifndef Rugnux_settings_H_ @@ -25,7 +25,7 @@ namespace org::openapitools::server::model { /// -/// Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux inside the broker while they arrive, and the merged result is sent to the writer. Serial collections converted by the FPGA (JUNGFRAU) are analysed on the GPU after a pre-pass on their first images that decides the beam centre, distance and mask (`stills` in `/rugnux/status`). +/// Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux inside the broker while they arrive, and the merged result is sent to the writer. Serial collections, from a DECTRIS detector or converted by the FPGA (JUNGFRAU), are analysed on the GPU after a pre-pass on their first images that decides the beam centre, distance and mask (`stills` in `/rugnux/status`), and merged as they go. /// class Rugnux_settings { @@ -58,7 +58,7 @@ public: /// Rugnux_settings members /// - /// Process DECTRIS rotation collections with rugnux live, and FPGA serial collections on the GPU after a pre-pass + /// Process DECTRIS rotation collections with rugnux live, and serial collections on the GPU after a pre-pass /// bool isEnable() const; void setEnable(bool const value); diff --git a/broker/jfjoch_api.yaml b/broker/jfjoch_api.yaml index e9d61485a..744fe36cf 100644 --- a/broker/jfjoch_api.yaml +++ b/broker/jfjoch_api.yaml @@ -2803,13 +2803,14 @@ components: description: | Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux inside the broker while they arrive, and the merged result is sent to the writer. Serial - collections converted by the FPGA (JUNGFRAU) are analysed on the GPU after a pre-pass on their - first images that decides the beam centre, distance and mask (`stills` in `/rugnux/status`). + collections, from a DECTRIS detector or converted by the FPGA (JUNGFRAU), are analysed on the GPU + after a pre-pass on their first images that decides the beam centre, distance and mask (`stills` in + `/rugnux/status`), and merged as they go. properties: enable: type: boolean default: false - description: Process DECTRIS rotation collections with rugnux live, and FPGA serial collections on the GPU after a pre-pass + description: Process DECTRIS rotation collections with rugnux live, and serial collections on the GPU after a pre-pass reserved_cpus: type: integer format: int64 @@ -3070,7 +3071,7 @@ components: rugnux_live_stills: type: object description: | - Serial frames converted by the FPGA and analysed on the GPU: the pre-pass on the first images (the beam + Serial frames analysed on the GPU: the pre-pass on the first images (the beam centre, distance and mask the whole collection is analysed with), and the analysis since. required: - prepass @@ -3251,7 +3252,7 @@ components: description: From the last image received to all results written verdict: type: string - description: OK, WARNINGS, UNUSABLE or FAILED; empty until the run is over + description: OK, WARNINGS, UNUSABLE, FAILED or CANCELLED; empty until the run is over verdict_text: type: string summary: diff --git a/docs/python_client/docs/RugnuxLiveStatus.md b/docs/python_client/docs/RugnuxLiveStatus.md index 420878a72..76648c31d 100644 --- a/docs/python_client/docs/RugnuxLiveStatus.md +++ b/docs/python_client/docs/RugnuxLiveStatus.md @@ -30,7 +30,7 @@ Name | Type | Description | Notes **cut** | **str** | Empty, or why the analysis was cut: `deadline` (`deadline_s` after the last image) or `cancel` (`POST /cancel`). The end message then carried what was established at the cut. | [optional] **cut_s** | **float** | When the analysis was cut, since the first image | [optional] **last_image_to_written_s** | **float** | From the last image received to all results written | [optional] -**verdict** | **str** | OK, WARNINGS, UNUSABLE or FAILED; empty until the run is over | +**verdict** | **str** | OK, WARNINGS, UNUSABLE, FAILED or CANCELLED; empty until the run is over | **verdict_text** | **str** | | **summary** | [**List[RugnuxSummaryRow]**](RugnuxSummaryRow.md) | | **warnings** | **List[str]** | | diff --git a/docs/python_client/docs/RugnuxLiveStills.md b/docs/python_client/docs/RugnuxLiveStills.md index 81ff88590..637a70855 100644 --- a/docs/python_client/docs/RugnuxLiveStills.md +++ b/docs/python_client/docs/RugnuxLiveStills.md @@ -1,6 +1,6 @@ # RugnuxLiveStills -Serial frames converted by the FPGA and analysed on the GPU: the pre-pass on the first images (the beam centre, distance and mask the whole collection is analysed with), and the analysis since. +Serial frames analysed on the GPU: the pre-pass on the first images (the beam centre, distance and mask the whole collection is analysed with), and the analysis since. ## Properties diff --git a/docs/python_client/docs/RugnuxSettings.md b/docs/python_client/docs/RugnuxSettings.md index 9dd894514..24774e9cc 100644 --- a/docs/python_client/docs/RugnuxSettings.md +++ b/docs/python_client/docs/RugnuxSettings.md @@ -1,12 +1,12 @@ # RugnuxSettings -Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux inside the broker while they arrive, and the merged result is sent to the writer. Serial collections converted by the FPGA (JUNGFRAU) are analysed on the GPU after a pre-pass on their first images that decides the beam centre, distance and mask (`stills` in `/rugnux/status`). +Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux inside the broker while they arrive, and the merged result is sent to the writer. Serial collections, from a DECTRIS detector or converted by the FPGA (JUNGFRAU), are analysed on the GPU after a pre-pass on their first images that decides the beam centre, distance and mask (`stills` in `/rugnux/status`), and merged as they go. ## Properties Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**enable** | **bool** | Process DECTRIS rotation collections with rugnux live, and FPGA serial collections on the GPU after a pre-pass | [optional] [default to False] +**enable** | **bool** | Process DECTRIS rotation collections with rugnux live, and serial collections on the GPU after a pre-pass | [optional] [default to False] **reserved_cpus** | **int** | CPU threads left to the receiver and the image pusher while rugnux runs | [optional] [default to 4] **deadline_s** | **float** | Fixed cutoff: this long after the last image the analysis is cut, and the collection ends with what it has established (as `POST /cancel` after the last image does). 0 means no cutoff. | [optional] [default to 60.0] diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts index 4cba588df..bd678725c 100644 --- a/frontend/src/client/types.gen.ts +++ b/frontend/src/client/types.gen.ts @@ -2007,13 +2007,14 @@ export type jfjoch_settings = { /** * Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux * inside the broker while they arrive, and the merged result is sent to the writer. Serial - * collections converted by the FPGA (JUNGFRAU) are analysed on the GPU after a pre-pass on their - * first images that decides the beam centre, distance and mask (`stills` in `/rugnux/status`). + * collections, from a DECTRIS detector or converted by the FPGA (JUNGFRAU), are analysed on the GPU + * after a pre-pass on their first images that decides the beam centre, distance and mask (`stills` in + * `/rugnux/status`), and merged as they go. * */ export type rugnux_settings = { /** - * Process DECTRIS rotation collections with rugnux live, and FPGA serial collections on the GPU after a pre-pass + * Process DECTRIS rotation collections with rugnux live, and serial collections on the GPU after a pre-pass */ enable?: boolean; /** @@ -2233,7 +2234,7 @@ export type rugnux_live_blob = { }; /** - * Serial frames converted by the FPGA and analysed on the GPU: the pre-pass on the first images (the beam + * Serial frames analysed on the GPU: the pre-pass on the first images (the beam * centre, distance and mask the whole collection is analysed with), and the analysis since. * */ @@ -2373,7 +2374,7 @@ export type rugnux_live_status = { */ last_image_to_written_s?: number; /** - * OK, WARNINGS, UNUSABLE or FAILED; empty until the run is over + * OK, WARNINGS, UNUSABLE, FAILED or CANCELLED; empty until the run is over */ verdict: string; verdict_text: string; diff --git a/frontend/src/client/zod.gen.ts b/frontend/src/client/zod.gen.ts index b28fd38ca..4b8d10c45 100644 --- a/frontend/src/client/zod.gen.ts +++ b/frontend/src/client/zod.gen.ts @@ -846,8 +846,9 @@ export const zBraggIntegrationSettings = z.object({ /** * Rugnux live mode: rotation collections from a DECTRIS detector are processed by rugnux * inside the broker while they arrive, and the merged result is sent to the writer. Serial - * collections converted by the FPGA (JUNGFRAU) are analysed on the GPU after a pre-pass on their - * first images that decides the beam centre, distance and mask (`stills` in `/rugnux/status`). + * collections, from a DECTRIS detector or converted by the FPGA (JUNGFRAU), are analysed on the GPU + * after a pre-pass on their first images that decides the beam centre, distance and mask (`stills` in + * `/rugnux/status`), and merged as they go. * */ export const zRugnuxSettings = z.object({ @@ -951,7 +952,7 @@ export const zRugnuxLiveModel = z.object({ }); /** - * Serial frames converted by the FPGA and analysed on the GPU: the pre-pass on the first images (the beam + * Serial frames analysed on the GPU: the pre-pass on the first images (the beam * centre, distance and mask the whole collection is analysed with), and the analysis since. * */ diff --git a/frontend/src/components/RugnuxPage.tsx b/frontend/src/components/RugnuxPage.tsx index abf3d164a..2322c28af 100644 --- a/frontend/src/components/RugnuxPage.tsx +++ b/frontend/src/components/RugnuxPage.tsx @@ -119,13 +119,18 @@ function SummaryTiles({s}: { s: rugnux_live_status }) { function ModelSection({s}: { s: rugnux_live_status }) { const m = s.model!; const mrSeconds = s.steps.find(step => step.title === "Molecular replacement")?.duration_s; + // Serial data place the model on each provisional merge while collecting: the viewer reloads when the model + // step has a new result, not every time the status is polled. + const resultKey = s.stills + ? `${m.placement} ${m.r_work} ${m.r_free} ${m.refinement?.r_free} ${s.refined_model}` + : `${s.elapsed_s}`; return (
{s.placed_model && Loading viewer...}> - @@ -166,7 +171,7 @@ function RugnuxPage({measuring}: { measuring: boolean }) {
- {s.model && s.state !== 'Running' && } + {s.model && (s.state !== 'Running' || s.stills) && }
diff --git a/frontend/src/components/RugnuxParts.tsx b/frontend/src/components/RugnuxParts.tsx index 6a8a9105e..7dfce46b0 100644 --- a/frontend/src/components/RugnuxParts.tsx +++ b/frontend/src/components/RugnuxParts.tsx @@ -11,6 +11,8 @@ export const verdictSeverity = (verdict: string) => { return "success"; case "WARNINGS": return "warning"; + case "CANCELLED": + return "info"; default: return "error"; } diff --git a/frontend/src/components/RugnuxSettings.tsx b/frontend/src/components/RugnuxSettings.tsx index 8fd76bbca..8c5de7ea0 100644 --- a/frontend/src/components/RugnuxSettings.tsx +++ b/frontend/src/components/RugnuxSettings.tsx @@ -50,7 +50,7 @@ function RugnuxSettings({measuring}: { measuring: boolean }) { onUpload={() => settings.submit({enable, reserved_cpus: cpus, deadline_s: deadline})} uploadDisabled={measuring || cpusError || deadlineError || settings.pending} snackbar={settings.snackbar}> - setEnable(e.target.checked)}/>}/> Date: Wed, 11 Nov 2026 13:08:24 +0100 Subject: [PATCH 09/96] docs: the receiver of a run and Rugnux live stills on the DECTRIS stream From rc186 2feef4b02 (JFJOCH_BROKER.md, IMAGE_STREAM.md), for what this branch has: the table of the receiver each run gets, the DECTRIS serial live mode (pre-pass, analysis, live merge, model step, CANCELLED after a cancel, a rotation run the buffer cannot hold getting the per-image analysis), no calibration forwarded from a DECTRIS stream, nothing on the preview and metadata streams for the DECTRIS mask sequence. Adapted: JFJochReceiverFPGA takes every PSI run, pedestal included, and still analyses serial runs live with rugnux on; a paragraph says how that differs (frames wait in half the image buffer, no model step). Left out: the broker_pipeline figure (it shows JFJochReceiverPedestal and the FPGA receiver without live stills), the internal-generator memory check (25542a9af, not ported) and docs/TESTS.md (the card mock, not ported). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK --- docs/IMAGE_STREAM.md | 13 +++++- docs/JFJOCH_BROKER.md | 99 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 90 insertions(+), 22 deletions(-) diff --git a/docs/IMAGE_STREAM.md b/docs/IMAGE_STREAM.md index 1cddb6ef5..a74acc90d 100644 --- a/docs/IMAGE_STREAM.md +++ b/docs/IMAGE_STREAM.md @@ -11,6 +11,8 @@ These are: ## Image stream Images (with metadata) are serialized as CBOR [image message](CBOR.md#image-message). The stream will also include CBOR [start message](CBOR.md#start-message), [calibration messages](CBOR.md#calibration-message) and [end message](CBOR.md#end-message) with run metadata. +Calibration messages (the JUNGFRAU pedestal and pedestal RMS, with `save_calibration` on) come only from the +FPGA workflow; a DECTRIS stream carries no calibration, so none is forwarded for it. If `file_prefix` is not provided for a data collection, images won't be sent to image stream (or its HDF5/CBOR replacements). @@ -239,12 +241,19 @@ For this reason it is also recommended to use the same option on receiver side. Given PUB socket properties, it is possible to connect multiple viewers to a single socket --- all the viewers should receive all the images sent. +The dark frames of the mask sequence from a DECTRIS detector are no images for a viewer: nothing is sent on +this stream for them, not even a start or end message. + ## Metadata stream Jungfraujoch can also send pure metadata for the purpose of archiving such information. Metadata are serialized as CBOR [metadata message](CBOR.md#metadata-message). This is very similar to the image message, but excludes the image array, the spot list and the reflection list. -As metadata are relatively small, to avoid large number of messages, Jungfraujoch bundles metadata of many images in one message. -Order of images within bundle, as well as the size of the bundle, are not guaranteed. +As metadata are relatively small, to avoid large number of messages, Jungfraujoch bundles metadata of many images in one message: +a bundle is cut every `period_ms` of the `zeromq_metadata` configuration and holds the images analysed since the last one. +Order of images within bundle, as well as the size of the bundle, are not guaranteed, and two bundles cut +close together can leave in either order; the end message is always last. The stream will also include CBOR [start message](CBOR.md#start-message) and [end message](CBOR.md#end-message) with run metadata. +A disabled metadata stream keeps nothing, and the dark frames of the mask sequence from a DECTRIS detector +send nothing on it, as on the preview stream. This is using PUB socket with watermark, so there is some queuing of messages with ZeroMQ. Multiple receivers can be connected. \ No newline at end of file diff --git a/docs/JFJOCH_BROKER.md b/docs/JFJOCH_BROKER.md index 6424b5404..d1313fd37 100644 --- a/docs/JFJOCH_BROKER.md +++ b/docs/JFJOCH_BROKER.md @@ -214,19 +214,42 @@ rugnux processed the data: by rugnux live mode (below) during the collection, or `rotation_indexing_angular_stride_deg` and `spot_finding_settings.quick_integration` are deprecated: accepted, ignored, and returned at their old defaults. `bragg_integration_settings` apply to rugnux live mode. +### The receiver of a run + +Each run is taken by one receiver, chosen when it starts from the detector type, the detector mode, the +goniometer and the rugnux live setting: + +| Input | Run | Receiver | What it does | +|---|---|---|---| +| FPGA cards (PSI JUNGFRAU, PSI EIGER) | every run | `JFJochReceiverFPGA` | the card's converted pixels, spots, azimuthal and ROI sums into the image; summation on the host above what the card sums; indexing of each image; the calibration to the writer; pedestal runs into the pedestal and its RMS; with rugnux live on, a serial run analysed on the GPU after a rugnux pre-pass (see [Serial collections](#serial-collections-experimental)) | +| DECTRIS stream | dark-mask sequence | `JFJochReceiverDarkMask` | the dark frames into the mask; nothing on any stream | +| DECTRIS stream | rugnux live off, or a rotation run the image buffer cannot hold | `JFJochReceiverLite` | each image on the GPU: masking, azimuthal and ROI integration, spot finding, indexing | +| DECTRIS stream | rugnux live on, rotation that fits the image buffer | `JFJochReceiverRugnuxRotation` | images forwarded as they come; rugnux reads the collection back from the image buffer and runs the whole pipeline | +| DECTRIS stream | rugnux live on, serial | `JFJochReceiverRugnuxStills` | a rugnux pre-pass on the first frames, then every frame analysed and integrated on the GPU and merged live | + +`receiver_threads` is the number of image threads. The Lite receiver uses at most 16 of them for a +collection at 100 Hz or slower or of fewer than 256 images (each thread holds a GPU analysis of its +own); the FPGA receiver takes four when it sums images on the host. The GPU FFT indexers and cuFFT +plans the broker's own indexing uses are built once and kept for the life of the process, and the ones +built for earlier indexing settings are released when the settings change. `azim_int.force_cpu` +moves the azimuthal integration of the FPGA workflow from the card to the host, which a profile with +more bins than the card holds needs. + ## Rugnux live mode -With rugnux live mode on, rotation collections from a DECTRIS detector are processed by rugnux inside -the broker while they arrive, and serial (stills) collections in the FPGA workflow are analysed on the GPU -with the geometry and mask a rugnux pre-pass decided (see [Serial collections](#serial-collections-experimental) -below). It is off by default; the configuration file switches it on with +With rugnux live mode on, collections from a DECTRIS detector are processed inside the broker while they +arrive: rotation collections by rugnux, and serial (stills) collections on the GPU with the geometry and mask a +rugnux pre-pass decided (see [Serial collections](#serial-collections-experimental) below). In the FPGA workflow +(PSI JUNGFRAU and EIGER) serial collections are analysed on the GPU after a pre-pass as well, with the differences +given there; rotation collections run image by image, as with the mode off. It is off by default; the +configuration file switches it on with `"rugnux": {"enable": true, "reserved_cpus": 4}`, where `reserved_cpus` is the number of CPU threads left to the receiver and the image pusher. For a rotation collection, rugnux reads the images back from the broker's image buffer, so the buffer must hold the whole -collection - one slot per image. Where it holds fewer, the collection is only forwarded to the writer, -as with the mode off; the live status reports it as skipped and the collection's statistics carry a -warning saying how many images the buffer holds against how many the collection has. +collection - one slot per image. Where it holds fewer, the collection gets the per-image analysis of +the mode off (spots, indexing, scan result); the live status reports it as skipped and the collection's +statistics carry a warning saying how many images the buffer holds against how many the collection has. The results - the merged data, the map coefficients and, with a model, the placed model and its refinement - travel to the writer in the end message, which writes them beside the master file as @@ -251,7 +274,8 @@ they prepare the next collection and expose nothing of the current one. The end message, and with it the end of the collection, waits for the analysis. `deadline_s` (default 60 s; 0 for no cutoff) bounds that wait: this long after the last image the analysis is cut. `POST /cancel` is the other way to cut it: while images still arrive it cancels the collection as with the mode off, -and the analysis with it; after the last image it ends only the analysis. A cut analysis gives what it +and the analysis with it - the live status then ends with verdict `CANCELLED` and the images that came before +the cancel; after the last image it ends only the analysis. A cut analysis gives what it has established: a pass that is past its images still merges and makes its outputs, the canonical pass cut among its images merges those it has read, only the placed model is left out, and any other pass cut short gives way to the last one that ran to its end. The end message goes out at @@ -277,9 +301,9 @@ estimate and background of every image in `GET /result/scan` and the master file ### Serial collections (experimental) Live processing of serial collections is **experimental**. With rugnux live mode on, a serial -(non-rotation) collection in the FPGA workflow, in the standard detector mode, is analysed as frames from a -DECTRIS detector are: the converted frames go to GPU spot finding, indexing and integration, with rugnux's -stills spot finder (adaptive threshold, single-pixel spots kept). Before any frame is analysed, the first +(non-rotation) collection from a DECTRIS detector, in the standard detector mode, is analysed frame by frame: the +frames as the stream delivers them go to GPU spot finding, indexing and integration, with rugnux's stills spot +finder (adaptive threshold, single-pixel spots kept). Before any frame is analysed, the first 500 frames are run through rugnux, in memory, as `rugnux --no-merge` would run on them. That pre-pass decides the beam centre, refines the detector distance (where the dataset settings give a unit cell) and masks the beam-stop shadow and defective pixels; every frame of the collection is then analysed with that @@ -288,14 +312,15 @@ the centre and distance used (`refined_beam_center_x/y`, `refined_detector_dista [CBOR](CBOR.md)). The broker's own distance is taken as the beamline's: unlike offline rugnux, the pre-pass does not search the whole distance range. It refines the centre and distance for a detector square to the beam, as the frames are then analysed. At a serial hit rate the distance needs the 500 frames to be refined at -all: on fewer, too few index for the geometry refinement to run, and the stated distance is kept. The image -buffer has to hold them, and the frames that arrive while the pre-pass runs, in half its slots (for a JUNGFRAU -8M, a slot is the size of an uncompressed frame); a smaller buffer starts the pre-pass on as many as it holds. +all: on fewer, too few index for the geometry refinement to run, and the stated distance is kept. The pre-pass +holds and fits on at most half the slots of the image buffer (a slot is the size of an uncompressed +frame), so a smaller buffer starts it on as many as that. Where a unit cell is given and those frames are too few for the geometry fit (200 indexed frames), frames go on being held and the pre-pass runs again on all of them each time they have doubled, until the fit has its frames, half -the image buffer is full, or the collection ends. The analysis starts when the pre-pass ends and catches up from the -frames held, so it starts later on a low hit rate; a frame finding the buffer full meanwhile is dropped as usual. +the image buffer or 2000 frames are held, or the collection ends; frames arriving during a run do not make the next +one larger than that, so the last run stays short. The analysis starts when the pre-pass ends and catches up from the +frames waiting, so it starts later on a low hit rate. The geometry is fixed when the pre-pass ends and does not change during the collection, so every frame is analysed and merged at it and the radial profile has one binning throughout. Where the pre-pass could not refine it (too few @@ -303,9 +328,10 @@ of its frames indexed, as with a small image buffer, a low hit rate or a chip wh cell given), the distance in the header is used for the whole run: this is a warning in the log and a note of the live status, and a wrong header distance then costs resolution and indexed frames. -Frames arriving during the pre-pass, or faster than the analysis, wait in the image buffer, in at most half of -its slots; a frame that finds no free slot is dropped and counted as for a full image buffer, so analysis -slower than the frame rate costs frames, not memory. A pre-pass that fails leaves the stated geometry and +Frames arriving during the pre-pass, or faster than the analysis, wait in the image buffer, in all its slots but +one per analysis thread, kept for sending; a frame that finds it full is dropped and counted as for a full image +buffer, so analysis slower than the frame rate costs frames, not memory. Where the writer lags behind, the analysis +waits for slots to come back. A pre-pass that fails leaves the stated geometry and mask in use. Where the start message cannot be sent after the pre-pass (no writer listening) or the analysis cannot be set up, the collection ends with that error, reported by `/status` and `/wait_till_done`, and the next `/start` runs as usual. @@ -323,11 +349,44 @@ JUNGFRAU 8M, against about half a minute for the same merge made from nothing. T writer in the end message, as for rotation (`.mtz`), and the run's step "Merge" carries the final statistics. -`GET /rugnux/status` reports such a run as three steps (pre-pass, GPU analysis, merge) and a `stills` block: the +With an uploaded model, the model step runs on every one of these merges, as `rugnux --mode scale --model` +places a model and `rugnux --mode refine` refines it: the model is placed by its rigid body where it fits the +data as given - its indexing ambiguity probed by R-free, and the merge put in the model's indexing - and by +molecular replacement where it does not (or is a prediction); the 2mFo-DFc and mFo-DFc maps are made, and the +placed model is refined, with the blobs of its difference map. Without a space group in the dataset settings +the frames are merged in the model's own, where the model's cell is the consensus cell of the run on the same +axes; otherwise the model is left out, with a warning in the live status saying why. The step on a provisional +merge runs on a thread of its own, on 8 CPU threads and the GPU, beside the analysis and the merge, which do not +wait for it: its result - the placed and the refined model, their maps, the scores - goes to the live status +(`model`, `/rugnux/model.cif`, `/rugnux/map.ccp4`) as soon as it is made, so the density can be looked at while +the collection goes on. A step still running when the next merge is made is cut (a refinement cut short keeps +the weight trials it finished), and each step starts from the placement of the one before: the merge is put in +the same indexing, and the model goes in where it was placed, with no null, indexing probe or search made again. +After the last frame the step on the merge of the collection runs on every thread, and the end message waits +for it until `deadline_s` after the last image; then it is cut as for rotation - the refinement first (cut +short, or left out where it has not started), and a molecular replacement that has not started. The end message +carries the merge in the model's indexing (`.mtz`), the map coefficients, the placed and the +refined model, the refined maps and the blobs, written as for rotation; one that would not fit the pushers' +buffer goes without them. The GPU memory the analysis's indexers hold for the broker's life is counted against +the card when the model step decides what goes on it. `POST /cancel` cuts it +the same way, at any time (`cut` in `/rugnux/status`): the frames that came are still analysed and merged. + +The frames wait in the image buffer compressed, and go on to the writer byte for byte, with their analysis. `efficiency` is the +images received over the images expected; a stream has no module packets, so per-image collection +efficiency, ADU histograms and pulse IDs are absent. The dark frames of the mask sequence stay outside this mode. + +`GET /rugnux/status` reports such a run as three steps (pre-pass, GPU analysis, merge; with a model, the steps of +the model on the merge of the collection after them) and a `stills` block: the pre-pass state and duration, the stated and decided beam centre and distance, the pixels masked, and the images analysed and indexed with the analysis rate. The "Rugnux live" page of the web frontend shows the same. `tools/jfjoch_stills_gpu_replay` replays a stored serial collection through the same analysis. +In the FPGA workflow (JUNGFRAU), a serial collection with rugnux live mode on is analysed by the receiver of the +card (`JFJochReceiverFPGA`) with the pre-pass, the GPU analysis and the live merge described above, with three +differences: the converted frames wait for the analysis in at most half of the image buffer's slots, a frame that +finds them taken is dropped, and there is no model step - an uploaded model is not placed or refined on these +merges. The end message carries the merged data, as for a DECTRIS stream. + ## Setting up a local test for Jungfraujoch For development, it is possible to set up a local installation of Jungfraujoch. This will work without FPGA installed in the computer and allows testing the Jungfraujoch software layer, including -- 2.54.0 From c7fb351bf5e7a91e444d474a3e0d1abaf1863b12 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 11 Nov 2026 13:37:09 +0100 Subject: [PATCH 10/96] 1.0.0-rc.186: VERSION and the version strings update_version.sh writes Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK --- VERSION | 2 +- broker/gen/model/Azim_int_settings.cpp | 2 +- broker/gen/model/Azim_int_settings.h | 2 +- broker/gen/model/Bragg_integration_settings.cpp | 2 +- broker/gen/model/Bragg_integration_settings.h | 2 +- broker/gen/model/Broker_status.cpp | 2 +- broker/gen/model/Broker_status.h | 2 +- broker/gen/model/Calibration_statistics_inner.cpp | 2 +- broker/gen/model/Calibration_statistics_inner.h | 2 +- broker/gen/model/Dark_mask_settings.cpp | 2 +- broker/gen/model/Dark_mask_settings.h | 2 +- broker/gen/model/Dataset_settings.cpp | 2 +- broker/gen/model/Dataset_settings.h | 2 +- broker/gen/model/Dataset_settings_smargon.cpp | 2 +- broker/gen/model/Dataset_settings_smargon.h | 2 +- .../gen/model/Dataset_settings_xray_fluorescence_spectrum.cpp | 2 +- .../gen/model/Dataset_settings_xray_fluorescence_spectrum.h | 2 +- broker/gen/model/Detector.cpp | 2 +- broker/gen/model/Detector.h | 2 +- broker/gen/model/Detector_list.cpp | 2 +- broker/gen/model/Detector_list.h | 2 +- broker/gen/model/Detector_list_element.cpp | 2 +- broker/gen/model/Detector_list_element.h | 2 +- broker/gen/model/Detector_module.cpp | 2 +- broker/gen/model/Detector_module.h | 2 +- broker/gen/model/Detector_module_direction.cpp | 2 +- broker/gen/model/Detector_module_direction.h | 2 +- broker/gen/model/Detector_module_placement.cpp | 2 +- broker/gen/model/Detector_module_placement.h | 2 +- broker/gen/model/Detector_power_state.cpp | 2 +- broker/gen/model/Detector_power_state.h | 2 +- broker/gen/model/Detector_selection.cpp | 2 +- broker/gen/model/Detector_selection.h | 2 +- broker/gen/model/Detector_settings.cpp | 2 +- broker/gen/model/Detector_settings.h | 2 +- broker/gen/model/Detector_state.cpp | 2 +- broker/gen/model/Detector_state.h | 2 +- broker/gen/model/Detector_status.cpp | 2 +- broker/gen/model/Detector_status.h | 2 +- broker/gen/model/Detector_timing.cpp | 2 +- broker/gen/model/Detector_timing.h | 2 +- broker/gen/model/Detector_type.cpp | 2 +- broker/gen/model/Detector_type.h | 2 +- broker/gen/model/Error_message.cpp | 2 +- broker/gen/model/Error_message.h | 2 +- broker/gen/model/File_writer_format.cpp | 2 +- broker/gen/model/File_writer_format.h | 2 +- broker/gen/model/File_writer_settings.cpp | 2 +- broker/gen/model/File_writer_settings.h | 2 +- broker/gen/model/Fpga_status_inner.cpp | 2 +- broker/gen/model/Fpga_status_inner.h | 2 +- broker/gen/model/Geom_refinement_algorithm.cpp | 2 +- broker/gen/model/Geom_refinement_algorithm.h | 2 +- broker/gen/model/Grid_scan.cpp | 2 +- broker/gen/model/Grid_scan.h | 2 +- broker/gen/model/Helpers.cpp | 2 +- broker/gen/model/Helpers.h | 2 +- broker/gen/model/Image_buffer_status.cpp | 2 +- broker/gen/model/Image_buffer_status.h | 2 +- broker/gen/model/Image_format_settings.cpp | 2 +- broker/gen/model/Image_format_settings.h | 2 +- broker/gen/model/Image_pusher_status.cpp | 2 +- broker/gen/model/Image_pusher_status.h | 2 +- broker/gen/model/Image_pusher_type.cpp | 2 +- broker/gen/model/Image_pusher_type.h | 2 +- broker/gen/model/Indexing_algorithm.cpp | 2 +- broker/gen/model/Indexing_algorithm.h | 2 +- broker/gen/model/Indexing_settings.cpp | 2 +- broker/gen/model/Indexing_settings.h | 2 +- broker/gen/model/Instrument_metadata.cpp | 2 +- broker/gen/model/Instrument_metadata.h | 2 +- broker/gen/model/Integration_model.cpp | 2 +- broker/gen/model/Integration_model.h | 2 +- broker/gen/model/Jfjoch_settings.cpp | 2 +- broker/gen/model/Jfjoch_settings.h | 2 +- broker/gen/model/Jfjoch_statistics.cpp | 2 +- broker/gen/model/Jfjoch_statistics.h | 2 +- broker/gen/model/Measurement_statistics.cpp | 2 +- broker/gen/model/Measurement_statistics.h | 2 +- broker/gen/model/Pcie_devices_inner.cpp | 2 +- broker/gen/model/Pcie_devices_inner.h | 2 +- broker/gen/model/Pixel_mask_statistics.cpp | 2 +- broker/gen/model/Pixel_mask_statistics.h | 2 +- broker/gen/model/Plot.cpp | 2 +- broker/gen/model/Plot.h | 2 +- broker/gen/model/Plot_unit_x.cpp | 2 +- broker/gen/model/Plot_unit_x.h | 2 +- broker/gen/model/Plots.cpp | 2 +- broker/gen/model/Plots.h | 2 +- broker/gen/model/Powder_calibration_fit_sigma.cpp | 2 +- broker/gen/model/Powder_calibration_fit_sigma.h | 2 +- broker/gen/model/Powder_calibration_output.cpp | 2 +- broker/gen/model/Powder_calibration_output.h | 2 +- broker/gen/model/Powder_calibration_quality.cpp | 2 +- broker/gen/model/Powder_calibration_quality.h | 2 +- broker/gen/model/Powder_calibration_spot_check.cpp | 2 +- broker/gen/model/Powder_calibration_spot_check.h | 2 +- broker/gen/model/Q_binning.cpp | 2 +- broker/gen/model/Q_binning.h | 2 +- broker/gen/model/Roi_azim_list.cpp | 2 +- broker/gen/model/Roi_azim_list.h | 2 +- broker/gen/model/Roi_azimuthal.cpp | 2 +- broker/gen/model/Roi_azimuthal.h | 2 +- broker/gen/model/Roi_box.cpp | 2 +- broker/gen/model/Roi_box.h | 2 +- broker/gen/model/Roi_box_list.cpp | 2 +- broker/gen/model/Roi_box_list.h | 2 +- broker/gen/model/Roi_circle.cpp | 2 +- broker/gen/model/Roi_circle.h | 2 +- broker/gen/model/Roi_circle_list.cpp | 2 +- broker/gen/model/Roi_circle_list.h | 2 +- broker/gen/model/Roi_definitions.cpp | 2 +- broker/gen/model/Roi_definitions.h | 2 +- broker/gen/model/Rotation_axis.cpp | 2 +- broker/gen/model/Rotation_axis.h | 2 +- broker/gen/model/Rugnux_live_blob.cpp | 2 +- broker/gen/model/Rugnux_live_blob.h | 2 +- broker/gen/model/Rugnux_live_model.cpp | 2 +- broker/gen/model/Rugnux_live_model.h | 2 +- broker/gen/model/Rugnux_live_refinement.cpp | 2 +- broker/gen/model/Rugnux_live_refinement.h | 2 +- broker/gen/model/Rugnux_live_status.cpp | 2 +- broker/gen/model/Rugnux_live_status.h | 2 +- broker/gen/model/Rugnux_live_step.cpp | 2 +- broker/gen/model/Rugnux_live_step.h | 2 +- broker/gen/model/Rugnux_live_stills.cpp | 2 +- broker/gen/model/Rugnux_live_stills.h | 2 +- broker/gen/model/Rugnux_settings.cpp | 2 +- broker/gen/model/Rugnux_settings.h | 2 +- broker/gen/model/Rugnux_status.cpp | 2 +- broker/gen/model/Rugnux_status.h | 2 +- broker/gen/model/Rugnux_summary_row.cpp | 2 +- broker/gen/model/Rugnux_summary_row.h | 2 +- broker/gen/model/Scan_result.cpp | 2 +- broker/gen/model/Scan_result.h | 2 +- broker/gen/model/Scan_result_images_inner.cpp | 2 +- broker/gen/model/Scan_result_images_inner.h | 2 +- broker/gen/model/Spot_finding_settings.cpp | 2 +- broker/gen/model/Spot_finding_settings.h | 2 +- broker/gen/model/Standard_detector_geometry.cpp | 2 +- broker/gen/model/Standard_detector_geometry.h | 2 +- broker/gen/model/Tcp_settings.cpp | 2 +- broker/gen/model/Tcp_settings.h | 2 +- broker/gen/model/Unit_cell.cpp | 2 +- broker/gen/model/Unit_cell.h | 2 +- broker/gen/model/Zeromq_metadata_settings.cpp | 2 +- broker/gen/model/Zeromq_metadata_settings.h | 2 +- broker/gen/model/Zeromq_preview_settings.cpp | 2 +- broker/gen/model/Zeromq_preview_settings.h | 2 +- broker/gen/model/Zeromq_settings.cpp | 2 +- broker/gen/model/Zeromq_settings.h | 2 +- broker/jfjoch_api.yaml | 2 +- broker/redoc-static.html | 4 ++-- docs/conf.py | 2 +- docs/python_client/README.md | 4 ++-- fpga/hdl/action_config.v | 2 +- fpga/pcie_driver/dkms.conf | 2 +- fpga/pcie_driver/install_dkms.sh | 2 +- fpga/pcie_driver/jfjoch_drv.c | 2 +- fpga/pcie_driver/postinstall.sh | 2 +- fpga/pcie_driver/preuninstall.sh | 2 +- frontend/package-lock.json | 4 ++-- frontend/package.json | 2 +- frontend/src/version.ts | 2 +- 164 files changed, 167 insertions(+), 167 deletions(-) diff --git a/VERSION b/VERSION index 6a44dfe85..40cd959ac 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0-rc.185 +1.0.0-rc.186 diff --git a/broker/gen/model/Azim_int_settings.cpp b/broker/gen/model/Azim_int_settings.cpp index 4be206b8a..600ddd6f5 100644 --- a/broker/gen/model/Azim_int_settings.cpp +++ b/broker/gen/model/Azim_int_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Azim_int_settings.h b/broker/gen/model/Azim_int_settings.h index 81921b9a5..ed5714309 100644 --- a/broker/gen/model/Azim_int_settings.h +++ b/broker/gen/model/Azim_int_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Bragg_integration_settings.cpp b/broker/gen/model/Bragg_integration_settings.cpp index 03e6134ba..b83cf807d 100644 --- a/broker/gen/model/Bragg_integration_settings.cpp +++ b/broker/gen/model/Bragg_integration_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Bragg_integration_settings.h b/broker/gen/model/Bragg_integration_settings.h index 76b8b77f0..625b48b36 100644 --- a/broker/gen/model/Bragg_integration_settings.h +++ b/broker/gen/model/Bragg_integration_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Broker_status.cpp b/broker/gen/model/Broker_status.cpp index e4a8bf4bf..3bcab994c 100644 --- a/broker/gen/model/Broker_status.cpp +++ b/broker/gen/model/Broker_status.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Broker_status.h b/broker/gen/model/Broker_status.h index 3ca978599..07e5e8f7c 100644 --- a/broker/gen/model/Broker_status.h +++ b/broker/gen/model/Broker_status.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Calibration_statistics_inner.cpp b/broker/gen/model/Calibration_statistics_inner.cpp index d6114f565..a43191373 100644 --- a/broker/gen/model/Calibration_statistics_inner.cpp +++ b/broker/gen/model/Calibration_statistics_inner.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Calibration_statistics_inner.h b/broker/gen/model/Calibration_statistics_inner.h index 068cbf4c8..c57a80ae4 100644 --- a/broker/gen/model/Calibration_statistics_inner.h +++ b/broker/gen/model/Calibration_statistics_inner.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Dark_mask_settings.cpp b/broker/gen/model/Dark_mask_settings.cpp index 08a7613c2..fab840343 100644 --- a/broker/gen/model/Dark_mask_settings.cpp +++ b/broker/gen/model/Dark_mask_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Dark_mask_settings.h b/broker/gen/model/Dark_mask_settings.h index 686d8b9ae..5703a7364 100644 --- a/broker/gen/model/Dark_mask_settings.h +++ b/broker/gen/model/Dark_mask_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Dataset_settings.cpp b/broker/gen/model/Dataset_settings.cpp index a3126a7d4..abf474ec9 100644 --- a/broker/gen/model/Dataset_settings.cpp +++ b/broker/gen/model/Dataset_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Dataset_settings.h b/broker/gen/model/Dataset_settings.h index acf637dd0..601df8418 100644 --- a/broker/gen/model/Dataset_settings.h +++ b/broker/gen/model/Dataset_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Dataset_settings_smargon.cpp b/broker/gen/model/Dataset_settings_smargon.cpp index 5beb81c44..1a69c3c84 100644 --- a/broker/gen/model/Dataset_settings_smargon.cpp +++ b/broker/gen/model/Dataset_settings_smargon.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Dataset_settings_smargon.h b/broker/gen/model/Dataset_settings_smargon.h index 5d1685a72..615fcb860 100644 --- a/broker/gen/model/Dataset_settings_smargon.h +++ b/broker/gen/model/Dataset_settings_smargon.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Dataset_settings_xray_fluorescence_spectrum.cpp b/broker/gen/model/Dataset_settings_xray_fluorescence_spectrum.cpp index 0b18c2195..a540c4f87 100644 --- a/broker/gen/model/Dataset_settings_xray_fluorescence_spectrum.cpp +++ b/broker/gen/model/Dataset_settings_xray_fluorescence_spectrum.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Dataset_settings_xray_fluorescence_spectrum.h b/broker/gen/model/Dataset_settings_xray_fluorescence_spectrum.h index 809b8024d..ba28a236b 100644 --- a/broker/gen/model/Dataset_settings_xray_fluorescence_spectrum.h +++ b/broker/gen/model/Dataset_settings_xray_fluorescence_spectrum.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector.cpp b/broker/gen/model/Detector.cpp index 2c0cfb0b2..300a30deb 100644 --- a/broker/gen/model/Detector.cpp +++ b/broker/gen/model/Detector.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector.h b/broker/gen/model/Detector.h index bc5ed3775..ee386987a 100644 --- a/broker/gen/model/Detector.h +++ b/broker/gen/model/Detector.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_list.cpp b/broker/gen/model/Detector_list.cpp index d3625ba64..939ec2c1d 100644 --- a/broker/gen/model/Detector_list.cpp +++ b/broker/gen/model/Detector_list.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_list.h b/broker/gen/model/Detector_list.h index d560f4ccd..cbb283a9a 100644 --- a/broker/gen/model/Detector_list.h +++ b/broker/gen/model/Detector_list.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_list_element.cpp b/broker/gen/model/Detector_list_element.cpp index c30dbf5ad..5516d38fb 100644 --- a/broker/gen/model/Detector_list_element.cpp +++ b/broker/gen/model/Detector_list_element.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_list_element.h b/broker/gen/model/Detector_list_element.h index a59f4f164..8bbea1eb8 100644 --- a/broker/gen/model/Detector_list_element.h +++ b/broker/gen/model/Detector_list_element.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_module.cpp b/broker/gen/model/Detector_module.cpp index 257db002c..f9c872e70 100644 --- a/broker/gen/model/Detector_module.cpp +++ b/broker/gen/model/Detector_module.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_module.h b/broker/gen/model/Detector_module.h index 21eef7718..aa596fc89 100644 --- a/broker/gen/model/Detector_module.h +++ b/broker/gen/model/Detector_module.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_module_direction.cpp b/broker/gen/model/Detector_module_direction.cpp index 14579c007..9d274e254 100644 --- a/broker/gen/model/Detector_module_direction.cpp +++ b/broker/gen/model/Detector_module_direction.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_module_direction.h b/broker/gen/model/Detector_module_direction.h index f3d55d4ba..522ff0145 100644 --- a/broker/gen/model/Detector_module_direction.h +++ b/broker/gen/model/Detector_module_direction.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_module_placement.cpp b/broker/gen/model/Detector_module_placement.cpp index 611add10b..979b21382 100644 --- a/broker/gen/model/Detector_module_placement.cpp +++ b/broker/gen/model/Detector_module_placement.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_module_placement.h b/broker/gen/model/Detector_module_placement.h index 3ce1829a2..da8ea65f4 100644 --- a/broker/gen/model/Detector_module_placement.h +++ b/broker/gen/model/Detector_module_placement.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_power_state.cpp b/broker/gen/model/Detector_power_state.cpp index 4636324c1..39ea54fc7 100644 --- a/broker/gen/model/Detector_power_state.cpp +++ b/broker/gen/model/Detector_power_state.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_power_state.h b/broker/gen/model/Detector_power_state.h index d0178c8a2..f9f687bd6 100644 --- a/broker/gen/model/Detector_power_state.h +++ b/broker/gen/model/Detector_power_state.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_selection.cpp b/broker/gen/model/Detector_selection.cpp index 07a2bd4b2..e6564d49c 100644 --- a/broker/gen/model/Detector_selection.cpp +++ b/broker/gen/model/Detector_selection.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_selection.h b/broker/gen/model/Detector_selection.h index c555db1a8..3166bceec 100644 --- a/broker/gen/model/Detector_selection.h +++ b/broker/gen/model/Detector_selection.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_settings.cpp b/broker/gen/model/Detector_settings.cpp index efd47866c..0461e0cb3 100644 --- a/broker/gen/model/Detector_settings.cpp +++ b/broker/gen/model/Detector_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_settings.h b/broker/gen/model/Detector_settings.h index 19d1fab7c..0bcd0dc90 100644 --- a/broker/gen/model/Detector_settings.h +++ b/broker/gen/model/Detector_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_state.cpp b/broker/gen/model/Detector_state.cpp index 2e2e26908..64bb76ff7 100644 --- a/broker/gen/model/Detector_state.cpp +++ b/broker/gen/model/Detector_state.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_state.h b/broker/gen/model/Detector_state.h index 2038ea26d..d2dd810e6 100644 --- a/broker/gen/model/Detector_state.h +++ b/broker/gen/model/Detector_state.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_status.cpp b/broker/gen/model/Detector_status.cpp index b4a4e98f7..b091fd096 100644 --- a/broker/gen/model/Detector_status.cpp +++ b/broker/gen/model/Detector_status.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_status.h b/broker/gen/model/Detector_status.h index 6ba372356..1e772ed6b 100644 --- a/broker/gen/model/Detector_status.h +++ b/broker/gen/model/Detector_status.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_timing.cpp b/broker/gen/model/Detector_timing.cpp index 8192707df..bbdbffcbe 100644 --- a/broker/gen/model/Detector_timing.cpp +++ b/broker/gen/model/Detector_timing.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_timing.h b/broker/gen/model/Detector_timing.h index 20758cc1d..a73b85840 100644 --- a/broker/gen/model/Detector_timing.h +++ b/broker/gen/model/Detector_timing.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_type.cpp b/broker/gen/model/Detector_type.cpp index d51ba3de9..a7704271c 100644 --- a/broker/gen/model/Detector_type.cpp +++ b/broker/gen/model/Detector_type.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Detector_type.h b/broker/gen/model/Detector_type.h index 4e23df3c5..ae2803899 100644 --- a/broker/gen/model/Detector_type.h +++ b/broker/gen/model/Detector_type.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Error_message.cpp b/broker/gen/model/Error_message.cpp index c1f49eea5..aa04b7ded 100644 --- a/broker/gen/model/Error_message.cpp +++ b/broker/gen/model/Error_message.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Error_message.h b/broker/gen/model/Error_message.h index 5287c0c8f..e4c1f7171 100644 --- a/broker/gen/model/Error_message.h +++ b/broker/gen/model/Error_message.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/File_writer_format.cpp b/broker/gen/model/File_writer_format.cpp index a8a0f3d67..e865d0f78 100644 --- a/broker/gen/model/File_writer_format.cpp +++ b/broker/gen/model/File_writer_format.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/File_writer_format.h b/broker/gen/model/File_writer_format.h index 2a1233a66..e2fcab9e0 100644 --- a/broker/gen/model/File_writer_format.h +++ b/broker/gen/model/File_writer_format.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/File_writer_settings.cpp b/broker/gen/model/File_writer_settings.cpp index 56bd39668..40e163b3c 100644 --- a/broker/gen/model/File_writer_settings.cpp +++ b/broker/gen/model/File_writer_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/File_writer_settings.h b/broker/gen/model/File_writer_settings.h index 80f70f5eb..e1f86db35 100644 --- a/broker/gen/model/File_writer_settings.h +++ b/broker/gen/model/File_writer_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Fpga_status_inner.cpp b/broker/gen/model/Fpga_status_inner.cpp index fb6f2c223..6018cae63 100644 --- a/broker/gen/model/Fpga_status_inner.cpp +++ b/broker/gen/model/Fpga_status_inner.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Fpga_status_inner.h b/broker/gen/model/Fpga_status_inner.h index 46213db70..accea61c7 100644 --- a/broker/gen/model/Fpga_status_inner.h +++ b/broker/gen/model/Fpga_status_inner.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Geom_refinement_algorithm.cpp b/broker/gen/model/Geom_refinement_algorithm.cpp index 859a54690..7c5badd93 100644 --- a/broker/gen/model/Geom_refinement_algorithm.cpp +++ b/broker/gen/model/Geom_refinement_algorithm.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Geom_refinement_algorithm.h b/broker/gen/model/Geom_refinement_algorithm.h index f027463f7..daca2d14d 100644 --- a/broker/gen/model/Geom_refinement_algorithm.h +++ b/broker/gen/model/Geom_refinement_algorithm.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Grid_scan.cpp b/broker/gen/model/Grid_scan.cpp index cf41b3938..436d7daf8 100644 --- a/broker/gen/model/Grid_scan.cpp +++ b/broker/gen/model/Grid_scan.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Grid_scan.h b/broker/gen/model/Grid_scan.h index ebbdfe17d..38fccb353 100644 --- a/broker/gen/model/Grid_scan.h +++ b/broker/gen/model/Grid_scan.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Helpers.cpp b/broker/gen/model/Helpers.cpp index 0b3dbd0df..ceb17cd43 100644 --- a/broker/gen/model/Helpers.cpp +++ b/broker/gen/model/Helpers.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Helpers.h b/broker/gen/model/Helpers.h index d8788c42d..3e109e54b 100644 --- a/broker/gen/model/Helpers.h +++ b/broker/gen/model/Helpers.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Image_buffer_status.cpp b/broker/gen/model/Image_buffer_status.cpp index a9e763f86..14b9273e2 100644 --- a/broker/gen/model/Image_buffer_status.cpp +++ b/broker/gen/model/Image_buffer_status.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Image_buffer_status.h b/broker/gen/model/Image_buffer_status.h index 21a0763ac..965dbac31 100644 --- a/broker/gen/model/Image_buffer_status.h +++ b/broker/gen/model/Image_buffer_status.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Image_format_settings.cpp b/broker/gen/model/Image_format_settings.cpp index c60e8e547..d4f314568 100644 --- a/broker/gen/model/Image_format_settings.cpp +++ b/broker/gen/model/Image_format_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Image_format_settings.h b/broker/gen/model/Image_format_settings.h index 58081acbb..b9c5fd8d5 100644 --- a/broker/gen/model/Image_format_settings.h +++ b/broker/gen/model/Image_format_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Image_pusher_status.cpp b/broker/gen/model/Image_pusher_status.cpp index d68a9a2ec..56a1fc38e 100644 --- a/broker/gen/model/Image_pusher_status.cpp +++ b/broker/gen/model/Image_pusher_status.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Image_pusher_status.h b/broker/gen/model/Image_pusher_status.h index 27e9a2fff..d469285f2 100644 --- a/broker/gen/model/Image_pusher_status.h +++ b/broker/gen/model/Image_pusher_status.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Image_pusher_type.cpp b/broker/gen/model/Image_pusher_type.cpp index f148c8504..48bca090b 100644 --- a/broker/gen/model/Image_pusher_type.cpp +++ b/broker/gen/model/Image_pusher_type.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Image_pusher_type.h b/broker/gen/model/Image_pusher_type.h index 720bcdbd7..ba74071f5 100644 --- a/broker/gen/model/Image_pusher_type.h +++ b/broker/gen/model/Image_pusher_type.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Indexing_algorithm.cpp b/broker/gen/model/Indexing_algorithm.cpp index 2199a01ef..8173be3f6 100644 --- a/broker/gen/model/Indexing_algorithm.cpp +++ b/broker/gen/model/Indexing_algorithm.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Indexing_algorithm.h b/broker/gen/model/Indexing_algorithm.h index c9b4dbfda..92743375a 100644 --- a/broker/gen/model/Indexing_algorithm.h +++ b/broker/gen/model/Indexing_algorithm.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Indexing_settings.cpp b/broker/gen/model/Indexing_settings.cpp index d2dd8a37d..2d8c37790 100644 --- a/broker/gen/model/Indexing_settings.cpp +++ b/broker/gen/model/Indexing_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Indexing_settings.h b/broker/gen/model/Indexing_settings.h index c2a10026f..f6d62f294 100644 --- a/broker/gen/model/Indexing_settings.h +++ b/broker/gen/model/Indexing_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Instrument_metadata.cpp b/broker/gen/model/Instrument_metadata.cpp index 1a052af19..cbb48acfd 100644 --- a/broker/gen/model/Instrument_metadata.cpp +++ b/broker/gen/model/Instrument_metadata.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Instrument_metadata.h b/broker/gen/model/Instrument_metadata.h index 6864cc048..ba003c554 100644 --- a/broker/gen/model/Instrument_metadata.h +++ b/broker/gen/model/Instrument_metadata.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Integration_model.cpp b/broker/gen/model/Integration_model.cpp index 0a4a642e6..ea4242ad3 100644 --- a/broker/gen/model/Integration_model.cpp +++ b/broker/gen/model/Integration_model.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Integration_model.h b/broker/gen/model/Integration_model.h index cc1a7bbe9..cd0eae3bc 100644 --- a/broker/gen/model/Integration_model.h +++ b/broker/gen/model/Integration_model.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Jfjoch_settings.cpp b/broker/gen/model/Jfjoch_settings.cpp index c00256caf..e5f77e727 100644 --- a/broker/gen/model/Jfjoch_settings.cpp +++ b/broker/gen/model/Jfjoch_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Jfjoch_settings.h b/broker/gen/model/Jfjoch_settings.h index 7a8adb0b0..0315594fc 100644 --- a/broker/gen/model/Jfjoch_settings.h +++ b/broker/gen/model/Jfjoch_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Jfjoch_statistics.cpp b/broker/gen/model/Jfjoch_statistics.cpp index 364a34fab..a9f5a6395 100644 --- a/broker/gen/model/Jfjoch_statistics.cpp +++ b/broker/gen/model/Jfjoch_statistics.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Jfjoch_statistics.h b/broker/gen/model/Jfjoch_statistics.h index 2aa46a502..cb0b4775c 100644 --- a/broker/gen/model/Jfjoch_statistics.h +++ b/broker/gen/model/Jfjoch_statistics.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Measurement_statistics.cpp b/broker/gen/model/Measurement_statistics.cpp index 87860c160..aa1219a99 100644 --- a/broker/gen/model/Measurement_statistics.cpp +++ b/broker/gen/model/Measurement_statistics.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Measurement_statistics.h b/broker/gen/model/Measurement_statistics.h index f2982d698..8151ee667 100644 --- a/broker/gen/model/Measurement_statistics.h +++ b/broker/gen/model/Measurement_statistics.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Pcie_devices_inner.cpp b/broker/gen/model/Pcie_devices_inner.cpp index 771d5ae07..b290956c1 100644 --- a/broker/gen/model/Pcie_devices_inner.cpp +++ b/broker/gen/model/Pcie_devices_inner.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Pcie_devices_inner.h b/broker/gen/model/Pcie_devices_inner.h index 95e688be2..accc1be55 100644 --- a/broker/gen/model/Pcie_devices_inner.h +++ b/broker/gen/model/Pcie_devices_inner.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Pixel_mask_statistics.cpp b/broker/gen/model/Pixel_mask_statistics.cpp index e1ed8b960..860de6055 100644 --- a/broker/gen/model/Pixel_mask_statistics.cpp +++ b/broker/gen/model/Pixel_mask_statistics.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Pixel_mask_statistics.h b/broker/gen/model/Pixel_mask_statistics.h index 8858743c4..7b1c49da8 100644 --- a/broker/gen/model/Pixel_mask_statistics.h +++ b/broker/gen/model/Pixel_mask_statistics.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Plot.cpp b/broker/gen/model/Plot.cpp index a67a6d827..6337cd32f 100644 --- a/broker/gen/model/Plot.cpp +++ b/broker/gen/model/Plot.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Plot.h b/broker/gen/model/Plot.h index b83968167..7ae09cb8e 100644 --- a/broker/gen/model/Plot.h +++ b/broker/gen/model/Plot.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Plot_unit_x.cpp b/broker/gen/model/Plot_unit_x.cpp index 5fdf55a1a..2a85b178c 100644 --- a/broker/gen/model/Plot_unit_x.cpp +++ b/broker/gen/model/Plot_unit_x.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Plot_unit_x.h b/broker/gen/model/Plot_unit_x.h index 9e381f5e2..21d8b1922 100644 --- a/broker/gen/model/Plot_unit_x.h +++ b/broker/gen/model/Plot_unit_x.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Plots.cpp b/broker/gen/model/Plots.cpp index ba0a99272..eb0c2c51b 100644 --- a/broker/gen/model/Plots.cpp +++ b/broker/gen/model/Plots.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Plots.h b/broker/gen/model/Plots.h index cf0b9a3f1..484029f51 100644 --- a/broker/gen/model/Plots.h +++ b/broker/gen/model/Plots.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Powder_calibration_fit_sigma.cpp b/broker/gen/model/Powder_calibration_fit_sigma.cpp index f2225c764..229059c05 100644 --- a/broker/gen/model/Powder_calibration_fit_sigma.cpp +++ b/broker/gen/model/Powder_calibration_fit_sigma.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Powder_calibration_fit_sigma.h b/broker/gen/model/Powder_calibration_fit_sigma.h index 0bce8001e..39e483f5f 100644 --- a/broker/gen/model/Powder_calibration_fit_sigma.h +++ b/broker/gen/model/Powder_calibration_fit_sigma.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Powder_calibration_output.cpp b/broker/gen/model/Powder_calibration_output.cpp index 646374562..98f4df8ce 100644 --- a/broker/gen/model/Powder_calibration_output.cpp +++ b/broker/gen/model/Powder_calibration_output.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Powder_calibration_output.h b/broker/gen/model/Powder_calibration_output.h index 812322a86..3f65c541b 100644 --- a/broker/gen/model/Powder_calibration_output.h +++ b/broker/gen/model/Powder_calibration_output.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Powder_calibration_quality.cpp b/broker/gen/model/Powder_calibration_quality.cpp index 99d467fbf..0e69e88d4 100644 --- a/broker/gen/model/Powder_calibration_quality.cpp +++ b/broker/gen/model/Powder_calibration_quality.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Powder_calibration_quality.h b/broker/gen/model/Powder_calibration_quality.h index 9b5e773c6..75c74ace3 100644 --- a/broker/gen/model/Powder_calibration_quality.h +++ b/broker/gen/model/Powder_calibration_quality.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Powder_calibration_spot_check.cpp b/broker/gen/model/Powder_calibration_spot_check.cpp index 28b5828ce..997d0db0b 100644 --- a/broker/gen/model/Powder_calibration_spot_check.cpp +++ b/broker/gen/model/Powder_calibration_spot_check.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Powder_calibration_spot_check.h b/broker/gen/model/Powder_calibration_spot_check.h index 00acc3e39..baf6307d5 100644 --- a/broker/gen/model/Powder_calibration_spot_check.h +++ b/broker/gen/model/Powder_calibration_spot_check.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Q_binning.cpp b/broker/gen/model/Q_binning.cpp index cc8dfcf22..e08db8da9 100644 --- a/broker/gen/model/Q_binning.cpp +++ b/broker/gen/model/Q_binning.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Q_binning.h b/broker/gen/model/Q_binning.h index e68f75a4f..58ca2fcd6 100644 --- a/broker/gen/model/Q_binning.h +++ b/broker/gen/model/Q_binning.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Roi_azim_list.cpp b/broker/gen/model/Roi_azim_list.cpp index c0b5efd45..d82c77e7c 100644 --- a/broker/gen/model/Roi_azim_list.cpp +++ b/broker/gen/model/Roi_azim_list.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Roi_azim_list.h b/broker/gen/model/Roi_azim_list.h index 51fc40e9d..2a4cf0f1c 100644 --- a/broker/gen/model/Roi_azim_list.h +++ b/broker/gen/model/Roi_azim_list.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Roi_azimuthal.cpp b/broker/gen/model/Roi_azimuthal.cpp index 4c199bc37..4a7bcd238 100644 --- a/broker/gen/model/Roi_azimuthal.cpp +++ b/broker/gen/model/Roi_azimuthal.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Roi_azimuthal.h b/broker/gen/model/Roi_azimuthal.h index 61dc7a3ea..f7f2fe5c1 100644 --- a/broker/gen/model/Roi_azimuthal.h +++ b/broker/gen/model/Roi_azimuthal.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Roi_box.cpp b/broker/gen/model/Roi_box.cpp index 9e55f01fb..caa631213 100644 --- a/broker/gen/model/Roi_box.cpp +++ b/broker/gen/model/Roi_box.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Roi_box.h b/broker/gen/model/Roi_box.h index 4a49a7114..57ce3f7a8 100644 --- a/broker/gen/model/Roi_box.h +++ b/broker/gen/model/Roi_box.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Roi_box_list.cpp b/broker/gen/model/Roi_box_list.cpp index f6cbc9b24..ca7560a70 100644 --- a/broker/gen/model/Roi_box_list.cpp +++ b/broker/gen/model/Roi_box_list.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Roi_box_list.h b/broker/gen/model/Roi_box_list.h index 3125279c5..bed58373c 100644 --- a/broker/gen/model/Roi_box_list.h +++ b/broker/gen/model/Roi_box_list.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Roi_circle.cpp b/broker/gen/model/Roi_circle.cpp index 44c02e91b..68e3c96bd 100644 --- a/broker/gen/model/Roi_circle.cpp +++ b/broker/gen/model/Roi_circle.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Roi_circle.h b/broker/gen/model/Roi_circle.h index 86723a6c8..9e3394824 100644 --- a/broker/gen/model/Roi_circle.h +++ b/broker/gen/model/Roi_circle.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Roi_circle_list.cpp b/broker/gen/model/Roi_circle_list.cpp index 9f60873b1..afdbf4ba4 100644 --- a/broker/gen/model/Roi_circle_list.cpp +++ b/broker/gen/model/Roi_circle_list.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Roi_circle_list.h b/broker/gen/model/Roi_circle_list.h index c9b4ac14e..339ff3530 100644 --- a/broker/gen/model/Roi_circle_list.h +++ b/broker/gen/model/Roi_circle_list.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Roi_definitions.cpp b/broker/gen/model/Roi_definitions.cpp index d9c0fc230..bf76aebe4 100644 --- a/broker/gen/model/Roi_definitions.cpp +++ b/broker/gen/model/Roi_definitions.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Roi_definitions.h b/broker/gen/model/Roi_definitions.h index 87285b8cc..0be5ee43a 100644 --- a/broker/gen/model/Roi_definitions.h +++ b/broker/gen/model/Roi_definitions.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rotation_axis.cpp b/broker/gen/model/Rotation_axis.cpp index 838c09baf..bede3f120 100644 --- a/broker/gen/model/Rotation_axis.cpp +++ b/broker/gen/model/Rotation_axis.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rotation_axis.h b/broker/gen/model/Rotation_axis.h index aa880baea..07d72c1d9 100644 --- a/broker/gen/model/Rotation_axis.h +++ b/broker/gen/model/Rotation_axis.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_live_blob.cpp b/broker/gen/model/Rugnux_live_blob.cpp index 2036b4085..187896f0b 100644 --- a/broker/gen/model/Rugnux_live_blob.cpp +++ b/broker/gen/model/Rugnux_live_blob.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_live_blob.h b/broker/gen/model/Rugnux_live_blob.h index 1d26d2893..a56f3c977 100644 --- a/broker/gen/model/Rugnux_live_blob.h +++ b/broker/gen/model/Rugnux_live_blob.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_live_model.cpp b/broker/gen/model/Rugnux_live_model.cpp index 3304f18db..e521df6b9 100644 --- a/broker/gen/model/Rugnux_live_model.cpp +++ b/broker/gen/model/Rugnux_live_model.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_live_model.h b/broker/gen/model/Rugnux_live_model.h index bbe4523ad..23052ae7b 100644 --- a/broker/gen/model/Rugnux_live_model.h +++ b/broker/gen/model/Rugnux_live_model.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_live_refinement.cpp b/broker/gen/model/Rugnux_live_refinement.cpp index 1786801ea..1495341ea 100644 --- a/broker/gen/model/Rugnux_live_refinement.cpp +++ b/broker/gen/model/Rugnux_live_refinement.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_live_refinement.h b/broker/gen/model/Rugnux_live_refinement.h index 3015c78e5..ca8f5e7dc 100644 --- a/broker/gen/model/Rugnux_live_refinement.h +++ b/broker/gen/model/Rugnux_live_refinement.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_live_status.cpp b/broker/gen/model/Rugnux_live_status.cpp index 1098e1693..2916b2f0f 100644 --- a/broker/gen/model/Rugnux_live_status.cpp +++ b/broker/gen/model/Rugnux_live_status.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_live_status.h b/broker/gen/model/Rugnux_live_status.h index 45e42dea7..4cd0ba02f 100644 --- a/broker/gen/model/Rugnux_live_status.h +++ b/broker/gen/model/Rugnux_live_status.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_live_step.cpp b/broker/gen/model/Rugnux_live_step.cpp index 266ab9d07..b78da5844 100644 --- a/broker/gen/model/Rugnux_live_step.cpp +++ b/broker/gen/model/Rugnux_live_step.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_live_step.h b/broker/gen/model/Rugnux_live_step.h index 77136e9a8..4682b9b3e 100644 --- a/broker/gen/model/Rugnux_live_step.h +++ b/broker/gen/model/Rugnux_live_step.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_live_stills.cpp b/broker/gen/model/Rugnux_live_stills.cpp index 26f285a24..7adf22968 100644 --- a/broker/gen/model/Rugnux_live_stills.cpp +++ b/broker/gen/model/Rugnux_live_stills.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_live_stills.h b/broker/gen/model/Rugnux_live_stills.h index 18bed6ef1..5e4eaba9e 100644 --- a/broker/gen/model/Rugnux_live_stills.h +++ b/broker/gen/model/Rugnux_live_stills.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_settings.cpp b/broker/gen/model/Rugnux_settings.cpp index a0190455f..c9253b775 100644 --- a/broker/gen/model/Rugnux_settings.cpp +++ b/broker/gen/model/Rugnux_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_settings.h b/broker/gen/model/Rugnux_settings.h index 8719ca61e..923cf030b 100644 --- a/broker/gen/model/Rugnux_settings.h +++ b/broker/gen/model/Rugnux_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_status.cpp b/broker/gen/model/Rugnux_status.cpp index 478e9874d..31c63f06a 100644 --- a/broker/gen/model/Rugnux_status.cpp +++ b/broker/gen/model/Rugnux_status.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_status.h b/broker/gen/model/Rugnux_status.h index ff288e87b..1d2b03af2 100644 --- a/broker/gen/model/Rugnux_status.h +++ b/broker/gen/model/Rugnux_status.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_summary_row.cpp b/broker/gen/model/Rugnux_summary_row.cpp index 9fe6406c3..49894dbdb 100644 --- a/broker/gen/model/Rugnux_summary_row.cpp +++ b/broker/gen/model/Rugnux_summary_row.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Rugnux_summary_row.h b/broker/gen/model/Rugnux_summary_row.h index d8763954f..15867dad6 100644 --- a/broker/gen/model/Rugnux_summary_row.h +++ b/broker/gen/model/Rugnux_summary_row.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Scan_result.cpp b/broker/gen/model/Scan_result.cpp index d84120f97..a44acf378 100644 --- a/broker/gen/model/Scan_result.cpp +++ b/broker/gen/model/Scan_result.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Scan_result.h b/broker/gen/model/Scan_result.h index 4450a97ce..e5d81a126 100644 --- a/broker/gen/model/Scan_result.h +++ b/broker/gen/model/Scan_result.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Scan_result_images_inner.cpp b/broker/gen/model/Scan_result_images_inner.cpp index 69ac66513..e2941db20 100644 --- a/broker/gen/model/Scan_result_images_inner.cpp +++ b/broker/gen/model/Scan_result_images_inner.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Scan_result_images_inner.h b/broker/gen/model/Scan_result_images_inner.h index 2e04d5540..d8bcb6efa 100644 --- a/broker/gen/model/Scan_result_images_inner.h +++ b/broker/gen/model/Scan_result_images_inner.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Spot_finding_settings.cpp b/broker/gen/model/Spot_finding_settings.cpp index edf752e35..8062a6a26 100644 --- a/broker/gen/model/Spot_finding_settings.cpp +++ b/broker/gen/model/Spot_finding_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Spot_finding_settings.h b/broker/gen/model/Spot_finding_settings.h index 6f4392004..564854041 100644 --- a/broker/gen/model/Spot_finding_settings.h +++ b/broker/gen/model/Spot_finding_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Standard_detector_geometry.cpp b/broker/gen/model/Standard_detector_geometry.cpp index 2b80f6c57..853772b3a 100644 --- a/broker/gen/model/Standard_detector_geometry.cpp +++ b/broker/gen/model/Standard_detector_geometry.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Standard_detector_geometry.h b/broker/gen/model/Standard_detector_geometry.h index 197281b10..7daf7c07e 100644 --- a/broker/gen/model/Standard_detector_geometry.h +++ b/broker/gen/model/Standard_detector_geometry.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Tcp_settings.cpp b/broker/gen/model/Tcp_settings.cpp index 00758e1a1..da5ad49f9 100644 --- a/broker/gen/model/Tcp_settings.cpp +++ b/broker/gen/model/Tcp_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Tcp_settings.h b/broker/gen/model/Tcp_settings.h index 29b2018a6..86f877ab0 100644 --- a/broker/gen/model/Tcp_settings.h +++ b/broker/gen/model/Tcp_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Unit_cell.cpp b/broker/gen/model/Unit_cell.cpp index 2e2f5efee..e28ab161b 100644 --- a/broker/gen/model/Unit_cell.cpp +++ b/broker/gen/model/Unit_cell.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Unit_cell.h b/broker/gen/model/Unit_cell.h index a5ca5fc5c..68d6950c3 100644 --- a/broker/gen/model/Unit_cell.h +++ b/broker/gen/model/Unit_cell.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Zeromq_metadata_settings.cpp b/broker/gen/model/Zeromq_metadata_settings.cpp index 275151e45..e32317724 100644 --- a/broker/gen/model/Zeromq_metadata_settings.cpp +++ b/broker/gen/model/Zeromq_metadata_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Zeromq_metadata_settings.h b/broker/gen/model/Zeromq_metadata_settings.h index ed33a48b8..147209f4c 100644 --- a/broker/gen/model/Zeromq_metadata_settings.h +++ b/broker/gen/model/Zeromq_metadata_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Zeromq_preview_settings.cpp b/broker/gen/model/Zeromq_preview_settings.cpp index a0395a233..87673b362 100644 --- a/broker/gen/model/Zeromq_preview_settings.cpp +++ b/broker/gen/model/Zeromq_preview_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Zeromq_preview_settings.h b/broker/gen/model/Zeromq_preview_settings.h index 17a7eb6f8..8764c63d1 100644 --- a/broker/gen/model/Zeromq_preview_settings.h +++ b/broker/gen/model/Zeromq_preview_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Zeromq_settings.cpp b/broker/gen/model/Zeromq_settings.cpp index 6beeecece..ac8616564 100644 --- a/broker/gen/model/Zeromq_settings.cpp +++ b/broker/gen/model/Zeromq_settings.cpp @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/gen/model/Zeromq_settings.h b/broker/gen/model/Zeromq_settings.h index 70d0dc370..83e4f52e3 100644 --- a/broker/gen/model/Zeromq_settings.h +++ b/broker/gen/model/Zeromq_settings.h @@ -2,7 +2,7 @@ * Jungfraujoch * API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. * -* The version of the OpenAPI document: 1.0.0-rc.185 +* The version of the OpenAPI document: 1.0.0-rc.186 * Contact: filip.leonarski@psi.ch * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/broker/jfjoch_api.yaml b/broker/jfjoch_api.yaml index 744fe36cf..e02ac297b 100644 --- a/broker/jfjoch_api.yaml +++ b/broker/jfjoch_api.yaml @@ -22,7 +22,7 @@ info: requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms. - version: 1.0.0-rc.185 + version: 1.0.0-rc.186 contact: name: Filip Leonarski (Paul Scherrer Institute) email: filip.leonarski@psi.ch diff --git a/broker/redoc-static.html b/broker/redoc-static.html index e19497bf0..1ead1b505 100644 --- a/broker/redoc-static.html +++ b/broker/redoc-static.html @@ -429,7 +429,7 @@ This format doesn't transmit information about X-axis, only values, so it i 55.627 l 55.6165,55.627 -231.245496,231.24803 c -127.185,127.1864 -231.5279,231.248 -231.873,231.248 -0.3451,0 -104.688, -104.0616 -231.873,-231.248 z - " fill="currentColor">

Jungfraujoch (1.0.0-rc.185)

Download OpenAPI specification:

Filip Leonarski (Paul Scherrer Institute): filip.leonarski@psi.ch License: GPL-3.0

API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). + " fill="currentColor">

Jungfraujoch (1.0.0-rc.186)

Download OpenAPI specification:

Filip Leonarski (Paul Scherrer Institute): filip.leonarski@psi.ch License: GPL-3.0

API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates.

License Clarification

While this API definition is licensed under GPL-3.0, the GPL copyleft provisions do not apply @@ -1144,7 +1144,7 @@ then image might be replaced in the buffer between calling /images and /image.cb