Files
scanengine-3/KNOWN_ISSUES.md
T
Thomas Ales 6e8c1cb7a2 Auto-align: level the sample on the DC bias levels from the camera window
The operator frames a good spot, confirms the two DC levels the detector
reads there, and the rig then measures its own tilt: step 1.5 mm either side
on X and then on Y, and tilt the platform until those levels come back.  The
correction that fixes an offset point is the correction that levels the whole
travel — height error and tilt effect are both proportional to the offset —
so the procedure ends by applying it and leaving it applied.

Both directions are measured from the same starting tilt and averaged, which
makes their disagreement a flatness read-out rather than something averaged
away silently.

core/auto_align.py holds the geometry and the search, Qt-free.  The three
T-axes' azimuths are the whole geometry: T1 lies along +X so it alone tilts
along X, and T0/T2 move as an equal-and-opposite pair to tilt along Y without
touching X (tilt_response derives that, and the tests pin it — an axis map
that drifts would still converge, on the wrong axis).  The search is a secant
null on the split-detector difference: probe once to learn what a microstep
is worth, sign included, then step at the null.  It refuses to servo on a
scope that has not re-triggered, escalates a probe that reads as no response
before calling an axis dead, and stops at a per-axis travel limit.

gui/align_bridge.py runs it on a worker thread; stopping is a threading.Event
rather than a queued command, because the worker is inside a long handler for
the whole run.  The camera window carries the button and the progress window,
and locks the scan panel and the jog pads while a run owns the stage.

Adds immediate MEAN measurements and an acquisition count to the scope
driver, and read_bias_mv to core/scope_inspect — the one scalar the
inspection state was missing.

KNOWN_ISSUES.md records what only the rig can settle: the probe step, the
travel limit, the hold current, and whether the piston the X phase applies
alongside its tilt matters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:00:36 -05:00

6.1 KiB
Raw Blame History

Known issues requiring on-rig verification

Questions that cannot be answered from the code alone. Check these the next time the hardware is available; each one gates a small code change.

uC480 camera: gain/exposure during active capture

The driver used to carry an (unused) _capture_paused context manager whose docstring claimed many IDS cameras return IS_CANT_COMMUNICATE_WITH_DRIVER (17) or IS_NO_SUCCESS (-1) when gain/exposure commands are issued during active capture. set_exposure() and set_gain() never used it, and the helper was deleted in the Phase-1 cleanup.

Bench check: with live streaming running, move the exposure and gain sliders in camera_test_app.py and watch the log for those error codes. If they appear, the setters need a stop-live/apply/restart sequence (re-create the helper around the two call sites in uc480_camera.py).

Helios: no output-power query

docs/hardware/HELIOS_DRIVER_README.md documents driver.get_power_mw(), but HeliosLaser has no such method and no output-power mnemonic appears anywhere in this repo's protocol notes. helios_test_app.py called it anyway and raised AttributeError into a popup; the button is now disabled and the handler reports the gap instead.

Bench check: find the power-read command in the Helios manual (the other reads are three-letter mnemonics like LDO, LDS, LTA). If one exists, add get_power_mw() to hardware/helios_laser.py using _query_int, then re-enable the button. If it doesn't, delete the Power Monitoring group from the test app and fix the README.

Genesis laser: forked protocol implementations disagree

hardware/genesis_core.py and the reference implementation tools/genesis_laser_gui.py disagree on ADC command bytes, LDD enable polarity, shutter semantics, filtering, and scaling. Do not modify either until the checklist in docs/genesis_verification.md has been run on the bench.

lib/ueye_loader.so — still needed?

lib/ueye_loader.c is an LD_PRELOAD shim that dlopens /usr/lib/libueye_api.so — yet nothing in the repo references it, and the vendored SDK copy is lib/libueye_api64.so.3.82 (a different file). On the rig, check whether the camera apps run without the shim; if they do, delete lib/ueye_loader.{c,so}. Either way, record in SETUP.md where libueye_api64.so.3.82 came from (IDS SDK version) and how the loader is meant to be used.

Per-angle background: trigger round trip mid-scan

Every angle now captures its own background, so the scope switches from the scan-time logic-AND trigger back to the single-record edge trigger and returns to it once per angle (core/scope_sras.py: configure_background_trigger → capture_background → configure_scan_trigger). Before this, that transition happened once per scan, with the stage idle and nothing depending on how long it took.

Bench check: run a multi-angle scan and watch the first row after each background. If frames go missing at the start of an angle, the 0.2 s settle in configure_scan_trigger is not enough for FastFrame to re-arm after an AVERAGE-mode sequence, and the row-packing warning ("N frames acquired, M expected") will say so in the log. Raise the settle rather than the ramp buffer — the stage geometry is not what changed.

Auto-align: constants that are guesses until the rig confirms them

core/auto_align.py closes a loop over hardware whose gain nobody has measured. Three numbers in AlignSettings/TAxisSettings are reasoned defaults, not readings:

  • probe_steps = 200 — the first move of every search, made only to learn how many millivolts a microstep is worth. Too small and each search wastes iterations doubling it (the status line says so: "moved N microsteps and the DC difference did not change"); too large and the first move overshoots by more than the platform should be asked to travel in one go.
  • max_excursion_steps = 20000 — the per-axis safety limit, measured from wherever the axis started. It exists to stop a runaway before the actuator reaches its end stop, so it has to be smaller than the real travel.
  • hold_current_ma = 300 — the run current (600 mA) and microstepping (32) are specified; the standstill current is half the run current by analogy with the GR axis, and has not been checked against the platform's weight.

Bench check: run one auto-align and read the log. The first search's iteration count is the probe verdict — 3 or 4 steps means the probe is about right, and a "did not change by 2 mV" line means it is too small. Convert the applied corrections into actuator travel and compare against the T-axis travel to set the excursion limit. Watch whether the platform holds its tilt between the two phases; if it sags, raise the hold current.

Auto-align: does the X phase's piston matter, and where is the pivot?

The X phase moves T1 alone, as specified. T1 is the only axis lying along X, so it does tilt the platform along X — but moving one leg of three also lifts the platform by a third of the move (tilt_response(X_TILT) returns a piston of 1/3 alongside the 2/3 tilt). The search nulls the split-detector difference, which a piston should not move, so the assumption is that the piston is harmless. The piston-free alternative is T1 +1 with T0 and T2 at −0.5 each.

Separately, the procedure assumes the tilt pivot is under the beam: if it is not, applying the correction shifts the DC levels at the reference point itself, and the Y phase then chases levels that no longer describe the rig. The code reports this rather than compensating for it — AxisResult's "back at the reference" reading after the X phase is exactly that measurement.

Bench check: during an X search, watch DC1 + DC2 (the sum, not the difference) on the scope. If the sum moves as T1 moves, the piston is changing the amount of collected light and X_TILT should become the piston-free triple. Then read the X phase's reference residual out of the log: more than a few millivolts means the pivot is not under the beam, and the Y phase's reference should be re-measured after the X correction instead of reusing the operator's original numbers.