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.
Files changed (245) hide show
  1. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/CITATION.cff +1 -1
  2. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/PKG-INFO +11 -3
  3. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/README.md +10 -2
  4. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/WhatsNew.md +38 -0
  5. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/object_control.py +3 -3
  6. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/probe_csv.py +78 -3
  7. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/probe_reconstruction.py +79 -1
  8. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/probe_utiles.py +28 -0
  9. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/version.py +1 -1
  10. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap.egg-info/PKG-INFO +11 -3
  11. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_object_control.py +2 -1
  12. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_packaging_metadata.py +1 -0
  13. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_probe_csv.py +28 -1
  14. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_probe_mapping.py +9 -0
  15. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_probe_reconstruction.py +17 -1
  16. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_version.py +1 -1
  17. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/AUTHOR.txt +0 -0
  18. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/CONTRIBUTORS.md +0 -0
  19. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/LICENSE.txt +0 -0
  20. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/MANIFEST.in +0 -0
  21. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/ORIGINS.md +0 -0
  22. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/THANKS.txt +0 -0
  23. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/__init__.py +0 -0
  24. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/__main__.py +0 -0
  25. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/about.py +0 -0
  26. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/allen_downloader.py +0 -0
  27. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/app.py +0 -0
  28. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/atlas_downloader.py +0 -0
  29. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/atlas_loader.py +0 -0
  30. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/atlas_processor.py +0 -0
  31. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/atlas_transform.py +0 -0
  32. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/atlas_view.py +0 -0
  33. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/cell_detection.py +0 -0
  34. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/coordinate_validation.py +0 -0
  35. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/czi_reader.py +0 -0
  36. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/data/WHS_atlas_labels.pkl +0 -0
  37. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/data/allen_mice_atlas_labels.pkl +0 -0
  38. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/data/atlas_labels.pkl +0 -0
  39. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/data/query.csv +0 -0
  40. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/download_utils.py +0 -0
  41. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/herbs.png +0 -0
  42. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/backward.svg +0 -0
  43. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/dot.svg +0 -0
  44. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/down-arrow.svg +0 -0
  45. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/fast_backward.svg +0 -0
  46. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/fast_forward.svg +0 -0
  47. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/forward.svg +0 -0
  48. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/layers/add.png +0 -0
  49. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/layers/ai.png +0 -0
  50. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/layers/eye_off.png +0 -0
  51. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/layers/eye_on.png +0 -0
  52. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/layers/eye_white.png +0 -0
  53. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/layers/hi.png +0 -0
  54. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/layers/trash.png +0 -0
  55. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/object.svg +0 -0
  56. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/add.png +0 -0
  57. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/atlascontrol.png +0 -0
  58. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/atlascontrol.svg +0 -0
  59. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/bnd.svg +0 -0
  60. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/c_section.png +0 -0
  61. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/c_section2.png +0 -0
  62. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/cell.svg +0 -0
  63. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/check.svg +0 -0
  64. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/compare.svg +0 -0
  65. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/contour.svg +0 -0
  66. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/h_section.png +0 -0
  67. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/info.svg +0 -0
  68. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/layers.png +0 -0
  69. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/layers.svg +0 -0
  70. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/line.svg +0 -0
  71. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/link.svg +0 -0
  72. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/link_off.svg +0 -0
  73. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/merge.svg +0 -0
  74. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/object.svg +0 -0
  75. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/probe.png +0 -0
  76. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/probe.svg +0 -0
  77. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/rotation_horizontal.svg +0 -0
  78. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/rotation_vertical.svg +0 -0
  79. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/s_section.png +0 -0
  80. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/tool.png +0 -0
  81. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/tool.svg +0 -0
  82. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/trash.png +0 -0
  83. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/tree_checked.svg +0 -0
  84. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/treeview.png +0 -0
  85. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/treeview.svg +0 -0
  86. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/treeview2.png +0 -0
  87. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/sidebar/virus.svg +0 -0
  88. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/tdown.svg +0 -0
  89. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/accept.svg +0 -0
  90. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/accept2.svg +0 -0
  91. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/aim.svg +0 -0
  92. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/aim_not.svg +0 -0
  93. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/anchor.svg +0 -0
  94. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/anticlockwise_rotation.svg +0 -0
  95. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/atlas_icon.png +0 -0
  96. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/boundary_register.svg +0 -0
  97. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/cancel.svg +0 -0
  98. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/cell_select.svg +0 -0
  99. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/cell_select_not.svg +0 -0
  100. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/check.svg +0 -0
  101. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/clockwise_rotation.svg +0 -0
  102. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/closed_path.svg +0 -0
  103. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/closed_path2 copy.svg +0 -0
  104. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/closed_path2.svg +0 -0
  105. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/eraser.png +0 -0
  106. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/eraser.svg +0 -0
  107. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/eye.svg +0 -0
  108. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/eye_closed.svg +0 -0
  109. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/fill.svg +0 -0
  110. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/gps.svg +0 -0
  111. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/h_flip.png +0 -0
  112. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/handle.png +0 -0
  113. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/image_icon.png +0 -0
  114. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/image_icon.svg +0 -0
  115. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/info.svg +0 -0
  116. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/inpart.png +0 -0
  117. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/inpart.svg +0 -0
  118. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/lasso.png +0 -0
  119. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/lasso.svg +0 -0
  120. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/left90.png +0 -0
  121. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/line.svg +0 -0
  122. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/line_sites.svg +0 -0
  123. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/linear_silicon.png +0 -0
  124. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/linear_silicon.svg +0 -0
  125. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/list.svg +0 -0
  126. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/location.svg +0 -0
  127. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/magic-wand.svg +0 -0
  128. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/magic_white.png +0 -0
  129. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/mask.svg +0 -0
  130. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/match.svg +0 -0
  131. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/matchbnd.svg +0 -0
  132. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/matching.svg +0 -0
  133. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/merge.svg +0 -0
  134. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/move_down.png +0 -0
  135. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/move_left.png +0 -0
  136. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/move_right.png +0 -0
  137. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/move_up.png +0 -0
  138. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/moving.png +0 -0
  139. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/multi-probe.svg +0 -0
  140. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/multi_pencil.svg +0 -0
  141. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/multi_probe.png +0 -0
  142. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/object.svg +0 -0
  143. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/open_path.svg +0 -0
  144. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/outpart.png +0 -0
  145. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/outpart.svg +0 -0
  146. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/pencil.png +0 -0
  147. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/pencil.svg +0 -0
  148. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/plasso.png +0 -0
  149. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/probe.svg +0 -0
  150. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/radar.svg +0 -0
  151. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/rotation.svg +0 -0
  152. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/rotation_clockwise.svg +0 -0
  153. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/rotation_counter_clockwise.svg +0 -0
  154. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/ruler.svg +0 -0
  155. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/separate_sites.svg +0 -0
  156. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/single_pencil.svg +0 -0
  157. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/toa.svg +0 -0
  158. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/toa_delete.svg +0 -0
  159. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/toaa.svg +0 -0
  160. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/toh.svg +0 -0
  161. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/toh_delete.svg +0 -0
  162. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/tohh.svg +0 -0
  163. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/trans.png +0 -0
  164. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/trans.svg +0 -0
  165. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/triangulation.svg +0 -0
  166. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/two_window.png +0 -0
  167. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/unmerge.svg +0 -0
  168. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/v_flip.png +0 -0
  169. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/virus.svg +0 -0
  170. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/virus_register.svg +0 -0
  171. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/vis2d.svg +0 -0
  172. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/window2.png +0 -0
  173. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/window3.png +0 -0
  174. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/toolbar/window4.png +0 -0
  175. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/tree_close.svg +0 -0
  176. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/tree_open.svg +0 -0
  177. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/icons/up-arrow.svg +0 -0
  178. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/image_curves.py +0 -0
  179. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/image_reader.py +0 -0
  180. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/image_stacks.py +0 -0
  181. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/image_view.py +0 -0
  182. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/images_reader.py +0 -0
  183. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/label_tree.py +0 -0
  184. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/layer_validation.py +0 -0
  185. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/layers_control.py +0 -0
  186. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/main_window.ui +0 -0
  187. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/movable_points.py +0 -0
  188. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/obj_items.py +0 -0
  189. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/persistence.py +0 -0
  190. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/popup_message.py +0 -0
  191. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/atlas_view_group_box.qss +0 -0
  192. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/box_label.qss +0 -0
  193. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/channel_selector.qss +0 -0
  194. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/color_combo.qss +0 -0
  195. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/decor_label.qss +0 -0
  196. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/dialogs.qss +0 -0
  197. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/hidden_line_edit.qss +0 -0
  198. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/label_tree.qss +0 -0
  199. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/main_window.qss +0 -0
  200. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/menu_bar.qss +0 -0
  201. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/multi_handle_slider.qss +0 -0
  202. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/obj_ctrl_bottom_button.qss +0 -0
  203. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/object_text_button.qss +0 -0
  204. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/page_control.qss +0 -0
  205. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/rotation_button.qss +0 -0
  206. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/side_bar.qss +0 -0
  207. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/spinbox.qss +0 -0
  208. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/tabs.qss +0 -0
  209. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/qss/tool_bar.qss +0 -0
  210. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/resources.py +0 -0
  211. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/roi_analysis.py +0 -0
  212. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/run_driftlessmap.py +0 -0
  213. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/slice_stacks.py +0 -0
  214. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/slice_validation.py +0 -0
  215. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/toolbox.py +0 -0
  216. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/triangulation.py +0 -0
  217. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/triangulation_points.py +0 -0
  218. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/user_settings.py +0 -0
  219. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/uuuuuu.py +0 -0
  220. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/widgets_utils.py +0 -0
  221. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap/wtiles.py +0 -0
  222. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap.egg-info/SOURCES.txt +0 -0
  223. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap.egg-info/dependency_links.txt +0 -0
  224. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap.egg-info/entry_points.txt +0 -0
  225. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap.egg-info/requires.txt +0 -0
  226. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/driftlessmap.egg-info/top_level.txt +0 -0
  227. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/pyproject.toml +0 -0
  228. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/setup.cfg +0 -0
  229. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/setup.py +0 -0
  230. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_allen_downloader.py +0 -0
  231. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_atlas_loader.py +0 -0
  232. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_atlas_transform.py +0 -0
  233. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_atlas_view.py +0 -0
  234. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_cell_detection.py +0 -0
  235. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_coordinate_validation.py +0 -0
  236. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_download_utils.py +0 -0
  237. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_image_reader.py +0 -0
  238. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_label_tree.py +0 -0
  239. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_layer_validation.py +0 -0
  240. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_layers_control.py +0 -0
  241. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_persistence.py +0 -0
  242. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_roi_analysis.py +0 -0
  243. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_runtime_paths.py +0 -0
  244. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_slice_validation.py +0 -0
  245. {driftlessmap-1.1.0 → driftlessmap-1.2.0}/tests/test_triangulation.py +0 -0
@@ -4,7 +4,7 @@ message: >-
4
4
  HERBS paper listed under references.
5
5
  title: DriftlessMap
6
6
  type: software
7
- version: 1.1.0
7
+ version: 1.2.0
8
8
  authors:
9
9
  - name: Mohebi & Associates
10
10
  website: https://www.mohebi-associates.org/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: driftlessmap
3
- Version: 1.1.0
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.1.0 supports Python 3.10–3.14 and Qt 6 through PyQt6. Python
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.1.0 supports Python 3.10–3.14 and Qt 6 through PyQt6. Python
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 CSV files",
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 three probe CSV files",
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 CSV files exported",
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, trajectory metadata, and regions."""
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 separate contact, trajectory, and region CSV files."""
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 = 1
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,3 +1,3 @@
1
1
  """Canonical DriftlessMap version."""
2
2
 
3
- __version__ = "1.1.0"
3
+ __version__ = "1.2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: driftlessmap
3
- Version: 1.1.0
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.1.0 supports Python 3.10–3.14 and Qt 6 through PyQt6. Python
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 CSV files",
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 test_writer_creates_three_consistent_csv_files(self):
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"], 1)
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.1.0")
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