TwoPy 0.2.0__tar.gz → 0.2.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.0 → twopy-0.2.1}/PKG-INFO +8 -4
- {twopy-0.2.0 → twopy-0.2.1}/README.md +5 -3
- {twopy-0.2.0 → twopy-0.2.1}/pyproject.toml +9 -1
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/background_subtraction.py +120 -21
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/dff.py +33 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/kernels/__init__.py +2 -2
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/persistence.py +1 -1
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis_cache.py +2 -1
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/config.py +47 -8
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/custom/__init__.py +2 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/custom/native_workflows/direction_selectivity.py +13 -12
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/custom/native_workflows/response_kernels.py +2 -7
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/custom/provenance.py +14 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/custom/types.py +247 -8
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/database/search.py +1 -1
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/controls.py +68 -13
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/custom_tab.py +168 -20
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/database_search.py +41 -1
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/display_paths.py +26 -0
- twopy-0.2.1/src/twopy/napari/group_matching/__init__.py +29 -0
- twopy-0.2.1/src/twopy/napari/group_matching/cards.py +127 -0
- twopy-0.2.1/src/twopy/napari/group_matching/fov_assignment.py +679 -0
- twopy-0.2.1/src/twopy/napari/group_matching/fov_cards.py +143 -0
- twopy-0.2.0/src/twopy/napari/group_matching_images.py → twopy-0.2.1/src/twopy/napari/group_matching/images.py +2 -2
- twopy-0.2.1/src/twopy/napari/group_matching/response_preview.py +334 -0
- twopy-0.2.1/src/twopy/napari/group_matching/responses.py +130 -0
- twopy-0.2.0/src/twopy/napari/group_matching_roi.py → twopy-0.2.1/src/twopy/napari/group_matching/roi_assignment.py +590 -532
- twopy-0.2.1/src/twopy/napari/group_matching/roi_cards.py +371 -0
- twopy-0.2.1/src/twopy/napari/group_matching/style.py +269 -0
- twopy-0.2.1/src/twopy/napari/group_matching/tables.py +27 -0
- twopy-0.2.1/src/twopy/napari/group_matching/window.py +267 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/live_analysis.py +75 -32
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/loading.py +37 -1
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/dff_options.py +1 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/docks/options_panel.py +15 -16
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/docks/paths.py +5 -6
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/docks/plot_area.py +42 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/docks/response_plot_widget.py +189 -19
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/docks/visibility_state.py +29 -2
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/export_controls.py +5 -6
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/normalization_options.py +1 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/options.py +1 -1
- twopy-0.2.1/src/twopy/napari/plotting/panels.py +188 -0
- twopy-0.2.1/src/twopy/napari/plotting/preview_strip.py +218 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/processing_options.py +1 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/widgets.py +403 -26
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/responses.py +58 -1
- twopy-0.2.1/src/twopy/napari/roi_signatures.py +20 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/session.py +20 -3
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/sidebar.py +27 -14
- twopy-0.2.1/src/twopy/napari/state.py +192 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy.egg-info/PKG-INFO +8 -4
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy.egg-info/SOURCES.txt +17 -3
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_config.py +28 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_custom_workflows.py +145 -18
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_dff.py +33 -3
- twopy-0.2.1/tests/test_function_inventory.py +155 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_kernels.py +7 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari.py +175 -6
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_database_search.py +60 -2
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_export.py +72 -2
- twopy-0.2.1/tests/test_napari_group_matching_responses.py +140 -0
- twopy-0.2.1/tests/test_napari_group_matching_roi_cards.py +156 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_load_tab.py +189 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_loaded_recordings.py +396 -18
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_path_resolution.py +40 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_plot_controls.py +104 -1
- twopy-0.2.1/tests/test_napari_plot_widgets.py +1808 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_processing_controls.py +100 -3
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_roi_visibility.py +18 -0
- twopy-0.2.0/src/twopy/napari/group_matching.py +0 -596
- twopy-0.2.0/src/twopy/napari/plotting/panels.py +0 -103
- twopy-0.2.0/src/twopy/napari/state.py +0 -106
- twopy-0.2.0/tests/test_napari_plot_widgets.py +0 -799
- {twopy-0.2.0 → twopy-0.2.1}/setup.cfg +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/_segments.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/_version.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/dff_options.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/epoch_mapping.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/group_matching.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/kernels/_fitting.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/kernels/_stimulus.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/motion.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/response_context.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/response_maps.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/response_processing/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/response_processing/apply.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/response_processing/options.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/response_processing/persistence.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/response_processing/qc.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/response_processing/signals.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/response_window_options.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/responses.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/timing.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/trials.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/analysis/workflow.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/api.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/calibrations/pixel_calibration_profiles.csv +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/calibrations/pixel_size.csv +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/conversion/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/conversion/alignment_crop.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/conversion/api.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/conversion/hdf5_writing.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/conversion/matlab_values.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/conversion/source_loading.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/conversion/stimulus_code.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/conversion/types.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/converted.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/custom/discovery.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/custom/native.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/custom/native_workflows/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/custom/parameters.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/custom/registry.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/database/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/database/cache.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/database/catalog.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/database/connection.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/database/generic.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/database/modeled.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/database/sql.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/database/types.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/filenames.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/frame_ranges.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/hdf5_utils.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/inspection.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/matlab.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/__main__.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/database_favorites.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/dims.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/display.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/errors.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/interactive.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/latest_worker.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/launcher.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/load_workflow.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/loaded_recordings_csv.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/movie.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/paths.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/data.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/docks/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/docks/api.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/docks/dynamic_options.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/docks/response_map_area.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/docks/save_actions.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/export.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/form_controls.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/label_visibility.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/response_map_colors.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/response_map_display.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/response_map_options.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/response_window_options.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/roi_generation/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/roi_generation/actions.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/roi_generation/controls.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/plotting/roi_generation/options.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/protocols.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/roi.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/text.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/trial_timeline.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/types.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/napari/viewer.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/parity/__init__.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/parity/dff.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/parity/psycho5.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/parity/roi.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/parity/roi_extraction.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/parity/saved_analysis.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/photodiode.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/photodiode_classification.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/pixel_calibration.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/pixel_calibration_profiles.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/response_roi_extraction.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/roi.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/roi_extraction.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/roi_mask_cleanup.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/session.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/spatial.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/stimulus.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/synchronization.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy/typing_guards.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy.egg-info/dependency_links.txt +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy.egg-info/entry_points.txt +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy.egg-info/requires.txt +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/src/twopy.egg-info/top_level.txt +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_api.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_background_subtraction.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_conversion.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_converted.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_database.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_epoch_mapping.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_filenames.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_frame_ranges.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_group_matching.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_hdf5_utils.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_import.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_inspection.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_load_workflow.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_matlab.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_motion.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_display.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_live_controller.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_plot_data.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_response_map_display.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_response_workflow.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_roi_generation.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_text.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_napari_timeline.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_packaging.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_parity.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_parity_psycho5.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_parity_roi_extraction.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_persistence.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_photodiode_classification.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_pixel_calibration.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_pixel_calibration_profiles.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_real_fixture.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_recording_timing.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_response_map_area.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_response_maps.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_response_processing.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_response_roi_extraction.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_response_window_options.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_responses.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_roi.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_roi_extraction.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_roi_mask_cleanup.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_saved_analysis.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_session.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_stimulus.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_synchronization.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_trials.py +0 -0
- {twopy-0.2.0 → twopy-0.2.1}/tests/test_workflow.py +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: twopy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Two-photon imaging analysis tool with a napari interface.
|
|
5
5
|
Author-email: Gustavo Madeira Santana <fromweb1@gumadeiras.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://twopy.gumadeiras.com
|
|
8
|
+
Project-URL: Repository, https://github.com/gumadeiras/twopy
|
|
7
9
|
Requires-Python: <3.14,>=3.13
|
|
8
10
|
Description-Content-Type: text/markdown
|
|
9
11
|
Requires-Dist: h5py<4,>=3.16
|
|
@@ -73,9 +75,11 @@ When a source recording has not been converted yet, twopy converts it first. Con
|
|
|
73
75
|
|
|
74
76
|
## Docs
|
|
75
77
|
|
|
76
|
-
- [
|
|
77
|
-
- [
|
|
78
|
-
- [
|
|
78
|
+
- [Full documentation](https://twopy.gumadeiras.com)
|
|
79
|
+
- [Getting started](docs/getting_started.md): install, configure, launch, and save your first analysis.
|
|
80
|
+
- [GUI guide](docs/gui/index.md): one page per task — loading, ROIs, plots, custom workflows, group matching, saving.
|
|
81
|
+
- [Python API guide](docs/python_api.md): find recordings, convert data, extract traces, fit kernels, and open napari from scripts.
|
|
82
|
+
- [Writing custom workflows](docs/writing_custom_workflows.md): add your own analysis to the napari Custom tab.
|
|
79
83
|
- [Development guide](docs/development.md): source setup and local checks.
|
|
80
84
|
- [Input data spec](docs/input_data_spec.md): short recording folder contract.
|
|
81
85
|
- [Recording file reference](docs/recording_file_schema.md): detailed source and converted file schema.
|
|
@@ -53,9 +53,11 @@ When a source recording has not been converted yet, twopy converts it first. Con
|
|
|
53
53
|
|
|
54
54
|
## Docs
|
|
55
55
|
|
|
56
|
-
- [
|
|
57
|
-
- [
|
|
58
|
-
- [
|
|
56
|
+
- [Full documentation](https://twopy.gumadeiras.com)
|
|
57
|
+
- [Getting started](docs/getting_started.md): install, configure, launch, and save your first analysis.
|
|
58
|
+
- [GUI guide](docs/gui/index.md): one page per task — loading, ROIs, plots, custom workflows, group matching, saving.
|
|
59
|
+
- [Python API guide](docs/python_api.md): find recordings, convert data, extract traces, fit kernels, and open napari from scripts.
|
|
60
|
+
- [Writing custom workflows](docs/writing_custom_workflows.md): add your own analysis to the napari Custom tab.
|
|
59
61
|
- [Development guide](docs/development.md): source setup and local checks.
|
|
60
62
|
- [Input data spec](docs/input_data_spec.md): short recording folder contract.
|
|
61
63
|
- [Recording file reference](docs/recording_file_schema.md): detailed source and converted file schema.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "twopy"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "Two-photon imaging analysis tool with a napari interface."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13,<3.14"
|
|
@@ -29,6 +29,10 @@ dependencies = [
|
|
|
29
29
|
[project.scripts]
|
|
30
30
|
twopy = "twopy.napari:main"
|
|
31
31
|
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://twopy.gumadeiras.com"
|
|
34
|
+
Repository = "https://github.com/gumadeiras/twopy"
|
|
35
|
+
|
|
32
36
|
[tool.setuptools.packages.find]
|
|
33
37
|
where = ["src"]
|
|
34
38
|
|
|
@@ -43,11 +47,15 @@ target-version = "py313"
|
|
|
43
47
|
[tool.ruff.lint]
|
|
44
48
|
select = [
|
|
45
49
|
"ANN",
|
|
50
|
+
"ARG",
|
|
46
51
|
"B",
|
|
52
|
+
"C4",
|
|
47
53
|
"D",
|
|
48
54
|
"E",
|
|
49
55
|
"F",
|
|
50
56
|
"I",
|
|
57
|
+
"NPY",
|
|
58
|
+
"PTH",
|
|
51
59
|
"SIM",
|
|
52
60
|
"UP",
|
|
53
61
|
"W",
|
|
@@ -15,6 +15,7 @@ from typing import Literal
|
|
|
15
15
|
|
|
16
16
|
import numpy as np
|
|
17
17
|
import numpy.typing as npt
|
|
18
|
+
from scipy import sparse
|
|
18
19
|
|
|
19
20
|
from twopy.converted import RecordingData
|
|
20
21
|
from twopy.frame_ranges import normalize_frame_range
|
|
@@ -278,6 +279,7 @@ def _extract_mean_traces_from_indices(
|
|
|
278
279
|
rather than total recording length.
|
|
279
280
|
"""
|
|
280
281
|
traces = np.empty((frame_stop - frame_start, len(pixel_indices_by_trace)))
|
|
282
|
+
projection = _mean_projection(pixel_indices_by_trace)
|
|
281
283
|
_check_cancelled(check_cancelled)
|
|
282
284
|
for chunk_start, chunk_stop, frames in recording.movie.iter_frame_batches(
|
|
283
285
|
chunk_frames=chunk_frames,
|
|
@@ -289,10 +291,7 @@ def _extract_mean_traces_from_indices(
|
|
|
289
291
|
chunk_offset = chunk_start - frame_start
|
|
290
292
|
chunk_slice = slice(chunk_offset, chunk_offset + (chunk_stop - chunk_start))
|
|
291
293
|
chunk_flat = frames.reshape(frames.shape[0], -1)
|
|
292
|
-
|
|
293
|
-
traces[chunk_slice, trace_index] = chunk_flat[:, pixel_indices].mean(
|
|
294
|
-
axis=1,
|
|
295
|
-
)
|
|
294
|
+
traces[chunk_slice, :] = _project_trace_pixels(chunk_flat, projection)
|
|
296
295
|
_check_cancelled(check_cancelled)
|
|
297
296
|
|
|
298
297
|
return traces
|
|
@@ -344,6 +343,7 @@ def _extract_movie_global_percentile_corrected_traces(
|
|
|
344
343
|
)
|
|
345
344
|
frame_count = frame_stop - frame_start
|
|
346
345
|
roi_count = len(roi_pixel_indices)
|
|
346
|
+
projection = _mean_projection(roi_pixel_indices)
|
|
347
347
|
raw_values = np.empty((frame_count, roi_count), dtype=np.float64)
|
|
348
348
|
background_values = np.empty((frame_count, roi_count), dtype=np.float64)
|
|
349
349
|
corrected_values = np.empty((frame_count, roi_count), dtype=np.float64)
|
|
@@ -359,14 +359,19 @@ def _extract_movie_global_percentile_corrected_traces(
|
|
|
359
359
|
chunk_slice = slice(chunk_offset, chunk_offset + (chunk_stop - chunk_start))
|
|
360
360
|
chunk_flat = frames.reshape(frames.shape[0], -1)
|
|
361
361
|
background_trace = chunk_flat[:, background_indices].mean(axis=1)
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
362
|
+
roi_pixels = chunk_flat[:, projection.pixel_indices]
|
|
363
|
+
raw_values[chunk_slice, :] = _project_selected_pixels(roi_pixels, projection)
|
|
364
|
+
background_values[chunk_slice, :] = background_trace[:, np.newaxis]
|
|
365
|
+
# Clamp pixels before averaging so dim pixels cannot pull the
|
|
366
|
+
# corrected ROI trace below zero.
|
|
367
|
+
corrected_pixels = np.maximum(
|
|
368
|
+
roi_pixels - background_trace[:, np.newaxis],
|
|
369
|
+
0.0,
|
|
370
|
+
)
|
|
371
|
+
corrected_values[chunk_slice, :] = _project_selected_pixels(
|
|
372
|
+
corrected_pixels,
|
|
373
|
+
projection,
|
|
374
|
+
)
|
|
370
375
|
_check_cancelled(check_cancelled)
|
|
371
376
|
|
|
372
377
|
metadata: dict[str, BackgroundMetadataValue] = {
|
|
@@ -440,6 +445,7 @@ def _extract_movie_y_stripe_percentile_corrected_traces(
|
|
|
440
445
|
)
|
|
441
446
|
frame_count = frame_stop - frame_start
|
|
442
447
|
roi_count = len(roi_pixel_indices)
|
|
448
|
+
projection = _mean_projection(roi_pixel_indices)
|
|
443
449
|
raw_values = np.empty((frame_count, roi_count), dtype=np.float64)
|
|
444
450
|
background_values = np.empty((frame_count, roi_count), dtype=np.float64)
|
|
445
451
|
corrected_values = np.empty((frame_count, roi_count), dtype=np.float64)
|
|
@@ -460,15 +466,21 @@ def _extract_movie_y_stripe_percentile_corrected_traces(
|
|
|
460
466
|
)
|
|
461
467
|
chunk_flat = frames.reshape(frames.shape[0], -1)
|
|
462
468
|
background_flat = background_field.reshape(background_field.shape[0], -1)
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
469
|
+
roi_pixels = chunk_flat[:, projection.pixel_indices]
|
|
470
|
+
roi_background_pixels = background_flat[:, projection.pixel_indices]
|
|
471
|
+
raw_values[chunk_slice, :] = _project_selected_pixels(roi_pixels, projection)
|
|
472
|
+
background_values[chunk_slice, :] = _project_selected_pixels(
|
|
473
|
+
roi_background_pixels,
|
|
474
|
+
projection,
|
|
475
|
+
)
|
|
476
|
+
corrected_pixels = np.maximum(
|
|
477
|
+
roi_pixels - roi_background_pixels,
|
|
478
|
+
0.0,
|
|
479
|
+
)
|
|
480
|
+
corrected_values[chunk_slice, :] = _project_selected_pixels(
|
|
481
|
+
corrected_pixels,
|
|
482
|
+
projection,
|
|
483
|
+
)
|
|
472
484
|
_check_cancelled(check_cancelled)
|
|
473
485
|
|
|
474
486
|
metadata: dict[str, BackgroundMetadataValue] = {
|
|
@@ -593,6 +605,93 @@ def _roi_pixel_indices_from_masks(
|
|
|
593
605
|
return tuple(np.flatnonzero(mask).astype(np.int64) for mask in flat_masks)
|
|
594
606
|
|
|
595
607
|
|
|
608
|
+
@dataclass(frozen=True)
|
|
609
|
+
class _MeanProjection:
|
|
610
|
+
"""Sparse averaging matrix plus selected flattened pixel indices."""
|
|
611
|
+
|
|
612
|
+
pixel_indices: npt.NDArray[np.int64]
|
|
613
|
+
matrix: sparse.csc_matrix
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def _mean_projection(
|
|
617
|
+
pixel_indices_by_trace: tuple[npt.NDArray[np.int64], ...],
|
|
618
|
+
) -> _MeanProjection:
|
|
619
|
+
"""Return selected pixels and a sparse pixel-to-trace averaging matrix.
|
|
620
|
+
|
|
621
|
+
Args:
|
|
622
|
+
pixel_indices_by_trace: One flattened pixel-index array per trace.
|
|
623
|
+
|
|
624
|
+
Returns:
|
|
625
|
+
Selected flattened pixel indices plus a sparse matrix shaped
|
|
626
|
+
``(selected_pixels, traces)`` whose columns average each trace.
|
|
627
|
+
|
|
628
|
+
Pixel indices are concatenated per trace instead of deduplicated. That
|
|
629
|
+
preserves the existing behavior for overlapping ROIs, where the same movie
|
|
630
|
+
pixel can contribute independently to more than one ROI mean.
|
|
631
|
+
"""
|
|
632
|
+
selected_pixel_indices: list[npt.NDArray[np.int64]] = []
|
|
633
|
+
rows: list[npt.NDArray[np.int64]] = []
|
|
634
|
+
columns: list[npt.NDArray[np.int64]] = []
|
|
635
|
+
values: list[npt.NDArray[np.float64]] = []
|
|
636
|
+
row_start = 0
|
|
637
|
+
for trace_index, pixel_indices in enumerate(pixel_indices_by_trace):
|
|
638
|
+
if pixel_indices.size == 0:
|
|
639
|
+
continue
|
|
640
|
+
selected_pixel_indices.append(pixel_indices)
|
|
641
|
+
rows.append(
|
|
642
|
+
np.arange(
|
|
643
|
+
row_start,
|
|
644
|
+
row_start + pixel_indices.size,
|
|
645
|
+
dtype=np.int64,
|
|
646
|
+
),
|
|
647
|
+
)
|
|
648
|
+
row_start += pixel_indices.size
|
|
649
|
+
columns.append(
|
|
650
|
+
np.full(pixel_indices.shape, trace_index, dtype=np.int64),
|
|
651
|
+
)
|
|
652
|
+
values.append(
|
|
653
|
+
np.full(
|
|
654
|
+
pixel_indices.shape,
|
|
655
|
+
1.0 / float(pixel_indices.size),
|
|
656
|
+
dtype=np.float64,
|
|
657
|
+
),
|
|
658
|
+
)
|
|
659
|
+
if len(selected_pixel_indices) == 0:
|
|
660
|
+
return _MeanProjection(
|
|
661
|
+
pixel_indices=np.array([], dtype=np.int64),
|
|
662
|
+
matrix=sparse.csc_matrix((0, len(pixel_indices_by_trace))),
|
|
663
|
+
)
|
|
664
|
+
return _MeanProjection(
|
|
665
|
+
pixel_indices=np.concatenate(selected_pixel_indices),
|
|
666
|
+
matrix=sparse.csc_matrix(
|
|
667
|
+
(
|
|
668
|
+
np.concatenate(values),
|
|
669
|
+
(np.concatenate(rows), np.concatenate(columns)),
|
|
670
|
+
),
|
|
671
|
+
shape=(row_start, len(pixel_indices_by_trace)),
|
|
672
|
+
),
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def _project_trace_pixels(
|
|
677
|
+
chunk_flat: npt.NDArray[np.float64],
|
|
678
|
+
projection: _MeanProjection,
|
|
679
|
+
) -> npt.NDArray[np.float64]:
|
|
680
|
+
"""Return trace means from a flattened movie chunk and projection."""
|
|
681
|
+
return _project_selected_pixels(chunk_flat[:, projection.pixel_indices], projection)
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def _project_selected_pixels(
|
|
685
|
+
selected_pixels: npt.NDArray[np.float64],
|
|
686
|
+
projection: _MeanProjection,
|
|
687
|
+
) -> npt.NDArray[np.float64]:
|
|
688
|
+
"""Return trace means from selected pixel columns."""
|
|
689
|
+
return np.asarray(
|
|
690
|
+
selected_pixels @ projection.matrix,
|
|
691
|
+
dtype=np.float64,
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
|
|
596
695
|
def _check_cancelled(check_cancelled: Callable[[], None] | None) -> None:
|
|
597
696
|
"""Raise ``CancelledError`` when an interactive caller superseded this work."""
|
|
598
697
|
if check_cancelled is None:
|
|
@@ -115,6 +115,10 @@ def compute_roi_delta_f_over_f(
|
|
|
115
115
|
data_rate_hz=data_rate_hz,
|
|
116
116
|
baseline_sample_seconds=baseline_sample_seconds,
|
|
117
117
|
)
|
|
118
|
+
_validate_baseline_samples_for_background_method(
|
|
119
|
+
traces=traces,
|
|
120
|
+
baseline_samples=baseline_samples,
|
|
121
|
+
)
|
|
118
122
|
tau = _fit_shared_tau(
|
|
119
123
|
frame_numbers=baseline_samples.frame_numbers,
|
|
120
124
|
mean_fluorescence=baseline_samples.values.mean(axis=1),
|
|
@@ -274,6 +278,35 @@ def _baseline_samples(
|
|
|
274
278
|
)
|
|
275
279
|
|
|
276
280
|
|
|
281
|
+
def _validate_baseline_samples_for_background_method(
|
|
282
|
+
*,
|
|
283
|
+
traces: BackgroundCorrectedRoiTraces,
|
|
284
|
+
baseline_samples: _BaselineSamples,
|
|
285
|
+
) -> None:
|
|
286
|
+
"""Reject background-corrected baselines that cannot support dF/F."""
|
|
287
|
+
if traces.method != "roi_y_stripe_percentile":
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
invalid = ~np.isfinite(baseline_samples.values) | (baseline_samples.values <= 0.0)
|
|
291
|
+
if not np.any(invalid):
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
roi_indices = np.flatnonzero(np.any(invalid, axis=0))
|
|
295
|
+
labels = ", ".join(traces.labels[index] for index in roi_indices[:5])
|
|
296
|
+
if roi_indices.size > 5:
|
|
297
|
+
labels = f"{labels}, ..."
|
|
298
|
+
minimum = float(np.nanmin(baseline_samples.values[:, roi_indices]))
|
|
299
|
+
msg = (
|
|
300
|
+
"ROI y-stripe P% background subtraction over-subtracted baseline "
|
|
301
|
+
f"fluorescence for {labels}; minimum corrected baseline sample is "
|
|
302
|
+
f"{minimum:.6g}. dF/F needs positive baseline fluorescence, so the "
|
|
303
|
+
"local y-stripe pixels are not a valid additive background for those "
|
|
304
|
+
"ROIs. Use shared y-stripe P%, global percentile, or revise the ROIs "
|
|
305
|
+
"to leave dim local background pixels."
|
|
306
|
+
)
|
|
307
|
+
raise ValueError(msg)
|
|
308
|
+
|
|
309
|
+
|
|
277
310
|
def _validate_baseline_local_range(
|
|
278
311
|
*,
|
|
279
312
|
window: FrameWindow,
|
|
@@ -80,8 +80,8 @@ class StimulusKernelOptions:
|
|
|
80
80
|
baseline_epoch_number: int = 1
|
|
81
81
|
discard_first_stimulus_epoch: bool = True
|
|
82
82
|
selected_epoch_numbers: tuple[int, ...] | None = None
|
|
83
|
-
num_stim_past: int =
|
|
84
|
-
num_stim_future: int =
|
|
83
|
+
num_stim_past: int = 100
|
|
84
|
+
num_stim_future: int = 10
|
|
85
85
|
method: KernelFitMethod = "ols"
|
|
86
86
|
hemisphere: Hemisphere | None = None
|
|
87
87
|
|
|
@@ -869,7 +869,7 @@ def _trial_response_row(
|
|
|
869
869
|
"stop_frame": trial.stop_frame,
|
|
870
870
|
"frame_count": trial.stop_frame - trial.start_frame,
|
|
871
871
|
}
|
|
872
|
-
row.update(
|
|
872
|
+
row.update(dict.fromkeys(time_columns, ""))
|
|
873
873
|
for time_seconds, value in zip(
|
|
874
874
|
trial.time_seconds,
|
|
875
875
|
trial.values[:, roi_index],
|
|
@@ -10,6 +10,7 @@ files are removed from the local cache after they copy successfully because the
|
|
|
10
10
|
publish destination is their durable location.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
+
import os
|
|
13
14
|
import shutil
|
|
14
15
|
import tempfile
|
|
15
16
|
from concurrent.futures import Executor, Future
|
|
@@ -266,7 +267,7 @@ def copy_file_atomically(source: Path, target: Path) -> None:
|
|
|
266
267
|
temp_path = Path(temp_name)
|
|
267
268
|
try:
|
|
268
269
|
with (
|
|
269
|
-
|
|
270
|
+
os.fdopen(temp_descriptor, "wb") as temp_file,
|
|
270
271
|
source.open(
|
|
271
272
|
"rb",
|
|
272
273
|
) as source_file,
|
|
@@ -21,6 +21,7 @@ __all__ = [
|
|
|
21
21
|
"DatabaseAccess",
|
|
22
22
|
"TwopyConfig",
|
|
23
23
|
"data_path_match",
|
|
24
|
+
"data_path_recording_candidates",
|
|
24
25
|
"load_config",
|
|
25
26
|
"resolve_data_recording_path",
|
|
26
27
|
"resolve_analysis_cache_dir",
|
|
@@ -84,7 +85,7 @@ def load_config(path: Path = DEFAULT_CONFIG_PATH) -> TwopyConfig:
|
|
|
84
85
|
msg = f"twopy config must be a YAML mapping: {config_path}"
|
|
85
86
|
raise ValueError(msg)
|
|
86
87
|
|
|
87
|
-
raw_config: dict[object, object] =
|
|
88
|
+
raw_config: dict[object, object] = dict(loaded.items())
|
|
88
89
|
|
|
89
90
|
return TwopyConfig(
|
|
90
91
|
database_path=_required_path(raw_config, "database_path", config_path),
|
|
@@ -136,13 +137,7 @@ def resolve_data_recording_path(
|
|
|
136
137
|
function is the ordered availability check that picks the preferred mounted
|
|
137
138
|
copy without hiding missing-data errors behind a second abstraction.
|
|
138
139
|
"""
|
|
139
|
-
candidates =
|
|
140
|
-
data_root.expanduser().joinpath(*relative_parts)
|
|
141
|
-
for data_root in config.data_paths
|
|
142
|
-
)
|
|
143
|
-
if not candidates:
|
|
144
|
-
msg = "twopy config must include at least one data path"
|
|
145
|
-
raise ValueError(msg)
|
|
140
|
+
candidates = _recording_candidates_for_relative_parts(config, relative_parts)
|
|
146
141
|
|
|
147
142
|
for candidate in candidates:
|
|
148
143
|
if candidate.exists():
|
|
@@ -177,6 +172,35 @@ def data_path_match(
|
|
|
177
172
|
return None
|
|
178
173
|
|
|
179
174
|
|
|
175
|
+
def data_path_recording_candidates(
|
|
176
|
+
config: TwopyConfig,
|
|
177
|
+
recording_dir: Path,
|
|
178
|
+
) -> tuple[Path, ...]:
|
|
179
|
+
"""Return every configured mirror candidate for one recording path.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
config: Loaded twopy configuration with ordered data roots.
|
|
183
|
+
recording_dir: Recording folder under one configured data root.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Candidate recording folders under every configured root with the same
|
|
187
|
+
root-relative path, or an empty tuple when ``recording_dir`` is external.
|
|
188
|
+
|
|
189
|
+
Database loads may fall back to the first configured root when no source
|
|
190
|
+
copy is mounted so cache-only loads can still work. This helper exposes the
|
|
191
|
+
full mirrored candidate set for diagnostics and audits without changing
|
|
192
|
+
that load behavior.
|
|
193
|
+
"""
|
|
194
|
+
match = data_path_match(config, recording_dir)
|
|
195
|
+
if match is None:
|
|
196
|
+
return ()
|
|
197
|
+
_matched_root, relative_recording = match
|
|
198
|
+
return _recording_candidates_for_relative_parts(
|
|
199
|
+
config,
|
|
200
|
+
tuple(str(part) for part in relative_recording.parts),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
180
204
|
def resolve_analysis_output_dir(config: TwopyConfig, recording_dir: Path) -> Path:
|
|
181
205
|
"""Resolve the output directory for one recording.
|
|
182
206
|
|
|
@@ -314,6 +338,21 @@ def _required_path_tuple(
|
|
|
314
338
|
return tuple(paths)
|
|
315
339
|
|
|
316
340
|
|
|
341
|
+
def _recording_candidates_for_relative_parts(
|
|
342
|
+
config: TwopyConfig,
|
|
343
|
+
relative_parts: tuple[str, ...],
|
|
344
|
+
) -> tuple[Path, ...]:
|
|
345
|
+
"""Return ordered data-root candidates for one relative recording path."""
|
|
346
|
+
candidates = tuple(
|
|
347
|
+
data_root.expanduser().joinpath(*relative_parts)
|
|
348
|
+
for data_root in config.data_paths
|
|
349
|
+
)
|
|
350
|
+
if not candidates:
|
|
351
|
+
msg = "twopy config must include at least one data path"
|
|
352
|
+
raise ValueError(msg)
|
|
353
|
+
return candidates
|
|
354
|
+
|
|
355
|
+
|
|
317
356
|
def _external_analysis_cache_dir(
|
|
318
357
|
config: TwopyConfig,
|
|
319
358
|
source_dir: Path,
|
|
@@ -31,6 +31,7 @@ from twopy.custom.types import (
|
|
|
31
31
|
CustomEpoch,
|
|
32
32
|
CustomLineBand,
|
|
33
33
|
CustomLinePlot,
|
|
34
|
+
CustomRecordingMetadata,
|
|
34
35
|
CustomResult,
|
|
35
36
|
CustomRunContext,
|
|
36
37
|
CustomTable,
|
|
@@ -43,6 +44,7 @@ __all__ = [
|
|
|
43
44
|
"CustomEpoch",
|
|
44
45
|
"CustomLineBand",
|
|
45
46
|
"CustomLinePlot",
|
|
47
|
+
"CustomRecordingMetadata",
|
|
46
48
|
"CustomParameterSpec",
|
|
47
49
|
"CustomResult",
|
|
48
50
|
"CustomRunContext",
|
|
@@ -25,7 +25,7 @@ class DirectionSelectivityParams:
|
|
|
25
25
|
window_stop_seconds: Epoch-relative metric stop time.
|
|
26
26
|
rectify_responses: Whether negative preferred/null responses are set
|
|
27
27
|
to zero before computing DSI.
|
|
28
|
-
dsi_threshold: Minimum absolute DSI to
|
|
28
|
+
dsi_threshold: Minimum absolute DSI to show and highlight.
|
|
29
29
|
output_name: CSV filename under the workflow output folder.
|
|
30
30
|
|
|
31
31
|
Returns:
|
|
@@ -88,9 +88,9 @@ class DirectionSelectivityParams:
|
|
|
88
88
|
dsi_threshold: float = field(
|
|
89
89
|
default=0.0,
|
|
90
90
|
metadata={
|
|
91
|
-
"label": "DSI
|
|
91
|
+
"label": "DSI show threshold",
|
|
92
92
|
"description": (
|
|
93
|
-
"Only ROIs with absolute DSI at or above this value are
|
|
93
|
+
"Only ROIs with absolute DSI at or above this value are shown."
|
|
94
94
|
),
|
|
95
95
|
"max": 1.0,
|
|
96
96
|
"twopy_role": "table_highlight_threshold",
|
|
@@ -125,8 +125,9 @@ def run(
|
|
|
125
125
|
params: Custom tab values.
|
|
126
126
|
|
|
127
127
|
Returns:
|
|
128
|
-
Table
|
|
128
|
+
Table output plus ROI visibility rows for the current response plot.
|
|
129
129
|
"""
|
|
130
|
+
selected_roi_indices = ctx.roi_indices_for_selector(params.roi_selector)
|
|
130
131
|
rois = ctx.rois_for_selector(params.roi_selector)
|
|
131
132
|
computation = ctx.compute_standard_responses(rois)
|
|
132
133
|
window_seconds = ctx.epoch_window(
|
|
@@ -158,27 +159,27 @@ def run(
|
|
|
158
159
|
},
|
|
159
160
|
roi_labels=rois.labels,
|
|
160
161
|
)
|
|
161
|
-
|
|
162
|
+
passing_table_rows = tuple(
|
|
162
163
|
int(index) for index in np.flatnonzero(np.abs(dsi) >= params.dsi_threshold)
|
|
163
164
|
)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
visible_roi_indices = tuple(
|
|
166
|
+
selected_roi_indices[index]
|
|
167
|
+
for index in passing_table_rows
|
|
168
|
+
if index < len(selected_roi_indices)
|
|
168
169
|
)
|
|
169
170
|
return CustomResult(
|
|
170
171
|
message=(
|
|
171
172
|
f"Computed DSI for {len(rois.labels)} ROIs; "
|
|
172
|
-
f"
|
|
173
|
+
f"showing {len(visible_roi_indices)} above absolute threshold."
|
|
173
174
|
),
|
|
174
175
|
tables=(
|
|
175
176
|
CustomTable(
|
|
176
177
|
"Direction selectivity",
|
|
177
178
|
csv_path,
|
|
178
|
-
highlighted_rows=
|
|
179
|
+
highlighted_rows=passing_table_rows,
|
|
179
180
|
),
|
|
180
181
|
),
|
|
181
|
-
|
|
182
|
+
visible_roi_indices=visible_roi_indices,
|
|
182
183
|
)
|
|
183
184
|
|
|
184
185
|
|
|
@@ -95,7 +95,7 @@ class ResponseKernelParams:
|
|
|
95
95
|
},
|
|
96
96
|
)
|
|
97
97
|
num_stim_past: int = field(
|
|
98
|
-
default=
|
|
98
|
+
default=100,
|
|
99
99
|
metadata={
|
|
100
100
|
"label": "Past samples",
|
|
101
101
|
"description": "Stimulus samples before each response sample.",
|
|
@@ -103,7 +103,7 @@ class ResponseKernelParams:
|
|
|
103
103
|
},
|
|
104
104
|
)
|
|
105
105
|
num_stim_future: int = field(
|
|
106
|
-
default=
|
|
106
|
+
default=10,
|
|
107
107
|
metadata={
|
|
108
108
|
"label": "Future samples",
|
|
109
109
|
"description": "Future stimulus samples used as negative-lag QC.",
|
|
@@ -244,7 +244,6 @@ def _olfactory_kernel_result(
|
|
|
244
244
|
ipsi_mean, ipsi_band = _mean_sem_plot_series(
|
|
245
245
|
kernels.ipsilateral[epoch_index],
|
|
246
246
|
series_index=len(mean_rows),
|
|
247
|
-
label=f"{epoch_name} ipsi",
|
|
248
247
|
)
|
|
249
248
|
mean_rows.append(ipsi_mean)
|
|
250
249
|
mean_labels.append(f"{epoch_name} ipsi")
|
|
@@ -252,7 +251,6 @@ def _olfactory_kernel_result(
|
|
|
252
251
|
contra_mean, contra_band = _mean_sem_plot_series(
|
|
253
252
|
kernels.contralateral[epoch_index],
|
|
254
253
|
series_index=len(mean_rows),
|
|
255
|
-
label=f"{epoch_name} contra",
|
|
256
254
|
)
|
|
257
255
|
mean_rows.append(contra_mean)
|
|
258
256
|
mean_labels.append(f"{epoch_name} contra")
|
|
@@ -324,7 +322,6 @@ def _visual_kernel_result(
|
|
|
324
322
|
contrast_mean, contrast_band = _mean_sem_plot_series(
|
|
325
323
|
contrast,
|
|
326
324
|
series_index=len(mean_rows),
|
|
327
|
-
label=epoch_name,
|
|
328
325
|
)
|
|
329
326
|
mean_rows.append(contrast_mean)
|
|
330
327
|
mean_labels.append(epoch_name)
|
|
@@ -355,7 +352,6 @@ def _mean_sem_plot_series(
|
|
|
355
352
|
values: np.ndarray,
|
|
356
353
|
*,
|
|
357
354
|
series_index: int,
|
|
358
|
-
label: str,
|
|
359
355
|
) -> tuple[np.ndarray, CustomLineBand]:
|
|
360
356
|
"""Return one mean row and filled SEM band for one kernel matrix."""
|
|
361
357
|
mean, sem = finite_mean_and_sem(values, axis=0)
|
|
@@ -363,7 +359,6 @@ def _mean_sem_plot_series(
|
|
|
363
359
|
series_index=series_index,
|
|
364
360
|
lower=mean - sem,
|
|
365
361
|
upper=mean + sem,
|
|
366
|
-
label=f"{label} SEM",
|
|
367
362
|
)
|
|
368
363
|
|
|
369
364
|
|
|
@@ -87,6 +87,7 @@ def validate_custom_result(
|
|
|
87
87
|
raise ValueError(msg)
|
|
88
88
|
if result.response_plot_data is not None:
|
|
89
89
|
_validate_response_plot_data(result.response_plot_data)
|
|
90
|
+
_validate_custom_result_visible_rois(result.visible_roi_indices)
|
|
90
91
|
return result
|
|
91
92
|
|
|
92
93
|
|
|
@@ -101,6 +102,19 @@ def _validate_table_highlighted_rows(highlighted_rows: object) -> None:
|
|
|
101
102
|
raise ValueError(msg)
|
|
102
103
|
|
|
103
104
|
|
|
105
|
+
def _validate_custom_result_visible_rois(visible_roi_indices: object) -> None:
|
|
106
|
+
"""Check optional ROI rows selected by a workflow result."""
|
|
107
|
+
if visible_roi_indices is None:
|
|
108
|
+
return
|
|
109
|
+
if not isinstance(visible_roi_indices, tuple):
|
|
110
|
+
msg = "CustomResult.visible_roi_indices must be a tuple of integers."
|
|
111
|
+
raise ValueError(msg)
|
|
112
|
+
for index in visible_roi_indices:
|
|
113
|
+
if type(index) is not int or index < 0:
|
|
114
|
+
msg = "CustomResult.visible_roi_indices must contain non-negative integers."
|
|
115
|
+
raise ValueError(msg)
|
|
116
|
+
|
|
117
|
+
|
|
104
118
|
def custom_result_artifact_paths(result: CustomResult) -> tuple[Path, ...]:
|
|
105
119
|
"""Return every output file path declared by a custom result.
|
|
106
120
|
|