xarray-behave 0.35.2__tar.gz → 0.35.3__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.
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/.github/workflows/publish.yaml +3 -3
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/PKG-INFO +1 -1
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/__init__.py +2 -1
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/annot.py +13 -4
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/app.py +135 -58
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/das.py +6 -2
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/formbuilder.py +1 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/io/audio.py +1 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/io/balltracks.py +1 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/io/movieparams.py +1 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/io/poses.py +1 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/loaders.py +1 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/metrics.py +1 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/xarray_behave.py +1 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/.gitignore +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/LICENSE +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/README.md +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/build_env.yml +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/conda/xarray-behave/bld.bat +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/conda/xarray-behave/build.sh +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/conda/xarray-behave/conda_build_config.yaml +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/conda/xarray-behave/meta.yaml +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/condarc.yml +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/doc/demo.ipynb +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/doc/demo_behavioral_features.ipynb +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/doc/demo_behavioral_features_large_group.ipynb +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/doc/ncb.mplstyle +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/pyproject.toml +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/setup.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/event_utils.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/__init__.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/audio_player.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/forms/das_make.yaml +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/forms/das_predict.yaml +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/forms/das_train.yaml +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/forms/envelope_computation.yaml +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/forms/export_for_das.yaml +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/forms/from_dir.yaml +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/forms/from_file.yaml +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/forms/from_zarr.yaml +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/forms/spec_freq.yaml +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/icon.png +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/table.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/utils.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/view_dialog.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/views.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/widgets.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/io/__init__.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/io/annotations.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/io/annotations_manual.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/io/timestamps.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/io/tracks.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/tests/test_annot.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/tests/test_assemble.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/tests/test_assemble_metrics.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/tests/test_imports.py +0 -0
- {xarray-behave-0.35.2 → xarray-behave-0.35.3}/tests/test_io.py +0 -0
|
@@ -19,9 +19,9 @@ jobs:
|
|
|
19
19
|
strategy:
|
|
20
20
|
fail-fast: False
|
|
21
21
|
matrix:
|
|
22
|
-
python-version: [3.9 , '3.10'
|
|
23
|
-
|
|
24
|
-
os: [macOS-13, macOS-14]
|
|
22
|
+
python-version: [3.9 , '3.10', '3.11']
|
|
23
|
+
os: [ubuntu-latest, windows-latest, macOS-13, macOS-14]
|
|
24
|
+
# os: [macOS-13, macOS-14]
|
|
25
25
|
# python-version: ['3.11']
|
|
26
26
|
# os: [ubuntu-latest]
|
|
27
27
|
defaults: # https://github.com/marketplace/actions/setup-miniconda#use-a-default-shell
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
TODO: From/to traces
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
import numpy as np
|
|
6
7
|
import xarray as xr
|
|
7
8
|
import pandas as pd
|
|
@@ -166,7 +167,9 @@ class Events(UserDict):
|
|
|
166
167
|
columns.append("channel")
|
|
167
168
|
return pd.DataFrame(columns=columns)
|
|
168
169
|
|
|
169
|
-
def _append_row(
|
|
170
|
+
def _append_row(
|
|
171
|
+
self, df: pd.DataFrame, name: str, start_seconds: float, stop_seconds: Optional[float] = None, channel: int = -1
|
|
172
|
+
):
|
|
170
173
|
if stop_seconds is None:
|
|
171
174
|
stop_seconds = start_seconds
|
|
172
175
|
|
|
@@ -195,12 +198,16 @@ class Events(UserDict):
|
|
|
195
198
|
"""
|
|
196
199
|
df = self._init_df()
|
|
197
200
|
for name in self.names:
|
|
198
|
-
for start_second, stop_second, channel in zip(
|
|
201
|
+
for start_second, stop_second, channel in zip(
|
|
202
|
+
self.start_seconds(name), self.stop_seconds(name), self.channels(name)
|
|
203
|
+
):
|
|
199
204
|
df = self._append_row(df, name, start_second, stop_second, channel)
|
|
200
205
|
if preserve_empty: # ensure we keep events without annotations
|
|
201
206
|
for name, cat in zip(self.names, self.categories.values()):
|
|
202
207
|
if name not in df.name.values:
|
|
203
|
-
stop_seconds =
|
|
208
|
+
stop_seconds = (
|
|
209
|
+
np.nan if cat == "event" else 0
|
|
210
|
+
) # (np.nan, np.nan) -> empty events, (np.nan, some number) -> empty segments
|
|
204
211
|
df = self._append_row(df, name, start_seconds=np.nan, stop_seconds=stop_seconds)
|
|
205
212
|
# make sure start and stop seconds are numeric
|
|
206
213
|
df["start_seconds"] = pd.to_numeric(df["start_seconds"], errors="coerce")
|
|
@@ -310,7 +317,9 @@ class Events(UserDict):
|
|
|
310
317
|
name = None
|
|
311
318
|
return name
|
|
312
319
|
|
|
313
|
-
def _get_index_of_nearest(
|
|
320
|
+
def _get_index_of_nearest(
|
|
321
|
+
self, time: float, name: str, tol: float = 0, min_time: Optional[float] = None, max_time: Optional[float] = None
|
|
322
|
+
):
|
|
314
323
|
within_range_indices = self.select_range(name, min_time, max_time, strict=False)
|
|
315
324
|
if len(within_range_indices):
|
|
316
325
|
nearest_start = self._find_nearest(self.start_seconds(name)[within_range_indices], time)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`python -m xarray_behave.ui datename root`
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
import os
|
|
6
7
|
import sys
|
|
7
8
|
import logging
|
|
@@ -44,7 +45,11 @@ except ImportError:
|
|
|
44
45
|
try:
|
|
45
46
|
from . import das
|
|
46
47
|
except Exception:
|
|
47
|
-
logger.warning(
|
|
48
|
+
logger.warning(
|
|
49
|
+
"Failed to import the das module.\nIgnore if you do not want to use das.\n"
|
|
50
|
+
"Otherwise follow these instructions to install:\n"
|
|
51
|
+
"https://janclemenslab.org/das/install.html"
|
|
52
|
+
)
|
|
48
53
|
|
|
49
54
|
sys.setrecursionlimit(10**6) # increase recursion limit to avoid errors when keeping key pressed for a long time
|
|
50
55
|
package_dir: str = xarray_behave.__path__[0]
|
|
@@ -153,7 +158,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
153
158
|
|
|
154
159
|
def save_swaps(self, qt_keycode=None):
|
|
155
160
|
savefilename = self._get_filename_from_ds(suffix="_idswaps.txt")
|
|
156
|
-
savefilename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
161
|
+
savefilename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
162
|
+
self, "Save swaps to", str(savefilename), filter="txt files (*.txt);;all files (*)"
|
|
163
|
+
)
|
|
157
164
|
if len(savefilename):
|
|
158
165
|
logger.info(f" Saving list of swap indices to {savefilename}.")
|
|
159
166
|
os.makedirs(os.path.dirname(savefilename), exist_ok=True)
|
|
@@ -162,7 +169,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
162
169
|
|
|
163
170
|
def save_definitions(self, qt_keycode=None):
|
|
164
171
|
savefilename = self._get_filename_from_ds(suffix="_definitions.csv")
|
|
165
|
-
savefilename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
172
|
+
savefilename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
173
|
+
self, caption="Save definitions to", dir=str(savefilename), filter="CSV files (*_definitions.csv);;all files (*)"
|
|
174
|
+
)
|
|
166
175
|
if len(savefilename):
|
|
167
176
|
# get defs from annot and save them to csv
|
|
168
177
|
logger.info(f" Saving definitions to {savefilename}.")
|
|
@@ -402,7 +411,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
402
411
|
self.export_to_h5(savefilename_trunk + ".h5", start_seconds, end_seconds) # , form_data["scale_audio"])
|
|
403
412
|
|
|
404
413
|
logger.info(f" annotations to CSV: {savefilename_trunk + '.csv'}.")
|
|
405
|
-
self.export_to_csv(
|
|
414
|
+
self.export_to_csv(
|
|
415
|
+
savefilename_trunk + "_annotations.csv", start_seconds, end_seconds, which_events, match_to_samples=True
|
|
416
|
+
)
|
|
406
417
|
logger.info("Done.")
|
|
407
418
|
|
|
408
419
|
def das_make(self, qt_keycode=None):
|
|
@@ -503,7 +514,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
503
514
|
return form_data
|
|
504
515
|
|
|
505
516
|
def save(arg):
|
|
506
|
-
savefilename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
517
|
+
savefilename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
518
|
+
self, "Save configuration to", "", filter="yaml files (*.yaml);;all files (*)"
|
|
519
|
+
)
|
|
507
520
|
if len(savefilename):
|
|
508
521
|
data = dialog.form.get_form_data()
|
|
509
522
|
logger.info(f" Saving form fields to {savefilename}.")
|
|
@@ -514,7 +527,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
514
527
|
def make_cli(arg):
|
|
515
528
|
script_ext = "cmd" if os.name == "nt" else "sh"
|
|
516
529
|
savefilename = "train." + script_ext
|
|
517
|
-
savefilename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
530
|
+
savefilename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
531
|
+
self, "Script name", savefilename, filter=f"script (*.{script_ext};;all files (*)"
|
|
532
|
+
)
|
|
518
533
|
if len(savefilename):
|
|
519
534
|
form_data = dialog.form.get_form_data()
|
|
520
535
|
form_data = _filter_form_data(form_data, is_cli=True)
|
|
@@ -696,13 +711,19 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
696
711
|
|
|
697
712
|
params = das.utils.load_params(model_path)
|
|
698
713
|
if audio.shape[0] < params["nb_hist"]:
|
|
699
|
-
logger.warning(
|
|
714
|
+
logger.warning(
|
|
715
|
+
f" Aborting. Audio has fewer samples ({audio.shape[0]}) shorter"
|
|
716
|
+
f" than network chunk size ({params['nb_hist']})."
|
|
717
|
+
" Fix by select longer audio."
|
|
718
|
+
)
|
|
700
719
|
return
|
|
701
720
|
|
|
702
721
|
# select batch size so that at least 10 batches are run
|
|
703
722
|
# minimizes loss of annotations from batch size "quantization" errors
|
|
704
723
|
batch_size = 32
|
|
705
|
-
nb_batches = lambda batch_size: int(
|
|
724
|
+
nb_batches = lambda batch_size: int(
|
|
725
|
+
np.floor((audio.shape[0] - ((batch_size - 1) + params["nb_hist"])) / (params["stride"] * (batch_size)))
|
|
726
|
+
)
|
|
706
727
|
while nb_batches(batch_size) < 10 and batch_size > 1:
|
|
707
728
|
batch_size -= 1
|
|
708
729
|
|
|
@@ -765,7 +786,11 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
765
786
|
# segments['sequence'] = [s for s in segments['sequence'] if s is not None]
|
|
766
787
|
detected_segment_names = np.unique(segments["sequence"])
|
|
767
788
|
# if these are indices, get corresponding names
|
|
768
|
-
if
|
|
789
|
+
if (
|
|
790
|
+
len(detected_segment_names)
|
|
791
|
+
and type(detected_segment_names[0]) is not str
|
|
792
|
+
and type(detected_segment_names[0]) is not np.str_
|
|
793
|
+
):
|
|
769
794
|
detected_segment_names = [segments["names"][ii] for ii in detected_segment_names]
|
|
770
795
|
|
|
771
796
|
if len(detected_segment_names) > 0: # and detected_segment_names[0] is not None:
|
|
@@ -781,7 +806,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
781
806
|
|
|
782
807
|
onsets_seconds = self.ds.sampletime[onsets_samples]
|
|
783
808
|
offsets_seconds = self.ds.sampletime[offsets_samples]
|
|
784
|
-
for name_or_index, onset_seconds, offset_seconds in zip(
|
|
809
|
+
for name_or_index, onset_seconds, offset_seconds in zip(
|
|
810
|
+
segments["sequence"], onsets_seconds, offsets_seconds
|
|
811
|
+
):
|
|
785
812
|
if type(name_or_index) is not str and type(detected_segment_names[0]) is not np.str_:
|
|
786
813
|
segment_name = segments["names"][name_or_index]
|
|
787
814
|
else:
|
|
@@ -830,8 +857,16 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
830
857
|
except KeyError:
|
|
831
858
|
pass
|
|
832
859
|
except KeyError:
|
|
833
|
-
logger.info(
|
|
834
|
-
|
|
860
|
+
logger.info(
|
|
861
|
+
f"{filename} no sample rate info in NPZ file."
|
|
862
|
+
f"Need to save 'samplerate' variable with the audio data. Defaulting to {samplerate}"
|
|
863
|
+
)
|
|
864
|
+
elif (
|
|
865
|
+
filename.endswith(".h5")
|
|
866
|
+
or filename.endswith(".hdfs")
|
|
867
|
+
or filename.endswith(".hdf5")
|
|
868
|
+
or filename.endswith(".mat")
|
|
869
|
+
):
|
|
835
870
|
# infer data set (for hdf5) and populate form
|
|
836
871
|
try:
|
|
837
872
|
# list all data sets in file and add to list
|
|
@@ -935,7 +970,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
935
970
|
if not dirname:
|
|
936
971
|
dirname = QtWidgets.QFileDialog.getExistingDirectory(parent=None, caption="Select data directory")
|
|
937
972
|
if dirname:
|
|
938
|
-
dialog = YamlDialog(
|
|
973
|
+
dialog = YamlDialog(
|
|
974
|
+
yaml_file=package_dir + "/gui/forms/from_dir.yaml", title=f"Dataset from data directory {dirname}"
|
|
975
|
+
)
|
|
939
976
|
|
|
940
977
|
# initialize form data with cli args
|
|
941
978
|
dialog.form["pixel_size_mm"] = pixel_size_mm # and un-disable
|
|
@@ -1010,7 +1047,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
1010
1047
|
|
|
1011
1048
|
# add event categories if they are missing in the dataset
|
|
1012
1049
|
if "song_events" in ds and "event_categories" not in ds:
|
|
1013
|
-
event_categories = [
|
|
1050
|
+
event_categories = [
|
|
1051
|
+
"segment" if "sine" in evt or "syllable" in evt else "event" for evt in ds.event_types.values
|
|
1052
|
+
]
|
|
1014
1053
|
ds = ds.assign_coords({"event_categories": (("event_types"), event_categories)})
|
|
1015
1054
|
|
|
1016
1055
|
# add missing song types
|
|
@@ -1068,7 +1107,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
1068
1107
|
if not filename:
|
|
1069
1108
|
filename, _ = QtWidgets.QFileDialog.getOpenFileName(parent=None, caption="Select dataset")
|
|
1070
1109
|
if filename:
|
|
1071
|
-
dialog = YamlDialog(
|
|
1110
|
+
dialog = YamlDialog(
|
|
1111
|
+
yaml_file=package_dir + "/gui/forms/from_zarr.yaml", title=f"Load dataset from zarr file {filename}"
|
|
1112
|
+
)
|
|
1072
1113
|
|
|
1073
1114
|
# initialize form data with cli args
|
|
1074
1115
|
if spec_freq_min is not None:
|
|
@@ -1106,7 +1147,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
1106
1147
|
|
|
1107
1148
|
# add event categories if they are missing in the dataset
|
|
1108
1149
|
if "song_events" in ds and "event_categories" not in ds:
|
|
1109
|
-
event_categories = [
|
|
1150
|
+
event_categories = [
|
|
1151
|
+
"segment" if "sine" in evt or "syllable" in evt else "event" for evt in ds.event_types.values
|
|
1152
|
+
]
|
|
1110
1153
|
ds = ds.assign_coords({"event_categories": (("event_types"), event_categories)})
|
|
1111
1154
|
logger.info(ds)
|
|
1112
1155
|
vr = None
|
|
@@ -1151,11 +1194,15 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
|
1151
1194
|
|
|
1152
1195
|
def save_dataset(self, qt_keycode=None):
|
|
1153
1196
|
try:
|
|
1154
|
-
savefilename = Path(
|
|
1197
|
+
savefilename = Path(
|
|
1198
|
+
self.ds.attrs["root"], self.ds.attrs["dat_path"], self.ds.attrs["datename"], f"{self.ds.attrs['datename']}.zarr"
|
|
1199
|
+
)
|
|
1155
1200
|
except KeyError:
|
|
1156
1201
|
savefilename = ""
|
|
1157
1202
|
|
|
1158
|
-
savefilename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
1203
|
+
savefilename, _ = QtWidgets.QFileDialog.getSaveFileName(
|
|
1204
|
+
self, "Save dataset to", str(savefilename), filter="zarr files (*.zarr);;all files (*)"
|
|
1205
|
+
)
|
|
1159
1206
|
|
|
1160
1207
|
if len(savefilename):
|
|
1161
1208
|
file_exists = os.path.exists(savefilename)
|
|
@@ -1407,10 +1454,16 @@ class PSV(MainWindow):
|
|
|
1407
1454
|
self._add_keyed_menuitem(view_video, "Change other fly", self.change_other_fly, "Z")
|
|
1408
1455
|
self._add_keyed_menuitem(view_video, "Swap flies", self.swap_flies, "X")
|
|
1409
1456
|
view_video.addSeparator()
|
|
1410
|
-
self._add_keyed_menuitem(
|
|
1457
|
+
self._add_keyed_menuitem(
|
|
1458
|
+
view_video, "Move poses", partial(self.toggle, "move_poses"), "B", checkable=True, checked=self.move_poses
|
|
1459
|
+
)
|
|
1411
1460
|
view_video.addSeparator()
|
|
1412
|
-
self._add_keyed_menuitem(
|
|
1413
|
-
|
|
1461
|
+
self._add_keyed_menuitem(
|
|
1462
|
+
view_video, "Show fly position", partial(self.toggle, "show_dot"), "O", checkable=True, checked=self.show_dot
|
|
1463
|
+
)
|
|
1464
|
+
self._add_keyed_menuitem(
|
|
1465
|
+
view_video, "Show poses", partial(self.toggle, "show_poses"), "P", checkable=True, checked=self.show_poses
|
|
1466
|
+
)
|
|
1414
1467
|
|
|
1415
1468
|
view_audio = self.bar.addMenu("Audio")
|
|
1416
1469
|
self._add_keyed_menuitem(view_audio, "Play waveform through speakers", self.play_audio, "E")
|
|
@@ -1434,7 +1487,9 @@ class PSV(MainWindow):
|
|
|
1434
1487
|
self._add_keyed_menuitem(view_audio, "Select previous channel", self.set_next_channel, "Up")
|
|
1435
1488
|
self._add_keyed_menuitem(view_audio, "Select next channel", self.set_prev_channel, "Down")
|
|
1436
1489
|
view_audio.addSeparator()
|
|
1437
|
-
self._add_keyed_menuitem(
|
|
1490
|
+
self._add_keyed_menuitem(
|
|
1491
|
+
view_audio, "Show spectrogram", partial(self.toggle, "show_spec"), None, checkable=True, checked=self.show_spec
|
|
1492
|
+
)
|
|
1438
1493
|
self._add_keyed_menuitem(view_audio, "Increase frequency resolution", self.inc_freq_res, "R")
|
|
1439
1494
|
self._add_keyed_menuitem(view_audio, "Increase temporal resolution", self.dec_freq_res, "T")
|
|
1440
1495
|
view_audio.addSeparator()
|
|
@@ -1488,20 +1543,34 @@ class PSV(MainWindow):
|
|
|
1488
1543
|
self._add_keyed_menuitem(view_annotations, "Generate proposal by envelope thresholding", self.threshold, "I")
|
|
1489
1544
|
self._add_keyed_menuitem(view_annotations, "Adjust thresholding mode", self.set_envelope_computation)
|
|
1490
1545
|
view_annotations.addSeparator()
|
|
1491
|
-
self._add_keyed_menuitem(
|
|
1492
|
-
|
|
1546
|
+
self._add_keyed_menuitem(
|
|
1547
|
+
view_annotations, "Approve proposals for active song type in view", self.approve_active_proposals, "G"
|
|
1548
|
+
)
|
|
1549
|
+
self._add_keyed_menuitem(
|
|
1550
|
+
view_annotations, "Approve proposals for all song types in view", self.approve_all_proposals, "H"
|
|
1551
|
+
)
|
|
1493
1552
|
|
|
1494
1553
|
view_view = self.bar.addMenu("View")
|
|
1495
1554
|
self._add_keyed_menuitem(view_view, "Video, waveform, and spectrogram display parameters", self.set_spec_freq)
|
|
1496
1555
|
view_view.addSeparator()
|
|
1497
1556
|
# TODO? only show these if tracks and/or video
|
|
1498
|
-
self._add_keyed_menuitem(
|
|
1499
|
-
|
|
1500
|
-
|
|
1557
|
+
self._add_keyed_menuitem(
|
|
1558
|
+
view_view, "Show spectrogram", partial(self.toggle, "show_spec"), None, checkable=True, checked=self.show_spec
|
|
1559
|
+
)
|
|
1560
|
+
self._add_keyed_menuitem(
|
|
1561
|
+
view_view, "Show waveform", partial(self.toggle, "show_trace"), None, checkable=True, checked=self.show_trace
|
|
1562
|
+
)
|
|
1563
|
+
self._add_keyed_menuitem(
|
|
1564
|
+
view_view, "Show ethogram", partial(self.toggle, "show_annot"), None, checkable=True, checked=self.show_annot
|
|
1565
|
+
)
|
|
1501
1566
|
if "pose_positions_allo" in self.ds:
|
|
1502
|
-
self._add_keyed_menuitem(
|
|
1567
|
+
self._add_keyed_menuitem(
|
|
1568
|
+
view_view, "Show tracks", partial(self.toggle, "show_tracks"), None, checkable=True, checked=self.show_tracks
|
|
1569
|
+
)
|
|
1503
1570
|
if self.vr is not None:
|
|
1504
|
-
self._add_keyed_menuitem(
|
|
1571
|
+
self._add_keyed_menuitem(
|
|
1572
|
+
view_view, "Show movie", partial(self.toggle, "show_movie"), None, checkable=True, checked=self.show_movie
|
|
1573
|
+
)
|
|
1505
1574
|
|
|
1506
1575
|
self.hl = QtWidgets.QHBoxLayout()
|
|
1507
1576
|
|
|
@@ -1872,7 +1941,9 @@ class PSV(MainWindow):
|
|
|
1872
1941
|
|
|
1873
1942
|
def delete_current_events(self, qt_keycode):
|
|
1874
1943
|
if self.current_event_index is not None:
|
|
1875
|
-
deleted_events = self.event_times.delete_range(
|
|
1944
|
+
deleted_events = self.event_times.delete_range(
|
|
1945
|
+
self.current_event_name, self.time0 / self.fs_song, self.time1 / self.fs_song
|
|
1946
|
+
)
|
|
1876
1947
|
nb_deleted_events = len(deleted_events)
|
|
1877
1948
|
if nb_deleted_events:
|
|
1878
1949
|
logger.info(f" Deleted {nb_deleted_events} annotation(s) of type {self.current_event_name}.")
|
|
@@ -1894,7 +1965,9 @@ class PSV(MainWindow):
|
|
|
1894
1965
|
def threshold(self, qt_keycode):
|
|
1895
1966
|
if self.STOP and self.current_event_name is not None:
|
|
1896
1967
|
if self.event_times.categories[self.current_event_name] == "event":
|
|
1897
|
-
indexes = peakutils.indexes(
|
|
1968
|
+
indexes = peakutils.indexes(
|
|
1969
|
+
self.envelope, thres=self.slice_view.threshold, min_dist=self.thres_min_dist * self.fs_song, thres_abs=True
|
|
1970
|
+
)
|
|
1898
1971
|
# add events to current song type
|
|
1899
1972
|
for t in self.x[indexes]:
|
|
1900
1973
|
self.event_times.add_time(self.current_event_name, t)
|
|
@@ -2044,7 +2117,9 @@ class PSV(MainWindow):
|
|
|
2044
2117
|
dialog.exec_()
|
|
2045
2118
|
|
|
2046
2119
|
def set_envelope_computation(self, qt_keycode):
|
|
2047
|
-
dialog = YamlDialog(
|
|
2120
|
+
dialog = YamlDialog(
|
|
2121
|
+
yaml_file=package_dir + "/gui/forms/envelope_computation.yaml", title="Set options for envelope computation"
|
|
2122
|
+
)
|
|
2048
2123
|
|
|
2049
2124
|
dialog.form["thres_min_dist"] = self.thres_min_dist
|
|
2050
2125
|
dialog.form["thres_env_std"] = self.thres_env_std
|
|
@@ -2122,7 +2197,9 @@ class PSV(MainWindow):
|
|
|
2122
2197
|
i1 = int(self.time1 / self.fs_ratio)
|
|
2123
2198
|
|
|
2124
2199
|
self.x_tracks = self.ds.time.data[i0:i1]
|
|
2125
|
-
self.y_tracks = self.ds.pose_positions_allo.data[
|
|
2200
|
+
self.y_tracks = self.ds.pose_positions_allo.data[
|
|
2201
|
+
i0:i1, self.focal_fly, self.track_sel_names, self.track_sel_coords
|
|
2202
|
+
]
|
|
2126
2203
|
self.tracks_view.update_trace()
|
|
2127
2204
|
self.tracks_view.show()
|
|
2128
2205
|
else:
|
|
@@ -2169,9 +2246,13 @@ class PSV(MainWindow):
|
|
|
2169
2246
|
if self.event_times.categories[event_name] == "segment":
|
|
2170
2247
|
for onset, offset in zip(events_in_view[:, 0], events_in_view[:, 1]):
|
|
2171
2248
|
if self.show_trace:
|
|
2172
|
-
self.slice_view.add_segment(
|
|
2249
|
+
self.slice_view.add_segment(
|
|
2250
|
+
onset, offset, event_index, brush=event_brush, pen=event_pen, movable=movable, text=segment_text
|
|
2251
|
+
)
|
|
2173
2252
|
if self.show_tracks:
|
|
2174
|
-
self.tracks_view.add_segment(
|
|
2253
|
+
self.tracks_view.add_segment(
|
|
2254
|
+
onset, offset, event_index, brush=event_brush, pen=event_pen, movable=movable, text=segment_text
|
|
2255
|
+
)
|
|
2175
2256
|
if self.show_annot:
|
|
2176
2257
|
self.annot_view.add_segment(
|
|
2177
2258
|
onset,
|
|
@@ -2183,7 +2264,9 @@ class PSV(MainWindow):
|
|
|
2183
2264
|
text=segment_text,
|
|
2184
2265
|
)
|
|
2185
2266
|
if self.show_spec:
|
|
2186
|
-
self.spec_view.add_segment(
|
|
2267
|
+
self.spec_view.add_segment(
|
|
2268
|
+
onset, offset, event_index, brush=event_brush, pen=event_pen, movable=movable, text=segment_text
|
|
2269
|
+
)
|
|
2187
2270
|
elif self.event_times.categories[event_name] == "event":
|
|
2188
2271
|
if self.show_trace:
|
|
2189
2272
|
self.slice_view.add_event(events_in_view[:, 0], event_index, event_pen, movable=movable, text=segment_text)
|
|
@@ -2216,15 +2299,9 @@ class PSV(MainWindow):
|
|
|
2216
2299
|
|
|
2217
2300
|
new_region = region.getRegion()
|
|
2218
2301
|
self.event_times.move_time(event_name_to_move, region.bounds, new_region)
|
|
2219
|
-
logger.info(
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
if mp > 0 and mp < 1:
|
|
2223
|
-
new_event_idx = int(mp * self.nb_eventtypes)
|
|
2224
|
-
new_event_name = self.event_times.names[new_event_idx]
|
|
2225
|
-
_, old_name, new_name = self.event_times.change_name(new_region[0], new_event_name)
|
|
2226
|
-
if old_name is not None:
|
|
2227
|
-
logger.info(f" Changed from {old_name} to {new_name}.")
|
|
2302
|
+
logger.info(
|
|
2303
|
+
f" Moved {event_name_to_move} from t=[{region.bounds[0]:1.4f}:{region.bounds[1]:1.4f}] to [{new_region[0]:1.4f}:{new_region[1]:1.4f}] seconds."
|
|
2304
|
+
)
|
|
2228
2305
|
|
|
2229
2306
|
self.update_xy()
|
|
2230
2307
|
|
|
@@ -2240,15 +2317,6 @@ class PSV(MainWindow):
|
|
|
2240
2317
|
self.event_times.move_time(event_name_to_move, position.position, new_position)
|
|
2241
2318
|
logger.info(f" Moved {event_name_to_move} from t={position.position:1.4f} to {new_position:1.4f} seconds.")
|
|
2242
2319
|
|
|
2243
|
-
mp = self.annot_view.mousePoint.y()
|
|
2244
|
-
if mp > 0 and mp < 1:
|
|
2245
|
-
new_event_idx = int(mp * self.nb_eventtypes)
|
|
2246
|
-
new_event_name = self.event_times.names[new_event_idx]
|
|
2247
|
-
print(new_event_name)
|
|
2248
|
-
_, old_name, new_name = self.event_times.change_name(new_position, new_event_name, tol=1)
|
|
2249
|
-
if old_name is not None:
|
|
2250
|
-
logger.info(f" Changed from {old_name} to {new_name}.")
|
|
2251
|
-
|
|
2252
2320
|
self.update_xy()
|
|
2253
2321
|
|
|
2254
2322
|
def on_position_dragged(self, fly, pos, offset):
|
|
@@ -2285,7 +2353,10 @@ class PSV(MainWindow):
|
|
|
2285
2353
|
fly_pos = self.ds.pose_positions_allo.data[self.index_other, :, self.pose_center_index, :]
|
|
2286
2354
|
fly_pos = np.array(fly_pos) # in case this is a dask.array
|
|
2287
2355
|
if self.crop: # transform fly pos to coordinates of the cropped box
|
|
2288
|
-
box_center =
|
|
2356
|
+
box_center = (
|
|
2357
|
+
self.ds.pose_positions_allo.data[self.index_other, self.focal_fly, self.pose_center_index]
|
|
2358
|
+
+ self.box_size / 2
|
|
2359
|
+
)
|
|
2289
2360
|
box_center = np.array(box_center) # in case this is a dask.array
|
|
2290
2361
|
fly_pos = fly_pos - box_center
|
|
2291
2362
|
fly_dist = np.sum((fly_pos - np.array([mouseY, mouseX])) ** 2, axis=-1)
|
|
@@ -2325,7 +2396,9 @@ class PSV(MainWindow):
|
|
|
2325
2396
|
if self.event_times.categories[self.current_event_name] == "event":
|
|
2326
2397
|
logger.info(f" Changed event at {changed_time[0]:1.4f} from {old_name} to {new_name}.")
|
|
2327
2398
|
else:
|
|
2328
|
-
logger.info(
|
|
2399
|
+
logger.info(
|
|
2400
|
+
f" Changed segment at {changed_time[0]:1.4f}:{changed_time[1]:1.4f} from {old_name} to {new_name}."
|
|
2401
|
+
)
|
|
2329
2402
|
self.update_xy()
|
|
2330
2403
|
elif mouseButton == 1: # add event
|
|
2331
2404
|
if self.current_event_index is not None:
|
|
@@ -2345,12 +2418,16 @@ class PSV(MainWindow):
|
|
|
2345
2418
|
stop_seconds=mouseT,
|
|
2346
2419
|
channel=self.current_channel_index,
|
|
2347
2420
|
)
|
|
2348
|
-
logger.info(
|
|
2421
|
+
logger.info(
|
|
2422
|
+
f" Added {self.current_event_name} on channel {self.current_channel_index} at t=[{self.sinet0:1.4f}:{mouseT:1.4f}] seconds."
|
|
2423
|
+
)
|
|
2349
2424
|
self.sinet0 = None
|
|
2350
2425
|
if self.event_times.categories[self.current_event_name] == "event":
|
|
2351
2426
|
self.sinet0 = None
|
|
2352
2427
|
self.event_times.add_time(self.current_event_name, start_seconds=mouseT, channel=self.current_channel_index)
|
|
2353
|
-
logger.info(
|
|
2428
|
+
logger.info(
|
|
2429
|
+
f" Added {self.current_event_name} on channel {self.current_channel_index} at t={mouseT:1.4f} seconds."
|
|
2430
|
+
)
|
|
2354
2431
|
self.update_xy()
|
|
2355
2432
|
else:
|
|
2356
2433
|
self.sinet0 = None
|
|
@@ -394,7 +394,9 @@ def make(
|
|
|
394
394
|
store.attrs["data_splits"] = data_split_dict
|
|
395
395
|
logger.info("Done.")
|
|
396
396
|
# report
|
|
397
|
-
logger.info(
|
|
397
|
+
logger.info(
|
|
398
|
+
f" Got {store['train']['x'].shape}, {store['val']['x'].shape}, {store['test']['x'].shape} train/val/test samples."
|
|
399
|
+
)
|
|
398
400
|
if to_npy_dir: # save as npy_dir
|
|
399
401
|
logger.info(f" Saving to {store_folder}.")
|
|
400
402
|
das.npy_dir.save(store_folder, store)
|
|
@@ -409,7 +411,9 @@ def make(
|
|
|
409
411
|
for key in store.keys():
|
|
410
412
|
ann = event_utils.traces_to_eventtimes(store[key]["y"][:, 1:].T, names, types, events_are_binary=False)
|
|
411
413
|
for k, v in ann.items():
|
|
412
|
-
if
|
|
414
|
+
if (
|
|
415
|
+
v.ndim == 1
|
|
416
|
+
): # add start/stop seconds for events - traces_to_eventtimes only returns event start times but annot expects both start and end
|
|
413
417
|
v = np.stack((v, v)).T
|
|
414
418
|
v = v / fs # convert indices to seconds
|
|
415
419
|
ann[k] = v
|
|
@@ -26,6 +26,7 @@ is :py:method:`FormBuilderLayout.add_item()`. Look there if you want to know
|
|
|
26
26
|
what's supported in the YAML file, what exactly each field type does, or if you
|
|
27
27
|
want to add a new type of supported form field.
|
|
28
28
|
"""
|
|
29
|
+
|
|
29
30
|
# modified from https://sleap.ai/_modules/sleap/gui/formbuilder.html
|
|
30
31
|
import yaml
|
|
31
32
|
from typing import Any, Dict, List, Optional, Union, Text
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xarray-behave-0.35.2 → xarray-behave-0.35.3}/doc/demo_behavioral_features_large_group.ipynb
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/forms/envelope_computation.yaml
RENAMED
|
File without changes
|
{xarray-behave-0.35.2 → xarray-behave-0.35.3}/src/xarray_behave/gui/forms/export_for_das.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|