TwoPy 0.2.1__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {twopy-0.2.1 → twopy-0.3.1}/PKG-INFO +1 -1
- {twopy-0.2.1 → twopy-0.3.1}/pyproject.toml +1 -1
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/__init__.py +0 -16
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/__init__.py +12 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/persistence.py +156 -70
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/response_maps.py +57 -7
- twopy-0.3.1/src/twopy/analysis/response_plotting.py +334 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis_cache.py +84 -22
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/conversion/alignment_crop.py +18 -13
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/conversion/api.py +10 -4
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/conversion/hdf5_writing.py +27 -4
- twopy-0.3.1/src/twopy/conversion/orientation.py +80 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/conversion/types.py +1 -1
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/converted.py +26 -3
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/custom/__init__.py +12 -7
- twopy-0.2.1/src/twopy/custom/types.py → twopy-0.3.1/src/twopy/custom/context.py +12 -484
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/custom/discovery.py +3 -6
- twopy-0.3.1/src/twopy/custom/metadata.py +228 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/custom/parameters.py +6 -5
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/custom/provenance.py +84 -7
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/custom/registry.py +2 -1
- twopy-0.3.1/src/twopy/custom/results.py +116 -0
- twopy-0.3.1/src/twopy/custom/runner.py +247 -0
- twopy-0.3.1/src/twopy/custom/workflows.py +98 -0
- twopy-0.3.1/src/twopy/napari/__init__.py +75 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/controls.py +90 -33
- twopy-0.3.1/src/twopy/napari/database_favorite_editor.py +106 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/database_favorites.py +56 -5
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/database_search.py +223 -2
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/dims.py +5 -9
- twopy-0.3.1/src/twopy/napari/display.py +106 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/group_matching/fov_assignment.py +1 -8
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/group_matching/images.py +4 -8
- twopy-0.3.1/src/twopy/napari/group_matching/protocols.py +27 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/group_matching/response_preview.py +1 -1
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/group_matching/responses.py +1 -1
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/group_matching/roi_assignment.py +2 -8
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/group_matching/roi_cards.py +4 -9
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/group_matching/window.py +9 -16
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/interactive.py +4 -16
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/live_analysis.py +1 -1
- twopy-0.3.1/src/twopy/napari/load_controller.py +400 -0
- twopy-0.3.1/src/twopy/napari/load_progress.py +168 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/load_workflow.py +225 -83
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/loaded_recordings_csv.py +65 -24
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/loading.py +21 -3
- twopy-0.3.1/src/twopy/napari/plotting/__init__.py +52 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/data.py +16 -289
- twopy-0.3.1/src/twopy/napari/plotting/docks/__init__.py +52 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/docks/api.py +20 -6
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/docks/dynamic_options.py +70 -15
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/docks/plot_area.py +1 -1
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/docks/response_map_area.py +5 -8
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/docks/response_plot_widget.py +119 -91
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/docks/visibility_state.py +1 -1
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/export.py +15 -28
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/export_controls.py +1 -1
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/options.py +49 -4
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/preview_strip.py +1 -1
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/widgets.py +1 -1
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/protocols.py +44 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/responses.py +4 -2
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/roi.py +145 -23
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/session.py +36 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/trial_timeline.py +11 -24
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/viewer.py +206 -67
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/roi.py +54 -5
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/spatial.py +3 -3
- twopy-0.3.1/src/twopy/spatial_orientation.py +78 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy.egg-info/PKG-INFO +1 -1
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy.egg-info/SOURCES.txt +14 -1
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_background_subtraction.py +22 -1
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_conversion.py +114 -11
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_converted.py +64 -2
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_custom_workflows.py +174 -2
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_function_inventory.py +45 -4
- twopy-0.3.1/tests/test_import.py +121 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_kernels.py +125 -1
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_load_workflow.py +58 -51
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari.py +54 -40
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_database_search.py +329 -3
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_display.py +12 -31
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_export.py +5 -3
- twopy-0.3.1/tests/test_napari_load_controller.py +257 -0
- twopy-0.3.1/tests/test_napari_load_progress.py +48 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_load_tab.py +145 -8
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_loaded_recordings.py +41 -4
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_path_resolution.py +67 -7
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_plot_data.py +6 -4
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_plot_widgets.py +2 -2
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_response_workflow.py +4 -2
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_roi_generation.py +133 -3
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_roi_visibility.py +32 -1
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_persistence.py +125 -1
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_response_maps.py +115 -11
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_roi.py +60 -1
- twopy-0.2.1/src/twopy/napari/__init__.py +0 -27
- twopy-0.2.1/src/twopy/napari/display.py +0 -181
- twopy-0.2.1/src/twopy/napari/plotting/__init__.py +0 -21
- twopy-0.2.1/src/twopy/napari/plotting/docks/__init__.py +0 -23
- twopy-0.2.1/tests/test_import.py +0 -26
- {twopy-0.2.1 → twopy-0.3.1}/README.md +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/setup.cfg +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/_segments.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/_version.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/background_subtraction.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/dff.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/dff_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/epoch_mapping.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/group_matching.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/kernels/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/kernels/_fitting.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/kernels/_stimulus.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/motion.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/response_context.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/response_processing/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/response_processing/apply.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/response_processing/options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/response_processing/persistence.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/response_processing/qc.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/response_processing/signals.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/response_window_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/responses.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/timing.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/trials.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/analysis/workflow.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/api.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/calibrations/pixel_calibration_profiles.csv +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/calibrations/pixel_size.csv +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/config.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/conversion/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/conversion/matlab_values.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/conversion/source_loading.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/conversion/stimulus_code.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/custom/native.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/custom/native_workflows/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/custom/native_workflows/direction_selectivity.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/custom/native_workflows/response_kernels.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/database/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/database/cache.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/database/catalog.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/database/connection.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/database/generic.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/database/modeled.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/database/search.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/database/sql.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/database/types.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/filenames.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/frame_ranges.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/hdf5_utils.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/inspection.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/matlab.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/__main__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/custom_tab.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/display_paths.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/errors.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/group_matching/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/group_matching/cards.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/group_matching/fov_cards.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/group_matching/style.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/group_matching/tables.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/latest_worker.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/launcher.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/movie.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/paths.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/dff_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/docks/options_panel.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/docks/paths.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/docks/save_actions.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/form_controls.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/label_visibility.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/normalization_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/panels.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/processing_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/response_map_colors.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/response_map_display.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/response_map_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/response_window_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/roi_generation/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/roi_generation/actions.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/roi_generation/controls.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/plotting/roi_generation/options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/roi_signatures.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/sidebar.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/state.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/text.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/napari/types.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/parity/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/parity/dff.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/parity/psycho5.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/parity/roi.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/parity/roi_extraction.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/parity/saved_analysis.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/photodiode.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/photodiode_classification.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/pixel_calibration.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/pixel_calibration_profiles.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/response_roi_extraction.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/roi_extraction.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/roi_mask_cleanup.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/session.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/stimulus.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/synchronization.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy/typing_guards.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy.egg-info/dependency_links.txt +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy.egg-info/entry_points.txt +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy.egg-info/requires.txt +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/src/twopy.egg-info/top_level.txt +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_api.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_config.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_database.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_dff.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_epoch_mapping.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_filenames.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_frame_ranges.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_group_matching.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_hdf5_utils.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_inspection.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_matlab.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_motion.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_group_matching_responses.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_group_matching_roi_cards.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_live_controller.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_plot_controls.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_processing_controls.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_response_map_display.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_text.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_napari_timeline.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_packaging.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_parity.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_parity_psycho5.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_parity_roi_extraction.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_photodiode_classification.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_pixel_calibration.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_pixel_calibration_profiles.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_real_fixture.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_recording_timing.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_response_map_area.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_response_processing.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_response_roi_extraction.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_response_window_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_responses.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_roi_extraction.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_roi_mask_cleanup.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_saved_analysis.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_session.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_stimulus.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_synchronization.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_trials.py +0 -0
- {twopy-0.2.1 → twopy-0.3.1}/tests/test_workflow.py +0 -0
|
@@ -94,15 +94,6 @@ from twopy.converted import (
|
|
|
94
94
|
recording_frame_rate_hz,
|
|
95
95
|
recording_hemisphere,
|
|
96
96
|
)
|
|
97
|
-
from twopy.napari import (
|
|
98
|
-
NapariRecordingView,
|
|
99
|
-
NapariSidebarWidgets,
|
|
100
|
-
add_twopy_magicgui_controls,
|
|
101
|
-
launch_napari,
|
|
102
|
-
open_recording_in_napari,
|
|
103
|
-
roi_label_image_from_layer,
|
|
104
|
-
save_napari_label_rois,
|
|
105
|
-
)
|
|
106
97
|
from twopy.photodiode_classification import (
|
|
107
98
|
ClassifiedPhotodiodeEvent,
|
|
108
99
|
ClassifiedStimulusTiming,
|
|
@@ -180,7 +171,6 @@ __all__ = [
|
|
|
180
171
|
"AnalysisResponseComputation",
|
|
181
172
|
"add_manual_roi_match_group",
|
|
182
173
|
"analyze_recording_responses",
|
|
183
|
-
"add_twopy_magicgui_controls",
|
|
184
174
|
"append_manual_roi_match_rows",
|
|
185
175
|
"apply_motion_artifact_mask_to_delta_f_over_f",
|
|
186
176
|
"BackgroundCorrectedRoiTraces",
|
|
@@ -227,7 +217,6 @@ __all__ = [
|
|
|
227
217
|
"is_baseline_epoch_name",
|
|
228
218
|
"KernelFitMethod",
|
|
229
219
|
"KernelStimulusModality",
|
|
230
|
-
"launch_napari",
|
|
231
220
|
"load_analysis_outputs",
|
|
232
221
|
"load_response_map_data",
|
|
233
222
|
"load_manual_fov_group_rows",
|
|
@@ -254,9 +243,6 @@ __all__ = [
|
|
|
254
243
|
"remove_manual_roi_match_group",
|
|
255
244
|
"replace_manual_roi_match_group",
|
|
256
245
|
"map_stimulus_specific_column",
|
|
257
|
-
"NapariRecordingView",
|
|
258
|
-
"NapariSidebarWidgets",
|
|
259
|
-
"open_recording_in_napari",
|
|
260
246
|
"pair_photodiode_events_to_imaging_frames",
|
|
261
247
|
"PhotodiodeAlignment",
|
|
262
248
|
"PhotodiodeDurationClass",
|
|
@@ -296,13 +282,11 @@ __all__ = [
|
|
|
296
282
|
"RoiResponseTrial",
|
|
297
283
|
"RoiSet",
|
|
298
284
|
"RoiTraces",
|
|
299
|
-
"roi_label_image_from_layer",
|
|
300
285
|
"roi_set_to_label_image",
|
|
301
286
|
"save_analysis_outputs",
|
|
302
287
|
"save_response_map_data",
|
|
303
288
|
"save_manual_fov_group_rows",
|
|
304
289
|
"save_manual_roi_match_rows",
|
|
305
|
-
"save_napari_label_rois",
|
|
306
290
|
"save_roi_set",
|
|
307
291
|
"select_baseline_frame_windows",
|
|
308
292
|
"select_pixel_calibration_group",
|
|
@@ -66,6 +66,13 @@ from twopy.analysis.response_maps import (
|
|
|
66
66
|
load_response_map_data,
|
|
67
67
|
save_response_map_data,
|
|
68
68
|
)
|
|
69
|
+
from twopy.analysis.response_plotting import (
|
|
70
|
+
EpochResponsePlotData,
|
|
71
|
+
ResponsePlotData,
|
|
72
|
+
filter_response_plot_data_rois,
|
|
73
|
+
response_plot_data_from_grouped,
|
|
74
|
+
response_plot_window_options_from_grouped,
|
|
75
|
+
)
|
|
69
76
|
from twopy.analysis.response_processing import (
|
|
70
77
|
CorrelationFilterOptions,
|
|
71
78
|
LowPassFilterOptions,
|
|
@@ -130,6 +137,7 @@ __all__ = [
|
|
|
130
137
|
"DeltaFOverFOptions",
|
|
131
138
|
"DeltaFOverFFitMode",
|
|
132
139
|
"EpochResponseMap",
|
|
140
|
+
"EpochResponsePlotData",
|
|
133
141
|
"EpochFrameWindow",
|
|
134
142
|
"extract_background_corrected_roi_traces",
|
|
135
143
|
"fit_recording_stimulus_kernels",
|
|
@@ -163,6 +171,7 @@ __all__ = [
|
|
|
163
171
|
"ResponseMapData",
|
|
164
172
|
"ResponseMapMode",
|
|
165
173
|
"ResponseMapOptions",
|
|
174
|
+
"ResponsePlotData",
|
|
166
175
|
"ResponseProcessingOptions",
|
|
167
176
|
"RoiCorrelationScores",
|
|
168
177
|
"RoiResponseSummary",
|
|
@@ -181,9 +190,12 @@ __all__ = [
|
|
|
181
190
|
"save_manual_roi_match_rows",
|
|
182
191
|
"select_baseline_frame_windows",
|
|
183
192
|
"finite_mean_and_sem",
|
|
193
|
+
"filter_response_plot_data_rois",
|
|
184
194
|
"split_traces_by_frame_windows",
|
|
185
195
|
"StimulusKernelOptions",
|
|
186
196
|
"SmoothingOptions",
|
|
197
|
+
"response_plot_data_from_grouped",
|
|
198
|
+
"response_plot_window_options_from_grouped",
|
|
187
199
|
"summarize_epoch_roi_responses",
|
|
188
200
|
"summarize_grouped_responses",
|
|
189
201
|
"TimingSource",
|
|
@@ -10,7 +10,9 @@ MATLAB/TIFF files or decide analysis parameters.
|
|
|
10
10
|
|
|
11
11
|
import csv
|
|
12
12
|
import json
|
|
13
|
-
|
|
13
|
+
import os
|
|
14
|
+
import tempfile
|
|
15
|
+
from collections.abc import Callable, Iterator, Mapping, Sequence
|
|
14
16
|
from dataclasses import dataclass
|
|
15
17
|
from pathlib import Path
|
|
16
18
|
|
|
@@ -48,6 +50,10 @@ from twopy.hdf5_utils import (
|
|
|
48
50
|
write_string_dataset as _write_string_dataset,
|
|
49
51
|
)
|
|
50
52
|
from twopy.roi import RoiSet, TraceStatistic, make_roi_set
|
|
53
|
+
from twopy.spatial_orientation import (
|
|
54
|
+
require_twopy_spatial_orientation,
|
|
55
|
+
write_twopy_spatial_orientation,
|
|
56
|
+
)
|
|
51
57
|
from twopy.typing_guards import (
|
|
52
58
|
require_bool_array,
|
|
53
59
|
require_float64_array,
|
|
@@ -153,8 +159,11 @@ def save_analysis_outputs(
|
|
|
153
159
|
None.
|
|
154
160
|
|
|
155
161
|
The HDF5 file stores arrays with gzip compression where arrays can be large.
|
|
156
|
-
The HDF5 file is the complete audit trail. CSV files
|
|
157
|
-
|
|
162
|
+
The HDF5 file is the complete audit trail. CSV files are derived exports for
|
|
163
|
+
quick spreadsheet plotting and checks, and are written after the HDF5 file.
|
|
164
|
+
Each output file is written through a temporary sibling path so a failed
|
|
165
|
+
write leaves the previous complete file in place; the HDF5 and CSV files are
|
|
166
|
+
not replaced as one multi-file transaction.
|
|
158
167
|
"""
|
|
159
168
|
summary_outputs = (
|
|
160
169
|
response_summary_trials_csv,
|
|
@@ -171,35 +180,40 @@ def save_analysis_outputs(
|
|
|
171
180
|
if grouped_responses is not None:
|
|
172
181
|
validate_grouped_roi_responses(grouped_responses)
|
|
173
182
|
|
|
174
|
-
output_path
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
183
|
+
def write_hdf5(output_path: Path) -> None:
|
|
184
|
+
"""Write the analysis HDF5 contents to one already-created path."""
|
|
185
|
+
with h5py.File(output_path, "w") as h5_file:
|
|
186
|
+
h5_file.attrs["twopy_format"] = ANALYSIS_OUTPUT_FILE_FORMAT
|
|
187
|
+
if roi_set is not None:
|
|
188
|
+
_write_roi_set(h5_file.create_group("roi_set"), roi_set)
|
|
189
|
+
if traces is not None:
|
|
190
|
+
_write_background_traces(h5_file.create_group("traces"), traces)
|
|
191
|
+
if dff is not None:
|
|
192
|
+
_write_dff(h5_file.create_group("dff"), dff)
|
|
193
|
+
if len(epoch_windows) > 0:
|
|
194
|
+
_write_epoch_windows(
|
|
195
|
+
h5_file.create_group("epoch_windows"),
|
|
196
|
+
epoch_windows,
|
|
197
|
+
)
|
|
198
|
+
if len(baseline_windows) > 0:
|
|
199
|
+
_write_frame_windows(
|
|
200
|
+
h5_file.create_group("baseline_windows"),
|
|
201
|
+
baseline_windows,
|
|
202
|
+
)
|
|
203
|
+
if grouped_responses is not None:
|
|
204
|
+
_write_grouped_responses(
|
|
205
|
+
h5_file.create_group("responses"),
|
|
206
|
+
grouped_responses,
|
|
207
|
+
)
|
|
208
|
+
if response_processing_options is not None:
|
|
209
|
+
write_response_processing_group(
|
|
210
|
+
h5_file.create_group("response_processing"),
|
|
211
|
+
options=response_processing_options,
|
|
212
|
+
normalization_factors=normalization_factors,
|
|
213
|
+
correlation_scores=correlation_scores,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
_write_file_atomically(path, write_hdf5)
|
|
203
217
|
|
|
204
218
|
if grouped_responses is not None:
|
|
205
219
|
if response_summary_trials_csv is not None:
|
|
@@ -214,6 +228,58 @@ def save_analysis_outputs(
|
|
|
214
228
|
)
|
|
215
229
|
|
|
216
230
|
|
|
231
|
+
def _write_file_atomically(
|
|
232
|
+
path: Path,
|
|
233
|
+
write_file: Callable[[Path], None],
|
|
234
|
+
) -> None:
|
|
235
|
+
"""Write a file beside its final path, then replace the old file.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
path: Final destination path.
|
|
239
|
+
write_file: Callback that writes a complete file at the provided
|
|
240
|
+
temporary path.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
None.
|
|
244
|
+
|
|
245
|
+
Existing analysis files are useful audit records. Writing through a sibling
|
|
246
|
+
temporary path keeps a failed save from replacing the last complete output.
|
|
247
|
+
"""
|
|
248
|
+
output_path = path.expanduser()
|
|
249
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
250
|
+
temp_descriptor, temp_name = tempfile.mkstemp(
|
|
251
|
+
prefix=f".{output_path.name}.",
|
|
252
|
+
suffix=".tmp",
|
|
253
|
+
dir=output_path.parent,
|
|
254
|
+
)
|
|
255
|
+
os.close(temp_descriptor)
|
|
256
|
+
temp_path = Path(temp_name)
|
|
257
|
+
try:
|
|
258
|
+
write_file(temp_path)
|
|
259
|
+
temp_path.chmod(_replacement_file_mode(output_path))
|
|
260
|
+
temp_path.replace(output_path)
|
|
261
|
+
except Exception:
|
|
262
|
+
temp_path.unlink(missing_ok=True)
|
|
263
|
+
raise
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _replacement_file_mode(path: Path) -> int:
|
|
267
|
+
"""Return the file mode an atomic replacement should keep.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
path: Final destination path.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Existing file permissions, or normal new-file permissions after the
|
|
274
|
+
process umask.
|
|
275
|
+
"""
|
|
276
|
+
if path.exists():
|
|
277
|
+
return path.stat().st_mode & 0o777
|
|
278
|
+
current_umask = os.umask(0)
|
|
279
|
+
os.umask(current_umask)
|
|
280
|
+
return 0o666 & ~current_umask
|
|
281
|
+
|
|
282
|
+
|
|
217
283
|
def load_analysis_outputs(path: Path) -> LoadedAnalysisOutputs:
|
|
218
284
|
"""Load twopy analysis outputs from HDF5.
|
|
219
285
|
|
|
@@ -238,7 +304,9 @@ def load_analysis_outputs(path: Path) -> LoadedAnalysisOutputs:
|
|
|
238
304
|
return LoadedAnalysisOutputs(
|
|
239
305
|
path=input_path,
|
|
240
306
|
roi_set=(
|
|
241
|
-
_read_roi_set(h5_file["roi_set"])
|
|
307
|
+
_read_roi_set(h5_file["roi_set"], path=input_path)
|
|
308
|
+
if "roi_set" in h5_file
|
|
309
|
+
else None
|
|
242
310
|
),
|
|
243
311
|
traces=(
|
|
244
312
|
_read_background_traces(h5_file["traces"])
|
|
@@ -291,25 +359,31 @@ def write_response_summary_trials_csv(
|
|
|
291
359
|
|
|
292
360
|
Returns:
|
|
293
361
|
None.
|
|
362
|
+
|
|
363
|
+
The CSV is written through a temporary sibling path so a failed write leaves
|
|
364
|
+
the previous complete file in place.
|
|
294
365
|
"""
|
|
295
|
-
output_path = path.expanduser()
|
|
296
|
-
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
297
366
|
validate_grouped_roi_responses(grouped_responses)
|
|
298
367
|
time_columns = _time_columns(grouped_responses.trials)
|
|
299
368
|
fieldnames = (*_TRIAL_RESPONSE_CSV_BASE_COLUMNS, *time_columns)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
369
|
+
|
|
370
|
+
def write_csv(output_path: Path) -> None:
|
|
371
|
+
"""Write trial-level rows to one complete CSV file."""
|
|
372
|
+
with output_path.open("w", encoding="utf-8", newline="") as csv_file:
|
|
373
|
+
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
|
|
374
|
+
writer.writeheader()
|
|
375
|
+
for trial in grouped_responses.trials:
|
|
376
|
+
for roi_index, roi_label in enumerate(grouped_responses.roi_labels):
|
|
377
|
+
writer.writerow(
|
|
378
|
+
_trial_response_row(
|
|
379
|
+
trial,
|
|
380
|
+
roi_index=roi_index,
|
|
381
|
+
roi_label=roi_label,
|
|
382
|
+
time_columns=time_columns,
|
|
383
|
+
),
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
_write_file_atomically(path, write_csv)
|
|
313
387
|
|
|
314
388
|
|
|
315
389
|
def write_response_summary_grouped_csv(
|
|
@@ -324,17 +398,23 @@ def write_response_summary_grouped_csv(
|
|
|
324
398
|
|
|
325
399
|
Returns:
|
|
326
400
|
None.
|
|
401
|
+
|
|
402
|
+
The CSV is written through a temporary sibling path so a failed write leaves
|
|
403
|
+
the previous complete file in place.
|
|
327
404
|
"""
|
|
328
|
-
output_path = path.expanduser()
|
|
329
|
-
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
330
405
|
validate_grouped_roi_responses(grouped_responses)
|
|
331
406
|
time_columns = _time_columns(grouped_responses.trials)
|
|
332
407
|
fieldnames = (*_GROUPED_RESPONSE_CSV_BASE_COLUMNS, *time_columns)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
writer.
|
|
408
|
+
|
|
409
|
+
def write_csv(output_path: Path) -> None:
|
|
410
|
+
"""Write grouped rows to one complete CSV file."""
|
|
411
|
+
with output_path.open("w", encoding="utf-8", newline="") as csv_file:
|
|
412
|
+
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
|
|
413
|
+
writer.writeheader()
|
|
414
|
+
for row in _iter_grouped_response_rows(grouped_responses, time_columns):
|
|
415
|
+
writer.writerow(row)
|
|
416
|
+
|
|
417
|
+
_write_file_atomically(path, write_csv)
|
|
338
418
|
|
|
339
419
|
|
|
340
420
|
def _write_roi_set(group: h5py.Group, roi_set: RoiSet) -> None:
|
|
@@ -347,19 +427,28 @@ def _write_roi_set(group: h5py.Group, roi_set: RoiSet) -> None:
|
|
|
347
427
|
Returns:
|
|
348
428
|
None.
|
|
349
429
|
"""
|
|
350
|
-
group
|
|
430
|
+
write_twopy_spatial_orientation(group)
|
|
431
|
+
masks = group.create_dataset("masks", data=roi_set.masks, compression="gzip")
|
|
432
|
+
write_twopy_spatial_orientation(masks)
|
|
351
433
|
_write_string_dataset(group, "labels", roi_set.labels)
|
|
352
434
|
|
|
353
435
|
|
|
354
|
-
def _read_roi_set(group: h5py.Group) -> RoiSet:
|
|
436
|
+
def _read_roi_set(group: h5py.Group, *, path: Path) -> RoiSet:
|
|
355
437
|
"""Read a persisted ROI set.
|
|
356
438
|
|
|
357
439
|
Args:
|
|
358
440
|
group: HDF5 ``roi_set`` group.
|
|
441
|
+
path: Analysis output path used in validation errors.
|
|
359
442
|
|
|
360
443
|
Returns:
|
|
361
444
|
``RoiSet`` with validated masks and labels.
|
|
362
445
|
"""
|
|
446
|
+
require_twopy_spatial_orientation(group, path=path, label="roi_set")
|
|
447
|
+
require_twopy_spatial_orientation(
|
|
448
|
+
group["masks"],
|
|
449
|
+
path=path,
|
|
450
|
+
label="roi_set/masks",
|
|
451
|
+
)
|
|
363
452
|
return make_roi_set(
|
|
364
453
|
require_bool_array(group["masks"][()], name="roi_set/masks", ndim=3),
|
|
365
454
|
labels=_read_string_dataset(group, "labels"),
|
|
@@ -879,10 +968,10 @@ def _trial_response_row(
|
|
|
879
968
|
return row
|
|
880
969
|
|
|
881
970
|
|
|
882
|
-
def
|
|
971
|
+
def _iter_grouped_response_rows(
|
|
883
972
|
grouped: GroupedRoiResponses,
|
|
884
973
|
time_columns: tuple[str, ...],
|
|
885
|
-
) ->
|
|
974
|
+
) -> Iterator[dict[str, str | int | float]]:
|
|
886
975
|
"""Convert grouped trial responses to mean, SEM, and count CSV rows.
|
|
887
976
|
|
|
888
977
|
Args:
|
|
@@ -890,12 +979,12 @@ def _grouped_response_rows(
|
|
|
890
979
|
time_columns: Complete CSV time-column union.
|
|
891
980
|
|
|
892
981
|
Returns:
|
|
893
|
-
|
|
982
|
+
Iterator over rows keyed by epoch, ROI, statistic, and relative
|
|
983
|
+
timepoint.
|
|
894
984
|
|
|
895
985
|
Counts are per timepoint rather than one scalar because pre/post context or
|
|
896
986
|
clipped recording edges can make some trials shorter than others.
|
|
897
987
|
"""
|
|
898
|
-
rows: list[dict[str, str | int | float]] = []
|
|
899
988
|
for epoch_number, epoch_name, trials in _trials_by_epoch(grouped.trials):
|
|
900
989
|
for roi_index, roi_label in enumerate(grouped.roi_labels):
|
|
901
990
|
values_by_time = _values_by_time_column(
|
|
@@ -903,16 +992,13 @@ def _grouped_response_rows(
|
|
|
903
992
|
roi_index=roi_index,
|
|
904
993
|
time_columns=time_columns,
|
|
905
994
|
)
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
time_columns=time_columns,
|
|
913
|
-
),
|
|
995
|
+
yield from _epoch_roi_statistic_rows(
|
|
996
|
+
epoch_number=epoch_number,
|
|
997
|
+
epoch_name=epoch_name,
|
|
998
|
+
roi_label=roi_label,
|
|
999
|
+
values_by_time=values_by_time,
|
|
1000
|
+
time_columns=time_columns,
|
|
914
1001
|
)
|
|
915
|
-
return tuple(rows)
|
|
916
1002
|
|
|
917
1003
|
|
|
918
1004
|
def _trials_by_epoch(
|
|
@@ -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")
|