Skip to content

feat(anatomy): enter probe pose by insertion point as well as by tip - #43

Open
arturoptophys wants to merge 1 commit into
m-beau:mainfrom
arturoptophys:feat/insertion-point-entry
Open

feat(anatomy): enter probe pose by insertion point as well as by tip#43
arturoptophys wants to merge 1 commit into
m-beau:mainfrom
arturoptophys:feat/insertion-point-entry

Conversation

@arturoptophys

Copy link
Copy Markdown

Addresses #42

Adds a Tip / Insertion switch to the anatomical overlay so probe coordinates can be entered from the craniotomy instead of from the tip. The inactive triplet is greyed out and live-mirrors the derived value, an insertion-depth field links the two, and a snap button finds where the trajectory actually enters the brain.

What it does

Tip / Insertion switch Pick which triplet you type. The other stays visible and updates live, so both ends of the trajectory are always readable.
Insertion depth Travel along the shank — the manipulator reading — so it stays correct at any tilt. Editable in both modes.
Pivot follows the mode Changing an angle holds whichever coordinate you are entering fixed. In insertion mode the probe pivots about the entry point, as it does over a real craniotomy.
⤓ Snap to brain surface In insertion mode, places the entry point on the surface. In tip mode, holds the tip and reports the depth you would need. Disabled until the atlas is downloaded.

Worth a careful look

Two different "tips". Insertion depth is referenced to the physical shank tip, because that is what a manipulator reads. tip_atlas everywhere else is the lowest electrode. The offset comes from tip_length_um in Bill Karsh's probe JSON (209 µm on 1.0, 206 µm on 2.0/QuadBase) — not hardcoded — and is re-applied when the part number changes. This is the one design decision I would most like a second opinion on; it is flagged in the issue.

A consequence, which is intended: a depth below the tip length correctly shows the shank tip in the brain with the electrodes still above the surface.

The overlay path is untouched. Because the tip fields are always kept in sync, _tip_atlas_um() is unchanged and compute_anatomy_overlay / compute_region_bands / render_locator never learn about the new mode. test_anatomy_schematic.py and test_anatomy_visualization.py pass unmodified, which is the check that this held.

Watcher wiring in gui.py is the fragile part. Widgets that move the probe are funnelled through _on_pose_input_change, which mirrors before recomputing. Watching them with _recompute_anatomy_if_active as well would render the overlay against a stale tip and then render it again — two 300-dpi savefig calls per keystroke. _syncing_coords guards the mirrored write, since disabled is front-end only and those writes still fire watchers. The mode switch deliberately does not re-sync, which is what keeps repeated toggling bit-stable.

surface_point_um clips the ray to the volume bounding box rather than marching a fixed window around the anchor. The insertion point is routinely outside the volume, so a fixed window is not guaranteed to reach the brain — and the answer would then depend on which point of the line you passed in, making the snap button behave differently in the two modes. test_result_depends_only_on_the_line pins this.

Small shared refactor: the (AP, ML, DV) µm → (ap, dv, ml) index math is extracted to _voxel_indices() and now shared with lookup_regions. The existing lookup_regions tests cover the change, plus a new one using an anisotropic voxel size — equal voxel dimensions hide a transposition bug.

Testing

150 passed (120 pre-existing, unmodified, + 30 new).

New coverage lives in tests/test_anatomy_transform.py and tests/test_anatomy_atlas.py, no atlas download needed. The load-bearing one is test_matches_probe_to_atlas_yp_displacement: it asserts that moving L µm up the shank is the same operation via probe_axis as via probe_to_atlas, across a grid of pitch/yaw/spin. That is what stops the insertion math drifting from the electrode math.

I sanity-checked the new tests by mutation — breaking the march step, the DV/ML transposition, the march direction, the dv_squish guard and the AP sign flip each produced a specific failure.

The GUI wiring itself (one render per edit, no drift across repeated mode toggles, cleared-field safety, snap gating) was verified with a throwaway harness rather than a committed test, since the repo has no GUI tests and starting that felt like a separate decision. Happy to add one if you would like it.

Surgeries are planned from the craniotomy, not from where the tip ends
up, but the anatomical overlay only accepted tip coordinates. A
Tip/Insertion switch now picks which triplet is authoritative; the other
is greyed out and live-mirrors the derived value, linked by an insertion
depth. A snap button ray-casts the trajectory to the brain surface.

The switch doubles as a pivot selector: changing an angle keeps whichever
coordinate you are entering fixed, so in insertion mode the probe pivots
about the entry point, as it does over a real craniotomy.

Depth is travel along the shank (manipulator reading), not a vertical
drop, so it is well defined at every pitch/yaw including +/-90 where a
vertical parameterisation divides by zero. It is referenced to the
physical shank tip, while tip_atlas is the lowest electrode; the offset
comes from tip_length_um in Bill Karsh's probe JSON (209 um on 1.0,
206 um on 2.0) rather than being hardcoded, and is reapplied when the
probe part number changes.

anatomy/transform.py
  probe_axis() factored out of probe_to_atlas so the insertion math and
  the electrode math cannot drift; tip_to_insertion / insertion_to_tip /
  signed insertion_depth_um; atlas_to_bregma_um inverting the display
  transform, needed because mirroring must round-trip through the atlas
  frame (the bregma map flips AP).

anatomy/atlas.py
  surface_point_um() clips the trajectory to the volume bounding box, so
  the answer depends only on the line and not on which point of it is
  passed -- casting from the tip and from the entry point agree. Voxel
  index math extracted to _voxel_indices() and shared with
  lookup_regions.

gui/gui.py
  Widgets that move the probe are funnelled through _on_pose_input_change,
  which mirrors before recomputing. Watching them with
  _recompute_anatomy_if_active as well would render the overlay against a
  stale tip and then render it a second time. _syncing_coords guards the
  mirrored write (disabled is front-end only, so those writes still fire
  watchers) and the mode switch deliberately does not re-sync, which
  keeps repeated toggling bit-stable. _fval() tolerates a cleared field,
  which previously raised out of the watcher unhandled.

_tip_atlas_um() and the whole downstream overlay path are unchanged,
since the tip fields are always kept current; the schematic and
visualization tests pass untouched as proof.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant