driftlessmap 1.6.0__tar.gz → 1.6.1__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 (272) hide show
  1. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/CITATION.cff +2 -2
  2. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/MANUAL.md +15 -8
  3. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/PKG-INFO +5 -5
  4. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/README.md +4 -4
  5. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/UpdateLog.md +5 -0
  6. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/WhatsNew.md +30 -0
  7. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/app.py +6 -3
  8. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/auto_registration.py +170 -37
  9. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/slice_stacks.py +3 -1
  10. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/version.py +1 -1
  11. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap.egg-info/PKG-INFO +5 -5
  12. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_atlas_view.py +9 -0
  13. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_auto_registration.py +73 -7
  14. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_version.py +1 -1
  15. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/AUTHOR.txt +0 -0
  16. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/CONTRIBUTING.md +0 -0
  17. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/CONTRIBUTORS.md +0 -0
  18. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/LICENSE.txt +0 -0
  19. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/MANIFEST.in +0 -0
  20. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/ORIGINS.md +0 -0
  21. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/THANKS.txt +0 -0
  22. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/__init__.py +0 -0
  23. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/__main__.py +0 -0
  24. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/about.py +0 -0
  25. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/allen_downloader.py +0 -0
  26. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/atlas_downloader.py +0 -0
  27. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/atlas_loader.py +0 -0
  28. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/atlas_matching.py +0 -0
  29. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/atlas_processor.py +0 -0
  30. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/atlas_transform.py +0 -0
  31. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/atlas_view.py +0 -0
  32. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/background.py +0 -0
  33. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/cell_detection.py +0 -0
  34. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/coordinate_validation.py +0 -0
  35. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/czi_reader.py +0 -0
  36. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/data/atlas_labels.pkl +0 -0
  37. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/data/query.csv +0 -0
  38. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/download_utils.py +0 -0
  39. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/app/driftlessmap.icns +0 -0
  40. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/app/driftlessmap.ico +0 -0
  41. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/app/driftlessmap.png +0 -0
  42. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/backward.svg +0 -0
  43. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/dot.svg +0 -0
  44. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/down-arrow.svg +0 -0
  45. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/fast_backward.svg +0 -0
  46. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/fast_forward.svg +0 -0
  47. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/forward.svg +0 -0
  48. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/layers/add.png +0 -0
  49. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/layers/ai.png +0 -0
  50. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/layers/eye_off.png +0 -0
  51. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/layers/eye_on.png +0 -0
  52. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/layers/eye_white.png +0 -0
  53. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/layers/hi.png +0 -0
  54. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/layers/trash.png +0 -0
  55. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/object.svg +0 -0
  56. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/add.png +0 -0
  57. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/atlascontrol.png +0 -0
  58. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/atlascontrol.svg +0 -0
  59. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/bnd.svg +0 -0
  60. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/c_section.png +0 -0
  61. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/c_section2.png +0 -0
  62. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/cell.svg +0 -0
  63. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/check.svg +0 -0
  64. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/compare.svg +0 -0
  65. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/contour.svg +0 -0
  66. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/h_section.png +0 -0
  67. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/info.svg +0 -0
  68. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/layers.png +0 -0
  69. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/layers.svg +0 -0
  70. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/line.svg +0 -0
  71. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/link.svg +0 -0
  72. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/link_off.svg +0 -0
  73. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/merge.svg +0 -0
  74. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/object.svg +0 -0
  75. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/probe.png +0 -0
  76. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/probe.svg +0 -0
  77. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/rotation_horizontal.svg +0 -0
  78. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/rotation_vertical.svg +0 -0
  79. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/s_section.png +0 -0
  80. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/tool.png +0 -0
  81. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/tool.svg +0 -0
  82. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/trash.png +0 -0
  83. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/tree_checked.svg +0 -0
  84. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/treeview.png +0 -0
  85. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/treeview.svg +0 -0
  86. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/treeview2.png +0 -0
  87. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/sidebar/virus.svg +0 -0
  88. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/tdown.svg +0 -0
  89. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/accept.svg +0 -0
  90. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/accept2.svg +0 -0
  91. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/aim.svg +0 -0
  92. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/aim_not.svg +0 -0
  93. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/anchor.svg +0 -0
  94. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/anticlockwise_rotation.svg +0 -0
  95. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/atlas_icon.png +0 -0
  96. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/boundary_register.svg +0 -0
  97. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/cancel.svg +0 -0
  98. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/cell_select.svg +0 -0
  99. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/cell_select_not.svg +0 -0
  100. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/check.svg +0 -0
  101. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/clockwise_rotation.svg +0 -0
  102. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/closed_path.svg +0 -0
  103. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/closed_path2 copy.svg +0 -0
  104. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/closed_path2.svg +0 -0
  105. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/eraser.png +0 -0
  106. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/eraser.svg +0 -0
  107. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/eye.svg +0 -0
  108. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/eye_closed.svg +0 -0
  109. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/fill.svg +0 -0
  110. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/gps.svg +0 -0
  111. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/h_flip.png +0 -0
  112. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/handle.png +0 -0
  113. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/image_icon.png +0 -0
  114. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/image_icon.svg +0 -0
  115. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/info.svg +0 -0
  116. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/inpart.png +0 -0
  117. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/inpart.svg +0 -0
  118. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/lasso.png +0 -0
  119. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/lasso.svg +0 -0
  120. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/left90.png +0 -0
  121. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/line.svg +0 -0
  122. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/line_sites.svg +0 -0
  123. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/linear_silicon.png +0 -0
  124. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/linear_silicon.svg +0 -0
  125. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/list.svg +0 -0
  126. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/location.svg +0 -0
  127. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/magic-wand.svg +0 -0
  128. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/magic_white.png +0 -0
  129. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/mask.svg +0 -0
  130. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/match.svg +0 -0
  131. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/matchbnd.svg +0 -0
  132. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/matching.svg +0 -0
  133. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/merge.svg +0 -0
  134. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/move_down.png +0 -0
  135. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/move_left.png +0 -0
  136. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/move_right.png +0 -0
  137. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/move_up.png +0 -0
  138. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/moving.png +0 -0
  139. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/multi-probe.svg +0 -0
  140. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/multi_pencil.svg +0 -0
  141. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/multi_probe.png +0 -0
  142. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/object.svg +0 -0
  143. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/open_path.svg +0 -0
  144. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/outpart.png +0 -0
  145. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/outpart.svg +0 -0
  146. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/pencil.png +0 -0
  147. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/pencil.svg +0 -0
  148. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/plasso.png +0 -0
  149. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/probe.svg +0 -0
  150. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/radar.svg +0 -0
  151. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/rotation.svg +0 -0
  152. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/rotation_clockwise.svg +0 -0
  153. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/rotation_counter_clockwise.svg +0 -0
  154. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/ruler.svg +0 -0
  155. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/separate_sites.svg +0 -0
  156. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/single_pencil.svg +0 -0
  157. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/toa.svg +0 -0
  158. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/toa_delete.svg +0 -0
  159. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/toaa.svg +0 -0
  160. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/toh.svg +0 -0
  161. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/toh_delete.svg +0 -0
  162. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/tohh.svg +0 -0
  163. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/trans.png +0 -0
  164. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/trans.svg +0 -0
  165. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/triangulation.svg +0 -0
  166. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/two_window.png +0 -0
  167. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/unmerge.svg +0 -0
  168. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/v_flip.png +0 -0
  169. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/virus.svg +0 -0
  170. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/virus_register.svg +0 -0
  171. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/vis2d.svg +0 -0
  172. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/window2.png +0 -0
  173. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/window3.png +0 -0
  174. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/toolbar/window4.png +0 -0
  175. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/tree_close.svg +0 -0
  176. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/tree_open.svg +0 -0
  177. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/icons/up-arrow.svg +0 -0
  178. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/image_curves.py +0 -0
  179. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/image_reader.py +0 -0
  180. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/image_stacks.py +0 -0
  181. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/image_view.py +0 -0
  182. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/label_tree.py +0 -0
  183. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/landmarks.py +0 -0
  184. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/layer_geometry.py +0 -0
  185. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/layer_validation.py +0 -0
  186. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/layers_control.py +0 -0
  187. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/main_window.ui +0 -0
  188. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/movable_points.py +0 -0
  189. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/obj_items.py +0 -0
  190. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/object_control.py +0 -0
  191. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/persistence.py +0 -0
  192. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/probe_csv.py +0 -0
  193. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/probe_reconstruction.py +0 -0
  194. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/probe_utiles.py +0 -0
  195. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/project_io.py +0 -0
  196. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/provenance.py +0 -0
  197. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/atlas_view_group_box.qss +0 -0
  198. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/box_label.qss +0 -0
  199. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/channel_selector.qss +0 -0
  200. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/color_combo.qss +0 -0
  201. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/decor_label.qss +0 -0
  202. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/dialogs.qss +0 -0
  203. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/hidden_line_edit.qss +0 -0
  204. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/label_tree.qss +0 -0
  205. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/main_window.qss +0 -0
  206. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/menu_bar.qss +0 -0
  207. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/multi_handle_slider.qss +0 -0
  208. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/obj_ctrl_bottom_button.qss +0 -0
  209. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/object_text_button.qss +0 -0
  210. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/page_control.qss +0 -0
  211. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/rotation_button.qss +0 -0
  212. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/side_bar.qss +0 -0
  213. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/spinbox.qss +0 -0
  214. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/tabs.qss +0 -0
  215. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/qss/tool_bar.qss +0 -0
  216. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/resources.py +0 -0
  217. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/roi_analysis.py +0 -0
  218. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/run_driftlessmap.py +0 -0
  219. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/section_suggestion_dialog.py +0 -0
  220. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/slice_validation.py +0 -0
  221. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/toolbox.py +0 -0
  222. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/triangulation.py +0 -0
  223. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/user_settings.py +0 -0
  224. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/utils.py +0 -0
  225. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/widgets_utils.py +0 -0
  226. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap/wtiles.py +0 -0
  227. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap.egg-info/SOURCES.txt +0 -0
  228. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap.egg-info/dependency_links.txt +0 -0
  229. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap.egg-info/entry_points.txt +0 -0
  230. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap.egg-info/requires.txt +0 -0
  231. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/driftlessmap.egg-info/top_level.txt +0 -0
  232. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/pyproject.toml +0 -0
  233. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/setup.cfg +0 -0
  234. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/setup.py +0 -0
  235. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/atlas_fixture.py +0 -0
  236. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/run_test_suite.py +0 -0
  237. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_allen_downloader.py +0 -0
  238. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_atlas_cache_integrity.py +0 -0
  239. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_atlas_loader.py +0 -0
  240. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_atlas_matching.py +0 -0
  241. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_atlas_transform.py +0 -0
  242. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_atlas_workers.py +0 -0
  243. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_background.py +0 -0
  244. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_cell_detection.py +0 -0
  245. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_coordinate_validation.py +0 -0
  246. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_czi_reader.py +0 -0
  247. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_download_utils.py +0 -0
  248. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_editing_tools.py +0 -0
  249. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_histogram_data.py +0 -0
  250. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_image_geometry.py +0 -0
  251. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_image_reader.py +0 -0
  252. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_label_tree.py +0 -0
  253. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_landmarks.py +0 -0
  254. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_layer_transforms.py +0 -0
  255. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_layer_validation.py +0 -0
  256. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_layers_control.py +0 -0
  257. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_mesh_cache.py +0 -0
  258. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_object_control.py +0 -0
  259. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_packaging_metadata.py +0 -0
  260. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_persistence.py +0 -0
  261. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_probe_csv.py +0 -0
  262. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_probe_dialogs.py +0 -0
  263. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_probe_mapping.py +0 -0
  264. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_probe_reconstruction.py +0 -0
  265. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_project_io.py +0 -0
  266. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_project_persistence.py +0 -0
  267. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_provenance.py +0 -0
  268. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_roi_analysis.py +0 -0
  269. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_runtime_paths.py +0 -0
  270. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_slice_validation.py +0 -0
  271. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_triangulation.py +0 -0
  272. {driftlessmap-1.6.0 → driftlessmap-1.6.1}/tests/test_widgets.py +0 -0
@@ -4,8 +4,8 @@ message: >-
4
4
  HERBS paper listed under references.
5
5
  title: DriftlessMap
6
6
  type: software
7
- version: 1.6.0
8
- date-released: 2026-09-27
7
+ version: 1.6.1
8
+ date-released: 2026-09-28
9
9
  authors:
10
10
  - name: Mohebi & Associates
11
11
  website: https://www.mohebi-associates.org/
@@ -1,6 +1,6 @@
1
1
  # DriftlessMap User Manual
2
2
 
3
- This manual applies to **DriftlessMap 1.6.0**.
3
+ This manual applies to **DriftlessMap 1.6.1**.
4
4
 
5
5
  DriftlessMap - Histological E-data Registration in Brain Space - is a desktop
6
6
  application for aligning rodent histology with a reference atlas, reconstructing
@@ -107,7 +107,7 @@ purposes. See [Saving, loading, and exporting](#18-saving-loading-and-exporting)
107
107
 
108
108
  ### 2.1 Requirements
109
109
 
110
- DriftlessMap 1.6.0 requires:
110
+ DriftlessMap 1.6.1 requires:
111
111
 
112
112
  - A 64-bit operating system and 64-bit Python 3.10 or newer.
113
113
  - Python 3.10-3.14 for the core application.
@@ -126,7 +126,7 @@ page. The macOS DMG (`-macOS-arm64.dmg`) is built for Apple Silicon; on an Intel
126
126
  Mac, use the developer installation below.
127
127
  The desktop builds include Python and all runtime dependencies. Windows users
128
128
  extract the entire ZIP and run `DriftlessMap.exe`; macOS users open the DMG and
129
- drag `DriftlessMap.app` to Applications. The 1.6.0 release is unsigned, so the
129
+ drag `DriftlessMap.app` to Applications. The 1.6.1 release is unsigned, so the
130
130
  first launch may require Windows SmartScreen confirmation or Control-clicking
131
131
  the macOS app and choosing **Open**.
132
132
 
@@ -174,7 +174,7 @@ python -m pip --version
174
174
  python -c "import driftlessmap; print(driftlessmap.__version__)"
175
175
  ```
176
176
 
177
- The final command should print `1.6.0`.
177
+ The final command should print `1.6.1`.
178
178
 
179
179
  ### 2.5 Launching DriftlessMap
180
180
 
@@ -756,10 +756,13 @@ section and fills the Triangulation tool with paired landmarks:
756
756
  2. Choose **Atlas > Propose Landmarks**. If landmarks already exist, you are
757
757
  asked before they are replaced.
758
758
  3. DriftlessMap fits the outlines, then refines the fit on the image
759
- intensities where that improves the match. It places about 36 landmarks
760
- evenly inside the atlas section, and moves the boundary anchors with the
761
- same fit.
762
- 4. The status bar reports the number of pairs and the outline overlap. The
759
+ intensities where that improves the match. It suggests up to 10 landmarks:
760
+ up to 5 at the sharpest tips and notches of the outline, snapped onto the
761
+ section outline, and the rest on strong internal edges that both images
762
+ show, such as the corpus callosum. It moves the boundary anchors with the
763
+ same fit. The landmarks are suggestions to confirm and edit.
764
+ 4. The status bar reports how many pairs are on the outline and how many are
765
+ on internal edges, and the outline overlap. The
763
766
  Triangulation tool opens so you can check the mesh. Drag any landmark that
764
767
  is off, add more where the anatomy needs them, and check mesh quality
765
768
  (Section 8.5) before transferring.
@@ -768,6 +771,10 @@ The fit follows the outline and the overall shape well, but internal
768
771
  boundaries can still be several voxels off. Treat the proposal as a first
769
772
  draft of the manual landmarks, not as a finished registration.
770
773
 
774
+ The tutorial [Automatic Section Matching and Landmark Proposal](Tutorial/Registration_Related/automatic_section_and_landmarks.md)
775
+ walks through both steps on a real section, with screenshots, and explains
776
+ how each stage works.
777
+
771
778
  ### 8.3 Place paired landmarks
772
779
 
773
780
  1. Activate Triangulation.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: driftlessmap
3
- Version: 1.6.0
3
+ Version: 1.6.1
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>
@@ -104,7 +104,7 @@ See [CITATION.cff](CITATION.cff) for machine-readable citation metadata.
104
104
 
105
105
  ## Installation
106
106
 
107
- DriftlessMap 1.6.0 is available as a desktop application for end users and as
107
+ DriftlessMap 1.6.1 is available as a desktop application for end users and as
108
108
  a Python package for developers.
109
109
 
110
110
  ### Mode 1: desktop application for end users
@@ -112,10 +112,10 @@ a Python package for developers.
112
112
  No Python or Conda installation is required. Download the asset for your
113
113
  computer from [GitHub Releases](https://github.com/mohebi-n-associates/DriftlessMap/releases):
114
114
 
115
- - **Windows 64-bit:** download `DriftlessMap-1.6.0-Windows-x64.zip`, extract
115
+ - **Windows 64-bit:** download `DriftlessMap-1.6.1-Windows-x64.zip`, extract
116
116
  the complete folder, and double-click `DriftlessMap.exe`. Do not move the
117
117
  executable out of its extracted folder.
118
- - **macOS (Apple Silicon):** download `DriftlessMap-1.6.0-macOS-arm64.dmg`,
118
+ - **macOS (Apple Silicon):** download `DriftlessMap-1.6.1-macOS-arm64.dmg`,
119
119
  open it, and drag `DriftlessMap.app` to Applications. The application bundle
120
120
  includes the DriftlessMap icon and all Python dependencies. Intel Macs are
121
121
  not covered by the desktop build; use the Conda/pip installation below.
@@ -139,7 +139,7 @@ malware:
139
139
  required only for the first launch.
140
140
 
141
141
  Only use **Open Anyway** for a copy downloaded from the
142
- [official DriftlessMap 1.6.0 release](https://github.com/mohebi-n-associates/DriftlessMap/releases/tag/v1.6.0).
142
+ [official DriftlessMap 1.6.1 release](https://github.com/mohebi-n-associates/DriftlessMap/releases/tag/v1.6.1).
143
143
  The official macOS DMG has this SHA-256 digest:
144
144
 
145
145
  ```text
@@ -46,7 +46,7 @@ See [CITATION.cff](CITATION.cff) for machine-readable citation metadata.
46
46
 
47
47
  ## Installation
48
48
 
49
- DriftlessMap 1.6.0 is available as a desktop application for end users and as
49
+ DriftlessMap 1.6.1 is available as a desktop application for end users and as
50
50
  a Python package for developers.
51
51
 
52
52
  ### Mode 1: desktop application for end users
@@ -54,10 +54,10 @@ a Python package for developers.
54
54
  No Python or Conda installation is required. Download the asset for your
55
55
  computer from [GitHub Releases](https://github.com/mohebi-n-associates/DriftlessMap/releases):
56
56
 
57
- - **Windows 64-bit:** download `DriftlessMap-1.6.0-Windows-x64.zip`, extract
57
+ - **Windows 64-bit:** download `DriftlessMap-1.6.1-Windows-x64.zip`, extract
58
58
  the complete folder, and double-click `DriftlessMap.exe`. Do not move the
59
59
  executable out of its extracted folder.
60
- - **macOS (Apple Silicon):** download `DriftlessMap-1.6.0-macOS-arm64.dmg`,
60
+ - **macOS (Apple Silicon):** download `DriftlessMap-1.6.1-macOS-arm64.dmg`,
61
61
  open it, and drag `DriftlessMap.app` to Applications. The application bundle
62
62
  includes the DriftlessMap icon and all Python dependencies. Intel Macs are
63
63
  not covered by the desktop build; use the Conda/pip installation below.
@@ -81,7 +81,7 @@ malware:
81
81
  required only for the first launch.
82
82
 
83
83
  Only use **Open Anyway** for a copy downloaded from the
84
- [official DriftlessMap 1.6.0 release](https://github.com/mohebi-n-associates/DriftlessMap/releases/tag/v1.6.0).
84
+ [official DriftlessMap 1.6.1 release](https://github.com/mohebi-n-associates/DriftlessMap/releases/tag/v1.6.1).
85
85
  The official macOS DMG has this SHA-256 digest:
86
86
 
87
87
  ```text
@@ -1,5 +1,10 @@
1
1
  # Update Log
2
2
 
3
+ ### 28th September 2026
4
+ ##### DriftlessMap 1.6.1
5
+
6
+ Fixes **Show Boundary**, which drew atlas region boundaries almost black and therefore invisibly. **Propose Landmarks** now suggests up to 10 landmarks on features both images show (outline tips and notches, shared internal edges) instead of about 36. Adds a tutorial for the automatic section and landmark suggestions.
7
+
3
8
  ### 27th September 2026
4
9
  ##### DriftlessMap 1.6.0
5
10
 
@@ -3,6 +3,36 @@
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.6.1
7
+
8
+ Release date: 28 September 2026
9
+
10
+ ### Fixed
11
+
12
+ - **Show Boundary** now draws atlas region boundaries in white. Since the
13
+ boundary images were stored as one byte per pixel, they had been drawn
14
+ almost black, so the button appeared to do nothing.
15
+
16
+ ### Changed
17
+
18
+ - **Propose Landmarks** now suggests up to 10 landmarks instead of about 36,
19
+ each on a feature you can see and check in both images:
20
+ - up to 5 at the sharpest tips and notches of the brain outline, snapped
21
+ onto the section outline;
22
+ - the rest on strong internal edges that both images show, such as the
23
+ corpus callosum and the ventricles.
24
+
25
+ An edge seen in only one image is never chosen: an atlas region border in
26
+ uniform-looking tissue, or a dye track in the section. The status bar says
27
+ how many points are on the outline and how many are internal. Confirm or
28
+ drag each point, and add more by hand where the anatomy needs them.
29
+
30
+ ### Documentation
31
+
32
+ - A new tutorial, [Automatic Section Matching and Landmark Proposal](Tutorial/Registration_Related/automatic_section_and_landmarks.md),
33
+ walks through **Suggest Atlas Section...** and **Propose Landmarks** on a
34
+ real sagittal section and explains how each stage works.
35
+
6
36
  ## DriftlessMap 1.6.0
7
37
 
8
38
  Release date: 27 September 2026
@@ -1944,10 +1944,13 @@ class DriftlessMap(QMainWindow, FORM_Main):
1944
1944
  else:
1945
1945
  self._build_triangulation_registration(strict=False, show_error=False)
1946
1946
  stage = "outline and intensity" if proposal.deformable_used else "outline"
1947
+ on_outline = sum(kind == "outline" for kind in proposal.kinds)
1947
1948
  self.print_message(
1948
- "Proposed {} landmark pairs from the {} fit (outline overlap {:.2f}). "
1949
- "Review them and drag any that are off before transferring.".format(
1950
- len(proposal.atlas_points), stage, proposal.overlap_final
1949
+ "Suggested {} landmark pairs ({} on the outline, {} on internal edges) "
1950
+ "from the {} fit (outline overlap {:.2f}). Confirm or drag each one "
1951
+ "before transferring.".format(
1952
+ len(proposal.atlas_points), on_outline,
1953
+ len(proposal.atlas_points) - on_outline, stage, proposal.overlap_final
1951
1954
  ),
1952
1955
  self.normal_color,
1953
1956
  )
@@ -1,9 +1,10 @@
1
1
  """Automatic landmark proposal between an atlas slice and a histology section.
2
2
 
3
3
  The atlas slice is the fixed image, so the fitted SimpleITK transform maps an
4
- atlas point directly to the matching section point; sampling it at a grid of
5
- atlas points yields landmark pairs for DriftlessMap's piecewise-affine
6
- registration, which the user then reviews and edits.
4
+ atlas point directly to the matching section point; sampling it at the tips
5
+ and notches of the outline and at edges that both images show yields landmark
6
+ pairs for DriftlessMap's piecewise-affine registration. They are suggestions
7
+ that the user confirms and edits.
7
8
 
8
9
  Both images are given physical coordinates in DriftlessMap's convention
9
10
  (pixel ``k`` spans ``[k, k + 1)``; its centre is ``k + 0.5``), so points need
@@ -35,6 +36,7 @@ class LandmarkProposal:
35
36
  deformable_used: bool
36
37
  boundary_points: np.ndarray # extra atlas points mapped into the section,
37
38
  # clamped to the section image
39
+ kinds: tuple = () # "outline" or "internal" for each landmark
38
40
 
39
41
 
40
42
  def _as_image(array, scale):
@@ -131,29 +133,149 @@ def _bspline_on_intensities(fixed, moving, fixed_mask, affine, mesh_size):
131
133
  return composite, after < before
132
134
 
133
135
 
134
- def _grid_points(mask, count):
135
- """Roughly ``count`` points on a regular grid inside the eroded mask."""
136
- height, width = mask.shape
137
- step = max(2, int(np.sqrt(mask.sum() / max(count, 1))))
138
- inner = cv2.erode(mask.astype(np.uint8), np.ones((3, 3), np.uint8), iterations=max(1, step // 3)) > 0
139
- points = [
140
- (x + 0.5, y + 0.5)
141
- for y in range(step // 2, height, step)
142
- for x in range(step // 2, width, step)
143
- if inner[y, x]
144
- ]
136
+ OUTLINE_LANDMARKS = 5 # tips and notches of the outline
137
+ EDGE_MARGIN = 8 # atlas pixels; the slice may cut the brain at the image edge
138
+ MIN_AGREEMENT = 0.2 # internal edges must show clearly in both images
139
+
140
+
141
+ def _spacing(mask, count):
142
+ """Typical distance between ``count`` points spread over ``mask``."""
143
+ return max(2.0, float(np.sqrt(mask.sum() / max(count, 1))))
144
+
145
+
146
+ def _outer_contour(mask):
147
+ contours, _ = cv2.findContours(mask.astype(np.uint8), cv2.RETR_EXTERNAL,
148
+ cv2.CHAIN_APPROX_NONE)
149
+ if not contours:
150
+ return np.zeros((0, 2))
151
+ return max(contours, key=cv2.contourArea)[:, 0, :].astype(float)
152
+
153
+
154
+ def _outline_points(atlas_mask, atlas_image, count):
155
+ """The sharpest tips and notches of the atlas outline, most pronounced first.
156
+
157
+ Points where the slice cuts the brain at the image edge are skipped, as
158
+ are points where the labels extend past the visible template (the
159
+ template can be dark at the brain surface), because neither is an
160
+ outline that can be seen in the section.
161
+ """
162
+ contour = _outer_contour(atlas_mask)
163
+ n = len(contour)
164
+ if n < 20 or count <= 0:
165
+ return np.zeros((0, 2))
166
+ def turning(step):
167
+ before = contour - np.roll(contour, step, axis=0)
168
+ after = np.roll(contour, -step, axis=0) - contour
169
+ return np.arctan2(before[:, 0] * after[:, 1] - before[:, 1] * after[:, 0],
170
+ (before * after).sum(axis=1))
171
+
172
+ # A wide window finds the pronounced tips and notches; a narrow one then
173
+ # places each at its sharpest point.
174
+ k = max(4, n // 40)
175
+ turn = turning(k)
176
+ turn = np.convolve(np.r_[turn[-3:], turn, turn[:3]], np.ones(7) / 7, "valid")
177
+ fine = turning(max(2, k // 3))
178
+
179
+ def sharpest(index):
180
+ window = (index + np.arange(-k, k + 1)) % n
181
+ same_sign = window[np.sign(fine[window]) == np.sign(turn[index])]
182
+ if not len(same_sign):
183
+ return index
184
+ return int(same_sign[np.argmax(np.abs(fine[same_sign]))])
185
+ height, width = atlas_mask.shape
186
+ visible = np.percentile(atlas_image[atlas_mask], 5)
187
+ chosen = []
188
+ for index in np.argsort(-np.abs(turn)):
189
+ if len(chosen) == count:
190
+ break
191
+ index = sharpest(int(index))
192
+ x, y = contour[index]
193
+ if not (EDGE_MARGIN <= x < width - EDGE_MARGIN and EDGE_MARGIN <= y < height - EDGE_MARGIN):
194
+ continue
195
+ if atlas_image[int(y) - 1:int(y) + 2, int(x) - 1:int(x) + 2].mean() < visible:
196
+ continue
197
+ if all(min(abs(index - j), n - abs(index - j)) > n / 10 for j in chosen):
198
+ chosen.append(index)
199
+ return contour[chosen] + 0.5
200
+
201
+
202
+ def _edge_magnitude(image):
203
+ blurred = cv2.GaussianBlur(np.asarray(image, dtype=np.float32), (0, 0), 1.0)
204
+ return np.hypot(cv2.Sobel(blurred, cv2.CV_32F, 1, 0), cv2.Sobel(blurred, cv2.CV_32F, 0, 1))
205
+
206
+
207
+ def _local_correlation(a, b, radius=5):
208
+ size = (2 * radius + 1, 2 * radius + 1)
209
+ mean_a, mean_b = cv2.boxFilter(a, -1, size), cv2.boxFilter(b, -1, size)
210
+ covariance = cv2.boxFilter(a * b, -1, size) - mean_a * mean_b
211
+ var_a = cv2.boxFilter(a * a, -1, size) - mean_a ** 2
212
+ var_b = cv2.boxFilter(b * b, -1, size) - mean_b ** 2
213
+ return covariance / np.sqrt(np.maximum(var_a * var_b, 1e-12))
214
+
215
+
216
+ def _shared_edge_score(atlas_image, section_in_atlas, inner):
217
+ """Strong atlas edges that the registered section shows too, in [0, 1].
218
+
219
+ An edge seen in only one image (an atlas region border in uniform
220
+ tissue, or a dye track in the section) scores near zero.
221
+ """
222
+ atlas_edges = _edge_magnitude(atlas_image)
223
+ section_edges = _edge_magnitude(section_in_atlas)
224
+ agreement = np.clip(_local_correlation(atlas_edges, section_edges), 0.0, 1.0)
225
+ top = np.percentile(atlas_edges[inner], 99) if inner.any() else 0.0
226
+ strength = np.clip(atlas_edges / top, 0.0, 1.0) if top > 0 else np.zeros_like(atlas_edges)
227
+ return np.where(inner, agreement * strength, 0.0)
228
+
229
+
230
+ def _peaks(score, count, radius, taken=()):
231
+ """Up to ``count`` score maxima above MIN_AGREEMENT, ``radius`` apart."""
232
+ score = score.copy()
233
+ for x, y in taken:
234
+ cv2.circle(score, (int(x), int(y)), int(radius), 0, -1)
235
+ points = []
236
+ while len(points) < count:
237
+ row, col = np.unravel_index(np.argmax(score), score.shape)
238
+ if score[row, col] < MIN_AGREEMENT:
239
+ break
240
+ points.append((col + 0.5, row + 0.5))
241
+ cv2.circle(score, (int(col), int(row)), int(radius), 0, -1)
145
242
  return np.asarray(points, dtype=float).reshape(-1, 2)
146
243
 
147
244
 
148
- def propose_landmarks(atlas_intensity, atlas_labels, section, count=36,
245
+ def _section_in_atlas(section_gray, atlas_shape, transform):
246
+ """The section resampled onto the atlas slice's pixels."""
247
+ reference = sitk.GetImageFromArray(np.zeros(atlas_shape, np.float32))
248
+ reference.SetOrigin((0.5, 0.5))
249
+ moving = sitk.GetImageFromArray(np.ascontiguousarray(section_gray, dtype=np.float32))
250
+ moving.SetOrigin((0.5, 0.5))
251
+ return sitk.GetArrayFromImage(sitk.Resample(moving, reference, transform, sitk.sitkLinear, 0.0))
252
+
253
+
254
+ def _snap_to_outline(points, contour, limit):
255
+ """Move each point to the nearest point of ``contour`` if within ``limit``."""
256
+ if not len(contour):
257
+ return points
258
+ snapped = points.copy()
259
+ for i, point in enumerate(points):
260
+ distances = np.linalg.norm(contour - point, axis=1)
261
+ nearest = int(np.argmin(distances))
262
+ if distances[nearest] <= limit:
263
+ snapped[i] = contour[nearest] + 0.5
264
+ return snapped
265
+
266
+
267
+ def propose_landmarks(atlas_intensity, atlas_labels, section, count=10,
149
268
  deformable=True, mesh_size=6, boundary_points=None):
150
269
  """Propose paired landmarks mapping an atlas slice onto a section.
151
270
 
152
271
  ``section`` must already be in the atlas orientation (see
153
- ``atlas_matching``). ``boundary_points`` are further atlas points (such
154
- as the mesh's frame points) to carry through the same transform; they are
155
- clamped into the section image so they remain valid landmarks.
156
- Returns a :class:`LandmarkProposal`.
272
+ ``atlas_matching``). Up to ``count`` landmarks are proposed: the sharpest
273
+ tips and notches of the outline, snapped onto the section outline, then
274
+ internal points on strong edges that both images show. They are
275
+ suggestions for the user to confirm and edit. ``boundary_points`` are
276
+ further atlas points (such as the mesh's frame points) to carry through
277
+ the same transform; they are clamped into the section image so they
278
+ remain valid landmarks. Returns a :class:`LandmarkProposal`.
157
279
  """
158
280
  atlas_mask = np.asarray(atlas_labels) > 0
159
281
  if atlas_mask.sum() < 100:
@@ -189,27 +311,38 @@ def propose_landmarks(atlas_intensity, atlas_labels, section, count=36,
189
311
  except RuntimeError:
190
312
  pass # optimiser failure: fall back to the affine fit
191
313
 
192
- atlas_points = _grid_points(atlas_mask, count)
193
- mapped = np.array([transform.TransformPoint((float(x), float(y)))
194
- for x, y in atlas_points]).reshape(-1, 2)
314
+ def to_section(points):
315
+ return np.array([transform.TransformPoint((float(x), float(y)))
316
+ for x, y in points]).reshape(-1, 2)
317
+
195
318
  height, width = section_mask.shape
196
- inside = (
197
- (mapped[:, 0] >= 0) & (mapped[:, 0] < width)
198
- & (mapped[:, 1] >= 0) & (mapped[:, 1] < height)
199
- )
200
- if inside.any():
201
- rows = np.clip(mapped[inside, 1].astype(int), 0, height - 1)
202
- cols = np.clip(mapped[inside, 0].astype(int), 0, width - 1)
203
- on_tissue = section_mask[rows, cols]
204
- keep = np.flatnonzero(inside)[on_tissue]
205
- else:
206
- keep = np.array([], dtype=int)
319
+ spacing = _spacing(atlas_mask, count)
320
+
321
+ outline_atlas = _outline_points(atlas_mask, atlas_image, min(OUTLINE_LANDMARKS, count))
322
+ outline_section = _snap_to_outline(to_section(outline_atlas), _outer_contour(section_mask),
323
+ limit=0.05 * np.hypot(height, width))
324
+
325
+ inner = cv2.erode(atlas_mask.astype(np.uint8), np.ones((3, 3), np.uint8), iterations=4) > 0
326
+ score = _shared_edge_score(atlas_image, _section_in_atlas(section_gray / 255.0,
327
+ atlas_mask.shape, transform), inner)
328
+ radius = 0.45 * spacing # keeps internal points about a tenth of the section apart
329
+ internal_atlas = _peaks(score, count - len(outline_atlas), radius, taken=outline_atlas)
330
+ internal_section = to_section(internal_atlas)
331
+ if len(internal_section):
332
+ rows = np.clip(internal_section[:, 1].astype(int), 0, height - 1)
333
+ cols = np.clip(internal_section[:, 0].astype(int), 0, width - 1)
334
+ inside = ((internal_section[:, 0] >= 0) & (internal_section[:, 0] < width)
335
+ & (internal_section[:, 1] >= 0) & (internal_section[:, 1] < height))
336
+ keep = inside & section_mask[rows, cols]
337
+ internal_atlas, internal_section = internal_atlas[keep], internal_section[keep]
338
+
207
339
  boundary = np.asarray(boundary_points if boundary_points is not None else [],
208
340
  dtype=float).reshape(-1, 2)
209
- carried = np.array([transform.TransformPoint((float(x), float(y)))
210
- for x, y in boundary]).reshape(-1, 2)
341
+ carried = to_section(boundary)
211
342
  if len(carried):
212
343
  carried[:, 0] = np.clip(carried[:, 0], 0, width - 1)
213
344
  carried[:, 1] = np.clip(carried[:, 1], 0, height - 1)
214
- return LandmarkProposal(atlas_points[keep], mapped[keep], overlap_affine,
215
- overlap_final, used, carried)
345
+ kinds = ("outline",) * len(outline_atlas) + ("internal",) * len(internal_atlas)
346
+ return LandmarkProposal(np.vstack([outline_atlas, internal_atlas]),
347
+ np.vstack([outline_section, internal_section]),
348
+ overlap_affine, overlap_final, used, carried, kinds)
@@ -202,7 +202,9 @@ class SliceStacks(pg.GraphicsLayoutWidget):
202
202
  self.set_boundary_data(contour)
203
203
 
204
204
  def set_boundary_data(self, contour):
205
- self.boundary.setImage(contour, autoLevels=False)
205
+ # Contours are 0/1; without explicit levels a uint8 image is drawn
206
+ # on a 0-255 scale and the boundary is invisible.
207
+ self.boundary.setImage(contour, autoLevels=False, levels=(0, 1))
206
208
 
207
209
  def add_display_obj_to_view(self, obj_data, obj_color, obj_type):
208
210
  if obj_type == 'probe':
@@ -1,6 +1,6 @@
1
1
  """Canonical DriftlessMap version."""
2
2
 
3
- __version__ = "1.6.0"
3
+ __version__ = "1.6.1"
4
4
  APPLICATION_DISPLAY_NAME = "DriftlessMap {}".format(__version__)
5
5
  APPLICATION_WINDOW_TITLE = (
6
6
  "{} - Interactive Histology Registration and Brain-Atlas Mapping".format(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: driftlessmap
3
- Version: 1.6.0
3
+ Version: 1.6.1
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>
@@ -104,7 +104,7 @@ See [CITATION.cff](CITATION.cff) for machine-readable citation metadata.
104
104
 
105
105
  ## Installation
106
106
 
107
- DriftlessMap 1.6.0 is available as a desktop application for end users and as
107
+ DriftlessMap 1.6.1 is available as a desktop application for end users and as
108
108
  a Python package for developers.
109
109
 
110
110
  ### Mode 1: desktop application for end users
@@ -112,10 +112,10 @@ a Python package for developers.
112
112
  No Python or Conda installation is required. Download the asset for your
113
113
  computer from [GitHub Releases](https://github.com/mohebi-n-associates/DriftlessMap/releases):
114
114
 
115
- - **Windows 64-bit:** download `DriftlessMap-1.6.0-Windows-x64.zip`, extract
115
+ - **Windows 64-bit:** download `DriftlessMap-1.6.1-Windows-x64.zip`, extract
116
116
  the complete folder, and double-click `DriftlessMap.exe`. Do not move the
117
117
  executable out of its extracted folder.
118
- - **macOS (Apple Silicon):** download `DriftlessMap-1.6.0-macOS-arm64.dmg`,
118
+ - **macOS (Apple Silicon):** download `DriftlessMap-1.6.1-macOS-arm64.dmg`,
119
119
  open it, and drag `DriftlessMap.app` to Applications. The application bundle
120
120
  includes the DriftlessMap icon and all Python dependencies. Intel Macs are
121
121
  not covered by the desktop build; use the Conda/pip installation below.
@@ -139,7 +139,7 @@ malware:
139
139
  required only for the first launch.
140
140
 
141
141
  Only use **Open Anyway** for a copy downloaded from the
142
- [official DriftlessMap 1.6.0 release](https://github.com/mohebi-n-associates/DriftlessMap/releases/tag/v1.6.0).
142
+ [official DriftlessMap 1.6.1 release](https://github.com/mohebi-n-associates/DriftlessMap/releases/tag/v1.6.1).
143
143
  The official macOS DMG has this SHA-256 digest:
144
144
 
145
145
  ```text
@@ -198,6 +198,15 @@ class AtlasViewPerformanceTests(unittest.TestCase):
198
198
  view.cimg.boundary.image.shape, view.cimg.label_data.shape
199
199
  )
200
200
 
201
+ def test_boundaries_are_drawn_at_full_brightness(self):
202
+ stack = SliceStacks()
203
+ contour = np.zeros((20, 20), dtype=np.uint8)
204
+ contour[5:15, 10] = 1
205
+ stack.set_boundary_data(contour)
206
+ stack.boundary.render()
207
+ self.assertEqual(stack.boundary.qimage.pixelColor(10, 10).getRgb()[:3],
208
+ (255, 255, 255))
209
+
201
210
  def test_image_edge_hover_coordinates_are_rejected(self):
202
211
  image = np.zeros((3, 4), dtype=np.uint8)
203
212
 
@@ -5,7 +5,11 @@ import unittest
5
5
  import cv2
6
6
  import numpy as np
7
7
 
8
- from driftlessmap.auto_registration import propose_landmarks
8
+ from driftlessmap.atlas_matching import tissue_mask
9
+ from driftlessmap.auto_registration import (
10
+ EDGE_MARGIN, MIN_AGREEMENT, _outer_contour, _outline_points, _shared_edge_score,
11
+ propose_landmarks,
12
+ )
9
13
 
10
14
 
11
15
  def synthetic_slice(size=(160, 240)):
@@ -54,14 +58,76 @@ class ProposeLandmarkTests(unittest.TestCase):
54
58
  def test_landmarks_follow_a_known_deformation(self):
55
59
  intensity, labels = synthetic_slice()
56
60
  section = warped_section(intensity, labels)
57
- proposal = propose_landmarks(intensity, labels, section, count=30)
58
- self.assertGreaterEqual(len(proposal.atlas_points), 15)
59
- truth = known_warp(proposal.atlas_points)
60
- errors = np.linalg.norm(proposal.histology_points - truth, axis=1)
61
- # section pixels are 2.5x atlas pixels: stay within ~1 atlas pixel
62
- self.assertLess(np.median(errors), 3.0, errors)
61
+ # Measure the fit on a fixed interior grid, independent of which
62
+ # landmarks are selected, by carrying the grid through the transform.
63
+ ys, xs = np.mgrid[10:160:12, 10:240:12]
64
+ grid = np.column_stack([xs.ravel(), ys.ravel()]).astype(float) + 0.5
65
+ inner = cv2.erode((labels > 0).astype(np.uint8), np.ones((9, 9), np.uint8)) > 0
66
+ grid = grid[inner[grid[:, 1].astype(int), grid[:, 0].astype(int)]]
67
+ proposal = propose_landmarks(intensity, labels, section, boundary_points=grid)
68
+ self.assertTrue(3 <= len(proposal.atlas_points) <= 10)
69
+ self.assertEqual(len(proposal.kinds), len(proposal.atlas_points))
70
+ errors = np.linalg.norm(proposal.boundary_points - known_warp(grid), axis=1)
71
+ # section pixels are 2.5x atlas pixels: within ~1.6 atlas pixels,
72
+ # including grid points close to the outline
73
+ self.assertLess(np.median(errors), 4.0, errors)
74
+ internal = np.array([kind == "internal" for kind in proposal.kinds])
75
+ if internal.any():
76
+ truth = known_warp(proposal.atlas_points[internal])
77
+ landmark_errors = np.linalg.norm(proposal.histology_points[internal] - truth, axis=1)
78
+ self.assertLess(np.median(landmark_errors), 6.0, landmark_errors)
63
79
  self.assertGreater(proposal.overlap_final, 0.9)
64
80
 
81
+ def test_outline_points_lie_on_both_outlines(self):
82
+ intensity, labels = synthetic_slice()
83
+ section = warped_section(intensity, labels)
84
+ proposal = propose_landmarks(intensity, labels, section, deformable=False)
85
+ outline = np.array([kind == "outline" for kind in proposal.kinds])
86
+ self.assertGreaterEqual(outline.sum(), 2)
87
+ atlas_contour = _outer_contour(labels > 0) + 0.5
88
+ section_contour = _outer_contour(tissue_mask(section)) + 0.5
89
+ for a, h in zip(proposal.atlas_points[outline], proposal.histology_points[outline]):
90
+ self.assertLess(np.linalg.norm(atlas_contour - a, axis=1).min(), 1.0)
91
+ self.assertLess(np.linalg.norm(section_contour - h, axis=1).min(), 1.0)
92
+ # a notch where the two lobes of the synthetic slice meet is found
93
+ notch = np.array([[35.8, 117.4], [20.0, 84.0]]) # ellipse intersections
94
+ self.assertLess(min(np.linalg.norm(proposal.atlas_points[outline] - q, axis=1).min()
95
+ for q in notch), 12.0)
96
+
97
+ def test_internal_points_sit_on_edges_both_images_show(self):
98
+ intensity, labels = synthetic_slice()
99
+ section = warped_section(intensity, labels)
100
+ proposal = propose_landmarks(intensity, labels, section, deformable=False)
101
+ internal = proposal.atlas_points[[kind == "internal" for kind in proposal.kinds]]
102
+ self.assertGreaterEqual(len(internal), 1)
103
+ edges = cv2.Canny((intensity * 255).astype(np.uint8), 20, 60) > 0
104
+ ys, xs = np.nonzero(edges)
105
+ edge_points = np.column_stack([xs, ys]) + 0.5
106
+ for point in internal:
107
+ self.assertLess(np.linalg.norm(edge_points - point, axis=1).min(), 4.0)
108
+
109
+ def test_an_edge_in_only_one_image_scores_nothing(self):
110
+ atlas = np.full((80, 80), 0.4, np.float32)
111
+ atlas[:, 40:] = 0.9 # an edge in both images
112
+ section = atlas.copy()
113
+ section[20:60, 15] = 1.0 # a "dye track" only in the section
114
+ atlas_only = atlas.copy()
115
+ atlas_only[40:, 60] = 0.1 # a border only in the atlas
116
+ inner = np.ones_like(atlas, bool)
117
+ score = _shared_edge_score(atlas_only, section, inner)
118
+ self.assertGreater(score[40, 39:41].max(), MIN_AGREEMENT)
119
+ self.assertLess(score[40, 12:18].max(), MIN_AGREEMENT)
120
+ self.assertLess(score[60, 58:62].max(), MIN_AGREEMENT)
121
+
122
+ def test_outline_cut_by_the_image_edge_is_skipped(self):
123
+ intensity, labels = synthetic_slice()
124
+ cut = labels.copy()
125
+ cut[:, :40] = 0
126
+ cut[40:130, :40] = 1 # brain runs off the left edge
127
+ image = cv2.normalize(intensity + 0.4 * (cut > 0), None, 0, 1, cv2.NORM_MINMAX)
128
+ points = _outline_points(cut > 0, image, 5)
129
+ self.assertTrue(np.all(points[:, 0] >= EDGE_MARGIN), points)
130
+
65
131
  def test_affine_only_mode_and_empty_atlas(self):
66
132
  intensity, labels = synthetic_slice()
67
133
  section = warped_section(intensity, labels)
@@ -22,7 +22,7 @@ class VersionTests(unittest.TestCase):
22
22
  cls.app = QApplication.instance() or QApplication([])
23
23
 
24
24
  def test_public_package_version_uses_the_canonical_value(self):
25
- self.assertEqual(__version__, "1.6.0")
25
+ self.assertEqual(__version__, "1.6.1")
26
26
  self.assertEqual(driftlessmap.__version__, __version__)
27
27
 
28
28
  def test_main_window_display_strings_include_the_version(self):
File without changes