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.
Files changed (523) hide show
  1. {ethograph-0.2.44 → ethograph-0.2.45}/PKG-INFO +1 -1
  2. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/active_panel.py +1 -0
  3. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/app_state.py +3 -0
  4. ethograph-0.2.45/ethograph/gui/dialog_space_geometry.py +521 -0
  5. ethograph-0.2.45/ethograph/gui/individual_colors.py +66 -0
  6. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_container.py +15 -3
  7. ethograph-0.2.45/ethograph/gui/plots_skeleton.py +964 -0
  8. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_space.py +210 -90
  9. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_render.py +47 -27
  10. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/right_context.py +6 -0
  11. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/source_popup.py +6 -0
  12. ethograph-0.2.45/ethograph/gui/space_shapes.py +225 -0
  13. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/top_bar.py +42 -0
  14. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_data.py +112 -5
  15. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_meta.py +13 -2
  16. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_plot_settings.py +21 -3
  17. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/data_loader.py +15 -6
  18. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/PKG-INFO +1 -1
  19. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/SOURCES.txt +4 -0
  20. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/scm_file_list.json +9 -0
  21. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/scm_version.json +2 -2
  22. ethograph-0.2.45/examples/convert_dataset_pair24.ipynb +2160 -0
  23. ethograph-0.2.44/examples/convert_dataset_pair24.ipynb +0 -294
  24. {ethograph-0.2.44 → ethograph-0.2.45}/.claude/settings.local.json +0 -0
  25. {ethograph-0.2.44 → ethograph-0.2.45}/.gitattributes +0 -0
  26. {ethograph-0.2.44 → ethograph-0.2.45}/.github/copilot-instructions.md +0 -0
  27. {ethograph-0.2.44 → ethograph-0.2.45}/.github/workflows/docs_build_and_deploy.yml +0 -0
  28. {ethograph-0.2.44 → ethograph-0.2.45}/.github/workflows/test_and_deploy.yml +0 -0
  29. {ethograph-0.2.44 → ethograph-0.2.45}/.gitignore +0 -0
  30. {ethograph-0.2.44 → ethograph-0.2.45}/.readthedocs.yaml +0 -0
  31. {ethograph-0.2.44 → ethograph-0.2.45}/CLAUDE.md +0 -0
  32. {ethograph-0.2.44 → ethograph-0.2.45}/LICENSE +0 -0
  33. {ethograph-0.2.44 → ethograph-0.2.45}/MANIFEST.in +0 -0
  34. {ethograph-0.2.44 → ethograph-0.2.45}/README.md +0 -0
  35. {ethograph-0.2.44 → ethograph-0.2.45}/THIRD_PARTY_NOTICES.md +0 -0
  36. {ethograph-0.2.44 → ethograph-0.2.45}/TODO.md +0 -0
  37. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/__init__.py +0 -0
  38. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/__main__.py +0 -0
  39. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/_vendor/__init__.py +0 -0
  40. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/_vendor/vocalseg/LICENSE +0 -0
  41. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/_vendor/vocalseg/NOTICE.md +0 -0
  42. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/_vendor/vocalseg/__init__.py +0 -0
  43. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/_vendor/vocalseg/continuity_filtering.py +0 -0
  44. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/_vendor/vocalseg/dynamic_thresholding.py +0 -0
  45. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/_vendor/vocalseg/utils.py +0 -0
  46. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/assets/icon.ico +0 -0
  47. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/assets/icon.png +0 -0
  48. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/assets/menu.json +0 -0
  49. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/assets/space/moll2025.yaml +0 -0
  50. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/cli.py +0 -0
  51. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/datasets.py +0 -0
  52. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/defaults/README.md +0 -0
  53. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/defaults/mapping.txt +0 -0
  54. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/defaults/octron/octron.yaml +0 -0
  55. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/defaults/segment/segment.yaml +0 -0
  56. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/defaults/spot/spot.yaml +0 -0
  57. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/__init__.py +0 -0
  58. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/audio_changepoints.py +0 -0
  59. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/changepoints.py +0 -0
  60. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/columns.py +0 -0
  61. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/energy.py +0 -0
  62. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/geometry.py +0 -0
  63. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/label_inputs.py +0 -0
  64. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/movement.py +0 -0
  65. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/neural.py +0 -0
  66. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/oscillatory.py +0 -0
  67. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/features/preprocessing.py +0 -0
  68. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/__init__.py +0 -0
  69. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/app_constants.py +0 -0
  70. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/birdpark0.png +0 -0
  71. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/birdpark1.png +0 -0
  72. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/canary.png +0 -0
  73. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/cricket0.png +0 -0
  74. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/cricket1.png +0 -0
  75. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/cricket2.png +0 -0
  76. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/lockbox1.png +0 -0
  77. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/lockbox2.gif +0 -0
  78. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/moll1.png +0 -0
  79. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/templates/moll2.png +0 -0
  80. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/wizard/video_setup_free_running.png +0 -0
  81. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/assets/wizard/video_setup_triggered.png +0 -0
  82. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/audio_clock.py +0 -0
  83. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/audio_player.py +0 -0
  84. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/box_annotate.py +0 -0
  85. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/box_overlay.py +0 -0
  86. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/cover_page.py +0 -0
  87. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_ambiguous_session.py +0 -0
  88. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_box_labelling.py +0 -0
  89. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_bulk_labels.py +0 -0
  90. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_busy_progress.py +0 -0
  91. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_collected_data.py +0 -0
  92. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_curation_workflow.py +0 -0
  93. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_curator_feedback.py +0 -0
  94. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_fit.py +0 -0
  95. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_function_params.py +0 -0
  96. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_keypoint_filter.py +0 -0
  97. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_label_gridview.py +0 -0
  98. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_label_inconsistencies.py +0 -0
  99. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_label_table.py +0 -0
  100. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_onset_model.py +0 -0
  101. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_pose_labelling.py +0 -0
  102. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_pose_refinement.py +0 -0
  103. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_pose_video_matcher.py +0 -0
  104. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_review_histogram.py +0 -0
  105. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_screen_recorder.py +0 -0
  106. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_select_template.py +0 -0
  107. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_session_folder.py +0 -0
  108. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_settings.py +0 -0
  109. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_skeleton_editor.py +0 -0
  110. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_spot_crop.py +0 -0
  111. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_tag_sheet.py +0 -0
  112. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/dialog_video_grid.py +0 -0
  113. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/file_dialogs.py +0 -0
  114. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/grid_section_container.py +0 -0
  115. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/heatmap_sort.py +0 -0
  116. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/label_drawing_mixin.py +0 -0
  117. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/left_sidebar.py +0 -0
  118. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/main_window.py +0 -0
  119. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/make_pretty.py +0 -0
  120. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/napari.yaml +0 -0
  121. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/notify.py +0 -0
  122. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_audiotrace.py +0 -0
  123. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_base.py +0 -0
  124. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_console.py +0 -0
  125. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_ephystrace.py +0 -0
  126. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_heatmap.py +0 -0
  127. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_labelribbon.py +0 -0
  128. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_lineplot.py +0 -0
  129. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_overlay.py +0 -0
  130. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_psth.py +0 -0
  131. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_radial.py +0 -0
  132. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_raster.py +0 -0
  133. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/plots_spectrogram.py +0 -0
  134. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_annotate.py +0 -0
  135. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_convert.py +0 -0
  136. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_detect.py +0 -0
  137. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_detect_preview.py +0 -0
  138. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_edit_mixin.py +0 -0
  139. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_fill.py +0 -0
  140. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_overlay.py +0 -0
  141. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_project_mode.py +0 -0
  142. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_refine.py +0 -0
  143. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_static_group.py +0 -0
  144. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_suggest.py +0 -0
  145. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_tagsheet.py +0 -0
  146. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pose_training_export.py +0 -0
  147. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/project.py +0 -0
  148. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/proxy_manager.py +0 -0
  149. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/pygfx_video.py +0 -0
  150. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/session_folder.py +0 -0
  151. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/shortcuts.py +0 -0
  152. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/table_filter.py +0 -0
  153. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/discover.py.j2 +0 -0
  154. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/open.md.j2 +0 -0
  155. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/parameters.py.j2 +0 -0
  156. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/timing_offset.py.j2 +0 -0
  157. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/timing_onsets.py.j2 +0 -0
  158. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/timing_pulses_free.py.j2 +0 -0
  159. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/timing_pulses_triggered.py.j2 +0 -0
  160. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/write_neuroconv.py.j2 +0 -0
  161. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/notebook/write_pair.py.j2 +0 -0
  162. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/templates/wizard_nwb_codegen.j2 +0 -0
  163. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/theme.py +0 -0
  164. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/video_manager.py +0 -0
  165. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/video_sync.py +0 -0
  166. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widget_trials.py +0 -0
  167. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_bottom_bar.py +0 -0
  168. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_changepoints.py +0 -0
  169. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_curation.py +0 -0
  170. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_ephys.py +0 -0
  171. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_frame_extract.py +0 -0
  172. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_help.py +0 -0
  173. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_io.py +0 -0
  174. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_labels.py +0 -0
  175. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_navigation.py +0 -0
  176. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_psth.py +0 -0
  177. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/widgets_transform.py +0 -0
  178. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_boris.py +0 -0
  179. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_media_files.py +0 -0
  180. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_multi_builder.py +0 -0
  181. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_multi_codegen.py +0 -0
  182. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_multi_tabs.py +0 -0
  183. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_multi_timeline.py +0 -0
  184. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_multi_trials.py +0 -0
  185. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_notebook.py +0 -0
  186. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_nwb.py +0 -0
  187. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_overview.py +0 -0
  188. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_pages.py +0 -0
  189. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_single.py +0 -0
  190. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/gui/wizard_state.py +0 -0
  191. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/audio_extract.py +0 -0
  192. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/catalog.py +0 -0
  193. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/dataset.py +0 -0
  194. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/derived.py +0 -0
  195. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/ephys_loader.py +0 -0
  196. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/external_features.py +0 -0
  197. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/image_sequence.py +0 -0
  198. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/metadata_edit.py +0 -0
  199. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/metadata_table.py +0 -0
  200. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/nc_drop.py +0 -0
  201. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/netcdf.py +0 -0
  202. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/nwb_alignment.py +0 -0
  203. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/nwb_import.py +0 -0
  204. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/overlay_source.py +0 -0
  205. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/pairing.py +0 -0
  206. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/plot_sources.py +0 -0
  207. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/pynapple.py +0 -0
  208. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/schema.py +0 -0
  209. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/session_layout.py +0 -0
  210. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/time_model.py +0 -0
  211. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/time_sources.py +0 -0
  212. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/trialtree.py +0 -0
  213. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/validation.py +0 -0
  214. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/video_decode.py +0 -0
  215. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/video_feature_files.py +0 -0
  216. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/video_probe.py +0 -0
  217. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/io/video_proxy.py +0 -0
  218. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/__init__.py +0 -0
  219. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/boris.py +0 -0
  220. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/check_labels.py +0 -0
  221. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/confidence.py +0 -0
  222. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/converters.py +0 -0
  223. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/crowsetta_format.py +0 -0
  224. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/curation.py +0 -0
  225. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/export.py +0 -0
  226. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/exporters/__init__.py +0 -0
  227. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/exporters/crowlab.py +0 -0
  228. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/intervals.py +0 -0
  229. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/label_inputs.py +0 -0
  230. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/ml.py +0 -0
  231. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/octron_project.py +0 -0
  232. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/onset_curves.py +0 -0
  233. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/onset_model.py +0 -0
  234. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/plots.py +0 -0
  235. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/pose_project.py +0 -0
  236. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/predictions.py +0 -0
  237. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/rescore.py +0 -0
  238. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/review_metrics.py +0 -0
  239. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/tsv_store.py +0 -0
  240. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/labels/workflow.py +0 -0
  241. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/__init__.py +0 -0
  242. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/augment.py +0 -0
  243. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/config.py +0 -0
  244. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/crossval.py +0 -0
  245. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dataset.py +0 -0
  246. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/LICENSE.APGPL +0 -0
  247. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/NOTICE.md +0 -0
  248. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/__init__.py +0 -0
  249. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/colors.txt +0 -0
  250. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/losses.yaml +0 -0
  251. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/asformer.yaml +0 -0
  252. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/c2f_tcn.yaml +0 -0
  253. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/c2f_transformer.yaml +0 -0
  254. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/edtcn.yaml +0 -0
  255. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/mlp.yaml +0 -0
  256. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/motionbert.yaml +0 -0
  257. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/ms_tcn3.yaml +0 -0
  258. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/model/transformer.yaml +0 -0
  259. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/config/training.yaml +0 -0
  260. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/loss/__init__.py +0 -0
  261. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/loss/asymmetric_loss.py +0 -0
  262. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/loss/ms_tcn.py +0 -0
  263. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/loss/mse.py +0 -0
  264. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/loss/tcc.py +0 -0
  265. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/__init__.py +0 -0
  266. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/asformer.py +0 -0
  267. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/base_model.py +0 -0
  268. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/c2f_tcn.py +0 -0
  269. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/c2f_tcn_par.py +0 -0
  270. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/c2f_transformer.py +0 -0
  271. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/c3d.py +0 -0
  272. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/edtcn.py +0 -0
  273. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/mlp.py +0 -0
  274. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/motionbert.py +0 -0
  275. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/motionbert_modules.py +0 -0
  276. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/ms_tcn.py +0 -0
  277. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/ms_tcn_modules.py +0 -0
  278. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/model/transformer.py +0 -0
  279. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/utils.py +0 -0
  280. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/dlc2action/version.py +0 -0
  281. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/feral.py +0 -0
  282. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/feral_defaults/NOTICE.md +0 -0
  283. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/feral_defaults/default_config.yaml +0 -0
  284. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/feral_defaults/presets.yaml +0 -0
  285. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/inference.py +0 -0
  286. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/joints.py +0 -0
  287. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/LICENSE +0 -0
  288. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/NOTICE.md +0 -0
  289. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/__init__.py +0 -0
  290. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/config/defaults.yaml +0 -0
  291. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/generalized_coordinates.py +0 -0
  292. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/loss_fn/ECloss.py +0 -0
  293. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/loss_fn/__init__.py +0 -0
  294. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/models/LaDy.py +0 -0
  295. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/models/SP.py +0 -0
  296. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/models/__init__.py +0 -0
  297. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/models/dynamics.py +0 -0
  298. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/models/graph/__init__.py +0 -0
  299. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/models/graph/tools.py +0 -0
  300. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/lady/models/tcn.py +0 -0
  301. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/losses.py +0 -0
  302. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/materialise.py +0 -0
  303. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/metrics.py +0 -0
  304. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/models/__init__.py +0 -0
  305. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/models/config/rnn.yaml +0 -0
  306. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/models/rnn.py +0 -0
  307. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/models/skeleton_graph.py +0 -0
  308. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/models/vendored.py +0 -0
  309. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/plotting.py +0 -0
  310. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/postprocess.py +0 -0
  311. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/prediction_sets.py +0 -0
  312. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/preprocess.py +0 -0
  313. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/project.py +0 -0
  314. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/roles.py +0 -0
  315. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/samples.py +0 -0
  316. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/search.py +0 -0
  317. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/sessions.py +0 -0
  318. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/LICENSE +0 -0
  319. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/NOTICE.md +0 -0
  320. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/__init__.py +0 -0
  321. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/config/defaults.yaml +0 -0
  322. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/models/SP.py +0 -0
  323. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/models/SpecScalpel.py +0 -0
  324. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/models/__init__.py +0 -0
  325. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/models/graph/__init__.py +0 -0
  326. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/models/graph/tools.py +0 -0
  327. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/specscalpel/models/tcn.py +0 -0
  328. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/train.py +0 -0
  329. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/video_features.py +0 -0
  330. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/segment/windows.py +0 -0
  331. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/shortcuts.py +0 -0
  332. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/CREDITS.md +0 -0
  333. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/__init__.py +0 -0
  334. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/config.py +0 -0
  335. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/graph.py +0 -0
  336. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/library.py +0 -0
  337. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/renderers/__init__.py +0 -0
  338. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/renderers/base.py +0 -0
  339. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/renderers/precomputed.py +0 -0
  340. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/shapes.py +0 -0
  341. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/state.py +0 -0
  342. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/skeleton/templates.py +0 -0
  343. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/__init__.py +0 -0
  344. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/confidence.py +0 -0
  345. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/config.py +0 -0
  346. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/crossval.py +0 -0
  347. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/dataset.py +0 -0
  348. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/LICENSE +0 -0
  349. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/NOTICE.md +0 -0
  350. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/__init__.py +0 -0
  351. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/dataset/__init__.py +0 -0
  352. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/dataset/frame.py +0 -0
  353. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/dataset/transform.py +0 -0
  354. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/__init__.py +0 -0
  355. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/common.py +0 -0
  356. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/impl/__init__.py +0 -0
  357. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/impl/asformer.py +0 -0
  358. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/impl/gsm.py +0 -0
  359. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/impl/tsm.py +0 -0
  360. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/modules.py +0 -0
  361. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/model/shift.py +0 -0
  362. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/test_e2e.py +0 -0
  363. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/train_e2e.py +0 -0
  364. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/util/__init__.py +0 -0
  365. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/util/dataset.py +0 -0
  366. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/util/eval.py +0 -0
  367. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/util/io.py +0 -0
  368. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/e2espot/util/score.py +0 -0
  369. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/features.py +0 -0
  370. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/inference.py +0 -0
  371. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/metrics.py +0 -0
  372. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/msagsm.py +0 -0
  373. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/pose_batch.py +0 -0
  374. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/prediction_sets.py +0 -0
  375. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/project.py +0 -0
  376. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/stream.py +0 -0
  377. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/spot/vendored.py +0 -0
  378. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/__init__.py +0 -0
  379. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/arraytools.py +0 -0
  380. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/audio.py +0 -0
  381. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/configkit.py +0 -0
  382. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/device.py +0 -0
  383. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/download.py +0 -0
  384. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/ffmpeg.py +0 -0
  385. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/logging.py +0 -0
  386. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/nwb.py +0 -0
  387. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/paths.py +0 -0
  388. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/qt.py +0 -0
  389. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/sequences.py +0 -0
  390. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/stream_durations.py +0 -0
  391. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/system_check.py +0 -0
  392. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/utils/xr_utils.py +0 -0
  393. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/__init__.py +0 -0
  394. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/base.py +0 -0
  395. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/checkpoint/S3D_kinetics400_torchified.pt +0 -0
  396. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/extract.py +0 -0
  397. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/frames.py +0 -0
  398. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/plan.py +0 -0
  399. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/s3d.py +0 -0
  400. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/select.py +0 -0
  401. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph/video_features/timm_extract.py +0 -0
  402. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/dependency_links.txt +0 -0
  403. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/entry_points.txt +0 -0
  404. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/requires.txt +0 -0
  405. {ethograph-0.2.44 → ethograph-0.2.45}/ethograph.egg-info/top_level.txt +0 -0
  406. {ethograph-0.2.44 → ethograph-0.2.45}/examples/README.md +0 -0
  407. {ethograph-0.2.44 → ethograph-0.2.45}/examples/birdpark_dev/birdpark_pose.ipynb +0 -0
  408. {ethograph-0.2.44 → ethograph-0.2.45}/examples/convert_dataset_birdpark.ipynb +0 -0
  409. {ethograph-0.2.44 → ethograph-0.2.45}/examples/convert_datset_lockbox.ipynb +0 -0
  410. {ethograph-0.2.44 → ethograph-0.2.45}/examples/create_dataset_Moll25.ipynb +0 -0
  411. {ethograph-0.2.44 → ethograph-0.2.45}/examples/create_dataset_cricket.ipynb +0 -0
  412. {ethograph-0.2.44 → ethograph-0.2.45}/examples/segement_s3d_features.ipynb +0 -0
  413. {ethograph-0.2.44 → ethograph-0.2.45}/examples/segment_changepoint_features.ipynb +0 -0
  414. {ethograph-0.2.44 → ethograph-0.2.45}/notes/Bench_neural-decoding.md +0 -0
  415. {ethograph-0.2.44 → ethograph-0.2.45}/notes/Bench_smoothalpha.md +0 -0
  416. {ethograph-0.2.44 → ethograph-0.2.45}/notes/Lit_curator-feedback.md +0 -0
  417. {ethograph-0.2.44 → ethograph-0.2.45}/notes/Lit_dlc2action.md +0 -0
  418. {ethograph-0.2.44 → ethograph-0.2.45}/notes/Lit_feralbeast.md +0 -0
  419. {ethograph-0.2.44 → ethograph-0.2.45}/notes/Lit_precise-event-spotting.md +0 -0
  420. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0001-vendor-dlc2action-architectures.md +0 -0
  421. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0002-literature-dataset-layout.md +0 -0
  422. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0003-advisory-variable-schema.md +0 -0
  423. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0004-scripted-not-cli.md +0 -0
  424. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0005-search-then-cross-validate.md +0 -0
  425. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0006-postprocess-from-gui-settings.md +0 -0
  426. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0007-distil-inside-the-vendored-trainer.md +0 -0
  427. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0008-features-not-graphs.md +0 -0
  428. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0009-extractors-pip-installed-not-vendored.md +0 -0
  429. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0009-spikes-as-a-config-transform.md +0 -0
  430. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0010-multilabel-targets-are-tracks.md +0 -0
  431. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0011-labels-as-inputs-across-branches.md +0 -0
  432. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0012-feral-is-an-external-extractor.md +0 -0
  433. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0013-no-learned-boundary-model-over-pose.md +0 -0
  434. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0013-session-is-a-folder-media-are-absolute.md +0 -0
  435. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0014-pose-project-as-two-sessions.md +0 -0
  436. {ethograph-0.2.44 → ethograph-0.2.45}/notes/adr/0015-flat-project-folder-and-libraries.md +0 -0
  437. {ethograph-0.2.44 → ethograph-0.2.45}/notes/feedback/audio_clock_dac_timestamps.md +0 -0
  438. {ethograph-0.2.44 → ethograph-0.2.45}/notes/feedback/debug_launcher_clock_patch.md +0 -0
  439. {ethograph-0.2.44 → ethograph-0.2.45}/notes/feedback/ethograph_annotation_feasibility_report.md +0 -0
  440. {ethograph-0.2.44 → ethograph-0.2.45}/notes/feedback/ethograph_windows_author_overview_2026-08-18.md +0 -0
  441. {ethograph-0.2.44 → ethograph-0.2.45}/notes/feedback/processed/ehtograph_feedback_report.md +0 -0
  442. {ethograph-0.2.44 → ethograph-0.2.45}/notes/feedback/processed/handoff_playback_and_video_freeze.md +0 -0
  443. {ethograph-0.2.44 → ethograph-0.2.45}/notes/feedback/reverted_pynapple_trials_timing.diff +0 -0
  444. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/ReadMe.md +0 -0
  445. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/config/space/moll2025.yaml +0 -0
  446. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/mapping.txt +0 -0
  447. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/mapping_setup_audio.txt +0 -0
  448. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/changepoint_settings_old.yaml +0 -0
  449. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/mapping_Ivy.txt +0 -0
  450. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Crow1_inference_20260330_161224.json +0 -0
  451. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Crow1_inference_20260330_161259.json +0 -0
  452. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Freddy_inference_20260330_160621.json +0 -0
  453. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Freddy_train_20251021_164220.json +0 -0
  454. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Freddy_train_20251021_164220_epoch-100.model +0 -0
  455. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Ivy_CV_20260130_173800.json +0 -0
  456. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Ivy_inference_20260203_092442.json +0 -0
  457. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Ivy_inference_20260204_141527.json +0 -0
  458. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Ivy_inference_20260206_122331.json +0 -0
  459. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Ivy_train_20260202_191138_epoch-100.model +0 -0
  460. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Ivy_train_20260203_153754.json +0 -0
  461. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Poppy_inference_20260312_100707.json +0 -0
  462. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Poppy_inference_20260327_102414.json +0 -0
  463. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Poppy_inference_20260330_140808.json +0 -0
  464. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Poppy_inference_20260402_104151.json +0 -0
  465. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Poppy_train_20260326_190925.json +0 -0
  466. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/old config/old segmentation config/Poppy_train_20260331_175332.json +0 -0
  467. {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
  468. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/project.yaml +0 -0
  469. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/sessions.txt +0 -0
  470. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/wizard/CrowBench.ipynb +0 -0
  471. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/wizard/SENTINEL.txt +0 -0
  472. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/workflows/01 PES Curation.yaml +0 -0
  473. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/workflows/Freddy_GBM.yaml +0 -0
  474. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/workflows/Ivy_GBM.yaml +0 -0
  475. {ethograph-0.2.44 → ethograph-0.2.45}/projects/crowlab/workflows/Poppy_GBM.yaml +0 -0
  476. {ethograph-0.2.44 → ethograph-0.2.45}/projects/neural/decoding.yaml +0 -0
  477. {ethograph-0.2.44 → ethograph-0.2.45}/projects/paper/labels.yaml +0 -0
  478. {ethograph-0.2.44 → ethograph-0.2.45}/projects/paper/spot/spot.yaml +0 -0
  479. {ethograph-0.2.44 → ethograph-0.2.45}/pyproject.toml +0 -0
  480. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/Developer.md +0 -0
  481. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/_sweep.py +0 -0
  482. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/bench.py +0 -0
  483. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/bench_search.py +0 -0
  484. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/bench_videofeat.py +0 -0
  485. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/bench_watch.ps1 +0 -0
  486. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/compare.py +0 -0
  487. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/dandi/dandi_ibl-brainwide.ipynb.ipynb +0 -0
  488. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/dandi/nwb_video_testing.ipynb +0 -0
  489. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/describe_sessions.py +0 -0
  490. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/ephys/ephys_drift_diagnosis.ipynb +0 -0
  491. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/ephys/ephys_loader.ipynb +0 -0
  492. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/export_frames.py +0 -0
  493. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/export_neural_rate.py +0 -0
  494. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/feral/export_feral.py +0 -0
  495. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/feral/reinfer_feral.py +0 -0
  496. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/feral/run_feral.py +0 -0
  497. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/feral/score_feral.py +0 -0
  498. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/neuroconv.ipynb +0 -0
  499. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/README.md +0 -0
  500. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/changepoint_bars.py +0 -0
  501. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/crossval_bars.py +0 -0
  502. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/crossval_label_counts.py +0 -0
  503. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/embedding_grid.py +0 -0
  504. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/embedding_heatmaps.py +0 -0
  505. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/figure_combined.py +0 -0
  506. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/figure_neural.py +0 -0
  507. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/figure_review.py +0 -0
  508. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/figure_review_curves.py +0 -0
  509. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/figure_review_speed.py +0 -0
  510. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/s3d_ablation_bars.py +0 -0
  511. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/session.py +0 -0
  512. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/style/style ppt.mplstyle +0 -0
  513. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/paper1/style/style.mplstyle +0 -0
  514. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/release.ps1 +0 -0
  515. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/run.py +0 -0
  516. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/run_sleep.py +0 -0
  517. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/screenshot_gui.py +0 -0
  518. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/spot.py +0 -0
  519. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/test.ipynb +0 -0
  520. {ethograph-0.2.44 → ethograph-0.2.45}/scripts/train_spot.py +0 -0
  521. {ethograph-0.2.44 → ethograph-0.2.45}/setup.cfg +0 -0
  522. {ethograph-0.2.44 → ethograph-0.2.45}/test.ipynb +0 -0
  523. {ethograph-0.2.44 → ethograph-0.2.45}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ethograph
3
- Version: 0.2.44
3
+ Version: 0.2.45
4
4
  Summary: A multimodal timeseries GUI for neuroethology
5
5
  Author-email: Akseli Ilmanen <akseli.ilmanen@gmail.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -27,6 +27,7 @@ class PanelKind:
27
27
  HEATMAP = "heatmap"
28
28
  SPACE = "space"
29
29
  RADIAL = "radial"
30
+ SKELETON = "skeleton"
30
31
  EPHYS = "ephys"
31
32
  NEO = "neo"
32
33
  RASTER = "raster"
@@ -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(menu: QMenu, names: list[str], pinned: str | None, sidebar: str | None, choose) -> None:
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, names, plot.pinned_individual, self.app_state.sidebar_individual(), lambda n: self.pin_panel(plot, n)
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: