TwoPy 0.2.0__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.
- {twopy-0.2.0 → twopy-0.3.0}/PKG-INFO +8 -4
- {twopy-0.2.0 → twopy-0.3.0}/README.md +5 -3
- {twopy-0.2.0 → twopy-0.3.0}/pyproject.toml +9 -1
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/background_subtraction.py +120 -21
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/dff.py +33 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/kernels/__init__.py +2 -2
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/persistence.py +19 -4
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/response_maps.py +57 -7
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis_cache.py +86 -23
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/config.py +47 -8
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/conversion/alignment_crop.py +18 -13
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/conversion/api.py +21 -5
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/conversion/hdf5_writing.py +27 -4
- twopy-0.3.0/src/twopy/conversion/orientation.py +80 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/conversion/types.py +1 -1
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/converted.py +26 -3
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/custom/__init__.py +8 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/custom/native_workflows/direction_selectivity.py +13 -12
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/custom/native_workflows/response_kernels.py +2 -7
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/custom/parameters.py +4 -3
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/custom/provenance.py +14 -0
- twopy-0.3.0/src/twopy/custom/runner.py +244 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/custom/types.py +247 -8
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/database/search.py +1 -1
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/controls.py +147 -26
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/custom_tab.py +168 -20
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/database_search.py +41 -1
- twopy-0.3.0/src/twopy/napari/display.py +106 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/display_paths.py +26 -0
- twopy-0.3.0/src/twopy/napari/group_matching/__init__.py +29 -0
- twopy-0.3.0/src/twopy/napari/group_matching/cards.py +127 -0
- twopy-0.3.0/src/twopy/napari/group_matching/fov_assignment.py +679 -0
- twopy-0.3.0/src/twopy/napari/group_matching/fov_cards.py +143 -0
- twopy-0.2.0/src/twopy/napari/group_matching_images.py → twopy-0.3.0/src/twopy/napari/group_matching/images.py +2 -2
- twopy-0.3.0/src/twopy/napari/group_matching/response_preview.py +334 -0
- twopy-0.3.0/src/twopy/napari/group_matching/responses.py +130 -0
- twopy-0.2.0/src/twopy/napari/group_matching_roi.py → twopy-0.3.0/src/twopy/napari/group_matching/roi_assignment.py +590 -532
- twopy-0.3.0/src/twopy/napari/group_matching/roi_cards.py +371 -0
- twopy-0.3.0/src/twopy/napari/group_matching/style.py +269 -0
- twopy-0.3.0/src/twopy/napari/group_matching/tables.py +27 -0
- twopy-0.3.0/src/twopy/napari/group_matching/window.py +267 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/live_analysis.py +75 -32
- twopy-0.3.0/src/twopy/napari/load_controller.py +400 -0
- twopy-0.3.0/src/twopy/napari/load_progress.py +168 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/load_workflow.py +225 -83
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/loaded_recordings_csv.py +35 -4
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/loading.py +59 -3
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/dff_options.py +1 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/docks/dynamic_options.py +60 -14
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/docks/options_panel.py +15 -16
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/docks/paths.py +5 -6
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/docks/plot_area.py +42 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/docks/response_map_area.py +5 -8
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/docks/response_plot_widget.py +255 -101
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/docks/visibility_state.py +29 -2
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/export.py +11 -19
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/export_controls.py +5 -6
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/normalization_options.py +1 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/options.py +1 -1
- twopy-0.3.0/src/twopy/napari/plotting/panels.py +188 -0
- twopy-0.3.0/src/twopy/napari/plotting/preview_strip.py +218 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/processing_options.py +1 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/widgets.py +403 -26
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/responses.py +58 -1
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/roi.py +91 -22
- twopy-0.3.0/src/twopy/napari/roi_signatures.py +20 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/session.py +20 -3
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/sidebar.py +27 -14
- twopy-0.3.0/src/twopy/napari/state.py +192 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/viewer.py +206 -67
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/roi.py +26 -5
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/spatial.py +3 -3
- twopy-0.3.0/src/twopy/spatial_orientation.py +78 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy.egg-info/PKG-INFO +8 -4
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy.egg-info/SOURCES.txt +23 -3
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_background_subtraction.py +22 -1
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_config.py +28 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_conversion.py +113 -10
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_converted.py +64 -2
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_custom_workflows.py +301 -19
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_dff.py +33 -3
- twopy-0.3.0/tests/test_function_inventory.py +196 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_kernels.py +132 -1
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_load_workflow.py +58 -51
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari.py +229 -46
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_database_search.py +60 -2
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_display.py +12 -31
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_export.py +77 -5
- twopy-0.3.0/tests/test_napari_group_matching_responses.py +140 -0
- twopy-0.3.0/tests/test_napari_group_matching_roi_cards.py +156 -0
- twopy-0.3.0/tests/test_napari_load_progress.py +48 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_load_tab.py +207 -4
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_loaded_recordings.py +406 -18
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_path_resolution.py +42 -2
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_plot_controls.py +104 -1
- twopy-0.3.0/tests/test_napari_plot_widgets.py +1808 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_processing_controls.py +100 -3
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_response_workflow.py +4 -2
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_roi_generation.py +76 -3
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_roi_visibility.py +26 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_persistence.py +35 -1
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_response_maps.py +115 -11
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_roi.py +31 -1
- twopy-0.2.0/src/twopy/napari/display.py +0 -181
- twopy-0.2.0/src/twopy/napari/group_matching.py +0 -596
- twopy-0.2.0/src/twopy/napari/plotting/panels.py +0 -103
- twopy-0.2.0/src/twopy/napari/state.py +0 -106
- twopy-0.2.0/tests/test_napari_plot_widgets.py +0 -799
- {twopy-0.2.0 → twopy-0.3.0}/setup.cfg +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/_segments.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/_version.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/dff_options.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/epoch_mapping.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/group_matching.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/kernels/_fitting.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/kernels/_stimulus.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/motion.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/response_context.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/response_processing/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/response_processing/apply.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/response_processing/options.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/response_processing/persistence.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/response_processing/qc.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/response_processing/signals.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/response_window_options.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/responses.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/timing.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/trials.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/analysis/workflow.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/api.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/calibrations/pixel_calibration_profiles.csv +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/calibrations/pixel_size.csv +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/conversion/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/conversion/matlab_values.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/conversion/source_loading.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/conversion/stimulus_code.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/custom/discovery.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/custom/native.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/custom/native_workflows/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/custom/registry.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/database/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/database/cache.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/database/catalog.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/database/connection.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/database/generic.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/database/modeled.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/database/sql.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/database/types.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/filenames.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/frame_ranges.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/hdf5_utils.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/inspection.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/matlab.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/__main__.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/database_favorites.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/dims.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/errors.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/interactive.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/latest_worker.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/launcher.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/movie.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/paths.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/data.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/docks/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/docks/api.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/docks/save_actions.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/form_controls.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/label_visibility.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/response_map_colors.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/response_map_display.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/response_map_options.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/response_window_options.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/roi_generation/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/roi_generation/actions.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/roi_generation/controls.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/plotting/roi_generation/options.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/protocols.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/text.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/trial_timeline.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/napari/types.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/parity/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/parity/dff.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/parity/psycho5.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/parity/roi.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/parity/roi_extraction.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/parity/saved_analysis.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/photodiode.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/photodiode_classification.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/pixel_calibration.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/pixel_calibration_profiles.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/response_roi_extraction.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/roi_extraction.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/roi_mask_cleanup.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/session.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/stimulus.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/synchronization.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy/typing_guards.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy.egg-info/dependency_links.txt +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy.egg-info/entry_points.txt +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy.egg-info/requires.txt +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/src/twopy.egg-info/top_level.txt +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_api.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_database.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_epoch_mapping.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_filenames.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_frame_ranges.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_group_matching.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_hdf5_utils.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_import.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_inspection.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_matlab.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_motion.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_live_controller.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_plot_data.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_response_map_display.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_text.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_napari_timeline.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_packaging.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_parity.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_parity_psycho5.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_parity_roi_extraction.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_photodiode_classification.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_pixel_calibration.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_pixel_calibration_profiles.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_real_fixture.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_recording_timing.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_response_map_area.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_response_processing.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_response_roi_extraction.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_response_window_options.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_responses.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_roi_extraction.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_roi_mask_cleanup.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_saved_analysis.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_session.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_stimulus.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_synchronization.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_trials.py +0 -0
- {twopy-0.2.0 → twopy-0.3.0}/tests/test_workflow.py +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: twopy
|
|
3
|
-
Version: 0.
|
|
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
|
|
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,9 +75,11 @@ When a source recording has not been converted yet, twopy converts it first. Con
|
|
|
73
75
|
|
|
74
76
|
## Docs
|
|
75
77
|
|
|
76
|
-
- [
|
|
77
|
-
- [
|
|
78
|
-
- [
|
|
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.
|
|
79
83
|
- [Development guide](docs/development.md): source setup and local checks.
|
|
80
84
|
- [Input data spec](docs/input_data_spec.md): short recording folder contract.
|
|
81
85
|
- [Recording file reference](docs/recording_file_schema.md): detailed source and converted file schema.
|
|
@@ -53,9 +53,11 @@ When a source recording has not been converted yet, twopy converts it first. Con
|
|
|
53
53
|
|
|
54
54
|
## Docs
|
|
55
55
|
|
|
56
|
-
- [
|
|
57
|
-
- [
|
|
58
|
-
- [
|
|
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.
|
|
59
61
|
- [Development guide](docs/development.md): source setup and local checks.
|
|
60
62
|
- [Input data spec](docs/input_data_spec.md): short recording folder contract.
|
|
61
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.
|
|
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"
|
|
@@ -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",
|
|
@@ -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
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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,
|
|
@@ -80,8 +80,8 @@ class StimulusKernelOptions:
|
|
|
80
80
|
baseline_epoch_number: int = 1
|
|
81
81
|
discard_first_stimulus_epoch: bool = True
|
|
82
82
|
selected_epoch_numbers: tuple[int, ...] | None = None
|
|
83
|
-
num_stim_past: int =
|
|
84
|
-
num_stim_future: int =
|
|
83
|
+
num_stim_past: int = 100
|
|
84
|
+
num_stim_future: int = 10
|
|
85
85
|
method: KernelFitMethod = "ols"
|
|
86
86
|
hemisphere: Hemisphere | None = None
|
|
87
87
|
|
|
@@ -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"])
|
|
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
|
|
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"),
|
|
@@ -869,7 +884,7 @@ def _trial_response_row(
|
|
|
869
884
|
"stop_frame": trial.stop_frame,
|
|
870
885
|
"frame_count": trial.stop_frame - trial.start_frame,
|
|
871
886
|
}
|
|
872
|
-
row.update(
|
|
887
|
+
row.update(dict.fromkeys(time_columns, ""))
|
|
873
888
|
for time_seconds, value in zip(
|
|
874
889
|
trial.time_seconds,
|
|
875
890
|
trial.values[:, roi_index],
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
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")
|