xarray-behave 0.35.5__tar.gz → 0.35.8__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.5 → xarray-behave-0.35.8}/.github/workflows/publish.yaml +18 -16
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/PKG-INFO +2 -4
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/README.md +1 -2
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/build_env.yml +0 -6
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/conda/xarray-behave/meta.yaml +11 -24
- xarray-behave-0.35.8/condarc.yml +9 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/pyproject.toml +1 -1
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/__init__.py +1 -1
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/annot.py +16 -1
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/app.py +12 -10
- xarray-behave-0.35.8/src/xarray_behave/io/samplestamps/__init__.py +6 -0
- xarray-behave-0.35.8/src/xarray_behave/io/samplestamps/samplestamps.py +114 -0
- xarray-behave-0.35.8/src/xarray_behave/io/samplestamps/utils.py +181 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/loaders.py +1 -1
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/xarray_behave.py +122 -32
- xarray-behave-0.35.5/conda/xarray-behave/conda_build_config.yaml +0 -4
- xarray-behave-0.35.5/condarc.yml +0 -3
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/.gitignore +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/LICENSE +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/conda/xarray-behave/bld.bat +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/conda/xarray-behave/build.sh +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/doc/demo.ipynb +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/doc/demo_behavioral_features.ipynb +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/doc/demo_behavioral_features_large_group.ipynb +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/doc/ncb.mplstyle +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/setup.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/event_utils.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/__init__.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/audio_player.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/das.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/formbuilder.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/forms/das_make.yaml +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/forms/das_predict.yaml +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/forms/das_train.yaml +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/forms/envelope_computation.yaml +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/forms/export_for_das.yaml +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/forms/from_dir.yaml +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/forms/from_file.yaml +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/forms/from_zarr.yaml +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/forms/spec_freq.yaml +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/icon.png +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/table.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/utils.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/view_dialog.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/views.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/widgets.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/io/__init__.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/io/annotations.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/io/annotations_manual.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/io/audio.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/io/balltracks.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/io/movieparams.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/io/poses.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/io/timestamps.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/io/tracks.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/metrics.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/tests/test_annot.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/tests/test_assemble.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/tests/test_assemble_metrics.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/tests/test_imports.py +0 -0
- {xarray-behave-0.35.5 → xarray-behave-0.35.8}/tests/test_io.py +0 -0
|
@@ -19,11 +19,8 @@ jobs:
|
|
|
19
19
|
strategy:
|
|
20
20
|
fail-fast: False
|
|
21
21
|
matrix:
|
|
22
|
-
python-version: ['3.10']
|
|
23
|
-
os: [ubuntu-latest, windows-latest, macOS-
|
|
24
|
-
include:
|
|
25
|
-
- python-version: 3.9
|
|
26
|
-
os: windows-latest
|
|
22
|
+
python-version: ['3.10', '3.11', '3.12']
|
|
23
|
+
os: [ubuntu-latest, windows-latest, macOS-14]
|
|
27
24
|
defaults: # https://github.com/marketplace/actions/setup-miniconda#use-a-default-shell
|
|
28
25
|
run:
|
|
29
26
|
shell: bash -l {0}
|
|
@@ -33,21 +30,26 @@ jobs:
|
|
|
33
30
|
uses: conda-incubator/setup-miniconda@v3
|
|
34
31
|
with:
|
|
35
32
|
auto-update-conda: true
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
channels: conda-forge,ncb,anaconda,defaults
|
|
39
|
-
python-version: 3.11
|
|
40
|
-
mamba-version: "*"
|
|
33
|
+
channels: conda-forge,ncb
|
|
34
|
+
python-version: 3.12
|
|
41
35
|
auto-activate-base: false
|
|
42
36
|
activate-environment: anaconda-client-env
|
|
43
37
|
environment-file: build_env.yml
|
|
44
38
|
condarc-file: condarc.yml
|
|
45
|
-
|
|
46
|
-
use-only-tar-bz2: true # IMPORTANT: This needs to be set for caching to work properly!
|
|
47
|
-
- name: Build and upload to anaconda
|
|
39
|
+
- name: Build
|
|
48
40
|
run: |
|
|
49
|
-
|
|
41
|
+
mkdir ./build
|
|
42
|
+
conda build conda/xarray-behave --no-test --no-anaconda-upload --python ${{ matrix.python-version }} --output-folder ./build
|
|
43
|
+
- name: Test
|
|
44
|
+
run: |
|
|
45
|
+
conda deactivate
|
|
46
|
+
conda create -n xb_test python=${{ matrix.python-version }} xarray-behave -c ./build -c local -c conda-forge -c ncb -y
|
|
47
|
+
conda activate xb_test
|
|
48
|
+
python -c "import xarray_behave; print(xarray_behave.__version__)"
|
|
49
|
+
python -c "import xarray_behave.gui.app"
|
|
50
|
+
# xb --help
|
|
51
|
+
- name: Upload
|
|
52
|
+
run: |
|
|
53
|
+
conda build conda/xarray-behave --no-test --python ${{ matrix.python-version }} --output-folder ./build --user ncb --token ${{ secrets.ANACONDA_TOKEN }}
|
|
50
54
|
env:
|
|
51
55
|
ANACONDA_TOKEN: ${{ secrets.ANACONDA_TOKEN }}
|
|
52
|
-
|
|
53
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: xarray-behave
|
|
3
|
-
Version: 0.35.
|
|
3
|
+
Version: 0.35.8
|
|
4
4
|
Summary: xarray tools for behavioral data.
|
|
5
5
|
Author-email: Jan Clemens <clemensjan@googlemail.com>
|
|
6
6
|
Requires-Python: >3.6
|
|
@@ -15,7 +15,6 @@ Requires-Dist: flammkuchen
|
|
|
15
15
|
Requires-Dist: rich
|
|
16
16
|
Requires-Dist: dask
|
|
17
17
|
Requires-Dist: toolz
|
|
18
|
-
Requires-Dist: samplestamps
|
|
19
18
|
Requires-Dist: pysoundfile
|
|
20
19
|
Requires-Dist: opencv-python-headless
|
|
21
20
|
Requires-Dist: pyside6 ; extra == "gui"
|
|
@@ -38,8 +37,7 @@ Provides-Extra: gui
|
|
|
38
37
|
Install a working conda installation (see [here](https://docs.conda.io/en/latest/miniconda.html)).
|
|
39
38
|
|
|
40
39
|
```shell
|
|
41
|
-
conda
|
|
42
|
-
mamba create -c conda-forge -c anaconda -c ncb -n xb python=3.9 xarray-behave
|
|
40
|
+
conda create -c conda-forge -c ncb -n xb python=3.12 xarray-behave
|
|
43
41
|
```
|
|
44
42
|
|
|
45
43
|
See `demo.ipynb` for usage examples.
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
Install a working conda installation (see [here](https://docs.conda.io/en/latest/miniconda.html)).
|
|
4
4
|
|
|
5
5
|
```shell
|
|
6
|
-
conda
|
|
7
|
-
mamba create -c conda-forge -c anaconda -c ncb -n xb python=3.9 xarray-behave
|
|
6
|
+
conda create -c conda-forge -c ncb -n xb python=3.12 xarray-behave
|
|
8
7
|
```
|
|
9
8
|
|
|
10
9
|
See `demo.ipynb` for usage examples.
|
|
@@ -16,46 +16,33 @@ requirements:
|
|
|
16
16
|
host:
|
|
17
17
|
- python {{ python }}
|
|
18
18
|
- pip
|
|
19
|
-
- numpy=1.23
|
|
20
19
|
run:
|
|
21
20
|
- python {{ python }}
|
|
22
21
|
- defopt=6.3
|
|
23
22
|
- rich
|
|
24
23
|
- flammkuchen
|
|
25
24
|
- h5py
|
|
26
|
-
- librosa>0.8
|
|
27
|
-
- matplotlib
|
|
28
|
-
- pandas
|
|
29
|
-
- scipy>=1.9
|
|
30
|
-
- peakutils
|
|
31
25
|
- pyyaml
|
|
32
|
-
- scikit-learn
|
|
33
26
|
- zarr
|
|
34
27
|
- numba
|
|
35
28
|
- xarray
|
|
36
29
|
- dask
|
|
37
|
-
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
- librosa>0.8
|
|
31
|
+
- pysoundfile
|
|
32
|
+
- python-sounddevice
|
|
33
|
+
- matplotlib
|
|
34
|
+
- colorcet
|
|
35
|
+
- pandas
|
|
36
|
+
- scipy>=1.9
|
|
37
|
+
- scikit-image
|
|
38
|
+
- scikit-learn
|
|
39
|
+
- peakutils
|
|
40
|
+
- pyside6
|
|
42
41
|
- pyqtgraph>0.12
|
|
43
42
|
- qtpy
|
|
44
43
|
- superqt
|
|
45
|
-
- rich
|
|
46
|
-
- colorcet
|
|
47
|
-
- python-sounddevice
|
|
48
|
-
- scikit-image
|
|
49
44
|
- ffmpeg
|
|
50
45
|
- pyvideoreader
|
|
51
|
-
- samplestamps>=0.6
|
|
52
|
-
- pysoundfile
|
|
53
|
-
|
|
54
|
-
test:
|
|
55
|
-
imports:
|
|
56
|
-
- xarray_behave
|
|
57
|
-
commands:
|
|
58
|
-
- xb --help [ not win ]
|
|
59
46
|
|
|
60
47
|
about:
|
|
61
48
|
home: "http://github.com/janclemenslab/xarray-behave"
|
|
@@ -11,7 +11,7 @@ classifiers = ["License :: OSI Approved :: MIT License"]
|
|
|
11
11
|
dynamic = ["version", "description"]
|
|
12
12
|
requires-python = ">3.6"
|
|
13
13
|
dependencies = ['numpy', 'scipy', 'xarray', 'h5py', 'zarr', 'flammkuchen', 'rich',
|
|
14
|
-
'dask', 'toolz', '
|
|
14
|
+
'dask', 'toolz', 'pysoundfile', 'opencv-python-headless']
|
|
15
15
|
readme = "README.md"
|
|
16
16
|
|
|
17
17
|
[project.optional-dependencies]
|
|
@@ -87,7 +87,7 @@ class Events(UserDict):
|
|
|
87
87
|
):
|
|
88
88
|
if possible_event_names is None:
|
|
89
89
|
possible_event_names = []
|
|
90
|
-
unique_names = list(
|
|
90
|
+
unique_names = list(np.unique(names)) # use `np.unique` instead of `set` to have reproducible order of names for now
|
|
91
91
|
unique_names.extend(possible_event_names)
|
|
92
92
|
dct = {name: [] for name in unique_names}
|
|
93
93
|
|
|
@@ -548,6 +548,18 @@ class Events(UserDict):
|
|
|
548
548
|
self[name] = np.delete(self[name], indices, axis=0)
|
|
549
549
|
return indices
|
|
550
550
|
|
|
551
|
+
def sort(self, names: Optional[List[str]] = None):
|
|
552
|
+
"""Sort annotations by start time.
|
|
553
|
+
|
|
554
|
+
Args:
|
|
555
|
+
names (Optional[List[str]], optional): _description_. Defaults to None.
|
|
556
|
+
"""
|
|
557
|
+
if names is None:
|
|
558
|
+
names = self.names
|
|
559
|
+
|
|
560
|
+
for name in names:
|
|
561
|
+
self[name] = self[name][np.argsort(self[name][:, 0]), :]
|
|
562
|
+
|
|
551
563
|
def find_next(self, t: float, names: Optional[List[str]] = None):
|
|
552
564
|
"""Find event starting after `t` of type in names.
|
|
553
565
|
|
|
@@ -564,7 +576,9 @@ class Events(UserDict):
|
|
|
564
576
|
names = self.names
|
|
565
577
|
|
|
566
578
|
nxt = []
|
|
579
|
+
|
|
567
580
|
for name in names:
|
|
581
|
+
self.sort([name])
|
|
568
582
|
cmp = self[name][:, 0] > t
|
|
569
583
|
if np.any(cmp):
|
|
570
584
|
nxt.append(self[name][np.argmax(cmp), 0])
|
|
@@ -588,6 +602,7 @@ class Events(UserDict):
|
|
|
588
602
|
|
|
589
603
|
nxt = []
|
|
590
604
|
for name in names:
|
|
605
|
+
self.sort([name])
|
|
591
606
|
cmp = self[name][:, 1] < t
|
|
592
607
|
if np.any(cmp):
|
|
593
608
|
nxt.append(self[name][np.argmin(cmp) - 1, 0])
|
|
@@ -2084,24 +2084,26 @@ class PSV(MainWindow):
|
|
|
2084
2084
|
self.update_frame()
|
|
2085
2085
|
|
|
2086
2086
|
def set_prev_cuepoint(self, qt_keycode):
|
|
2087
|
-
if self.edit_only_current_events: # of the currently active type
|
|
2088
|
-
|
|
2089
|
-
else: # of any type
|
|
2090
|
-
|
|
2091
|
-
|
|
2087
|
+
# if self.edit_only_current_events: # of the currently active type
|
|
2088
|
+
# names = [self.current_event_name]
|
|
2089
|
+
# else: # of any type
|
|
2090
|
+
# names = self.event_times.names
|
|
2091
|
+
names = [self.current_event_name]
|
|
2092
2092
|
t = (self.t0 - 1) / self.fs_song
|
|
2093
2093
|
nxt = self.event_times.find_prev(t, names)
|
|
2094
|
+
|
|
2094
2095
|
if nxt is not None:
|
|
2095
2096
|
self.t0 = nxt * self.fs_song
|
|
2096
2097
|
|
|
2097
2098
|
def set_next_cuepoint(self, qt_keycode):
|
|
2098
|
-
if self.edit_only_current_events: # of the currently active type
|
|
2099
|
-
|
|
2100
|
-
else: # of any type
|
|
2101
|
-
|
|
2102
|
-
|
|
2099
|
+
# if self.edit_only_current_events: # of the currently active type
|
|
2100
|
+
# names = [self.current_event_name]
|
|
2101
|
+
# else: # of any type
|
|
2102
|
+
# names = self.event_times.names
|
|
2103
|
+
names = [self.current_event_name]
|
|
2103
2104
|
t = (self.t0 + 1) / self.fs_song
|
|
2104
2105
|
nxt = self.event_times.find_next(t, names)
|
|
2106
|
+
|
|
2105
2107
|
if nxt is not None:
|
|
2106
2108
|
self.t0 = nxt * self.fs_song
|
|
2107
2109
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Tools for converting between samples and time stamps.
|
|
2
|
+
|
|
3
|
+
glossary:
|
|
4
|
+
densely stamped - each sample is consecutively time stamped
|
|
5
|
+
sparsely stamped - only selected samples are time stamped - need to provide sample numbers
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from . import utils
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SampStamp:
|
|
12
|
+
"""Converts between frames and samples."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
sample_times,
|
|
17
|
+
frame_times=None,
|
|
18
|
+
sample_numbers=None,
|
|
19
|
+
frame_numbers=None,
|
|
20
|
+
frame_samples=None,
|
|
21
|
+
sample_times_offset=0,
|
|
22
|
+
frame_times_offset=0,
|
|
23
|
+
auto_monotonize=True,
|
|
24
|
+
):
|
|
25
|
+
"""Get converter.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
sample_times(np.ndarray)
|
|
29
|
+
frame_times(np.ndarray)
|
|
30
|
+
sample_number(np.ndarray)
|
|
31
|
+
frame_number(np.ndarray)
|
|
32
|
+
frame_samples
|
|
33
|
+
sample_times_offset(float)
|
|
34
|
+
frame_times_offset(float)
|
|
35
|
+
auto_monotonize(bool)
|
|
36
|
+
"""
|
|
37
|
+
# we want:
|
|
38
|
+
# samples -> frames
|
|
39
|
+
# samples -> times
|
|
40
|
+
# frames -> samples
|
|
41
|
+
# frames -> times
|
|
42
|
+
|
|
43
|
+
# we need:
|
|
44
|
+
# (samples, times) from DAQ, (frames, times) from video timestamps
|
|
45
|
+
# (samples, times) from DAQ, (samples, frames) from movieframes
|
|
46
|
+
# (samples, frames) from movieframes, (frames, times) from video timestamps (ignore edge case)
|
|
47
|
+
|
|
48
|
+
# generate dense x_number arrays
|
|
49
|
+
if sample_numbers is None and sample_times is not None:
|
|
50
|
+
sample_numbers = range(sample_times.shape[0])
|
|
51
|
+
if frame_numbers is None and frame_times is not None:
|
|
52
|
+
frame_numbers = range(frame_times.shape[0])
|
|
53
|
+
|
|
54
|
+
# correct for offsets
|
|
55
|
+
sample_times += sample_times_offset
|
|
56
|
+
if frame_times is not None:
|
|
57
|
+
frame_times += frame_times_offset
|
|
58
|
+
|
|
59
|
+
if auto_monotonize:
|
|
60
|
+
if sample_times is not None:
|
|
61
|
+
sample_times = utils.monotonize(sample_times)
|
|
62
|
+
sample_numbers = sample_numbers[: sample_times.shape[0]]
|
|
63
|
+
if frame_times is not None:
|
|
64
|
+
frame_times = utils.monotonize(frame_times)
|
|
65
|
+
frame_numbers = frame_numbers[: frame_times.shape[0]]
|
|
66
|
+
if frame_samples is not None:
|
|
67
|
+
frame_samples = utils.monotonize(frame_samples)
|
|
68
|
+
# frame_numbers = frame_numbers[:frame_times.shape[0]]
|
|
69
|
+
|
|
70
|
+
# get all interpolators for re-use
|
|
71
|
+
if sample_numbers is not None and sample_times is not None:
|
|
72
|
+
self.samples2times = utils.interpolator(sample_numbers, sample_times)
|
|
73
|
+
if frame_times is None and frame_samples is not None:
|
|
74
|
+
frame_times = self.samples2times(frame_samples)
|
|
75
|
+
# self.frame_times = self.samples2times(frame_samples)
|
|
76
|
+
|
|
77
|
+
if frame_numbers is not None and frame_times is not None:
|
|
78
|
+
self.frames2times = utils.interpolator(frame_numbers, frame_times)
|
|
79
|
+
if frame_times is not None and sample_numbers is not None:
|
|
80
|
+
self.times2samples = utils.interpolator(sample_times, sample_numbers)
|
|
81
|
+
if frame_times is not None and frame_numbers is not None:
|
|
82
|
+
self.times2frames = utils.interpolator(frame_times, frame_numbers)
|
|
83
|
+
|
|
84
|
+
if frame_samples is not None:
|
|
85
|
+
self.samples2frames = utils.interpolator(frame_samples, frame_numbers, fill_value="extrapolate")
|
|
86
|
+
|
|
87
|
+
def frame(self, sample):
|
|
88
|
+
"""Get frame number from sample number."""
|
|
89
|
+
return self.times2frames(self.sample_time(sample))
|
|
90
|
+
|
|
91
|
+
def sample(self, frame):
|
|
92
|
+
"""Get sample number from frame number."""
|
|
93
|
+
return self.times2samples(self.frame_time(frame))
|
|
94
|
+
|
|
95
|
+
def frame_time(self, frame):
|
|
96
|
+
"""Get time of frame number."""
|
|
97
|
+
return self.frames2times(frame)
|
|
98
|
+
|
|
99
|
+
def sample_time(self, sample):
|
|
100
|
+
"""Get time of sample number."""
|
|
101
|
+
return self.samples2times(sample)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class SimpleStamp(SampStamp):
|
|
105
|
+
"""If all you need is conversion between sampling rates."""
|
|
106
|
+
|
|
107
|
+
def __init__(self, sampling_rate: float):
|
|
108
|
+
|
|
109
|
+
self.sampling_rate = sampling_rate
|
|
110
|
+
self.samples2times = utils.SampleInterpolator(self.sampling_rate)
|
|
111
|
+
self.times2samples = utils.SampleInterpolator(1 / self.sampling_rate)
|
|
112
|
+
|
|
113
|
+
self.frames2times = self.samples2times
|
|
114
|
+
self.times2frames = self.times2samples
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import operator as op
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import scipy.interpolate
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def ismonotonous(x, direction="increasing", strict=True):
|
|
8
|
+
"""Check if vector is monotonous.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
x(np.ndarray)
|
|
12
|
+
direction(str): 'increasing' or 'decreasing'
|
|
13
|
+
strict(bool): defaults to True
|
|
14
|
+
Returns:
|
|
15
|
+
(bool)
|
|
16
|
+
"""
|
|
17
|
+
allowed_directions = ["increasing", "decreasing"]
|
|
18
|
+
if direction not in allowed_directions:
|
|
19
|
+
raise ValueError(f'Direction "{direction}" must be in {allowed_directions}.')
|
|
20
|
+
|
|
21
|
+
if direction == "decreasing":
|
|
22
|
+
x = -x
|
|
23
|
+
|
|
24
|
+
if strict:
|
|
25
|
+
comp_op = op.gt # >
|
|
26
|
+
else:
|
|
27
|
+
comp_op = op.ge # >=
|
|
28
|
+
|
|
29
|
+
return np.all(comp_op(x[1:], x[:-1]))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def monotonize(x, direction="increasing", strict=True):
|
|
33
|
+
"""Cut trailing non-monotonous values.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
x
|
|
37
|
+
direction - montonously 'increasing' (default) or 'decreasing'
|
|
38
|
+
strict - strictly (default) or non-strictly monotonous
|
|
39
|
+
Returns:
|
|
40
|
+
truncated array
|
|
41
|
+
"""
|
|
42
|
+
allowed_directions = ["increasing", "decreasing"]
|
|
43
|
+
if direction not in allowed_directions:
|
|
44
|
+
raise ValueError(f'Direction "{direction}" must be in {allowed_directions}.')
|
|
45
|
+
|
|
46
|
+
if strict:
|
|
47
|
+
comp_op = op.le # >=
|
|
48
|
+
else:
|
|
49
|
+
comp_op = op.lt # >
|
|
50
|
+
|
|
51
|
+
if direction == "decreasing":
|
|
52
|
+
comp = comp_op(x[:-1], x[1:])
|
|
53
|
+
else:
|
|
54
|
+
comp = comp_op(x[1:], x[:-1])
|
|
55
|
+
|
|
56
|
+
if np.all(~comp):
|
|
57
|
+
last_idx = len(comp) + 1
|
|
58
|
+
else:
|
|
59
|
+
last_idx = np.argmax(comp) + 1
|
|
60
|
+
|
|
61
|
+
return x[:last_idx]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def interpolator(x, y, fill_value="extrapolate"):
|
|
65
|
+
return scipy.interpolate.interp1d(x, y, fill_value=fill_value)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class SampleInterpolator:
|
|
69
|
+
"""For samples on a regular grid"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, sampling_rate: float):
|
|
72
|
+
self.sampling_rate = float(sampling_rate)
|
|
73
|
+
# dummy values for compatibililty with Interpolators from scipy.interpolate
|
|
74
|
+
self.x = np.array([0.0, 1.0])
|
|
75
|
+
self.y = self.x / self.sampling_rate
|
|
76
|
+
|
|
77
|
+
def __call__(self, sample_numbers):
|
|
78
|
+
return np.asarray(sample_numbers) / self.sampling_rate
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def time_from_log(logfilename, line_number=1):
|
|
82
|
+
"""Parse time stamp from a specified lines in a log file.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
logfilename(str)
|
|
86
|
+
line_number(int): line in the log file from which parse the time stamp (defaults to 1 - will read the first line (not 0-indexed!))
|
|
87
|
+
Returns:
|
|
88
|
+
(datetime) time stamp
|
|
89
|
+
"""
|
|
90
|
+
with open(logfilename, "r") as f:
|
|
91
|
+
for _ in range(line_number):
|
|
92
|
+
current_line = f.readline()
|
|
93
|
+
|
|
94
|
+
current_line_parts = current_line.partition(" ")[0]
|
|
95
|
+
return datetime.strptime(current_line_parts, "%Y-%m-%d,%H:%M:%S.%f")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def samplenumber_from_timestamps(target_time, timestamps, sample_at_timestamps=None):
|
|
99
|
+
"""Gets samplenumber from timestamps given time.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
target_time (numpy.ndarrary): time of desired sample
|
|
103
|
+
timestamps (numpy.ndarrary): list of timestamps
|
|
104
|
+
sample_at_timestamps: can be provided for sparsely stamped data, the sample number for each timestamp
|
|
105
|
+
Returns:
|
|
106
|
+
samplenumber at target time (as an index, starts at 0, can be <0 if target is before first timestamps) (np.intp)
|
|
107
|
+
"""
|
|
108
|
+
if not ismonotonous(timestamps, strict=True):
|
|
109
|
+
raise ValueError(f"Timestamps must increase strictly monotonously.")
|
|
110
|
+
|
|
111
|
+
if sample_at_timestamps is None:
|
|
112
|
+
sample_at_timestamps = range(timestamps.shape[0])
|
|
113
|
+
|
|
114
|
+
f = interpolator(timestamps, sample_at_timestamps)
|
|
115
|
+
samplenumber = np.intp(np.round(f(target_time)))
|
|
116
|
+
|
|
117
|
+
return samplenumber
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def samplerange_from_timestamps(target_epoch, timestamps, sample_at_timestamps=None):
|
|
121
|
+
"""Gets range of samples from timestamps given a epoch defined by start and stop time.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
target_epoch (numpy.ndarrary): start and stop time
|
|
125
|
+
timestamps (numpy.ndarrary): list of timestamps
|
|
126
|
+
sample_at_timestamps: can be provided for sparsely stamped data, the sample number for each timestamp
|
|
127
|
+
Returns:
|
|
128
|
+
range of samples spanning epoch (as an indices, starts at 0, can be <0 if targets extends to before first timestamps)
|
|
129
|
+
"""
|
|
130
|
+
samplenumber_start = samplenumber_from_timestamps(target_epoch[0], timestamps, sample_at_timestamps)
|
|
131
|
+
samplenumber_end = samplenumber_from_timestamps(target_epoch[1], timestamps, sample_at_timestamps)
|
|
132
|
+
return range(samplenumber_start, samplenumber_end)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def timestamp_from_samplenumber(samplenumber, timestamps, sample_at_timestamps=None):
|
|
136
|
+
"""Gets samplenumber from timestamps given time.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
samplenumber (numpy.ndarrary): sample number for which we want the time stamp
|
|
140
|
+
timestamps (numpy.ndarrary): list of timestamps
|
|
141
|
+
sample_at_timestamps: can be provided for sparsely stamped data, the sample number for each timestamp
|
|
142
|
+
Returns:
|
|
143
|
+
time stamp for that sample (float)
|
|
144
|
+
"""
|
|
145
|
+
if not ismonotonous(timestamps, strict=True):
|
|
146
|
+
raise ValueError(f"Timestamps must increase strictly monotonously.")
|
|
147
|
+
|
|
148
|
+
if sample_at_timestamps is None:
|
|
149
|
+
sample_at_timestamps = range(timestamps.shape[0])
|
|
150
|
+
|
|
151
|
+
f = interpolator(sample_at_timestamps, timestamps)
|
|
152
|
+
timestamp = f(samplenumber)
|
|
153
|
+
|
|
154
|
+
return timestamp
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def samples_from_samples(sample_in, in_stamps, in_samplenumber=None, out_stamps=None, out_samplenumber=None):
|
|
158
|
+
"""Convert between different sampling grids via a common clock.
|
|
159
|
+
Args:
|
|
160
|
+
sample_in: sample number in INPUT sampling grid
|
|
161
|
+
in_stamps: time stamps of samples numbers `in_samplenumber` in OUTPUT sampling grid
|
|
162
|
+
in_samplenumber: sample numbers for INPUT timestamps (defaults to None for densely stamped data)
|
|
163
|
+
out_stamps: time stamps of samples numbers `out_samplenumber` in OUTPUT sampling grid
|
|
164
|
+
out_samplenumber: sample numbers for INPUT timestamps (defaults to None for densely stamped data)
|
|
165
|
+
Returns:
|
|
166
|
+
sample number in the OUTPUT sampling grid corresponding to sample_in
|
|
167
|
+
"""
|
|
168
|
+
time_in = timestamp_from_samplenumber(sample_in, in_stamps, in_samplenumber)
|
|
169
|
+
sample_out = samplenumber_from_timestamps(time_in, out_stamps, out_samplenumber)
|
|
170
|
+
return sample_out
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def timestamp_from_cycles(cycleOffset, cycleSecs):
|
|
174
|
+
times = cycleOffset + cycleSecs / 8000
|
|
175
|
+
# correct counter overflows
|
|
176
|
+
overflows = np.cumsum((np.diff(times) <= 0).astype(np.uint))
|
|
177
|
+
overflows = np.insert(overflows, 0, 0)
|
|
178
|
+
times = times + overflows * 128
|
|
179
|
+
# offset such that first time is zero
|
|
180
|
+
times = times - np.min(times)
|
|
181
|
+
return times
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Create self-documenting xarray dataset from behavioral recordings and annotations."""
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
-
from
|
|
4
|
+
from .io.samplestamps import SampStamp, SimpleStamp
|
|
5
5
|
import scipy.interpolate
|
|
6
6
|
import scipy.stats
|
|
7
7
|
import xarray as xr
|
|
@@ -145,7 +145,11 @@ def assemble(
|
|
|
145
145
|
else:
|
|
146
146
|
basename = filepath_daq
|
|
147
147
|
|
|
148
|
-
audio_loader = io.get_loader(
|
|
148
|
+
audio_loader = io.get_loader(
|
|
149
|
+
kind="audio",
|
|
150
|
+
basename=basename,
|
|
151
|
+
basename_is_full_name=filepath_daq_is_custom,
|
|
152
|
+
)
|
|
149
153
|
if not audio_loader and filepath_daq_is_custom:
|
|
150
154
|
audio_loader = io.audio.AudioFile(basename)
|
|
151
155
|
|
|
@@ -177,6 +181,7 @@ def assemble(
|
|
|
177
181
|
ss_ball, last_sample_number_ball, sampling_rate_ball = ld.load_times(filepath_timestamps_ball, filepath_daq)
|
|
178
182
|
|
|
179
183
|
filepath_timestamps_movie = Path(root, res_path, datename, f"{datename}_movieframes.csv")
|
|
184
|
+
ss_movie = None
|
|
180
185
|
if os.path.exists(filepath_daq) and os.path.exists(filepath_timestamps_movie):
|
|
181
186
|
ss_movie, last_sample_number_movie, sampling_rate_movie = ld.load_movietimes(filepath_timestamps_movie, filepath_daq)
|
|
182
187
|
|
|
@@ -284,20 +289,31 @@ def assemble(
|
|
|
284
289
|
with_movieparams = False
|
|
285
290
|
if include_movieparams:
|
|
286
291
|
logger.info("Loading movie params:")
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
try:
|
|
290
|
-
xr_movieparams = movieparams_loader.make(movieparams_loader.path)
|
|
291
|
-
xr_movieparams = add_time(xr_movieparams, ss_movie, dim="frame_number_movie", suffix="_movie")
|
|
292
|
-
|
|
293
|
-
logger.info(f" {movieparams_loader.path} loaded.")
|
|
294
|
-
with_movieparams = True
|
|
295
|
-
except Exception as e:
|
|
296
|
-
logger.info(f" Loading {movieparams_loader.path} failed.")
|
|
297
|
-
logger.exception(e)
|
|
292
|
+
if ss_movie is None:
|
|
293
|
+
logger.warning(" Failed loading movie params - no ss_movie")
|
|
298
294
|
else:
|
|
299
|
-
|
|
300
|
-
|
|
295
|
+
movieparams_loader = io.get_loader(
|
|
296
|
+
kind="movieparams",
|
|
297
|
+
basename=os.path.join(root, dat_path, datename, datename),
|
|
298
|
+
)
|
|
299
|
+
if movieparams_loader:
|
|
300
|
+
try:
|
|
301
|
+
xr_movieparams = movieparams_loader.make(movieparams_loader.path)
|
|
302
|
+
xr_movieparams = add_time(
|
|
303
|
+
xr_movieparams,
|
|
304
|
+
ss_movie,
|
|
305
|
+
dim="frame_number_movie",
|
|
306
|
+
suffix="_movie",
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
logger.info(f" {movieparams_loader.path} loaded.")
|
|
310
|
+
with_movieparams = True
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logger.info(f" Loading {movieparams_loader.path} failed.")
|
|
313
|
+
logger.exception(e)
|
|
314
|
+
else:
|
|
315
|
+
logger.warning(" Found no movie params data.")
|
|
316
|
+
logger.info("Done.")
|
|
301
317
|
|
|
302
318
|
# Init empty and event data
|
|
303
319
|
auto_event_seconds: Dict[str, Any] = {}
|
|
@@ -352,7 +368,9 @@ def assemble(
|
|
|
352
368
|
# load MANUAL SONG ANNOTATIONS
|
|
353
369
|
logger.info("Loading manual annotations:")
|
|
354
370
|
manual_annot_loader = io.get_loader(
|
|
355
|
-
kind="annotations_manual",
|
|
371
|
+
kind="annotations_manual",
|
|
372
|
+
basename=filepath_annotations,
|
|
373
|
+
basename_is_full_name=custom_filepath_annotations,
|
|
356
374
|
)
|
|
357
375
|
if manual_annot_loader:
|
|
358
376
|
try:
|
|
@@ -377,7 +395,9 @@ def assemble(
|
|
|
377
395
|
) # or construct form audio file name - .ext + _definitions.csv
|
|
378
396
|
|
|
379
397
|
definitions_loader = io.get_loader(
|
|
380
|
-
kind="definitions_manual",
|
|
398
|
+
kind="definitions_manual",
|
|
399
|
+
basename=filepath_definitions,
|
|
400
|
+
basename_is_full_name=custom_filepath_definitions,
|
|
381
401
|
)
|
|
382
402
|
if definitions_loader:
|
|
383
403
|
try:
|
|
@@ -404,14 +424,20 @@ def assemble(
|
|
|
404
424
|
else:
|
|
405
425
|
basename = filepath_daq
|
|
406
426
|
|
|
407
|
-
audio_loader = io.get_loader(
|
|
427
|
+
audio_loader = io.get_loader(
|
|
428
|
+
kind="audio",
|
|
429
|
+
basename=basename,
|
|
430
|
+
basename_is_full_name=filepath_daq_is_custom,
|
|
431
|
+
)
|
|
408
432
|
if not audio_loader and filepath_daq_is_custom:
|
|
409
433
|
audio_loader = io.audio.AudioFile(basename)
|
|
410
434
|
|
|
411
435
|
if audio_loader:
|
|
412
436
|
try:
|
|
413
437
|
song_raw, non_song_raw, samplerate = audio_loader.load(
|
|
414
|
-
audio_loader.path,
|
|
438
|
+
audio_loader.path,
|
|
439
|
+
return_nonsong_channels=True,
|
|
440
|
+
lazy=lazy_load_song,
|
|
415
441
|
)
|
|
416
442
|
logger.info(f" {audio_loader.path} with shape {song_raw.shape} loaded using {audio_loader.NAME}.")
|
|
417
443
|
except Exception as e:
|
|
@@ -441,7 +467,10 @@ def assemble(
|
|
|
441
467
|
f"No tracks - setting first/last tracked frame numbers to those of the first/last sample in the recording ({first_tracked_frame}, {last_tracked_frame})."
|
|
442
468
|
)
|
|
443
469
|
else:
|
|
444
|
-
first_tracked_frame, last_tracked_frame =
|
|
470
|
+
first_tracked_frame, last_tracked_frame = (
|
|
471
|
+
int(xr_tracks.frame_number[0]),
|
|
472
|
+
int(xr_tracks.frame_number[-1]),
|
|
473
|
+
)
|
|
445
474
|
logger.info(f"Tracked frame {first_tracked_frame} to {last_tracked_frame}.")
|
|
446
475
|
|
|
447
476
|
# construct desired sample grid for data
|
|
@@ -544,7 +573,14 @@ def assemble(
|
|
|
544
573
|
|
|
545
574
|
if with_tracks:
|
|
546
575
|
logger.info(" Tracking")
|
|
547
|
-
xr_tracks = align_time(
|
|
576
|
+
xr_tracks = align_time(
|
|
577
|
+
xr_tracks,
|
|
578
|
+
ss,
|
|
579
|
+
target_samples,
|
|
580
|
+
ref_time=ref_time,
|
|
581
|
+
target_time=time,
|
|
582
|
+
extrapolate=True,
|
|
583
|
+
)
|
|
548
584
|
xr_tracks.attrs.update(
|
|
549
585
|
{
|
|
550
586
|
"description": 'coords are "allocentric" - rel. to the full frame',
|
|
@@ -595,7 +631,13 @@ def assemble(
|
|
|
595
631
|
if with_balltracker:
|
|
596
632
|
logger.info(" Balltracker")
|
|
597
633
|
xr_balltracks = align_time(
|
|
598
|
-
xr_balltracks,
|
|
634
|
+
xr_balltracks,
|
|
635
|
+
ss_ball,
|
|
636
|
+
target_samples,
|
|
637
|
+
target_time=time,
|
|
638
|
+
dim="frame_number_ball",
|
|
639
|
+
suffix="_ball",
|
|
640
|
+
ref_time=ref_time,
|
|
599
641
|
)
|
|
600
642
|
xr_balltracks.attrs.update(
|
|
601
643
|
{
|
|
@@ -639,10 +681,17 @@ def assemble(
|
|
|
639
681
|
if "sampletime" not in dataset:
|
|
640
682
|
dataset.coords["sampletime"] = time
|
|
641
683
|
if "nearest_frame" not in dataset:
|
|
642
|
-
dataset.coords["nearest_frame"] = (
|
|
684
|
+
dataset.coords["nearest_frame"] = (
|
|
685
|
+
("time"),
|
|
686
|
+
(ss.times2frames(dataset["time"] + ref_time).astype(np.intp)),
|
|
687
|
+
)
|
|
643
688
|
|
|
644
689
|
# convert spatial units to mm using info in attrs
|
|
645
|
-
dataset = convert_spatial_units(
|
|
690
|
+
dataset = convert_spatial_units(
|
|
691
|
+
dataset,
|
|
692
|
+
to_units="mm",
|
|
693
|
+
names=["body_positions", "pose_positions", "pose_positions_allo"],
|
|
694
|
+
)
|
|
646
695
|
|
|
647
696
|
# save command line args
|
|
648
697
|
dataset.attrs = {
|
|
@@ -688,7 +737,12 @@ def _interp(ds, dim, target_frames_float, interp_kwargs={}):
|
|
|
688
737
|
# but now the first dim is size 1 - so we need to fix this
|
|
689
738
|
coords = ds_new.coords
|
|
690
739
|
# maps for the too short dims: dim name - ref dim
|
|
691
|
-
coords_map = {
|
|
740
|
+
coords_map = {
|
|
741
|
+
"flies": "flies",
|
|
742
|
+
"chambers": "flies",
|
|
743
|
+
"data_ball": "data_ball",
|
|
744
|
+
"params_movie": "params_movie",
|
|
745
|
+
}
|
|
692
746
|
new_coords = {}
|
|
693
747
|
for key in coords_map.keys():
|
|
694
748
|
if key in coords:
|
|
@@ -710,7 +764,12 @@ def _interp(ds, dim, target_frames_float, interp_kwargs={}):
|
|
|
710
764
|
# now interp all remaining dims
|
|
711
765
|
for fly in range(1, new_shape[1]):
|
|
712
766
|
interpolator = scipy.interpolate.interp1d(
|
|
713
|
-
ds[dim].data,
|
|
767
|
+
ds[dim].data,
|
|
768
|
+
ds.data[:, fly, ...],
|
|
769
|
+
axis=0,
|
|
770
|
+
bounds_error=False,
|
|
771
|
+
assume_sorted=True,
|
|
772
|
+
**interp_kwargs,
|
|
714
773
|
)
|
|
715
774
|
ds_x.data[:, fly, ...] = interpolator(target_frames_float)
|
|
716
775
|
|
|
@@ -736,12 +795,23 @@ def align_time(
|
|
|
736
795
|
ds = _interp(ds, dim, target_frames_float, interp_kwargs)
|
|
737
796
|
# ds = ds.interp({dim: target_frames_float}, assume_sorted=True, kwargs=interp_kwargs)
|
|
738
797
|
|
|
739
|
-
ds = ds.drop_vars(
|
|
798
|
+
ds = ds.drop_vars(
|
|
799
|
+
["frame_number" + suffix, "frame_times" + suffix, "frame_samples" + suffix],
|
|
800
|
+
errors="Ignore",
|
|
801
|
+
)
|
|
740
802
|
|
|
741
803
|
# time_new = ds['frametimes' + suffix] - ref_time
|
|
742
804
|
ds = ds.assign_coords({"time": ((dim), target_time)})
|
|
743
805
|
ds = ds.swap_dims({dim: "time"})
|
|
744
|
-
ds = ds.assign_coords(
|
|
806
|
+
ds = ds.assign_coords(
|
|
807
|
+
{
|
|
808
|
+
"nearest_frame"
|
|
809
|
+
+ suffix: (
|
|
810
|
+
("time"),
|
|
811
|
+
np.round(target_frames_float).astype(np.intp),
|
|
812
|
+
)
|
|
813
|
+
}
|
|
814
|
+
)
|
|
745
815
|
|
|
746
816
|
fps = 1 / np.nanmean(np.diff(ds["frametimes" + suffix]))
|
|
747
817
|
ds.attrs.update({"video_fps": fps})
|
|
@@ -802,7 +872,12 @@ def assemble_metrics(
|
|
|
802
872
|
time = dataset.time.data
|
|
803
873
|
nearest_frame = dataset.nearest_frame.data
|
|
804
874
|
|
|
805
|
-
pose_names = {
|
|
875
|
+
pose_names = {
|
|
876
|
+
"thorax": "thorax",
|
|
877
|
+
"head": "head",
|
|
878
|
+
"left_wing": "left_wing",
|
|
879
|
+
"right_wing": "right_wing",
|
|
880
|
+
}
|
|
806
881
|
if custom_pose_names is not None:
|
|
807
882
|
pose_names.update(custom_pose_names)
|
|
808
883
|
|
|
@@ -912,7 +987,11 @@ def assemble_metrics(
|
|
|
912
987
|
ds_dict["abs_features"] = xr.DataArray(
|
|
913
988
|
data=absolute.data,
|
|
914
989
|
dims=["time", "flies", "absolute_features"],
|
|
915
|
-
coords={
|
|
990
|
+
coords={
|
|
991
|
+
"time": time,
|
|
992
|
+
"absolute_features": abs_feature_names,
|
|
993
|
+
"nearest_frame": (("time"), nearest_frame),
|
|
994
|
+
},
|
|
916
995
|
attrs={
|
|
917
996
|
"description": 'coords are "egocentric" - rel. to box',
|
|
918
997
|
"sampling_rate_Hz": sampling_rate,
|
|
@@ -932,7 +1011,14 @@ def assemble_metrics(
|
|
|
932
1011
|
)
|
|
933
1012
|
rel_velocities_mag = np.sqrt(rel_velocities_forward**2 + rel_velocities_lateral**2)
|
|
934
1013
|
|
|
935
|
-
list_relative = [
|
|
1014
|
+
list_relative = [
|
|
1015
|
+
dis,
|
|
1016
|
+
rel_angles,
|
|
1017
|
+
rel_orientation,
|
|
1018
|
+
rel_velocities_mag,
|
|
1019
|
+
rel_velocities_forward,
|
|
1020
|
+
rel_velocities_lateral,
|
|
1021
|
+
]
|
|
936
1022
|
|
|
937
1023
|
rel_feature_names = [
|
|
938
1024
|
"distance",
|
|
@@ -947,7 +1033,11 @@ def assemble_metrics(
|
|
|
947
1033
|
ds_dict["rel_features"] = xr.DataArray(
|
|
948
1034
|
data=relative.data,
|
|
949
1035
|
dims=["time", "flies", "relative_flies", "relative_features"],
|
|
950
|
-
coords={
|
|
1036
|
+
coords={
|
|
1037
|
+
"time": time,
|
|
1038
|
+
"relative_features": rel_feature_names,
|
|
1039
|
+
"nearest_frame": (("time"), nearest_frame),
|
|
1040
|
+
},
|
|
951
1041
|
attrs={
|
|
952
1042
|
"description": 'coords are "egocentric" - rel. to box',
|
|
953
1043
|
"sampling_rate_Hz": sampling_rate,
|
xarray-behave-0.35.5/condarc.yml
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xarray-behave-0.35.5 → xarray-behave-0.35.8}/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
|
|
File without changes
|
{xarray-behave-0.35.5 → xarray-behave-0.35.8}/src/xarray_behave/gui/forms/envelope_computation.yaml
RENAMED
|
File without changes
|
{xarray-behave-0.35.5 → xarray-behave-0.35.8}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|