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