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