v1.0.0-rc.169 #79
Merged
leonarski_f
merged 83 commits from 2026-09-15 17:09:31 +02:00
rc169 into main
83
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0fccbe21b5 |
symmetry: the cell a group is adopted on is refined under that group
Build Packages / Create release (push) Successful in 15s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 7m21s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 8m28s
Build Packages / build:viewer-tgz:cpu (push) Successful in 9m53s
Build Packages / build:viewer-tgz:cuda (push) Successful in 11m57s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 15m12s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 15m50s
Build Packages / build:windows:nocuda (push) Successful in 17m15s
Build Packages / build:windows:cuda (push) Successful in 19m50s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 23m14s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 19m9s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m12s
Build Packages / build:rugnux:windows (push) Successful in 10m50s
Build Packages / Generate python client (push) Successful in 44s
Build Packages / Build documentation (push) Successful in 1m23s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 20m10s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m23s
Build Packages / build:rpm (rocky8) (push) Successful in 18m2s
Build Packages / build:rpm (rocky9) (push) Successful in 18m22s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m46s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 12m25s
Build Packages / Unit tests (push) Successful in 1h12m4s
A space group confirmed from the intensities AFTER integration is one no constrained fit has produced. The Bravais class is decided on the unrefined indexing candidate, so a two-fold the spot positions never offered leaves the freely refined metric standing in the group's own setting - a C 1 2 1 whose alpha is 88.5 - and that cell goes to the report, the master file and the MTZ. At adoption, re-refine the lattice under the group's constraint against the accumulated rotation spots, at the geometry the images were integrated at, and keep it when the spots do - the bar the indexer's own pseudo-symmetry guard uses. Where they refuse it, report the nearest metric the group fixes and say that a deviation that size is not refinement noise. A cell whose violation is above MAX_METRIC_VIOLATION is the WRONG cell for its group rather than an unconstrained one, and nothing here touches it: a visible mismatch must not become a plausible-looking one. The projection is applied as a change of basis, not as three rebuilt vectors: the three-Coord constructor enforces a right-handed basis, and a left-handed lattice came back with an axis flipped and its free angle replaced by the supplement, which failed the merge outright. Battery over 151 datasets, base against this: 144 byte-identical, 7 cells moved onto their group's metric, no space group changed, no run gained or lost, open arm 94/99 both ways. Every merge statistic of the seven is unchanged except completeness, which rises on four and falls 0.1 % on one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KWkZ1o2aoQ9EimF2wtzBky |
||
|
|
22b4700c1e |
docs: the analysis chapters describe what rc169 actually does
Build Packages / Create release (push) Successful in 44s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 9m57s
Build Packages / build:viewer-tgz:cpu (push) Successful in 11m14s
Build Packages / build:viewer-tgz:cuda (push) Successful in 12m13s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 6m6s
Build Packages / build:windows:nocuda (push) Successful in 16m48s
Build Packages / build:windows:cuda (push) Successful in 19m18s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 23m56s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 13m58s
Build Packages / build:rugnux:windows (push) Successful in 10m46s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 15m38s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 16m0s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 17m3s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 14m19s
Build Packages / Generate python client (push) Successful in 20s
Build Packages / Build documentation (push) Successful in 1m21s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 16m8s
Build Packages / build:rpm (rocky8) (push) Successful in 15m47s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 15m46s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 15m49s
Build Packages / build:rpm (rocky9) (push) Successful in 16m41s
Build Packages / Unit tests (push) Successful in 1h14m45s
The branch changed the beam centre, the beam stop, rotation acceptance, post-refinement, the space-group gates and the resolution cut, and left the three chapters that describe them untouched. Several passages had stopped being out of date and become wrong: the beam stop is described flooding from a centre it no longer floods from, at a ratio it no longer cuts at; the space-group section lists a veto that has been removed; the pseudo-symmetry control is the one that was refuted and replaced; and post-refinement is bounded by a rule it no longer applies. Images and geometry. The whole-detector FFT capture is new here in full - the self-convolution that scores every candidate centre at once, the three surfaces, the shortlist-and-margin rather than a centre, and the walk that refines it - along with the check comparing against the direct beam rather than the PONI, the per-trial spot cache, and the ladder asked at both centres with its harmonic refusal. The beam-stop section is rewritten around what it now does: a Poisson significance beside the ratio, rings drawn about a fitted centre in the order fit - mask - fit, the polarization divided out before the ring comparison, and size rather than connection to the beam as what makes a low region a shadow. The claim that an empty mask means the seeds fell in a gap is gone with the seeds. Powder rings measured from the run's own spots are described in the ice section. Indexing. A rotation lattice is accepted on the spots it explains against the same spots at other frames' angles; the leaner, shallower retry and its harmonic arbiter; the refinement chain committing its best round on the wide gate; and post-refinement's one per cent as a trust region on a step, with the walk, the never-settled refusal and the re-index ratification. The pass guard is compared on the signal each pass measured. Decisions. The error-model b test is a rescue and not a veto, with why the veto went; a confirmed promotion that is refused is decided by merging under it; the modulation null is the shell-wise permutation; and the resolution cut keeps ice out of its fit and is not quoted where the shell table refutes it. Also: the ice flag reaching merged reflections on the rotation path, a report section for the powder keys, --min-indexed-spots matching the usage message it had already been corrected in, and one typo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
d80a9c429e |
docs: the rc169 changelog is seven lines a user can read
Build Packages / Create release (push) Successful in 15s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 8m17s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 9m28s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m40s
Build Packages / build:viewer-tgz:cuda (push) Successful in 11m43s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 14m52s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 15m57s
Build Packages / build:windows:nocuda (push) Successful in 17m24s
Build Packages / build:windows:cuda (push) Successful in 19m58s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 23m32s
Build Packages / build:rugnux:windows (push) Successful in 10m47s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m2s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 21m26s
Build Packages / Generate python client (push) Successful in 59s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 20m58s
Build Packages / Build documentation (push) Successful in 1m25s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 20m49s
Build Packages / build:rpm (rocky8) (push) Successful in 18m18s
Build Packages / build:rpm (rocky9) (push) Successful in 18m2s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 20m0s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 17m42s
Build Packages / Unit tests (push) Successful in 1h37m22s
The branch accumulated 44 bullets, one per landing, written by whoever landed it: individual fixes, mechanisms and investigation outcomes that a user cannot act on. Replaced by seven entries covering the whole branch - build dependencies, offline processing quality, frame rejection and the resolution cut, the machine-readable results report, the viewer, and the broker fixes. Rationale and measurements stay in the commit messages, which already carry them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
0bc1c4b7e0 |
scale/merge: a stretch is removed frame by frame, not on its average
The delta-CC1/2 stage asks an independent channel to corroborate a conviction before any frame leaves the merge, and the commit that landed the acting behaviour said the frames must disagree with the merge on their own per-image correlation. The code took the mean over the stretch, and a mean cannot see a healthy arc lying beside a dead one: on a sweep with two dead arcs half a turn apart, 38 frames at 0.97 of the run's scale and 0.91 of its CC, averaged with the 47 at 0.11 and 0.21 beside them, read 0.52 - below the margin - and came out with them. At matched resolution that pair took CC1/2 from 0.80 to -0.14, where removing the healthy 38 on their own left it at 0.90. The corroboration is now taken frame by frame, on the same running medians MeasureSweepQuality segments the ledger on, so both stages call the same frames bad and one odd frame decides nothing. Each edge is first pulled in off any frame that is at the run's typical scale AND at its typical agreement, and what is left is removed only if it holds no such frame at all and still carries the conviction on its own. A stretch that cannot be taken without healthy frames has more than one thing in it, and refusing it is the honest answer. No new constant: both margins and the median window are MeasureSweepQuality's own. Normal means up on BOTH channels, because the largest measured gains are stretches whose scale has collapsed while their per-image CC stays high, and a CC-only rule would refuse those. Measured on the reproducer in both compiler-flag builds. The rejected row reading 0.97 of the run's scale and 0.91 of its CC - 1.07 and 1.01 when the same source is built without -march, frames that diffracted better than the run average - is gone, and only frames at 6-13% of the run's scale and CC are removed: R_meas 1.3568 -> 1.2840, unique 19381 -> 20302, multiplicity 8.27 -> 9.61, I/sigma 2.23 -> 2.36, d_min 3.227 -> 3.178, CC1/2 -0.0009; in the unflagged build every statistic improves. The two largest corpus gains are byte-identical. The damage controls hold: two byte-identical, one keeping every unique reflection and its resolution shell, and on the cleanest sweep in the corpus three false positives on 10-frame blocks at 0.62-0.92 of the run's scale are refused. This is not a strict tightening, and the risk should not be read as one-sided. The removal loop is iterative, so refusing one candidate lets another be convicted against a reference that still holds the refused stretch - six frames are now removed that the mean kept - and a stretch whose scale has collapsed while its CC stays near the run's is refused by the mean and passed here. What is still placed by an argmin of delta-CC1/2, on a surface the code's own comment calls very nearly flat at the outside of a long defect, is the outer edge inside a defect. Letting the per-frame channel carry that edge to the end of the run of frames both channels call bad would settle it and would take a defect whole rather than in pieces, but it expands removals on about ten datasets and needs its own evidence before it lands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
c53139281a |
resolution: rotation data now keeps ice out of the cutoff fit too
The ice flag that excludes powder-ring reflections from the CC1/2 resolution fit was carried only through MergeOnTheFly, which is the stills merge. Rotation data merges through RotationScaleMerge's own accumulator, which had no ice field, so MergedReflection::on_ice_ring was left false on every merged reflection and the exclusion never fired on the workflow it was written for. Carry it through the rotation merge: an ice-flagged observation marks its merged reflection, on the host loop and in the device MergeAccum kernel (a new per-group output and staging array - the device path writes every accumulator field itself, so a host-only change would have kept the old behaviour there). The intensity is still merged, written and counted; only the cutoff fit skips it. Measured on a rotation dataset with 24% of its reflections on ice rings, with a probe counting the flag at the top of the cutoff fit: 0 of 52180 merged reflections flagged before, 12881 of 52180 after, and the written resolution moves from 1.410 A (fit crossing CC1/2 0.30 at 1.48 A) to 1.372 A (1.44 A). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
6a87d097b1 |
rotation indexing: the chain commits its best round, not the round it stops on
The post-selection refinement chain solves, re-accumulates the reciprocal-space cloud under the refined geometry and solves again, up to twenty times, and then commits whatever round it stopped on. Which round that is, is decided by a test on the detector-tilt step - and the unrestrained perpendicular component of that step is scatter at the size of its own bound, taking values over three decades with no trend. So the round a run commits is settled by the last bits, and the compiler flags settle it: on a C-centred monoclinic crystal refined free, the two primitive axes the lattice makes equal walk from 0.84 % apart one way, through equality, to 1.15 % apart the other, and the plain-Release and -march=x86-64-v3 builds stopped seventeen rounds apart on that one walk. The walk is an overfit, and that is the larger half. Every solve ends by fitting only the spots inside its tightest gate, so a free cell whose metric is near a Bravais class can slide along the one direction the data barely constrain, pulling a core of spots tighter while the periphery falls out of the fit altogether. Over those seventeen rounds the spots inside the tight gate rise 0.295 -> 0.327 while the spots inside the widest gate peak at round three (0.754) and fall to 0.730 - and round three is the round that merges ISa 11.0 against 6.3 and R_meas 0.148 against 0.213, and the one that agrees with the archived reference cell. Nothing the run consulted could see it: indexed fraction, validation frames, validation spots and the tight-gate count all prefer the overfitted end. So score every round on the widest gate - the one XtalOptimizer's first pass selects on and its last pass does not fit, which makes it the population a converged solve is not optimising - and commit the best-scoring round. Two conditions keep that from acting on noise. The score is a COUNT of spots, and a lead of fewer than sqrt(count) of them is inside the count's own noise, so a smaller lead leaves the last round standing; at the default accumulation cap that bound is about 0.9 percentage points, against the 2.4 the case above leads by. And the round taken has to be the less distorted lattice as well as the better-fitting one: LatticeSearch is re-asked every round to MEASURE the drift from the class the metric matches - imposing that class is measured fatal, the snap puts almost everything outside the refinement's own gate - and the earlier round is taken only when it matched the same class and sits closer to it. Same class is a precondition, not a precaution. The deviation is a fraction of the tolerance of whichever class the round matched, so two classes' deviations are not the same quantity; and a round that matched no class reports 0, which means "nothing was asserted", not "undistorted". Comparing that against a class's deviation reads the absence of a constraint as the absence of distortion, and switches the veto off - measured, on 11 of 1132 traced chains, four of them against a round with no class at all - which leaves the spot count deciding alone. That configuration was measured: it costs another crystal R_meas 0.2553 -> 0.3181 by firing on a symmetry-constrained chain whose cell moves 0.10 % over twenty rounds and which is not drifting anywhere. Measured over 31 datasets and 914 chains: an earlier round scores higher on 44 % of chains, but the rule changes the committed round on 1 of 54. Both conditions are why - a chain that has settled scores its rounds within a spot or two of each other, and a symmetry-constrained solve holds its distortion at zero throughout, so the rule cannot fire on either. Twelve paired datasets are byte-identical, including five where it fires on a chain whose result the run does not use, and including the tilt-critical sweeps whose committed chains peak at their last round 19 times out of 19. Both builds now agree on the crystal above at ISa 11.00 against 11.01, and the second flag-dependent crystal is byte-identical in both builds. Deterministic across -N 6/8/32. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
6ba95a84c4 |
symmetry: a refused point group is decided by merging under it
Stage A of the space-group search can confirm a higher point group from the operator correlations and then refuse to adopt it. Every gate that does so is a ratio - the added operators' disagreement H against a parent's, or their R against the best-agreeing operator anywhere - and both references are properties of how the crystal was mounted rather than of its symmetry. A 2-fold within a degree of the spindle records its mates on the same detector pixel half a turn later, so it carries no geometry-dependent systematic at all and by being that clean makes every other operator look bad against it; and the best-agreeing operator can be one of the operators under test, which collapses the ratio on worse data and raises it on better. On 6fid the reference operator's R sits below the crystal's own half-set noise floor; on a cubic-metric case the same gate reads 1.59 on worse data and 2.2 on better. So where a confirmed promotion is refused, ask the merge instead of the ratio. Both groups are scaled and merged over one pinned resolution range - the adopted group's own cut - and the promotion is taken only if neither R_meas nor ISa gets worse. R_meas is multiplicity-corrected, so folding non-equivalent reflections together has to inflate it, and the error model is refitted per merge, so ISa says whether the extra multiplicity was bought with systematic disagreement. CC1/2 and <I/sigma> cannot arbitrate this: both RISE on a false promotion too. Two details the comparison turns on. The refused point group's representative is primitive and symmorphic, so the arm merged under it has to take the ADOPTED group's centring - merged in the bare representative a centred crystal is handed its centring-absent class as data, and reads R_meas 0.1819 where the right group reads 0.1153, which inverts the decision. And the group is asked of Stage B on the adopted merge, so both arms carry the same absence classes and differ by the added rotations alone. Six refused promotions, complete sign separation: 6fid P2 -> P2(1)2(1)2(1) R_meas 0.1215 -> 0.1027 ISa 8.00 -> 10.88 adopt cubic I222 -> I23 R_meas 0.1155 -> 0.1153 ISa 17.34 -> 17.46 adopt 6iu8 P3(1) -> 32 R_meas 0.1357 -> 0.1762 ISa 7.03 -> 5.40 keep 7k1l P6 -> 622 R_meas 0.1538 -> 0.3062 ISa 8.86 -> 2.91 keep 9hnc P2 -> 422 R_meas 0.4982 -> 0.6497 ISa 1.40 -> 1.13 keep 9hs7 P6(1) -> 622 R_meas 0.0849 -> 0.3411 ISa 12.02 -> 1.93 keep 6fid gains the deposited space group and the second gains the one XDS reports; the four that keep their group come out unchanged. The cost is two extra merges, and only on a run that records a refusal - a few in a hundred on the open corpus. 6fid is unchanged across -N 6/8/32 and between a plain Release build and one built with -march=x86-64-v3. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
d00175c8b4 |
resolution: a cut the shell table refutes is not quoted
One run was written to 1.089 A where I/sigma was zero and R_meas 1776 per cent, and the summary said only "Merged to 1.09 A". Two things let that through. The logistic's crossing was taken wherever the fitted curve met the target, even when that point lay past every bin the curve was fitted through - an extrapolation of a fall-off the data never showed, quoted as a measurement. The crossing now has to lie inside the fitted bins, with the one-shell extension still there to spare; outside them the number is read off the bins themselves instead. And the shipped guard asked only for the finest shell whose CC1/2 still reached the target, with no requirement that the curve get there monotonically. A noise shell that climbs back over the bar therefore became the edge of the data. It is the climbing back that disqualifies it, and the program already noticed - it printed "CC1/2 is not monotone with resolution" and then cut there anyway, because the test was developer-only and decided nothing. It decides now, in the report everyone reads, and the duplicate is gone so there is one such test rather than two. The dataset above is written to 1.527 A: CC1/2 0.209 to 0.688, I/sigma 0.40 to 1.26, R_meas 245 to 145 per cent. Forty-four of fifty-one reports are unchanged to the character; of the seven that move, five are cut coarser and every headline number of all five improves, one loses a fit that was an extrapolation without changing anything written, and one gains a quotable fit and loses a warning. This is a guard, not the cause. On four of those five the reflections doing the damage are ice, which the resolution fit now leaves out for its own reasons; the guard still earns its place, because on those four removing the ice alone does not stop the cut being quoted past what the shells support. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
5f1292ca3b |
resolution: the cut is decided on the crystal's own diffraction, not on the ice
Build Packages / Create release (push) Successful in 18s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 6m52s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 9m4s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m37s
Build Packages / build:viewer-tgz:cuda (push) Successful in 12m6s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 15m3s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 15m20s
Build Packages / build:windows:nocuda (push) Successful in 17m9s
Build Packages / build:windows:cuda (push) Successful in 19m45s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 17m54s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m21s
Build Packages / build:rugnux:windows (push) Successful in 11m3s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m46s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 18m49s
Build Packages / Generate python client (push) Successful in 1m4s
Build Packages / Build documentation (push) Successful in 1m29s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m3s
Build Packages / build:rpm (rocky8) (push) Successful in 17m23s
Build Packages / build:rpm (rocky9) (push) Successful in 18m10s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m21s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 13m22s
Build Packages / Unit tests (push) Successful in 1h13m48s
A powder ring is reproducible. Past the crystal's own limit the ice is still there and still the same in both half-sets, so the two halves agree about the ice, and a Pearson correlation cannot tell that agreement from diffraction. Shells have been measured at CC1/2 0.765 where <I/sigma> is -0.1 and R_meas is 470 per cent, and at 0.828 where R_meas is 560 - inside the written range, because the logistic was fitted through them. Against archived references a run whose frames carry ice is written a mean twelve per cent finer than the reference and one without four, and every over-claim past twenty per cent has rings. The same ring drags the curve the other way where the data are good: one shell of forty falls to CC1/2 0.51 because the two strongest ice lines cross it, and the shell either side reads 0.99. That is a real defect of those reflections, but it is narrower than the shell it is reported in, so it defames three quarters of a shell that is fine. Both signs are the same cause, and both leave the fit here. Scaling, the error model and the space-group search already keep ice out of their own fits - what changes is only that the resolution DECISION now reads the curve those three read. The reflections are still merged, still written, still counted in the shell table: the ring contaminates an intensity, it does not make it absent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
154f4f8895 |
scale/merge: a stretch of a sweep whose removal helps the merge is removed
Build Packages / Unit tests (push) Successful in 1h37m59s
Build Packages / build:windows:nocuda (push) Successful in 16m1s
Build Packages / build:windows:cuda (push) Successful in 18m38s
Build Packages / build:viewer-tgz:cpu (push) Successful in 7m16s
Build Packages / build:viewer-tgz:cuda (push) Successful in 8m11s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 6m46s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 6m12s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 14m15s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 15m18s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 16m19s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 15m42s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 17m11s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 17m50s
Build Packages / build:rpm (rocky8) (push) Successful in 15m11s
Build Packages / build:rpm (rocky9) (push) Successful in 16m7s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m18s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 13m0s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 23m19s
Build Packages / Generate python client (push) Successful in 38s
Build Packages / Build documentation (push) Successful in 1m5s
Build Packages / Create release (push) Successful in 26s
Build Packages / build:rugnux:windows (push) Successful in 16m1s
The measurement landed report-only because the code could not say which frames it was talking about. It can now. A candidate is scanned at the batch width and at that width doubled and doubled again, so a defect far longer than one batch is judged against a reference that does not still contain it - on one sweep a 137 degree stretch measured minus 0.0352 at 59 sigma while its worst single batch read minus 0.008 and was refused. Its edges are then found by moving them a frame at a time with the WHOLE stretch re-measured at each position, rather than by halving it: the reflection count the verdict rests on never shrinks as the edge sharpens, which is what used to drive the harm onto one frame. Nothing narrower than one rocking event is ever removed, that width taken from the run's own combine, because a single frame is below what the statistic can resolve. And delta-CC1/2 may confirm a removal, never propose one. The frames must also disagree with the merge on their own per-image correlation, computed on the partials and independent of the statistic being confirmed - otherwise the test selects on the very quantity it then reports as improved, which flatters any removal whatsoever. Measured: on a sweep where dropping 450 frames takes R_meas from 0.28 to 0.21, dropping 450 frames of the same sizes chosen by position makes every number worse. Over a hundred and forty-eight datasets: no space group and no cell changes, no single-frame removals anywhere, and the ledger no longer calls a frame inconsistent with a merge it agrees with better than average. Eighty-eight are byte-identical. Where it acts it is worth R_meas 0.82 to 0.23, ISa 9.5 to 14.1, a resolution shell. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
926b68c658 |
indexing: a rotation lattice is accepted on the spots it explains, not the frames
The acceptance test counted frames: a frame indexed if at least six of its spots and a fifth of them fell on the lattice, and a run was refused if fewer than ten of sixty sampled frames did. That test comes from serial crystallography, where each image is its own experiment. A rotation sweep is one crystal and one orientation matrix, and its frames are not independent of each other - so what the count mostly measured was how many spots happen to land on a frame. A sweep carrying four spots an image cannot reach six on three frames in four however right the lattice is, and one such run found the correct cell and threw it away at three frames of sixty. Count the spots instead: the fraction of all spots in the sampled frames that the lattice explains, against what the same lattice explains when each frame's spots are put at another frame's angle. Same lattice, same spots, same detector, same refinement - only the claim that these spots were seen at these angles is removed. That difference is the evidence, and it carries no spots-per-frame number anywhere, so nothing has to be chosen for a crystal that diffracts weakly. Measured on the sparse sweeps this was found with: ninety-three per cent of spots explained against nought point nought by chance, and they merge to 0.58 A with the deposited cell. Over a hundred and three datasets the chance level never exceeds 2.6 per cent and the smallest true margin is seventeen points; ninety-nine of them are byte-identical, because only the refusal is decided this way - every rescue and every arbiter still counts frames. The floor on --min-indexed-spots goes from six to four. Six was the serial gate's number; four is where a lattice stops being fitted by any three spots. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
5d643498c6 |
post-refinement: a one per cent bound on a step, not on the answer
The fit was refused outright if it wanted to move the detector distance or a cell edge by one per cent or more. The bound exists to stop a runaway, but it was applied to the whole move rather than to a step, so a geometry that was genuinely several per cent out could never be reached - on one dataset the refused fit was 310.000 -> 305.692 mm with a cell within 0.06 per cent of the deposited one, and it was thrown away for being too large a correction. A move of one per cent or more is now re-fitted as a walk of one per cent steps, each seeded where the last arrived and each required to lower the held-out split-half residual, and a walk of more than one step is ratified by re-indexing at where it arrived. That is what the bound was really for: a second lattice does not index better at the new geometry, and a real distance error does - on the dataset this was found with, pass-1 frames go 37/60 to 60/60, R_meas 0.74 to 0.081, ISa 0.98 to 15.0, and the walk stops at a minimum bracketed on both sides without ever being told what the answer should be. A walk that uses every step it is allowed has not settled and is refused; that test is what stops the runaway this replaces the veto with. The first solve is untouched. An earlier version that also widened its box was measurably not inert - two microns on one control moved a 694-reflection merge by three points of R_meas - so a move inside the bound still commits exactly as before. Refusals now say so in the report rather than passing silently. Four of ninety-nine open datasets carry a uniform cell-scale error; ninety-eight of a hundred and three are byte-identical or differ only by the new line, and no run on forty-eight datasets with calibrated headers moves at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
33c0124833 |
viewer: the direct beam reads as x and y rows, like the PONI
Build Packages / Create release (push) Successful in 20s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 8m48s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 10m2s
Build Packages / build:viewer-tgz:cpu (push) Successful in 11m31s
Build Packages / build:viewer-tgz:cuda (push) Successful in 12m17s
Build Packages / build:windows:nocuda (push) Successful in 17m20s
Build Packages / build:windows:cuda (push) Successful in 19m52s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 14m50s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 24m30s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 15m12s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 16m41s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 14m28s
Build Packages / build:rugnux:windows (push) Successful in 11m4s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 17m26s
Build Packages / Generate python client (push) Successful in 37s
Build Packages / build:rpm (rocky8) (push) Successful in 18m50s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m10s
Build Packages / Build documentation (push) Successful in 1m59s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 18m38s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 18m36s
Build Packages / build:rpm (rocky9) (push) Successful in 19m20s
Build Packages / Unit tests (push) Successful in 1h36m23s
One row holding both coordinates sat oddly under a PONI split over two, so the direct beam gets the same treatment - and with it a real "From header" column (the header geometry's own direct beam, the file's beam centre while the header is untilted) and a Change column that says how far the beam actually moved, where the combined row could only show dashes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vRwzF8PLaaa2novAKYorq |
||
|
|
03883924d9 |
viewer: the analysis window carries the rugnux report
The merge-analysis window gets a fourth view beside the plot, the table and the ISa diagnostic: the rugnux result report, the same text the command line writes as <prefix>_report.txt, in a fixed-width read-only page. The viewer's in-process run never wrote that file, so the controller renders it from the run's own results right after the pipeline finishes and hands it along with the finished signal; as with WriteResultReport, a report failure leaves the report empty rather than losing the run. The view buttons now map to page widgets instead of stack indices, so an absent page (no ISa scatter, no report) cannot shift the pages behind it. The calibration result window already opens itself when a calibration job finishes (rc-161); verified against a LaB6 dataset, unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vRwzF8PLaaa2novAKYorq |
||
|
|
52d37bd673 |
viewer: Spots + background is two stacked plots, and the angle axis carries its unit
Qt Charts caps each axis' layout area at a fraction of the chart size, so in a dock this short a horizontal axis title is elided to nothing at any window size - which is also why the resolution plot's vertical "d (A)" was the only axis title that ever drew. The angle axis now carries the degree sign on every tick instead of a title that cannot render, and the lone "d (A)" goes: its labels each carry the unit already, and no other plot titles its y axis. "Spots + background" becomes two half-height plots stacked in the same slot - the spot count above, the background below - each named by a short bold label in its series' colour, its y axis reduced to the two range ends, the lower plot carrying the x axis for both. Clicking either jumps to the image, and Reset zoom resets both. This replaces the twin-axis single chart, whose machinery (the secondary series and its second y axis) is removed: two scales in one plot made the reader match colours across the frame to know which curve reads on which axis. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vRwzF8PLaaa2novAKYorq |
||
|
|
941c6e45cc |
viewer: ring labels in plain type at one screen size, and a cell that holds still
Build Packages / Create release (push) Successful in 59s
Build Packages / build:viewer-tgz:cpu (push) Successful in 11m44s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 9m55s
Build Packages / build:viewer-tgz:cuda (push) Successful in 11m51s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 5m3s
Build Packages / build:windows:nocuda (push) Successful in 16m58s
Build Packages / build:windows:cuda (push) Successful in 19m23s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 23m30s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 15m12s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 14m46s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 15m2s
Build Packages / build:rugnux:windows (push) Successful in 10m52s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 16m16s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 14m53s
Build Packages / Generate python client (push) Successful in 33s
Build Packages / Build documentation (push) Successful in 1m18s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 16m27s
Build Packages / build:rpm (rocky8) (push) Successful in 16m46s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 16m47s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 15m58s
Build Packages / build:rpm (rocky9) (push) Successful in 17m35s
Build Packages / Unit tests (push) Successful in 1h14m23s
The cased white label read as an outline artefact, and its scene-space font - scaled by 1/zoom to hold an apparent size - broke down at high magnification, where a sub-point font rasterises into a white block. The label is now drawn in device coordinates (ItemIgnoresTransformations) at 1.6x the application font, bold, with no outline, so it is the same size at every zoom by construction. The colour is picked from what ends up under it: near-black normally - the label usually sits outside the detector image, on the canvas that is white under every colour map - and white only over the image of a dark map. Not the ring's magenta: where the label meets its own dashes the two blend and the glyphs dissolve. The unit-cell rows pad every number to three integer digits with figure spaces, so 95.0 and 102.3 occupy the same width and the columns do not shift as values change magnitude; no axis reaches 1000 A and no angle 1000 degrees. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vRwzF8PLaaa2novAKYorq |
||
|
|
53d6aa4ddf |
viewer: ring labels that stay in view, and an inspector that reads at a glance
Build Packages / Create release (push) Successful in 17s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 7m35s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 8m55s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m40s
Build Packages / build:viewer-tgz:cuda (push) Successful in 12m1s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 15m35s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 16m9s
Build Packages / build:windows:nocuda (push) Successful in 17m10s
Build Packages / build:windows:cuda (push) Successful in 19m39s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 14m39s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m5s
Build Packages / build:rugnux:windows (push) Successful in 10m49s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m3s
Build Packages / Generate python client (push) Successful in 44s
Build Packages / Build documentation (push) Successful in 1m15s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 19m33s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m18s
Build Packages / build:rpm (rocky8) (push) Successful in 17m17s
Build Packages / build:rpm (rocky9) (push) Successful in 18m28s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 21m45s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 19m38s
Build Packages / Unit tests (push) Successful in 1h38m36s
The resolution-ring label was placed at one of four fixed azimuths with its corner on the ring line, in a hardcoded 16 pt Arial, in the ring's own magenta: it clipped at the viewport edge, ignored the font size, and vanished into busy patterns. It now walks the ring's cached trace, centres on the first point where the whole text fits in the viewport (failing that, clamps the nearest visible point inside), keeps a constant on-screen size of 1.2x the application font, and is cased like the spot markers so it reads on any colour map. In the same function visibleRect now maps viewport()->rect(), which is what mapToScene expects. The dataset plot's angle axis snaps its ticks to round angles - multiples of 90 degrees when the sweep is wide enough - instead of whatever angle the evenly-spaced image ticks landed on. The inspector prints the indexed cell as two aligned rows (alpha below a, beta below b, gamma below c), mosaicity with two decimals, and "No lattice" in grey, keeping red for actual trouble. The armed "Analyze image" button stays navy and is marked by a coral outline instead of turning into a differently-coloured button. The dataset navigation keys leave the image's keyPressEvent - the window-wide filter of the previous commit sees them first. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vRwzF8PLaaa2novAKYorq |
||
|
|
702d8f2a1e |
viewer: the background is a control, and shortcuts work from every panel
The display mapping always had a black point; it was just pinned to zero with no way to move it. It gets the second slider in the display toolbar (0 = off, as before; both sliders are width-capped so the strip stays tight), a logarithmic scale that starts at a true zero, and the wheel with B held, mirroring F for the foreground. The white point now floors at the black point plus one. F, B, A and Home/End/PageUp/PageDown used to reach their targets only with the image focused, because they lived in keyPressEvent. They move into an application-wide event filter that steps aside when the focused widget genuinely uses the key: text entry gets everything, the file tree keeps its navigation keys, and open menus are untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vRwzF8PLaaa2novAKYorq |
||
|
|
7801f309cc |
viewer: the file panel is called Files, not File manager
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vRwzF8PLaaa2novAKYorq |
||
|
|
89272850fb |
viewer: the splitter is a cased centre grip, not a full coral line
The 10 px coral band gave the resize handles the 3:1 contrast that made them findable, but three of them frame the image panel and dominated the view. The separator keeps its 10 px grab area and is painted in the window colour; what marks it is a short dark-amber pill (4.4:1 on salmon) at its midpoint, cased in a near-white rim like the spot markers so the amber does not bleed into the salmon, and taking the brand coral under the cursor. Drawn by a QProxyStyle over Fusion, since a stylesheet can only fill the whole band. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015vRwzF8PLaaa2novAKYorq |
||
|
|
2c1a459195 |
build: zlib and Eigen come from the build, not from the host
Build Packages / Create release (push) Successful in 16s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 8m15s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 8m18s
Build Packages / build:viewer-tgz:cpu (push) Successful in 9m16s
Build Packages / build:viewer-tgz:cuda (push) Successful in 10m48s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 12m48s
Build Packages / build:windows:nocuda (push) Successful in 17m20s
Build Packages / build:windows:cuda (push) Successful in 19m43s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 22m47s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 16m6s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 17m17s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 18m25s
Build Packages / Generate python client (push) Successful in 37s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 16m37s
Build Packages / Build documentation (push) Successful in 46s
Build Packages / build:rugnux:windows (push) Successful in 10m50s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 16m8s
Build Packages / build:rpm (rocky8) (push) Successful in 15m39s
Build Packages / build:rpm (rocky9) (push) Successful in 16m1s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 14m2s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 14m22s
Build Packages / Unit tests (push) Successful in 1h13m32s
They were the last two dependencies a machine had to supply itself. Everything else - spdlog, zstd, HDF5, Catch2, libzmq, libtiff, FFTW, Ceres, the indexer, libcurl, libjpeg-turbo - the build fetches, so a checkout and a compiler were almost enough and then were not. Now they are: a plain Release configure needs neither zlib-devel nor eigen3-devel. Eigen is fetched as headers and never added as a subdirectory; a two-file config package is written into the build tree and Eigen3_DIR points at it, so Ceres and the indexer resolve their own find_package(Eigen3) through the ordinary config path. This is what makes it safe: OVERRIDE_FIND_PACKAGE remains banned, for the reason the note in CMakeLists has always given - it segfaults the CMake that ships with Visual Studio, one configure in three and every configure with Ceres CUDA on - and the shim never enters that code path at all, which the empty pkgRedirects directory in a configured build shows. The version file uses CMake's own AnyNewerVersion rather than Eigen's same-major rule, which is what declined Ceres' cross-major range and cost us a mixed-Eigen hunt in August. zlib is zlib-ng in compat mode, built during the configure into a prefix that FindZLIB is pointed at ahead of the system one. Built, not added as a subdirectory: libcurl puts ZLIB::ZLIB in CMAKE_REQUIRED_LIBRARIES and try_compile against an alias of an in-tree target is a hard error, so the viewer build breaks. A real archive has no such problem. It is forced position-independent, which the distro archive is and a default zlib-ng build is not - the xds plugin does not link otherwise. An existing installation still wins when it is asked for: -DZLIB_ROOT= and -DEigen3_DIR= configure as before and skip the fetches. About eighteen seconds on a first configure, nothing on a rebuild, and nothing added to the build itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
dc0d7e65d8 |
space group: the b-veto goes
It refused a higher point group when the added operator cost too much extra error in the scaling model, relative to the parent's. It was guarded by "H could not be computed" and followed by the H refusal, so it could only ever act where H - the one statistic that discriminates here - is blind. Over 1213 candidate rows on 148 datasets it is reached seven times, every one of them with H not a number, fires twice on a single dataset, and changes no result anywhere. What it does do is make a space group depend on the compiler flags. On that one dataset a plain Release build refuses a correct P2_12_12_1 and reports P2, while the same source built for x86-64-v3 does not, because the quantity it compares is not a measurement at all - it is the bisection's own ceiling, over a parent whose error term is larger than the signal it is added to. A decision that moves with the flags is worse than one that is consistently wrong, and no bound repairs this one: genuine promotions now measure up to 5.8 where the bound was calibrated at 1.62 against a twin at 2.1, so the two populations have swapped sides. The case it was meant to catch - a merohedral twin too sparse for H - is already answered below it by the added-operator R gate, whose reference is the global best rather than the parent, so it still speaks where H does not. No space group and no cell changes on 148 datasets: 100 open, 34 in-house, 14 private. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
beae384bb7 |
scale/merge: what each stretch of a sweep costs the merged data, measured
Build Packages / Create release (push) Successful in 32s
Build Packages / build:viewer-tgz:cpu (push) Successful in 7m40s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 6m54s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 7m7s
Build Packages / build:viewer-tgz:cuda (push) Successful in 8m29s
Build Packages / build:windows:nocuda (push) Successful in 16m47s
Build Packages / build:windows:cuda (push) Successful in 19m18s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 21m1s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 11m53s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 14m3s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 12m48s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 14m34s
Build Packages / build:rugnux:windows (push) Successful in 10m19s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 15m38s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 16m35s
Build Packages / Generate python client (push) Successful in 47s
Build Packages / Build documentation (push) Successful in 1m19s
Build Packages / build:rpm (rocky8) (push) Successful in 15m59s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 15m34s
Build Packages / build:rpm (rocky9) (push) Successful in 16m26s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 9m48s
Build Packages / Unit tests (push) Successful in 1h34m17s
The sweep-quality diagnostic says a range is weak; it cannot say whether that weakness costs anything. A 60 degree stretch of poor diffraction and a radiation damage tail look alike in scale and correlation, and on one dataset they measure -0.0758 +- 0.0005 and -0.0019: one is 150 sigma of real harm, the other is nothing once the corrections have done their work. So measure it, per batch of the sweep, as the change in CC1/2 when that batch is left out - Assmann, Brehm & Diederichs (2016) Acta Cryst. D72, 1071-1081. In the sigma-tau form, so no half-set is drawn and nothing depends on a seed; against the observed scatter of each reflection's own observations rather than the error model's sigmas, which would invert the sign and make a bad batch read as added precision; and on the 10 degree batch the damage monitor already uses, because a single frame is far below what the statistic can resolve - the precision goes as one over the root of the reflection count, and 0.1 degrees of rotation does not have the reflections. Report only. Nothing is dropped, no observation is weighted differently, and every existing key of every dataset tested is unchanged to the digit. The disposition column says merged for all of them; acting on it needs a frame attribution this code does not yet have, since a full is currently labelled with its peak frame rather than with the frames its partials came from. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
c367e88261 |
viewer: spot markers are cased in black, so they can be seen on any colour map
Build Packages / Create release (push) Successful in 22s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 20m57s
Build Packages / build:windows:nocuda (push) Successful in 15m48s
Build Packages / build:windows:cuda (push) Successful in 20m58s
Build Packages / build:viewer-tgz:cpu (push) Successful in 8m30s
Build Packages / build:rugnux:windows (push) Successful in 15m40s
Build Packages / build:viewer-tgz:cuda (push) Successful in 9m40s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 6m23s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 5m44s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 10m51s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 13m15s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 12m18s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 14m41s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 14m35s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 14m59s
Build Packages / Generate python client (push) Successful in 30s
Build Packages / Build documentation (push) Successful in 1m18s
Build Packages / build:rpm (rocky8) (push) Successful in 15m39s
Build Packages / build:rpm (rocky9) (push) Successful in 16m24s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 14m52s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 15m15s
Build Packages / Unit tests (push) Successful in 2h9m29s
A green marker on the default colour map measured 1.37:1 against its background and a cyan one 1.25:1 - invisible to anyone, not only to a viewer who is colour blind. Two markers were also indistinguishable from what they sat on: a second-lattice marker on the coral beam stop at 1.10:1, and a feature box on a magenta bad pixel at 1.00:1, which is to say a pixel row through it contains one colour and the marker is simply not there. Draw each one twice, a black pen a little wider underneath. No hue moves: magenta stays the strong colour no colour map contains, cyan stays the colour of ice. Against the light maps every marker reaches 21:1 and the two collisions become 8.40:1 and 6.70:1; against a dark map the bright core already carried it and nothing changes, so one mechanism serves both without asking which map is loaded. Spots only. The predicted-reflection markers have the same complaint with a different cause - dark red on a black-low map - which a black casing cannot mend, and casing them cost 55% of the overlay rebuild for no benefit: 16.0 to 34.3 ms with them, 16.0 to 19.1 ms without. Both overlays are off by default and a frame's spots are counted in hundreds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
7e186d6d51 |
rugnux: the beam-centre check compares against the direct beam, not the PONI
Build Packages / Create release (push) Successful in 31s
Build Packages / build:viewer-tgz:cpu (push) Successful in 9m40s
Build Packages / build:viewer-tgz:cuda (push) Successful in 10m14s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 5m45s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 5m8s
Build Packages / build:windows:nocuda (push) Successful in 16m33s
Build Packages / build:windows:cuda (push) Successful in 18m57s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 20m41s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 9m24s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 10m6s
Build Packages / build:rugnux:windows (push) Successful in 11m13s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 13m12s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 12m38s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 14m9s
Build Packages / build:rpm (rocky8) (push) Successful in 14m25s
Build Packages / Generate python client (push) Successful in 15s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 15m22s
Build Packages / Build documentation (push) Successful in 1m17s
Build Packages / build:rpm (rocky9) (push) Successful in 15m26s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 16m6s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 14m1s
Build Packages / Unit tests (push) Successful in 2h5m11s
Both the FFT capture and the background walk measure where the beam lands - the centrosymmetry of the scattered background is about that point. The geometry's beam_x_pxl is the PONI, which parts from it by distance*tan(rot)/pixel the moment the detector is tilted. Comparing the two reported a disagreement the data do not have: 3.3 px at 120 mm on the in-house files, whose header carries a 0.234 degree tilt, against a stated need of 0.7 px. Measured on a calibrant at 110/150/200/300 mm, the capture sits 0.5-1.5 px from the fitted direct beam and 5.0 to 17.9 px from the fitted PONI. With the tilt at zero the two are the same point and the line is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
e3da313c84 |
reader: a goniometer whose inner circle is inclined says so in its axis table
A fixed-chi stage carries phi at a standing angle to the base circle, and states that angle in the CBF _axis table and nowhere else - its "# Chi" line reads 0.0000, because no chi circle is driven. ParseAxisTable kept only rotation axes hanging off nothing, so the inclination was discarded and RotationAxis() fell back to that zero: a phi scan was run about the base axis, tens of degrees from the one the crystal actually turned about. Measured on a magic-angle head 54.74 degrees off omega: the axis goes from 1 0 0 to 0.577382 -0.707087 -0.408237, against dxtbx's 0.5774 -0.7071 -0.4083, and the first pass from a P-centred triclinic 27.2 58.1 126.2 that indexes nothing to P-orthorhombic 5.16 14.05 14.81, which is the cell DIALS and XDS both reach on these frames. The mirrored inclination returns to nothing, so it is that angle and not any perturbation of the axis. Every other header in the corpus states one rotation axis, which is every Eulerian head, and is untouched. The two that share this table were written three days later by a template that puts the same angle on the "# Chi" line, agreeing to 0.03 degrees - they read identically either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
b243fa21d3 |
docs: a Linux jfjoch_viewer build needs OpenSSL 3 and the krb5 headers
Build Packages / Create release (push) Successful in 25s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 7m9s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 7m59s
Build Packages / build:viewer-tgz:cpu (push) Successful in 8m23s
Build Packages / build:viewer-tgz:cuda (push) Successful in 9m50s
Build Packages / build:windows:nocuda (push) Successful in 16m46s
Build Packages / build:windows:cuda (push) Successful in 19m12s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 20m6s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 14m4s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 13m38s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 13m4s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 14m58s
Build Packages / build:rugnux:windows (push) Successful in 10m20s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 17m24s
Build Packages / Generate python client (push) Successful in 44s
Build Packages / Build documentation (push) Successful in 1m46s
Build Packages / build:rpm (rocky8) (push) Successful in 20m2s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 18m27s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 18m37s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 20m57s
Build Packages / build:rpm (rocky9) (push) Successful in 20m16s
Build Packages / Unit tests (push) Successful in 2h7m25s
The fetched libcurl 8.22 refuses to compile against OpenSSL 1.1 ("OpenSSL 3.0.0
or greater required"), so a viewer build on an older distro fails deep inside a
dependency with nothing in the requirements list to explain it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56
|
||
|
|
94c5437885 |
writer: the temporary file name is defined when the arm date is missing
tmp_suffix was left uninitialised and only assigned inside the `if (!arm_date.empty())` body or in the catch handler. An empty arm_date takes neither path, so the `.tmp` file name was formatted from an indeterminate value. Initialise it to the same wall-clock fallback the catch handler already uses. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
7dc749fb31 |
tests: the synthetic merged reflections are built without narrowing conversions
MergedReflection's I, sigma and d are floats; the designated initialisers fed them doubles, which is a narrowing conversion in a braced initialiser and five of the nine default-visible warnings the project emitted. The casts are what the compiler was doing anyway - the values are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
167af290c2 |
build: Release CUDA objects carry -DNDEBUG, like every other Release object
CMAKE_CUDA_FLAGS_RELEASE replaced CMake's default (`-O3 -DNDEBUG`) instead of adding to it, so `.cu` translation units were the only ones in a Release build compiled with assertions live. No project `.cu` uses a runtime `assert` today - the one assertion in ImageSpotFinderGPU.cu is a `static_assert`, unaffected - but the headers a `.cu` pulls in do, and Eigen's `eigen_assert` in particular was being expanded differently in `.cu` and `.cpp` translation units of the same build. Costs a full `.cu` rebuild once. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
b3c9987ed7 |
build: the vendored HLS headers no longer warn, and three stale CMake rules go
Four unrelated but equally small build-tree fixes, none of which changes what any binary does: * fpga/hls: `fpga/include/` holds the vendored Xilinx `ap_int`/`ap_fixed` headers. Included with a plain `-I` they emit 790 default-visible `-Wdeprecated-enum-enum-conversion` warnings across the targets that consume them - every warning a developer sees in a build of the receiver, the broker, the tests or the HLS simulation comes from that one header. `SYSTEM` propagates as `-isystem`, silencing the vendored header while leaving warnings in our own HLS sources visible. Measured on one HLS source: 78 -> 0 at default flags, 289 -> 56 under -Wall. * compression: `zstd/lib` was the old in-tree vendored location, gone from the tree since zstd moved to FetchContent. It stays harmless only while nothing sits there; a checkout with a leftover `compression/zstd/` puts that copy's internal headers (`common/huf.h`, `common/mem.h`) ahead of the fetched zstd the library actually links. The fetched zstd already exports its `lib/` root, so dropping the entry is all that is needed. * xds-plugin: `target_link_options(... --exclude-libs,ALL)` was applied twice, once unconditionally and once guarded by `if(UNIX AND NOT APPLE)`. Keep the guarded one. * xds-plugin's install rule uses `CMAKE_INSTALL_LIBDIR`, which was only ever set as a side effect of a fetched dependency including GNUInstallDirs first. Include it at the top level, where it belongs. * broker: `redoc-static.html` was installed to `<prefix>/jfjoch/frontend/`, a path nothing serves - the broker mounts its frontend from `share/jfjoch/frontend`, whose copy of the API reference is `dist/openapi.html` written by `npm run redocly`, while `redoc-static.html` itself reaches the bundle through Sphinx's `html_static_path`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
5546c165d7 |
viewer: text the eye can follow, and a font size the user can set
Build Packages / Create release (push) Successful in 23s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 7m30s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 7m41s
Build Packages / build:viewer-tgz:cpu (push) Successful in 8m9s
Build Packages / build:viewer-tgz:cuda (push) Successful in 9m19s
Build Packages / build:windows:nocuda (push) Successful in 16m37s
Build Packages / build:windows:cuda (push) Successful in 19m3s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 21m50s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 17m19s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 16m7s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 17m53s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 17m51s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 16m21s
Build Packages / Generate python client (push) Successful in 31s
Build Packages / build:rugnux:windows (push) Successful in 10m4s
Build Packages / Build documentation (push) Successful in 47s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 14m44s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 16m11s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 16m22s
Build Packages / build:rpm (rocky8) (push) Successful in 16m33s
Build Packages / build:rpm (rocky9) (push) Successful in 17m12s
Build Packages / Unit tests (push) Successful in 1h54m27s
Written for the person this program is mostly used by: someone reading a diffraction pattern on a 4K monitor with eyesight that is no longer 25. The rotation-angle axis of the dataset plot showed four ellipses and nothing else. Qt Charts has truncated axis labels by default since 6.2, so the plot has been unreadable along that axis for as long as we have shipped it; turning truncation off then exposed the last label clipped by the widget edge, hence the right margin that now follows the font instead of a hard 8 px. The splitters were 10 px of the window's own salmon - a 1.00:1 contrast ratio, which is to say invisible. They are now a darker coral band, 3.31:1, past the 3:1 that WCAG asks of a control's boundary, with the brand coral as the hover state. The brand colour itself is 2.38:1 on salmon and cannot carry the idle band. View > Font size, Ctrl+plus and Ctrl+minus, 100/125/150 %. It multiplies whatever the desktop already set rather than replacing it, so a session already scaled by GNOME steps from there and steps back exactly. Qt does not deliver a runtime application font to widgets that already exist, so the change is broadcast to them; per-widget attributes survive it. The Inspector is the panel people read all day, and its value column was capped at a flat 600 px, which clipped as soon as the text grew. The bounds are character widths now, chosen to be exactly 600 at the default font and to grow with it. The dataset plot's curves are a fifth of the font height rather than 2 px, for the same reason: a curve is read at the distance the labels beside it are. Also the four QChart::axisX/axisY calls deprecated since 6.0, replaced by what they do internally, and the two button rows the larger fonts clipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
b673a05176 |
space group: the modulation's control is drawn from data with the modulation removed
Build Packages / Create release (push) Successful in 21s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 7m17s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 8m19s
Build Packages / build:viewer-tgz:cpu (push) Successful in 9m39s
Build Packages / build:viewer-tgz:cuda (push) Successful in 11m13s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 12m16s
Build Packages / build:windows:nocuda (push) Successful in 16m42s
Build Packages / build:windows:cuda (push) Successful in 19m4s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 21m36s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 17m3s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 18m14s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 16m16s
Build Packages / build:rugnux:windows (push) Successful in 10m29s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 16m5s
Build Packages / Generate python client (push) Successful in 17s
Build Packages / Build documentation (push) Successful in 1m25s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 16m41s
Build Packages / build:rpm (rocky8) (push) Successful in 14m1s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 14m7s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 13m54s
Build Packages / build:rpm (rocky9) (push) Successful in 15m5s
Build Packages / Unit tests (push) Successful in 1h52m20s
The pseudo-translation test has two halves: a Patterson peak, judged against a permutation null, and an intensity modulation along that peak's vector, judged against a control. The control was the same greedy search run from random starting vectors over the SAME intensities - so it measured the search AND whatever modulation the crystal carries, because a greedy search started anywhere walks into a real modulation's basin. Measured over 110 merged datasets, single draws of that control reached 199x and 6162x on crystals whose own modulation reads 4.0x and 67x, and on 28 of the 110 the control came out at or above the signal it is subtracted from. Shuffle the intensities inside each resolution shell instead - the permutation the Patterson null in the same function already uses - and start the search where the measurement starts. The reflection count, the E^2 distribution and the phase-bin populations are untouched; the only thing removed is the correlation between a reflection's intensity and h.u, which is the thing the modulation claims to see. No threshold moved and no verdict changed: over those 110 datasets the same 8 are called, none gained, none lost. What goes away is a control that reads its own signal, and with it the one route by which something upstream can move the control while the modulation stands still. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 (cherry picked from commit bae480b9202b403629fcad5dc3408aec4998bd6a) |
||
|
|
54968a245f |
viewer: the file manager starts where the data is, and follows a dataset opened elsewhere
Build Packages / Create release (push) Successful in 18s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 7m41s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 8m36s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m51s
Build Packages / build:viewer-tgz:cuda (push) Successful in 11m28s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 15m37s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 15m40s
Build Packages / build:windows:nocuda (push) Successful in 16m50s
Build Packages / build:windows:cuda (push) Successful in 19m6s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 23m20s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 15m57s
Build Packages / build:rugnux:windows (push) Successful in 10m23s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m21s
Build Packages / Generate python client (push) Successful in 48s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 19m11s
Build Packages / Build documentation (push) Successful in 1m23s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m36s
Build Packages / build:rpm (rocky8) (push) Successful in 17m45s
Build Packages / build:rpm (rocky9) (push) Successful in 19m12s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 14m20s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 15m11s
Build Packages / Unit tests (push) Successful in 1h42m33s
Two things a beamline user did every session by hand. The browser's default root now resolves JUNGFRAUJOCH_DATA_ROOT, then the root last used, then - for an SLS account, which is eNNNNN against a pNNNNN group - /sls/mx/data/pNNNNN/raw, then home. The environment variable stays the general mechanism and keeps precedence; the account rule is there because it needs nothing rolled out to benefit from it. Both go through DefaultRoot(), so a reset resolves them the same way a first run does. A dataset opened from the File menu or over D-Bus now expands and selects itself in the browser when it lies inside the current root. Containment is tested on resolved paths, so a sibling directory sharing a prefix is not inside it. Neither says anything when it does not apply: an account that is not a beamline one, a directory that is not there, a dataset outside the root - each is a bare return, because for everyone who is not at this one facility the condition should be invisible. Following never writes the root, so the location the user chose is still the location they get back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
f4a838afb9 |
merge viewer-x11-perf: composite dataset plot, per-kind plot choices, spot-count band
Build Packages / Create release (push) Successful in 29s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 7m44s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 9m4s
Build Packages / build:viewer-tgz:cpu (push) Successful in 9m32s
Build Packages / build:viewer-tgz:cuda (push) Successful in 10m52s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 12m37s
Build Packages / build:windows:nocuda (push) Successful in 16m29s
Build Packages / build:windows:cuda (push) Successful in 19m2s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 21m46s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 16m7s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 17m55s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 16m38s
Build Packages / build:rugnux:windows (push) Successful in 10m30s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 16m18s
Build Packages / Generate python client (push) Successful in 14s
Build Packages / Build documentation (push) Successful in 1m29s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 16m13s
Build Packages / build:rpm (rocky8) (push) Successful in 14m23s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m39s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 13m43s
Build Packages / build:rpm (rocky9) (push) Successful in 14m42s
Build Packages / Unit tests (push) Successful in 1h12m43s
|
||
|
|
8c4d991447 |
viewer: the spot count takes the lower band of the composite plot
Swapped which curve occupies which half of the spots + background plot: the spot count now sits in the lower band with its axis floor at its natural minimum (zero), and the background in the upper band with its axis floor pushed down instead. A spot count below zero is nonsense and its axis never shows one now; a background below zero is at least conceivable on an integrating detector, so that is the axis that carries the inflated range. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WbjnQhutnboy4qbxwmgDAX |
||
|
|
7d872fd762 |
viewer: plot choices persist per dataset kind, and settings reset live
The dataset-info plot remembers the selected quantity and the Grid/Per-image toggle states across restarts, with grid scans and standard scans each keeping their own favourite (QSettings groups datasetInfoPlot/gridScan and /standard). Choices are stored as the combo item's data code, not its index, so a saved choice survives the plot list changing and a code no longer offered falls back to the first item. Favourites are saved on every explicit user action and applied whenever a dataset of the other kind loads; loads of the same kind keep the in-session state, and the live-source default still wins until the user picks. The unconditional Grid auto-check on grid scans moved into the favourite's default, so unchecking Grid on a grid dataset now sticks. View > "Reset all settings to defaults" clears the whole settings store after confirmation and brings the running session to a first-start state in place: default layout, window size, display settings (Indigo, Auto off, HDR off), plot choices, and the file manager root. Because the live state then equals the defaults, saving on close stays enabled and later changes persist as usual. File > Quit now closes the main window instead of calling QApplication::quit(), so closeEvent runs and the persistent settings are saved on that path too - previously they were saved only when the window was closed by the window manager. The file manager root persistence was audited and left unchanged: saved on every successful change, restored behind JUNGFRAUJOCH_DATA_ROOT, and a vanished directory falls back to the home directory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WbjnQhutnboy4qbxwmgDAX |
||
|
|
337ea76fda |
viewer: the composite plot reads on two coloured axes, and fluorescence goes
The "Spots + background" dataset plot now scales each quantity on its own axis - background on the left, spot count on the right - with each axis's labels and line coloured like its curve, so which axis reads which quantity is visible at a glance. Each axis is ranged from its own series alone, so live growth of one quantity never rescales the other; on top of that the background's range is inflated upward and the spot count's downward, each by its own span, so the two curves occupy separate halves of the plot instead of crossing. The spot count stays the primary series (current-image marker, hover, click-to-load, overlays). Trade-off accepted: the spots axis shows ticks below zero in its empty lower band. The per-image plot selector no longer offers the X-ray fluorescence spectrum; the reader-side fluorescence plumbing is untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WbjnQhutnboy4qbxwmgDAX |
||
|
|
51f2b306a2 |
two-pass: pass-1's cell is scaled to the distance it is integrated at
Build Packages / Create release (push) Successful in 15s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 7m49s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 9m8s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m23s
Build Packages / build:viewer-tgz:cuda (push) Successful in 12m16s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 15m37s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 16m14s
Build Packages / build:windows:nocuda (push) Successful in 16m45s
Build Packages / build:windows:cuda (push) Successful in 19m15s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 16m20s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m33s
Build Packages / build:rugnux:windows (push) Successful in 10m12s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 18m35s
Build Packages / Generate python client (push) Successful in 57s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 18m42s
Build Packages / Build documentation (push) Successful in 1m47s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m18s
Build Packages / build:rpm (rocky8) (push) Successful in 16m30s
Build Packages / build:rpm (rocky9) (push) Successful in 16m54s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 13m31s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 14m34s
Build Packages / Unit tests (push) Successful in 1h37m31s
When the refined-geometry re-index indexes too few frames, the run falls back to pass 1's lattice and integrates it at the post-refined geometry. That lattice was fitted at pass 1's detector distance. A real-space cell is measured against the distance the spots were seen at, so carrying it across a distance change scales the whole cell by the ratio of the two - the code re-scored it at the new geometry but never re-fitted it. Measured on a 545 A axis where post-refinement moved the distance 225 -> 226 mm: the shipped cell came out 0.4 per cent small, exactly 225/226, against a deposited value the run's own post-refinement had already matched to 0.02 per cent. The same build given the refined distance to start with shipped the right cell, which is what identified the stale pairing. Two datasets in a hundred reach this fallback; on the other the distance moves 0.08 per cent, so nobody saw it. The fallback now scales the cell to the distance it will be used at. The orientation is untouched. |
||
|
|
a5ba1f8e95 |
docs: what a rugnux run costs in host and device memory
Measured on rc.169 over rotation sweeps from 2.5 to 18 megapixels: both peaks fall in scaling and merging, and both track the number of integrated observations rather than the detector size - two sweeps on the same detector differ five-fold. Device memory is linear in -N at about 8 bytes per detector pixel per worker over a fixed floor, so the 16 GB card that fits four light runs fits one heavy one. --no-merge and -N lower the peak; --no-export-unmerged was measured and does not - it saves disk, not RAM. Also records what happens when the card is too small: device decode, the beam-stop projection and an indexer thread degrade to the host, everything else fails the run with a non-zero exit rather than returning a short dataset. |
||
|
|
0f11897615 |
beam centre: the combination and the search follow the transforms onto the device
With the transforms on cuFFT the capture's cost was no longer the transforms: on a 16 Mpixel detector they are 26 ms, while bringing the four convolution surfaces back is 0.9 s, the masked Pearson over them is 0.4 s and the greedy search is 1.4 s. All three now run where the surfaces already are, and only the shortlist crosses PCIe. The engine interface gains one virtual, PointShortlist, whose default is exactly what the code did before - PointSurfaces, then BeamCenterPointScore, then BeamCenterShortlist2D on the host - so the CPU path is unchanged and an engine that has nothing to gain overrides nothing. Those two functions stop being file-local and become the documented reference the device kernels are held to. Memory: three 2h x 2w surfaces, not four. The last inverse's output is read where it lies in the transform buffer, which nothing overwrites afterwards. Peak on a 16 Mpixel detector goes from 1.68 GB to 2.55 GB, and DeviceMemoryNeeded counts the surfaces so the fit check and the CPU fallback still cover it. The parity test is widened to compensate for what the two paths no longer share. It now compares the four convolutions, the scored surface (the device kernel against BeamCenterPointScore, which is what PointScoreSurface exists for), the shortlist (the device search against BeamCenterShortlist2D on one surface, exactly - the device path is deterministic), and the whole score. Where the surfaces are compared the tolerance is stated against the shortlist's reach: r is a ratio of two cancellations, so a weak centre carries 1e-4 of the transform's 1e-6 whichever path computed it, and at a peak - the only part of the surface anything reads - the two agree to 7.5e-6. |
||
|
|
6326c8b301 |
beam centre: the capture's transforms run on the GPU, and the CPU stays the fallback
BeamCenterFFTScore is split the way the FFT indexer is: an engine interface with a cuFFT implementation and an fftw3f one, chosen by whether CUDA is compiled in, a device is visible and the card has room for the image. Everything that decides anything - the preparation, the masked Pearson, the shortlist and the margins - is shared, so the engines can differ only in how the four convolutions are computed, and the parity test compares two shortlists rather than two answers. The whole-detector transform was the entire added cost of the capture (4-15 s a run on CPU, all of it the transforms). On the device it is milliseconds, so the composition is now cheaper than the walk it replaced rather than dearer - which is what makes 2x2 binning, the other way out, unnecessary: full resolution is affordable and the binned capture was measured to pick a neighbouring peak on one dataset of 51. Device discipline, because the card is shared with the run's own analysis workers: the four convolution surfaces are brought back to the host and combined there, so the device holds only one real buffer and three spectra; the spectrum of the image is reused for its square; plans and buffers are created inside the call that needs them and freed when it returns; and an image that would not fit is scored on the CPU instead. Tests, none of which need a GPU or a dataset: the autoconvolution identity (an exactly symmetric image is recovered at integer and half-pixel centres, scoring exactly 1), shift equivariance, the four convolutions against brute force, the no-variance overlap that VARIANCE_FLOOR exists for, the smooth pad, GPU against CPU, and the composition - a walk seeded at the capture where the walk alone declines, the fallback to the capture alone at BEAM_CENTER_CAPTURE_SIGMA_PXL, and the beam stop being blanked out of the scored image. |
||
|
|
d75102b9b4 | beam centre: the FFT captures the whole detector, the walk refines what it captured | ||
|
|
1d1d7fd686 |
rugnux: whole-detector FFT beam-centre capture, measured on every run, consumed by none
The centrosymmetry score of the pre-scan projection is a self-convolution, so one FFT set scores every candidate centre on the detector at half-pixel spacing - the cost is O(N log N) and independent of how far the header centre is from the truth, where every existing search pays per pixel of error. BeamCenterFFT computes the 2D point-inversion score (a background measurement, the physics FindBeamCenterFromBackground fits locally) and the two 1D line-mirror scores (for the along-spindle coordinate this is exact Friedel physics, sharp exactly where the indexing count is blind), and returns a non-maximum-suppressed shortlist plus a peak-to-runner-up margin per surface - a shortlist and a margin, never a centre: measured on 17 datasets the surface can be locally flat (~25 px), and the margin is what says so. Measured on the gross-header cases that motivate it (offline, float64 reference): a header 351 px wrong is captured at rank 1 within 1.0 px from 30 frames, and still from a 30 deg wedge; two 73 px placeholder headers at rank 1 within 0.5 px; a dataset whose header is ~170 px wrong but which no centre can index is flagged by the lowest margin of the set (0.8 %) instead of being answered confidently. Capture survives 30-120 deg wedges on all four sets tried (union of 8 candidates within 6 px everywhere). Numerics: fftwf (the tree's FFTW is single precision) with the valid-pixel mean subtracted before the transform - the masked Pearson is exactly invariant under a global shift, and the subtraction removes the large-term cancellation - and the per-element combination done in double. Against the float64 numpy reference the shortlist positions are identical and the margins agree to 3e-6 absolute on the two controls whose margins are 0.3 %. An overlap with no image variance (a mirrored empty region) carries no evidence and minted r values of 4-274 in float64 as much as float32; such centres are now not scored (VARIANCE_FLOOR). Wired report-only into the pre-scan next to the background estimate: one log line with the strongest candidates, the margins and the wall time. Nothing consumes it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
a7ed4e2d34 |
beam stop: a bright ring is not a beam stop
Inside the stop a whole ring is blocked, so its own median is blocked too and the per-pixel comparison has nothing to work with. The walk that covers that case declared a ring to be inside the stop when its background fell below a third of the LARGEST background of any ring further out - and on a sample whose background peaks in a strong ring away from the beam, the ordinary background inside that ring is legitimately below a third of the peak. The walk then runs out to the ring and returns a filled disk of good detector: on one corpus dataset 16 % of the area, with diffraction rings plainly visible inside the disk it masked, and on another 2.5 %. The defect predates the branch; what changed is that the flood from the beam centre used to discard the disk whenever its seeds landed on invalid pixels, which is how the first of those two datasets came back with an empty mask instead of a wrong one. Dropping that anchor was right, and it made this visible. A ring is now compared against what this detector's background typically is - the median over the rings the walk is willing to judge - which is robust to a bright ring and to a corner ring of a handful of pixels alike, and is less code. Evaluated over 151 corpus datasets: the number returning a disk larger than 0.2 % of the detector falls from 12 to 3, the two pathological cases collapse (radius 974 -> 68 px and 188 -> 12 px), and genuine stops move by a few pixels at most (152 -> 132, 122 -> 120, 70 -> 64). The new test fails on the old walk and passes on this one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
99910e187e |
beam stop: a ring is only flat once the polarization is divided out
The shadow test compares a pixel with the median of its ring, which assumes the background is flat around a ring with nothing in the beam. It is not: a polarized source suppresses the background in its own plane by 1 - sin^2(2 theta), a factor of three at 2 theta = 55 degrees and four at 70. That is several times the dip the test is looking for, so on a short-distance geometry the two horizontal lobes of every outer ring read as shadow. Measured over the corpus it cost one dataset 5.1 % of its detector, and another 11.2 %, with nothing visible under either mask. The mean projection is therefore divided by the Kahn factor before the ring comparison, and the Poisson deficit multiplies it back in so the significance is still the significance of the counts that were recorded. The factor is the geometry's own CalcAzIntPolarizationCorr, evaluated about the centre the caller named rather than the one in the file: the azimuth is the whole point of this correction, and the geometry is what knows where the polarization plane lies once the detector is tilted, the stored image quarter-turned or the detector rotated in its own plane. Six corpus datasets are quarter-turned, and a flat-detector formula on the stored image's own axes gets them 90 degrees out of phase - measured, it takes one of them from 16.5 % masked to 22.8 % where the geometry takes it to 4.4 %. Polarization is also the only correction a ring carries that varies along it; solid angle, detector and air absorption are functions of 2 theta and the ring median absorbs them. Measured, mask fraction of the detector: a clean 110 mm sweep 0.82 -> 0.51 %, a clean 200 mm 18 Mpx sweep 0.94 -> 0.80 %, a 150 mm sweep whose header centre is 351 px out 5.05 -> 0.74 %, and the sweep with a third of the detector behind a pin 32.22 -> 32.83 %. Clean detectors lose mask, a real obstruction gains it. Across 151 corpus datasets nothing gains more than a tenth of a percent from the correction and 26 lose some, 6 of them by more than 3 percentage points. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
dd95704d7b |
beam stop: the rings are drawn about the centre the data measures, not the one the file claims
Every pixel is compared against the ring it sits on, and the rings were drawn about the beam centre in the file. Displacing that centre costs nothing for tens of pixels and a great deal beyond: on clean sweeps 50 px leaves the mask where it was, 150 px returns a tenth of the detector as shadow with nothing blocking it. Header centres are wrong by that much - one sweep in the corpus states one 351 px from where the data put it, on a run that otherwise indexes every frame and merges at CC1/2 0.999 - so a shadow search that trusts the header is a live hazard now that an empty mask is no longer the fail-safe it used to be. The centre is therefore fitted from the projection BEFORE the mask is read, and named to the finder with BeamCenter(). It costs no frame of its own: the fit reads the same per-pixel mean the mask is computed from. Measured on the phase boundary, the pre-scan grows by 0.4-0.6 s on a 2.5 Mpx detector and by 3-4 s on an 18 Mpx one. The other half of the circle is that this fit is itself biased by a shadow it has not yet masked - on a sweep with a third of the detector behind a pin it put the centre 5 px from the truth and called it 0.40 px, ten of its own sigmas out. It does not have to be unbiased here. The ring comparison does not notice tens of pixels and the bias is a few, and the centre the run REPORTS and consumes is not this one: it is the fit that already ran after the mask was loaded, unchanged, with the shadow out of the way. The order is fit, mask, fit, and only the second answer leaves the function. Two rounds are enough, measured rather than assumed: on four clean sweeps the fit before the mask and the fit after it agree to 0.03 px, so a third round would draw the same rings. The centre estimator is not made robust to a shadow either - it already robustifies across azimuthal sectors, three IRLS rounds on the per-sector shifts, and that is what returned 5 px at 0.40 px, because a third of the azimuth is a second population and not an outlier. And no guard refuses to mask when the fitted centre disagrees with the header: a header 351 px out is precisely the case this has to survive. Mask, before -> after (pixels, and per cent of the detector): clean, 2.5 Mpx 20851 (0.82%) -> 20670 (0.82%) clean, 2.5 Mpx 33308 (1.32%) -> 32730 (1.29%) clean, 18 Mpx, low bkg 173804 (0.96%) -> 170031 (0.94%) clean, 18 Mpx 250196 (1.38%) -> 249915 (1.38%) a third behind a pin 818184 (32.32%) -> 815699 (32.22%) header 351 px out 54183 (0.87%) -> 314592 (5.05%) The clean sweeps do not move; on all four the measured centre is 1-9 px from the file's and the mask shrinks by under 3 %. The pinned sweep does not move either - its header centre happens to be right, so the rings were already where they belong - and its merge is unchanged to the third decimal. The last row goes the other way and is not the ordering: with the rings finally about the beam, this geometry reaches 2 theta = 68 deg, where the polarization of the source modulates the background around a ring by a factor of three. That is four times the threshold this test cuts at, so the horizontal lobes read as shadow. The commit that follows removes that confound; with it the same row reads 39 k px (0.63 %), which is the holder arm and nothing else. Even uncorrected the run's answer is unchanged - same space group, same cell to the third decimal, 12658 unique reflections either way, R_meas 9.06 -> 9.10 %, and 1 % of the multiplicity lost. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d412306c77 |
beam stop: a shadow does not have to touch the direct beam
The shadow finder kept only the low region connected to the beam centre, flooding outward from seeds within 4 px of it. That is written for the beam stop and its holder arm, an object that touches the direct beam. Hardware that stands in the beam further out - a pin, a loop - casts a shadow that begins some way out in radius, with lit detector between it and the stop, and no bridge crosses that gap. On a sweep where such a shadow covers 38 % of the detector the per-ring test found the whole of it and connectivity then discarded 950 k pixels, leaving the beam-stop disk alone; those pixels went into integration as measured-and-near-zero. What makes the test specific instead is size: keep the connected components that hold at least MIN_SHADOW_PIXELS core pixels. A shadow is cast by something physical and is correspondingly large, while the background wanders a pixel or two at a time. Measured on five clean in-house sweeps spanning 0.05 to 9.5 counts/px/frame of background, every one returns exactly one such component - the beam stop - and the largest spurious candidate anywhere is 74 pixels, a 27x margin. MIN_EXPECTED_COUNTS becomes a Poisson significance. A ratio says nothing when the background behind it is a handful of photons, so the deficit is now required to be significant against its own scatter, sqrt(2 (E - N + N ln(N/E))) over the pooled counts. This is what makes the comparison scale-free rather than tuned to one exposure: rebuilt from six frames of a low-background sweep, the same thresholds without it mask three quarters of the detector and with it mask none of it. The index of dispersion, measured over the frames, does NOT do this job - it is 1.0 inside the shadow and 1.0 outside it, because a shadowed pixel is Poisson at a low rate and a lit one is Poisson at a high rate. Only the rate relative to the ring separates them. The rest follows: the core ratio moves 0.35 -> 0.50 and the half-shadow reach 0.72 within 14 px -> 0.75 within 30 px, since a pin's penumbra is much wider than a beam stop edge's; the ring walk that finds the rings lying wholly inside the stop keeps its own threshold so the central disk does not move; and those rings join the region after the size filter rather than being asked to be large themselves. On the shadowed sweep this takes the mask from 16,587 px (0.66 %) to 818,184 px (32.32 %), against 956,705 px (37.79 %) for the reference implementation, at a precision of 0.92 against it. Clean sweeps move from 0.60 % to 0.82 %, the width of the same arm. One thing this costs: an empty mask used to be a statement about the beam centre, because the seeds were placed at it. It no longer is, and a centre wrong by a couple of hundred pixels now draws the rings across the background's own fall-off and returns a large spurious mask instead of an empty one. The log message says so. The fix is ordering - the centre is measured from this same projection a moment later - and is not attempted here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
591c8cbc4b |
beam centre: the depth question is asked at both centres
The ladder inside the beam-centre check ran only at the MEASURED centre, and its count was then set against the whole spot list at the file's. Two things differ between those two numbers - where the beam is and how much of each frame the pass reads - so the count cannot say which of them the gain came from, and the run attributed all of it to the centre. Measured on one crystal: at the measured centre, 15 px and 2.8 sigma from the file's, the ladder reached 40/60 on a cell five times the deposited primitive volume; at the file's own centre it reaches 49/60 on the deposited cell. The run moved the centre, post-refined the supercell, and - standing at 40/60 - skipped the beam-centre walk that finds the crystal's own centre six pixels the other way. It shipped a C-centred monoclinic cell of the right volume in the wrong lattice, on 43 per cent of the unique reflections. The ladder now runs at both centres and the centre moves only when it is the centre that pays. An exact tie keeps the file's centre. Where the file's centre wins, the run takes that rung and says the depth, not the centre, was the problem. No arbiter can be asked instead here: at the measured centre the true cell is not among the rungs, so no rung-versus-rung comparison contains the right answer, and the standing pass the winner would be judged against is itself a wrong 18 thousand cubic Angstrom cell 32 times smaller. |
||
|
|
a751aec32c |
beam centre: a walk that is still travelling is not at its fixed point
The background fit stops early when two consecutive steps make an obtuse angle, which was meant to catch the period-2 limit cycle a damped walk falls into at its fixed point. A travelling walk wobbles too, and the test cannot tell the two apart: on one sweep whose centre is 170 px from the header, a single 0.56 px step between 2 px ones turned twice in passing and ended the fit 118 px short, with a last step of 4.96 px - i.e. while still moving at 2.5 px an iteration. Which of the two happens is set by the last ulp of the per-sector regression, so the same source measured (1231.55,1311.77) built with -march=x86-64-v3 and (1224.37,1430.16) without it, and the run indexed every frame of the sweep in one build and refused to index at all in the other. A reversal now counts only where the pair CANCELS - the two steps together move the centre less than the smaller of them would alone - which is what a limit cycle does and what a wobble does not. The travel budget goes from 100 iterations to 300, because this walk needs 107-115 to reach CONVERGED_PXL and 100 cut it off in the last few pixels of its approach, leaving where it stopped to the build as well. Both builds now converge on the same centre to 0.047 px, and both recover the sweep. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
3d9c8b878b |
beam-centre rescue: a rung is scored at its own centre, and not adopted on a harmonic
The ladder set a trial centre and then scored it on spots found at the starting centre. Raw centroids do not move with the beam centre, but which spots are in the list does: the resolution limit, the ice-ring flag, the beam-stop mask and the strongest-N ranking are all computed against a radius. Every rung was therefore judged on the starting centre's evidence, and the count it produced was not a function of the centre it named. A trial centre now gets its own azimuthal mapping, spot engines and spot cache; the starting centre's cache is parked and restored, so a ladder that adopts nothing leaves the run exactly as it was. Measured against standalone runs pinned at the same centres, the ladder's score goes from a mean absolute error of 21.8 validation frames to 0.43, and at nine pinned centres the patched and unpatched binaries agree exactly - the change is confined to the ladder. Scoring honestly is not by itself an improvement: with the count honest, one crystal adopted a rung at a real 56/60 whose cell is a four-fold axis harmonic of the standing one, and merged the supercell. A longer cell indexes more, which is why the harmonic arbiter exists elsewhere in this file. A rung whose primitive volume is an integer or sqrt(3) multiple of the standing cell is now refused, and that crystal walks past both harmonics to a 60/60 rung. Over a hundred rotation datasets no space group, cell or run status changes. Eight merges move: one gains 126 per cent of its unique reflections, one 3.7, five move by less than half a per cent, and one loses 3.2 - that last was attributed separately and is the difference between two failed merges, both peaking at a half-dataset correlation of 5 per cent, with the honest run the slightly better of the two. Wall time rises 1.7 per cent, the cost of finding spots again at each trial centre. On a corpus processed from a deliberately destroyed beam centre the same change recovers eight datasets outright on their deposited space group and cell, restores a ninth's space-group call by returning its axial absence counts to the values the correct geometry gives, and withdraws one adoption that had shipped a wrong cell in the wrong crystal system at a half-dataset correlation of 0.10. Note that only the spot-cache half of this does the work. Reading the indexer's geometry per call rather than from construction was measured to change nothing on a rotation ladder, because the per-frame validation dispatches to a path that takes its geometry from the forced indexer result; it is kept because the copy was misleading to read, not because it moves a number. |
||
|
|
7a62032765 |
rugnux: the first-pass ladder does not adopt an axis sub-multiple
The ladder picks its rung on the validation-frame count, and that count cannot arbitrate an axis harmonic: a cell twice as long has to place every spot twice as accurately to score the same, which is why the scheme comparison already refuses to decide such a pair on it. Across rungs the bias is worse, because the leanest and shallowest rung - the one that scores highest on a harmonic - is also the one a smaller cell is easiest on, so the two biases compound. Measured on one crystal: the "30 spots/image, stop at 4.91 A" rung indexed 48/60 frames on a halved c axis while the deeper rungs found 2x, 3x and 4x of the true one. The ladder adopted the halved cell together with a beam centre 15 px from the file's, which pre-empted the beam-centre walk that otherwise moves 6 px and finds the crystal's own lattice at 42/60; the run ended in a different Bravais class on a cell half as long. So the winner is now put through the same axis-harmonic arbiter the scheme comparison uses - which of the two cells accounts for more of the validation frames' spots - against every rung that cleared the caller's bar and whose primitive volume is a near-integer multiple of the winner's. A winner that loses that question is a sub-multiple and the ladder adopts nothing; promoting the rival instead would be no better, since on the measured crystal the rival is a multiple of the true axis in its own right. The comparison is made on the spot list the run stands on, so both cells are counted against the same spots. Measured: the crystal above returns to the cell and space group it reaches with no ladder at all, and the three crystals the ladder was built for are unchanged (two of them never reach the guard, the third meets it and keeps its cell, the doubled rival accounting for 923 spots against its 929). |
||
|
|
de839021dc |
rugnux: the whole-spot-list rung does not overflow the cloud's reserve
The rung that reads every spot of a frame passes SIZE_MAX as the per-image cap, and the accumulated cloud reserved max_spots_per_image * images - which overflows and throws length_error out of vector::reserve, killing the run. The count held is already tracked exactly, so reserve that. |
||
|
|
1b28dd2072 |
rugnux: a first pass that finds no lattice tries a leaner, shallower one
A crystal sitting in a crystalline powder floods the first pass with spots that are not its own. Two in-house crystals gave 2112 spots a frame against the 80 a clean crystal on the same beamline gives, and no scheme indexed a single validation frame; the FFT took a 10-30 A cell out of the shells. A third dataset, with no powder at all, failed for the neighbouring reason: its file beam centre is 170 px out, and the measured centre was tested with the full spot list, indexed nothing there either, and was thrown away. Powder rings are now MEASURED from each run's own pre-scan spots rather than read off the fixed hexagonal-ice list, and reported on every run (POWDER_*) whether or not anything acted on them - hexagonal ice is the only phase that can be named in advance, and 16 of the 24 rings on one of these crystals are ice while the rest are not. A first pass that ends with no usable lattice then retries over how much of each frame it reads: the strongest 30, 80, 200 or all spots an image, each at the file's resolution and at the resolutions a quarter and a half of this sample's own spots lie coarser than, with the measured rings set aside where there are any. Decided late on the validation-frame count, the way the rotation-axis sign already is, and adopted only on a win of a sixth of the frames. The same ladder is asked inside the beam-centre check, where a centre that is wrong and a spot list that is too deep otherwise hide each other. Measured over 146 datasets: unchanged on all of them - the open arm scores 91/99 either way and the 46 SLS datasets are identical on every scientific field - while the three that failed now index. The deposited cell is recovered to 0.1% on the beam-centre case, at 100% of frames and 0.597 A. |
||
|
|
ea37056fe9 |
rugnux: judge the two-pass on the signal each pass measured, not on a pooled CC1/2
The rotation two-pass reverts to the header geometry when the refined pass looks worse. It decided that on the search merge's pooled, uncut, pre-correction P1 CC1/2 - a number that reads 0.13 on a crystal whose data merge at 0.995, and that moves 0.93 to 0.76 between two runs whose merged data agree to 0.7% of R_meas. Two crystals were sent back to a geometry that ships 22% fewer reflections than the one they refused. Compare instead what each pass MEASURED: reflections merged at I/sigma >= 2. Against an external arbiter that objective agrees with which geometry is the more accurate one on 23 of 27 arm-dataset pairs, where self-consistency measures agree on 13 of 27. Count it on both sides over the centring-allowed reflections, so a pass that finds a centring the other did not is not judged on the extinguished half its P1 merge holds as noise (two crystals compared merges holding 2.0x the reflections of the other), and ask the axial-row arm only where the two passes are in the same setting, which is the only case where the two counts are counts of the same reflections. The bar is a decisive loss rather than a bare inequality: the two passes never integrate the same way - the canonical pass integrates at the measured spot width and predicts with the pre-pass's smoothed mosaicity - so a difference that small is not evidence about the geometry either way. |
||
|
|
7b868fb9f5 |
viewer: raster window composition and remote-display repaint throttling
Over "ssh -X" the viewer was near unusable, and the July finding that every
repaint uploads the whole window (5.3 MiB at the default size) regardless of
how small the damage is turns out to have a single cause: the reciprocal-space
window constructed a QOpenGLWidget at startup, and one dormant QOpenGLWidget -
unmapped, in a window nobody ever opened - switches Qt's window composition
onto the OpenGL swapchain path. That path re-presents the entire window on
every flush (and is also the llvmpipe CPU burn seen on headless boxes). The
reciprocal-space window was a placeholder feature and is removed; with it gone
Qt flushes through the raster backing store, which sends only the damaged
region.
On top of that, interactions that repainted once per input event are held to
the hover cadence (15 Hz) on remote sessions only: panning accumulates its
pixel delta, wheel zoom its steps, a recolour burst its final value, live
playback keeps only the newest frame (the worker ack stays per-frame), and the
toolbar counter/slider readbacks coalesce the same way. Each throttle applies
inline with a single-shot tail, the same shape as the existing hover limiter,
so the view always lands exactly where the gesture put it. Local sessions are
unchanged - every event still applies inline.
Remote sessions are detected once at startup (xcb platform and a DISPLAY with
a host part, i.e. "localhost:10.0" from SSH forwarding or "host:0"); the
JFJOCH_VIEWER_REMOTE environment variable and a View-menu toggle override the
detection in either direction.
Measured through a byte-counting X relay against Xvfb with MIT-SHM disabled
(client-to-server bytes are what the SSH link carries), same scripted
interaction sequence, 1200x1100 window, KiB:
rc169 removal only removal+throttles
hover, 50 motions 82088 2040 2040
pan, 50 motions 290046 12087 4778
wheel zoom x10 71143 12603 7649
ctrl-wheel x6 65672 7194 4797
frame step x10 284572 21454 21454
Removing the reciprocal-space window from the source measured byte-identical
to only deferring its GL view to first show, confirming the cost is the live
QOpenGLWidget, not the code being compiled in. The image panel renders
pixel-identically (AE=0) to rc169 after identical zoom+pan gestures, with the
throttles on and off.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WbjnQhutnboy4qbxwmgDAX
|
||
|
|
483cba0aaa |
report: authorship and the acknowledgement open the report
They were at the foot, where a reader who stops after the first screen never reaches them. A results file travels further than the person who produced it, so who wrote the program, what its licence permits and whose methods it implements belong before the data rather than after. The header's explanatory paragraph goes with them. It described what a `KEY= value` line is and what --developer does, which a reader works out without being told. |
||
|
|
db16f06e0f |
report: the header says what the lines are, and stops there
The opening paragraph explained that the keys are a stable interface, that REPORT_VERSION records when it changed, and that --developer relocates rather than loses the long explanations. All true, none of it what a reader opening a results file needs in the first six lines, and the register gave it away as something no crystallographer had written. Two sentences remain: what the three kinds of line are, and what --developer adds. |
||
|
|
d1d1748fed |
docs: typographical fixes in the hardware and deployment pages
Jungfruajoch, frotend, allways, statisitics, pixed point, "the two has to be purchases separately", "servers are the moment", "how it processed". These pages are published to Read The Docs. |
||
|
|
50a23c2945 |
docs: six instruction-shaped sentences become plain statements
A prose pass over docs/ for text that reads as written by, or addressed
to, a language model. The documentation is almost entirely in the house
voice already; what needed changing was six hollow directives in the
older infrastructure pages ('it is important to ensure...', 'extra care
has to be taken...', a leftover 'Be explicit about the gaps' addressed
to the document's own author). Each is rewritten as a statement of fact
with the technical content untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56
|
||
|
|
43decf99a8 |
report: REPORT_VERSION counts releases, not format changes
The constant went to 14 on the reasoning that the format history above it listed thirteen entries while the number still read 7. That is backwards. A reader only ever meets the report that was built from main, so a bump inside a release branch numbers a format nobody received, and several of those entries shipped together in one release. Seven is what main ships; this release changes the format once, so it is eight. The convention is now written down beside the constant and in the report documentation, because this is the second time it has been read the other way. |
||
|
|
f8763ca875 |
rugnux report: parsable comments, honest build provenance, terms of use
Every line of <prefix>_report.txt is now KEY= value, a # comment, or blank
(REPORT_VERSION 14; warnings print as "# WARNING:"), so a consumer keeps the
data by dropping the # lines. The header records the exact build: the git
commit, stamped at BUILD time by common/GitInfoStamp.cmake so it cannot go
stale in a reconfigured tree, with -dirty marking uncommitted changes; the
CMAKE_CXX_FLAGS of the build, because two builds of one commit can differ by
flags alone; and the release page of exactly this version, whose tag is the
version string. The foot states authorship and terms of use: GPLv3, free for
academic institutions and commercial companies alike.
Every sentence phrased as a directive to the reader ("Read the shell table
rather than quoting a single number", "Do not refine against the written
reflections", "Treat the reported cell as a supercell candidate", "believe
the _BEFORE_SEARCH one") is rewritten as a statement of fact about the
measurement: a report describes the data, it does not instruct whoever - or
whatever - reads it. Also fixed: REPORT_VERSION had stayed at 7 while its own
history comment reached 13; ANISOTROPY_D_MIN_* printed "nan" against the
report's own no-placeholder rule; "1 condition(s) need attention"; "rises by"
on a signed quantity.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56
|
||
|
|
df1536db7f |
unmerged export: the comments name the FLIGHT column they forgot
The header and the two in-function comments describing what the unmerged MTZ carries still said "two columns" and "raw counts are I / LP * QE", written before the flight-path term and its FLIGHT column were added. The code, the test and docs/RUGNUX_INTEGRATION.md all say I / LP * QE * FLIGHT; only these comments did not. Also drops the self-referential note in Reflection.h about a comment having been overtaken, and states the flight factor's symbols and the medium rather than assuming air. No behaviour change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011GxZqDiFP3KqriBhNdcR56 |
||
|
|
051e2aef46 |
ci: pin every job on the 2609 images
Build Packages / Create release (push) Successful in 29s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 8m4s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 9m20s
Build Packages / build:viewer-tgz:cpu (push) Successful in 9m59s
Build Packages / build:viewer-tgz:cuda (push) Successful in 11m53s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 15m39s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 15m54s
Build Packages / build:windows:nocuda (push) Successful in 16m32s
Build Packages / build:windows:cuda (push) Successful in 19m0s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 23m16s
Build Packages / build:rugnux:windows (push) Successful in 10m0s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 18m37s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 20m9s
Build Packages / Generate python client (push) Successful in 44s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 19m26s
Build Packages / Build documentation (push) Successful in 1m49s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m21s
Build Packages / build:rpm (rocky8) (push) Successful in 17m28s
Build Packages / build:rpm (rocky9) (push) Successful in 17m33s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m57s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 10m53s
Build Packages / Unit tests (push) Successful in 1h11m53s
One tag for all four images, replacing the 2607b/2608b split. The 2609 images carry Qt 6.11.1, CMake 3.31.8 and Ninja 1.13.2 identically, so a build no longer depends on which distro's tooling a job happened to land on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6iekpDa2xiKobEj1R5xnp |
||
|
|
090a01e3cf |
docker: Qt 6.11.1, with the pinned ninja and xcb package it needs
Qt 6.11 did not build on either Rocky image, for two unrelated reasons: rocky8 carried ninja 1.8.2, which cannot parse what Qt 6.11 generates -- "multiple outputs aren't (yet?) supported by depslog", a ninja limitation lifted in 1.10. Ninja is now pinned at 1.13.2 from the upstream release binary, for the same reason CMake is: the distro versions spanned 1.8.2 to 1.11.1 and moved under us. rocky9 installed xcb-util-*-devel, a glob that does not match the plain xcb-util-devel package, so the XCB::UTIL target Qt 6.11's xcb platform plugin requires was absent. Qt 6.9 did not ask for it, which is why it appeared only now. build-qt.sh also derives the download path's series from the version, so a bump no longer 404s against the old 6.9 directory. Built on all four images, and jfjoch_viewer links against the static Qt 6.11.1 on both rockys (2708/2708 targets, Qt fully static -- only system X/GL remain dynamic). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6iekpDa2xiKobEj1R5xnp |
||
|
|
cc4ded5aad |
ci: build a release in one run instead of two
Releasing took two full runs of the matrix. The manual dispatch built every package and threw them away -- the uploads were gated on being on a tag -- then created the tag, whose push re-ran the whole workflow only to rebuild the same packages and upload them. That is where ">3 hours from push to release" came from, and the second run tested nothing the first had not. Now the run that creates the release also fills it. create-release runs first and every uploading job waits for it, so the uploads can target the release directly; they are gated on the same dispatch input that creates it rather than on ref_type. Tags no longer trigger the workflow at all, because nothing is left for that run to do. The release is still created before its assets exist, exactly as it was when the tag run populated it. create-release now runs on every event, because a skipped job skips its dependents. On an ordinary push it is a bare checkout: both the LFS pull and the release call are gated, so nothing is fetched or created. Also: the pull_request trigger is gone. Branches live in this repository, so the push trigger had already run the whole workflow on the identical commit. And the HDF5 consumer tests no longer repeat on a tag run, which no longer exists anyway. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6iekpDa2xiKobEj1R5xnp |
||
|
|
c94fe9e740 |
docker: one copy of the shared build steps, and a pinned CMake
OpenSSL, libdbus, Qt and Eigen were built from byte-identical shell in all four images. They now live once in docker/common/ and each Dockerfile runs them, which is why the build context is docker/ rather than docker/<variant>/. The four copies are what let the git HTTP/1.1 fix land in both Ubuntu images and neither Rocky one. CMake is pinned and installed from cmake.org instead of coming from the distro. The base images ship three different versions (EL8 3.26.5, EL9 3.31.8, Jammy 3.22 via a Kitware repo held at 3.26, Noble 3.28.3) and they move at every base refresh; that spread is what makes a configure fail on one image and pass on the next, as the CMP0169 deprecation that only appears on 3.31 did. The Kitware apt repo that existed solely to hold CMake at 3.26 is gone with it. Also: duplicate zlib-static (both rockys) and libxcb-devel (rocky9) package entries, and an ENV PATH=/usr/bin that never changed anything (ubuntu2204). Dockerfiles drop from 872 to 684 lines against ~110 lines of shared script. All four build clean through the eigen step; Qt is unchanged text and is covered by the image rebuild. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6iekpDa2xiKobEj1R5xnp |
||
|
|
cb55a60002 |
ci: one job for the HDF5 consumer tests, pinned images, one -j
The DIALS and three XDS jobs each configured and built the same jfjoch_hdf5_test on the same rocky9 image -- four identical builds for twelve short test cases. They are now one job that builds once and runs all twelve, and every case runs even when an earlier one fails: each is a subshell under `set -e`, so a case stops at its own first bad command without taking the rest down, and a summary at the end decides the job's result. Each XDS case also clears its directory first, which two of them did not do before their first run -- on a runner that reuses its workspace volume, that let XDS index a previous run's files. Every job now names its image instead of taking whatever the runner label points at. Two jobs had pinned much older images (rocky8:2511, ubuntu2404:2508), so the unit tests did not run on the toolchain that builds the packages. The rockys stay on 2607b because no 2608 was ever built for them. Unit tests build with -j16 like everything else; -j48 from one job starves the other seven at runner capacity 8. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6iekpDa2xiKobEj1R5xnp |
||
|
|
9efe72f9b1 |
licenses: list libcurl in the notices
libcurl is fetched and statically linked into the shipped jfjoch_viewer, but it had no row, no licence text and no COLLECT.sh entry. Its collection is the one that only resolves after a viewer-enabled configure, which is noted at the entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6iekpDa2xiKobEj1R5xnp |
||
|
|
4f3d2f85b6 |
licenses: follow the dependency updates in the notices
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 7m30s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 10m7s
Build Packages / build:viewer-tgz:cpu (push) Successful in 12m2s
Build Packages / build:viewer-tgz:cuda (push) Successful in 12m52s
Build Packages / build:windows:nocuda (push) Successful in 16m58s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 18m35s
Build Packages / build:windows:cuda (push) Successful in 19m12s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 20m57s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 21m54s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m26s
Build Packages / build:rugnux:windows (push) Successful in 10m27s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 22m32s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 21m23s
Build Packages / build:rpm (rocky8) (push) Successful in 22m7s
Build Packages / build:rpm (rocky9) (push) Successful in 19m55s
Build Packages / Generate python client (push) Successful in 33s
Build Packages / Build documentation (push) Successful in 1m21s
Build Packages / Create release (push) Skipped
Build Packages / XDS test (durin plugin) (push) Successful in 9m25s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m33s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 20m52s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m19s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 22m21s
Build Packages / DIALS test (push) Successful in 19m20s
Build Packages / Unit tests (push) Successful in 1h18m13s
Versions in the fetched-at-build-time table, and the two licence texts that changed with them: HDF5 2.2.0 appends the Apache-2.0 text to its BSD-3-Clause-style licence, and libjpeg-turbo 3.2.0 rewrote its licence sections (the licence set is unchanged: IJG + BSD-3-Clause + zlib). Abseil gets a row and a licence text of its own. It was always linked in, arriving as Ceres' git submodule; now that the build fetches it explicitly it is listed rather than left implicit. docs/THIRD_PARTY_NOTICES.md regenerated by the update_version.sh recipe. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6iekpDa2xiKobEj1R5xnp |
||
|
|
2b30850d1a |
build: update fetched dependencies to current releases
zstd 1.5.6 (a commit pin) -> 1.5.7, hdf5 2.1.0 -> 2.2.0, Catch2 3.13.0 -> 3.16.0, cpp-httplib 0.39.0 -> 0.56.0, curl 8.11.1 -> 8.22.0, libtiff 4.7.1 -> 4.7.2, libjpeg-turbo 3.0.4 -> 3.2.0. spdlog and libzmq were already current. Ceres stays on its pinned commit, which is newer than the 2.2.0 tag. sls and fast-feedback-indexer are unchanged. Built clean on all four CI images -- rocky8, rocky9, ubuntu2204, ubuntu2404 -- with the CI flags and CUDA on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6iekpDa2xiKobEj1R5xnp |
||
|
|
2bd6ddd577 |
build: fetch dependencies as pinned tarballs instead of git clones
GitHub throttles unauthenticated git-upload-pack and selects targets by TLS fingerprint (x-github-edge-protection: ja4-campaign-git-error). A clone gets 200 for the ref advertisement and then 401 for the fetch, so git asks for a password, finds no terminal, and the configure stops with no error and hangs to the CI timeout. Whether an image is hit follows its OpenSSL version: rocky8 (1.1.1) passed 5/5 while rocky9 (3.5.5) and ubuntu2204 (3.0.2) failed 5/5, which is why it presented as two broken build images. The archive endpoint is not part of the campaign, and a tarball is a fraction of the transfer -- hdf5 is 40 MB against a 271 MB shallow clone. Every URL is pinned by tag or commit and its bytes by URL_HASH. Ceres is the exception: it carries abseil as a git submodule, so it is populated first, the abseil commit its submodule points at is unpacked into third_party/abseil-cpp, and the subdirectory is added only then. Versions are unchanged; the archives are the same trees the clones checked out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6iekpDa2xiKobEj1R5xnp |
||
|
|
ff91fd0c0c |
docker: clone over HTTP/1.1 in the Ubuntu build images
The HTTP/2 multiplexing libcurl uses by default makes clones from some servers fail mid-transfer with "RPC failed; curl 92 HTTP/2 stream not closed cleanly". Every source dependency in the image, and the project's own FetchContent step, clones over HTTPS, so pin HTTP/1.1 system-wide. Orthogonal to GitHub's unauthenticated-download throttling, which answers git-upload-pack with 401 regardless of the HTTP version. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6iekpDa2xiKobEj1R5xnp |
||
|
|
0ca6b251e5 |
jfjoch_viewer: no settings section is wider than the panel it sits in
Build Packages / build:windows:nocuda (push) Successful in 17m28s
Build Packages / build:windows:cuda (push) Successful in 19m59s
Build Packages / build:rugnux:windows (push) Successful in 10m55s
Build Packages / Unit tests (push) Successful in 47m41s
Build Packages / build:viewer-tgz:cpu (push) Successful in 7m17s
Build Packages / build:viewer-tgz:cuda (push) Successful in 8m8s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 7m2s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 4m57s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 10m21s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 9m10s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 10m14s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 8m17s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 10m57s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 10m9s
Build Packages / build:rpm (rocky8) (push) Successful in 11m10s
Build Packages / build:rpm (rocky9) (push) Successful in 10m17s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 10m55s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 8m58s
Build Packages / Create release (push) Skipped
Build Packages / Generate python client (push) Successful in 14s
Build Packages / Build documentation (push) Successful in 43s
Build Packages / DIALS test (push) Successful in 10m29s
Build Packages / XDS test (neggia plugin) (push) Failing after 3h12m41s
Build Packages / XDS test (JFJoch plugin) (push) Failing after 3h12m43s
Build Packages / XDS test (durin plugin) (push) Failing after 3h12m45s
Measured every section's content minimum width against the 330 px the
settings dock gives it (the powder one, fixed yesterday, sat at 278):
geometry 346, unit cell 355, goniometer 322, spot finding 370,
indexing 321, bragg 287, scaling 297, azint 243, reference 149
Five over. Three causes, none of them about the section:
- addRow("", w) reserves the label column and puts the widget in the
field column, so a checkbox's whole width was added to the widest
label. addRow(w) spans both columns instead, which is what a
label-less row means anyway. Spot finding 370 -> 251.
- A spin box is never narrower than its longest possible value, suffix
included, and these rows hold two or three abreast. The unit moves to
the row label, which is how the panel names units elsewhere
("High resolution [Å]"): "a, b, c [Å]", "Beam origin [px]",
"Detector tilt [°]", "Step x, y [μm]", "Radii r1/r2/r3 [px]".
A cell length can still be four digits and three decimals, so those
six fields also divide the row rather than demand their own width.
- A combo is never narrower than its longest entry - the same thing
that pinned the powder panel. "Flex (best per-image refinement)" set
the indexing section's width; it elides now, as the calibrant does.
Every section is now 291 or less, and none of them moves the dock.
Two inspector changes in the same panel:
- An axis that is present but does not turn covers no angular range, so
the image angle is stated as one number rather than "-145° + 0°".
- Sample-head angles that stay put during a run - an SLS Smargon writes
chi and phi - get a row of their own, "chi 0° / phi 0°". Both its
labels stay empty for a file that carries none, so it costs nothing
where it means nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oVDk7aukYqfPZBLHn5nGg
|
||
|
|
a7a4c28925 |
jfjoch_viewer: the inspector states the numbers it used to hide
Build Packages / Unit tests (push) Successful in 49m15s
Build Packages / build:windows:nocuda (push) Successful in 18m5s
Build Packages / build:windows:cuda (push) Successful in 20m26s
Build Packages / build:viewer-tgz:cpu (push) Successful in 7m17s
Build Packages / build:viewer-tgz:cuda (push) Successful in 8m15s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 6m57s
Build Packages / build:rugnux:windows (push) Successful in 10m56s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 5m0s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 10m21s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 8m22s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 11m49s
Build Packages / build:rpm (rocky8) (push) Successful in 11m57s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 9m5s
Build Packages / XDS test (neggia plugin) (push) Successful in 5m45s
Build Packages / Generate python client (push) Successful in 10s
Build Packages / Build documentation (push) Successful in 36s
Build Packages / Create release (push) Skipped
Build Packages / XDS test (JFJoch plugin) (push) Failing after 3h12m36s
Build Packages / XDS test (durin plugin) (push) Failing after 3h12m38s
Build Packages / DIALS test (push) Failing after 3h12m40s
Build Packages / build:rpm (ubuntu2204) (push) Failing after 3h12m42s
Build Packages / build:rpm (rocky9) (push) Failing after 3h12m44s
Build Packages / build:rpm (rocky9_sls9) (push) Failing after 3h12m46s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Failing after 3h12m48s
Build Packages / build:rpm (rocky9_nocuda) (push) Failing after 3h12m50s
Six changes to the desktop viewer, all in what it shows and what it is called: - The left-hand reprocessing panel is titled "Processing" (it sits next to "File manager"); the jobs panel that held that name is "Jobs". - The file manager lists *.cbf beside the HDF5 datasets. A CBF is one frame per file and opening any of them opens its sweep, which is what the File > Open dialog already did. - Image statistics: collection time, detector distance, beam centre and wavelength were in the dataset row's tooltip, where a number nobody hovers over is a number nobody reads. They are rows of their own, in the order a person asks for them - what the dataset is, how it was collected, then what this image contains. Source/instrument and sample are bold like every other value. The B-factor row is gone. - A "Spots + background" plot: spot count on the left axis, background per pixel on the right, since a common scale would flatten one of them. The axis labels take the colour of their line - a legend costs exactly the vertical room those labels need at this dock height. It is what a live (HTTP) connection selects by default, until the user picks something else. - Expanding the powder-calibration section no longer widens the settings panel past the space it has. Two buttons side by side cannot be narrower than both their labels, and a combo is never narrower than its longest entry - a 40-character cell in the calibrant list set the minimum width of the whole dock. One button per row, the cell in a tooltip, and both combos allowed to elide. - The grid/per-image toggles both rebuild the metric selector now. The grid button did not, so leaving per-image mode through it left the per-image plot types listed - X-ray fluorescence offered as a metric of a grid scan - and their values were then read as per-dataset metrics. Each mode also remembers its own selection instead of carrying an index across two unrelated lists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011oVDk7aukYqfPZBLHn5nGg |
||
|
|
75eb6ce59c |
jfjoch_viewer: a dataset opened over HTTP keeps its source and instrument
Build Packages / Unit tests (push) Successful in 1h8m44s
Build Packages / build:windows:nocuda (push) Successful in 16m57s
Build Packages / build:windows:cuda (push) Successful in 20m12s
Build Packages / build:viewer-tgz:cpu (push) Successful in 16m28s
Build Packages / build:viewer-tgz:cuda (push) Successful in 17m5s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 14m54s
Build Packages / build:rugnux:windows (push) Successful in 11m7s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 8m31s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 19m32s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 14m0s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 15m23s
Build Packages / build:rpm (rocky8) (push) Successful in 14m28s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 9m2s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 6m23s
Build Packages / XDS test (neggia plugin) (push) Successful in 5m44s
Build Packages / Generate python client (push) Successful in 10s
Build Packages / Build documentation (push) Successful in 34s
Build Packages / Create release (push) Skipped
Build Packages / XDS test (durin plugin) (push) Failing after 3h12m36s
Build Packages / DIALS test (push) Failing after 3h12m38s
Build Packages / build:rpm (ubuntu2204) (push) Failing after 3h12m40s
Build Packages / build:rpm (rocky9) (push) Failing after 3h12m43s
Build Packages / build:rpm (rocky9_sls9) (push) Failing after 3h12m45s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Failing after 3h12m47s
Build Packages / build:rpm (rocky9_nocuda) (push) Failing after 3h12m49s
The start message carries source_name, source_type and instrument_name, and the broker fills all three from its config, but the HTTP reader never imported them into the dataset's experiment - so the Inspector showed "-" for Source / Instrument on a live connection while the same file opened from disk showed it. Total flux and attenuator transmission, which the tooltip on that line reports, were dropped the same way; ring current already came through. Sample name was never affected and is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PogHY7bWXV4bpctDPdyuDN |
||
|
|
a59be89192 |
receiver: bound the wait for a start message that never arrives
JFJochReceiverLite::MeasurementThread polls for the start message with no way out other than the cancelled flag, and JFJochServices::Stop cancels the receiver silently once the detector reads back idle - a silent cancel deliberately does not set cancelled, since it reports that the detector has finished rather than asking for an abort. A run whose start message never came therefore left Stop() waiting on this thread indefinitely, and only an operator pressing cancel got the broker back. Cancel(bool) is overridden to record that the detector has finished, and the pre-start poll gives up five seconds later. The grace is there because 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; the flag is consulted nowhere else, so images already being processed are untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bew392LTGP2fkhfRJsMcB |
||
|
|
db5068b2f8 |
tests: a puller whose queue filled must recover, not go silent
Covers the half of the deadlock fix that decides whether a collection poisons the ones after it. outside_fifo fills whenever the detector keeps streaming past the point where the run stopped consuming - the analysis threads have gone, and Suspend() only arrives once Stop() has finished with the writer - and the CBOR thread then parks in PutBlocking on the full queue. Suspend() cannot release it, since the flag is tested before the put, so ResumeAndClear at the next start has to; nothing else ever would, because a Get on an empty queue notifies no producer. Without that notify the puller is dead for good: cbor_fifo fills behind it, the puller thread blocks in turn, and every later collection receives nothing at all - no images and no start message - while the puller itself looks healthy. The test fails with the c_full notify in ThreadSafeFIFO::Clear removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bew392LTGP2fkhfRJsMcB |
||
|
|
1da94bdfe2 |
broker: a cancelled dark-mask collection is not a finished calibration
DarkMaskAnalysis::GetMask always returns a full-size array - a pixel that saw no frame reads as good - so the dark_mask_result.size() check in TakeDarkMaskInternal accepted a mask measured on nothing. The mask was adopted through LoadDarkBadPixelMask, the state went to Idle and the sequence logged "Calibration sequence done" on a calibration the operator had just cancelled. The dark mask is a single step, so unlike a pedestal sequence there is no later cancel_sequence check to catch it. Refuse it after Stop() the way the check at the top of the same function does: Inactive with the "Mask sequence cancelled" error, matching what the pedestal steps report. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bew392LTGP2fkhfRJsMcB |
||
|
|
c40e0eb11d |
broker: fix deadlock when re-initialising after a DECTRIS run that never started
A re-initialisation left the previous run's ZMQImagePuller connected: JFJochServices::On replaces its own shared_ptr, but JFJochReceiverService keeps a second reference to the puller of the last run. Two PULL sockets on one PUSH peer share messages round-robin, so the orphaned puller silently took half of the DECTRIS stream - the start message included - and the receiver then waited for a start message that had already been discarded. Once such a run was cancelled, nothing drained the orphaned puller's outside_fifo any more. Its CBOR thread parked in PutBlocking on the full queue (suspend is only tested before the put), the puller thread backed up behind it in cbor_fifo, and neither could reach the disconnect flag again. The next JFJochReceiverService::Start dropped the last reference to that puller, so ~ZMQImagePuller joined two threads that could never exit - while holding state_mutex, inside a calibration sequence that itself holds the state machine's mutex. The whole control plane froze with no way to cancel; only a restart got out of it. * ThreadSafeFIFO gains Stop(), which releases every waiter and makes further blocking operations return at once. Clear() now notifies c_full as well: clearing a full queue used to leave the blocked producer asleep, since the next Get on an empty queue notifies no one. * ZMQImagePuller::Disconnect and TCPImagePuller::Disconnect stop their queues before joining, so a puller whose consumer is gone can always shut down. * JFJochServices::On and ::Off disconnect the previous puller explicitly rather than relying on the shared_ptr going away, so two readers never share the detector stream. ZMQImagePuller_DisconnectWithFullQueue covers the shutdown; it hangs on the previous code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012bew392LTGP2fkhfRJsMcB |
||
|
|
d24cdfcff4 |
jfjoch_viewer: navigation keys, one-shot auto-contrast, file manager panel, cleanup
* Dataset-info plot hover shows a horizontal crosshair alongside the existing vertical one, so a run of hovers reads as a level or a drift at a glance. * `A` applies auto-contrast once instead of switching on persistent Auto mode (a new `oneShotAutoForeground()`, distinct from the toolbar's Auto toggle); pressing it a second time on the same image switches Auto on for good, and pressing it while Auto is already on leaves it on. The value itself comes from one `AutoForegroundValue()` shared with the continuous Auto path. * `Home`/`End`/`Page Up`/`Page Down` navigate the dataset (first/last image, one image forward/back); claimed in the diffraction view's keyPressEvent before QGraphicsView's default handling, which otherwise eats them to scroll the viewport. * Alt+wheel dataset navigation is removed - some window managers already took it before the app ever saw it, per the caveat that used to sit next to its entry in the shortcuts list. * The image strip dock and its dedicated thumbnail-rendering machinery in the reading worker (SetThumbnail*, RenderThumbnail_i) are removed; it cost a lot and nothing else used any of it. * Toolbar text: "Colour" -> "Color". * Color map, Auto and HDR mode now persist across sessions, the same way the window layout already does. * The Inspector's "Dataset:" path wraps at '/' (a zero-width space after each one) instead of being cut off when it doesn't fit one line. * LoadFile no longer reopens an already-open file - it forwards straight to LoadImage instead - which was the likely cause of a D-Bus client's per-image navigation lagging behind the same navigation done via the grid-scan hover. * New file-manager dock (left, tabbed with Settings via tabifyDockWidget): a directory tree filtered to *_master.h5/*_process.h5, rooted at JUNGFRAUJOCH_DATA_ROOT if set, else the last-browsed root, else the home directory. Most beamline users care about opening images, not reprocessing settings, so it's the tab raised by default. Its filter box narrows files only - a directory always passes - because filtering the directories too hid the matching files under any directory whose own name did not match, and hid the root's ancestors, which left the tree with no valid root index until the root was set again by hand. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PogHY7bWXV4bpctDPdyuDN Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
40dd6f25fd |
release: version 1.0.0-rc.169
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 9m39s
Build Packages / build:windows:nocuda (push) Successful in 17m21s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 18m31s
Build Packages / build:windows:cuda (push) Successful in 20m26s
Build Packages / build:viewer-tgz:cpu (push) Successful in 22m32s
Build Packages / build:viewer-tgz:cuda (push) Successful in 22m51s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 23m1s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 27m58s
Build Packages / build:rugnux:windows (push) Successful in 11m10s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 28m43s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m44s
Build Packages / build:rpm (rocky9) (push) Successful in 23m4s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 28m23s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 24m37s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 23m9s
Build Packages / build:rpm (rocky8) (push) Successful in 29m39s
Build Packages / Generate python client (push) Successful in 38s
Build Packages / Create release (push) Skipped
Build Packages / Build documentation (push) Successful in 1m10s
Build Packages / DIALS test (push) Successful in 25m36s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 28m18s
Build Packages / XDS test (durin plugin) (push) Successful in 10m24s
Build Packages / XDS test (neggia plugin) (push) Successful in 9m40s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 10m17s
Build Packages / Unit tests (push) Successful in 1h27m12s
|
||
|
|
92c441d4f2 |
space group: the operators the data confirm are asked whether they are a group
Stage A admits a point group only when every one of its operators clears the correlation bar, so admission is a conjunction over an enumerated list: one operator reading low deletes the group, and the operators that did clear the bar can then generate a group no candidate names. That is a contradiction in the operator table, not a low symmetry. The generated group is now offered as a candidate as well, and judged by the same chi^2, systematic-b, H and added-operator-R tests as every other; like a widened rung it does not set chi2_ref and is not a parent, so offering it cannot make another promotion harder. Exactly the generated group, never a supergroup of it - proposing the smallest candidate that merely CONTAINS the generated set was measured and over-calls where only one operator is confirmed. Measured on a tetragonal crystal recorded in two unequally exposed arcs, where the search merge is scaled in P1 and no P1 operator ties the two halves together: the four operators that exchange h and k read 0.11-0.14 against a bar of 0.30 while the five that do not read 0.28-0.82 and generate all of 422. The run adopted an orthorhombic subgroup and wrote 0.98 A of unusable data; it now adopts the tetragonal group and writes 1.51 A at CC1/2 0.96 and 99.7 per cent completeness. A measured pseudo-translation on an axial row no longer revokes the screw deferral by itself. It is already paid for quantitatively - the absent class is divided by the modulation depth before either the evidence or the violation count reads it - and revoking the deferral as well charges one measurement twice, categorically, on an estimate that moves with anything upstream that changes which reflections enter the cone. Masking a beam-stop arm, 2.3 per cent of the detector, took one row's depth across the bound and cost an orthorhombic crystal both of its screws while its zone evidence rose. The per-reflection rate test keeps the deferral honest where a modulation really does hide a row. No threshold changed value. Over 146 datasets - 100 open, 32 in-house, 14 private - no row's space group or cell moves except the crystal above. |