TwoPy 0.3.2__tar.gz → 0.3.3__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 (253) hide show
  1. {twopy-0.3.2 → twopy-0.3.3}/PKG-INFO +2 -1
  2. {twopy-0.3.2 → twopy-0.3.3}/pyproject.toml +3 -2
  3. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/group_matching.py +23 -10
  4. twopy-0.3.3/src/twopy/napari/assets/twopy-app-icon.png +0 -0
  5. twopy-0.3.3/src/twopy/napari/empty_state.py +159 -0
  6. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/group_matching/images.py +17 -11
  7. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/group_matching/response_preview.py +17 -9
  8. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/group_matching/roi_assignment.py +106 -51
  9. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/group_matching/roi_cards.py +122 -38
  10. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/group_matching/window.py +1 -1
  11. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/docks/options_panel.py +9 -4
  12. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/docks/response_plot_widget.py +31 -0
  13. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/panels.py +5 -0
  14. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/session.py +9 -0
  15. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/state.py +65 -0
  16. twopy-0.3.3/src/twopy/napari/version_check.py +280 -0
  17. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/viewer.py +26 -1
  18. {twopy-0.3.2 → twopy-0.3.3}/src/twopy.egg-info/PKG-INFO +2 -1
  19. {twopy-0.3.2 → twopy-0.3.3}/src/twopy.egg-info/SOURCES.txt +4 -0
  20. {twopy-0.3.2 → twopy-0.3.3}/src/twopy.egg-info/requires.txt +1 -0
  21. {twopy-0.3.2 → twopy-0.3.3}/tests/test_group_matching.py +65 -28
  22. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari.py +93 -2
  23. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_group_matching_roi_cards.py +94 -12
  24. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_loaded_recordings.py +14 -10
  25. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_plot_controls.py +4 -0
  26. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_plot_widgets.py +123 -12
  27. {twopy-0.3.2 → twopy-0.3.3}/tests/test_packaging.py +17 -0
  28. twopy-0.3.3/tests/test_version_check.py +207 -0
  29. {twopy-0.3.2 → twopy-0.3.3}/README.md +0 -0
  30. {twopy-0.3.2 → twopy-0.3.3}/setup.cfg +0 -0
  31. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/__init__.py +0 -0
  32. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/_segments.py +0 -0
  33. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/_version.py +0 -0
  34. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/__init__.py +0 -0
  35. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/background_subtraction.py +0 -0
  36. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/dff.py +0 -0
  37. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/dff_options.py +0 -0
  38. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/epoch_mapping.py +0 -0
  39. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/kernels/__init__.py +0 -0
  40. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/kernels/_fitting.py +0 -0
  41. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/kernels/_stimulus.py +0 -0
  42. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/motion.py +0 -0
  43. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/persistence.py +0 -0
  44. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/response_context.py +0 -0
  45. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/response_maps.py +0 -0
  46. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/response_plotting.py +0 -0
  47. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/response_processing/__init__.py +0 -0
  48. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/response_processing/apply.py +0 -0
  49. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/response_processing/options.py +0 -0
  50. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/response_processing/persistence.py +0 -0
  51. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/response_processing/qc.py +0 -0
  52. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/response_processing/signals.py +0 -0
  53. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/response_window_options.py +0 -0
  54. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/responses.py +0 -0
  55. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/timing.py +0 -0
  56. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/trials.py +0 -0
  57. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis/workflow.py +0 -0
  58. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/analysis_cache.py +0 -0
  59. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/api.py +0 -0
  60. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/cache_inventory.py +0 -0
  61. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/calibrations/pixel_calibration_profiles.csv +0 -0
  62. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/calibrations/pixel_size.csv +0 -0
  63. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/config.example.yml +0 -0
  64. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/config.py +0 -0
  65. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/conversion/__init__.py +0 -0
  66. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/conversion/alignment_crop.py +0 -0
  67. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/conversion/api.py +0 -0
  68. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/conversion/hdf5_writing.py +0 -0
  69. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/conversion/matlab_values.py +0 -0
  70. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/conversion/orientation.py +0 -0
  71. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/conversion/source_loading.py +0 -0
  72. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/conversion/stimulus_code.py +0 -0
  73. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/conversion/types.py +0 -0
  74. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/converted.py +0 -0
  75. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/custom/__init__.py +0 -0
  76. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/custom/context.py +0 -0
  77. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/custom/discovery.py +0 -0
  78. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/custom/metadata.py +0 -0
  79. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/custom/native.py +0 -0
  80. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/custom/native_workflows/__init__.py +0 -0
  81. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/custom/native_workflows/direction_selectivity.py +0 -0
  82. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/custom/native_workflows/response_kernels.py +0 -0
  83. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/custom/parameters.py +0 -0
  84. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/custom/provenance.py +0 -0
  85. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/custom/registry.py +0 -0
  86. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/custom/results.py +0 -0
  87. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/custom/runner.py +0 -0
  88. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/custom/workflows.py +0 -0
  89. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/database/__init__.py +0 -0
  90. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/database/cache.py +0 -0
  91. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/database/catalog.py +0 -0
  92. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/database/connection.py +0 -0
  93. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/database/generic.py +0 -0
  94. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/database/modeled.py +0 -0
  95. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/database/search.py +0 -0
  96. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/database/sql.py +0 -0
  97. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/database/types.py +0 -0
  98. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/filenames.py +0 -0
  99. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/frame_ranges.py +0 -0
  100. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/hdf5_utils.py +0 -0
  101. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/inspection.py +0 -0
  102. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/matlab.py +0 -0
  103. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/__init__.py +0 -0
  104. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/__main__.py +0 -0
  105. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/controls.py +0 -0
  106. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/custom_tab.py +0 -0
  107. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/database_favorite_editor.py +0 -0
  108. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/database_favorites.py +0 -0
  109. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/database_search.py +0 -0
  110. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/dims.py +0 -0
  111. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/display.py +0 -0
  112. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/display_paths.py +0 -0
  113. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/errors.py +0 -0
  114. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/group_matching/__init__.py +0 -0
  115. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/group_matching/cards.py +0 -0
  116. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/group_matching/fov_assignment.py +0 -0
  117. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/group_matching/fov_cards.py +0 -0
  118. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/group_matching/protocols.py +0 -0
  119. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/group_matching/responses.py +0 -0
  120. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/group_matching/style.py +0 -0
  121. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/group_matching/tables.py +0 -0
  122. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/interactive.py +0 -0
  123. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/latest_worker.py +0 -0
  124. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/launcher.py +0 -0
  125. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/live_analysis.py +0 -0
  126. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/load_controller.py +0 -0
  127. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/load_progress.py +0 -0
  128. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/load_workflow.py +0 -0
  129. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/loaded_recordings_csv.py +0 -0
  130. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/loading.py +0 -0
  131. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/movie.py +0 -0
  132. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/output_routing.py +0 -0
  133. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/paths.py +0 -0
  134. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/__init__.py +0 -0
  135. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/data.py +0 -0
  136. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/dff_options.py +0 -0
  137. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/docks/__init__.py +0 -0
  138. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/docks/api.py +0 -0
  139. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/docks/dynamic_options.py +0 -0
  140. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/docks/paths.py +0 -0
  141. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/docks/plot_area.py +0 -0
  142. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/docks/response_map_area.py +0 -0
  143. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/docks/save_actions.py +0 -0
  144. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/docks/visibility_state.py +0 -0
  145. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/export.py +0 -0
  146. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/export_controls.py +0 -0
  147. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/form_controls.py +0 -0
  148. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/label_visibility.py +0 -0
  149. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/normalization_options.py +0 -0
  150. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/options.py +0 -0
  151. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/preview_strip.py +0 -0
  152. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/processing_options.py +0 -0
  153. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/response_map_colors.py +0 -0
  154. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/response_map_display.py +0 -0
  155. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/response_map_options.py +0 -0
  156. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/response_window_options.py +0 -0
  157. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/roi_generation/__init__.py +0 -0
  158. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/roi_generation/actions.py +0 -0
  159. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/roi_generation/controls.py +0 -0
  160. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/roi_generation/options.py +0 -0
  161. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/plotting/widgets.py +0 -0
  162. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/protocols.py +0 -0
  163. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/responses.py +0 -0
  164. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/roi.py +0 -0
  165. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/roi_signatures.py +0 -0
  166. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/sidebar.py +0 -0
  167. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/text.py +0 -0
  168. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/trial_timeline.py +0 -0
  169. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/napari/types.py +0 -0
  170. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/parity/__init__.py +0 -0
  171. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/parity/dff.py +0 -0
  172. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/parity/psycho5.py +0 -0
  173. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/parity/roi.py +0 -0
  174. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/parity/roi_extraction.py +0 -0
  175. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/parity/saved_analysis.py +0 -0
  176. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/photodiode.py +0 -0
  177. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/photodiode_classification.py +0 -0
  178. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/pixel_calibration.py +0 -0
  179. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/pixel_calibration_profiles.py +0 -0
  180. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/response_roi_extraction.py +0 -0
  181. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/roi.py +0 -0
  182. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/roi_extraction.py +0 -0
  183. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/roi_mask_cleanup.py +0 -0
  184. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/session.py +0 -0
  185. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/spatial.py +0 -0
  186. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/spatial_orientation.py +0 -0
  187. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/stimulus.py +0 -0
  188. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/synchronization.py +0 -0
  189. {twopy-0.3.2 → twopy-0.3.3}/src/twopy/typing_guards.py +0 -0
  190. {twopy-0.3.2 → twopy-0.3.3}/src/twopy.egg-info/dependency_links.txt +0 -0
  191. {twopy-0.3.2 → twopy-0.3.3}/src/twopy.egg-info/entry_points.txt +0 -0
  192. {twopy-0.3.2 → twopy-0.3.3}/src/twopy.egg-info/top_level.txt +0 -0
  193. {twopy-0.3.2 → twopy-0.3.3}/tests/test_api.py +0 -0
  194. {twopy-0.3.2 → twopy-0.3.3}/tests/test_background_subtraction.py +0 -0
  195. {twopy-0.3.2 → twopy-0.3.3}/tests/test_cache_inventory.py +0 -0
  196. {twopy-0.3.2 → twopy-0.3.3}/tests/test_config.py +0 -0
  197. {twopy-0.3.2 → twopy-0.3.3}/tests/test_conversion.py +0 -0
  198. {twopy-0.3.2 → twopy-0.3.3}/tests/test_converted.py +0 -0
  199. {twopy-0.3.2 → twopy-0.3.3}/tests/test_custom_workflows.py +0 -0
  200. {twopy-0.3.2 → twopy-0.3.3}/tests/test_database.py +0 -0
  201. {twopy-0.3.2 → twopy-0.3.3}/tests/test_dff.py +0 -0
  202. {twopy-0.3.2 → twopy-0.3.3}/tests/test_epoch_mapping.py +0 -0
  203. {twopy-0.3.2 → twopy-0.3.3}/tests/test_filenames.py +0 -0
  204. {twopy-0.3.2 → twopy-0.3.3}/tests/test_frame_ranges.py +0 -0
  205. {twopy-0.3.2 → twopy-0.3.3}/tests/test_function_inventory.py +0 -0
  206. {twopy-0.3.2 → twopy-0.3.3}/tests/test_hdf5_utils.py +0 -0
  207. {twopy-0.3.2 → twopy-0.3.3}/tests/test_import.py +0 -0
  208. {twopy-0.3.2 → twopy-0.3.3}/tests/test_inspection.py +0 -0
  209. {twopy-0.3.2 → twopy-0.3.3}/tests/test_kernels.py +0 -0
  210. {twopy-0.3.2 → twopy-0.3.3}/tests/test_load_workflow.py +0 -0
  211. {twopy-0.3.2 → twopy-0.3.3}/tests/test_matlab.py +0 -0
  212. {twopy-0.3.2 → twopy-0.3.3}/tests/test_motion.py +0 -0
  213. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_database_search.py +0 -0
  214. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_display.py +0 -0
  215. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_export.py +0 -0
  216. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_group_matching_responses.py +0 -0
  217. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_live_controller.py +0 -0
  218. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_load_controller.py +0 -0
  219. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_load_progress.py +0 -0
  220. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_load_tab.py +0 -0
  221. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_path_resolution.py +0 -0
  222. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_plot_data.py +0 -0
  223. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_processing_controls.py +0 -0
  224. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_response_map_display.py +0 -0
  225. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_response_workflow.py +0 -0
  226. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_roi_generation.py +0 -0
  227. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_roi_visibility.py +0 -0
  228. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_text.py +0 -0
  229. {twopy-0.3.2 → twopy-0.3.3}/tests/test_napari_timeline.py +0 -0
  230. {twopy-0.3.2 → twopy-0.3.3}/tests/test_parity.py +0 -0
  231. {twopy-0.3.2 → twopy-0.3.3}/tests/test_parity_psycho5.py +0 -0
  232. {twopy-0.3.2 → twopy-0.3.3}/tests/test_parity_roi_extraction.py +0 -0
  233. {twopy-0.3.2 → twopy-0.3.3}/tests/test_persistence.py +0 -0
  234. {twopy-0.3.2 → twopy-0.3.3}/tests/test_photodiode_classification.py +0 -0
  235. {twopy-0.3.2 → twopy-0.3.3}/tests/test_pixel_calibration.py +0 -0
  236. {twopy-0.3.2 → twopy-0.3.3}/tests/test_pixel_calibration_profiles.py +0 -0
  237. {twopy-0.3.2 → twopy-0.3.3}/tests/test_real_fixture.py +0 -0
  238. {twopy-0.3.2 → twopy-0.3.3}/tests/test_recording_timing.py +0 -0
  239. {twopy-0.3.2 → twopy-0.3.3}/tests/test_response_map_area.py +0 -0
  240. {twopy-0.3.2 → twopy-0.3.3}/tests/test_response_maps.py +0 -0
  241. {twopy-0.3.2 → twopy-0.3.3}/tests/test_response_processing.py +0 -0
  242. {twopy-0.3.2 → twopy-0.3.3}/tests/test_response_roi_extraction.py +0 -0
  243. {twopy-0.3.2 → twopy-0.3.3}/tests/test_response_window_options.py +0 -0
  244. {twopy-0.3.2 → twopy-0.3.3}/tests/test_responses.py +0 -0
  245. {twopy-0.3.2 → twopy-0.3.3}/tests/test_roi.py +0 -0
  246. {twopy-0.3.2 → twopy-0.3.3}/tests/test_roi_extraction.py +0 -0
  247. {twopy-0.3.2 → twopy-0.3.3}/tests/test_roi_mask_cleanup.py +0 -0
  248. {twopy-0.3.2 → twopy-0.3.3}/tests/test_saved_analysis.py +0 -0
  249. {twopy-0.3.2 → twopy-0.3.3}/tests/test_session.py +0 -0
  250. {twopy-0.3.2 → twopy-0.3.3}/tests/test_stimulus.py +0 -0
  251. {twopy-0.3.2 → twopy-0.3.3}/tests/test_synchronization.py +0 -0
  252. {twopy-0.3.2 → twopy-0.3.3}/tests/test_trials.py +0 -0
  253. {twopy-0.3.2 → twopy-0.3.3}/tests/test_workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: twopy
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Two-photon imaging analysis tool with a napari interface.
5
5
  Author-email: Gustavo Madeira Santana <fromweb1@gumadeiras.com>
6
6
  License-Expression: MIT
@@ -12,6 +12,7 @@ Requires-Dist: h5py<4,>=3.16
12
12
  Requires-Dist: matplotlib<4,>=3.10
13
13
  Requires-Dist: napari[all]<0.8,>=0.7
14
14
  Requires-Dist: numpy<3,>=2.3
15
+ Requires-Dist: packaging<27,>=26
15
16
  Requires-Dist: pre-commit<5,>=4.6
16
17
  Requires-Dist: qtpy<3,>=2.4
17
18
  Requires-Dist: pyyaml<7,>=6.0.3
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "twopy"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "Two-photon imaging analysis tool with a napari interface."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13,<3.14"
@@ -17,6 +17,7 @@ dependencies = [
17
17
  "matplotlib>=3.10,<4",
18
18
  "napari[all]>=0.7,<0.8",
19
19
  "numpy>=2.3,<3",
20
+ "packaging>=26,<27",
20
21
  "pre-commit>=4.6,<5",
21
22
  "qtpy>=2.4,<3",
22
23
  "pyyaml>=6.0.3,<7",
@@ -37,7 +38,7 @@ Repository = "https://github.com/gumadeiras/twopy"
37
38
  where = ["src"]
38
39
 
39
40
  [tool.setuptools.package-data]
40
- twopy = ["calibrations/*.csv", "config.example.yml"]
41
+ twopy = ["calibrations/*.csv", "config.example.yml", "napari/assets/*.png"]
41
42
 
42
43
  [tool.ruff]
43
44
  line-length = 88
@@ -143,7 +143,7 @@ def make_manual_fov_group_rows(
143
143
 
144
144
 
145
145
  def make_manual_roi_match_rows(
146
- recording_rois: Mapping[Path, str],
146
+ recording_rois: Sequence[tuple[Path, str]],
147
147
  *,
148
148
  group_cell_id: int,
149
149
  fov_group_id: str = "",
@@ -153,7 +153,9 @@ def make_manual_roi_match_rows(
153
153
  """Create validated match-table rows for one user decision.
154
154
 
155
155
  Args:
156
- recording_rois: Mapping from recording path to ROI label.
156
+ recording_rois: Recording path and ROI label pairs selected for one
157
+ group. A recording can appear more than once when multiple ROIs
158
+ from that recording belong in the group.
157
159
  group_cell_id: Positive cell-group identifier for this decision.
158
160
  fov_group_id: Optional user-facing FOV group identifier.
159
161
  status: Decision status for every emitted row.
@@ -172,16 +174,27 @@ def make_manual_roi_match_rows(
172
174
  raise ValueError(msg)
173
175
 
174
176
  rows: list[ManualRoiMatchRow] = []
177
+ seen: set[tuple[Path, str]] = set()
175
178
  for recording_path, roi_label in sorted(
176
- recording_rois.items(),
177
- key=lambda item: str(item[0]),
179
+ recording_rois,
180
+ key=lambda item: (str(item[0]), item[1]),
178
181
  ):
182
+ validated_path = _validate_recording_path(recording_path)
183
+ validated_roi = _validate_roi_label(roi_label)
184
+ key = (validated_path, validated_roi)
185
+ if key in seen:
186
+ msg = (
187
+ "Manual ROI match rows cannot repeat the same recording and "
188
+ f"ROI label: {validated_path} {validated_roi}."
189
+ )
190
+ raise ValueError(msg)
191
+ seen.add(key)
179
192
  rows.append(
180
193
  ManualRoiMatchRow(
181
194
  fov_group_id=fov_group_id,
182
195
  group_cell_id=group_cell_id,
183
- recording_path=_validate_recording_path(recording_path),
184
- roi_label=_validate_roi_label(roi_label),
196
+ recording_path=validated_path,
197
+ roi_label=validated_roi,
185
198
  status=status,
186
199
  note=note,
187
200
  ),
@@ -225,7 +238,7 @@ def matched_manual_roi_groups(
225
238
 
226
239
  def add_manual_roi_match_group(
227
240
  rows: Sequence[ManualRoiMatchRow],
228
- recording_rois: Mapping[Path, str],
241
+ recording_rois: Sequence[tuple[Path, str]],
229
242
  *,
230
243
  fov_group_id: str,
231
244
  note: str = "",
@@ -234,7 +247,7 @@ def add_manual_roi_match_group(
234
247
 
235
248
  Args:
236
249
  rows: Existing manual ROI match rows.
237
- recording_rois: Mapping from recording path to selected ROI label.
250
+ recording_rois: Selected recording path and ROI label pairs.
238
251
  fov_group_id: FOV id for the new matched group.
239
252
  note: Optional note copied to every new row.
240
253
 
@@ -255,7 +268,7 @@ def add_manual_roi_match_group(
255
268
 
256
269
  def replace_manual_roi_match_group(
257
270
  rows: Sequence[ManualRoiMatchRow],
258
- recording_rois: Mapping[Path, str],
271
+ recording_rois: Sequence[tuple[Path, str]],
259
272
  *,
260
273
  fov_group_id: str,
261
274
  group_cell_id: int,
@@ -265,7 +278,7 @@ def replace_manual_roi_match_group(
265
278
 
266
279
  Args:
267
280
  rows: Existing manual ROI match rows.
268
- recording_rois: Replacement recording-to-ROI selections.
281
+ recording_rois: Replacement recording path and ROI label pairs.
269
282
  fov_group_id: FOV id containing the group.
270
283
  group_cell_id: Existing group id to replace.
271
284
  note: Optional note copied to every replacement row.
@@ -0,0 +1,159 @@
1
+ """Empty-viewer message helpers for the twopy napari adapter.
2
+
3
+ Inputs: napari viewer objects that may expose welcome and text overlays.
4
+ Outputs: a twopy-owned message while no recording is loaded.
5
+
6
+ The center viewer belongs to napari, but twopy owns the empty recording
7
+ workflow. These helpers hide napari's generic welcome overlay and use napari's
8
+ public text overlay for the small twopy-specific launch message.
9
+ """
10
+
11
+ from typing import Protocol, cast
12
+
13
+ from twopy._version import __version__
14
+
15
+ __all__ = [
16
+ "EMPTY_VIEWER_MESSAGE",
17
+ "hide_empty_viewer_message",
18
+ "refresh_empty_viewer_message",
19
+ "show_empty_viewer_message",
20
+ ]
21
+
22
+ _BASE_EMPTY_VIEWER_MESSAGE = (
23
+ f"twopy {__version__}\n\n"
24
+ "Getting Started\n\n"
25
+ "Search or load recordings manually\n"
26
+ "using the Load tab on the right."
27
+ )
28
+ EMPTY_VIEWER_MESSAGE = _BASE_EMPTY_VIEWER_MESSAGE
29
+
30
+
31
+ class _WelcomeScreen(Protocol):
32
+ """Small shape of napari's welcome overlay."""
33
+
34
+ visible: bool
35
+
36
+
37
+ class _ViewerWithWelcomeScreen(Protocol):
38
+ """Small viewer shape for napari welcome-screen updates."""
39
+
40
+ welcome_screen: object
41
+
42
+
43
+ class _ViewerWithTextOverlay(Protocol):
44
+ """Small viewer shape for napari text overlay updates."""
45
+
46
+ text_overlay: object
47
+
48
+
49
+ class _TextOverlay(Protocol):
50
+ """Small shape of napari's canvas text overlay."""
51
+
52
+ text: str
53
+ visible: bool
54
+ position: str
55
+ font_size: int
56
+ color: object
57
+ box: bool
58
+ box_color: object
59
+
60
+
61
+ def show_empty_viewer_message(
62
+ viewer: object, *, update_command: str | None = None
63
+ ) -> None:
64
+ """Show twopy's empty-recording message on a napari viewer.
65
+
66
+ Args:
67
+ viewer: Napari viewer or test double.
68
+ update_command: Optional pip command shown when a newer twopy release is
69
+ known.
70
+
71
+ Returns:
72
+ None.
73
+
74
+ This replaces napari's generic welcome screen only while twopy has no
75
+ loaded recording. The message tells the user what the empty canvas means in
76
+ the twopy workflow.
77
+ """
78
+ welcome_screen = _welcome_screen(viewer)
79
+ if welcome_screen is not None:
80
+ welcome_screen.visible = False
81
+
82
+ overlay = _text_overlay(viewer)
83
+ if overlay is None:
84
+ return
85
+ overlay.text = empty_viewer_message(update_command=update_command)
86
+ overlay.visible = True
87
+ overlay.position = "top_center"
88
+ overlay.font_size = 13
89
+ overlay.color = "white"
90
+ overlay.box = True
91
+ overlay.box_color = (0.0, 0.0, 0.0, 0.65)
92
+
93
+
94
+ def hide_empty_viewer_message(viewer: object) -> None:
95
+ """Hide twopy's empty-recording message when real content owns the viewer.
96
+
97
+ Args:
98
+ viewer: Napari viewer or test double.
99
+
100
+ Returns:
101
+ None.
102
+
103
+ The helper clears only the exact twopy empty message. Trial HUD text and any
104
+ other overlay owner are left untouched.
105
+ """
106
+ overlay = _text_overlay(viewer)
107
+ if overlay is None or not _is_empty_viewer_message(overlay.text):
108
+ return
109
+ overlay.text = ""
110
+ overlay.visible = False
111
+
112
+
113
+ def refresh_empty_viewer_message(
114
+ viewer: object,
115
+ *,
116
+ update_command: str | None,
117
+ ) -> None:
118
+ """Update twopy's empty-recording message when it already owns the overlay.
119
+
120
+ Args:
121
+ viewer: Napari viewer or test double.
122
+ update_command: Optional pip command shown when a newer twopy release is
123
+ known.
124
+
125
+ Returns:
126
+ None.
127
+ """
128
+ overlay = _text_overlay(viewer)
129
+ if overlay is None or not _is_empty_viewer_message(overlay.text):
130
+ return
131
+ overlay.text = empty_viewer_message(update_command=update_command)
132
+
133
+
134
+ def empty_viewer_message(*, update_command: str | None = None) -> str:
135
+ """Return the empty-viewer text for the current update state."""
136
+ if not update_command:
137
+ return _BASE_EMPTY_VIEWER_MESSAGE
138
+ return f"{_BASE_EMPTY_VIEWER_MESSAGE}\n\nUpdate\n{update_command}"
139
+
140
+
141
+ def _is_empty_viewer_message(text: str) -> bool:
142
+ """Return whether text is owned by twopy's empty-recording overlay."""
143
+ return text == EMPTY_VIEWER_MESSAGE or text.startswith(
144
+ f"{EMPTY_VIEWER_MESSAGE}\n\nUpdate\n"
145
+ )
146
+
147
+
148
+ def _welcome_screen(viewer: object) -> _WelcomeScreen | None:
149
+ """Return napari's welcome screen overlay when the viewer exposes one."""
150
+ if not hasattr(viewer, "welcome_screen"):
151
+ return None
152
+ return cast(_WelcomeScreen, cast(_ViewerWithWelcomeScreen, viewer).welcome_screen)
153
+
154
+
155
+ def _text_overlay(viewer: object) -> _TextOverlay | None:
156
+ """Return napari's text overlay when the viewer exposes one."""
157
+ if not hasattr(viewer, "text_overlay"):
158
+ return None
159
+ return cast(_TextOverlay, cast(_ViewerWithTextOverlay, viewer).text_overlay)
@@ -50,16 +50,16 @@ def mean_image_roi_overlay_pixmap(
50
50
  *,
51
51
  mean_image_layer: object,
52
52
  roi_labels_layer: object | None,
53
- roi_label: str | None,
53
+ roi_labels: tuple[str, ...],
54
54
  selected_color: QColor,
55
55
  contrast_percentile: float,
56
56
  ) -> QPixmap | None:
57
- """Return a mean-image preview with one selected ROI highlighted.
57
+ """Return a mean-image preview with selected ROIs highlighted.
58
58
 
59
59
  Args:
60
60
  mean_image_layer: Napari mean-image layer.
61
61
  roi_labels_layer: Napari Labels layer for the same displayed crop.
62
- roi_label: ``roi_####`` label to highlight, or ``None`` for no overlay.
62
+ roi_labels: ``roi_####`` labels to highlight.
63
63
  selected_color: Color used for the selected ROI mask and matching trace.
64
64
  contrast_percentile: Upper percentile used as the white point.
65
65
 
@@ -72,7 +72,11 @@ def mean_image_roi_overlay_pixmap(
72
72
  if image is None:
73
73
  return None
74
74
  rgba = _grayscale_rgba(image, contrast_percentile=contrast_percentile)
75
- label_value = _roi_label_value(roi_label)
75
+ label_values = tuple(
76
+ label_value
77
+ for label_value in (_roi_label_value(roi_label) for roi_label in roi_labels)
78
+ if label_value is not None
79
+ )
76
80
  labels = _layer_image(roi_labels_layer)
77
81
  roi_centers: tuple[tuple[int, float, float], ...] = ()
78
82
  if labels is not None and labels.shape == image.shape:
@@ -81,7 +85,7 @@ def mean_image_roi_overlay_pixmap(
81
85
  _blend_roi_masks(
82
86
  rgba,
83
87
  integer_labels,
84
- selected_label=label_value,
88
+ selected_labels=label_values,
85
89
  selected_color=selected_color,
86
90
  )
87
91
  pixmap = _rgba_pixmap(rgba, size=ROI_PREVIEW_SIZE)
@@ -89,7 +93,7 @@ def mean_image_roi_overlay_pixmap(
89
93
  pixmap,
90
94
  roi_centers=roi_centers,
91
95
  image_shape=image.shape,
92
- selected_label=label_value,
96
+ selected_labels=label_values,
93
97
  )
94
98
  return pixmap
95
99
 
@@ -166,14 +170,15 @@ def _blend_roi_masks(
166
170
  rgba: np.ndarray,
167
171
  labels: np.ndarray,
168
172
  *,
169
- selected_label: int | None,
173
+ selected_labels: tuple[int, ...],
170
174
  selected_color: QColor,
171
175
  ) -> None:
172
176
  """Blend all ROI masks into the preview, emphasizing the selected mask."""
177
+ selected = set(selected_labels)
173
178
  roi_values = tuple(
174
179
  int(value)
175
180
  for value in np.unique(labels)
176
- if int(value) > 0 and int(value) != selected_label
181
+ if int(value) > 0 and int(value) not in selected
177
182
  )
178
183
  for roi_value in roi_values:
179
184
  _blend_mask_color(
@@ -182,7 +187,7 @@ def _blend_roi_masks(
182
187
  color=(31, 180, 190),
183
188
  alpha=0.28,
184
189
  )
185
- if selected_label is not None:
190
+ for selected_label in selected_labels:
186
191
  selected_mask = labels == selected_label
187
192
  rgba[selected_mask, 0] = selected_color.red()
188
193
  rgba[selected_mask, 1] = selected_color.green()
@@ -228,7 +233,7 @@ def _draw_roi_numbers(
228
233
  *,
229
234
  roi_centers: tuple[tuple[int, float, float], ...],
230
235
  image_shape: tuple[int, ...],
231
- selected_label: int | None,
236
+ selected_labels: tuple[int, ...],
232
237
  ) -> None:
233
238
  """Draw ROI numbers at scaled ROI centroids on top of a preview pixmap."""
234
239
  if len(roi_centers) == 0 or len(image_shape) < 2:
@@ -244,13 +249,14 @@ def _draw_roi_numbers(
244
249
  painter.setFont(font)
245
250
  x_scale = pixmap.width() / width
246
251
  y_scale = pixmap.height() / height
252
+ selected = set(selected_labels)
247
253
  for label_value, x_center, y_center in roi_centers:
248
254
  text = str(label_value)
249
255
  x = int(round(x_center * x_scale))
250
256
  y = int(round(y_center * y_scale))
251
257
  foreground = (
252
258
  QColor(255, 246, 230)
253
- if label_value == selected_label
259
+ if label_value in selected
254
260
  else QColor(215, 255, 255)
255
261
  )
256
262
  painter.setPen(QColor(0, 0, 0))
@@ -36,7 +36,10 @@ from twopy.napari.roi import roi_label_image_from_layer_for_recording
36
36
  from twopy.napari.session import LoadedNapariRecording
37
37
  from twopy.napari.text import counted_noun
38
38
 
39
+ SelectedRoiKey = tuple[Path, str]
40
+
39
41
  __all__ = [
42
+ "SelectedRoiKey",
40
43
  "SelectedRoiResponse",
41
44
  "add_response_legend",
42
45
  "combined_response_plot_data",
@@ -58,6 +61,11 @@ class SelectedRoiResponse:
58
61
  plot_data: ResponsePlotData
59
62
  color: QColor
60
63
 
64
+ @property
65
+ def key(self) -> SelectedRoiKey:
66
+ """Return the visibility key for this selected ROI trace."""
67
+ return (self.recording_path, self.roi_label)
68
+
61
69
 
62
70
  def selected_roi_response_plot_data(
63
71
  recording: LoadedNapariRecording,
@@ -184,11 +192,11 @@ def selected_response_visibility_status(
184
192
  """Return the Selected ROIs summary for the current trace visibility."""
185
193
  if visible_count == total_count:
186
194
  if total_count == 1:
187
- return "Showing the selected recording trace."
188
- selected_traces = counted_noun(total_count, "selected recording trace")
195
+ return "Showing the selected ROI trace."
196
+ selected_traces = counted_noun(total_count, "selected ROI trace")
189
197
  return f"Showing all {selected_traces}."
190
- total_traces = counted_noun(total_count, "selected recording trace")
191
- hidden_traces = counted_noun(total_count - visible_count, "recording trace")
198
+ total_traces = counted_noun(total_count, "selected ROI trace")
199
+ hidden_traces = counted_noun(total_count - visible_count, "ROI trace")
192
200
  return f"Showing {visible_count} of {total_traces}; {hidden_traces} hidden."
193
201
 
194
202
 
@@ -261,18 +269,18 @@ def add_response_legend(
261
269
  layout: QGridLayout,
262
270
  selected_responses: tuple[SelectedRoiResponse, ...],
263
271
  *,
264
- hidden_recordings: set[Path],
265
- set_visible: Callable[[Path, bool], None],
272
+ hidden_responses: set[SelectedRoiKey],
273
+ set_visible: Callable[[SelectedRoiKey, bool], None],
266
274
  max_width: int,
267
275
  ) -> None:
268
- """Add clickable color toggles for overlaid recording traces."""
276
+ """Add clickable color toggles for overlaid selected-ROI traces."""
269
277
  row_index = 0
270
278
  row_width = 0
271
279
  spacing = max(0, layout.spacing())
272
280
  available_width = max(120, max_width)
273
281
  row_layout = _new_response_legend_row(layout, row_index, spacing)
274
282
  for response in selected_responses:
275
- visible = response.recording_path not in hidden_recordings
283
+ visible = response.key not in hidden_responses
276
284
  recording_label = format_recording_minute_label(response.recording_path)
277
285
  button = QPushButton(
278
286
  f"{recording_label} - ROI {roi_label_display_id(response.roi_label)}",
@@ -281,7 +289,7 @@ def add_response_legend(
281
289
  button.setChecked(visible)
282
290
  button.setStyleSheet(trace_button_style(response.color, visible=visible))
283
291
  button.clicked.connect(
284
- lambda checked, path=response.recording_path: set_visible(path, checked),
292
+ lambda checked, key=response.key: set_visible(key, checked),
285
293
  )
286
294
  button_width = button.sizeHint().width()
287
295
  button.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)