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.
- {twopy-0.2.1 → twopy-0.3.0}/PKG-INFO +1 -1
- {twopy-0.2.1 → twopy-0.3.0}/pyproject.toml +1 -1
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/persistence.py +18 -3
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_maps.py +57 -7
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis_cache.py +84 -22
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/conversion/alignment_crop.py +18 -13
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/conversion/api.py +21 -5
- {twopy-0.2.1 → 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.1 → twopy-0.3.0}/src/twopy/conversion/types.py +1 -1
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/converted.py +26 -3
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/__init__.py +6 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/parameters.py +4 -3
- twopy-0.3.0/src/twopy/custom/runner.py +244 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/controls.py +81 -15
- twopy-0.3.0/src/twopy/napari/display.py +106 -0
- 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.1 → twopy-0.3.0}/src/twopy/napari/load_workflow.py +225 -83
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/loaded_recordings_csv.py +35 -4
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/loading.py +22 -2
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/dynamic_options.py +60 -14
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/response_map_area.py +5 -8
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/response_plot_widget.py +68 -84
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/export.py +11 -19
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/roi.py +91 -22
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/viewer.py +206 -67
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/roi.py +26 -5
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/spatial.py +3 -3
- twopy-0.3.0/src/twopy/spatial_orientation.py +78 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy.egg-info/PKG-INFO +1 -1
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy.egg-info/SOURCES.txt +6 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_background_subtraction.py +22 -1
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_conversion.py +113 -10
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_converted.py +64 -2
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_custom_workflows.py +156 -1
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_function_inventory.py +45 -4
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_kernels.py +125 -1
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_load_workflow.py +58 -51
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari.py +54 -40
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_display.py +12 -31
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_export.py +5 -3
- twopy-0.3.0/tests/test_napari_load_progress.py +48 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_load_tab.py +19 -5
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_loaded_recordings.py +10 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_path_resolution.py +2 -2
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_plot_widgets.py +2 -2
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_response_workflow.py +4 -2
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_roi_generation.py +76 -3
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_roi_visibility.py +9 -1
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_persistence.py +35 -1
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_response_maps.py +115 -11
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_roi.py +31 -1
- twopy-0.2.1/src/twopy/napari/display.py +0 -181
- {twopy-0.2.1 → twopy-0.3.0}/README.md +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/setup.cfg +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/_segments.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/_version.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/background_subtraction.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/dff.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/dff_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/epoch_mapping.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/group_matching.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/kernels/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/kernels/_fitting.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/kernels/_stimulus.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/motion.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_context.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_processing/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_processing/apply.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_processing/options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_processing/persistence.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_processing/qc.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_processing/signals.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/response_window_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/responses.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/timing.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/trials.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/analysis/workflow.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/api.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/calibrations/pixel_calibration_profiles.csv +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/calibrations/pixel_size.csv +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/config.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/conversion/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/conversion/matlab_values.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/conversion/source_loading.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/conversion/stimulus_code.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/discovery.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/native.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/native_workflows/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/native_workflows/direction_selectivity.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/native_workflows/response_kernels.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/provenance.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/registry.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/custom/types.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/cache.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/catalog.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/connection.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/generic.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/modeled.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/search.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/sql.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/database/types.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/filenames.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/frame_ranges.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/hdf5_utils.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/inspection.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/matlab.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/__main__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/custom_tab.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/database_favorites.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/database_search.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/dims.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/display_paths.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/errors.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/cards.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/fov_assignment.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/fov_cards.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/images.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/response_preview.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/responses.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/roi_assignment.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/roi_cards.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/style.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/tables.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/group_matching/window.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/interactive.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/latest_worker.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/launcher.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/live_analysis.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/movie.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/paths.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/data.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/dff_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/api.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/options_panel.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/paths.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/plot_area.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/save_actions.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/docks/visibility_state.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/export_controls.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/form_controls.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/label_visibility.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/normalization_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/panels.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/preview_strip.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/processing_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/response_map_colors.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/response_map_display.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/response_map_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/response_window_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/roi_generation/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/roi_generation/actions.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/roi_generation/controls.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/roi_generation/options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/plotting/widgets.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/protocols.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/responses.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/roi_signatures.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/session.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/sidebar.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/state.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/text.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/trial_timeline.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/napari/types.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/parity/__init__.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/parity/dff.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/parity/psycho5.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/parity/roi.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/parity/roi_extraction.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/parity/saved_analysis.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/photodiode.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/photodiode_classification.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/pixel_calibration.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/pixel_calibration_profiles.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/response_roi_extraction.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/roi_extraction.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/roi_mask_cleanup.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/session.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/stimulus.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/synchronization.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy/typing_guards.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy.egg-info/dependency_links.txt +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy.egg-info/entry_points.txt +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy.egg-info/requires.txt +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/src/twopy.egg-info/top_level.txt +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_api.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_config.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_database.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_dff.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_epoch_mapping.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_filenames.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_frame_ranges.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_group_matching.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_hdf5_utils.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_import.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_inspection.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_matlab.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_motion.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_database_search.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_group_matching_responses.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_group_matching_roi_cards.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_live_controller.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_plot_controls.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_plot_data.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_processing_controls.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_response_map_display.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_text.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_napari_timeline.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_packaging.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_parity.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_parity_psycho5.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_parity_roi_extraction.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_photodiode_classification.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_pixel_calibration.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_pixel_calibration_profiles.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_real_fixture.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_recording_timing.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_response_map_area.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_response_processing.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_response_roi_extraction.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_response_window_options.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_responses.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_roi_extraction.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_roi_mask_cleanup.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_saved_analysis.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_session.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_stimulus.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_synchronization.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_trials.py +0 -0
- {twopy-0.2.1 → twopy-0.3.0}/tests/test_workflow.py +0 -0
|
@@ -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"),
|
|
@@ -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")
|
|
@@ -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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
|
141
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
255
|
+
def _build_sync_plan_from_config(
|
|
210
256
|
*,
|
|
211
|
-
|
|
257
|
+
source_session_dir: Path,
|
|
258
|
+
local_root: Path,
|
|
212
259
|
local_paths: tuple[Path | None, ...],
|
|
213
260
|
config: TwopyConfig,
|
|
214
|
-
) -> AnalysisSyncPlan
|
|
261
|
+
) -> AnalysisSyncPlan:
|
|
215
262
|
"""Return a sync plan from an already-loaded config."""
|
|
216
|
-
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
227
|
-
|
|
228
|
-
|
|
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=
|
|
239
|
+
cutoff=y_cutoff,
|
|
235
240
|
)
|
|
236
241
|
axis1_start, axis1_stop = _crop_bounds_from_cutoff(
|
|
237
242
|
length=spatial_shape[1],
|
|
238
|
-
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
|
|
287
|
-
"""Return the
|
|
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
|
-
``(
|
|
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 =
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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"] =
|
|
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=
|
|
352
|
+
shape=destination_shape,
|
|
334
353
|
dtype=source_dataset.dtype,
|
|
335
|
-
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, :, :] =
|
|
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:
|