The score gated on 60 spots, but the seed escalation stops at the leanest seed that indexes -
30 spots on precisely the clean frames a grid scan produces - so the value was absent exactly
where beamline automation most needs it, and absence maps to "engage": the protocol would have
fired on every good frame, which degenerates the trigger into "always". The floor itself stays
where it was calibrated; what changes is what it gates. When no escalation pass could answer,
one severity-only pass runs over the full spot list - the row search alone, no reduction, no
refinement - purely to produce the number.
The same was true of the indexer choice: only the FFT family computes a row shortlist, so a
deployment configured with the known-cell indexer - the ordinary online stills path - never
produced the score at all. Where the severity-only pass has no row search to run, the severity
is read off the rows of the winning lattice instead, which any indexer produces: the lattice's
shortest few distinct directions, as many as the FFT shortlist resolves in practice, fed through
the same window and scoring with equal magnitudes. The count parity is load-bearing - a worst
case over every enumerable lattice direction fires on 100% of harmless mounts of a generic
triclinic cell against 74% for this selection at theta_max = 15 deg, and an always-firing
trigger decides nothing - while the diad-detection rate stays 1.00 on the monoclinic classes
either way, a dropped axis row being recovered by the pair normals exactly as an invisible one
is. A frame that neither indexed nor reached the spot floor still reports nothing, which is the
honest answer and maps to the recoverable error.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
The severity took theta_max from the still's own spot resolution - the 95th percentile of |q| -
so a weak, attenuated grid still reaching 3 A scored a row at an 11 deg miss-angle as exactly 0,
while the real sweep collected afterwards at the detector's 1.8 A loses a fifth of a cone that
wide. The number exists to decide whether that sweep needs a second orientation, so the cone it
must describe is the sweep's, and the still's spot list systematically understates it in the one
direction that produces silent misses.
theta_max is now asin(lambda/2d) at the geometric resolution of the setup - the detector corner
at the recorded distance and wavelength. That is an upper bound on any sweep collected without
moving the detector, it is available on every frame however weak, and where it overstates what
the sweep will reach it errs towards reporting loss, which is the cheap error. Computed once per
Setup in the Indexer base rather than per frame in the FFT implementation, because nothing about
it is specific to either the frame or the indexer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
A rotation sweep never records a double cone of half-angle asin(lambda/2d) about the spindle.
That loss is normally repaired by the point group; it is not repaired when an operator's axis
lies inside the cone, because the cone then maps onto itself. A still cannot know the point
group, but it can see where the crystal's short lattice rows are, and a symmetry axis is always
one of them - measured over 107 solved cells its length is 1.4x the shortest row at the median.
The number is the fraction of the blind cone that a 2-fold about the nearest short row carries
back into the blind cone: two equal caps of angular radius theta_max whose centres are 2*beta
apart, which is the circle-lens area in beta/theta_max and within 0.035 of the spherical value
even for a 55 deg cone. 0 means one sweep reaches everything symmetry could give; 1 means the
row is on the spindle and the whole cone is lost coherently. No goniometer geometry enters, so
the number describes the problem and leaves the remedy to the beamline.
Free: it rides on the FilterFFTResults shortlist the indexer already builds, needing only the
spindle, the wavelength and the frame's own resolution. FilterFFTResults gains an optional
out-parameter for each row's peak prominence, which the length window is taken over.
Measured on 384 stills of 22 solved crystals against the frames' own symmetry axes, with the
same procedure re-run along five decoy directions per frame as the null: 0.93 of severe
orientations reported severe, 0.010 of harmless ones reported severe, AUC 0.948, and the value
tracks the true severity to 0.10 at the 90th percentile. Below 60 spots the misses triple, so
that is where it stops answering.
It is blind to a symmetry axis much longer than the crystal's shortest row: measured on
synthetic stills the search grid finds a 150 A row every time, a 200 A row half the time and a
300 A row once in sixteen. Two cheap consistency tests refuse to answer for part of that regime
and cost nothing on real frames, but they do not cover it - the reach is a documented property
of the number, not something every frame can detect.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
This is an UNSTABLE release. The release has significant modifications and bug fixes, if things go wrong, it is better to revert to 1.0.0-rc.132.
* jfjoch_broker: Improve logic regarding indexing architecture and thread pools (work in progress).
Reviewed-on: #45
This is an UNSTABLE release. The release has significant modifications and bug fixes, if things go wrong, it is better to revert to 1.0.0-rc.132.
* jfjoch_broker: Use httplib for HTTP server instead of Pistache
* jfjoch_broker: Drop OpenSSL support
* jfjoch_broker: Base work for multi-lattice support in the future
* Update dependencies to more recent versions (spdlog, HDF5, Catch2, httplib)
Reviewed-on: #41
This is an UNSTABLE release and not recommended for production use (please use rc.11 instead).
* jfjoch_broker: Experimental rotation (3D) indexing
* jfjoch_broker: Minor fix to error in optimizer potentially returning NaN values
Reviewed-on: #18
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
Co-committed-by: Filip Leonarski <filip.leonarski@psi.ch>