Calibrate the throw of each L3 slit blade motor
Diagnostic procedure: for each of the four blade motors in a chosen
L3-style slit station, drive the motor by a known mechanical throw
(± blade_throw_mm from baseline), measure how far the
corresponding bright-spot edge moves on the detector, and report
the resulting pixels-per-mm slope.
The two H-axis blades should agree on |slope|; the two V-axis
blades should agree on |slope|; H and V means should also agree
to within a few percent when both axes share the same underlying
encoder calibration. A spread between same-axis blades points at
one specific motor record; an H-vs-V mismatch points at the whole
axis pair.
This procedure produces no deliberate output – every blade is
restored to its baseline position after measurement. Intended as a
diagnostic to run when Centre and close an L3-style slit aperture produces an asymmetric image
(e.g. a 1×1 mm aperture rendering as a noticeably non-square
rectangle on the detector), to identify which blade(s) need their
.MRES / encoder direction corrected in the motor IOC.
Name
calibrate_slit_blade_throw
Source
Implementation: procedures/calibrate_slit_blade_throw.py
Release notes: 2bm-procedures CHANGELOG
Devices
Beamline components: A-station L3 Slits OR B-station L3-style Slits – selected via the
--slit-stationflag. The procedure drives the four underlying blade motor records directly (e.g.2bma:m9.VAL), not the virtualSlit*Hsize/*Hcenteretc. ao records – the slope it measures is the raw motor-mm calibration, independent of the slit calc.Beamline components: MCTOptics – read only. Camera + lens are auto-detected from
2bm:MCTOptics:CameraSelect / LensSelectas in Detector Z-rail alignment to the beam.Beamline components: Detector microscope (whichever Camera/Lens is currently selected) – used to image the beam spot whose bounding-box edges are tracked.
Preconditions
Same upstream preconditions as Detector Z-rail alignment to the beam (FES + B-shutter open, ACIS permit, hutch secure, energy configured, sample out of beam, detector visible). The procedure-specific preconditions:
State |
Predicate |
Satisfied by |
|---|---|---|
|
The chosen slit has Hsize > 0 and Vsize > 0 large enough
that the spot survives a |
typically the operator runs Centre and close an L3-style slit aperture immediately beforehand, then opens to 1 mm before calibrating. |
|
The current camera + lens combination produces a bright spot above the background-MAD threshold. Procedure aborts at the initial COM measurement otherwise. |
cascade of all upstream preconditions for Detector Z-rail alignment to the beam. |
Operating envelope (v0.0.1)
Per-motion confirmation gate on every blade move (
+throw,-throw, restore-to-baseline – 3 gated motions per blade, 12 total for a 4-blade station).Snapshot + restore for all four blade baseline positions plus camera state. The blade-restore order in the snapshot is the order the blade motors are listed in
SLIT_STATIONS; camera follows.Always restores, regardless of completion – this is a measurement, not a state-change procedure.
Centroid-ROI gating: bbox edges are detected within a
±edge_roi_half_size_pixwindow around the initial spot centroid. The ROI must be large enough to contain the spot at+ blade_throw_mm(where one edge has shifted outward from baseline). Default 400 pix covers a ~500-px spot with ~200 pix shift on each side.Threshold from background MAD: bbox threshold =
bg_median + N · σ, withσfrom the median absolute deviation of four ROI-corner background patches (corner size--bg-corner-size, default 80 pix). DefaultN = bg_sigma_threshold = 5.0.
Parameters
Name |
Type |
Unit |
Description |
|---|---|---|---|
|
|
— |
Which station’s blades to calibrate. Default: |
|
> 0 |
mm |
Each blade is driven from |
|
> 0 |
pix |
Half-side of the ROI around the initial spot centroid
used for bbox edge detection. Must contain the entire
spot at |
|
> 0 |
pix |
Per-side length of the four ROI-corner patches used to
estimate background |
|
> 0 |
— |
Bbox threshold = |
|
0 < f < 1 |
— |
Used by the initial intensity-weighted COM that sets the ROI centre. Same definition as Centre and close an L3-style slit aperture. Default: 0.5. |
|
≥ 1 |
— |
Acquire and average N frames per bbox measurement. With
multilayer-stripe images, |
|
> 0 |
s |
Camera exposure. Default: 0.2. |
(Common parameters with Detector Z-rail alignment to the beam and Centre and close an L3-style slit aperture —
camera_pixel_um, --yes, --confirm-restore,
--no-cora-log, --dry-run, --log-level — are
documented in their respective pages.)
Steps
# |
Action |
PV / call |
|---|---|---|
1 |
Detect operator-set configuration. Read MCTOptics
CameraSelect / LensSelect; derive |
|
2 |
Snapshot pre-procedure state: each blade motor’s
|
|
3 |
Measure baseline spot centroid (intensity-weighted
COM at |
|
4 |
For each blade motor (4 iterations): (a) [gated] Move blade by (b) Acquire image, measure bbox within ROI
( (c) [gated] Move blade by
(f) Compute edge changes: |
|
5 |
Report. Per-blade: primary edge, signed pixel
displacement, motor throw, signed slope. Aggregate:
mean
|
log lines only; no PV writes. |
6 |
Restore. Run by |
|
Postconditions
On clean completion:
All 4 blade motors are back at their pre-procedure baselines (within motor positioning tolerance; verified by
move_motor’sDMOVwait).Camera state restored to the operator’s pre-procedure values.
Console + cora log contain the per-blade slope table and the per-axis aggregate report.
Slit virtual motors (
Slit*Hsize/Vcenteretc.) are untouched – this procedure drives the underlying motor records directly.
On abort:
Same: all 4 blades restored, camera restored. The aggregate report covers only the blades that completed both
+throwand−throwmeasurements; partial blades are listed withprimary_edge="?"andslope = 0.
Failure modes
Symptom |
Recovery |
|---|---|
|
|
“could not measure baseline spot centroid” at step 3. |
Open the slits enough that a spot is visible
( |
“only N pixels above threshold” warning during bbox measurement. |
The spot is too small inside the ROI, or the multilayer
stripes are pushing the MAD-derived
|
V/H ratio noticeably off 1.0 (e.g. 1.55). |
Not a failure – this is the diagnostic signal the
procedure was written to expose. Indicates the
motor-record calibration ( |
Same-axis spread > 5%. |
The two blades within one axis disagree on mm-per-pixel.
The outlier blade is the suspect – check its
|
Operator walkthrough
Run Centre and close an L3-style slit aperture first to centre + close + rezero + reopen the chosen station’s slits, ending with a centred 1 × 1 mm aperture. The spot should be roughly in the middle of the detector frame.
Launch with default flags;
--slit-station Bis the default. To diagnose the A station, pass--slit-station A.The first prompt is the
+throwmove of the first blade (e.g.2bma:m9.VAL→baseline + 0.5 mm). Confirmy– the move should be visible on the live view as the spot’s top or bottom (or left / right) edge shifting.Each blade triggers 3 gates:
+throw,−throw,restore. After all 12, the per-blade slope table and the aggregate report appear in the console.A V/H ratio close to 1.0 (within ~5%) is the expected outcome on a healthy station; anything substantially off is a finding worth taking up with the motor IOC config.
Notes
The procedure does NOT auto-fix the calibration: it only identifies which blade(s) are wrong. The fix is a manual edit in the motor IOC substitution file followed by an IOC restart.
“Primary edge” detection is heuristic but robust: a blade motion of order 0.5 mm typically shifts one bbox edge by ~100-200 pixels while the other three change by < 10 pixels (residual jitter from the centroid). The argmax is reliable in this regime.
If two edges shift by comparable amounts, the blade may be mis-mounted (driving a diagonal instead of an axis) – the output dict
all_edge_changesis logged so the operator can see all four edges and judge.Open trigger this procedure creates: extend the cora
SlitAsset for each station with per-bladecalibration_slope_pix_per_mmfields populated from this procedure’s report (one record per blade motor).
Field-test results (v0.0.2, 2026-06-14)
First end-to-end runs on both 2-BM stations. Surfaced and fixed a long-standing V-blade miscal on A; confirmed B is healthy.
- Camera:
FLIR Oryx 31MP at
2bmSP2:via MCTOptics- Lens:
1.1× (slot 0)
- Camera binning:
2 × 2
- Frames per measurement:
4
- Blade throw:
±0.25 mm
A station — before MRES fix
blade |
primary edge |
slope px/mm |
notes |
|---|---|---|---|
|
left |
339.8 |
H+ (X+, outboard) |
|
right |
328.0 |
H− (X−, inboard) |
|
bottom |
506.8 |
V+ (Y+, up) |
|
top |
486.4 |
V− (Y−, down) |
H mean = 334 px/mm, V mean = 497 px/mm, V/H = 1.487 ← WARN.
Same-axis spread: H 3.5%, V 4.1% (both blades within an axis agree; whole-axis V miscal, not single-blade).
Cross-checked against the operator’s pre-existing workaround (“setting Vsize = 0.6 mm with Hsize = 1.0 mm gives a square image on the detector”) and against a hand-bbox of an earlier PNG showing the same 1.55× V/H aspect.
Root cause: the motor IOC substitution file
(/net/s2dserv/xorApps/epics/synApps_5_8/ioc/2bma/iocBoot/ioc2bma/motor.substitutions)
uses a single template for every motor m1..m44+ — all share
MRES = 2.5e-4, EGU = "degrees", VELO = 1. The per-
axis mechanical differences between the A H blade assembly and
the A V blade assembly were never compensated.
Fix (applied live via caput with the SET/Use position-
redefinition pattern, persisted via autosave):
# For each of m15 and m16:
caput 2bma:m15.VAL 0 # park at known position
caput 2bma:m15.SET 1 # enter position-redefine mode
caput 2bma:m15.MRES 5.95e-4 # = 4e-4 (old) × 1.487 (correction factor)
caput 2bma:m15.VAL 0 # relabel position in new units (0 → 0)
caput 2bma:m15.SET 0 # exit Set mode
A station — after MRES fix
blade |
primary edge |
slope px/mm |
notes |
|---|---|---|---|
|
left |
339.7 |
unchanged ✓ |
|
right |
328.0 |
unchanged ✓ |
|
bottom |
350.4 |
MRES 4e-4 → 5.95e-4 |
|
top |
322.3 |
MRES 4e-4 → 5.95e-4 |
H mean = 334 px/mm, V mean = 336 px/mm, V/H = 1.008 ← PASS.
Same-axis spread: H 3.5%, V 8.4% (V borderline; likely tighten with
--frames-per-measurement 8).Visually validated: Hsize = Vsize = 1.0 mm now produces a square aperture image on the detector.
B station (no fix needed)
blade |
primary edge |
slope px/mm |
notes |
|---|---|---|---|
|
top |
170.0 |
V+ (Y+, up) |
|
bottom |
172.0 |
V− (Y−, down) |
|
left |
168.3 |
H pair |
|
right |
167.5 |
H pair |
H mean = 167.9 px/mm, V mean = 171.0 px/mm, V/H = 1.019 ← PASS.
Same-axis spread: H 0.5%, V 1.2% (textbook).
B slopes are ~½ of A’s, matching the 2× geometric magnification of A’s projection at the detector plane (A at z = 25225 mm, B and detector both at z ≈ 50500 mm).
Notes from the field test
DMM image flip. A’s blade-to-edge mapping is mirrored from Beamline components’s labels (m13 = H+ outboard moves the LEFT edge of the spot; m15 = V+ up moves the BOTTOM edge). B’s mapping is straight (m9 = V+ moves the TOP edge). The DMM Bragg reflection between A and B inverts one axis of A’s image relative to B’s at the detector. Worth knowing for any future blade-direction debugging.
A V-blade tilt. Even after the MRES fix, the centring- sensitivity matrix (from Centre and close an L3-style slit aperture) shows H_sens = 330 px/mm but V_sens = 134 px/mm — a factor 2.5 asymmetry that the blade-throw procedure doesn’t see (both axes give ~334 px/mm per blade). The discrepancy is consistent with a small tilt on the V blade pair that smears the V intensity profile, so the COM tracks the slit centre less than 1:1 when Vcenter moves. Not a calibration error — a geometric fact about A’s V kinematic. B’s centring sensitivity is much more symmetric (H_sens = 170, V_sens = 109, ratio 1.55) consistent with B not having the same tilt.
Run details and the architectural / bug history that got here are in 2bm-procedures CHANGELOG.