ethograph 0.2.3__tar.gz → 0.2.4__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.

Potentially problematic release.


This version of ethograph might be problematic. Click here for more details.

Files changed (201) hide show
  1. {ethograph-0.2.3 → ethograph-0.2.4}/PKG-INFO +2 -1
  2. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/cli.py +5 -6
  3. ethograph-0.2.4/ethograph/gui/grid_section_container.py +227 -0
  4. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/widgets_changepoints.py +1 -1
  5. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/widgets_ephys.py +7 -5
  6. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/widgets_meta.py +5 -4
  7. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/widgets_navigation.py +4 -0
  8. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/widgets_plot_settings.py +8 -2
  9. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/labels/intervals.py +19 -13
  10. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph.egg-info/PKG-INFO +2 -1
  11. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph.egg-info/SOURCES.txt +1 -0
  12. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph.egg-info/requires.txt +1 -0
  13. {ethograph-0.2.3 → ethograph-0.2.4}/pyproject.toml +1 -0
  14. {ethograph-0.2.3 → ethograph-0.2.4}/.claude/settings.local.json +0 -0
  15. {ethograph-0.2.3 → ethograph-0.2.4}/.github/copilot-instructions.md +0 -0
  16. {ethograph-0.2.3 → ethograph-0.2.4}/.github/workflows/docs_build_and_deploy.yml +0 -0
  17. {ethograph-0.2.3 → ethograph-0.2.4}/.github/workflows/test_and_deploy.yml +0 -0
  18. {ethograph-0.2.3 → ethograph-0.2.4}/.gitignore +0 -0
  19. {ethograph-0.2.3 → ethograph-0.2.4}/.readthedocs.yaml +0 -0
  20. {ethograph-0.2.3 → ethograph-0.2.4}/CLAUDE.md +0 -0
  21. {ethograph-0.2.3 → ethograph-0.2.4}/LICENSE +0 -0
  22. {ethograph-0.2.3 → ethograph-0.2.4}/MANIFEST.in +0 -0
  23. {ethograph-0.2.3 → ethograph-0.2.4}/README.md +0 -0
  24. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/ReadMe.md +0 -0
  25. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/changepoint_settings_old.yaml +0 -0
  26. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/mapping.txt +0 -0
  27. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/mapping_Ivy.txt +0 -0
  28. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/mapping_setup_audio.txt +0 -0
  29. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Crow1_inference_20260330_161224.json +0 -0
  30. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Crow1_inference_20260330_161259.json +0 -0
  31. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Freddy_inference_20260330_160621.json +0 -0
  32. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Freddy_train_20251021_164220.json +0 -0
  33. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Freddy_train_20251021_164220_epoch-100.model +0 -0
  34. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Ivy_CV_20260130_173800.json +0 -0
  35. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Ivy_inference_20260203_092442.json +0 -0
  36. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Ivy_inference_20260204_141527.json +0 -0
  37. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Ivy_inference_20260206_122331.json +0 -0
  38. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Ivy_train_20260202_191138_epoch-100.model +0 -0
  39. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Ivy_train_20260203_153754.json +0 -0
  40. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Poppy_inference_20260312_100707.json +0 -0
  41. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Poppy_inference_20260327_102414.json +0 -0
  42. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Poppy_inference_20260330_140808.json +0 -0
  43. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Poppy_inference_20260402_104151.json +0 -0
  44. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Poppy_train_20260326_190925.json +0 -0
  45. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/Poppy_train_20260331_175332.json +0 -0
  46. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/model/ReadMe - model Freddy_train_20251021_164220_epoch-100.txt +0 -0
  47. {ethograph-0.2.3 → ethograph-0.2.4}/configs/crowlab/space.yaml +0 -0
  48. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/__init__.py +0 -0
  49. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/__main__.py +0 -0
  50. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/assets/icon.ico +0 -0
  51. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/assets/icon.png +0 -0
  52. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/assets/menu.json +0 -0
  53. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/crowlab/io_matlab.py +0 -0
  54. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/crowlab/legacy.py +0 -0
  55. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/datasets.py +0 -0
  56. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/features/__init__.py +0 -0
  57. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/features/audio_changepoints.py +0 -0
  58. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/features/changepoints.py +0 -0
  59. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/features/energy.py +0 -0
  60. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/features/movement.py +0 -0
  61. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/features/neural.py +0 -0
  62. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/features/oscillatory.py +0 -0
  63. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/features/preprocessing.py +0 -0
  64. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/__init__.py +0 -0
  65. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/app_constants.py +0 -0
  66. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/app_state.py +0 -0
  67. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/assets/templates/birdpark0.png +0 -0
  68. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/assets/templates/birdpark1.png +0 -0
  69. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/assets/templates/canary.png +0 -0
  70. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/assets/templates/cricket0.png +0 -0
  71. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/assets/templates/cricket1.png +0 -0
  72. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/assets/templates/cricket2.png +0 -0
  73. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/assets/templates/lockbox1.png +0 -0
  74. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/assets/templates/lockbox2.gif +0 -0
  75. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/assets/templates/moll1.png +0 -0
  76. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/assets/templates/moll2.png +0 -0
  77. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/audio_player.py +0 -0
  78. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/dialog_busy_progress.py +0 -0
  79. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/dialog_function_params.py +0 -0
  80. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/dialog_pose_video_matcher.py +0 -0
  81. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/dialog_screen_recorder.py +0 -0
  82. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/dialog_select_template.py +0 -0
  83. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/dialog_video_downsample.py +0 -0
  84. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/label_drawing_mixin.py +0 -0
  85. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/make_pretty.py +0 -0
  86. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/napari.yaml +0 -0
  87. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/notify.py +0 -0
  88. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/plots_audiotrace.py +0 -0
  89. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/plots_base.py +0 -0
  90. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/plots_container.py +0 -0
  91. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/plots_ephystrace.py +0 -0
  92. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/plots_heatmap.py +0 -0
  93. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/plots_lineplot.py +0 -0
  94. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/plots_overlay.py +0 -0
  95. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/plots_psth.py +0 -0
  96. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/plots_raster.py +0 -0
  97. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/plots_space.py +0 -0
  98. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/plots_spectrogram.py +0 -0
  99. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/pose_render.py +0 -0
  100. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/shortcuts.py +0 -0
  101. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/templates/wizard_nwb_codegen.j2 +0 -0
  102. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/video_manager.py +0 -0
  103. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/video_sync.py +0 -0
  104. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/widget_trials.py +0 -0
  105. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/widgets_data.py +0 -0
  106. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/widgets_help.py +0 -0
  107. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/widgets_io.py +0 -0
  108. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/widgets_labels.py +0 -0
  109. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/widgets_psth.py +0 -0
  110. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/widgets_transform.py +0 -0
  111. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/wizard_boris.py +0 -0
  112. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/wizard_media_files.py +0 -0
  113. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/wizard_multi_builder.py +0 -0
  114. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/wizard_multi_codegen.py +0 -0
  115. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/wizard_multi_tabs.py +0 -0
  116. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/wizard_multi_timeline.py +0 -0
  117. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/wizard_multi_trials.py +0 -0
  118. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/wizard_nwb.py +0 -0
  119. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/wizard_overview.py +0 -0
  120. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/gui/wizard_single.py +0 -0
  121. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/io/catalog.py +0 -0
  122. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/io/data_loader.py +0 -0
  123. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/io/dataset.py +0 -0
  124. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/io/ephys_loader.py +0 -0
  125. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/io/metadata_table.py +0 -0
  126. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/io/nwb_alignment.py +0 -0
  127. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/io/nwb_import.py +0 -0
  128. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/io/plot_sources.py +0 -0
  129. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/io/pynapple.py +0 -0
  130. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/io/time_model.py +0 -0
  131. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/io/time_sources.py +0 -0
  132. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/io/trialtree.py +0 -0
  133. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/io/validation.py +0 -0
  134. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/labels/__init__.py +0 -0
  135. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/labels/boris.py +0 -0
  136. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/labels/converters.py +0 -0
  137. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/labels/crowsetta_format.py +0 -0
  138. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/labels/export.py +0 -0
  139. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/labels/ml.py +0 -0
  140. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/labels/plots.py +0 -0
  141. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/labels/predictions.py +0 -0
  142. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/labels/tsv_store.py +0 -0
  143. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/model/batch_gen.py +0 -0
  144. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/model/cetnet_encoder.py +0 -0
  145. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/model/dataset.py +0 -0
  146. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/model/eval_metrics.py +0 -0
  147. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/model/eval_plotting.py +0 -0
  148. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/shortcuts.py +0 -0
  149. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/utils/__init__.py +0 -0
  150. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/utils/arraytools.py +0 -0
  151. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/utils/audio.py +0 -0
  152. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/utils/download.py +0 -0
  153. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/utils/nwb.py +0 -0
  154. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/utils/paths.py +0 -0
  155. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/utils/qt.py +0 -0
  156. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/utils/sequences.py +0 -0
  157. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/utils/stream_durations.py +0 -0
  158. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/utils/xr_utils.py +0 -0
  159. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/video_features/base_extractor.py +0 -0
  160. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/video_features/checkpoint/S3D_kinetics400_torchified.pt +0 -0
  161. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/video_features/extract_s3d.py +0 -0
  162. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/video_features/s3d.py +0 -0
  163. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/video_features/s3d.yml +0 -0
  164. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/video_features/transforms.py +0 -0
  165. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph/video_features/utils.py +0 -0
  166. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph.egg-info/dependency_links.txt +0 -0
  167. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph.egg-info/entry_points.txt +0 -0
  168. {ethograph-0.2.3 → ethograph-0.2.4}/ethograph.egg-info/top_level.txt +0 -0
  169. {ethograph-0.2.3 → ethograph-0.2.4}/examples/assets/birdpark0.png +0 -0
  170. {ethograph-0.2.3 → ethograph-0.2.4}/examples/assets/birdpark1.png +0 -0
  171. {ethograph-0.2.3 → ethograph-0.2.4}/examples/assets/canary.png +0 -0
  172. {ethograph-0.2.3 → ethograph-0.2.4}/examples/assets/cricket0.png +0 -0
  173. {ethograph-0.2.3 → ethograph-0.2.4}/examples/assets/cricket1.png +0 -0
  174. {ethograph-0.2.3 → ethograph-0.2.4}/examples/assets/cricket2.png +0 -0
  175. {ethograph-0.2.3 → ethograph-0.2.4}/examples/assets/lockbox1.png +0 -0
  176. {ethograph-0.2.3 → ethograph-0.2.4}/examples/assets/lockbox2.gif +0 -0
  177. {ethograph-0.2.3 → ethograph-0.2.4}/examples/assets/moll1.png +0 -0
  178. {ethograph-0.2.3 → ethograph-0.2.4}/examples/assets/moll2.png +0 -0
  179. {ethograph-0.2.3 → ethograph-0.2.4}/examples/convert_dataset_birdpark.ipynb +0 -0
  180. {ethograph-0.2.3 → ethograph-0.2.4}/examples/convert_dataset_pair24.ipynb +0 -0
  181. {ethograph-0.2.3 → ethograph-0.2.4}/examples/convert_datset_lockbox.ipynb +0 -0
  182. {ethograph-0.2.3 → ethograph-0.2.4}/examples/create_dataset_Moll25.ipynb +0 -0
  183. {ethograph-0.2.3 → ethograph-0.2.4}/examples/create_dataset_cricket.ipynb +0 -0
  184. {ethograph-0.2.3 → ethograph-0.2.4}/examples/segment_audio_whisperseg.ipynb +0 -0
  185. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/DOCS_commands.md +0 -0
  186. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/PUBLISHING_commands.md +0 -0
  187. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/dandi_ibl-brainwide.ipynb.ipynb +0 -0
  188. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/debug_gui.py +0 -0
  189. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/debug_launch_copy_paste.json +0 -0
  190. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/ephys/ephys_drift_diagnosis.ipynb +0 -0
  191. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/ephys/ephys_loader.ipynb +0 -0
  192. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/ethograph_alignment_setup_20260402_220508.ipynb +0 -0
  193. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/model/eval.ipynb +0 -0
  194. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/model/find_best_s3d.ipynb +0 -0
  195. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/model/model_config_Ivy.py +0 -0
  196. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/model/model_config_Poppy.py +0 -0
  197. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/model/model_run.py +0 -0
  198. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/model/s3d_features.py +0 -0
  199. {ethograph-0.2.3 → ethograph-0.2.4}/scripts/nwb_video_testing.ipynb +0 -0
  200. {ethograph-0.2.3 → ethograph-0.2.4}/setup.cfg +0 -0
  201. {ethograph-0.2.3 → ethograph-0.2.4}/todo.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ethograph
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: A multimodal timeseries GUI for neuroethology
5
5
  Author-email: Akseli Ilmanen <akseli.ilmanen@gmail.com>
6
6
  License-Expression: BSD-3-Clause
@@ -26,6 +26,7 @@ Requires-Dist: pandas
26
26
  Requires-Dist: xarray
27
27
  Requires-Dist: netcdf4
28
28
  Requires-Dist: scipy
29
+ Requires-Dist: scikit-learn
29
30
  Requires-Dist: matplotlib
30
31
  Requires-Dist: pynapple
31
32
  Requires-Dist: pyyaml
@@ -32,13 +32,9 @@ def _ensure_qt_plugins():
32
32
 
33
33
 
34
34
  def _fix_wayland_opengl():
35
- """Force XCB/GLX on Linux+Wayland to avoid OpenGL crashes with napari.
35
+ """Force XCB/GLX on GNOME+Wayland to avoid OpenGL crashes with napari.
36
36
 
37
- Wayland's EGL/native-OpenGL path segfaults with vispy on both Nvidia and
38
- AMD hardware across PyQt6 and PySide6. XCB (X11 via XWayland) is the
39
- recommended workaround per napari's own troubleshooting guide. We only
40
- apply this when Wayland is actually active and the user has not already
41
- set a preference via the environment.
37
+ See https://github.com/Akseli-Ilmanen/ethograph/issues/1
42
38
  """
43
39
  if sys.platform != "linux":
44
40
  return
@@ -47,6 +43,9 @@ def _fix_wayland_opengl():
47
43
  )
48
44
  if not wayland_active:
49
45
  return
46
+ desktop = os.environ.get("XDG_CURRENT_DESKTOP", "").upper()
47
+ if "GNOME" not in desktop:
48
+ return
50
49
  os.environ.setdefault("QT_QPA_PLATFORM", "xcb")
51
50
  os.environ.setdefault("PYOPENGL_PLATFORM", "glx")
52
51
 
@@ -0,0 +1,227 @@
1
+ """3x3 grid tab bar replacing the collapsible accordion headers.
2
+
3
+ GridSectionContainer is a drop-in replacement for CollapsibleWidgetContainer.
4
+ Instead of 9 stacked clickable header rows it shows a compact 3-column grid of
5
+ buttons at the top; clicking one expands that section's content below (one at a
6
+ time, same accordion semantics as before).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from qtpy.QtCore import QObject, QSize, Qt, Signal
12
+ from qtpy.QtWidgets import (
13
+ QGridLayout,
14
+ QPushButton,
15
+ QSizePolicy,
16
+ QStackedWidget,
17
+ QVBoxLayout,
18
+ QWidget,
19
+ )
20
+
21
+ _GRID_COLS = 3
22
+
23
+ # Short labels shown on the buttons; order must match add_widget call order in
24
+ # widgets_meta.py.
25
+ _SHORT_LABELS = ["Help", "I/O", "Data", "Neural", "Label", "CP", "Plots", "Trials", "Nav"]
26
+
27
+ _STATUS_CHARS = set("✅❌🎯⭕")
28
+
29
+ _BTN_STYLE = """
30
+ QPushButton {{
31
+ padding: 3px 2px;
32
+ font-size: 7pt;
33
+ border: 1px solid rgba(255,255,255,35);
34
+ border-radius: 3px;
35
+ background: rgba(255,255,255,12);
36
+ color: #cccccc;
37
+ min-height: 22px;
38
+ }}
39
+ QPushButton:hover {{
40
+ background: rgba(255,255,255,25);
41
+ color: #ffffff;
42
+ border: 1px solid rgba(255,255,255,60);
43
+ }}
44
+ QPushButton:checked {{
45
+ background: rgba(65,125,195,160);
46
+ border: 1px solid rgba(90,150,220,200);
47
+ color: #ffffff;
48
+ font-weight: bold;
49
+ }}
50
+ """
51
+
52
+
53
+ class _AdaptiveStack(QStackedWidget):
54
+ """QStackedWidget that reports only the *current* page's size hint.
55
+
56
+ The default QStackedWidget always sizes itself to the largest of all its
57
+ pages. That causes the content area to be over-tall when a short section
58
+ is active, which makes QVBoxLayout distribute the spare pixels as blank
59
+ gaps between the section's own widgets. Overriding sizeHint / minimumSizeHint
60
+ so they reflect the current page fixes this entirely.
61
+ """
62
+
63
+ def sizeHint(self) -> QSize:
64
+ w = self.currentWidget()
65
+ return w.sizeHint() if w is not None else super().sizeHint()
66
+
67
+ def minimumSizeHint(self) -> QSize:
68
+ w = self.currentWidget()
69
+ return w.minimumSizeHint() if w is not None else super().minimumSizeHint()
70
+
71
+
72
+ class SectionProxy(QObject):
73
+ """Duck-type stand-in for qt_niu.CollapsibleWidget.
74
+
75
+ Exposes the subset of the QCollapsible API used by MetaWidget so that
76
+ widgets_meta.py needs no structural changes.
77
+ """
78
+
79
+ toggled = Signal(bool)
80
+
81
+ def __init__(self, container: GridSectionContainer, index: int) -> None:
82
+ super().__init__()
83
+ self._c = container
84
+ self._i = index
85
+
86
+ # --- QCollapsible-compatible API ---
87
+
88
+ def expand(self, animate: bool = True) -> None:
89
+ self._c._expand(self._i)
90
+
91
+ def collapse(self, animate: bool = False) -> None:
92
+ self._c._collapse(self._i)
93
+
94
+ def isExpanded(self) -> bool:
95
+ return self._c._active == self._i
96
+
97
+ def content(self) -> QWidget | None:
98
+ widgets = self._c._content_widgets
99
+ return widgets[self._i] if self._i < len(widgets) else None
100
+
101
+ def setText(self, full_title: str) -> None:
102
+ self._c._update_button_label(self._i, full_title)
103
+
104
+ def setTitle(self, full_title: str) -> None:
105
+ self.setText(full_title)
106
+
107
+ def _expand_collapse(self, *_args, **_kwargs) -> None:
108
+ """No-op: _AdaptiveStack + updateGeometry handles sizing."""
109
+
110
+ def _emit_toggled(self, state: bool) -> None:
111
+ self.toggled.emit(state)
112
+
113
+
114
+ class GridSectionContainer(QWidget):
115
+ """Container with a 3-column grid of tab buttons + a single content area.
116
+
117
+ API mirrors CollapsibleWidgetContainer so MetaWidget.add_widget() and
118
+ MetaWidget.collapsible_widgets work unchanged.
119
+ """
120
+
121
+ def __init__(self, parent: QWidget | None = None) -> None:
122
+ super().__init__(parent)
123
+
124
+ outer = QVBoxLayout(self)
125
+ outer.setContentsMargins(0, 0, 0, 0)
126
+ outer.setSpacing(2)
127
+ outer.setAlignment(Qt.AlignTop)
128
+
129
+ # ── 3×3 grid of buttons ──────────────────────────────────────────────
130
+ self._grid_widget = QWidget()
131
+ self._grid_layout = QGridLayout(self._grid_widget)
132
+ self._grid_layout.setContentsMargins(2, 2, 2, 2)
133
+ self._grid_layout.setSpacing(2)
134
+ for col in range(_GRID_COLS):
135
+ self._grid_layout.setColumnStretch(col, 1)
136
+ outer.addWidget(self._grid_widget)
137
+
138
+ # ── Content area: shows the active section ───────────────────────────
139
+ # _AdaptiveStack reports only the current page's sizeHint so the outer
140
+ # layout never allocates more vertical space than the content needs.
141
+ self._stack = _AdaptiveStack()
142
+ self._stack.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
143
+ self._stack.hide()
144
+ outer.addWidget(self._stack, 0, Qt.AlignTop)
145
+
146
+ self._buttons: list[QPushButton] = []
147
+ self._content_widgets: list[QWidget] = []
148
+ self._active: int | None = None
149
+
150
+ # Public list of proxy objects (replaces CollapsibleWidgetContainer.collapsible_widgets)
151
+ self.collapsible_widgets: list[SectionProxy] = []
152
+
153
+ # ── Public API (mirrors CollapsibleWidgetContainer) ──────────────────────
154
+
155
+ def add_widget(
156
+ self,
157
+ widget: QWidget,
158
+ collapsible: bool = True,
159
+ widget_title: str = "",
160
+ ) -> None:
161
+ index = len(self._content_widgets)
162
+ label = _SHORT_LABELS[index] if index < len(_SHORT_LABELS) else widget_title
163
+
164
+ btn = QPushButton(label)
165
+ btn.setCheckable(True)
166
+ btn.setToolTip(widget_title)
167
+ btn.setStyleSheet(_BTN_STYLE)
168
+ btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
169
+ btn.clicked.connect(lambda _, i=index: self._on_btn_clicked(i))
170
+
171
+ row, col = divmod(index, _GRID_COLS)
172
+ self._grid_layout.addWidget(btn, row, col)
173
+ self._buttons.append(btn)
174
+
175
+ self._stack.addWidget(widget)
176
+ self._content_widgets.append(widget)
177
+
178
+ proxy = SectionProxy(self, index)
179
+ self.collapsible_widgets.append(proxy)
180
+
181
+ def notify_content_resized(self) -> None:
182
+ """Call this when the active section's content changes height.
183
+
184
+ EphysWidget and PlotSettingsWidget call meta_widget.refresh_widget_layout()
185
+ after toggling their sub-panels; that call routes here so the adaptive
186
+ stack recalculates its size hint.
187
+ """
188
+ self._stack.updateGeometry()
189
+ self.updateGeometry()
190
+
191
+ # ── Internal expand / collapse ───────────────────────────────────────────
192
+
193
+ def _on_btn_clicked(self, index: int) -> None:
194
+ if self._active == index:
195
+ self._collapse(index)
196
+ else:
197
+ self._expand(index)
198
+
199
+ def _expand(self, index: int) -> None:
200
+ if self._active is not None and self._active != index:
201
+ prev = self._active
202
+ self._buttons[prev].setChecked(False)
203
+ self.collapsible_widgets[prev]._emit_toggled(False)
204
+ self._active = index
205
+ self._buttons[index].setChecked(True)
206
+ self._stack.setCurrentIndex(index)
207
+ self._stack.show()
208
+ self._stack.updateGeometry()
209
+ self.updateGeometry()
210
+ self.collapsible_widgets[index]._emit_toggled(True)
211
+
212
+ def _collapse(self, index: int) -> None:
213
+ self._active = None
214
+ self._buttons[index].setChecked(False)
215
+ self._stack.hide()
216
+ self._stack.updateGeometry()
217
+ self.updateGeometry()
218
+ self.collapsible_widgets[index]._emit_toggled(False)
219
+
220
+ def _update_button_label(self, index: int, full_title: str) -> None:
221
+ """Keep the short base label but append a trailing status character."""
222
+ if not (0 <= index < len(self._buttons)):
223
+ return
224
+ base = _SHORT_LABELS[index] if index < len(_SHORT_LABELS) else ""
225
+ label = f"{base} {full_title[-1]}" if full_title and full_title[-1] in _STATUS_CHARS else base
226
+ self._buttons[index].setText(label)
227
+ self._buttons[index].setToolTip(full_title)
@@ -205,7 +205,7 @@ class ChangepointsWidget(QWidget):
205
205
  row1_layout = QHBoxLayout()
206
206
  row1_layout.setContentsMargins(0, 0, 0, 0)
207
207
 
208
- self.show_cp_checkbox = QCheckBox("Show changepoints")
208
+ self.show_cp_checkbox = QCheckBox("Show changepoints (CPs)")
209
209
  self.show_cp_checkbox.setToolTip("Display changepoints on plot")
210
210
  self.show_cp_checkbox.setChecked(True)
211
211
  self.show_cp_checkbox.stateChanged.connect(self._on_show_changepoints_changed)
@@ -780,7 +780,7 @@ class EphysWidget(QWidget):
780
780
  toggle_widget.setLayout(toggle_layout)
781
781
 
782
782
  toggle_defs = [
783
- ("traceview_toggle", "TraceView", self._toggle_traceview),
783
+ ("traceview_toggle", "Phy TraceView", self._toggle_traceview),
784
784
  ("firing_rate_toggle", "Firing rates", self._toggle_firing_rate),
785
785
  ]
786
786
  for attr, label, callback in toggle_defs:
@@ -848,6 +848,8 @@ class EphysWidget(QWidget):
848
848
 
849
849
  group = QGroupBox("Ephys trace controls")
850
850
  group_layout = QVBoxLayout()
851
+ group_layout.setSpacing(2)
852
+ group_layout.setContentsMargins(2, 2, 2, 2)
851
853
  group.setLayout(group_layout)
852
854
  layout.addWidget(group)
853
855
 
@@ -2244,8 +2246,8 @@ class EphysWidget(QWidget):
2244
2246
 
2245
2247
  group = QGroupBox("Firing rates")
2246
2248
  group_layout = QVBoxLayout()
2247
- group_layout.setSpacing(6)
2248
- group_layout.setContentsMargins(8, 12, 8, 8)
2249
+ group_layout.setSpacing(2)
2250
+ group_layout.setContentsMargins(2, 2, 2, 2)
2249
2251
  group.setLayout(group_layout)
2250
2252
  layout.addWidget(group)
2251
2253
 
@@ -2291,8 +2293,8 @@ class EphysWidget(QWidget):
2291
2293
 
2292
2294
  pca_group = QGroupBox("PCA")
2293
2295
  pca_layout = QVBoxLayout()
2294
- pca_layout.setSpacing(6)
2295
- pca_layout.setContentsMargins(8, 12, 8, 8)
2296
+ pca_layout.setSpacing(2)
2297
+ pca_layout.setContentsMargins(2, 2, 2, 2)
2296
2298
  pca_group.setLayout(pca_layout)
2297
2299
  layout.addWidget(pca_group)
2298
2300
 
@@ -3,7 +3,6 @@
3
3
  import logging
4
4
 
5
5
  from napari.viewer import Viewer
6
- from qt_niu.collapsible_widget import CollapsibleWidgetContainer
7
6
  from qtpy.QtCore import Qt, QTimer
8
7
  from qtpy.QtWidgets import (
9
8
  QMessageBox,
@@ -26,6 +25,7 @@ from .app_constants import (
26
25
  SIDEBAR_MIN_WIDTH_PX,
27
26
  )
28
27
  from .app_state import ObservableAppState
28
+ from .grid_section_container import GridSectionContainer
29
29
  from .make_pretty import LayoutManager
30
30
  from .plots_container import UnifiedPanelContainer
31
31
  from .shortcuts import bind_global_shortcuts
@@ -42,7 +42,7 @@ from .widgets_plot_settings import PlotSettingsWidget
42
42
  logger = logging.getLogger(__name__)
43
43
 
44
44
 
45
- class MetaWidget(CollapsibleWidgetContainer):
45
+ class MetaWidget(GridSectionContainer):
46
46
  def __init__(self, napari_viewer: Viewer):
47
47
  """Initialize the meta-widget."""
48
48
  super().__init__()
@@ -226,11 +226,11 @@ class MetaWidget(CollapsibleWidgetContainer):
226
226
  widget_title="Data",
227
227
  )
228
228
 
229
- # Index 3: Phy extension
229
+ # Index 3: Phy TraceView
230
230
  self.add_widget(
231
231
  self.ephys_widget,
232
232
  collapsible=True,
233
- widget_title="Phy extension",
233
+ widget_title="Phy TraceView",
234
234
  )
235
235
 
236
236
  # Index 4: Labelling
@@ -323,6 +323,7 @@ class MetaWidget(CollapsibleWidgetContainer):
323
323
 
324
324
  def refresh_widget_layout(self, widget: QWidget):
325
325
  self._schedule_layout_refresh()
326
+ self.notify_content_resized()
326
327
 
327
328
  def _cycle_channel(self, direction: int):
328
329
  if not self.app_state.ready:
@@ -100,6 +100,8 @@ class NavigationWidget(QWidget):
100
100
  # ── Navigate (unified prev/next + mode) ──────────────────────
101
101
  navigate_group = QGroupBox("Navigate")
102
102
  navigate_layout = QVBoxLayout()
103
+ navigate_layout.setSpacing(2)
104
+ navigate_layout.setContentsMargins(2, 2, 2, 2)
103
105
  navigate_group.setLayout(navigate_layout)
104
106
 
105
107
  filter_hint = QLabel("Only navigates trials visible in the Trials tab")
@@ -234,6 +236,8 @@ class NavigationWidget(QWidget):
234
236
  # ── Playback ─────────────────────────────────────────────────
235
237
  playback_group = QGroupBox("Playback")
236
238
  playback_layout = QGridLayout()
239
+ playback_layout.setSpacing(2)
240
+ playback_layout.setContentsMargins(2, 2, 2, 2)
237
241
  playback_group.setLayout(playback_layout)
238
242
 
239
243
  self.fps_label = QLabel("Playback FPS:")
@@ -145,6 +145,8 @@ class PlotSettingsWidget(QWidget):
145
145
 
146
146
  group_box = QGroupBox("Axes Controls")
147
147
  group_layout = QGridLayout()
148
+ group_layout.setSpacing(2)
149
+ group_layout.setContentsMargins(2, 2, 2, 2)
148
150
  group_box.setLayout(group_layout)
149
151
  layout.addWidget(group_box)
150
152
 
@@ -297,6 +299,8 @@ class PlotSettingsWidget(QWidget):
297
299
 
298
300
  group_box = QGroupBox("Space Plot Controls")
299
301
  group_layout = QGridLayout()
302
+ group_layout.setSpacing(2)
303
+ group_layout.setContentsMargins(2, 2, 2, 2)
300
304
  group_box.setLayout(group_layout)
301
305
  layout.addWidget(group_box)
302
306
 
@@ -410,8 +414,8 @@ class PlotSettingsWidget(QWidget):
410
414
 
411
415
  group_box = QGroupBox("Spectrogram Controls")
412
416
  group_layout = QGridLayout()
413
- group_layout.setVerticalSpacing(6)
414
- group_layout.setContentsMargins(6, 10, 6, 6)
417
+ group_layout.setSpacing(2)
418
+ group_layout.setContentsMargins(2, 2, 2, 2)
415
419
  group_box.setLayout(group_layout)
416
420
  layout.addWidget(group_box)
417
421
 
@@ -740,6 +744,8 @@ class PlotSettingsWidget(QWidget):
740
744
 
741
745
  hm_group = QGroupBox("Heatmap Display")
742
746
  hm_layout = QGridLayout()
747
+ hm_layout.setSpacing(2)
748
+ hm_layout.setContentsMargins(2, 2, 2, 2)
743
749
  hm_group.setLayout(hm_layout)
744
750
  layout.addWidget(hm_group)
745
751
 
@@ -201,17 +201,24 @@ _LABEL_COLORS = [
201
201
  _GAP_COLOR = [128 / 255.0, 128 / 255.0, 128 / 255.0]
202
202
 
203
203
 
204
- def load_label_mapping(mapping_file: str | Path = "mapping.txt") -> Dict[int, Dict]:
204
+ def load_label_mapping(
205
+ mapping_file: str | Path = "mapping.txt",
206
+ order: list[int] | None = None,
207
+ ) -> Dict[int, Dict]:
205
208
  """Load a label mapping with colors for visualization.
206
209
 
207
210
  Parameters
208
211
  ----------
209
212
  mapping_file : str or Path
210
- Path to the mapping file. Each line is
213
+ Path to the mapping file. Each line is
211
214
  ``<id> <name> [<branch>] [<event_type>]`` where *branch* is an
212
215
  optional integer (default 0) grouping labels into branches for
213
216
  independent labeling, and *event_type* is ``"state"`` (default) or
214
- ``"point"``. Missing trailing columns inherit their defaults.
217
+ ``"point"``.
218
+ order : list[int] or None
219
+ Label IDs in the desired display sequence. If provided, overrides
220
+ the default order (which follows label ID). Any ID not listed
221
+ retains its default position.
215
222
 
216
223
  Returns
217
224
  -------
@@ -229,10 +236,12 @@ def load_label_mapping(mapping_file: str | Path = "mapping.txt") -> Dict[int, Di
229
236
  >>> mappings = load_label_mapping("mapping.txt")
230
237
  >>> mappings[1]["name"]
231
238
  'walk'
232
- >>> mappings[1]["color"].shape
233
- (3,)
234
239
 
235
- Use the RGB colors to draw labelled rectangles on a plot::
240
+ Reorder labels for display without changing the file::
241
+
242
+ mappings = load_label_mapping("mapping.txt", order=[0, 3, 1, 2])
243
+
244
+ Draw labelled intervals on a plot::
236
245
 
237
246
  import matplotlib.pyplot as plt
238
247
  import matplotlib.patches as mpatches
@@ -240,12 +249,9 @@ def load_label_mapping(mapping_file: str | Path = "mapping.txt") -> Dict[int, Di
240
249
  mappings = load_label_mapping("mapping.txt")
241
250
  fig, ax = plt.subplots()
242
251
  ax.plot(time, signal)
243
-
244
252
  for _, row in intervals_df.iterrows():
245
- color = mappings[int(row["labels"])]["color"] # (3,) RGB in [0, 1]
253
+ color = mappings[int(row["labels"])]["color"]
246
254
  ax.axvspan(row["onset_s"], row["offset_s"], alpha=0.5, color=color)
247
-
248
- # Build a legend from the mapping
249
255
  handles = [mpatches.Patch(color=m["color"], label=m["name"]) for m in mappings.values()]
250
256
  ax.legend(handles=handles)
251
257
  plt.show()
@@ -263,11 +269,9 @@ def load_label_mapping(mapping_file: str | Path = "mapping.txt") -> Dict[int, Di
263
269
  if parts[0].startswith("("):
264
270
  nums = parts[0].strip("()").split(",")
265
271
  label_id = (int(nums[0]), int(nums[1]))
266
- order = int(parts[-1])
267
272
  label_mappings[label_id] = {
268
273
  "name": parts[1],
269
274
  "color": _GAP_COLOR,
270
- "order": order,
271
275
  "branch": 0,
272
276
  "event_type": EVENT_TYPE_STATE,
273
277
  }
@@ -280,11 +284,13 @@ def load_label_mapping(mapping_file: str | Path = "mapping.txt") -> Dict[int, Di
280
284
  label_mappings[label_id] = {
281
285
  "name": parts[1],
282
286
  "color": np.array(_LABEL_COLORS[label_id % len(_LABEL_COLORS)]) / 255.0,
283
- "order": label_id,
284
287
  "branch": branch,
285
288
  "event_type": event_type,
286
289
  }
287
290
 
291
+ if order is not None:
292
+ label_mappings = {k: label_mappings[k] for k in order if k in label_mappings}
293
+
288
294
  return label_mappings
289
295
 
290
296
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ethograph
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: A multimodal timeseries GUI for neuroethology
5
5
  Author-email: Akseli Ilmanen <akseli.ilmanen@gmail.com>
6
6
  License-Expression: BSD-3-Clause
@@ -26,6 +26,7 @@ Requires-Dist: pandas
26
26
  Requires-Dist: xarray
27
27
  Requires-Dist: netcdf4
28
28
  Requires-Dist: scipy
29
+ Requires-Dist: scikit-learn
29
30
  Requires-Dist: matplotlib
30
31
  Requires-Dist: pynapple
31
32
  Requires-Dist: pyyaml
@@ -68,6 +68,7 @@ ethograph/gui/dialog_pose_video_matcher.py
68
68
  ethograph/gui/dialog_screen_recorder.py
69
69
  ethograph/gui/dialog_select_template.py
70
70
  ethograph/gui/dialog_video_downsample.py
71
+ ethograph/gui/grid_section_container.py
71
72
  ethograph/gui/label_drawing_mixin.py
72
73
  ethograph/gui/make_pretty.py
73
74
  ethograph/gui/napari.yaml
@@ -3,6 +3,7 @@ pandas
3
3
  xarray
4
4
  netcdf4
5
5
  scipy
6
+ scikit-learn
6
7
  matplotlib
7
8
  pynapple
8
9
  pyyaml
@@ -28,6 +28,7 @@ dependencies = [
28
28
  "xarray",
29
29
  "netcdf4",
30
30
  "scipy",
31
+ "scikit-learn",
31
32
  "matplotlib",
32
33
  "pynapple",
33
34
  "pyyaml",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes