TwoPy 0.2.1__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. {twopy-0.2.1 → twopy-0.3.0}/PKG-INFO +1 -1
  2. {twopy-0.2.1 → twopy-0.3.0}/pyproject.toml +1 -1
  3. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/persistence.py +18 -3
  4. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_maps.py +57 -7
  5. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis_cache.py +84 -22
  6. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/conversion/alignment_crop.py +18 -13
  7. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/conversion/api.py +21 -5
  8. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/conversion/hdf5_writing.py +27 -4
  9. twopy-0.3.0/src/twopy/conversion/orientation.py +80 -0
  10. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/conversion/types.py +1 -1
  11. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/converted.py +26 -3
  12. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/__init__.py +6 -0
  13. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/parameters.py +4 -3
  14. twopy-0.3.0/src/twopy/custom/runner.py +244 -0
  15. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/controls.py +81 -15
  16. twopy-0.3.0/src/twopy/napari/display.py +106 -0
  17. twopy-0.3.0/src/twopy/napari/load_controller.py +400 -0
  18. twopy-0.3.0/src/twopy/napari/load_progress.py +168 -0
  19. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/load_workflow.py +225 -83
  20. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/loaded_recordings_csv.py +35 -4
  21. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/loading.py +22 -2
  22. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/dynamic_options.py +60 -14
  23. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/response_map_area.py +5 -8
  24. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/response_plot_widget.py +68 -84
  25. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/export.py +11 -19
  26. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/roi.py +91 -22
  27. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/viewer.py +206 -67
  28. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/roi.py +26 -5
  29. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/spatial.py +3 -3
  30. twopy-0.3.0/src/twopy/spatial_orientation.py +78 -0
  31. {twopy-0.2.1 → twopy-0.3.0}/src/twopy.egg-info/PKG-INFO +1 -1
  32. {twopy-0.2.1 → twopy-0.3.0}/src/twopy.egg-info/SOURCES.txt +6 -0
  33. {twopy-0.2.1 → twopy-0.3.0}/tests/test_background_subtraction.py +22 -1
  34. {twopy-0.2.1 → twopy-0.3.0}/tests/test_conversion.py +113 -10
  35. {twopy-0.2.1 → twopy-0.3.0}/tests/test_converted.py +64 -2
  36. {twopy-0.2.1 → twopy-0.3.0}/tests/test_custom_workflows.py +156 -1
  37. {twopy-0.2.1 → twopy-0.3.0}/tests/test_function_inventory.py +45 -4
  38. {twopy-0.2.1 → twopy-0.3.0}/tests/test_kernels.py +125 -1
  39. {twopy-0.2.1 → twopy-0.3.0}/tests/test_load_workflow.py +58 -51
  40. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari.py +54 -40
  41. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_display.py +12 -31
  42. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_export.py +5 -3
  43. twopy-0.3.0/tests/test_napari_load_progress.py +48 -0
  44. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_load_tab.py +19 -5
  45. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_loaded_recordings.py +10 -0
  46. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_path_resolution.py +2 -2
  47. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_plot_widgets.py +2 -2
  48. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_response_workflow.py +4 -2
  49. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_roi_generation.py +76 -3
  50. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_roi_visibility.py +9 -1
  51. {twopy-0.2.1 → twopy-0.3.0}/tests/test_persistence.py +35 -1
  52. {twopy-0.2.1 → twopy-0.3.0}/tests/test_response_maps.py +115 -11
  53. {twopy-0.2.1 → twopy-0.3.0}/tests/test_roi.py +31 -1
  54. twopy-0.2.1/src/twopy/napari/display.py +0 -181
  55. {twopy-0.2.1 → twopy-0.3.0}/README.md +0 -0
  56. {twopy-0.2.1 → twopy-0.3.0}/setup.cfg +0 -0
  57. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/__init__.py +0 -0
  58. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/_segments.py +0 -0
  59. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/_version.py +0 -0
  60. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/__init__.py +0 -0
  61. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/background_subtraction.py +0 -0
  62. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/dff.py +0 -0
  63. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/dff_options.py +0 -0
  64. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/epoch_mapping.py +0 -0
  65. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/group_matching.py +0 -0
  66. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/kernels/__init__.py +0 -0
  67. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/kernels/_fitting.py +0 -0
  68. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/kernels/_stimulus.py +0 -0
  69. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/motion.py +0 -0
  70. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_context.py +0 -0
  71. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_processing/__init__.py +0 -0
  72. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_processing/apply.py +0 -0
  73. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_processing/options.py +0 -0
  74. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_processing/persistence.py +0 -0
  75. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_processing/qc.py +0 -0
  76. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_processing/signals.py +0 -0
  77. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_window_options.py +0 -0
  78. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/responses.py +0 -0
  79. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/timing.py +0 -0
  80. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/trials.py +0 -0
  81. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/workflow.py +0 -0
  82. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/api.py +0 -0
  83. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/calibrations/pixel_calibration_profiles.csv +0 -0
  84. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/calibrations/pixel_size.csv +0 -0
  85. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/config.py +0 -0
  86. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/conversion/__init__.py +0 -0
  87. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/conversion/matlab_values.py +0 -0
  88. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/conversion/source_loading.py +0 -0
  89. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/conversion/stimulus_code.py +0 -0
  90. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/discovery.py +0 -0
  91. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/native.py +0 -0
  92. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/native_workflows/__init__.py +0 -0
  93. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/native_workflows/direction_selectivity.py +0 -0
  94. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/native_workflows/response_kernels.py +0 -0
  95. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/provenance.py +0 -0
  96. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/registry.py +0 -0
  97. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/types.py +0 -0
  98. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/__init__.py +0 -0
  99. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/cache.py +0 -0
  100. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/catalog.py +0 -0
  101. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/connection.py +0 -0
  102. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/generic.py +0 -0
  103. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/modeled.py +0 -0
  104. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/search.py +0 -0
  105. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/sql.py +0 -0
  106. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/types.py +0 -0
  107. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/filenames.py +0 -0
  108. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/frame_ranges.py +0 -0
  109. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/hdf5_utils.py +0 -0
  110. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/inspection.py +0 -0
  111. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/matlab.py +0 -0
  112. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/__init__.py +0 -0
  113. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/__main__.py +0 -0
  114. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/custom_tab.py +0 -0
  115. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/database_favorites.py +0 -0
  116. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/database_search.py +0 -0
  117. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/dims.py +0 -0
  118. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/display_paths.py +0 -0
  119. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/errors.py +0 -0
  120. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/__init__.py +0 -0
  121. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/cards.py +0 -0
  122. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/fov_assignment.py +0 -0
  123. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/fov_cards.py +0 -0
  124. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/images.py +0 -0
  125. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/response_preview.py +0 -0
  126. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/responses.py +0 -0
  127. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/roi_assignment.py +0 -0
  128. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/roi_cards.py +0 -0
  129. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/style.py +0 -0
  130. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/tables.py +0 -0
  131. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/window.py +0 -0
  132. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/interactive.py +0 -0
  133. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/latest_worker.py +0 -0
  134. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/launcher.py +0 -0
  135. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/live_analysis.py +0 -0
  136. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/movie.py +0 -0
  137. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/paths.py +0 -0
  138. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/__init__.py +0 -0
  139. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/data.py +0 -0
  140. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/dff_options.py +0 -0
  141. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/__init__.py +0 -0
  142. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/api.py +0 -0
  143. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/options_panel.py +0 -0
  144. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/paths.py +0 -0
  145. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/plot_area.py +0 -0
  146. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/save_actions.py +0 -0
  147. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/visibility_state.py +0 -0
  148. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/export_controls.py +0 -0
  149. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/form_controls.py +0 -0
  150. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/label_visibility.py +0 -0
  151. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/normalization_options.py +0 -0
  152. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/options.py +0 -0
  153. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/panels.py +0 -0
  154. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/preview_strip.py +0 -0
  155. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/processing_options.py +0 -0
  156. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/response_map_colors.py +0 -0
  157. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/response_map_display.py +0 -0
  158. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/response_map_options.py +0 -0
  159. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/response_window_options.py +0 -0
  160. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/roi_generation/__init__.py +0 -0
  161. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/roi_generation/actions.py +0 -0
  162. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/roi_generation/controls.py +0 -0
  163. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/roi_generation/options.py +0 -0
  164. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/widgets.py +0 -0
  165. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/protocols.py +0 -0
  166. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/responses.py +0 -0
  167. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/roi_signatures.py +0 -0
  168. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/session.py +0 -0
  169. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/sidebar.py +0 -0
  170. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/state.py +0 -0
  171. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/text.py +0 -0
  172. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/trial_timeline.py +0 -0
  173. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/types.py +0 -0
  174. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/parity/__init__.py +0 -0
  175. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/parity/dff.py +0 -0
  176. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/parity/psycho5.py +0 -0
  177. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/parity/roi.py +0 -0
  178. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/parity/roi_extraction.py +0 -0
  179. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/parity/saved_analysis.py +0 -0
  180. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/photodiode.py +0 -0
  181. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/photodiode_classification.py +0 -0
  182. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/pixel_calibration.py +0 -0
  183. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/pixel_calibration_profiles.py +0 -0
  184. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/response_roi_extraction.py +0 -0
  185. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/roi_extraction.py +0 -0
  186. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/roi_mask_cleanup.py +0 -0
  187. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/session.py +0 -0
  188. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/stimulus.py +0 -0
  189. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/synchronization.py +0 -0
  190. {twopy-0.2.1 → twopy-0.3.0}/src/twopy/typing_guards.py +0 -0
  191. {twopy-0.2.1 → twopy-0.3.0}/src/twopy.egg-info/dependency_links.txt +0 -0
  192. {twopy-0.2.1 → twopy-0.3.0}/src/twopy.egg-info/entry_points.txt +0 -0
  193. {twopy-0.2.1 → twopy-0.3.0}/src/twopy.egg-info/requires.txt +0 -0
  194. {twopy-0.2.1 → twopy-0.3.0}/src/twopy.egg-info/top_level.txt +0 -0
  195. {twopy-0.2.1 → twopy-0.3.0}/tests/test_api.py +0 -0
  196. {twopy-0.2.1 → twopy-0.3.0}/tests/test_config.py +0 -0
  197. {twopy-0.2.1 → twopy-0.3.0}/tests/test_database.py +0 -0
  198. {twopy-0.2.1 → twopy-0.3.0}/tests/test_dff.py +0 -0
  199. {twopy-0.2.1 → twopy-0.3.0}/tests/test_epoch_mapping.py +0 -0
  200. {twopy-0.2.1 → twopy-0.3.0}/tests/test_filenames.py +0 -0
  201. {twopy-0.2.1 → twopy-0.3.0}/tests/test_frame_ranges.py +0 -0
  202. {twopy-0.2.1 → twopy-0.3.0}/tests/test_group_matching.py +0 -0
  203. {twopy-0.2.1 → twopy-0.3.0}/tests/test_hdf5_utils.py +0 -0
  204. {twopy-0.2.1 → twopy-0.3.0}/tests/test_import.py +0 -0
  205. {twopy-0.2.1 → twopy-0.3.0}/tests/test_inspection.py +0 -0
  206. {twopy-0.2.1 → twopy-0.3.0}/tests/test_matlab.py +0 -0
  207. {twopy-0.2.1 → twopy-0.3.0}/tests/test_motion.py +0 -0
  208. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_database_search.py +0 -0
  209. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_group_matching_responses.py +0 -0
  210. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_group_matching_roi_cards.py +0 -0
  211. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_live_controller.py +0 -0
  212. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_plot_controls.py +0 -0
  213. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_plot_data.py +0 -0
  214. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_processing_controls.py +0 -0
  215. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_response_map_display.py +0 -0
  216. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_text.py +0 -0
  217. {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_timeline.py +0 -0
  218. {twopy-0.2.1 → twopy-0.3.0}/tests/test_packaging.py +0 -0
  219. {twopy-0.2.1 → twopy-0.3.0}/tests/test_parity.py +0 -0
  220. {twopy-0.2.1 → twopy-0.3.0}/tests/test_parity_psycho5.py +0 -0
  221. {twopy-0.2.1 → twopy-0.3.0}/tests/test_parity_roi_extraction.py +0 -0
  222. {twopy-0.2.1 → twopy-0.3.0}/tests/test_photodiode_classification.py +0 -0
  223. {twopy-0.2.1 → twopy-0.3.0}/tests/test_pixel_calibration.py +0 -0
  224. {twopy-0.2.1 → twopy-0.3.0}/tests/test_pixel_calibration_profiles.py +0 -0
  225. {twopy-0.2.1 → twopy-0.3.0}/tests/test_real_fixture.py +0 -0
  226. {twopy-0.2.1 → twopy-0.3.0}/tests/test_recording_timing.py +0 -0
  227. {twopy-0.2.1 → twopy-0.3.0}/tests/test_response_map_area.py +0 -0
  228. {twopy-0.2.1 → twopy-0.3.0}/tests/test_response_processing.py +0 -0
  229. {twopy-0.2.1 → twopy-0.3.0}/tests/test_response_roi_extraction.py +0 -0
  230. {twopy-0.2.1 → twopy-0.3.0}/tests/test_response_window_options.py +0 -0
  231. {twopy-0.2.1 → twopy-0.3.0}/tests/test_responses.py +0 -0
  232. {twopy-0.2.1 → twopy-0.3.0}/tests/test_roi_extraction.py +0 -0
  233. {twopy-0.2.1 → twopy-0.3.0}/tests/test_roi_mask_cleanup.py +0 -0
  234. {twopy-0.2.1 → twopy-0.3.0}/tests/test_saved_analysis.py +0 -0
  235. {twopy-0.2.1 → twopy-0.3.0}/tests/test_session.py +0 -0
  236. {twopy-0.2.1 → twopy-0.3.0}/tests/test_stimulus.py +0 -0
  237. {twopy-0.2.1 → twopy-0.3.0}/tests/test_synchronization.py +0 -0
  238. {twopy-0.2.1 → twopy-0.3.0}/tests/test_trials.py +0 -0
  239. {twopy-0.2.1 → twopy-0.3.0}/tests/test_workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: twopy
3
- Version: 0.2.1
3
+ Version: 0.3.0
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.2.1"
7
+ version = "0.3.0"
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"
@@ -48,6 +48,10 @@ from twopy.hdf5_utils import (
48
48
  write_string_dataset as _write_string_dataset,
49
49
  )
50
50
  from twopy.roi import RoiSet, TraceStatistic, make_roi_set
51
+ from twopy.spatial_orientation import (
52
+ require_twopy_spatial_orientation,
53
+ write_twopy_spatial_orientation,
54
+ )
51
55
  from twopy.typing_guards import (
52
56
  require_bool_array,
53
57
  require_float64_array,
@@ -238,7 +242,9 @@ def load_analysis_outputs(path: Path) -> LoadedAnalysisOutputs:
238
242
  return LoadedAnalysisOutputs(
239
243
  path=input_path,
240
244
  roi_set=(
241
- _read_roi_set(h5_file["roi_set"]) if "roi_set" in h5_file else None
245
+ _read_roi_set(h5_file["roi_set"], path=input_path)
246
+ if "roi_set" in h5_file
247
+ else None
242
248
  ),
243
249
  traces=(
244
250
  _read_background_traces(h5_file["traces"])
@@ -347,19 +353,28 @@ def _write_roi_set(group: h5py.Group, roi_set: RoiSet) -> None:
347
353
  Returns:
348
354
  None.
349
355
  """
350
- group.create_dataset("masks", data=roi_set.masks, compression="gzip")
356
+ write_twopy_spatial_orientation(group)
357
+ masks = group.create_dataset("masks", data=roi_set.masks, compression="gzip")
358
+ write_twopy_spatial_orientation(masks)
351
359
  _write_string_dataset(group, "labels", roi_set.labels)
352
360
 
353
361
 
354
- def _read_roi_set(group: h5py.Group) -> RoiSet:
362
+ def _read_roi_set(group: h5py.Group, *, path: Path) -> RoiSet:
355
363
  """Read a persisted ROI set.
356
364
 
357
365
  Args:
358
366
  group: HDF5 ``roi_set`` group.
367
+ path: Analysis output path used in validation errors.
359
368
 
360
369
  Returns:
361
370
  ``RoiSet`` with validated masks and labels.
362
371
  """
372
+ require_twopy_spatial_orientation(group, path=path, label="roi_set")
373
+ require_twopy_spatial_orientation(
374
+ group["masks"],
375
+ path=path,
376
+ label="roi_set/masks",
377
+ )
363
378
  return make_roi_set(
364
379
  require_bool_array(group["masks"][()], name="roi_set/masks", ndim=3),
365
380
  labels=_read_string_dataset(group, "labels"),
@@ -43,6 +43,10 @@ from twopy.analysis.timing import resolve_recording_timing
43
43
  from twopy.analysis.trials import EpochFrameWindow, is_baseline_epoch_name
44
44
  from twopy.converted import RecordingData, recording_frame_rate_hz
45
45
  from twopy.spatial import SpatialCrop
46
+ from twopy.spatial_orientation import (
47
+ require_twopy_spatial_orientation,
48
+ write_twopy_spatial_orientation,
49
+ )
46
50
 
47
51
  __all__ = [
48
52
  "EpochResponseMap",
@@ -247,29 +251,33 @@ def save_response_map_data(path: Path, map_data: ResponseMapData) -> None:
247
251
  recording-level movie products. Values are stored as normalized signed maps
248
252
  with the original dF/F scale in ``response_scale`` for audit.
249
253
  """
254
+ _require_response_map_shapes(map_data)
250
255
  output_path = path.expanduser()
251
256
  output_path.parent.mkdir(parents=True, exist_ok=True)
252
257
  with h5py.File(output_path, "w") as h5_file:
253
258
  h5_file.attrs["twopy_format"] = RESPONSE_MAP_FILE_FORMAT
254
259
  h5_file.attrs["response_scale"] = map_data.response_scale
260
+ write_twopy_spatial_orientation(h5_file)
255
261
  _write_response_map_options(h5_file.create_group("options"), map_data.options)
256
262
  _write_spatial_crop(h5_file.create_group("spatial_crop"), map_data.spatial_crop)
257
- h5_file.create_dataset(
263
+ mean_image = h5_file.create_dataset(
258
264
  "mean_image",
259
265
  data=map_data.mean_image,
260
266
  compression="gzip",
261
267
  )
268
+ write_twopy_spatial_orientation(mean_image)
262
269
  epochs_group = h5_file.create_group("epochs")
263
270
  for index, epoch in enumerate(map_data.epochs):
264
271
  epoch_group = epochs_group.create_group(f"{index:04d}")
265
272
  epoch_group.attrs["epoch_name"] = epoch.epoch_name
266
273
  epoch_group.attrs["epoch_number"] = epoch.epoch_number
267
274
  epoch_group.attrs["trial_count"] = epoch.trial_count
268
- epoch_group.create_dataset(
275
+ response_values = epoch_group.create_dataset(
269
276
  "response_values",
270
277
  data=epoch.response_values,
271
278
  compression="gzip",
272
279
  )
280
+ write_twopy_spatial_orientation(response_values)
273
281
 
274
282
 
275
283
  def load_response_map_data(path: Path) -> ResponseMapData:
@@ -287,21 +295,34 @@ def load_response_map_data(path: Path) -> ResponseMapData:
287
295
  input_path = path.expanduser()
288
296
  with h5py.File(input_path, "r") as h5_file:
289
297
  _require_response_map_format(h5_file, input_path)
298
+ require_twopy_spatial_orientation(
299
+ h5_file,
300
+ path=input_path,
301
+ label="response heatmap file",
302
+ )
290
303
  options = _read_response_map_options(h5_file["options"])
291
304
  spatial_crop = _read_spatial_crop(h5_file["spatial_crop"])
292
- mean_image = np.asarray(h5_file["mean_image"], dtype=np.float64)
305
+ mean_image_dataset = h5_file["mean_image"]
306
+ require_twopy_spatial_orientation(
307
+ mean_image_dataset,
308
+ path=input_path,
309
+ label="mean_image",
310
+ )
311
+ mean_image = np.asarray(mean_image_dataset, dtype=np.float64)
293
312
  epochs_group = h5_file["epochs"]
294
313
  epochs = tuple(
295
- _read_epoch_response_map(epochs_group[name])
314
+ _read_epoch_response_map(epochs_group[name], path=input_path)
296
315
  for name in sorted(epochs_group.keys())
297
316
  )
298
- return ResponseMapData(
317
+ map_data = ResponseMapData(
299
318
  mean_image=mean_image,
300
319
  epochs=epochs,
301
320
  options=options,
302
321
  spatial_crop=spatial_crop,
303
322
  response_scale=float(h5_file.attrs["response_scale"]),
304
323
  )
324
+ _require_response_map_shapes(map_data)
325
+ return map_data
305
326
 
306
327
 
307
328
  def _write_response_map_options(
@@ -357,16 +378,45 @@ def _read_spatial_crop(group: h5py.Group) -> SpatialCrop:
357
378
  )
358
379
 
359
380
 
360
- def _read_epoch_response_map(group: h5py.Group) -> EpochResponseMap:
381
+ def _read_epoch_response_map(group: h5py.Group, *, path: Path) -> EpochResponseMap:
361
382
  """Read one persisted epoch response map."""
383
+ response_values = group["response_values"]
384
+ require_twopy_spatial_orientation(
385
+ response_values,
386
+ path=path,
387
+ label=f"{group.name}/response_values",
388
+ )
362
389
  return EpochResponseMap(
363
390
  epoch_name=_string_attr(group, "epoch_name"),
364
391
  epoch_number=int(group.attrs["epoch_number"]),
365
- response_values=np.asarray(group["response_values"], dtype=np.float64),
392
+ response_values=np.asarray(response_values, dtype=np.float64),
366
393
  trial_count=int(group.attrs["trial_count"]),
367
394
  )
368
395
 
369
396
 
397
+ def _require_response_map_shapes(map_data: ResponseMapData) -> None:
398
+ """Require heatmap spatial arrays to match their recorded crop."""
399
+ if map_data.mean_image.ndim != 2:
400
+ msg = (
401
+ f"response heatmap mean_image must be 2-D; got {map_data.mean_image.shape}"
402
+ )
403
+ raise ValueError(msg)
404
+ if map_data.mean_image.shape != map_data.spatial_crop.shape:
405
+ msg = (
406
+ "response heatmap mean_image shape must match spatial_crop shape; "
407
+ f"got {map_data.mean_image.shape} and {map_data.spatial_crop.shape}"
408
+ )
409
+ raise ValueError(msg)
410
+ for epoch in map_data.epochs:
411
+ if epoch.response_values.shape != map_data.mean_image.shape:
412
+ msg = (
413
+ "response heatmap response_values shape must match mean_image "
414
+ f"shape; got {epoch.response_values.shape} for "
415
+ f"{epoch.epoch_name!r} and {map_data.mean_image.shape}"
416
+ )
417
+ raise ValueError(msg)
418
+
419
+
370
420
  def _require_response_map_format(h5_file: h5py.File, path: Path) -> None:
371
421
  """Raise when an HDF5 file is not a twopy response heatmap file."""
372
422
  actual = _string_attr(h5_file, "twopy_format")
@@ -4,10 +4,10 @@ Inputs: configured recording roots, local cache folders, and saved analysis file
4
4
  Outputs: cache refreshes and publish-sync plans for twopy-owned outputs.
5
5
 
6
6
  The cache keeps interactive reads and writes on local storage. The publish sync
7
- copies only user-visible analysis outputs back to the configured output folder;
8
- converted movies remain derived local working files by default. Exported figure
9
- files are removed from the local cache after they copy successfully because the
10
- publish destination is their durable location.
7
+ copies twopy-owned converted files and user-visible analysis outputs back to the
8
+ configured output folder. Exported figure files are removed from the local cache
9
+ after they copy successfully because the publish destination is their durable
10
+ location.
11
11
  """
12
12
 
13
13
  import os
@@ -25,7 +25,9 @@ from twopy.config import (
25
25
  )
26
26
  from twopy.converted import RecordingData
27
27
  from twopy.filenames import (
28
+ ALIGNED_MOVIE_FILENAME,
28
29
  ANALYSIS_OUTPUT_FILENAME,
30
+ RECORDING_DATA_FILENAME,
29
31
  RESPONSE_HEATMAPS_FILENAME,
30
32
  ROI_FILENAME,
31
33
  )
@@ -36,11 +38,14 @@ __all__ = [
36
38
  "build_analysis_sync_plan",
37
39
  "copy_file_atomically",
38
40
  "copy_analysis_sync_plan",
41
+ "copy_converted_files_to_publish",
39
42
  "refresh_cached_analysis_outputs",
40
43
  "start_analysis_sync",
41
44
  ]
42
45
 
43
46
  _CACHE_REFRESH_FILENAMES = (
47
+ RECORDING_DATA_FILENAME,
48
+ ALIGNED_MOVIE_FILENAME,
44
49
  ROI_FILENAME,
45
50
  ANALYSIS_OUTPUT_FILENAME,
46
51
  RESPONSE_HEATMAPS_FILENAME,
@@ -91,8 +96,8 @@ def refresh_cached_analysis_outputs(
91
96
  Local paths refreshed from published outputs.
92
97
 
93
98
  Missing config or disabled caching means there is no cache refresh to do.
94
- Only the HDF5 files needed by the GUI reload path are pulled down; CSV and
95
- image exports are regenerated from saved analysis when needed.
99
+ HDF5 files needed by the GUI are pulled down; CSV and image exports are
100
+ regenerated from saved analysis when needed.
96
101
  """
97
102
  try:
98
103
  config = load_config(config_path)
@@ -137,21 +142,60 @@ def build_analysis_sync_plan(
137
142
  config_path: YAML config file with cache and publish settings.
138
143
 
139
144
  Returns:
140
- A sync plan, or ``None`` when caching is disabled, config is missing,
141
- the publish root is the local root, or no syncable files were written.
145
+ A sync plan, or ``None`` when config is missing, the publish root is the
146
+ local root, or no syncable files were written.
142
147
  """
143
148
  try:
144
149
  config = load_config(config_path)
145
150
  except FileNotFoundError:
146
151
  return None
147
152
  try:
148
- return _build_analysis_sync_plan_from_config(
149
- recording=recording,
150
- local_paths=local_paths,
153
+ plan = _build_sync_plan_from_config(
154
+ source_session_dir=recording.source_session_dir,
155
+ local_root=recording.path.expanduser().parent,
156
+ local_paths=(*_converted_recording_paths(recording), *local_paths),
151
157
  config=config,
152
158
  )
153
159
  except ValueError:
154
160
  return None
161
+ if len(plan.local_paths) == 0:
162
+ return None
163
+ return plan
164
+
165
+
166
+ def copy_converted_files_to_publish(
167
+ *,
168
+ recording_data_path: Path,
169
+ movie_path: Path,
170
+ source_session_dir: Path,
171
+ config_path: Path = DEFAULT_CONFIG_PATH,
172
+ ) -> AnalysisSyncResult | None:
173
+ """Copy converted recording files to the configured publish folder.
174
+
175
+ Args:
176
+ recording_data_path: Local ``recording_data.h5`` file.
177
+ movie_path: Local ``aligned_movie.h5`` file.
178
+ source_session_dir: Source microscope recording folder that defines the
179
+ configured publish location.
180
+ config_path: YAML config file with cache and publish settings.
181
+
182
+ Returns:
183
+ Sync result, or ``None`` when config is missing, the publish root is the
184
+ local root, or publish routing does not apply.
185
+ """
186
+ try:
187
+ config = load_config(config_path)
188
+ plan = _build_sync_plan_from_config(
189
+ source_session_dir=source_session_dir,
190
+ local_root=recording_data_path.expanduser().parent,
191
+ local_paths=(recording_data_path, movie_path),
192
+ config=config,
193
+ )
194
+ except (FileNotFoundError, ValueError):
195
+ return None
196
+ if len(plan.local_paths) == 0:
197
+ return None
198
+ return copy_analysis_sync_plan(plan)
155
199
 
156
200
 
157
201
  def start_analysis_sync(
@@ -194,6 +238,8 @@ def copy_analysis_sync_plan(plan: AnalysisSyncPlan) -> AnalysisSyncResult:
194
238
  publish_path = plan.publish_root / relative
195
239
  if _same_path(local_path, publish_path):
196
240
  continue
241
+ if _copy_is_current(local_path, publish_path):
242
+ continue
197
243
  copy_file_atomically(local_path, publish_path)
198
244
  copied.append(publish_path)
199
245
  if _should_remove_after_sync(local_path):
@@ -206,20 +252,21 @@ def copy_analysis_sync_plan(plan: AnalysisSyncPlan) -> AnalysisSyncResult:
206
252
  )
207
253
 
208
254
 
209
- def _build_analysis_sync_plan_from_config(
255
+ def _build_sync_plan_from_config(
210
256
  *,
211
- recording: RecordingData,
257
+ source_session_dir: Path,
258
+ local_root: Path,
212
259
  local_paths: tuple[Path | None, ...],
213
260
  config: TwopyConfig,
214
- ) -> AnalysisSyncPlan | None:
261
+ ) -> AnalysisSyncPlan:
215
262
  """Return a sync plan from an already-loaded config."""
216
- if not config.analysis_caching:
217
- return None
218
-
219
- local_root = recording.path.expanduser().parent
220
- publish_root = resolve_analysis_output_dir(config, recording.source_session_dir)
263
+ publish_root = resolve_analysis_output_dir(config, source_session_dir)
221
264
  if _same_path(local_root, publish_root):
222
- return None
265
+ return AnalysisSyncPlan(
266
+ local_root=local_root,
267
+ publish_root=publish_root,
268
+ local_paths=(),
269
+ )
223
270
 
224
271
  syncable: list[Path] = []
225
272
  seen: set[Path] = set()
@@ -236,8 +283,6 @@ def _build_analysis_sync_plan_from_config(
236
283
  seen.add(local_path)
237
284
  syncable.append(local_path)
238
285
 
239
- if len(syncable) == 0:
240
- return None
241
286
  return AnalysisSyncPlan(
242
287
  local_root=local_root,
243
288
  publish_root=publish_root,
@@ -287,6 +332,23 @@ def _same_path(left: Path, right: Path) -> bool:
287
332
  )
288
333
 
289
334
 
335
+ def _copy_is_current(source: Path, target: Path) -> bool:
336
+ """Return whether the published file already matches this source version."""
337
+ if not target.is_file():
338
+ return False
339
+ source_stat = source.stat()
340
+ target_stat = target.stat()
341
+ return (
342
+ target_stat.st_size == source_stat.st_size
343
+ and target_stat.st_mtime_ns >= source_stat.st_mtime_ns
344
+ )
345
+
346
+
347
+ def _converted_recording_paths(recording: RecordingData) -> tuple[Path, Path]:
348
+ """Return the converted HDF5 files that travel with every publish sync."""
349
+ return (recording.path, recording.movie.path)
350
+
351
+
290
352
  def _should_remove_after_sync(path: Path) -> bool:
291
353
  """Return whether a local cache file is disposable after publish sync."""
292
354
  return path.suffix.lower() in _REMOVE_AFTER_SYNC_SUFFIXES
@@ -10,6 +10,7 @@ from pathlib import Path
10
10
  import numpy as np
11
11
  import numpy.typing as npt
12
12
 
13
+ from twopy.conversion.orientation import twopy_spatial_shape_from_source_spatial_shape
13
14
  from twopy.conversion.types import (
14
15
  AcquisitionMetadata,
15
16
  AlignedMovieSource,
@@ -69,9 +70,13 @@ def load_alignment_valid_crop(
69
70
 
70
71
  alignment_window = alignment[frame_start:frame_stop, :]
71
72
  line_scan = _is_line_scan(acquisition)
73
+ source_spatial_shape = _source_movie_spatial_shape(aligned_movie)
74
+ twopy_spatial_shape = twopy_spatial_shape_from_source_spatial_shape(
75
+ source_spatial_shape,
76
+ )
72
77
  motion_threshold_pixels = _motion_threshold_pixels(
73
78
  acquisition=acquisition,
74
- spatial_shape=_movie_spatial_shape(aligned_movie),
79
+ source_x_pixel_count=source_spatial_shape[0],
75
80
  line_scan=line_scan,
76
81
  )
77
82
  alignment_shift_pixels = _alignment_shift_pixels(
@@ -86,7 +91,7 @@ def load_alignment_valid_crop(
86
91
  )
87
92
  crop, x_cutoff, y_cutoff = _alignment_crop_from_offsets(
88
93
  kept_alignment,
89
- spatial_shape=_movie_spatial_shape(aligned_movie),
94
+ spatial_shape=twopy_spatial_shape,
90
95
  line_scan=line_scan,
91
96
  )
92
97
 
@@ -132,14 +137,14 @@ def _stimulus_alignment_frame_range(
132
137
  def _motion_threshold_pixels(
133
138
  *,
134
139
  acquisition: AcquisitionMetadata,
135
- spatial_shape: tuple[int, int],
140
+ source_x_pixel_count: int,
136
141
  line_scan: bool,
137
142
  ) -> float:
138
143
  """Convert the motion rejection threshold from microns to pixels.
139
144
 
140
145
  Args:
141
146
  acquisition: Acquisition metadata with zoom.
142
- spatial_shape: Full movie spatial shape in twopy array axes.
147
+ source_x_pixel_count: Number of source pixels along the physical x axis.
143
148
  line_scan: Whether this recording is a line scan.
144
149
 
145
150
  Returns:
@@ -151,7 +156,7 @@ def _motion_threshold_pixels(
151
156
  else DEFAULT_PLANE_MOTION_LIMIT_MICRONS
152
157
  )
153
158
  zoom = _required_float_field(acquisition, "acq.zoomFactor")
154
- x_axis_conversion = 256.0 / float(spatial_shape[0])
159
+ x_axis_conversion = 256.0 / float(source_x_pixel_count)
155
160
  return micron_limit * ONE_X_PIXELS_PER_MICRON_AT_256_X * zoom * x_axis_conversion
156
161
 
157
162
 
@@ -223,19 +228,19 @@ def _alignment_crop_from_offsets(
223
228
  Returns:
224
229
  Spatial crop plus x/y cutoff values.
225
230
 
226
- Source alignment columns are x/y motion offsets. twopy arrays store x along
227
- spatial axis 0 and y along spatial axis 1, so the crop is mapped directly to
228
- those axes.
231
+ Source alignment columns are x/y motion offsets. Converted twopy arrays use
232
+ Python image axis order matching MATLAB display of the source data: spatial
233
+ axis 0 is displayed y/rows, and spatial axis 1 is displayed x/columns.
229
234
  """
230
235
  x_cutoff = _ceil_abs_offset(alignment[:, 0])
231
236
  y_cutoff = 1 if line_scan else _ceil_abs_offset(alignment[:, 1])
232
237
  axis0_start, axis0_stop = _crop_bounds_from_cutoff(
233
238
  length=spatial_shape[0],
234
- cutoff=x_cutoff,
239
+ cutoff=y_cutoff,
235
240
  )
236
241
  axis1_start, axis1_stop = _crop_bounds_from_cutoff(
237
242
  length=spatial_shape[1],
238
- cutoff=y_cutoff,
243
+ cutoff=x_cutoff,
239
244
  )
240
245
  return (
241
246
  SpatialCrop(
@@ -283,14 +288,14 @@ def _crop_bounds_from_cutoff(*, length: int, cutoff: int) -> tuple[int, int]:
283
288
  return start, stop
284
289
 
285
290
 
286
- def _movie_spatial_shape(aligned_movie: AlignedMovieSource) -> tuple[int, int]:
287
- """Return the two spatial dimensions of the aligned source movie.
291
+ def _source_movie_spatial_shape(aligned_movie: AlignedMovieSource) -> tuple[int, int]:
292
+ """Return the source spatial dimensions of the aligned source movie.
288
293
 
289
294
  Args:
290
295
  aligned_movie: Source movie metadata.
291
296
 
292
297
  Returns:
293
- ``(axis0, axis1)`` spatial shape.
298
+ ``(source_x, source_y)`` spatial shape.
294
299
  """
295
300
  if len(aligned_movie.shape) != 3:
296
301
  msg = f"Aligned movie must be 3D; got {aligned_movie.shape}"
@@ -20,6 +20,10 @@ from twopy.conversion.hdf5_writing import (
20
20
  write_aligned_movie_file,
21
21
  write_recording_data_file,
22
22
  )
23
+ from twopy.conversion.orientation import (
24
+ orient_source_array_to_twopy,
25
+ twopy_shape_from_source_movie_shape,
26
+ )
23
27
  from twopy.conversion.source_loading import load_source_conversion_inputs
24
28
  from twopy.conversion.types import (
25
29
  ConvertedRecording,
@@ -71,9 +75,11 @@ def convert_recording_to_twopy(
71
75
  stop_frame=mean_stop_frame,
72
76
  context="frame range for mean image",
73
77
  )
74
- mean_image = inputs.aligned_movie.mean_image(
75
- start_frame=start_frame,
76
- stop_frame=stop_frame,
78
+ mean_image = orient_source_array_to_twopy(
79
+ inputs.aligned_movie.mean_image(
80
+ start_frame=start_frame,
81
+ stop_frame=stop_frame,
82
+ ),
77
83
  )
78
84
 
79
85
  destination_dir = _resolve_conversion_output_dir(
@@ -94,14 +100,24 @@ def convert_recording_to_twopy(
94
100
  mean_stop_frame=stop_frame,
95
101
  )
96
102
 
97
- return ConvertedRecording(
103
+ converted = ConvertedRecording(
98
104
  path=recording_data_path,
99
105
  movie_path=movie_path,
100
106
  source_session_dir=inputs.session_files.session_dir,
101
- movie_shape=inputs.aligned_movie.shape,
107
+ movie_shape=twopy_shape_from_source_movie_shape(inputs.aligned_movie.shape),
102
108
  mean_image_start_frame=start_frame,
103
109
  mean_image_stop_frame=stop_frame,
104
110
  )
111
+ if output_dir is None:
112
+ from twopy.analysis_cache import copy_converted_files_to_publish
113
+
114
+ copy_converted_files_to_publish(
115
+ recording_data_path=converted.path,
116
+ movie_path=converted.movie_path,
117
+ source_session_dir=converted.source_session_dir,
118
+ config_path=config_path,
119
+ )
120
+ return converted
105
121
 
106
122
 
107
123
  def _resolve_conversion_output_dir(
@@ -15,6 +15,10 @@ import numpy as np
15
15
  import numpy.typing as npt
16
16
 
17
17
  from twopy.conversion.matlab_values import json_ready
18
+ from twopy.conversion.orientation import (
19
+ orient_source_array_to_twopy,
20
+ twopy_shape_from_source_movie_shape,
21
+ )
18
22
  from twopy.conversion.types import (
19
23
  AlignedMovieSource,
20
24
  AlignmentCropAudit,
@@ -26,6 +30,10 @@ from twopy.conversion.types import (
26
30
  )
27
31
  from twopy.filenames import ALIGNED_MOVIE_FILENAME
28
32
  from twopy.hdf5_utils import write_string_dataset
33
+ from twopy.spatial_orientation import (
34
+ SOURCE_SPATIAL_ORIENTATION,
35
+ write_twopy_spatial_orientation,
36
+ )
29
37
 
30
38
  __all__ = [
31
39
  "ALIGNED_MOVIE_DATASET",
@@ -146,13 +154,18 @@ def _write_movie_reference_group(
146
154
  movie_group = h5_file.create_group("movie")
147
155
  movie_group.attrs["aligned_movie_file"] = ALIGNED_MOVIE_FILENAME
148
156
  movie_group.attrs["aligned_movie_dataset"] = ALIGNED_MOVIE_DATASET
149
- movie_group.attrs["aligned_movie_shape"] = movie.shape
157
+ movie_group.attrs["aligned_movie_shape"] = twopy_shape_from_source_movie_shape(
158
+ movie.shape,
159
+ )
150
160
  movie_group.attrs["aligned_movie_dtype"] = movie.dtype
161
+ write_twopy_spatial_orientation(movie_group)
162
+ movie_group.attrs["source_spatial_orientation"] = SOURCE_SPATIAL_ORIENTATION
151
163
 
152
164
  mean_dataset = movie_group.create_dataset(
153
165
  "mean_image",
154
166
  data=mean_image,
155
167
  )
168
+ write_twopy_spatial_orientation(mean_dataset)
156
169
  mean_dataset.attrs["start_frame"] = mean_start_frame
157
170
  mean_dataset.attrs["stop_frame"] = mean_stop_frame
158
171
  _write_alignment_crop_group(movie_group, alignment_crop)
@@ -326,21 +339,31 @@ def _copy_aligned_movie(source: AlignedMovieSource, movie_group: h5py.Group) ->
326
339
  None. The function writes ``movie/aligned``.
327
340
  """
328
341
  chunk_frames = source.chunks[0] if source.chunks is not None else 64
342
+ destination_shape = twopy_shape_from_source_movie_shape(source.shape)
343
+ destination_chunks = (
344
+ (source.chunks[0], source.chunks[2], source.chunks[1])
345
+ if source.chunks is not None
346
+ else None
347
+ )
329
348
  with h5py.File(source.path, "r") as source_file:
330
349
  source_dataset = source_file[source.dataset_name]
331
350
  aligned = movie_group.create_dataset(
332
351
  "aligned",
333
- shape=source.shape,
352
+ shape=destination_shape,
334
353
  dtype=source_dataset.dtype,
335
- chunks=source.chunks,
354
+ chunks=destination_chunks,
336
355
  compression="gzip",
337
356
  )
338
357
  aligned.attrs["source_path"] = str(source.path)
339
358
  aligned.attrs["source_dataset"] = source.dataset_name
359
+ write_twopy_spatial_orientation(aligned)
360
+ aligned.attrs["source_spatial_orientation"] = SOURCE_SPATIAL_ORIENTATION
340
361
 
341
362
  for start in range(0, source.shape[0], chunk_frames):
342
363
  stop = min(start + chunk_frames, source.shape[0])
343
- aligned[start:stop, :, :] = source_dataset[start:stop, :, :]
364
+ aligned[start:stop, :, :] = orient_source_array_to_twopy(
365
+ source_dataset[start:stop, :, :],
366
+ )
344
367
 
345
368
 
346
369
  def _write_attrs(group: h5py.Group, values: dict[str, object]) -> None: