ethograph 0.2.44__tar.gz → 0.2.45__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.
- {ethograph-0.2.44 → ethograph-0.2.45}/PKG-INFO +1 -1
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/active_panel.py +1 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/app_state.py +3 -0
- ethograph-0.2.45/ethograph/gui/dialog_space_geometry.py +521 -0
- ethograph-0.2.45/ethograph/gui/individual_colors.py +66 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_container.py +15 -3
- ethograph-0.2.45/ethograph/gui/plots_skeleton.py +964 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_space.py +210 -90
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_render.py +47 -27
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/right_context.py +6 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/source_popup.py +6 -0
- ethograph-0.2.45/ethograph/gui/space_shapes.py +225 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/top_bar.py +42 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_data.py +112 -5
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_meta.py +13 -2
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_plot_settings.py +21 -3
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/data_loader.py +15 -6
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/PKG-INFO +1 -1
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/SOURCES.txt +4 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/scm_file_list.json +9 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/scm_version.json +2 -2
- ethograph-0.2.45/examples/convert_dataset_pair24.ipynb +2160 -0
- ethograph-0.2.44/examples/convert_dataset_pair24.ipynb +0 -294
- {ethograph-0.2.44 → ethograph-0.2.45}/.claude/settings.local.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/.gitattributes +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/.github/copilot-instructions.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/.github/workflows/docs_build_and_deploy.yml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/.github/workflows/test_and_deploy.yml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/.gitignore +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/.readthedocs.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/CLAUDE.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/LICENSE +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/MANIFEST.in +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/README.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/THIRD_PARTY_NOTICES.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/TODO.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/__main__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/_vendor/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/_vendor/vocalseg/LICENSE +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/_vendor/vocalseg/NOTICE.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/_vendor/vocalseg/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/_vendor/vocalseg/continuity_filtering.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/_vendor/vocalseg/dynamic_thresholding.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/_vendor/vocalseg/utils.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/assets/icon.ico +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/assets/icon.png +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/assets/menu.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/assets/space/moll2025.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/cli.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/datasets.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/defaults/README.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/defaults/mapping.txt +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/defaults/octron/octron.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/defaults/segment/segment.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/defaults/spot/spot.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/audio_changepoints.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/changepoints.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/columns.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/energy.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/geometry.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/label_inputs.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/movement.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/neural.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/oscillatory.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/preprocessing.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/app_constants.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/birdpark0.png +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/birdpark1.png +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/canary.png +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/cricket0.png +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/cricket1.png +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/cricket2.png +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/lockbox1.png +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/lockbox2.gif +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/moll1.png +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/moll2.png +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/wizard/video_setup_free_running.png +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/wizard/video_setup_triggered.png +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/audio_clock.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/audio_player.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/box_annotate.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/box_overlay.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/cover_page.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_ambiguous_session.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_box_labelling.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_bulk_labels.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_busy_progress.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_collected_data.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_curation_workflow.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_curator_feedback.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_fit.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_function_params.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_keypoint_filter.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_label_gridview.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_label_inconsistencies.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_label_table.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_onset_model.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_pose_labelling.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_pose_refinement.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_pose_video_matcher.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_review_histogram.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_screen_recorder.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_select_template.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_session_folder.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_settings.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_skeleton_editor.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_spot_crop.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_tag_sheet.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_video_grid.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/file_dialogs.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/grid_section_container.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/heatmap_sort.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/label_drawing_mixin.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/left_sidebar.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/main_window.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/make_pretty.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/napari.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/notify.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_audiotrace.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_base.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_console.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_ephystrace.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_heatmap.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_labelribbon.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_lineplot.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_overlay.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_psth.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_radial.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_raster.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_spectrogram.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_annotate.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_convert.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_detect.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_detect_preview.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_edit_mixin.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_fill.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_overlay.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_project_mode.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_refine.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_static_group.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_suggest.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_tagsheet.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_training_export.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/project.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/proxy_manager.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pygfx_video.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/session_folder.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/shortcuts.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/table_filter.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/discover.py.j2 +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/open.md.j2 +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/parameters.py.j2 +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/timing_offset.py.j2 +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/timing_onsets.py.j2 +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/timing_pulses_free.py.j2 +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/timing_pulses_triggered.py.j2 +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/write_neuroconv.py.j2 +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/write_pair.py.j2 +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/wizard_nwb_codegen.j2 +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/theme.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/video_manager.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/video_sync.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widget_trials.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_bottom_bar.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_changepoints.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_curation.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_ephys.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_frame_extract.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_help.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_io.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_labels.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_navigation.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_psth.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_transform.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_boris.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_media_files.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_multi_builder.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_multi_codegen.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_multi_tabs.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_multi_timeline.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_multi_trials.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_notebook.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_nwb.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_overview.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_pages.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_single.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_state.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/audio_extract.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/catalog.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/dataset.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/derived.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/ephys_loader.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/external_features.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/image_sequence.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/metadata_edit.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/metadata_table.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/nc_drop.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/netcdf.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/nwb_alignment.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/nwb_import.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/overlay_source.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/pairing.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/plot_sources.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/pynapple.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/schema.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/session_layout.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/time_model.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/time_sources.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/trialtree.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/validation.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/video_decode.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/video_feature_files.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/video_probe.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/video_proxy.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/boris.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/check_labels.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/confidence.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/converters.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/crowsetta_format.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/curation.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/export.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/exporters/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/exporters/crowlab.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/intervals.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/label_inputs.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/ml.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/octron_project.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/onset_curves.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/onset_model.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/plots.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/pose_project.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/predictions.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/rescore.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/review_metrics.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/tsv_store.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/workflow.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/augment.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/config.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/crossval.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dataset.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/LICENSE.APGPL +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/NOTICE.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/colors.txt +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/losses.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/asformer.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/c2f_tcn.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/c2f_transformer.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/edtcn.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/mlp.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/motionbert.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/ms_tcn3.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/transformer.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/training.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/loss/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/loss/asymmetric_loss.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/loss/ms_tcn.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/loss/mse.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/loss/tcc.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/asformer.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/base_model.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/c2f_tcn.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/c2f_tcn_par.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/c2f_transformer.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/c3d.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/edtcn.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/mlp.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/motionbert.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/motionbert_modules.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/ms_tcn.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/ms_tcn_modules.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/transformer.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/utils.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/version.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/feral.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/feral_defaults/NOTICE.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/feral_defaults/default_config.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/feral_defaults/presets.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/inference.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/joints.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/LICENSE +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/NOTICE.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/config/defaults.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/generalized_coordinates.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/loss_fn/ECloss.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/loss_fn/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/models/LaDy.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/models/SP.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/models/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/models/dynamics.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/models/graph/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/models/graph/tools.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/models/tcn.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/losses.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/materialise.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/metrics.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/models/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/models/config/rnn.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/models/rnn.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/models/skeleton_graph.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/models/vendored.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/plotting.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/postprocess.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/prediction_sets.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/preprocess.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/project.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/roles.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/samples.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/search.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/sessions.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/LICENSE +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/NOTICE.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/config/defaults.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/models/SP.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/models/SpecScalpel.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/models/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/models/graph/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/models/graph/tools.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/models/tcn.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/train.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/video_features.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/windows.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/shortcuts.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/CREDITS.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/config.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/graph.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/library.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/renderers/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/renderers/base.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/renderers/precomputed.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/shapes.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/state.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/templates.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/confidence.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/config.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/crossval.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/dataset.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/LICENSE +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/NOTICE.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/dataset/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/dataset/frame.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/dataset/transform.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/common.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/impl/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/impl/asformer.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/impl/gsm.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/impl/tsm.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/modules.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/shift.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/test_e2e.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/train_e2e.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/util/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/util/dataset.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/util/eval.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/util/io.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/util/score.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/features.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/inference.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/metrics.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/msagsm.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/pose_batch.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/prediction_sets.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/project.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/stream.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/vendored.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/arraytools.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/audio.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/configkit.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/device.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/download.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/ffmpeg.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/logging.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/nwb.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/paths.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/qt.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/sequences.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/stream_durations.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/system_check.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/xr_utils.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/__init__.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/base.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/checkpoint/S3D_kinetics400_torchified.pt +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/extract.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/frames.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/plan.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/s3d.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/select.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/timm_extract.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/dependency_links.txt +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/entry_points.txt +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/requires.txt +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/top_level.txt +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/examples/README.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/examples/birdpark_dev/birdpark_pose.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/examples/convert_dataset_birdpark.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/examples/convert_datset_lockbox.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/examples/create_dataset_Moll25.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/examples/create_dataset_cricket.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/examples/segement_s3d_features.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/examples/segment_changepoint_features.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/Bench_neural-decoding.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/Bench_smoothalpha.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/Lit_curator-feedback.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/Lit_dlc2action.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/Lit_feralbeast.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/Lit_precise-event-spotting.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0001-vendor-dlc2action-architectures.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0002-literature-dataset-layout.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0003-advisory-variable-schema.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0004-scripted-not-cli.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0005-search-then-cross-validate.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0006-postprocess-from-gui-settings.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0007-distil-inside-the-vendored-trainer.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0008-features-not-graphs.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0009-extractors-pip-installed-not-vendored.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0009-spikes-as-a-config-transform.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0010-multilabel-targets-are-tracks.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0011-labels-as-inputs-across-branches.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0012-feral-is-an-external-extractor.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0013-no-learned-boundary-model-over-pose.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0013-session-is-a-folder-media-are-absolute.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0014-pose-project-as-two-sessions.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0015-flat-project-folder-and-libraries.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/feedback/audio_clock_dac_timestamps.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/feedback/debug_launcher_clock_patch.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/feedback/ethograph_annotation_feasibility_report.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/feedback/ethograph_windows_author_overview_2026-08-18.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/feedback/processed/ehtograph_feedback_report.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/feedback/processed/handoff_playback_and_video_freeze.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/notes/feedback/reverted_pynapple_trials_timing.diff +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/ReadMe.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/config/space/moll2025.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/mapping.txt +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/mapping_setup_audio.txt +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/changepoint_settings_old.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/mapping_Ivy.txt +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Crow1_inference_20260330_161224.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Crow1_inference_20260330_161259.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Freddy_inference_20260330_160621.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Freddy_train_20251021_164220.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Freddy_train_20251021_164220_epoch-100.model +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Ivy_CV_20260130_173800.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Ivy_inference_20260203_092442.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Ivy_inference_20260204_141527.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Ivy_inference_20260206_122331.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Ivy_train_20260202_191138_epoch-100.model +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Ivy_train_20260203_153754.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Poppy_inference_20260312_100707.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Poppy_inference_20260327_102414.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Poppy_inference_20260330_140808.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Poppy_inference_20260402_104151.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Poppy_train_20260326_190925.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Poppy_train_20260331_175332.json +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/ReadMe - model Freddy_train_20251021_164220_epoch-100.txt +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/project.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/sessions.txt +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/wizard/CrowBench.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/wizard/SENTINEL.txt +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/workflows/01 PES Curation.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/workflows/Freddy_GBM.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/workflows/Ivy_GBM.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/workflows/Poppy_GBM.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/neural/decoding.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/paper/labels.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/projects/paper/spot/spot.yaml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/pyproject.toml +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/Developer.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/_sweep.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/bench.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/bench_search.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/bench_videofeat.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/bench_watch.ps1 +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/compare.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/dandi/dandi_ibl-brainwide.ipynb.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/dandi/nwb_video_testing.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/describe_sessions.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/ephys/ephys_drift_diagnosis.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/ephys/ephys_loader.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/export_frames.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/export_neural_rate.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/feral/export_feral.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/feral/reinfer_feral.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/feral/run_feral.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/feral/score_feral.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/neuroconv.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/README.md +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/changepoint_bars.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/crossval_bars.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/crossval_label_counts.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/embedding_grid.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/embedding_heatmaps.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/figure_combined.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/figure_neural.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/figure_review.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/figure_review_curves.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/figure_review_speed.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/s3d_ablation_bars.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/session.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/style/style ppt.mplstyle +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/style/style.mplstyle +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/release.ps1 +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/run.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/run_sleep.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/screenshot_gui.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/spot.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/test.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/scripts/train_spot.py +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/setup.cfg +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/test.ipynb +0 -0
- {ethograph-0.2.44 → ethograph-0.2.45}/uv.lock +0 -0
|
@@ -531,6 +531,9 @@ class AppStateSpec:
|
|
|
531
531
|
"space_hide_zeros": (bool, False, True),
|
|
532
532
|
"space_show_references": (bool, True, True),
|
|
533
533
|
"space_library_geometry": (str | None, None, True, SCOPE_LOCAL),
|
|
534
|
+
# The Edit space geometry dialog's unsaved geometry, {"name": stem, "config": yaml dict}:
|
|
535
|
+
# drawn instead of that file while the dialog is open. Never saved.
|
|
536
|
+
"space_geometry_preview": (dict | None, None, False),
|
|
534
537
|
"primary_camera": (str | None, None, True),
|
|
535
538
|
"primary_camera_previous": (str | None, None, False),
|
|
536
539
|
"extra_cameras": (list[str], [], True),
|
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
"""Settings ▸ Edit space geometry: place parametric shapes in a geometry file and watch them move.
|
|
2
|
+
|
|
3
|
+
One geometry file of the space library (:func:`~ethograph.gui.plots_space.geometry_dirs`)
|
|
4
|
+
is edited at a time, in a narrow window meant to sit beside the plots. The dialog
|
|
5
|
+
draws nothing itself: every change goes, through ``app_state.space_geometry_preview``,
|
|
6
|
+
to the open space and skeleton plots — the pose is what a shape is placed against —
|
|
7
|
+
with the selected shape highlighted. Nothing is written until Save; closing the
|
|
8
|
+
dialog drops the preview and the plots go back to the file.
|
|
9
|
+
|
|
10
|
+
The file's raw ``references`` (vertex/edge wireframes) are drawn too and saved back
|
|
11
|
+
unchanged; only its ``shapes`` are edited here.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
from qtpy.QtCore import QEvent, Qt
|
|
21
|
+
from qtpy.QtGui import QColor
|
|
22
|
+
from qtpy.QtWidgets import (
|
|
23
|
+
QColorDialog,
|
|
24
|
+
QComboBox,
|
|
25
|
+
QDialog,
|
|
26
|
+
QDialogButtonBox,
|
|
27
|
+
QDoubleSpinBox,
|
|
28
|
+
QFormLayout,
|
|
29
|
+
QHBoxLayout,
|
|
30
|
+
QInputDialog,
|
|
31
|
+
QLabel,
|
|
32
|
+
QLineEdit,
|
|
33
|
+
QListWidget,
|
|
34
|
+
QMessageBox,
|
|
35
|
+
QPushButton,
|
|
36
|
+
QVBoxLayout,
|
|
37
|
+
QWidget,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
from ethograph.gui.notify import notify
|
|
41
|
+
from ethograph.gui.plots_space import (
|
|
42
|
+
ReferenceGeometry,
|
|
43
|
+
geometry_config,
|
|
44
|
+
geometry_dirs_of,
|
|
45
|
+
library_geometry_files,
|
|
46
|
+
load_geometry_yaml,
|
|
47
|
+
parse_geometry,
|
|
48
|
+
write_geometry_yaml,
|
|
49
|
+
)
|
|
50
|
+
from ethograph.gui.project import project_dir_of
|
|
51
|
+
from ethograph.gui.space_shapes import (
|
|
52
|
+
ANGLE_PARAMS,
|
|
53
|
+
AXES,
|
|
54
|
+
SHAPE_PARAMS,
|
|
55
|
+
SHAPE_TYPES,
|
|
56
|
+
Shape,
|
|
57
|
+
axes_half_length,
|
|
58
|
+
parse_shapes,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
#: The shape selected in the list, as the plots draw it while it is edited.
|
|
62
|
+
_SELECTED_COLOR = "#FF6600"
|
|
63
|
+
#: Spin-box steps offered for nudging a shape; the data's units decide which is sensible.
|
|
64
|
+
_STEPS = ("0.001", "0.01", "0.1", "1", "10", "100")
|
|
65
|
+
_LIMIT = 1e6
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _save_targets(app_state) -> list[tuple[str, Path]]:
|
|
69
|
+
"""The library folders a geometry can be saved to, nearest first, each with a name a user knows."""
|
|
70
|
+
dirs = geometry_dirs_of(app_state)
|
|
71
|
+
project = project_dir_of(app_state)
|
|
72
|
+
project_space = dirs[-2] if project is not None else None
|
|
73
|
+
targets = []
|
|
74
|
+
for folder in dirs:
|
|
75
|
+
if folder == dirs[-1]:
|
|
76
|
+
label = "Your defaults"
|
|
77
|
+
elif folder == project_space:
|
|
78
|
+
label = "Project"
|
|
79
|
+
else:
|
|
80
|
+
label = "This session"
|
|
81
|
+
targets.append((label, folder))
|
|
82
|
+
return targets
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class SpaceGeometryDialog(QDialog):
|
|
86
|
+
"""Add, place and size the shapes of one space geometry file, then save it."""
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
app_state,
|
|
91
|
+
on_saved: Callable[[], None] | None = None,
|
|
92
|
+
panels_open: Callable[[], bool] | None = None,
|
|
93
|
+
open_panel: Callable[[], None] | None = None,
|
|
94
|
+
data_center: Callable[[], tuple[float, float, float] | None] | None = None,
|
|
95
|
+
parent=None,
|
|
96
|
+
):
|
|
97
|
+
"""*panels_open* says whether a space or skeleton plot shows the edit, *open_panel*
|
|
98
|
+
opens one, and *data_center* is where the animal is now — where a new shape goes."""
|
|
99
|
+
super().__init__(parent)
|
|
100
|
+
self.setWindowTitle("Edit space geometry")
|
|
101
|
+
self.app_state = app_state
|
|
102
|
+
self._on_saved = on_saved
|
|
103
|
+
self._panels_open = panels_open
|
|
104
|
+
self._open_panel = open_panel
|
|
105
|
+
self._data_center = data_center
|
|
106
|
+
self._name: str | None = None
|
|
107
|
+
self._base: dict = {}
|
|
108
|
+
self._shapes: list[Shape] = []
|
|
109
|
+
self._dirty = False
|
|
110
|
+
self._loading = False
|
|
111
|
+
self._path: Path | None = None
|
|
112
|
+
self._references: list[ReferenceGeometry] = []
|
|
113
|
+
self._param_spins: dict[str, QDoubleSpinBox] = {}
|
|
114
|
+
|
|
115
|
+
root = QVBoxLayout(self)
|
|
116
|
+
|
|
117
|
+
# Shown while no plot draws the edit: without one, nothing is seen.
|
|
118
|
+
self._no_panel = QWidget()
|
|
119
|
+
no_panel_layout = QVBoxLayout(self._no_panel)
|
|
120
|
+
no_panel_layout.setContentsMargins(0, 0, 0, 0)
|
|
121
|
+
hint = QLabel("Shapes are drawn in the space and skeleton plots. Open one to place them against the pose.")
|
|
122
|
+
hint.setWordWrap(True)
|
|
123
|
+
no_panel_layout.addWidget(hint)
|
|
124
|
+
open_btn = QPushButton("Open a 3D plot")
|
|
125
|
+
open_btn.clicked.connect(self._on_open_panel)
|
|
126
|
+
open_btn.setVisible(open_panel is not None)
|
|
127
|
+
no_panel_layout.addWidget(open_btn)
|
|
128
|
+
root.addWidget(self._no_panel)
|
|
129
|
+
|
|
130
|
+
form = QFormLayout()
|
|
131
|
+
form.setContentsMargins(0, 0, 0, 0)
|
|
132
|
+
geometry_row = QHBoxLayout()
|
|
133
|
+
self._geometry_combo = QComboBox()
|
|
134
|
+
self._geometry_combo.setToolTip("One file per geometry in the space library")
|
|
135
|
+
self._geometry_combo.currentTextChanged.connect(self._on_geometry_picked)
|
|
136
|
+
geometry_row.addWidget(self._geometry_combo, 1)
|
|
137
|
+
new_btn = QPushButton("New…")
|
|
138
|
+
new_btn.clicked.connect(self._new_geometry)
|
|
139
|
+
geometry_row.addWidget(new_btn)
|
|
140
|
+
form.addRow("Geometry", geometry_row)
|
|
141
|
+
self._target_combo = QComboBox()
|
|
142
|
+
for label, folder in _save_targets(app_state):
|
|
143
|
+
self._target_combo.addItem(label, str(folder))
|
|
144
|
+
self._target_combo.setItemData(self._target_combo.count() - 1, str(folder), Qt.ItemDataRole.ToolTipRole)
|
|
145
|
+
form.addRow("Save to", self._target_combo)
|
|
146
|
+
root.addLayout(form)
|
|
147
|
+
|
|
148
|
+
self._summary = QLabel("")
|
|
149
|
+
self._summary.setWordWrap(True)
|
|
150
|
+
root.addWidget(self._summary)
|
|
151
|
+
|
|
152
|
+
root.addWidget(self._build_editor(), 1)
|
|
153
|
+
|
|
154
|
+
buttons = QDialogButtonBox(
|
|
155
|
+
QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Close, parent=self
|
|
156
|
+
)
|
|
157
|
+
buttons.accepted.connect(self._save)
|
|
158
|
+
buttons.rejected.connect(self.reject)
|
|
159
|
+
root.addWidget(buttons)
|
|
160
|
+
|
|
161
|
+
self.finished.connect(self._drop_preview)
|
|
162
|
+
# The geometry is what the user came to see.
|
|
163
|
+
self.app_state.space_show_references = True
|
|
164
|
+
self._reload_library(select=getattr(app_state, "space_library_geometry", None))
|
|
165
|
+
self._sync_panel_hint()
|
|
166
|
+
self.resize(340, 640)
|
|
167
|
+
|
|
168
|
+
# -- layout ------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
def _build_editor(self) -> QWidget:
|
|
171
|
+
editor = QWidget()
|
|
172
|
+
layout = QVBoxLayout(editor)
|
|
173
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
174
|
+
|
|
175
|
+
self._shape_list = QListWidget()
|
|
176
|
+
self._shape_list.setMinimumHeight(80)
|
|
177
|
+
self._shape_list.currentRowChanged.connect(self._on_shape_selected)
|
|
178
|
+
layout.addWidget(self._shape_list, 1)
|
|
179
|
+
|
|
180
|
+
add_row = QHBoxLayout()
|
|
181
|
+
self._add_type = QComboBox()
|
|
182
|
+
self._add_type.addItems(list(SHAPE_TYPES))
|
|
183
|
+
add_row.addWidget(self._add_type, 1)
|
|
184
|
+
add_btn = QPushButton("Add")
|
|
185
|
+
add_btn.setToolTip("Add a shape of this type where the animal is now")
|
|
186
|
+
add_btn.clicked.connect(self._add_shape)
|
|
187
|
+
add_row.addWidget(add_btn)
|
|
188
|
+
layout.addLayout(add_row)
|
|
189
|
+
edit_row = QHBoxLayout()
|
|
190
|
+
self._duplicate_btn = QPushButton("Duplicate")
|
|
191
|
+
self._duplicate_btn.clicked.connect(self._duplicate_shape)
|
|
192
|
+
edit_row.addWidget(self._duplicate_btn)
|
|
193
|
+
self._remove_btn = QPushButton("Remove")
|
|
194
|
+
self._remove_btn.clicked.connect(self._remove_shape)
|
|
195
|
+
edit_row.addWidget(self._remove_btn)
|
|
196
|
+
layout.addLayout(edit_row)
|
|
197
|
+
|
|
198
|
+
self._form_widget = QWidget()
|
|
199
|
+
form = QFormLayout(self._form_widget)
|
|
200
|
+
form.setContentsMargins(0, 0, 0, 0)
|
|
201
|
+
self._name_edit = QLineEdit()
|
|
202
|
+
self._name_edit.textEdited.connect(self._on_field_changed)
|
|
203
|
+
form.addRow("Name", self._name_edit)
|
|
204
|
+
self._type_combo = QComboBox()
|
|
205
|
+
self._type_combo.addItems(list(SHAPE_TYPES))
|
|
206
|
+
self._type_combo.currentTextChanged.connect(self._on_type_changed)
|
|
207
|
+
form.addRow("Type", self._type_combo)
|
|
208
|
+
self._color_btn = QPushButton()
|
|
209
|
+
self._color_btn.clicked.connect(self._pick_color)
|
|
210
|
+
form.addRow("Colour", self._color_btn)
|
|
211
|
+
self._center_spins = [self._spin() for _ in range(3)]
|
|
212
|
+
for axis, spin in zip("XYZ", self._center_spins):
|
|
213
|
+
form.addRow(f"Centre {axis}", spin)
|
|
214
|
+
self._axis_combo = QComboBox()
|
|
215
|
+
self._axis_combo.addItems(list(AXES))
|
|
216
|
+
self._axis_combo.setToolTip("The axis the cylinder's height runs along")
|
|
217
|
+
self._axis_combo.currentTextChanged.connect(self._on_field_changed)
|
|
218
|
+
form.addRow("Axis", self._axis_combo)
|
|
219
|
+
self._params_form = QFormLayout()
|
|
220
|
+
self._params_form.setContentsMargins(0, 0, 0, 0)
|
|
221
|
+
form.addRow(self._params_form)
|
|
222
|
+
self._step_combo = QComboBox()
|
|
223
|
+
self._step_combo.addItems(list(_STEPS))
|
|
224
|
+
self._step_combo.setCurrentText("0.1")
|
|
225
|
+
self._step_combo.setToolTip("How far one arrow press or wheel notch moves or grows a shape")
|
|
226
|
+
self._step_combo.currentTextChanged.connect(self._apply_step)
|
|
227
|
+
form.addRow("Step", self._step_combo)
|
|
228
|
+
layout.addWidget(self._form_widget)
|
|
229
|
+
return editor
|
|
230
|
+
|
|
231
|
+
def _spin(self, angle: bool = False, minimum: float = -_LIMIT) -> QDoubleSpinBox:
|
|
232
|
+
spin = QDoubleSpinBox()
|
|
233
|
+
spin.setRange(-360.0 if angle else minimum, 360.0 if angle else _LIMIT)
|
|
234
|
+
spin.setDecimals(1 if angle else 4)
|
|
235
|
+
spin.setKeyboardTracking(False)
|
|
236
|
+
if angle:
|
|
237
|
+
spin.setSuffix(" °")
|
|
238
|
+
spin.setSingleStep(5.0)
|
|
239
|
+
spin.valueChanged.connect(self._on_field_changed)
|
|
240
|
+
return spin
|
|
241
|
+
|
|
242
|
+
def _apply_step(self, *_args) -> None:
|
|
243
|
+
step = float(self._step_combo.currentText())
|
|
244
|
+
for spin in [*self._center_spins, *self._param_spins.values()]:
|
|
245
|
+
if spin.suffix() == "":
|
|
246
|
+
spin.setSingleStep(step)
|
|
247
|
+
|
|
248
|
+
# -- the library -------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
def _reload_library(self, select: str | None = None, row: int = 0) -> None:
|
|
251
|
+
self._files = library_geometry_files(geometry_dirs_of(self.app_state))
|
|
252
|
+
names = sorted(set(self._files) | ({self._name} if self._name and self._dirty else set()))
|
|
253
|
+
self._geometry_combo.blockSignals(True)
|
|
254
|
+
self._geometry_combo.clear()
|
|
255
|
+
self._geometry_combo.addItems(names)
|
|
256
|
+
index = self._geometry_combo.findText(select or "")
|
|
257
|
+
self._geometry_combo.setCurrentIndex(index if index >= 0 else 0)
|
|
258
|
+
self._geometry_combo.blockSignals(False)
|
|
259
|
+
self._load(self._geometry_combo.currentText() or None, row=row)
|
|
260
|
+
|
|
261
|
+
def _load(self, name: str | None, row: int = 0) -> None:
|
|
262
|
+
"""Show geometry *name* as its file holds it (a new, unsaved name starts empty)."""
|
|
263
|
+
path = self._files.get(name or "")
|
|
264
|
+
base = (load_geometry_yaml(path) or {}) if path is not None else {}
|
|
265
|
+
try:
|
|
266
|
+
shapes = parse_shapes(base)
|
|
267
|
+
references = parse_geometry({"references": base.get("references")})
|
|
268
|
+
except (KeyError, TypeError, ValueError) as exc:
|
|
269
|
+
notify(f"Could not read {path}: {exc}", "warning")
|
|
270
|
+
base, shapes, references = {}, [], []
|
|
271
|
+
self._name, self._base, self._shapes, self._dirty = name, base, shapes, False
|
|
272
|
+
self._path, self._references = path, references
|
|
273
|
+
if path is not None:
|
|
274
|
+
index = self._target_combo.findData(str(path.parent))
|
|
275
|
+
if index >= 0:
|
|
276
|
+
self._target_combo.setCurrentIndex(index)
|
|
277
|
+
self._refresh_list(select=row)
|
|
278
|
+
self._update_summary()
|
|
279
|
+
|
|
280
|
+
def _on_geometry_picked(self, name: str) -> None:
|
|
281
|
+
if name == self._name or not self._confirm_discard():
|
|
282
|
+
self._geometry_combo.blockSignals(True)
|
|
283
|
+
self._geometry_combo.setCurrentText(self._name or "")
|
|
284
|
+
self._geometry_combo.blockSignals(False)
|
|
285
|
+
return
|
|
286
|
+
self._load(name or None)
|
|
287
|
+
|
|
288
|
+
def _new_geometry(self) -> None:
|
|
289
|
+
if not self._confirm_discard():
|
|
290
|
+
return
|
|
291
|
+
self._dirty = False
|
|
292
|
+
name, ok = QInputDialog.getText(self, "New geometry", "Name (one file):")
|
|
293
|
+
name = name.strip()
|
|
294
|
+
if not ok or not name:
|
|
295
|
+
return
|
|
296
|
+
if name in self._files:
|
|
297
|
+
self._geometry_combo.setCurrentText(name)
|
|
298
|
+
return
|
|
299
|
+
self._load(name)
|
|
300
|
+
self._dirty = True
|
|
301
|
+
project_index = self._target_combo.findText("Project")
|
|
302
|
+
self._target_combo.setCurrentIndex(project_index if project_index >= 0 else 0)
|
|
303
|
+
self._geometry_combo.blockSignals(True)
|
|
304
|
+
self._geometry_combo.addItem(name)
|
|
305
|
+
self._geometry_combo.setCurrentText(name)
|
|
306
|
+
self._geometry_combo.blockSignals(False)
|
|
307
|
+
self._update_summary()
|
|
308
|
+
|
|
309
|
+
def _confirm_discard(self) -> bool:
|
|
310
|
+
if not self._dirty:
|
|
311
|
+
return True
|
|
312
|
+
answer = QMessageBox.question(self, "Unsaved shapes", f"Discard the unsaved changes to {self._name}?")
|
|
313
|
+
return answer == QMessageBox.StandardButton.Yes
|
|
314
|
+
|
|
315
|
+
# -- the shape list ----------------------------------------------------
|
|
316
|
+
|
|
317
|
+
def _refresh_list(self, select: int | None = None) -> None:
|
|
318
|
+
row = self._shape_list.currentRow() if select is None else select
|
|
319
|
+
self._shape_list.blockSignals(True)
|
|
320
|
+
self._shape_list.clear()
|
|
321
|
+
for shape in self._shapes:
|
|
322
|
+
self._shape_list.addItem(f"{shape.name} ({shape.type})")
|
|
323
|
+
self._shape_list.blockSignals(False)
|
|
324
|
+
row = min(row, len(self._shapes) - 1)
|
|
325
|
+
self._shape_list.setCurrentRow(row)
|
|
326
|
+
self._on_shape_selected(row)
|
|
327
|
+
|
|
328
|
+
def _selected(self) -> Shape | None:
|
|
329
|
+
row = self._shape_list.currentRow()
|
|
330
|
+
return self._shapes[row] if 0 <= row < len(self._shapes) else None
|
|
331
|
+
|
|
332
|
+
def _new_center(self) -> tuple[float, float, float]:
|
|
333
|
+
"""Where a new shape goes: at the animal, else amid the geometry, else the origin."""
|
|
334
|
+
center = self._data_center() if self._data_center is not None else None
|
|
335
|
+
if center is not None:
|
|
336
|
+
return center
|
|
337
|
+
refs = parse_geometry(self._config())
|
|
338
|
+
if not refs:
|
|
339
|
+
return (0.0, 0.0, 0.0)
|
|
340
|
+
vertices = np.vstack([np.pad(r.vertices, ((0, 0), (0, 3 - r.vertices.shape[1]))) for r in refs])
|
|
341
|
+
mid = (vertices.min(axis=0) + vertices.max(axis=0)) / 2.0
|
|
342
|
+
return (float(mid[0]), float(mid[1]), float(mid[2]))
|
|
343
|
+
|
|
344
|
+
def _add_shape(self) -> None:
|
|
345
|
+
if self._name is None:
|
|
346
|
+
self._new_geometry()
|
|
347
|
+
if self._name is None:
|
|
348
|
+
return
|
|
349
|
+
shape_type = self._add_type.currentText()
|
|
350
|
+
taken = {s.name for s in self._shapes}
|
|
351
|
+
name = next(f"{shape_type}{i}" for i in range(1, len(taken) + 2) if f"{shape_type}{i}" not in taken)
|
|
352
|
+
self._shapes.append(Shape(name, shape_type, center=self._new_center()))
|
|
353
|
+
self._changed(select=len(self._shapes) - 1)
|
|
354
|
+
|
|
355
|
+
def _duplicate_shape(self) -> None:
|
|
356
|
+
shape = self._selected()
|
|
357
|
+
if shape is None:
|
|
358
|
+
return
|
|
359
|
+
self._shapes.append(shape.retyped(shape.type))
|
|
360
|
+
self._shapes[-1].name = f"{shape.name} copy"
|
|
361
|
+
self._changed(select=len(self._shapes) - 1)
|
|
362
|
+
|
|
363
|
+
def _remove_shape(self) -> None:
|
|
364
|
+
row = self._shape_list.currentRow()
|
|
365
|
+
if 0 <= row < len(self._shapes):
|
|
366
|
+
del self._shapes[row]
|
|
367
|
+
self._changed(select=max(row - 1, 0))
|
|
368
|
+
|
|
369
|
+
# -- the form ----------------------------------------------------------
|
|
370
|
+
|
|
371
|
+
def _on_shape_selected(self, _row: int) -> None:
|
|
372
|
+
shape = self._selected()
|
|
373
|
+
self._form_widget.setEnabled(shape is not None)
|
|
374
|
+
self._duplicate_btn.setEnabled(shape is not None)
|
|
375
|
+
self._remove_btn.setEnabled(shape is not None)
|
|
376
|
+
if shape is not None:
|
|
377
|
+
self._show_shape(shape)
|
|
378
|
+
self._publish()
|
|
379
|
+
|
|
380
|
+
def _show_shape(self, shape: Shape) -> None:
|
|
381
|
+
"""Fill the form from *shape* without writing back into it."""
|
|
382
|
+
self._loading = True
|
|
383
|
+
self._name_edit.setText(shape.name)
|
|
384
|
+
self._type_combo.setCurrentText(shape.type)
|
|
385
|
+
self._set_color_swatch(shape.color)
|
|
386
|
+
for spin, value in zip(self._center_spins, shape.center):
|
|
387
|
+
spin.setValue(value)
|
|
388
|
+
self._axis_combo.setCurrentText(shape.axis)
|
|
389
|
+
self._axis_combo.setEnabled(shape.type == "cylinder")
|
|
390
|
+
self._rebuild_param_spins(shape)
|
|
391
|
+
self._loading = False
|
|
392
|
+
|
|
393
|
+
def _rebuild_param_spins(self, shape: Shape) -> None:
|
|
394
|
+
while self._params_form.rowCount():
|
|
395
|
+
self._params_form.removeRow(0)
|
|
396
|
+
self._param_spins = {}
|
|
397
|
+
for param in SHAPE_PARAMS[shape.type]:
|
|
398
|
+
angle = param in ANGLE_PARAMS
|
|
399
|
+
spin = self._spin(angle=angle, minimum=0.0)
|
|
400
|
+
spin.blockSignals(True)
|
|
401
|
+
spin.setValue(shape.params[param])
|
|
402
|
+
spin.blockSignals(False)
|
|
403
|
+
self._params_form.addRow(param.capitalize(), spin)
|
|
404
|
+
self._param_spins[param] = spin
|
|
405
|
+
self._apply_step()
|
|
406
|
+
|
|
407
|
+
def _set_color_swatch(self, color: str) -> None:
|
|
408
|
+
self._color_btn.setText(color)
|
|
409
|
+
self._color_btn.setStyleSheet(f"QPushButton {{ border-left: 16px solid {QColor(color).name()}; }}")
|
|
410
|
+
|
|
411
|
+
def _pick_color(self) -> None:
|
|
412
|
+
shape = self._selected()
|
|
413
|
+
if shape is None:
|
|
414
|
+
return
|
|
415
|
+
color = QColorDialog.getColor(QColor(shape.color), self, "Shape colour")
|
|
416
|
+
if color.isValid():
|
|
417
|
+
shape.color = color.name().upper()
|
|
418
|
+
self._set_color_swatch(shape.color)
|
|
419
|
+
self._changed()
|
|
420
|
+
|
|
421
|
+
def _on_type_changed(self, shape_type: str) -> None:
|
|
422
|
+
row = self._shape_list.currentRow()
|
|
423
|
+
if self._loading or not 0 <= row < len(self._shapes):
|
|
424
|
+
return
|
|
425
|
+
self._shapes[row] = self._shapes[row].retyped(shape_type)
|
|
426
|
+
self._changed(select=row)
|
|
427
|
+
|
|
428
|
+
def _on_field_changed(self, *_args) -> None:
|
|
429
|
+
shape = self._selected()
|
|
430
|
+
if self._loading or shape is None:
|
|
431
|
+
return
|
|
432
|
+
shape.name = self._name_edit.text().strip() or shape.type
|
|
433
|
+
shape.center = (
|
|
434
|
+
self._center_spins[0].value(),
|
|
435
|
+
self._center_spins[1].value(),
|
|
436
|
+
self._center_spins[2].value(),
|
|
437
|
+
)
|
|
438
|
+
shape.axis = self._axis_combo.currentText()
|
|
439
|
+
shape.params = {param: spin.value() for param, spin in self._param_spins.items()}
|
|
440
|
+
item = self._shape_list.currentItem()
|
|
441
|
+
if item is not None:
|
|
442
|
+
item.setText(f"{shape.name} ({shape.type})")
|
|
443
|
+
self._changed(select=None, rebuild=False)
|
|
444
|
+
|
|
445
|
+
def _changed(self, select: int | None = None, rebuild: bool = True) -> None:
|
|
446
|
+
self._dirty = True
|
|
447
|
+
if rebuild:
|
|
448
|
+
self._refresh_list(select=select)
|
|
449
|
+
self._publish()
|
|
450
|
+
self._update_summary()
|
|
451
|
+
|
|
452
|
+
def _update_summary(self) -> None:
|
|
453
|
+
if self._name is None:
|
|
454
|
+
self._summary.setText("No geometry yet: New… starts one.")
|
|
455
|
+
return
|
|
456
|
+
where = str(self._path) if self._path is not None else "Not saved yet"
|
|
457
|
+
text = f"{where}{' (unsaved changes)' if self._dirty else ''} — {len(self._shapes)} shape(s)"
|
|
458
|
+
if self._references:
|
|
459
|
+
text += f", plus {len(self._references)} raw wireframe(s) kept as they are"
|
|
460
|
+
self._summary.setText(text + ".")
|
|
461
|
+
|
|
462
|
+
# -- drawing -----------------------------------------------------------
|
|
463
|
+
|
|
464
|
+
def _config(self) -> dict:
|
|
465
|
+
return geometry_config(self._base, self._shapes)
|
|
466
|
+
|
|
467
|
+
def _publish(self) -> None:
|
|
468
|
+
"""Show the edit in every open space and skeleton plot, the selected shape highlighted."""
|
|
469
|
+
if self._name is None:
|
|
470
|
+
self.app_state.space_geometry_preview = None
|
|
471
|
+
return
|
|
472
|
+
selected = self._selected()
|
|
473
|
+
shown = [
|
|
474
|
+
Shape(s.name, s.type, s.center, dict(s.params), s.axis, _SELECTED_COLOR) if s is selected else s
|
|
475
|
+
for s in self._shapes
|
|
476
|
+
]
|
|
477
|
+
preview: dict = {"name": self._name, "config": geometry_config(self._base, shown)}
|
|
478
|
+
if selected is not None:
|
|
479
|
+
preview["axes"] = {"center": list(selected.center), "half_length": axes_half_length(selected)}
|
|
480
|
+
self.app_state.space_geometry_preview = preview
|
|
481
|
+
self.app_state.space_library_geometry = self._name
|
|
482
|
+
|
|
483
|
+
# -- save / close ------------------------------------------------------
|
|
484
|
+
|
|
485
|
+
def _save(self) -> None:
|
|
486
|
+
if self._name is None:
|
|
487
|
+
return
|
|
488
|
+
path = Path(self._target_combo.currentData()) / f"{self._name}.yaml"
|
|
489
|
+
try:
|
|
490
|
+
write_geometry_yaml(path, self._config())
|
|
491
|
+
except OSError as exc:
|
|
492
|
+
notify(f"Could not save {path}: {exc}", "warning")
|
|
493
|
+
return
|
|
494
|
+
self._dirty = False
|
|
495
|
+
notify(f"Saved {path}")
|
|
496
|
+
self._reload_library(select=self._name, row=max(self._shape_list.currentRow(), 0))
|
|
497
|
+
if self._on_saved is not None:
|
|
498
|
+
self._on_saved()
|
|
499
|
+
|
|
500
|
+
def _sync_panel_hint(self) -> None:
|
|
501
|
+
self._no_panel.setVisible(self._panels_open is not None and not self._panels_open())
|
|
502
|
+
|
|
503
|
+
def _on_open_panel(self) -> None:
|
|
504
|
+
if self._open_panel is not None:
|
|
505
|
+
self._open_panel()
|
|
506
|
+
self._publish()
|
|
507
|
+
self._sync_panel_hint()
|
|
508
|
+
|
|
509
|
+
def changeEvent(self, event) -> None:
|
|
510
|
+
# A plot opened or closed while this window was in the background.
|
|
511
|
+
if event.type() == QEvent.Type.ActivationChange and self.isActiveWindow():
|
|
512
|
+
self._sync_panel_hint()
|
|
513
|
+
super().changeEvent(event)
|
|
514
|
+
|
|
515
|
+
def _drop_preview(self, *_args) -> None:
|
|
516
|
+
"""The plots go back to drawing the file."""
|
|
517
|
+
self.app_state.space_geometry_preview = None
|
|
518
|
+
|
|
519
|
+
def reject(self) -> None:
|
|
520
|
+
if self._confirm_discard():
|
|
521
|
+
super().reject()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""One colour per individual, shown wherever an individual is named.
|
|
2
|
+
|
|
3
|
+
The pose overlay and the skeleton panel colour each animal from
|
|
4
|
+
:func:`~ethograph.gui.pose_convert.individual_color_map` (the palette by the
|
|
5
|
+
dataset's order, the user's own picks winning). The same swatch goes next to the
|
|
6
|
+
name in the sidebar's Individual and Receiver combos and in every pin menu, so
|
|
7
|
+
"which one is mouse 2" is answered by looking, not by reading a legend.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from qtpy.QtGui import QColor, QIcon, QPixmap
|
|
13
|
+
from qtpy.QtWidgets import QComboBox, QMenu
|
|
14
|
+
|
|
15
|
+
from ethograph.gui.pose_convert import individual_color_map
|
|
16
|
+
|
|
17
|
+
_SWATCH_PX = 14
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def color_swatch_icon(color: str | tuple, size: int = _SWATCH_PX) -> QIcon:
|
|
21
|
+
"""A filled square: *color* is a hex string or an RGBA tuple of 0–1 floats."""
|
|
22
|
+
if isinstance(color, str):
|
|
23
|
+
qcolor = QColor(color)
|
|
24
|
+
else:
|
|
25
|
+
qcolor = QColor.fromRgbF(*(float(c) for c in color[:3]))
|
|
26
|
+
pixmap = QPixmap(size, size)
|
|
27
|
+
pixmap.fill(qcolor)
|
|
28
|
+
return QIcon(pixmap)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def individual_swatches(app_state, names: list[str]) -> dict[str, QIcon]:
|
|
32
|
+
"""The swatch of every individual in *names* — the colour the overlay draws it in.
|
|
33
|
+
|
|
34
|
+
*names* should be the dataset's full list (``app_state.label_individuals()``)
|
|
35
|
+
so an animal keeps its colour whatever subset a widget offers.
|
|
36
|
+
"""
|
|
37
|
+
overrides = getattr(app_state, "pose_individual_colors", None) or {}
|
|
38
|
+
return {name: color_swatch_icon(rgba) for name, rgba in individual_color_map(names, overrides).items()}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def decorate_individual_combo(combo: QComboBox, app_state, names: list[str] | None = None) -> None:
|
|
42
|
+
"""Put each individual's swatch beside its entry; entries naming nobody keep none.
|
|
43
|
+
|
|
44
|
+
Items are matched by their data (the raw name) first, then their text, so
|
|
45
|
+
both the sidebar's display-label combos and plain ``addItems`` combos work.
|
|
46
|
+
"""
|
|
47
|
+
if names is None:
|
|
48
|
+
names = list(app_state.label_individuals())
|
|
49
|
+
swatches = individual_swatches(app_state, [str(n) for n in names])
|
|
50
|
+
for index in range(combo.count()):
|
|
51
|
+
key = combo.itemData(index)
|
|
52
|
+
icon = swatches.get(str(key)) if key not in (None, "") else None
|
|
53
|
+
if icon is None:
|
|
54
|
+
icon = swatches.get(combo.itemText(index))
|
|
55
|
+
combo.setItemIcon(index, icon if icon is not None else QIcon())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def decorate_menu(menu: QMenu, app_state, names: list[str] | None = None) -> None:
|
|
59
|
+
"""Swatch every action of *menu* whose text is an individual's name."""
|
|
60
|
+
if names is None:
|
|
61
|
+
names = list(app_state.label_individuals())
|
|
62
|
+
swatches = individual_swatches(app_state, [str(n) for n in names])
|
|
63
|
+
for action in menu.actions():
|
|
64
|
+
icon = swatches.get(action.text())
|
|
65
|
+
if icon is not None:
|
|
66
|
+
action.setIcon(icon)
|
|
@@ -45,6 +45,7 @@ from .app_constants import (
|
|
|
45
45
|
PLOT_CONTAINER_SIZE_HINT_HEIGHT,
|
|
46
46
|
)
|
|
47
47
|
from .audio_player import AudioPlayer
|
|
48
|
+
from .individual_colors import individual_swatches
|
|
48
49
|
from .label_drawing_mixin import LabelDrawingMixin
|
|
49
50
|
from .plots_audiotrace import AudioTracePlot
|
|
50
51
|
from .plots_base import ThrottleDebounce, right_gutter_width
|
|
@@ -172,11 +173,14 @@ class CurrentLabelIndicator(QLabel):
|
|
|
172
173
|
self.move(max(0, x), self._MARGIN)
|
|
173
174
|
|
|
174
175
|
|
|
175
|
-
def add_pin_choices(
|
|
176
|
+
def add_pin_choices(
|
|
177
|
+
menu: QMenu, names: list[str], pinned: str | None, sidebar: str | None, choose, app_state=None
|
|
178
|
+
) -> None:
|
|
176
179
|
"""One radio choice for a panel's individual: follow the sidebar, or one name.
|
|
177
180
|
|
|
178
181
|
Exactly one entry is checked, so the menu itself says the two are
|
|
179
|
-
alternatives. *choose* is called with ``None`` (follow) or a name.
|
|
182
|
+
alternatives. *choose* is called with ``None`` (follow) or a name. With
|
|
183
|
+
*app_state*, every name carries the swatch the overlay draws it in.
|
|
180
184
|
"""
|
|
181
185
|
group = QActionGroup(menu)
|
|
182
186
|
group.setExclusive(True)
|
|
@@ -186,8 +190,11 @@ def add_pin_choices(menu: QMenu, names: list[str], pinned: str | None, sidebar:
|
|
|
186
190
|
follow.triggered.connect(lambda _=False: choose(None))
|
|
187
191
|
group.addAction(follow)
|
|
188
192
|
menu.addSeparator()
|
|
193
|
+
swatches = individual_swatches(app_state, names) if app_state is not None else {}
|
|
189
194
|
for name in names:
|
|
190
195
|
action = menu.addAction(name)
|
|
196
|
+
if name in swatches:
|
|
197
|
+
action.setIcon(swatches[name])
|
|
191
198
|
action.setCheckable(True)
|
|
192
199
|
action.setChecked(pinned == name)
|
|
193
200
|
action.triggered.connect(lambda _=False, n=name: choose(n))
|
|
@@ -566,7 +573,12 @@ class UnifiedPanelContainer(LabelDrawingMixin, QWidget):
|
|
|
566
573
|
menu.addSeparator()
|
|
567
574
|
sub = menu.addMenu("Individual")
|
|
568
575
|
add_pin_choices(
|
|
569
|
-
sub,
|
|
576
|
+
sub,
|
|
577
|
+
names,
|
|
578
|
+
plot.pinned_individual,
|
|
579
|
+
self.app_state.sidebar_individual(),
|
|
580
|
+
lambda n: self.pin_panel(plot, n),
|
|
581
|
+
app_state=self.app_state,
|
|
570
582
|
)
|
|
571
583
|
|
|
572
584
|
def pin_panel(self, plot, individual: str | None) -> None:
|