feat(anatomy): enter probe pose by insertion point as well as by tip - #43
Open
arturoptophys wants to merge 1 commit into
Open
feat(anatomy): enter probe pose by insertion point as well as by tip#43arturoptophys wants to merge 1 commit into
arturoptophys wants to merge 1 commit into
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Worth a careful look
Two different "tips". Insertion depth is referenced to the physical shank tip, because that is what a manipulator reads.
tip_atlaseverywhere else is the lowest electrode. The offset comes fromtip_length_umin 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 andcompute_anatomy_overlay/compute_region_bands/render_locatornever learn about the new mode.test_anatomy_schematic.pyandtest_anatomy_visualization.pypass unmodified, which is the check that this held.Watcher wiring in
gui.pyis 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_activeas well would render the overlay against a stale tip and then render it again — two 300-dpisavefigcalls per keystroke._syncing_coordsguards the mirrored write, sincedisabledis 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_umclips 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_linepins this.Small shared refactor: the
(AP, ML, DV) µm → (ap, dv, ml)index math is extracted to_voxel_indices()and now shared withlookup_regions. The existinglookup_regionstests 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.pyandtests/test_anatomy_atlas.py, no atlas download needed. The load-bearing one istest_matches_probe_to_atlas_yp_displacement: it asserts that moving L µm up the shank is the same operation viaprobe_axisas viaprobe_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_squishguard 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.