TwoPy 0.4.1__tar.gz → 0.4.2__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 (271) hide show
  1. {twopy-0.4.1 → twopy-0.4.2}/PKG-INFO +1 -1
  2. {twopy-0.4.1 → twopy-0.4.2}/pyproject.toml +1 -1
  3. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/persistence.py +23 -1
  4. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/response_plotting.py +3 -5
  5. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/response_processing/apply.py +5 -23
  6. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/response_processing/options.py +3 -2
  7. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/response_processing/persistence.py +32 -8
  8. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/response_processing/qc.py +43 -18
  9. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/responses.py +26 -3
  10. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/docks/response_plot_widget.py +5 -3
  11. {twopy-0.4.1 → twopy-0.4.2}/src/twopy.egg-info/PKG-INFO +1 -1
  12. {twopy-0.4.1 → twopy-0.4.2}/src/twopy.egg-info/SOURCES.txt +2 -0
  13. twopy-0.4.2/tests/test_correlation_qc.py +341 -0
  14. twopy-0.4.2/tests/test_napari_correlation_qc.py +94 -0
  15. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_response_workflow.py +6 -0
  16. {twopy-0.4.1 → twopy-0.4.2}/tests/test_response_processing.py +97 -0
  17. {twopy-0.4.1 → twopy-0.4.2}/README.md +0 -0
  18. {twopy-0.4.1 → twopy-0.4.2}/setup.cfg +0 -0
  19. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/__init__.py +0 -0
  20. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/__init__.pyi +0 -0
  21. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/_segments.py +0 -0
  22. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/_version.py +0 -0
  23. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/__init__.py +0 -0
  24. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/background_subtraction.py +0 -0
  25. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/dff.py +0 -0
  26. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/dff_options.py +0 -0
  27. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/epoch_mapping.py +0 -0
  28. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/group_matching.py +0 -0
  29. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/kernels/__init__.py +0 -0
  30. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/kernels/_fitting.py +0 -0
  31. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/kernels/_stimulus.py +0 -0
  32. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/motion.py +0 -0
  33. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/response_context.py +0 -0
  34. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/response_maps.py +0 -0
  35. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/response_processing/__init__.py +0 -0
  36. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/response_processing/signals.py +0 -0
  37. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/response_window_options.py +0 -0
  38. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/timing.py +0 -0
  39. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/trials.py +0 -0
  40. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis/workflow.py +0 -0
  41. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/analysis_cache.py +0 -0
  42. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/api.py +0 -0
  43. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/cache_inventory.py +0 -0
  44. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/calibrations/pixel_calibration_profiles.csv +0 -0
  45. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/calibrations/pixel_size.csv +0 -0
  46. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/config.example.yml +0 -0
  47. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/config.py +0 -0
  48. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/conversion/__init__.py +0 -0
  49. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/conversion/alignment_crop.py +0 -0
  50. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/conversion/api.py +0 -0
  51. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/conversion/hdf5_writing.py +0 -0
  52. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/conversion/matlab_values.py +0 -0
  53. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/conversion/orientation.py +0 -0
  54. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/conversion/source_loading.py +0 -0
  55. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/conversion/stimulus_code.py +0 -0
  56. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/conversion/types.py +0 -0
  57. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/converted.py +0 -0
  58. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/custom/__init__.py +0 -0
  59. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/custom/context.py +0 -0
  60. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/custom/discovery.py +0 -0
  61. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/custom/metadata.py +0 -0
  62. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/custom/native.py +0 -0
  63. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/custom/native_workflows/__init__.py +0 -0
  64. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/custom/native_workflows/direction_selectivity.py +0 -0
  65. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/custom/native_workflows/response_kernels.py +0 -0
  66. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/custom/parameters.py +0 -0
  67. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/custom/provenance.py +0 -0
  68. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/custom/registry.py +0 -0
  69. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/custom/results.py +0 -0
  70. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/custom/runner.py +0 -0
  71. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/custom/workflows.py +0 -0
  72. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/database/__init__.py +0 -0
  73. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/database/cache.py +0 -0
  74. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/database/catalog.py +0 -0
  75. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/database/connection.py +0 -0
  76. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/database/date_filters.py +0 -0
  77. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/database/generic.py +0 -0
  78. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/database/modeled.py +0 -0
  79. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/database/search.py +0 -0
  80. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/database/sql.py +0 -0
  81. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/database/types.py +0 -0
  82. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/filenames.py +0 -0
  83. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/frame_ranges.py +0 -0
  84. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/hdf5_utils.py +0 -0
  85. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/inspection.py +0 -0
  86. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/matlab.py +0 -0
  87. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/__init__.py +0 -0
  88. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/__main__.py +0 -0
  89. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/assets/twopy-app-icon-128.png +0 -0
  90. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/assets/twopy-app-icon-16.png +0 -0
  91. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/assets/twopy-app-icon-256.png +0 -0
  92. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/assets/twopy-app-icon-32.png +0 -0
  93. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/assets/twopy-app-icon-512.png +0 -0
  94. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/assets/twopy-app-icon-64.png +0 -0
  95. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/assets/twopy-app-icon.icns +0 -0
  96. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/assets/twopy-app-icon.png +0 -0
  97. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/controls.py +0 -0
  98. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/custom_tab.py +0 -0
  99. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/database_favorite_editor.py +0 -0
  100. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/database_favorites.py +0 -0
  101. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/database_search.py +0 -0
  102. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/dims.py +0 -0
  103. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/display.py +0 -0
  104. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/display_paths.py +0 -0
  105. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/empty_state.py +0 -0
  106. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/errors.py +0 -0
  107. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/group_matching/__init__.py +0 -0
  108. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/group_matching/cards.py +0 -0
  109. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/group_matching/fov_assignment.py +0 -0
  110. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/group_matching/fov_cards.py +0 -0
  111. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/group_matching/images.py +0 -0
  112. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/group_matching/protocols.py +0 -0
  113. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/group_matching/response_preview.py +0 -0
  114. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/group_matching/responses.py +0 -0
  115. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/group_matching/roi_assignment.py +0 -0
  116. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/group_matching/roi_cards.py +0 -0
  117. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/group_matching/style.py +0 -0
  118. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/group_matching/tables.py +0 -0
  119. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/group_matching/window.py +0 -0
  120. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/interactive.py +0 -0
  121. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/latest_worker.py +0 -0
  122. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/launcher.py +0 -0
  123. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/live_analysis.py +0 -0
  124. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/load_controller.py +0 -0
  125. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/load_picker.py +0 -0
  126. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/load_progress.py +0 -0
  127. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/load_workflow.py +0 -0
  128. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/loaded_recordings_csv.py +0 -0
  129. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/loading.py +0 -0
  130. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/movie.py +0 -0
  131. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/output_routing.py +0 -0
  132. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/paths.py +0 -0
  133. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/__init__.py +0 -0
  134. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/data.py +0 -0
  135. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/dff_options.py +0 -0
  136. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/docks/__init__.py +0 -0
  137. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/docks/api.py +0 -0
  138. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/docks/dynamic_options.py +0 -0
  139. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/docks/options_panel.py +0 -0
  140. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/docks/paths.py +0 -0
  141. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/docks/plot_area.py +0 -0
  142. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/docks/response_map_area.py +0 -0
  143. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/docks/save_actions.py +0 -0
  144. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/docks/visibility_state.py +0 -0
  145. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/export.py +0 -0
  146. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/export_controls.py +0 -0
  147. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/form_controls.py +0 -0
  148. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/label_visibility.py +0 -0
  149. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/motion_summary.py +0 -0
  150. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/normalization_options.py +0 -0
  151. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/options.py +0 -0
  152. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/panels.py +0 -0
  153. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/preview_strip.py +0 -0
  154. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/processing_options.py +0 -0
  155. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/response_map_colors.py +0 -0
  156. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/response_map_display.py +0 -0
  157. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/response_map_options.py +0 -0
  158. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/response_window_options.py +0 -0
  159. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/roi_generation/__init__.py +0 -0
  160. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/roi_generation/actions.py +0 -0
  161. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/roi_generation/controls.py +0 -0
  162. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/roi_generation/options.py +0 -0
  163. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/plotting/widgets.py +0 -0
  164. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/protocols.py +0 -0
  165. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/responses.py +0 -0
  166. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/roi.py +0 -0
  167. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/roi_signatures.py +0 -0
  168. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/session.py +0 -0
  169. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/sidebar.py +0 -0
  170. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/state.py +0 -0
  171. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/text.py +0 -0
  172. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/theme.py +0 -0
  173. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/trial_timeline.py +0 -0
  174. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/types.py +0 -0
  175. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/version_check.py +0 -0
  176. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/napari/viewer.py +0 -0
  177. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/parity/__init__.py +0 -0
  178. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/parity/dff.py +0 -0
  179. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/parity/psycho5.py +0 -0
  180. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/parity/roi.py +0 -0
  181. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/parity/roi_extraction.py +0 -0
  182. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/parity/saved_analysis.py +0 -0
  183. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/photodiode.py +0 -0
  184. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/photodiode_classification.py +0 -0
  185. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/pixel_calibration.py +0 -0
  186. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/pixel_calibration_profiles.py +0 -0
  187. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/response_roi_extraction.py +0 -0
  188. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/roi.py +0 -0
  189. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/roi_extraction.py +0 -0
  190. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/roi_mask_cleanup.py +0 -0
  191. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/session.py +0 -0
  192. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/spatial.py +0 -0
  193. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/spatial_orientation.py +0 -0
  194. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/stimulus.py +0 -0
  195. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/synchronization.py +0 -0
  196. {twopy-0.4.1 → twopy-0.4.2}/src/twopy/typing_guards.py +0 -0
  197. {twopy-0.4.1 → twopy-0.4.2}/src/twopy.egg-info/dependency_links.txt +0 -0
  198. {twopy-0.4.1 → twopy-0.4.2}/src/twopy.egg-info/entry_points.txt +0 -0
  199. {twopy-0.4.1 → twopy-0.4.2}/src/twopy.egg-info/requires.txt +0 -0
  200. {twopy-0.4.1 → twopy-0.4.2}/src/twopy.egg-info/top_level.txt +0 -0
  201. {twopy-0.4.1 → twopy-0.4.2}/tests/test_api.py +0 -0
  202. {twopy-0.4.1 → twopy-0.4.2}/tests/test_background_subtraction.py +0 -0
  203. {twopy-0.4.1 → twopy-0.4.2}/tests/test_cache_inventory.py +0 -0
  204. {twopy-0.4.1 → twopy-0.4.2}/tests/test_config.py +0 -0
  205. {twopy-0.4.1 → twopy-0.4.2}/tests/test_conversion.py +0 -0
  206. {twopy-0.4.1 → twopy-0.4.2}/tests/test_converted.py +0 -0
  207. {twopy-0.4.1 → twopy-0.4.2}/tests/test_custom_workflows.py +0 -0
  208. {twopy-0.4.1 → twopy-0.4.2}/tests/test_database.py +0 -0
  209. {twopy-0.4.1 → twopy-0.4.2}/tests/test_database_date_filters.py +0 -0
  210. {twopy-0.4.1 → twopy-0.4.2}/tests/test_dff.py +0 -0
  211. {twopy-0.4.1 → twopy-0.4.2}/tests/test_epoch_mapping.py +0 -0
  212. {twopy-0.4.1 → twopy-0.4.2}/tests/test_filenames.py +0 -0
  213. {twopy-0.4.1 → twopy-0.4.2}/tests/test_frame_ranges.py +0 -0
  214. {twopy-0.4.1 → twopy-0.4.2}/tests/test_function_inventory.py +0 -0
  215. {twopy-0.4.1 → twopy-0.4.2}/tests/test_group_matching.py +0 -0
  216. {twopy-0.4.1 → twopy-0.4.2}/tests/test_hdf5_utils.py +0 -0
  217. {twopy-0.4.1 → twopy-0.4.2}/tests/test_import.py +0 -0
  218. {twopy-0.4.1 → twopy-0.4.2}/tests/test_inspection.py +0 -0
  219. {twopy-0.4.1 → twopy-0.4.2}/tests/test_kernels.py +0 -0
  220. {twopy-0.4.1 → twopy-0.4.2}/tests/test_load_workflow.py +0 -0
  221. {twopy-0.4.1 → twopy-0.4.2}/tests/test_matlab.py +0 -0
  222. {twopy-0.4.1 → twopy-0.4.2}/tests/test_motion.py +0 -0
  223. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari.py +0 -0
  224. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_database_search.py +0 -0
  225. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_display.py +0 -0
  226. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_export.py +0 -0
  227. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_group_matching_responses.py +0 -0
  228. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_group_matching_roi_cards.py +0 -0
  229. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_live_controller.py +0 -0
  230. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_load_controller.py +0 -0
  231. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_load_picker.py +0 -0
  232. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_load_progress.py +0 -0
  233. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_load_tab.py +0 -0
  234. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_loaded_recordings.py +0 -0
  235. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_path_resolution.py +0 -0
  236. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_plot_controls.py +0 -0
  237. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_plot_data.py +0 -0
  238. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_plot_widgets.py +0 -0
  239. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_processing_controls.py +0 -0
  240. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_response_map_display.py +0 -0
  241. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_roi_generation.py +0 -0
  242. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_roi_visibility.py +0 -0
  243. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_state.py +0 -0
  244. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_text.py +0 -0
  245. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_theme.py +0 -0
  246. {twopy-0.4.1 → twopy-0.4.2}/tests/test_napari_timeline.py +0 -0
  247. {twopy-0.4.1 → twopy-0.4.2}/tests/test_packaging.py +0 -0
  248. {twopy-0.4.1 → twopy-0.4.2}/tests/test_parity.py +0 -0
  249. {twopy-0.4.1 → twopy-0.4.2}/tests/test_parity_psycho5.py +0 -0
  250. {twopy-0.4.1 → twopy-0.4.2}/tests/test_parity_roi_extraction.py +0 -0
  251. {twopy-0.4.1 → twopy-0.4.2}/tests/test_persistence.py +0 -0
  252. {twopy-0.4.1 → twopy-0.4.2}/tests/test_photodiode_classification.py +0 -0
  253. {twopy-0.4.1 → twopy-0.4.2}/tests/test_pixel_calibration.py +0 -0
  254. {twopy-0.4.1 → twopy-0.4.2}/tests/test_pixel_calibration_profiles.py +0 -0
  255. {twopy-0.4.1 → twopy-0.4.2}/tests/test_real_fixture.py +0 -0
  256. {twopy-0.4.1 → twopy-0.4.2}/tests/test_recording_timing.py +0 -0
  257. {twopy-0.4.1 → twopy-0.4.2}/tests/test_response_map_area.py +0 -0
  258. {twopy-0.4.1 → twopy-0.4.2}/tests/test_response_maps.py +0 -0
  259. {twopy-0.4.1 → twopy-0.4.2}/tests/test_response_roi_extraction.py +0 -0
  260. {twopy-0.4.1 → twopy-0.4.2}/tests/test_response_window_options.py +0 -0
  261. {twopy-0.4.1 → twopy-0.4.2}/tests/test_responses.py +0 -0
  262. {twopy-0.4.1 → twopy-0.4.2}/tests/test_roi.py +0 -0
  263. {twopy-0.4.1 → twopy-0.4.2}/tests/test_roi_extraction.py +0 -0
  264. {twopy-0.4.1 → twopy-0.4.2}/tests/test_roi_mask_cleanup.py +0 -0
  265. {twopy-0.4.1 → twopy-0.4.2}/tests/test_saved_analysis.py +0 -0
  266. {twopy-0.4.1 → twopy-0.4.2}/tests/test_session.py +0 -0
  267. {twopy-0.4.1 → twopy-0.4.2}/tests/test_stimulus.py +0 -0
  268. {twopy-0.4.1 → twopy-0.4.2}/tests/test_synchronization.py +0 -0
  269. {twopy-0.4.1 → twopy-0.4.2}/tests/test_trials.py +0 -0
  270. {twopy-0.4.1 → twopy-0.4.2}/tests/test_version_check.py +0 -0
  271. {twopy-0.4.1 → twopy-0.4.2}/tests/test_workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: twopy
3
- Version: 0.4.1
3
+ Version: 0.4.2
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "twopy"
7
- version = "0.4.1"
7
+ version = "0.4.2"
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"
@@ -102,7 +102,7 @@ class LoadedAnalysisOutputs:
102
102
 
103
103
  Inputs: one file written by ``save_analysis_outputs``.
104
104
  Outputs: optional ROI, trace, dF/F, window, response objects, and a warning
105
- when an old file uses a setting twopy no longer uses.
105
+ when saved calculation rules are unknown or no longer supported.
106
106
 
107
107
  Groups are optional because scripts can save only the products they have
108
108
  computed. Missing groups load as ``None`` or an empty window tuple.
@@ -768,6 +768,9 @@ def _write_grouped_responses(
768
768
  trial_group.attrs["window_index"] = trial.window_index
769
769
  trial_group.attrs["start_frame"] = trial.start_frame
770
770
  trial_group.attrs["stop_frame"] = trial.stop_frame
771
+ if trial.epoch_frame_range is not None:
772
+ trial_group.attrs["epoch_start_frame"] = trial.epoch_frame_range[0]
773
+ trial_group.attrs["epoch_stop_frame"] = trial.epoch_frame_range[1]
771
774
  trial_group.create_dataset("frame_numbers", data=trial.frame_numbers)
772
775
  trial_group.create_dataset("time_seconds", data=trial.time_seconds)
773
776
  trial_group.create_dataset("values", data=trial.values, compression="gzip")
@@ -831,9 +834,28 @@ def _read_response_trial(group: h5py.Group) -> RoiResponseTrial:
831
834
  name="responses/trial/values",
832
835
  ndim=2,
833
836
  ),
837
+ epoch_frame_range=_read_trial_epoch_frame_range(group),
834
838
  )
835
839
 
836
840
 
841
+ def _read_trial_epoch_frame_range(group: h5py.Group) -> tuple[int, int] | None:
842
+ """Read exact epoch bounds, preserving unknown bounds in historical files."""
843
+ names = ("epoch_start_frame", "epoch_stop_frame")
844
+ if not any(name in group.attrs for name in names):
845
+ return None
846
+ bounds: list[int] = []
847
+ for name in names:
848
+ if name not in group.attrs:
849
+ msg = f"Saved response trial is missing {name}"
850
+ raise ValueError(msg)
851
+ value = plain_hdf5_attr_value(group.attrs[name], scalar_only=True)
852
+ if isinstance(value, bool) or not isinstance(value, int):
853
+ msg = f"Saved response trial {name} must be an integer"
854
+ raise ValueError(msg)
855
+ bounds.append(value)
856
+ return bounds[0], bounds[1]
857
+
858
+
837
859
  def _optional_float_attr(group: h5py.Group, name: str) -> float | None:
838
860
  """Read one optional float HDF5 attribute.
839
861
 
@@ -10,7 +10,7 @@ custom workflows can share the same response-plot data contract.
10
10
  """
11
11
 
12
12
  from collections.abc import Sequence
13
- from dataclasses import dataclass
13
+ from dataclasses import dataclass, replace
14
14
  from pathlib import Path
15
15
 
16
16
  import numpy as np
@@ -274,13 +274,11 @@ def _filter_correlation_scores(
274
274
  if scores is None:
275
275
  return None
276
276
  keep_rows = list(keep_indices)
277
- return RoiCorrelationScores(
277
+ return replace(
278
+ scores,
278
279
  roi_labels=tuple(scores.roi_labels[index] for index in keep_indices),
279
280
  scores=scores.scores[keep_rows],
280
281
  included_mask=scores.included_mask[keep_rows],
281
- minimum_correlation=scores.minimum_correlation,
282
- reference=scores.reference,
283
- window_seconds=scores.window_seconds,
284
282
  )
285
283
 
286
284
 
@@ -8,7 +8,7 @@ not know how options were selected, where outputs are saved, or how plots are
8
8
  drawn.
9
9
  """
10
10
 
11
- from dataclasses import dataclass
11
+ from dataclasses import dataclass, replace
12
12
 
13
13
  import numpy as np
14
14
  import numpy.typing as npt
@@ -198,7 +198,7 @@ def normalize_grouped_roi_responses_by_epoch_abs_peak(
198
198
  raise ValueError(msg)
199
199
  factors = _normalization_factors(grouped, options=options)
200
200
  trials = tuple(
201
- _copy_trial_with_values(trial, trial.values / factors.factors)
201
+ replace(trial, values=trial.values / factors.factors)
202
202
  for trial in grouped.trials
203
203
  )
204
204
  return (
@@ -313,9 +313,9 @@ def _copy_grouped_with_processed_values(
313
313
  ) -> GroupedRoiResponses:
314
314
  """Return grouped responses with each trial value matrix processed."""
315
315
  trials = tuple(
316
- _copy_trial_with_values(
316
+ replace(
317
317
  trial,
318
- _process_values(
318
+ values=_process_values(
319
319
  trial.values,
320
320
  options=options,
321
321
  data_rate_hz=grouped.data_rate_hz,
@@ -395,7 +395,7 @@ def _mask_excluded_rois(
395
395
  for trial in grouped.trials:
396
396
  values = trial.values.copy()
397
397
  values[:, ~included_mask] = np.nan
398
- trials.append(_copy_trial_with_values(trial, values))
398
+ trials.append(replace(trial, values=values))
399
399
  return GroupedRoiResponses(
400
400
  roi_labels=grouped.roi_labels,
401
401
  data_rate_hz=grouped.data_rate_hz,
@@ -406,24 +406,6 @@ def _mask_excluded_rois(
406
406
  )
407
407
 
408
408
 
409
- def _copy_trial_with_values(
410
- trial: RoiResponseTrial,
411
- values: npt.NDArray[np.float64],
412
- ) -> RoiResponseTrial:
413
- """Return one trial response with replacement values."""
414
- return RoiResponseTrial(
415
- epoch_number=trial.epoch_number,
416
- epoch_name=trial.epoch_name,
417
- trial_index=trial.trial_index,
418
- window_index=trial.window_index,
419
- start_frame=trial.start_frame,
420
- stop_frame=trial.stop_frame,
421
- frame_numbers=trial.frame_numbers,
422
- time_seconds=trial.time_seconds,
423
- values=values,
424
- )
425
-
426
-
427
409
  def _validate_dff_for_processing(dff: RoiDeltaFOverF) -> None:
428
410
  """Validate a dF/F object before value-only processing."""
429
411
  if dff.values.ndim != 2:
@@ -99,8 +99,9 @@ class CorrelationFilterOptions:
99
99
  available elsewhere in the analysis record.
100
100
 
101
101
  ``window_seconds`` is a half-open ``(start, stop)`` interval on each trial's
102
- epoch-relative time axis. ``None`` on the stop side leaves it unbounded, so
103
- the default starts at epoch onset and uses the full stored epoch response.
102
+ epoch-relative time axis. The window is intersected with each trial's exact
103
+ epoch bounds. ``None`` on the stop side uses the epoch end. Plot context
104
+ never contributes, even when the requested window extends beyond an epoch.
104
105
  """
105
106
 
106
107
  reference: CorrelationFilterReference = "none"
@@ -25,7 +25,10 @@ from twopy.analysis.response_processing.options import (
25
25
  SmoothingMethod,
26
26
  SmoothingOptions,
27
27
  )
28
- from twopy.analysis.response_processing.qc import RoiCorrelationScores
28
+ from twopy.analysis.response_processing.qc import (
29
+ CORRELATION_SCORING_RULE,
30
+ RoiCorrelationScores,
31
+ )
29
32
  from twopy.hdf5_utils import (
30
33
  plain_hdf5_attr_value,
31
34
  write_string_dataset,
@@ -65,7 +68,7 @@ class PersistedResponseProcessing:
65
68
 
66
69
  Inputs: one ``response_processing`` HDF5 group.
67
70
  Outputs: typed options plus optional factors, ROI correlation scores, and a
68
- warning when an old file uses a setting twopy no longer uses.
71
+ warning when saved calculation rules are unknown or no longer supported.
69
72
  """
70
73
 
71
74
  options: ResponseProcessingOptions
@@ -137,6 +140,8 @@ def write_response_processing_group(
137
140
  correlation_scores.minimum_correlation
138
141
  )
139
142
  scores_group.attrs["reference"] = correlation_scores.reference
143
+ if correlation_scores.scoring_rule is not None:
144
+ scores_group.attrs["scoring_rule"] = correlation_scores.scoring_rule
140
145
  scores_group.attrs["window_start_seconds"] = _optional_float_attr(
141
146
  correlation_scores.window_seconds[0],
142
147
  )
@@ -175,6 +180,21 @@ def read_response_processing_group(group: h5py.Group) -> PersistedResponseProces
175
180
  group["correlation_filter"],
176
181
  ),
177
182
  )
183
+ correlation_scores = (
184
+ _read_correlation_scores(group["correlation_scores"])
185
+ if "correlation_scores" in group
186
+ else None
187
+ )
188
+ warnings = [normalization_load_warning] if normalization_load_warning else []
189
+ if (
190
+ correlation_scores is not None
191
+ and correlation_scores.scoring_rule != CORRELATION_SCORING_RULE
192
+ ):
193
+ warnings.append(
194
+ "The scoring rule for these saved correlation scores is missing or "
195
+ "not supported. Twopy is showing the saved responses as stored. "
196
+ "Recompute responses to use stimulus-only correlation QC."
197
+ )
178
198
  return PersistedResponseProcessing(
179
199
  options=options,
180
200
  normalization_factors=(
@@ -189,12 +209,8 @@ def read_response_processing_group(group: h5py.Group) -> PersistedResponseProces
189
209
  )
190
210
  else None
191
211
  ),
192
- correlation_scores=(
193
- _read_correlation_scores(group["correlation_scores"])
194
- if "correlation_scores" in group
195
- else None
196
- ),
197
- load_warning=normalization_load_warning,
212
+ correlation_scores=correlation_scores,
213
+ load_warning=" ".join(warnings) or None,
198
214
  )
199
215
 
200
216
 
@@ -289,6 +305,13 @@ def _read_normalization_factors(
289
305
 
290
306
  def _read_correlation_scores(group: h5py.Group) -> RoiCorrelationScores:
291
307
  """Read persisted ROI correlation QC scores."""
308
+ scoring_rule = None
309
+ if "scoring_rule" in group.attrs:
310
+ value = plain_hdf5_attr_value(group.attrs["scoring_rule"], scalar_only=True)
311
+ if not isinstance(value, str) or not value.strip():
312
+ msg = "Saved correlation scoring_rule must be a non-empty string"
313
+ raise ValueError(msg)
314
+ scoring_rule = value
292
315
  return RoiCorrelationScores(
293
316
  roi_labels=_read_string_dataset(group, "roi_labels"),
294
317
  scores=require_float64_array(
@@ -308,6 +331,7 @@ def _read_correlation_scores(group: h5py.Group) -> RoiCorrelationScores:
308
331
  allowed=_CORRELATION_REFERENCES,
309
332
  ),
310
333
  window_seconds=_window_seconds_from_attrs(group),
334
+ scoring_rule=scoring_rule,
311
335
  )
312
336
 
313
337
 
@@ -22,12 +22,16 @@ from twopy.analysis.responses import (
22
22
  RoiResponseTrial,
23
23
  validate_grouped_roi_responses,
24
24
  )
25
+ from twopy.analysis.trials import is_baseline_epoch_name
25
26
 
26
27
  __all__ = [
28
+ "CORRELATION_SCORING_RULE",
27
29
  "RoiCorrelationScores",
28
30
  "score_roi_correlations",
29
31
  ]
30
32
 
33
+ CORRELATION_SCORING_RULE = "stimulus_frames_without_interleave"
34
+
31
35
 
32
36
  @dataclass(frozen=True)
33
37
  class RoiCorrelationScores:
@@ -36,8 +40,12 @@ class RoiCorrelationScores:
36
40
  Inputs: grouped responses, a reference strategy, and a time window.
37
41
  Outputs: one score per ROI plus a Boolean inclusion mask.
38
42
 
39
- Scores are averaged across repeated epoch trials. ROIs with no estimable
40
- score are excluded when correlation filtering is enabled.
43
+ Scores average trial correlations across epochs except gray, grey, and
44
+ interleave epochs. Blank epochs contribute. ROIs with no estimable score
45
+ are excluded when correlation filtering is enabled.
46
+
47
+ ``scoring_rule`` identifies the calculation that produced these scores.
48
+ ``None`` marks scores whose calculation rule is unknown.
41
49
  """
42
50
 
43
51
  roi_labels: tuple[str, ...]
@@ -46,6 +54,7 @@ class RoiCorrelationScores:
46
54
  minimum_correlation: float
47
55
  reference: CorrelationFilterReference
48
56
  window_seconds: CorrelationWindowSeconds
57
+ scoring_rule: str | None = None
49
58
 
50
59
 
51
60
  def score_roi_correlations(
@@ -58,7 +67,8 @@ def score_roi_correlations(
58
67
  """Score each ROI by correlation to repeated epoch responses.
59
68
 
60
69
  Args:
61
- grouped: Trial responses grouped by stimulus epoch.
70
+ grouped: Trial responses grouped by stimulus epoch, with exact
71
+ ``epoch_frame_range`` bounds on each scored trial.
62
72
  minimum_correlation: Minimum score required for inclusion.
63
73
  reference: Reference strategy. ``"epoch_mean"`` compares each trial to
64
74
  the other trials' mean. ``"epoch_peak"`` compares each non-peak
@@ -71,11 +81,15 @@ def score_roi_correlations(
71
81
 
72
82
  Raises:
73
83
  ValueError: If grouped response shapes, threshold, or reference are
74
- invalid.
84
+ invalid, or a scored trial has no exact epoch bounds.
75
85
 
76
- Correlations are computed only over samples inside ``window_seconds``. Mean
86
+ Correlations use only original epoch frames inside ``window_seconds``.
87
+ Pre- and post-stimulus plot context never contributes. Mean
77
88
  references are leave-one-out when an epoch has more than one trial, which
78
89
  avoids scoring a trial against itself.
90
+
91
+ Gray, grey, and interleave epochs do not contribute to either reference
92
+ strategy. Blank epochs contribute correlations within their own trial type.
79
93
  """
80
94
  _validate_correlation_inputs(grouped, minimum_correlation, reference)
81
95
  correlations: list[list[float]] = [[] for _ in grouped.roi_labels]
@@ -102,6 +116,7 @@ def score_roi_correlations(
102
116
  minimum_correlation=float(minimum_correlation),
103
117
  reference=reference,
104
118
  window_seconds=window_seconds,
119
+ scoring_rule=CORRELATION_SCORING_RULE,
105
120
  )
106
121
 
107
122
 
@@ -190,9 +205,12 @@ def _validate_correlation_inputs(
190
205
  def _trials_by_epoch(
191
206
  grouped: GroupedRoiResponses,
192
207
  ) -> dict[tuple[int, str], list[RoiResponseTrial]]:
193
- """Group trials by stable epoch identity."""
208
+ """Group scored trials by epoch, excluding gray and interleave epochs."""
194
209
  by_epoch: dict[tuple[int, str], list[RoiResponseTrial]] = {}
195
210
  for trial in grouped.trials:
211
+ # Interleave responses do not measure stimulus response consistency.
212
+ if is_baseline_epoch_name(trial.epoch_name):
213
+ continue
196
214
  by_epoch.setdefault((trial.epoch_number, trial.epoch_name), []).append(trial)
197
215
  return by_epoch
198
216
 
@@ -201,23 +219,30 @@ def _aligned_epoch_cube(
201
219
  trials: list[RoiResponseTrial],
202
220
  grouped: GroupedRoiResponses,
203
221
  ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
204
- """Return trials aligned onto one relative-time frame axis."""
205
- first_time = min(float(trial.time_seconds[0]) for trial in trials)
206
- last_time = max(float(trial.time_seconds[-1]) for trial in trials)
207
- frame_count = int(round((last_time - first_time) * grouped.data_rate_hz)) + 1
222
+ """Align original epoch frames without pre- or post-stimulus plot context."""
223
+ bounds = []
224
+ for trial in trials:
225
+ if trial.epoch_frame_range is None:
226
+ msg = (
227
+ "Correlation QC requires epoch_frame_range on each scored trial. "
228
+ "Recompute responses from epoch windows."
229
+ )
230
+ raise ValueError(msg)
231
+ bounds.append(trial.epoch_frame_range)
232
+ frame_count = max(stop - start for start, stop in bounds)
208
233
  cube = np.full(
209
234
  (len(trials), frame_count, len(grouped.roi_labels)),
210
235
  np.nan,
211
236
  dtype=np.float64,
212
237
  )
213
- for trial_index, trial in enumerate(trials):
214
- offsets = np.rint(
215
- (trial.time_seconds - first_time) * grouped.data_rate_hz
216
- ).astype(np.int64, copy=False)
217
- cube[trial_index, offsets, :] = trial.values
218
- time_seconds = first_time + (
219
- np.arange(frame_count, dtype=np.float64) / grouped.data_rate_hz
220
- )
238
+ for trial_index, (trial, (start, stop)) in enumerate(
239
+ zip(trials, bounds, strict=True)
240
+ ):
241
+ # Each trial contributes only its own epoch, even when durations differ.
242
+ cube[trial_index, : stop - start, :] = trial.values[
243
+ start - trial.start_frame : stop - trial.start_frame, :
244
+ ]
245
+ time_seconds = np.arange(frame_count, dtype=np.float64) / grouped.data_rate_hz
221
246
  return cube, time_seconds
222
247
 
223
248
 
@@ -41,6 +41,9 @@ class RoiResponseTrial:
41
41
 
42
42
  ``trial_index`` is one-based within each epoch because that is the number a
43
43
  scientist naturally uses when checking repeated presentations.
44
+
45
+ ``epoch_frame_range`` stores the original half-open epoch bounds without
46
+ plot context. ``None`` marks unknown bounds and prevents correlation QC.
44
47
  """
45
48
 
46
49
  epoch_number: int
@@ -52,6 +55,7 @@ class RoiResponseTrial:
52
55
  frame_numbers: npt.NDArray[np.int64]
53
56
  time_seconds: npt.NDArray[np.float64]
54
57
  values: npt.NDArray[np.float64]
58
+ epoch_frame_range: tuple[int, int] | None = None
55
59
 
56
60
 
57
61
  @dataclass(frozen=True)
@@ -203,6 +207,7 @@ def group_delta_f_over_f_by_epoch(
203
207
  frame_numbers=frame_numbers,
204
208
  time_seconds=time_seconds.astype(np.float64, copy=False),
205
209
  values=dff.values[local_start:local_stop, :],
210
+ epoch_frame_range=(window.start_frame, window.stop_frame),
206
211
  ),
207
212
  )
208
213
 
@@ -347,11 +352,11 @@ def summarize_roi_response_trials(
347
352
  if len(trials) == 0:
348
353
  msg = "At least one trial is required for response trace summary"
349
354
  raise ValueError(msg)
350
- for trial in trials:
351
- _validate_grouped_response_trial(trial, roi_labels)
352
355
  if data_rate_hz <= 0:
353
356
  msg = f"data_rate_hz must be positive. Got {data_rate_hz}"
354
357
  raise ValueError(msg)
358
+ for trial in trials:
359
+ _validate_grouped_response_trial(trial, roi_labels, data_rate_hz)
355
360
 
356
361
  trial_tuple = tuple(trials)
357
362
  time_seconds = _shared_trial_time_axis(trial_tuple, data_rate_hz)
@@ -429,12 +434,15 @@ def validate_grouped_roi_responses(grouped: GroupedRoiResponses) -> None:
429
434
  msg = f"data_rate_hz must be positive. Got {grouped.data_rate_hz}"
430
435
  raise ValueError(msg)
431
436
  for trial in grouped.trials:
432
- _validate_grouped_response_trial(trial, grouped.roi_labels)
437
+ _validate_grouped_response_trial(
438
+ trial, grouped.roi_labels, grouped.data_rate_hz
439
+ )
433
440
 
434
441
 
435
442
  def _validate_grouped_response_trial(
436
443
  trial: RoiResponseTrial,
437
444
  roi_labels: tuple[str, ...],
445
+ data_rate_hz: float,
438
446
  ) -> None:
439
447
  """Validate one grouped response trial against shared ROI labels."""
440
448
  if not np.issubdtype(trial.values.dtype, np.floating):
@@ -507,6 +515,21 @@ def _validate_grouped_response_trial(
507
515
  if not np.all(np.isfinite(trial.time_seconds)):
508
516
  msg = "trial time_seconds must be finite"
509
517
  raise ValueError(msg)
518
+ if trial.epoch_frame_range is not None:
519
+ if any(
520
+ isinstance(bound, bool) or not isinstance(bound, (int, np.integer))
521
+ for bound in trial.epoch_frame_range
522
+ ):
523
+ msg = "trial epoch_frame_range bounds must be integers"
524
+ raise ValueError(msg)
525
+ epoch_start, epoch_stop = trial.epoch_frame_range
526
+ if not trial.start_frame <= epoch_start < epoch_stop <= trial.stop_frame:
527
+ msg = "trial epoch_frame_range must lie within the response frame range"
528
+ raise ValueError(msg)
529
+ expected_time = (trial.frame_numbers - epoch_start) / data_rate_hz
530
+ if not np.allclose(trial.time_seconds, expected_time):
531
+ msg = "trial time_seconds must be relative to epoch_frame_range start"
532
+ raise ValueError(msg)
510
533
 
511
534
 
512
535
  def _validate_grouping_inputs(
@@ -1047,10 +1047,12 @@ class _ResponsePlotWidget(QWidget):
1047
1047
  return
1048
1048
  roi_error = self._reload_saved_rois()
1049
1049
  self.set_response_plot_data(result, reset_axes=True)
1050
+ messages = ["Reloaded saved analysis."]
1051
+ if result.load_warning is not None:
1052
+ messages.append(result.load_warning)
1050
1053
  if roi_error is not None:
1051
- self._update_status_label.setText(f"Reloaded saved analysis. {roi_error}")
1052
- elif result.load_warning is None:
1053
- self._update_status_label.setText("Reloaded saved analysis.")
1054
+ messages.append(roi_error)
1055
+ self._update_status_label.setText(" ".join(messages))
1054
1056
 
1055
1057
  def _reload_saved_rois(self) -> str | None:
1056
1058
  """Replace the active Labels layer with the saved ROI HDF5 file."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: twopy
3
- Version: 0.4.1
3
+ Version: 0.4.2
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
@@ -198,6 +198,7 @@ tests/test_cache_inventory.py
198
198
  tests/test_config.py
199
199
  tests/test_conversion.py
200
200
  tests/test_converted.py
201
+ tests/test_correlation_qc.py
201
202
  tests/test_custom_workflows.py
202
203
  tests/test_database.py
203
204
  tests/test_database_date_filters.py
@@ -215,6 +216,7 @@ tests/test_load_workflow.py
215
216
  tests/test_matlab.py
216
217
  tests/test_motion.py
217
218
  tests/test_napari.py
219
+ tests/test_napari_correlation_qc.py
218
220
  tests/test_napari_database_search.py
219
221
  tests/test_napari_display.py
220
222
  tests/test_napari_export.py