driftlessmap 1.1.0__tar.gz → 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/CITATION.cff +1 -1
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/PKG-INFO +11 -3
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/README.md +10 -2
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/WhatsNew.md +38 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/object_control.py +3 -3
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/probe_csv.py +78 -3
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/probe_reconstruction.py +79 -1
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/probe_utiles.py +28 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/version.py +1 -1
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap.egg-info/PKG-INFO +11 -3
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_object_control.py +2 -1
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_packaging_metadata.py +1 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_probe_csv.py +28 -1
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_probe_mapping.py +9 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_probe_reconstruction.py +17 -1
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_version.py +1 -1
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/AUTHOR.txt +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/CONTRIBUTORS.md +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/LICENSE.txt +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/MANIFEST.in +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/ORIGINS.md +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/THANKS.txt +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/__init__.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/__main__.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/about.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/allen_downloader.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/app.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/atlas_downloader.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/atlas_loader.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/atlas_processor.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/atlas_transform.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/atlas_view.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/cell_detection.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/coordinate_validation.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/czi_reader.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/data/WHS_atlas_labels.pkl +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/data/allen_mice_atlas_labels.pkl +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/data/atlas_labels.pkl +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/data/query.csv +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/download_utils.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/herbs.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/backward.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/dot.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/down-arrow.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/fast_backward.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/fast_forward.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/forward.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/layers/add.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/layers/ai.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/layers/eye_off.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/layers/eye_on.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/layers/eye_white.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/layers/hi.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/layers/trash.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/object.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/add.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/atlascontrol.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/atlascontrol.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/bnd.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/c_section.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/c_section2.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/cell.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/check.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/compare.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/contour.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/h_section.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/info.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/layers.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/layers.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/line.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/link.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/link_off.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/merge.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/object.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/probe.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/probe.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/rotation_horizontal.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/rotation_vertical.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/s_section.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/tool.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/tool.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/trash.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/tree_checked.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/treeview.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/treeview.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/treeview2.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/virus.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/tdown.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/accept.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/accept2.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/aim.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/aim_not.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/anchor.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/anticlockwise_rotation.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/atlas_icon.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/boundary_register.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/cancel.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/cell_select.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/cell_select_not.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/check.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/clockwise_rotation.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/closed_path.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/closed_path2 copy.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/closed_path2.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/eraser.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/eraser.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/eye.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/eye_closed.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/fill.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/gps.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/h_flip.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/handle.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/image_icon.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/image_icon.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/info.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/inpart.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/inpart.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/lasso.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/lasso.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/left90.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/line.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/line_sites.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/linear_silicon.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/linear_silicon.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/list.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/location.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/magic-wand.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/magic_white.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/mask.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/match.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/matchbnd.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/matching.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/merge.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/move_down.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/move_left.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/move_right.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/move_up.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/moving.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/multi-probe.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/multi_pencil.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/multi_probe.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/object.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/open_path.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/outpart.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/outpart.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/pencil.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/pencil.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/plasso.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/probe.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/radar.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/rotation.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/rotation_clockwise.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/rotation_counter_clockwise.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/ruler.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/separate_sites.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/single_pencil.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/toa.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/toa_delete.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/toaa.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/toh.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/toh_delete.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/tohh.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/trans.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/trans.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/triangulation.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/two_window.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/unmerge.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/v_flip.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/virus.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/virus_register.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/vis2d.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/window2.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/window3.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/window4.png +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/tree_close.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/tree_open.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/up-arrow.svg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/image_curves.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/image_reader.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/image_stacks.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/image_view.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/images_reader.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/label_tree.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/layer_validation.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/layers_control.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/main_window.ui +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/movable_points.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/obj_items.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/persistence.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/popup_message.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/atlas_view_group_box.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/box_label.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/channel_selector.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/color_combo.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/decor_label.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/dialogs.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/hidden_line_edit.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/label_tree.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/main_window.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/menu_bar.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/multi_handle_slider.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/obj_ctrl_bottom_button.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/object_text_button.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/page_control.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/rotation_button.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/side_bar.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/spinbox.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/tabs.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/tool_bar.qss +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/resources.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/roi_analysis.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/run_driftlessmap.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/slice_stacks.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/slice_validation.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/toolbox.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/triangulation.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/triangulation_points.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/user_settings.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/uuuuuu.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/widgets_utils.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/wtiles.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap.egg-info/SOURCES.txt +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap.egg-info/dependency_links.txt +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap.egg-info/entry_points.txt +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap.egg-info/requires.txt +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap.egg-info/top_level.txt +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/pyproject.toml +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/setup.cfg +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/setup.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_allen_downloader.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_atlas_loader.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_atlas_transform.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_atlas_view.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_cell_detection.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_coordinate_validation.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_download_utils.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_image_reader.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_label_tree.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_layer_validation.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_layers_control.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_persistence.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_roi_analysis.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_runtime_paths.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_slice_validation.py +0 -0
- {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_triangulation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: driftlessmap
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Interactive histology registration and brain-atlas mapping
|
|
5
5
|
Author: Pearl Saldanha, Jacopo Paglia, Jonathan R. Whitlock, Mohebi & Associates, Ali Mohebi
|
|
6
6
|
Author-email: Jingyi Guo Fuglstad <jingyi.g.fuglstad@gmail.com>
|
|
@@ -98,11 +98,19 @@ See [CITATION.cff](CITATION.cff) for machine-readable citation metadata.
|
|
|
98
98
|
- Safe, versioned project and object archives.
|
|
99
99
|
- Support for custom compatible atlases.
|
|
100
100
|
|
|
101
|
-
## Install
|
|
101
|
+
## Install from PyPI
|
|
102
102
|
|
|
103
|
-
DriftlessMap 1.
|
|
103
|
+
DriftlessMap 1.2.0 supports Python 3.10–3.14 and Qt 6 through PyQt6. Python
|
|
104
104
|
3.14 in a dedicated environment is recommended for new installations.
|
|
105
105
|
|
|
106
|
+
Install the latest stable release:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
python -m pip install driftlessmap
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
For a dedicated Conda environment:
|
|
113
|
+
|
|
106
114
|
```bash
|
|
107
115
|
conda create --name DriftlessMap python=3.14 -y
|
|
108
116
|
conda activate DriftlessMap
|
|
@@ -43,11 +43,19 @@ See [CITATION.cff](CITATION.cff) for machine-readable citation metadata.
|
|
|
43
43
|
- Safe, versioned project and object archives.
|
|
44
44
|
- Support for custom compatible atlases.
|
|
45
45
|
|
|
46
|
-
## Install
|
|
46
|
+
## Install from PyPI
|
|
47
47
|
|
|
48
|
-
DriftlessMap 1.
|
|
48
|
+
DriftlessMap 1.2.0 supports Python 3.10–3.14 and Qt 6 through PyQt6. Python
|
|
49
49
|
3.14 in a dedicated environment is recommended for new installations.
|
|
50
50
|
|
|
51
|
+
Install the latest stable release:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python -m pip install driftlessmap
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
For a dedicated Conda environment:
|
|
58
|
+
|
|
51
59
|
```bash
|
|
52
60
|
conda create --name DriftlessMap python=3.14 -y
|
|
53
61
|
conda activate DriftlessMap
|
|
@@ -3,6 +3,44 @@
|
|
|
3
3
|
This cumulative release history is maintained as a single document. New
|
|
4
4
|
releases are added at the top; earlier release notes remain below them.
|
|
5
5
|
|
|
6
|
+
## DriftlessMap 1.2.0
|
|
7
|
+
|
|
8
|
+
Release date: 27 July 2026
|
|
9
|
+
|
|
10
|
+
This release adds a versioned probe-localization export designed for
|
|
11
|
+
downstream electrophysiology and NWB workflows.
|
|
12
|
+
|
|
13
|
+
### Probe-localization export schema 2
|
|
14
|
+
|
|
15
|
+
The **Export probe localization files** action now creates four companion
|
|
16
|
+
files:
|
|
17
|
+
|
|
18
|
+
- `_contacts.csv` contains physical probe contacts, ordered deepest to
|
|
19
|
+
shallowest, with atlas coordinates and anatomical assignments.
|
|
20
|
+
- `_track.csv` contains labeled samples along the fitted centerline, ordered
|
|
21
|
+
from insertion to tip and spaced no farther apart than one atlas voxel. This
|
|
22
|
+
is the appropriate input for localizing continuously estimated unit depths.
|
|
23
|
+
- `_trajectory.csv` records insertion, tip, angles, fit quality, atlas and
|
|
24
|
+
coordinate-system metadata, export schema, software version, and the
|
|
25
|
+
probe-model tip-to-contact offset.
|
|
26
|
+
- `_regions.csv` summarizes region IDs, names, acronyms, contact totals, and
|
|
27
|
+
reconstructed path lengths.
|
|
28
|
+
|
|
29
|
+
Track samples include distance from insertion and tip, atlas-space
|
|
30
|
+
coordinates, and the structure ID, acronym, and name at each point. For
|
|
31
|
+
recognized Allen CCFv3 atlases, the export includes Allen voxel coordinates
|
|
32
|
+
and physical coordinates alongside the existing explicitly qualified
|
|
33
|
+
estimated Bregma values.
|
|
34
|
+
|
|
35
|
+
### Upgrade notes
|
|
36
|
+
|
|
37
|
+
Downstream tools that assign unit locations should require schema 2 and use
|
|
38
|
+
the track table rather than inferring region boundaries from the contact or
|
|
39
|
+
region summaries. Re-merge probes saved by older versions to generate the
|
|
40
|
+
complete reconstruction and export metadata.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
6
44
|
## DriftlessMap 1.1.0
|
|
7
45
|
|
|
8
46
|
Release date: 27 July 2026
|
|
@@ -895,7 +895,7 @@ class ProbeInfoWindow(QDialog):
|
|
|
895
895
|
# export and close buttons
|
|
896
896
|
ok_btn = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok)
|
|
897
897
|
self.export_btn = ok_btn.addButton(
|
|
898
|
-
"Export probe
|
|
898
|
+
"Export probe localization files",
|
|
899
899
|
QDialogButtonBox.ButtonRole.ActionRole,
|
|
900
900
|
)
|
|
901
901
|
self.export_btn.clicked.connect(self.export_coordinates)
|
|
@@ -919,7 +919,7 @@ class ProbeInfoWindow(QDialog):
|
|
|
919
919
|
)
|
|
920
920
|
file_path, _ = QFileDialog.getSaveFileName(
|
|
921
921
|
self,
|
|
922
|
-
"Choose a base name for the
|
|
922
|
+
"Choose a base name for the probe localization CSV files",
|
|
923
923
|
"{}_probe_export.csv".format(safe_name or "probe"),
|
|
924
924
|
"CSV files (*.csv)",
|
|
925
925
|
)
|
|
@@ -938,7 +938,7 @@ class ProbeInfoWindow(QDialog):
|
|
|
938
938
|
return
|
|
939
939
|
QMessageBox.information(
|
|
940
940
|
self,
|
|
941
|
-
"Probe
|
|
941
|
+
"Probe localization files exported",
|
|
942
942
|
"Created:\n{}".format(
|
|
943
943
|
"\n".join(str(path) for path in paths.values())
|
|
944
944
|
),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""CSV exports for probe contacts,
|
|
1
|
+
"""CSV exports for probe contacts, labeled track, trajectory, and regions."""
|
|
2
2
|
|
|
3
3
|
import csv
|
|
4
4
|
from pathlib import Path
|
|
@@ -159,6 +159,54 @@ def iter_probe_contact_rows(probe_name, probe_data):
|
|
|
159
159
|
yield row
|
|
160
160
|
|
|
161
161
|
|
|
162
|
+
def iter_probe_track_rows(probe_name, probe_data):
|
|
163
|
+
"""Yield labeled centerline samples ordered from insertion to tip."""
|
|
164
|
+
reconstruction = _reconstruction(probe_data)
|
|
165
|
+
coordinates = reconstruction["coordinates"]
|
|
166
|
+
if "track" not in coordinates:
|
|
167
|
+
raise ValueError(
|
|
168
|
+
"This probe has no labeled centerline. Re-merge the probe with the "
|
|
169
|
+
"current DriftlessMap version before exporting it."
|
|
170
|
+
)
|
|
171
|
+
track = coordinates["track"]
|
|
172
|
+
source_axes = reconstruction["atlas"]["source_axes"]
|
|
173
|
+
count = int(track["count"])
|
|
174
|
+
sample_indexes = np.asarray(track["sample_index"], dtype=int)
|
|
175
|
+
depth_from_insertion = np.asarray(
|
|
176
|
+
track["axial_depth_from_insertion_um"], dtype=float
|
|
177
|
+
)
|
|
178
|
+
distance_from_tip = np.asarray(
|
|
179
|
+
track["axial_distance_up_from_tip_um"], dtype=float
|
|
180
|
+
)
|
|
181
|
+
structure_ids = np.asarray(track["structure_id"], dtype=int)
|
|
182
|
+
structure_acronyms = np.asarray(
|
|
183
|
+
track["structure_acronym"], dtype=object
|
|
184
|
+
)
|
|
185
|
+
structure_names = np.asarray(track["structure_name"], dtype=object)
|
|
186
|
+
|
|
187
|
+
for index in range(count):
|
|
188
|
+
coordinate = _indexed_coordinate_record(track, index)
|
|
189
|
+
row = {
|
|
190
|
+
"probe_name": probe_name,
|
|
191
|
+
"track_sample_index": int(sample_indexes[index]),
|
|
192
|
+
"axial_depth_from_insertion_um": float(
|
|
193
|
+
depth_from_insertion[index]
|
|
194
|
+
),
|
|
195
|
+
"axial_distance_up_from_tip_um": float(
|
|
196
|
+
distance_from_tip[index]
|
|
197
|
+
),
|
|
198
|
+
}
|
|
199
|
+
row.update(_coordinate_columns(coordinate, source_axes))
|
|
200
|
+
row.update(
|
|
201
|
+
{
|
|
202
|
+
"structure_id": int(structure_ids[index]),
|
|
203
|
+
"structure_acronym": str(structure_acronyms[index]),
|
|
204
|
+
"structure_name": str(structure_names[index]),
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
yield row
|
|
208
|
+
|
|
209
|
+
|
|
162
210
|
def probe_trajectory_row(probe_name, probe_data):
|
|
163
211
|
"""Return one complete trajectory-summary row."""
|
|
164
212
|
reconstruction = _reconstruction(probe_data)
|
|
@@ -167,8 +215,14 @@ def probe_trajectory_row(probe_name, probe_data):
|
|
|
167
215
|
coordinates = reconstruction["coordinates"]
|
|
168
216
|
source_axes = atlas["source_axes"]
|
|
169
217
|
settings = probe.get("settings") or {}
|
|
218
|
+
software = reconstruction.get("software") or {}
|
|
219
|
+
contacts = coordinates["contacts"]
|
|
220
|
+
track = coordinates.get("track") or {}
|
|
170
221
|
row = {
|
|
171
222
|
"probe_name": probe_name,
|
|
223
|
+
"reconstruction_schema_version": int(reconstruction["schema_version"]),
|
|
224
|
+
"software_name": software.get("name", "DriftlessMap"),
|
|
225
|
+
"software_version": software.get("version") or "unknown",
|
|
172
226
|
"probe_type": settings.get(
|
|
173
227
|
"probe_type_name", probe_data.get("probe_type_name", "unspecified")
|
|
174
228
|
),
|
|
@@ -183,7 +237,14 @@ def probe_trajectory_row(probe_name, probe_data):
|
|
|
183
237
|
"ML_tilt_direction": probe_data.get("ml_tilt", "unspecified"),
|
|
184
238
|
"insertion_to_tip_length_um": float(probe_data["probe_length"]),
|
|
185
239
|
"vertical_depth_change_um": float(probe_data["dv"]),
|
|
240
|
+
"tip_to_lowest_contact_center_um": float(
|
|
241
|
+
np.min(contacts["axial_distance_up_from_tip_um"])
|
|
242
|
+
),
|
|
186
243
|
}
|
|
244
|
+
if track:
|
|
245
|
+
row["track_sampling_interval_um"] = float(
|
|
246
|
+
track["sampling_interval_um"]
|
|
247
|
+
)
|
|
187
248
|
if atlas.get("source_version"):
|
|
188
249
|
row["atlas_version"] = atlas["source_version"]
|
|
189
250
|
if atlas.get("identifier"):
|
|
@@ -293,22 +354,36 @@ def write_probe_csv(path, probe_name, probe_data):
|
|
|
293
354
|
def _export_paths(path):
|
|
294
355
|
path = Path(path)
|
|
295
356
|
base_name = path.stem if path.suffix else path.name
|
|
296
|
-
for suffix in ("_contacts", "_trajectory", "_regions"):
|
|
357
|
+
for suffix in ("_contacts", "_track", "_trajectory", "_regions"):
|
|
297
358
|
if base_name.endswith(suffix):
|
|
298
359
|
base_name = base_name[: -len(suffix)]
|
|
299
360
|
break
|
|
300
361
|
parent = path.parent
|
|
301
362
|
return {
|
|
302
363
|
"contacts": parent / "{}_contacts.csv".format(base_name),
|
|
364
|
+
"track": parent / "{}_track.csv".format(base_name),
|
|
303
365
|
"trajectory": parent / "{}_trajectory.csv".format(base_name),
|
|
304
366
|
"regions": parent / "{}_regions.csv".format(base_name),
|
|
305
367
|
}
|
|
306
368
|
|
|
307
369
|
|
|
308
370
|
def write_probe_csv_files(path, probe_name, probe_data):
|
|
309
|
-
"""Write
|
|
371
|
+
"""Write contacts, labeled track, trajectory, and region CSV files."""
|
|
310
372
|
paths = _export_paths(path)
|
|
311
373
|
write_probe_csv(paths["contacts"], probe_name, probe_data)
|
|
374
|
+
_write_rows(
|
|
375
|
+
paths["track"],
|
|
376
|
+
iter_probe_track_rows(probe_name, probe_data),
|
|
377
|
+
(
|
|
378
|
+
"probe_name",
|
|
379
|
+
"track_sample_index",
|
|
380
|
+
"axial_depth_from_insertion_um",
|
|
381
|
+
"axial_distance_up_from_tip_um",
|
|
382
|
+
"structure_id",
|
|
383
|
+
"structure_acronym",
|
|
384
|
+
"structure_name",
|
|
385
|
+
),
|
|
386
|
+
)
|
|
312
387
|
trajectory = probe_trajectory_row(probe_name, probe_data)
|
|
313
388
|
_write_rows(paths["trajectory"], [trajectory], tuple(trajectory))
|
|
314
389
|
_write_rows(
|
|
@@ -5,7 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
PROBE_RECONSTRUCTION_SCHEMA_VERSION =
|
|
8
|
+
PROBE_RECONSTRUCTION_SCHEMA_VERSION = 2
|
|
9
9
|
HERBS_AXES = ("LR", "AP", "DV")
|
|
10
10
|
HERBS_AXIS_DIRECTIONS = ("right", "anterior", "superior")
|
|
11
11
|
_OPPOSITE_DIRECTION = {
|
|
@@ -243,6 +243,10 @@ def build_probe_reconstruction(
|
|
|
243
243
|
contact_vox_index,
|
|
244
244
|
contact_structure_ids,
|
|
245
245
|
contact_local_from_tip_base_um,
|
|
246
|
+
track_bregma_vox=None,
|
|
247
|
+
track_vox_index=None,
|
|
248
|
+
track_structure_ids=None,
|
|
249
|
+
track_axial_depth_from_insertion_um=None,
|
|
246
250
|
probe_length_um,
|
|
247
251
|
probe_settings,
|
|
248
252
|
site_face,
|
|
@@ -260,6 +264,9 @@ def build_probe_reconstruction(
|
|
|
260
264
|
|
|
261
265
|
Contacts are flattened column-major. Within a column, index zero is the
|
|
262
266
|
contact nearest the geometric tip for probe geometries generated by HERBS.
|
|
267
|
+
The optional track arrays describe the fitted probe centerline in
|
|
268
|
+
insertion-to-tip order and make downstream per-unit localization possible
|
|
269
|
+
without requiring access to the original atlas annotation volume.
|
|
263
270
|
"""
|
|
264
271
|
voxel_size_um = float(voxel_size_um)
|
|
265
272
|
if not np.isfinite(voxel_size_um) or voxel_size_um <= 0:
|
|
@@ -335,6 +342,76 @@ def build_probe_reconstruction(
|
|
|
335
342
|
allen_ccf_to_estimated_bregma_mm(contacts["allen_ccf_um"])
|
|
336
343
|
)
|
|
337
344
|
|
|
345
|
+
track = None
|
|
346
|
+
supplied_track_values = (
|
|
347
|
+
track_bregma_vox,
|
|
348
|
+
track_vox_index,
|
|
349
|
+
track_structure_ids,
|
|
350
|
+
track_axial_depth_from_insertion_um,
|
|
351
|
+
)
|
|
352
|
+
if any(value is not None for value in supplied_track_values):
|
|
353
|
+
if not all(value is not None for value in supplied_track_values):
|
|
354
|
+
raise ValueError(
|
|
355
|
+
"Track coordinates, indexes, structure IDs, and axial depths "
|
|
356
|
+
"must be supplied together."
|
|
357
|
+
)
|
|
358
|
+
track_bregma_vox = np.asarray(track_bregma_vox, dtype=float)
|
|
359
|
+
track_vox_index = np.asarray(track_vox_index, dtype=int)
|
|
360
|
+
track_structure_ids = np.ravel(
|
|
361
|
+
np.asarray(track_structure_ids, dtype=int)
|
|
362
|
+
)
|
|
363
|
+
track_axial_depth_from_insertion_um = np.ravel(
|
|
364
|
+
np.asarray(track_axial_depth_from_insertion_um, dtype=float)
|
|
365
|
+
)
|
|
366
|
+
track_count = len(track_axial_depth_from_insertion_um)
|
|
367
|
+
if (
|
|
368
|
+
track_bregma_vox.shape != (track_count, 3)
|
|
369
|
+
or track_vox_index.shape != (track_count, 3)
|
|
370
|
+
or len(track_structure_ids) != track_count
|
|
371
|
+
):
|
|
372
|
+
raise ValueError(
|
|
373
|
+
"Track coordinate arrays must contain the same number of rows."
|
|
374
|
+
)
|
|
375
|
+
if track_count < 2:
|
|
376
|
+
raise ValueError("A reconstructed track requires at least two samples.")
|
|
377
|
+
if np.any(np.diff(track_axial_depth_from_insertion_um) < 0):
|
|
378
|
+
raise ValueError(
|
|
379
|
+
"Track depths must be ordered from insertion toward the tip."
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
track_record = _coordinate_record(
|
|
383
|
+
track_bregma_vox,
|
|
384
|
+
bregma_herbs_vox,
|
|
385
|
+
voxel_size_um,
|
|
386
|
+
normalized_axis_info,
|
|
387
|
+
voxel_index=track_vox_index,
|
|
388
|
+
allen_ccf=allen_ccf,
|
|
389
|
+
)
|
|
390
|
+
track = {
|
|
391
|
+
"count": track_count,
|
|
392
|
+
"ordering": "insertion-to-tip",
|
|
393
|
+
"sample_index": np.arange(track_count, dtype=int),
|
|
394
|
+
"axial_depth_from_insertion_um": (
|
|
395
|
+
track_axial_depth_from_insertion_um
|
|
396
|
+
),
|
|
397
|
+
"axial_distance_up_from_tip_um": (
|
|
398
|
+
float(probe_length_um)
|
|
399
|
+
- track_axial_depth_from_insertion_um
|
|
400
|
+
),
|
|
401
|
+
"structure_id": track_structure_ids,
|
|
402
|
+
"structure_acronym": _structure_text(
|
|
403
|
+
track_structure_ids, label_info, "abbrev"
|
|
404
|
+
),
|
|
405
|
+
"structure_name": _structure_text(
|
|
406
|
+
track_structure_ids, label_info, "label"
|
|
407
|
+
),
|
|
408
|
+
**track_record,
|
|
409
|
+
}
|
|
410
|
+
if track_count > 1:
|
|
411
|
+
track["sampling_interval_um"] = float(
|
|
412
|
+
np.median(np.diff(track_axial_depth_from_insertion_um))
|
|
413
|
+
)
|
|
414
|
+
|
|
338
415
|
source_name = "Allen Mouse Common Coordinate Framework"
|
|
339
416
|
source_version = "CCFv3 2017"
|
|
340
417
|
if not allen_ccf:
|
|
@@ -405,5 +482,6 @@ def build_probe_reconstruction(
|
|
|
405
482
|
allen_ccf=allen_ccf,
|
|
406
483
|
),
|
|
407
484
|
"contacts": contacts,
|
|
485
|
+
**({"track": track} if track is not None else {}),
|
|
408
486
|
},
|
|
409
487
|
}
|
|
@@ -1050,6 +1050,30 @@ def calculate_probe_info(
|
|
|
1050
1050
|
"trajectory_fit": trajectory_fit,
|
|
1051
1051
|
}
|
|
1052
1052
|
|
|
1053
|
+
# Sample the fitted centerline at no more than one atlas voxel between
|
|
1054
|
+
# points. The resulting labeled track is persisted with the reconstruction
|
|
1055
|
+
# so downstream tools can assign continuous unit depths to atlas regions
|
|
1056
|
+
# without reopening DriftlessMap's processed annotation volume.
|
|
1057
|
+
track_vector = np.asarray(pc_ep, dtype=float) - np.asarray(pc_sp, dtype=float)
|
|
1058
|
+
track_length_vox = float(np.linalg.norm(track_vector))
|
|
1059
|
+
if track_length_vox <= np.finfo(float).eps:
|
|
1060
|
+
return None, PROBE_COORDINATES_OUTSIDE_ATLAS
|
|
1061
|
+
track_direction = track_vector / track_length_vox
|
|
1062
|
+
track_count = max(2, int(np.ceil(probe_length_with_tip_um / vxsize_um)) + 1)
|
|
1063
|
+
track_depth_um = np.linspace(
|
|
1064
|
+
0.0, float(probe_length_with_tip_um), track_count
|
|
1065
|
+
)
|
|
1066
|
+
track_pnt = (
|
|
1067
|
+
np.asarray(pc_sp, dtype=float)
|
|
1068
|
+
+ (track_depth_um / vxsize_um)[:, None] * track_direction
|
|
1069
|
+
)
|
|
1070
|
+
track_vox = (track_pnt + np.asarray(bregma, dtype=float)).astype(int)
|
|
1071
|
+
if not coordinates_in_bounds(track_vox, label_data.shape):
|
|
1072
|
+
return None, PROBE_COORDINATES_OUTSIDE_ATLAS
|
|
1073
|
+
track_labels = label_data[
|
|
1074
|
+
track_vox[:, 0], track_vox[:, 1], track_vox[:, 2]
|
|
1075
|
+
]
|
|
1076
|
+
|
|
1053
1077
|
atlas_metadata = atlas_metadata or {}
|
|
1054
1078
|
data_dict["reconstruction"] = build_probe_reconstruction(
|
|
1055
1079
|
insertion_bregma_vox=pc_sp,
|
|
@@ -1060,6 +1084,10 @@ def calculate_probe_info(
|
|
|
1060
1084
|
contact_vox_index=contact_vox,
|
|
1061
1085
|
contact_structure_ids=contact_labels,
|
|
1062
1086
|
contact_local_from_tip_base_um=sites_loc_to_base_temp,
|
|
1087
|
+
track_bregma_vox=track_pnt,
|
|
1088
|
+
track_vox_index=track_vox,
|
|
1089
|
+
track_structure_ids=track_labels,
|
|
1090
|
+
track_axial_depth_from_insertion_um=track_depth_um,
|
|
1063
1091
|
probe_length_um=probe_length_with_tip_um,
|
|
1064
1092
|
probe_settings=probe_settings,
|
|
1065
1093
|
site_face=site_face,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: driftlessmap
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Interactive histology registration and brain-atlas mapping
|
|
5
5
|
Author: Pearl Saldanha, Jacopo Paglia, Jonathan R. Whitlock, Mohebi & Associates, Ali Mohebi
|
|
6
6
|
Author-email: Jingyi Guo Fuglstad <jingyi.g.fuglstad@gmail.com>
|
|
@@ -98,11 +98,19 @@ See [CITATION.cff](CITATION.cff) for machine-readable citation metadata.
|
|
|
98
98
|
- Safe, versioned project and object archives.
|
|
99
99
|
- Support for custom compatible atlases.
|
|
100
100
|
|
|
101
|
-
## Install
|
|
101
|
+
## Install from PyPI
|
|
102
102
|
|
|
103
|
-
DriftlessMap 1.
|
|
103
|
+
DriftlessMap 1.2.0 supports Python 3.10–3.14 and Qt 6 through PyQt6. Python
|
|
104
104
|
3.14 in a dedicated environment is recommended for new installations.
|
|
105
105
|
|
|
106
|
+
Install the latest stable release:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
python -m pip install driftlessmap
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
For a dedicated Conda environment:
|
|
113
|
+
|
|
106
114
|
```bash
|
|
107
115
|
conda create --name DriftlessMap python=3.14 -y
|
|
108
116
|
conda activate DriftlessMap
|
|
@@ -133,7 +133,7 @@ class ProbeInfoWindowTests(unittest.TestCase):
|
|
|
133
133
|
|
|
134
134
|
self.assertEqual(
|
|
135
135
|
window.export_btn.text(),
|
|
136
|
-
"Export probe
|
|
136
|
+
"Export probe localization files",
|
|
137
137
|
)
|
|
138
138
|
self.assertIn("AP tilt from vertical : ", labels)
|
|
139
139
|
self.assertIn("Vertical depth change : ", labels)
|
|
@@ -158,6 +158,7 @@ class ProbeInfoWindowTests(unittest.TestCase):
|
|
|
158
158
|
"driftlessmap.object_control.write_probe_csv_files",
|
|
159
159
|
return_value={
|
|
160
160
|
"contacts": "/tmp/probe_export_contacts.csv",
|
|
161
|
+
"track": "/tmp/probe_export_track.csv",
|
|
161
162
|
"trajectory": "/tmp/probe_export_trajectory.csv",
|
|
162
163
|
"regions": "/tmp/probe_export_regions.csv",
|
|
163
164
|
},
|
|
@@ -113,6 +113,7 @@ class PackagingMetadataTests(unittest.TestCase):
|
|
|
113
113
|
sorted(path.name for path in REPOSITORY_ROOT.glob("WhatsNew*.md")),
|
|
114
114
|
["WhatsNew.md"],
|
|
115
115
|
)
|
|
116
|
+
self.assertIn("## DriftlessMap 1.2.0", history)
|
|
116
117
|
self.assertIn("## DriftlessMap 1.1.0", history)
|
|
117
118
|
for version in (
|
|
118
119
|
"1.0.5",
|
|
@@ -8,6 +8,7 @@ import numpy as np
|
|
|
8
8
|
from driftlessmap.probe_csv import (
|
|
9
9
|
iter_probe_contact_rows,
|
|
10
10
|
iter_probe_region_rows,
|
|
11
|
+
iter_probe_track_rows,
|
|
11
12
|
probe_trajectory_row,
|
|
12
13
|
write_probe_csv_files,
|
|
13
14
|
)
|
|
@@ -43,6 +44,14 @@ def probe_data():
|
|
|
43
44
|
np.array([[10.0, -8.0, 12.0], [50.0, -8.0, 12.0]]),
|
|
44
45
|
np.array([[30.0, 16.0, 12.0]]),
|
|
45
46
|
],
|
|
47
|
+
track_bregma_vox=np.array(
|
|
48
|
+
[[0.0, 0.0, value] for value in np.linspace(1.0, -4.0, 6)]
|
|
49
|
+
),
|
|
50
|
+
track_vox_index=np.array(
|
|
51
|
+
[[228, 263, value] for value in range(160, 154, -1)]
|
|
52
|
+
),
|
|
53
|
+
track_structure_ids=np.array([10, 10, 10, 11, 11, 11]),
|
|
54
|
+
track_axial_depth_from_insertion_um=np.linspace(0, 10000, 6),
|
|
46
55
|
probe_length_um=10000,
|
|
47
56
|
probe_settings={"probe_type_name": "test", "tip_length": 175},
|
|
48
57
|
site_face="Front",
|
|
@@ -115,6 +124,9 @@ class ProbeCsvTests(unittest.TestCase):
|
|
|
115
124
|
|
|
116
125
|
self.assertEqual(trajectory["insertion_to_tip_length_um"], 10000)
|
|
117
126
|
self.assertEqual(trajectory["fit_inliers"], 3)
|
|
127
|
+
self.assertEqual(trajectory["reconstruction_schema_version"], 2)
|
|
128
|
+
self.assertEqual(trajectory["tip_to_lowest_contact_center_um"], 185)
|
|
129
|
+
self.assertEqual(trajectory["track_sampling_interval_um"], 2000)
|
|
118
130
|
self.assertIn("insertion_allen_AP_vox", trajectory)
|
|
119
131
|
self.assertIn("tip_allen_AP_vox", trajectory)
|
|
120
132
|
self.assertNotIn("structure_id", trajectory)
|
|
@@ -129,7 +141,19 @@ class ProbeCsvTests(unittest.TestCase):
|
|
|
129
141
|
all(value != "" for row in regions for value in row.values())
|
|
130
142
|
)
|
|
131
143
|
|
|
132
|
-
def
|
|
144
|
+
def test_track_rows_are_in_insertion_to_tip_order(self):
|
|
145
|
+
rows = list(iter_probe_track_rows("probe one", probe_data()))
|
|
146
|
+
|
|
147
|
+
self.assertEqual(len(rows), 6)
|
|
148
|
+
self.assertEqual(rows[0]["axial_depth_from_insertion_um"], 0)
|
|
149
|
+
self.assertEqual(rows[0]["axial_distance_up_from_tip_um"], 10000)
|
|
150
|
+
self.assertEqual(rows[-1]["axial_depth_from_insertion_um"], 10000)
|
|
151
|
+
self.assertEqual(rows[-1]["axial_distance_up_from_tip_um"], 0)
|
|
152
|
+
self.assertEqual(rows[0]["structure_acronym"], "R10")
|
|
153
|
+
self.assertEqual(rows[-1]["structure_acronym"], "R11")
|
|
154
|
+
self.assertIn("allen_AP_vox", rows[0])
|
|
155
|
+
|
|
156
|
+
def test_writer_creates_four_consistent_csv_files(self):
|
|
133
157
|
with tempfile.TemporaryDirectory() as folder:
|
|
134
158
|
selected_path = Path(folder) / "probe_export.csv"
|
|
135
159
|
paths = write_probe_csv_files(
|
|
@@ -140,6 +164,7 @@ class ProbeCsvTests(unittest.TestCase):
|
|
|
140
164
|
paths,
|
|
141
165
|
{
|
|
142
166
|
"contacts": Path(folder) / "probe_export_contacts.csv",
|
|
167
|
+
"track": Path(folder) / "probe_export_track.csv",
|
|
143
168
|
"trajectory": (
|
|
144
169
|
Path(folder) / "probe_export_trajectory.csv"
|
|
145
170
|
),
|
|
@@ -152,9 +177,11 @@ class ProbeCsvTests(unittest.TestCase):
|
|
|
152
177
|
tables[table_name] = list(csv.DictReader(csv_file))
|
|
153
178
|
|
|
154
179
|
self.assertEqual(len(tables["contacts"]), 3)
|
|
180
|
+
self.assertEqual(len(tables["track"]), 6)
|
|
155
181
|
self.assertEqual(len(tables["trajectory"]), 1)
|
|
156
182
|
self.assertEqual(len(tables["regions"]), 2)
|
|
157
183
|
self.assertNotIn("record_type", tables["contacts"][0])
|
|
184
|
+
self.assertIn("structure_id", tables["track"][0])
|
|
158
185
|
self.assertNotIn("structure_id", tables["trajectory"][0])
|
|
159
186
|
self.assertNotIn("herbs_ML_vox", tables["regions"][0])
|
|
160
187
|
|
|
@@ -127,6 +127,15 @@ class ProbeMappingTests(unittest.TestCase):
|
|
|
127
127
|
],
|
|
128
128
|
"3D fitted-line intersection with atlas brain mask",
|
|
129
129
|
)
|
|
130
|
+
track = info["reconstruction"]["coordinates"]["track"]
|
|
131
|
+
self.assertGreaterEqual(track["count"], 2)
|
|
132
|
+
self.assertEqual(track["ordering"], "insertion-to-tip")
|
|
133
|
+
self.assertTrue(
|
|
134
|
+
np.all(np.diff(track["axial_depth_from_insertion_um"]) >= 0)
|
|
135
|
+
)
|
|
136
|
+
self.assertEqual(
|
|
137
|
+
track["structure_acronym"], ["TR"] * track["count"]
|
|
138
|
+
)
|
|
130
139
|
|
|
131
140
|
|
|
132
141
|
if __name__ == "__main__":
|
|
@@ -135,6 +135,14 @@ class ProbeReconstructionTests(unittest.TestCase):
|
|
|
135
135
|
np.array([[10.0, -8.0, 12.0], [50.0, -8.0, 12.0]]),
|
|
136
136
|
np.array([[30.0, 16.0, 12.0]]),
|
|
137
137
|
],
|
|
138
|
+
track_bregma_vox=np.array(
|
|
139
|
+
[[0.0, 0.0, value] for value in np.linspace(1.0, -4.0, 6)]
|
|
140
|
+
),
|
|
141
|
+
track_vox_index=np.array(
|
|
142
|
+
[[228, 263, value] for value in range(160, 154, -1)]
|
|
143
|
+
),
|
|
144
|
+
track_structure_ids=np.array([10, 10, 10, 11, 11, 11]),
|
|
145
|
+
track_axial_depth_from_insertion_um=np.linspace(0, 10000, 6),
|
|
138
146
|
probe_length_um=10000,
|
|
139
147
|
probe_settings={"probe_type_name": "test", "tip_length": 175},
|
|
140
148
|
site_face="Front",
|
|
@@ -165,7 +173,7 @@ class ProbeReconstructionTests(unittest.TestCase):
|
|
|
165
173
|
atlas = payload["atlas"]
|
|
166
174
|
contacts = payload["coordinates"]["contacts"]
|
|
167
175
|
|
|
168
|
-
self.assertEqual(payload["schema_version"],
|
|
176
|
+
self.assertEqual(payload["schema_version"], 2)
|
|
169
177
|
self.assertEqual(atlas["source_version"], "CCFv3 2017")
|
|
170
178
|
self.assertEqual(atlas["source_axes"], ["AP", "DV", "LR"])
|
|
171
179
|
self.assertEqual(tuple(atlas["source_shape_vox"]), (528, 320, 456))
|
|
@@ -196,6 +204,14 @@ class ProbeReconstructionTests(unittest.TestCase):
|
|
|
196
204
|
transform = atlas["estimated_stereotaxic_transform"]
|
|
197
205
|
self.assertFalse(transform["ground_truth"])
|
|
198
206
|
self.assertIn("brain surface", transform["targeting_note"])
|
|
207
|
+
track = payload["coordinates"]["track"]
|
|
208
|
+
self.assertEqual(track["ordering"], "insertion-to-tip")
|
|
209
|
+
self.assertEqual(track["count"], 6)
|
|
210
|
+
np.testing.assert_allclose(
|
|
211
|
+
track["axial_distance_up_from_tip_um"],
|
|
212
|
+
[10000, 8000, 6000, 4000, 2000, 0],
|
|
213
|
+
)
|
|
214
|
+
self.assertEqual(track["structure_acronym"], ["R10"] * 3 + ["R11"] * 3)
|
|
199
215
|
|
|
200
216
|
def test_payload_round_trips_inside_one_herbs_object(self):
|
|
201
217
|
data = {
|
|
@@ -16,7 +16,7 @@ class VersionTests(unittest.TestCase):
|
|
|
16
16
|
cls.app = QApplication.instance() or QApplication([])
|
|
17
17
|
|
|
18
18
|
def test_public_package_version_uses_the_canonical_value(self):
|
|
19
|
-
self.assertEqual(__version__, "1.
|
|
19
|
+
self.assertEqual(__version__, "1.2.0")
|
|
20
20
|
self.assertEqual(driftlessmap.__version__, __version__)
|
|
21
21
|
|
|
22
22
|
def test_about_dialog_reports_version_and_current_repository(self):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|