TwoPy 0.1.9__tar.gz → 0.2.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 (241) hide show
  1. {twopy-0.1.9 → twopy-0.2.1}/PKG-INFO +8 -3
  2. {twopy-0.1.9 → twopy-0.2.1}/README.md +5 -2
  3. {twopy-0.1.9 → twopy-0.2.1}/pyproject.toml +9 -1
  4. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/__init__.py +40 -9
  5. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/__init__.py +38 -10
  6. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/background_subtraction.py +120 -21
  7. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/dff.py +33 -0
  8. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/group_matching.py +149 -0
  9. twopy-0.2.1/src/twopy/analysis/kernels/__init__.py +352 -0
  10. twopy-0.2.1/src/twopy/analysis/kernels/_fitting.py +360 -0
  11. twopy-0.2.1/src/twopy/analysis/kernels/_stimulus.py +432 -0
  12. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/persistence.py +10 -19
  13. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/response_context.py +8 -11
  14. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/response_maps.py +9 -7
  15. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/response_processing/__init__.py +1 -1
  16. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/response_processing/options.py +7 -8
  17. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/response_processing/signals.py +7 -4
  18. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/response_window_options.py +1 -1
  19. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/responses.py +63 -19
  20. twopy-0.2.1/src/twopy/analysis/timing.py +315 -0
  21. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/trials.py +1 -84
  22. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/workflow.py +9 -9
  23. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis_cache.py +25 -5
  24. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/api.py +6 -4
  25. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/config.py +211 -36
  26. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/conversion/api.py +63 -6
  27. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/conversion/hdf5_writing.py +1 -1
  28. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/conversion/source_loading.py +26 -6
  29. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/conversion/stimulus_code.py +1 -1
  30. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/conversion/types.py +2 -2
  31. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/converted.py +60 -1
  32. twopy-0.2.1/src/twopy/custom/__init__.py +71 -0
  33. twopy-0.2.1/src/twopy/custom/discovery.py +303 -0
  34. twopy-0.2.1/src/twopy/custom/native.py +27 -0
  35. twopy-0.2.1/src/twopy/custom/native_workflows/__init__.py +5 -0
  36. twopy-0.2.1/src/twopy/custom/native_workflows/direction_selectivity.py +202 -0
  37. twopy-0.2.1/src/twopy/custom/native_workflows/response_kernels.py +497 -0
  38. twopy-0.2.1/src/twopy/custom/parameters.py +380 -0
  39. twopy-0.2.1/src/twopy/custom/provenance.py +399 -0
  40. twopy-0.2.1/src/twopy/custom/registry.py +78 -0
  41. twopy-0.2.1/src/twopy/custom/types.py +1077 -0
  42. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/database/__init__.py +4 -2
  43. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/database/cache.py +1 -1
  44. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/database/catalog.py +1 -1
  45. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/database/generic.py +1 -1
  46. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/database/modeled.py +7 -4
  47. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/database/search.py +147 -6
  48. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/database/types.py +0 -3
  49. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/hdf5_utils.py +2 -2
  50. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/controls.py +152 -281
  51. twopy-0.2.1/src/twopy/napari/custom_tab.py +755 -0
  52. twopy-0.2.1/src/twopy/napari/database_favorites.py +416 -0
  53. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/database_search.py +387 -68
  54. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/display_paths.py +41 -3
  55. twopy-0.2.1/src/twopy/napari/group_matching/__init__.py +29 -0
  56. twopy-0.2.1/src/twopy/napari/group_matching/cards.py +127 -0
  57. twopy-0.2.1/src/twopy/napari/group_matching/fov_assignment.py +679 -0
  58. twopy-0.2.1/src/twopy/napari/group_matching/fov_cards.py +143 -0
  59. twopy-0.1.9/src/twopy/napari/group_matching_images.py → twopy-0.2.1/src/twopy/napari/group_matching/images.py +2 -2
  60. twopy-0.2.1/src/twopy/napari/group_matching/response_preview.py +334 -0
  61. twopy-0.2.1/src/twopy/napari/group_matching/responses.py +130 -0
  62. twopy-0.2.1/src/twopy/napari/group_matching/roi_assignment.py +1269 -0
  63. twopy-0.2.1/src/twopy/napari/group_matching/roi_cards.py +371 -0
  64. twopy-0.2.1/src/twopy/napari/group_matching/style.py +269 -0
  65. twopy-0.2.1/src/twopy/napari/group_matching/tables.py +27 -0
  66. twopy-0.2.1/src/twopy/napari/group_matching/window.py +267 -0
  67. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/interactive.py +17 -21
  68. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/live_analysis.py +125 -122
  69. twopy-0.2.1/src/twopy/napari/load_workflow.py +409 -0
  70. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/loading.py +69 -13
  71. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/data.py +89 -47
  72. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/dff_options.py +1 -0
  73. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/docks/api.py +2 -2
  74. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/docks/options_panel.py +18 -13
  75. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/docks/paths.py +5 -6
  76. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/docks/plot_area.py +88 -0
  77. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/docks/response_map_area.py +13 -0
  78. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/docks/response_plot_widget.py +737 -21
  79. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/docks/save_actions.py +30 -67
  80. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/docks/visibility_state.py +29 -2
  81. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/export.py +37 -0
  82. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/export_controls.py +13 -5
  83. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/form_controls.py +5 -1
  84. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/normalization_options.py +7 -1
  85. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/options.py +1 -1
  86. twopy-0.2.1/src/twopy/napari/plotting/panels.py +188 -0
  87. twopy-0.2.1/src/twopy/napari/plotting/preview_strip.py +218 -0
  88. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/processing_options.py +172 -94
  89. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/roi_generation/actions.py +2 -2
  90. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/widgets.py +470 -26
  91. twopy-0.2.1/src/twopy/napari/responses.py +302 -0
  92. twopy-0.2.1/src/twopy/napari/roi_signatures.py +20 -0
  93. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/session.py +42 -3
  94. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/sidebar.py +36 -14
  95. twopy-0.2.1/src/twopy/napari/state.py +192 -0
  96. twopy-0.2.1/src/twopy/napari/text.py +64 -0
  97. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/trial_timeline.py +3 -3
  98. {twopy-0.1.9 → twopy-0.2.1}/src/twopy.egg-info/PKG-INFO +8 -3
  99. {twopy-0.1.9 → twopy-0.2.1}/src/twopy.egg-info/SOURCES.txt +55 -3
  100. {twopy-0.1.9 → twopy-0.2.1}/tests/test_api.py +12 -2
  101. {twopy-0.1.9 → twopy-0.2.1}/tests/test_background_subtraction.py +2 -4
  102. {twopy-0.1.9 → twopy-0.2.1}/tests/test_config.py +176 -19
  103. {twopy-0.1.9 → twopy-0.2.1}/tests/test_conversion.py +181 -2
  104. {twopy-0.1.9 → twopy-0.2.1}/tests/test_converted.py +63 -101
  105. twopy-0.2.1/tests/test_custom_workflows.py +1053 -0
  106. {twopy-0.1.9 → twopy-0.2.1}/tests/test_database.py +40 -3
  107. {twopy-0.1.9 → twopy-0.2.1}/tests/test_dff.py +33 -3
  108. {twopy-0.1.9 → twopy-0.2.1}/tests/test_epoch_mapping.py +5 -30
  109. twopy-0.2.1/tests/test_function_inventory.py +155 -0
  110. twopy-0.2.1/tests/test_group_matching.py +384 -0
  111. {twopy-0.1.9 → twopy-0.2.1}/tests/test_hdf5_utils.py +1 -2
  112. twopy-0.2.1/tests/test_kernels.py +489 -0
  113. twopy-0.2.1/tests/test_load_workflow.py +617 -0
  114. {twopy-0.1.9 → twopy-0.2.1}/tests/test_motion.py +4 -33
  115. twopy-0.2.1/tests/test_napari.py +674 -0
  116. twopy-0.2.1/tests/test_napari_database_search.py +697 -0
  117. twopy-0.2.1/tests/test_napari_display.py +132 -0
  118. twopy-0.2.1/tests/test_napari_export.py +394 -0
  119. twopy-0.2.1/tests/test_napari_group_matching_responses.py +140 -0
  120. twopy-0.2.1/tests/test_napari_group_matching_roi_cards.py +156 -0
  121. twopy-0.2.1/tests/test_napari_live_controller.py +574 -0
  122. twopy-0.2.1/tests/test_napari_load_tab.py +634 -0
  123. twopy-0.2.1/tests/test_napari_loaded_recordings.py +1004 -0
  124. twopy-0.2.1/tests/test_napari_path_resolution.py +659 -0
  125. twopy-0.2.1/tests/test_napari_plot_controls.py +569 -0
  126. twopy-0.2.1/tests/test_napari_plot_data.py +366 -0
  127. twopy-0.2.1/tests/test_napari_plot_widgets.py +1808 -0
  128. twopy-0.2.1/tests/test_napari_processing_controls.py +747 -0
  129. twopy-0.2.1/tests/test_napari_response_map_display.py +85 -0
  130. twopy-0.2.1/tests/test_napari_response_workflow.py +756 -0
  131. twopy-0.2.1/tests/test_napari_roi_generation.py +620 -0
  132. twopy-0.2.1/tests/test_napari_roi_visibility.py +332 -0
  133. twopy-0.2.1/tests/test_napari_text.py +49 -0
  134. twopy-0.2.1/tests/test_napari_timeline.py +251 -0
  135. {twopy-0.1.9 → twopy-0.2.1}/tests/test_packaging.py +15 -23
  136. {twopy-0.1.9 → twopy-0.2.1}/tests/test_parity.py +9 -31
  137. {twopy-0.1.9 → twopy-0.2.1}/tests/test_parity_psycho5.py +7 -62
  138. {twopy-0.1.9 → twopy-0.2.1}/tests/test_photodiode_classification.py +4 -31
  139. twopy-0.2.1/tests/test_recording_timing.py +200 -0
  140. {twopy-0.1.9 → twopy-0.2.1}/tests/test_response_maps.py +6 -32
  141. {twopy-0.1.9 → twopy-0.2.1}/tests/test_response_processing.py +45 -28
  142. {twopy-0.1.9 → twopy-0.2.1}/tests/test_response_roi_extraction.py +7 -33
  143. {twopy-0.1.9 → twopy-0.2.1}/tests/test_responses.py +21 -0
  144. {twopy-0.1.9 → twopy-0.2.1}/tests/test_roi.py +2 -6
  145. {twopy-0.1.9 → twopy-0.2.1}/tests/test_stimulus.py +38 -73
  146. {twopy-0.1.9 → twopy-0.2.1}/tests/test_synchronization.py +25 -57
  147. twopy-0.2.1/tests/test_trials.py +182 -0
  148. {twopy-0.1.9 → twopy-0.2.1}/tests/test_workflow.py +9 -72
  149. twopy-0.1.9/src/twopy/napari/group_matching.py +0 -585
  150. twopy-0.1.9/src/twopy/napari/group_matching_roi.py +0 -1147
  151. twopy-0.1.9/src/twopy/napari/plotting/panels.py +0 -103
  152. twopy-0.1.9/src/twopy/napari/responses.py +0 -141
  153. twopy-0.1.9/src/twopy/napari/state.py +0 -106
  154. twopy-0.1.9/src/twopy/napari/text.py +0 -26
  155. twopy-0.1.9/tests/test_group_matching.py +0 -180
  156. twopy-0.1.9/tests/test_napari.py +0 -7001
  157. twopy-0.1.9/tests/test_trials.py +0 -372
  158. {twopy-0.1.9 → twopy-0.2.1}/setup.cfg +0 -0
  159. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/_segments.py +0 -0
  160. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/_version.py +0 -0
  161. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/dff_options.py +0 -0
  162. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/epoch_mapping.py +0 -0
  163. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/motion.py +0 -0
  164. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/response_processing/apply.py +0 -0
  165. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/response_processing/persistence.py +0 -0
  166. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/analysis/response_processing/qc.py +0 -0
  167. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/calibrations/pixel_calibration_profiles.csv +0 -0
  168. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/calibrations/pixel_size.csv +0 -0
  169. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/conversion/__init__.py +0 -0
  170. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/conversion/alignment_crop.py +0 -0
  171. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/conversion/matlab_values.py +0 -0
  172. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/database/connection.py +0 -0
  173. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/database/sql.py +0 -0
  174. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/filenames.py +0 -0
  175. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/frame_ranges.py +0 -0
  176. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/inspection.py +0 -0
  177. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/matlab.py +0 -0
  178. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/__init__.py +0 -0
  179. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/__main__.py +0 -0
  180. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/dims.py +0 -0
  181. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/display.py +0 -0
  182. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/errors.py +0 -0
  183. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/latest_worker.py +0 -0
  184. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/launcher.py +0 -0
  185. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/loaded_recordings_csv.py +0 -0
  186. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/movie.py +0 -0
  187. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/paths.py +0 -0
  188. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/__init__.py +0 -0
  189. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/docks/__init__.py +0 -0
  190. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/docks/dynamic_options.py +0 -0
  191. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/label_visibility.py +0 -0
  192. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/response_map_colors.py +0 -0
  193. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/response_map_display.py +0 -0
  194. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/response_map_options.py +0 -0
  195. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/response_window_options.py +0 -0
  196. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/roi_generation/__init__.py +0 -0
  197. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/roi_generation/controls.py +0 -0
  198. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/plotting/roi_generation/options.py +0 -0
  199. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/protocols.py +0 -0
  200. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/roi.py +0 -0
  201. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/types.py +0 -0
  202. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/napari/viewer.py +0 -0
  203. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/parity/__init__.py +0 -0
  204. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/parity/dff.py +0 -0
  205. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/parity/psycho5.py +0 -0
  206. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/parity/roi.py +0 -0
  207. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/parity/roi_extraction.py +0 -0
  208. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/parity/saved_analysis.py +0 -0
  209. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/photodiode.py +0 -0
  210. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/photodiode_classification.py +0 -0
  211. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/pixel_calibration.py +0 -0
  212. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/pixel_calibration_profiles.py +0 -0
  213. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/response_roi_extraction.py +0 -0
  214. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/roi.py +0 -0
  215. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/roi_extraction.py +0 -0
  216. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/roi_mask_cleanup.py +0 -0
  217. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/session.py +0 -0
  218. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/spatial.py +0 -0
  219. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/stimulus.py +0 -0
  220. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/synchronization.py +0 -0
  221. {twopy-0.1.9 → twopy-0.2.1}/src/twopy/typing_guards.py +0 -0
  222. {twopy-0.1.9 → twopy-0.2.1}/src/twopy.egg-info/dependency_links.txt +0 -0
  223. {twopy-0.1.9 → twopy-0.2.1}/src/twopy.egg-info/entry_points.txt +0 -0
  224. {twopy-0.1.9 → twopy-0.2.1}/src/twopy.egg-info/requires.txt +0 -0
  225. {twopy-0.1.9 → twopy-0.2.1}/src/twopy.egg-info/top_level.txt +0 -0
  226. {twopy-0.1.9 → twopy-0.2.1}/tests/test_filenames.py +0 -0
  227. {twopy-0.1.9 → twopy-0.2.1}/tests/test_frame_ranges.py +0 -0
  228. {twopy-0.1.9 → twopy-0.2.1}/tests/test_import.py +0 -0
  229. {twopy-0.1.9 → twopy-0.2.1}/tests/test_inspection.py +0 -0
  230. {twopy-0.1.9 → twopy-0.2.1}/tests/test_matlab.py +0 -0
  231. {twopy-0.1.9 → twopy-0.2.1}/tests/test_parity_roi_extraction.py +0 -0
  232. {twopy-0.1.9 → twopy-0.2.1}/tests/test_persistence.py +0 -0
  233. {twopy-0.1.9 → twopy-0.2.1}/tests/test_pixel_calibration.py +0 -0
  234. {twopy-0.1.9 → twopy-0.2.1}/tests/test_pixel_calibration_profiles.py +0 -0
  235. {twopy-0.1.9 → twopy-0.2.1}/tests/test_real_fixture.py +0 -0
  236. {twopy-0.1.9 → twopy-0.2.1}/tests/test_response_map_area.py +0 -0
  237. {twopy-0.1.9 → twopy-0.2.1}/tests/test_response_window_options.py +0 -0
  238. {twopy-0.1.9 → twopy-0.2.1}/tests/test_roi_extraction.py +0 -0
  239. {twopy-0.1.9 → twopy-0.2.1}/tests/test_roi_mask_cleanup.py +0 -0
  240. {twopy-0.1.9 → twopy-0.2.1}/tests/test_saved_analysis.py +0 -0
  241. {twopy-0.1.9 → twopy-0.2.1}/tests/test_session.py +0 -0
@@ -1,9 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: twopy
3
- Version: 0.1.9
3
+ Version: 0.2.1
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
7
+ Project-URL: Homepage, https://twopy.gumadeiras.com
8
+ Project-URL: Repository, https://github.com/gumadeiras/twopy
7
9
  Requires-Python: <3.14,>=3.13
8
10
  Description-Content-Type: text/markdown
9
11
  Requires-Dist: h5py<4,>=3.16
@@ -73,8 +75,11 @@ When a source recording has not been converted yet, twopy converts it first. Con
73
75
 
74
76
  ## Docs
75
77
 
76
- - [GUI guide](docs/gui.md): napari loading, ROI editing, plotting, and saved outputs.
77
- - [Python API guide](docs/python_api.md): find recordings, convert data, run analysis, and open napari from scripts.
78
+ - [Full documentation](https://twopy.gumadeiras.com)
79
+ - [Getting started](docs/getting_started.md): install, configure, launch, and save your first analysis.
80
+ - [GUI guide](docs/gui/index.md): one page per task — loading, ROIs, plots, custom workflows, group matching, saving.
81
+ - [Python API guide](docs/python_api.md): find recordings, convert data, extract traces, fit kernels, and open napari from scripts.
82
+ - [Writing custom workflows](docs/writing_custom_workflows.md): add your own analysis to the napari Custom tab.
78
83
  - [Development guide](docs/development.md): source setup and local checks.
79
84
  - [Input data spec](docs/input_data_spec.md): short recording folder contract.
80
85
  - [Recording file reference](docs/recording_file_schema.md): detailed source and converted file schema.
@@ -53,8 +53,11 @@ When a source recording has not been converted yet, twopy converts it first. Con
53
53
 
54
54
  ## Docs
55
55
 
56
- - [GUI guide](docs/gui.md): napari loading, ROI editing, plotting, and saved outputs.
57
- - [Python API guide](docs/python_api.md): find recordings, convert data, run analysis, and open napari from scripts.
56
+ - [Full documentation](https://twopy.gumadeiras.com)
57
+ - [Getting started](docs/getting_started.md): install, configure, launch, and save your first analysis.
58
+ - [GUI guide](docs/gui/index.md): one page per task — loading, ROIs, plots, custom workflows, group matching, saving.
59
+ - [Python API guide](docs/python_api.md): find recordings, convert data, extract traces, fit kernels, and open napari from scripts.
60
+ - [Writing custom workflows](docs/writing_custom_workflows.md): add your own analysis to the napari Custom tab.
58
61
  - [Development guide](docs/development.md): source setup and local checks.
59
62
  - [Input data spec](docs/input_data_spec.md): short recording folder contract.
60
63
  - [Recording file reference](docs/recording_file_schema.md): detailed source and converted file schema.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "twopy"
7
- version = "0.1.9"
7
+ version = "0.2.1"
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"
@@ -29,6 +29,10 @@ dependencies = [
29
29
  [project.scripts]
30
30
  twopy = "twopy.napari:main"
31
31
 
32
+ [project.urls]
33
+ Homepage = "https://twopy.gumadeiras.com"
34
+ Repository = "https://github.com/gumadeiras/twopy"
35
+
32
36
  [tool.setuptools.packages.find]
33
37
  where = ["src"]
34
38
 
@@ -43,11 +47,15 @@ target-version = "py313"
43
47
  [tool.ruff.lint]
44
48
  select = [
45
49
  "ANN",
50
+ "ARG",
46
51
  "B",
52
+ "C4",
47
53
  "D",
48
54
  "E",
49
55
  "F",
50
56
  "I",
57
+ "NPY",
58
+ "PTH",
51
59
  "SIM",
52
60
  "UP",
53
61
  "W",
@@ -22,12 +22,17 @@ from twopy.analysis import (
22
22
  FrameWindow,
23
23
  GroupedRoiResponses,
24
24
  GroupedRoiResponseSummary,
25
- InterpolatedEpochMapping,
25
+ Hemisphere,
26
+ KernelFitMethod,
27
+ KernelStimulusModality,
26
28
  LoadedAnalysisOutputs,
27
29
  LowPassFilterOptions,
28
30
  ManualFovGroupRow,
31
+ ManualRoiMatchGroup,
29
32
  ManualRoiMatchRow,
30
33
  ManualRoiMatchStatus,
34
+ RecordingKernelFit,
35
+ RecordingTiming,
31
36
  ResponseMapData,
32
37
  ResponseMapMode,
33
38
  ResponseMapOptions,
@@ -37,7 +42,10 @@ from twopy.analysis import (
37
42
  RoiResponseSummary,
38
43
  RoiResponseTrial,
39
44
  SmoothingOptions,
45
+ StimulusKernelOptions,
46
+ TimingSource,
40
47
  WindowedRoiResponse,
48
+ add_manual_roi_match_group,
41
49
  analyze_recording_responses,
42
50
  append_manual_roi_match_rows,
43
51
  apply_motion_artifact_mask_to_delta_f_over_f,
@@ -45,11 +53,13 @@ from twopy.analysis import (
45
53
  compute_recording_responses,
46
54
  compute_roi_delta_f_over_f,
47
55
  default_baseline_epoch_number,
56
+ default_kernel_stimulus_column,
48
57
  default_recording_baseline_epoch_number,
49
58
  extract_background_corrected_roi_traces,
50
- frame_windows_from_photodiode_alignment,
59
+ finite_mean_and_sem,
60
+ fit_recording_stimulus_kernels,
61
+ fit_stimulus_kernel,
51
62
  group_delta_f_over_f_by_epoch,
52
- interpolate_stimulus_epochs_to_frame_windows,
53
63
  is_baseline_epoch_name,
54
64
  load_analysis_outputs,
55
65
  load_manual_fov_group_rows,
@@ -58,10 +68,13 @@ from twopy.analysis import (
58
68
  make_frame_windows,
59
69
  make_manual_fov_group_rows,
60
70
  make_manual_roi_match_rows,
61
- map_stimulus_epochs_to_frame_windows,
71
+ matched_manual_roi_groups,
62
72
  next_group_cell_id,
63
73
  no_baseline_epoch_frame_windows,
74
+ remove_manual_roi_match_group,
75
+ replace_manual_roi_match_group,
64
76
  resolve_baseline_frame_windows,
77
+ resolve_recording_timing,
65
78
  save_analysis_outputs,
66
79
  save_manual_fov_group_rows,
67
80
  save_manual_roi_match_rows,
@@ -76,7 +89,11 @@ from twopy.analysis import (
76
89
  )
77
90
  from twopy.api import find_recordings
78
91
  from twopy.conversion import convert_recording_to_twopy, load_source_conversion_inputs
79
- from twopy.converted import load_converted_recording, recording_frame_rate_hz
92
+ from twopy.converted import (
93
+ load_converted_recording,
94
+ recording_frame_rate_hz,
95
+ recording_hemisphere,
96
+ )
80
97
  from twopy.napari import (
81
98
  NapariRecordingView,
82
99
  NapariSidebarWidgets,
@@ -161,6 +178,7 @@ __all__ = [
161
178
  "AlignedPhotodiodeEvent",
162
179
  "AnalysisResponseRun",
163
180
  "AnalysisResponseComputation",
181
+ "add_manual_roi_match_group",
164
182
  "analyze_recording_responses",
165
183
  "add_twopy_magicgui_controls",
166
184
  "append_manual_roi_match_rows",
@@ -181,6 +199,7 @@ __all__ = [
181
199
  "DEFAULT_PIXEL_CALIBRATION_PATH",
182
200
  "DEFAULT_PIXEL_CALIBRATION_PROFILE_PATH",
183
201
  "default_baseline_epoch_number",
202
+ "default_kernel_stimulus_column",
184
203
  "default_recording_baseline_epoch_number",
185
204
  "detect_photodiode_events",
186
205
  "detect_recording_photodiode_events",
@@ -194,18 +213,20 @@ __all__ = [
194
213
  "extract_rois_from_image",
195
214
  "extract_roi_traces",
196
215
  "find_recordings",
197
- "frame_windows_from_photodiode_alignment",
216
+ "fit_recording_stimulus_kernels",
217
+ "fit_stimulus_kernel",
198
218
  "FrameWindow",
199
219
  "full_frame_crop",
200
220
  "group_delta_f_over_f_by_epoch",
201
221
  "GroupedRoiResponseSummary",
202
222
  "GroupedRoiResponses",
223
+ "Hemisphere",
203
224
  "grid_roi_set",
204
225
  "grid_roi_set_microns",
205
226
  "grid_size_pixels_from_microns",
206
- "InterpolatedEpochMapping",
207
- "interpolate_stimulus_epochs_to_frame_windows",
208
227
  "is_baseline_epoch_name",
228
+ "KernelFitMethod",
229
+ "KernelStimulusModality",
209
230
  "launch_napari",
210
231
  "load_analysis_outputs",
211
232
  "load_response_map_data",
@@ -219,16 +240,19 @@ __all__ = [
219
240
  "LoadedAnalysisOutputs",
220
241
  "LowPassFilterOptions",
221
242
  "ManualFovGroupRow",
243
+ "ManualRoiMatchGroup",
222
244
  "ManualRoiMatchRow",
223
245
  "ManualRoiMatchStatus",
246
+ "matched_manual_roi_groups",
224
247
  "make_roi_set_from_label_image",
225
248
  "make_frame_windows",
226
249
  "make_manual_fov_group_rows",
227
250
  "make_manual_roi_match_rows",
228
251
  "make_roi_set",
229
- "map_stimulus_epochs_to_frame_windows",
230
252
  "next_group_cell_id",
231
253
  "no_baseline_epoch_frame_windows",
254
+ "remove_manual_roi_match_group",
255
+ "replace_manual_roi_match_group",
232
256
  "map_stimulus_specific_column",
233
257
  "NapariRecordingView",
234
258
  "NapariSidebarWidgets",
@@ -255,10 +279,14 @@ __all__ = [
255
279
  "ResponseStatistic",
256
280
  "ResponseWatershedExtraction",
257
281
  "ResponseWatershedScoreImages",
282
+ "RecordingKernelFit",
283
+ "RecordingTiming",
258
284
  "recording_frame_rate_hz",
285
+ "recording_hemisphere",
259
286
  "resolve_pixel_size_um",
260
287
  "resolve_pixel_calibration_profile",
261
288
  "resolve_baseline_frame_windows",
289
+ "resolve_recording_timing",
262
290
  "response_watershed_roi_set",
263
291
  "RoiExtractionConfig",
264
292
  "RoiExtractionMethod",
@@ -282,10 +310,13 @@ __all__ = [
282
310
  "SmoothingOptions",
283
311
  "SpatialCrop",
284
312
  "SpatialDomain",
313
+ "StimulusKernelOptions",
285
314
  "StimulusSpecificColumnMapping",
286
315
  "stimulus_epoch_names_by_number",
316
+ "finite_mean_and_sem",
287
317
  "summarize_epoch_roi_responses",
288
318
  "summarize_grouped_responses",
319
+ "TimingSource",
289
320
  "validate_grouped_roi_responses",
290
321
  "watershed_roi_set",
291
322
  "WindowedRoiResponse",
@@ -19,23 +19,34 @@ from twopy.analysis.dff import (
19
19
  compute_roi_delta_f_over_f,
20
20
  )
21
21
  from twopy.analysis.dff_options import DeltaFOverFBaselineMode, DeltaFOverFOptions
22
- from twopy.analysis.epoch_mapping import (
23
- InterpolatedEpochMapping,
24
- interpolate_stimulus_epochs_to_frame_windows,
25
- )
26
22
  from twopy.analysis.group_matching import (
27
23
  ManualFovGroupRow,
24
+ ManualRoiMatchGroup,
28
25
  ManualRoiMatchRow,
29
26
  ManualRoiMatchStatus,
27
+ add_manual_roi_match_group,
30
28
  append_manual_roi_match_rows,
31
29
  load_manual_fov_group_rows,
32
30
  load_manual_roi_match_rows,
33
31
  make_manual_fov_group_rows,
34
32
  make_manual_roi_match_rows,
33
+ matched_manual_roi_groups,
35
34
  next_group_cell_id,
35
+ remove_manual_roi_match_group,
36
+ replace_manual_roi_match_group,
36
37
  save_manual_fov_group_rows,
37
38
  save_manual_roi_match_rows,
38
39
  )
40
+ from twopy.analysis.kernels import (
41
+ Hemisphere,
42
+ KernelFitMethod,
43
+ KernelStimulusModality,
44
+ RecordingKernelFit,
45
+ StimulusKernelOptions,
46
+ default_kernel_stimulus_column,
47
+ fit_recording_stimulus_kernels,
48
+ fit_stimulus_kernel,
49
+ )
39
50
  from twopy.analysis.motion import apply_motion_artifact_mask_to_delta_f_over_f
40
51
  from twopy.analysis.persistence import (
41
52
  ANALYSIS_OUTPUT_FILE_FORMAT,
@@ -67,20 +78,24 @@ from twopy.analysis.responses import (
67
78
  GroupedRoiResponseSummary,
68
79
  RoiResponseSummary,
69
80
  RoiResponseTrial,
81
+ finite_mean_and_sem,
70
82
  group_delta_f_over_f_by_epoch,
71
83
  summarize_epoch_roi_responses,
72
84
  summarize_grouped_responses,
73
85
  validate_grouped_roi_responses,
74
86
  )
87
+ from twopy.analysis.timing import (
88
+ RecordingTiming,
89
+ TimingSource,
90
+ resolve_recording_timing,
91
+ )
75
92
  from twopy.analysis.trials import (
76
93
  EpochFrameWindow,
77
94
  FrameWindow,
78
95
  WindowedRoiResponse,
79
96
  default_baseline_epoch_number,
80
- frame_windows_from_photodiode_alignment,
81
97
  is_baseline_epoch_name,
82
98
  make_frame_windows,
83
- map_stimulus_epochs_to_frame_windows,
84
99
  no_baseline_epoch_frame_windows,
85
100
  resolve_baseline_frame_windows,
86
101
  select_baseline_frame_windows,
@@ -117,27 +132,33 @@ __all__ = [
117
132
  "EpochResponseMap",
118
133
  "EpochFrameWindow",
119
134
  "extract_background_corrected_roi_traces",
120
- "frame_windows_from_photodiode_alignment",
135
+ "fit_recording_stimulus_kernels",
136
+ "fit_stimulus_kernel",
137
+ "default_kernel_stimulus_column",
138
+ "Hemisphere",
121
139
  "FrameWindow",
122
140
  "group_delta_f_over_f_by_epoch",
123
141
  "GroupedRoiResponseSummary",
124
142
  "GroupedRoiResponses",
125
- "InterpolatedEpochMapping",
126
- "interpolate_stimulus_epochs_to_frame_windows",
127
143
  "is_baseline_epoch_name",
144
+ "KernelFitMethod",
145
+ "KernelStimulusModality",
128
146
  "load_analysis_outputs",
129
147
  "load_response_map_data",
130
148
  "LoadedAnalysisOutputs",
131
149
  "LowPassFilterOptions",
132
150
  "ManualFovGroupRow",
151
+ "ManualRoiMatchGroup",
133
152
  "ManualRoiMatchRow",
134
153
  "ManualRoiMatchStatus",
154
+ "matched_manual_roi_groups",
135
155
  "make_frame_windows",
136
156
  "make_manual_fov_group_rows",
137
157
  "make_manual_roi_match_rows",
138
- "map_stimulus_epochs_to_frame_windows",
139
158
  "no_baseline_epoch_frame_windows",
140
159
  "next_group_cell_id",
160
+ "RecordingKernelFit",
161
+ "RecordingTiming",
141
162
  "RoiDeltaFOverF",
142
163
  "ResponseMapData",
143
164
  "ResponseMapMode",
@@ -147,18 +168,25 @@ __all__ = [
147
168
  "RoiResponseSummary",
148
169
  "RoiResponseTrial",
149
170
  "resolve_baseline_frame_windows",
171
+ "resolve_recording_timing",
172
+ "add_manual_roi_match_group",
150
173
  "append_manual_roi_match_rows",
151
174
  "load_manual_fov_group_rows",
152
175
  "load_manual_roi_match_rows",
176
+ "remove_manual_roi_match_group",
177
+ "replace_manual_roi_match_group",
153
178
  "save_analysis_outputs",
154
179
  "save_response_map_data",
155
180
  "save_manual_fov_group_rows",
156
181
  "save_manual_roi_match_rows",
157
182
  "select_baseline_frame_windows",
183
+ "finite_mean_and_sem",
158
184
  "split_traces_by_frame_windows",
185
+ "StimulusKernelOptions",
159
186
  "SmoothingOptions",
160
187
  "summarize_epoch_roi_responses",
161
188
  "summarize_grouped_responses",
189
+ "TimingSource",
162
190
  "validate_grouped_roi_responses",
163
191
  "WindowedRoiResponse",
164
192
  "write_response_summary_grouped_csv",
@@ -15,6 +15,7 @@ from typing import Literal
15
15
 
16
16
  import numpy as np
17
17
  import numpy.typing as npt
18
+ from scipy import sparse
18
19
 
19
20
  from twopy.converted import RecordingData
20
21
  from twopy.frame_ranges import normalize_frame_range
@@ -278,6 +279,7 @@ def _extract_mean_traces_from_indices(
278
279
  rather than total recording length.
279
280
  """
280
281
  traces = np.empty((frame_stop - frame_start, len(pixel_indices_by_trace)))
282
+ projection = _mean_projection(pixel_indices_by_trace)
281
283
  _check_cancelled(check_cancelled)
282
284
  for chunk_start, chunk_stop, frames in recording.movie.iter_frame_batches(
283
285
  chunk_frames=chunk_frames,
@@ -289,10 +291,7 @@ def _extract_mean_traces_from_indices(
289
291
  chunk_offset = chunk_start - frame_start
290
292
  chunk_slice = slice(chunk_offset, chunk_offset + (chunk_stop - chunk_start))
291
293
  chunk_flat = frames.reshape(frames.shape[0], -1)
292
- for trace_index, pixel_indices in enumerate(pixel_indices_by_trace):
293
- traces[chunk_slice, trace_index] = chunk_flat[:, pixel_indices].mean(
294
- axis=1,
295
- )
294
+ traces[chunk_slice, :] = _project_trace_pixels(chunk_flat, projection)
296
295
  _check_cancelled(check_cancelled)
297
296
 
298
297
  return traces
@@ -344,6 +343,7 @@ def _extract_movie_global_percentile_corrected_traces(
344
343
  )
345
344
  frame_count = frame_stop - frame_start
346
345
  roi_count = len(roi_pixel_indices)
346
+ projection = _mean_projection(roi_pixel_indices)
347
347
  raw_values = np.empty((frame_count, roi_count), dtype=np.float64)
348
348
  background_values = np.empty((frame_count, roi_count), dtype=np.float64)
349
349
  corrected_values = np.empty((frame_count, roi_count), dtype=np.float64)
@@ -359,14 +359,19 @@ def _extract_movie_global_percentile_corrected_traces(
359
359
  chunk_slice = slice(chunk_offset, chunk_offset + (chunk_stop - chunk_start))
360
360
  chunk_flat = frames.reshape(frames.shape[0], -1)
361
361
  background_trace = chunk_flat[:, background_indices].mean(axis=1)
362
- for roi_index, pixel_indices in enumerate(roi_pixel_indices):
363
- roi_pixels = chunk_flat[:, pixel_indices]
364
- raw_values[chunk_slice, roi_index] = roi_pixels.mean(axis=1)
365
- background_values[chunk_slice, roi_index] = background_trace
366
- # Clamp pixels before averaging so dim pixels cannot pull the
367
- # corrected ROI trace below zero.
368
- corrected_pixels = np.maximum(roi_pixels - background_trace[:, None], 0.0)
369
- corrected_values[chunk_slice, roi_index] = corrected_pixels.mean(axis=1)
362
+ roi_pixels = chunk_flat[:, projection.pixel_indices]
363
+ raw_values[chunk_slice, :] = _project_selected_pixels(roi_pixels, projection)
364
+ background_values[chunk_slice, :] = background_trace[:, np.newaxis]
365
+ # Clamp pixels before averaging so dim pixels cannot pull the
366
+ # corrected ROI trace below zero.
367
+ corrected_pixels = np.maximum(
368
+ roi_pixels - background_trace[:, np.newaxis],
369
+ 0.0,
370
+ )
371
+ corrected_values[chunk_slice, :] = _project_selected_pixels(
372
+ corrected_pixels,
373
+ projection,
374
+ )
370
375
  _check_cancelled(check_cancelled)
371
376
 
372
377
  metadata: dict[str, BackgroundMetadataValue] = {
@@ -440,6 +445,7 @@ def _extract_movie_y_stripe_percentile_corrected_traces(
440
445
  )
441
446
  frame_count = frame_stop - frame_start
442
447
  roi_count = len(roi_pixel_indices)
448
+ projection = _mean_projection(roi_pixel_indices)
443
449
  raw_values = np.empty((frame_count, roi_count), dtype=np.float64)
444
450
  background_values = np.empty((frame_count, roi_count), dtype=np.float64)
445
451
  corrected_values = np.empty((frame_count, roi_count), dtype=np.float64)
@@ -460,15 +466,21 @@ def _extract_movie_y_stripe_percentile_corrected_traces(
460
466
  )
461
467
  chunk_flat = frames.reshape(frames.shape[0], -1)
462
468
  background_flat = background_field.reshape(background_field.shape[0], -1)
463
- for roi_index, pixel_indices in enumerate(roi_pixel_indices):
464
- roi_pixels = chunk_flat[:, pixel_indices]
465
- roi_background_pixels = background_flat[:, pixel_indices]
466
- raw_values[chunk_slice, roi_index] = roi_pixels.mean(axis=1)
467
- background_values[chunk_slice, roi_index] = roi_background_pixels.mean(
468
- axis=1,
469
- )
470
- corrected_pixels = np.maximum(roi_pixels - roi_background_pixels, 0.0)
471
- corrected_values[chunk_slice, roi_index] = corrected_pixels.mean(axis=1)
469
+ roi_pixels = chunk_flat[:, projection.pixel_indices]
470
+ roi_background_pixels = background_flat[:, projection.pixel_indices]
471
+ raw_values[chunk_slice, :] = _project_selected_pixels(roi_pixels, projection)
472
+ background_values[chunk_slice, :] = _project_selected_pixels(
473
+ roi_background_pixels,
474
+ projection,
475
+ )
476
+ corrected_pixels = np.maximum(
477
+ roi_pixels - roi_background_pixels,
478
+ 0.0,
479
+ )
480
+ corrected_values[chunk_slice, :] = _project_selected_pixels(
481
+ corrected_pixels,
482
+ projection,
483
+ )
472
484
  _check_cancelled(check_cancelled)
473
485
 
474
486
  metadata: dict[str, BackgroundMetadataValue] = {
@@ -593,6 +605,93 @@ def _roi_pixel_indices_from_masks(
593
605
  return tuple(np.flatnonzero(mask).astype(np.int64) for mask in flat_masks)
594
606
 
595
607
 
608
+ @dataclass(frozen=True)
609
+ class _MeanProjection:
610
+ """Sparse averaging matrix plus selected flattened pixel indices."""
611
+
612
+ pixel_indices: npt.NDArray[np.int64]
613
+ matrix: sparse.csc_matrix
614
+
615
+
616
+ def _mean_projection(
617
+ pixel_indices_by_trace: tuple[npt.NDArray[np.int64], ...],
618
+ ) -> _MeanProjection:
619
+ """Return selected pixels and a sparse pixel-to-trace averaging matrix.
620
+
621
+ Args:
622
+ pixel_indices_by_trace: One flattened pixel-index array per trace.
623
+
624
+ Returns:
625
+ Selected flattened pixel indices plus a sparse matrix shaped
626
+ ``(selected_pixels, traces)`` whose columns average each trace.
627
+
628
+ Pixel indices are concatenated per trace instead of deduplicated. That
629
+ preserves the existing behavior for overlapping ROIs, where the same movie
630
+ pixel can contribute independently to more than one ROI mean.
631
+ """
632
+ selected_pixel_indices: list[npt.NDArray[np.int64]] = []
633
+ rows: list[npt.NDArray[np.int64]] = []
634
+ columns: list[npt.NDArray[np.int64]] = []
635
+ values: list[npt.NDArray[np.float64]] = []
636
+ row_start = 0
637
+ for trace_index, pixel_indices in enumerate(pixel_indices_by_trace):
638
+ if pixel_indices.size == 0:
639
+ continue
640
+ selected_pixel_indices.append(pixel_indices)
641
+ rows.append(
642
+ np.arange(
643
+ row_start,
644
+ row_start + pixel_indices.size,
645
+ dtype=np.int64,
646
+ ),
647
+ )
648
+ row_start += pixel_indices.size
649
+ columns.append(
650
+ np.full(pixel_indices.shape, trace_index, dtype=np.int64),
651
+ )
652
+ values.append(
653
+ np.full(
654
+ pixel_indices.shape,
655
+ 1.0 / float(pixel_indices.size),
656
+ dtype=np.float64,
657
+ ),
658
+ )
659
+ if len(selected_pixel_indices) == 0:
660
+ return _MeanProjection(
661
+ pixel_indices=np.array([], dtype=np.int64),
662
+ matrix=sparse.csc_matrix((0, len(pixel_indices_by_trace))),
663
+ )
664
+ return _MeanProjection(
665
+ pixel_indices=np.concatenate(selected_pixel_indices),
666
+ matrix=sparse.csc_matrix(
667
+ (
668
+ np.concatenate(values),
669
+ (np.concatenate(rows), np.concatenate(columns)),
670
+ ),
671
+ shape=(row_start, len(pixel_indices_by_trace)),
672
+ ),
673
+ )
674
+
675
+
676
+ def _project_trace_pixels(
677
+ chunk_flat: npt.NDArray[np.float64],
678
+ projection: _MeanProjection,
679
+ ) -> npt.NDArray[np.float64]:
680
+ """Return trace means from a flattened movie chunk and projection."""
681
+ return _project_selected_pixels(chunk_flat[:, projection.pixel_indices], projection)
682
+
683
+
684
+ def _project_selected_pixels(
685
+ selected_pixels: npt.NDArray[np.float64],
686
+ projection: _MeanProjection,
687
+ ) -> npt.NDArray[np.float64]:
688
+ """Return trace means from selected pixel columns."""
689
+ return np.asarray(
690
+ selected_pixels @ projection.matrix,
691
+ dtype=np.float64,
692
+ )
693
+
694
+
596
695
  def _check_cancelled(check_cancelled: Callable[[], None] | None) -> None:
597
696
  """Raise ``CancelledError`` when an interactive caller superseded this work."""
598
697
  if check_cancelled is None:
@@ -115,6 +115,10 @@ def compute_roi_delta_f_over_f(
115
115
  data_rate_hz=data_rate_hz,
116
116
  baseline_sample_seconds=baseline_sample_seconds,
117
117
  )
118
+ _validate_baseline_samples_for_background_method(
119
+ traces=traces,
120
+ baseline_samples=baseline_samples,
121
+ )
118
122
  tau = _fit_shared_tau(
119
123
  frame_numbers=baseline_samples.frame_numbers,
120
124
  mean_fluorescence=baseline_samples.values.mean(axis=1),
@@ -274,6 +278,35 @@ def _baseline_samples(
274
278
  )
275
279
 
276
280
 
281
+ def _validate_baseline_samples_for_background_method(
282
+ *,
283
+ traces: BackgroundCorrectedRoiTraces,
284
+ baseline_samples: _BaselineSamples,
285
+ ) -> None:
286
+ """Reject background-corrected baselines that cannot support dF/F."""
287
+ if traces.method != "roi_y_stripe_percentile":
288
+ return
289
+
290
+ invalid = ~np.isfinite(baseline_samples.values) | (baseline_samples.values <= 0.0)
291
+ if not np.any(invalid):
292
+ return
293
+
294
+ roi_indices = np.flatnonzero(np.any(invalid, axis=0))
295
+ labels = ", ".join(traces.labels[index] for index in roi_indices[:5])
296
+ if roi_indices.size > 5:
297
+ labels = f"{labels}, ..."
298
+ minimum = float(np.nanmin(baseline_samples.values[:, roi_indices]))
299
+ msg = (
300
+ "ROI y-stripe P% background subtraction over-subtracted baseline "
301
+ f"fluorescence for {labels}; minimum corrected baseline sample is "
302
+ f"{minimum:.6g}. dF/F needs positive baseline fluorescence, so the "
303
+ "local y-stripe pixels are not a valid additive background for those "
304
+ "ROIs. Use shared y-stripe P%, global percentile, or revise the ROIs "
305
+ "to leave dim local background pixels."
306
+ )
307
+ raise ValueError(msg)
308
+
309
+
277
310
  def _validate_baseline_local_range(
278
311
  *,
279
312
  window: FrameWindow,