Compare commits
No commits in common. "master" and "plot_event_timeline" have entirely different histories.
master
...
plot_event
3
.gitignore
vendored
@ -5,8 +5,6 @@
|
||||
data
|
||||
env
|
||||
output
|
||||
trash
|
||||
.vscode
|
||||
|
||||
# Mac Stuff
|
||||
*.DS_Store
|
||||
@ -16,7 +14,6 @@ trash
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
poster/main.pdf
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
294
README.md
@ -1,90 +1,248 @@
|
||||
# Chirpdetector 2023
|
||||
<!-- Improved compatibility of back to top link: See: https://github.com/othneildrew/Best-README-Template/pull/73 -->
|
||||
<a name="readme-top"></a>
|
||||
<!--
|
||||
*** Thanks for checking out the Best-README-Template. If you have a suggestion
|
||||
*** that would make this better, please fork the repo and create a pull request
|
||||
*** or simply open an issue with the tag "enhancement".
|
||||
*** Don't forget to give the project a star!
|
||||
*** Thanks again! Now go create something AMAZING! :D
|
||||
-->
|
||||
|
||||
|
||||
|
||||
<!-- PROJECT SHIELDS -->
|
||||
<!--
|
||||
*** I'm using markdown "reference style" links for readability.
|
||||
*** Reference links are enclosed in brackets [ ] instead of parentheses ( ).
|
||||
*** See the bottom of this document for the declaration of the reference variables
|
||||
*** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use.
|
||||
*** https://www.markdownguide.org/basic-syntax/#reference-style-links
|
||||
-->
|
||||
<!-- [![Contributors][contributors-shield]][contributors-url] -->
|
||||
<!-- [![Forks][forks-shield]][forks-url] -->
|
||||
<!-- [![Stargazers][stars-shield]][stars-url] -->
|
||||
<!-- [![Issues][issues-shield]][issues-url] -->
|
||||
<!-- [![MIT License][license-shield]][license-url] -->
|
||||
<!-- [![LinkedIn][linkedin-shield]][linkedin-url] -->
|
||||
|
||||
|
||||
|
||||
<!-- PROJECT LOGO -->
|
||||
<br />
|
||||
<div align="center">
|
||||
<a href="https://github.com/github_username/repo_name">
|
||||
<img src="assets/logo.png" alt="Logo" width="150" height="100">
|
||||
</a>
|
||||
|
||||
<h3 align="center">chirpdetector</h3>
|
||||
|
||||
<p align="center">
|
||||
An algorithm to detect the chirps of weakly electric fish.
|
||||
<br />
|
||||
<a href="https://github.com/github_username/repo_name"><strong>Explore the docs »</strong></a>
|
||||
<br />
|
||||
<br />
|
||||
<a href="https://github.com/github_username/repo_name">View Demo</a>
|
||||
·
|
||||
<a href="https://github.com/github_username/repo_name/issues">Report Bug</a>
|
||||
·
|
||||
<a href="https://github.com/github_username/repo_name/issues">Request Feature</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Chirps are transient communication singals of many wave-type electric fish. Because they are so fast, detecting them when the recorded signal includes multiple individuals is hard. But to understand if, and what kind of information they transmit in a natural setting, analyzing chirps in multiple freely interacting individual is nessecary. This repository documents an approach to detect these signals on electrode grid recordings with many freely behaving individuals by extracting EOD parameters that change during a chirp and detecting anomalies on them.
|
||||
|
||||
The majority of the code and its tests were part of a lab rotation with the [Neuroethology](https://github.com/bendalab) at the University of Tuebingen. It also contains a [poster](poster_printed/main.pdf) and a more thorough [lab protocol](protocol/main.pdf).
|
||||
|
||||
## The Approach
|
||||
<!-- TABLE OF CONTENTS -->
|
||||
<details>
|
||||
<summary>Table of Contents</summary>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#about-the-project">About The Project</a>
|
||||
<ul>
|
||||
<li><a href="#built-with">Built With</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#getting-started">Getting Started</a>
|
||||
<ul>
|
||||
<li><a href="#prerequisites">Prerequisites</a></li>
|
||||
<li><a href="#installation">Installation</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#usage">Usage</a></li>
|
||||
<li><a href="#roadmap">Roadmap</a></li>
|
||||
<li><a href="#contributing">Contributing</a></li>
|
||||
<li><a href="#license">License</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
<li><a href="#acknowledgments">Acknowledgments</a></li>
|
||||
</ol>
|
||||
</details>
|
||||
|
||||
To detect chirps, we extract some features of the raw signal using frequency traces that were computed beforehand using the [wavetracker](https://github.com/tillraab/wavetracker). For a frequency band of a single fish, we filter the signal using a bandpass filter, extract the instantaneous frequency, the envelelope and the envelope of a frequency band slightly above the fundamental frequency of the fish. We then transform those features using various filters and detect the peaks on them. Peaks on all three features are chirps. In an ideal world, the result looks like this:
|
||||
|
||||

|
||||
|
||||
We always use the strongest electrode relative to the fish of interest. By that, we include the spatial demensions to increase our detection performance, if fish are spaced sufficiently apart. The full algorithm is thoroughly explained in the lab protocol. All parameters can be tuned using a `yaml` config file. While this approach excels in assigning detected chirps to the currect fish, the actial chirp detection is not that reliable. If peak detection thresholds are set for each feature manually, the detector can become quite reliable but performance will be sensible to changes in the recording setup. But if features are normalized to generalize the detector, noise often introduces false detections, especially during amplitude breakdowns. Below is an example of a recording with false detections:
|
||||
<!-- ABOUT THE PROJECT -->
|
||||
## About The Project
|
||||
|
||||

|
||||
[![Product Name Screen Shot][product-screenshot]](https://example.com)
|
||||
|
||||
## Open questions
|
||||
Here's a blank template to get started: To avoid retyping too much info. Do a search and replace with your text editor for the following: `github_username`, `repo_name`, `twitter_handle`, `linkedin_username`, `email_client`, `email`, `project_title`, `project_description`
|
||||
|
||||
One of the three features we exract to detect peaks is the instantaneous frequency of the baseline EOD of a single fish. To do that, we apply a narrow bandpass filter (+- 5 Hz) around the fundamental frequency in a single 5 second window. If when the fish chirps, its real frequency should increase beyond the filter cutoff frequencies and we should see a slight increase in the instantaneous frequency. But sometimes, the frequency decreases or does not change at all.
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
To understand this, we simulated chirps while changing some parameters of the chirp, notably the height, width, kurtosis, contrast and EOD phase in which the chirp is produced. What we find, is that all parameters that change the integral of the chirp frequency evolution (i.e. the height, width, and kurtosis) occasionally result in negative instantaneous frequencies in the narrow filtered EOD. Specifically, if the integral increases, the instantaneous frequency increases as well, until an integer + 0.57 is reached. At this point, the frequency flips around the x axis and gradually becomes positive again. This is illustrated in the figure below for the chirp width. The integral of the zero-shifted instantaneous frequency is indicated as the title of each subplot. 1.57 would be $\pi/2$ but how this relates to the observed pattern is not clear yet. What becomes clear from the waveform below is that the point at which the EOD flips is the point where the amplitude of just the filtered signal breaks down to 0.
|
||||
|
||||

|
||||
|
||||
To explore the other parameters, there is a jupyter notebook in the `notebooks` [here](chirp_instantaneous_freq/chirp_exploration.ipynb).
|
||||
<!-- ### Built With -->
|
||||
|
||||
<!-- # Chirp detection - GP2023 -->
|
||||
<!-- ## Git-Repository and commands -->
|
||||
<!-- * [![Next][Next.js]][Next-url] -->
|
||||
<!-- * [![React][React.js]][React-url] -->
|
||||
<!-- * [![Vue][Vue.js]][Vue-url] -->
|
||||
<!-- * [![Angular][Angular.io]][Angular-url] -->
|
||||
<!-- * [![Svelte][Svelte.dev]][Svelte-url] -->
|
||||
<!-- * [![Laravel][Laravel.com]][Laravel-url] -->
|
||||
<!-- * [![Bootstrap][Bootstrap.com]][Bootstrap-url] -->
|
||||
<!-- * [![JQuery][JQuery.com]][JQuery-url] -->
|
||||
|
||||
<!-- - Go to the [Bendalab Git-Server](https://whale.am28.uni-tuebingen.de/git/) (https://whale.am28.uni-tuebingen.de/git/) -->
|
||||
<!-- - Create your own account (and tell me ;D) -->
|
||||
<!-- * I'll invite you the repository -->
|
||||
<!-- - Clone the repository -->
|
||||
<!-- - -->
|
||||
<!-- ```sh -->
|
||||
<!-- git clone https://whale.am28.uni-tuebingen.de/git/raab/GP2023_chirp_detection.git -->
|
||||
<!-- ``` -->
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
<!-- ## Basic git commands -->
|
||||
|
||||
<!-- - pull changes in git -->
|
||||
<!-- ```shell -->
|
||||
<!-- git pull origin <branch> -->
|
||||
<!-- ``` -->
|
||||
<!-- - commit chances -->
|
||||
<!-- ```shell -->
|
||||
<!-- git commit -m '<explaination>' file # commit one file -->
|
||||
<!-- git commit -a -m '<explaination>' # commit all files -->
|
||||
<!-- ``` -->
|
||||
<!-- - push commits -->
|
||||
<!-- ```shell -->
|
||||
<!-- git push origin <branch> -->
|
||||
<!-- ``` -->
|
||||
|
||||
<!-- ## Branches -->
|
||||
<!-- Use branches to work on specific topics (e.g. 'algorithm', 'analysis', 'writing', ore even more specific ones) and merge -->
|
||||
<!-- them into Master-Branch when it works are up to your expectations. -->
|
||||
<!-- GETTING STARTED -->
|
||||
## Getting Started
|
||||
|
||||
<!-- The "master" branch should always contain a working/correct version of your project. -->
|
||||
This is an example of how you may give instructions on setting up your project locally.
|
||||
To get a local copy up and running follow these simple example steps.
|
||||
|
||||
<!-- - Create/change into branches -->
|
||||
<!-- ```shell -->
|
||||
<!-- # list all branches (highlight active branch) -->
|
||||
<!-- git banch -a -->
|
||||
<!-- # switch into existing -->
|
||||
<!-- git checkout <existing branch> -->
|
||||
<!-- # switch into new branch -->
|
||||
<!-- git checkout master -->
|
||||
<!-- git checkout -b <new branch> -->
|
||||
<!-- ``` -->
|
||||
<!-- ### Prerequisites -->
|
||||
|
||||
<!-- This is an example of how to list things you need to use the software and how to install them. -->
|
||||
<!-- * npm -->
|
||||
<!-- ```sh -->
|
||||
<!-- npm install npm@latest -g -->
|
||||
<!-- ``` -->
|
||||
|
||||
<!-- - Re-merging with master branch -->
|
||||
<!-- 1) get current version of master and implement it into branch -->
|
||||
<!-- ```shell -->
|
||||
<!-- git checkout master -->
|
||||
<!-- git pull origin master -->
|
||||
<!-- git checkout <branch> -->
|
||||
<!-- git rebase master -->
|
||||
<!-- ``` -->
|
||||
<!-- This resets you branch to the fork-point, executes all commits of the current master before adding the commits of you -->
|
||||
<!-- branch. You may have to resolve potential conflicts. Afterwards commit the corrected version and push it to your branch. -->
|
||||
### Installation
|
||||
|
||||
1. Get a free API Key at [https://example.com](https://example.com)
|
||||
2. Clone the repo
|
||||
```sh
|
||||
git clone https://github.com/github_username/repo_name.git
|
||||
```
|
||||
3. Install NPM packages
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
4. Enter your API in `config.js`
|
||||
```js
|
||||
const API_KEY = 'ENTER YOUR API';
|
||||
```
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
<!-- USAGE EXAMPLES -->
|
||||
## Usage
|
||||
|
||||
Use this space to show useful examples of how a project can be used. Additional screenshots, code examples and demos work well in this space. You may also link to more resources.
|
||||
|
||||
_For more examples, please refer to the [Documentation](https://example.com)_
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
<!-- ROADMAP -->
|
||||
## Roadmap
|
||||
|
||||
- [ ] Feature 1
|
||||
- [ ] Feature 2
|
||||
- [ ] Feature 3
|
||||
- [ ] Nested Feature
|
||||
|
||||
See the [open issues](https://github.com/github_username/repo_name/issues) for a full list of proposed features (and known issues).
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
<!-- <!-1- CONTRIBUTING -1-> -->
|
||||
<!-- ## Contributing -->
|
||||
|
||||
<!-- Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. -->
|
||||
|
||||
<!-- If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". -->
|
||||
<!-- Don't forget to give the project a star! Thanks again! -->
|
||||
|
||||
<!-- 1. Fork the Project -->
|
||||
<!-- 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) -->
|
||||
<!-- 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) -->
|
||||
<!-- 4. Push to the Branch (`git push origin feature/AmazingFeature`) -->
|
||||
<!-- 5. Open a Pull Request -->
|
||||
|
||||
<!-- <p align="right">(<a href="#readme-top">back to top</a>)</p> -->
|
||||
|
||||
|
||||
|
||||
<!-- <!-1- LICENSE -1-> -->
|
||||
<!-- ## License -->
|
||||
|
||||
<!-- Distributed under the MIT License. See `LICENSE.txt` for more information. -->
|
||||
|
||||
<!-- <p align="right">(<a href="#readme-top">back to top</a>)</p> -->
|
||||
|
||||
|
||||
|
||||
<!-- CONTACT -->
|
||||
## Contact
|
||||
|
||||
Your Name - [@twitter_handle](https://twitter.com/twitter_handle) - email@email_client.com
|
||||
|
||||
Project Link: [https://github.com/github_username/repo_name](https://github.com/github_username/repo_name)
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
<!-- ACKNOWLEDGMENTS -->
|
||||
## Acknowledgments
|
||||
|
||||
* []()
|
||||
* []()
|
||||
* []()
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
<!-- MARKDOWN LINKS & IMAGES -->
|
||||
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
|
||||
[contributors-shield]: https://img.shields.io/github/contributors/github_username/repo_name.svg?style=for-the-badge
|
||||
[contributors-url]: https://github.com/github_username/repo_name/graphs/contributors
|
||||
[forks-shield]: https://img.shields.io/github/forks/github_username/repo_name.svg?style=for-the-badge
|
||||
[forks-url]: https://github.com/github_username/repo_name/network/members
|
||||
[stars-shield]: https://img.shields.io/github/stars/github_username/repo_name.svg?style=for-the-badge
|
||||
[stars-url]: https://github.com/github_username/repo_name/stargazers
|
||||
[issues-shield]: https://img.shields.io/github/issues/github_username/repo_name.svg?style=for-the-badge
|
||||
[issues-url]: https://github.com/github_username/repo_name/issues
|
||||
[license-shield]: https://img.shields.io/github/license/github_username/repo_name.svg?style=for-the-badge
|
||||
[license-url]: https://github.com/github_username/repo_name/blob/master/LICENSE.txt
|
||||
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
|
||||
[linkedin-url]: https://linkedin.com/in/linkedin_username
|
||||
[product-screenshot]: images/screenshot.png
|
||||
[Next.js]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white
|
||||
[Next-url]: https://nextjs.org/
|
||||
[React.js]: https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB
|
||||
[React-url]: https://reactjs.org/
|
||||
[Vue.js]: https://img.shields.io/badge/Vue.js-35495E?style=for-the-badge&logo=vuedotjs&logoColor=4FC08D
|
||||
[Vue-url]: https://vuejs.org/
|
||||
[Angular.io]: https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular&logoColor=white
|
||||
[Angular-url]: https://angular.io/
|
||||
[Svelte.dev]: https://img.shields.io/badge/Svelte-4A4A55?style=for-the-badge&logo=svelte&logoColor=FF3E00
|
||||
[Svelte-url]: https://svelte.dev/
|
||||
[Laravel.com]: https://img.shields.io/badge/Laravel-FF2D20?style=for-the-badge&logo=laravel&logoColor=white
|
||||
[Laravel-url]: https://laravel.com
|
||||
[Bootstrap.com]: https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white
|
||||
[Bootstrap-url]: https://getbootstrap.com
|
||||
[JQuery.com]: https://img.shields.io/badge/jQuery-0769AD?style=for-the-badge&logo=jquery&logoColor=white
|
||||
[JQuery-url]: https://jquery.com
|
||||
|
||||
<!-- 2) Update master branch master -->
|
||||
<!-- - correct way: Create -->
|
||||
<!-- ```shell -->
|
||||
<!-- git checkout master -->
|
||||
<!-- git merge <branch> -->
|
||||
<!-- git push origin master -->
|
||||
<!-- ``` -->
|
||||
|
64
README1.md
Normal file
@ -0,0 +1,64 @@
|
||||
# Chirp detection - GP2023
|
||||
## Git-Repository and commands
|
||||
|
||||
- Go to the [Bendalab Git-Server](https://whale.am28.uni-tuebingen.de/git/) (https://whale.am28.uni-tuebingen.de/git/)
|
||||
- Create your own account (and tell me ;D)
|
||||
* I'll invite you the repository
|
||||
- Clone the repository
|
||||
-
|
||||
```sh
|
||||
git clone https://whale.am28.uni-tuebingen.de/git/raab/GP2023_chirp_detection.git
|
||||
```
|
||||
|
||||
## Basic git commands
|
||||
|
||||
- pull changes in git
|
||||
```shell
|
||||
git pull origin <branch>
|
||||
```
|
||||
- commit chances
|
||||
```shell
|
||||
git commit -m '<explaination>' file # commit one file
|
||||
git commit -a -m '<explaination>' # commit all files
|
||||
```
|
||||
- push commits
|
||||
```shell
|
||||
git push origin <branch>
|
||||
```
|
||||
|
||||
## Branches
|
||||
Use branches to work on specific topics (e.g. 'algorithm', 'analysis', 'writing', ore even more specific ones) and merge
|
||||
them into Master-Branch when it works are up to your expectations.
|
||||
|
||||
The "master" branch should always contain a working/correct version of your project.
|
||||
|
||||
- Create/change into branches
|
||||
```shell
|
||||
# list all branches (highlight active branch)
|
||||
git banch -a
|
||||
# switch into existing
|
||||
git checkout <existing branch>
|
||||
# switch into new branch
|
||||
git checkout master
|
||||
git checkout -b <new branch>
|
||||
```
|
||||
|
||||
|
||||
- Re-merging with master branch
|
||||
1) get current version of master and implement it into branch
|
||||
```shell
|
||||
git checkout master
|
||||
git pull origin master
|
||||
git checkout <branch>
|
||||
git rebase master
|
||||
```
|
||||
This resets you branch to the fork-point, executes all commits of the current master before adding the commits of you
|
||||
branch. You may have to resolve potential conflicts. Afterwards commit the corrected version and push it to your branch.
|
||||
|
||||
2) Update master branch master
|
||||
- correct way: Create
|
||||
```shell
|
||||
git checkout master
|
||||
git merge <branch>
|
||||
git push origin master
|
||||
```
|
14906
assets/bad_.svg
Before Width: | Height: | Size: 448 KiB |
14924
assets/chirpsize.svg
Before Width: | Height: | Size: 371 KiB |
15704
assets/good_.svg
Before Width: | Height: | Size: 466 KiB |
14910
assets/width.svg
Before Width: | Height: | Size: 370 KiB |
@ -1,241 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# # Why can the instantaneous frequency of a band-pass filtered chirp recording go down ...
|
||||
# ... if a chirp is an up-modulation of the frequency?
|
||||
#
|
||||
# This is the question we try to answer in this notebook
|
||||
|
||||
# In[14]:
|
||||
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import thunderfish.fakefish as ff
|
||||
from filters import instantaneous_frequency, bandpass_filter
|
||||
#get_ipython().run_line_magic('matplotlib', 'qt')
|
||||
|
||||
# parameters that stay the same
|
||||
samplerate = 20000
|
||||
duration = 0.2
|
||||
chirp_freq = 5
|
||||
smooth = 3
|
||||
|
||||
|
||||
# In[7]:
|
||||
|
||||
|
||||
def make_chirp(eodf, size, width, kurtosis, contrast, phase0):
|
||||
|
||||
chirp_trace, amp = ff.chirps(
|
||||
eodf = eodf,
|
||||
samplerate = samplerate,
|
||||
duration = duration,
|
||||
chirp_freq = chirp_freq,
|
||||
chirp_size = size,
|
||||
chirp_width = width,
|
||||
chirp_kurtosis = kurtosis,
|
||||
chirp_contrast = contrast,
|
||||
)
|
||||
|
||||
chirp = ff.wavefish_eods(
|
||||
fish = 'Alepto',
|
||||
frequency = chirp_trace,
|
||||
samplerate = samplerate,
|
||||
duration = duration,
|
||||
phase0 = phase0,
|
||||
noise_std = 0,
|
||||
)
|
||||
|
||||
chirp *= amp
|
||||
|
||||
return chirp_trace, chirp
|
||||
|
||||
def filtered_chirp(eodf, size, width, kurtosis, contrast, phase0):
|
||||
|
||||
time = np.arange(0, duration, 1/samplerate)
|
||||
chirp_trace, chirp = make_chirp(
|
||||
eodf = eodf,
|
||||
size = size,
|
||||
width = width,
|
||||
kurtosis = kurtosis,
|
||||
contrast = contrast,
|
||||
phase0 = phase0,
|
||||
)
|
||||
|
||||
# apply filters
|
||||
narrow_filtered = bandpass_filter(chirp, samplerate, eodf-10, eodf+10)
|
||||
narrow_freqtime, narrow_freq = instantaneous_frequency(narrow_filtered, samplerate, smooth)
|
||||
broad_filtered = bandpass_filter(chirp, samplerate, eodf-300, eodf+300)
|
||||
broad_freqtime, broad_freq = instantaneous_frequency(broad_filtered, samplerate, smooth)
|
||||
|
||||
original = (time, chirp_trace, chirp)
|
||||
broad = (broad_freqtime, broad_freq, broad_filtered)
|
||||
narrow = (narrow_freqtime, narrow_freq, narrow_filtered)
|
||||
|
||||
return original, broad, narrow
|
||||
|
||||
def plot(original, broad, narrow, axs):
|
||||
|
||||
axs[0].plot(original[0], original[1], label='chirp trace')
|
||||
axs[0].plot(broad[0], broad[1], label='broad filtered')
|
||||
axs[0].plot(narrow[0], narrow[1], label='narrow filtered')
|
||||
axs[1].plot(original[0], original[2], label='unfiltered')
|
||||
axs[1].plot(original[0], broad[2], label='broad filtered')
|
||||
axs[1].plot(original[0], narrow[2], label='narrow filtered')
|
||||
|
||||
original, broad, narrow = filtered_chirp(600, 100, 0.02, 1, 0.1, 0)
|
||||
fig, axs = plt.subplots(2, 1, figsize=(10, 5), sharex=True)
|
||||
plot(original, broad, narrow, axs)
|
||||
fig.align_labels()
|
||||
plt.show()
|
||||
|
||||
|
||||
# ## Chirp size
|
||||
# now that we have established an easy way to simulate and plot the chirps, lets change the chirp size and see how the narrow-filtered instantaneous frequency changes.
|
||||
|
||||
# In[17]:
|
||||
|
||||
|
||||
sizes = np.arange(10, 600, 20)[:10]
|
||||
fig, axs = plt.subplots(2, len(sizes), figsize=(20, 5), sharex=True, sharey='row')
|
||||
integrals = []
|
||||
|
||||
for i, size in enumerate(sizes):
|
||||
original, broad, narrow = filtered_chirp(600, size, 0.02, 1, 0.1, 0)
|
||||
|
||||
integral = np.sum(original[1]-600)/(20000)
|
||||
integrals.append(integral)
|
||||
|
||||
plot(original, broad, narrow, axs[:, i])
|
||||
axs[:, i][0].set_xlim(0.06, 0.14)
|
||||
axs[0, i].set_title(np.round(integral, 3))
|
||||
print(f'size {size} Hz; Integral {np.round(integral,3)}')
|
||||
|
||||
fig.legend(handles=axs[0,0].get_lines(), loc='upper center', ncol=3)
|
||||
axs[0,0].set_ylabel('frequency [Hz]')
|
||||
axs[1,0].set_ylabel('amplitude [a.u.]')
|
||||
fig.supxlabel('time [s]')
|
||||
fig.align_labels()
|
||||
plt.show()
|
||||
|
||||
|
||||
# ## Chirp width
|
||||
|
||||
# In[9]:
|
||||
|
||||
|
||||
widths = np.arange(0.02, 0.08, 0.005)
|
||||
fig, axs = plt.subplots(2, len(widths), figsize=(10, 5), sharex=True, sharey='row')
|
||||
integrals = []
|
||||
|
||||
for i, width in enumerate(widths):
|
||||
if i > 9:
|
||||
break
|
||||
|
||||
original, broad, narrow = filtered_chirp(600, 100, width, 1, 0.1, 0)
|
||||
|
||||
integral = np.sum(original[1]-600)/(20000)
|
||||
|
||||
plot(original, broad, narrow, axs[:, i])
|
||||
axs[:, i][0].set_xlim(0.06, 0.14)
|
||||
axs[0, i].set_title(f'width {np.round(width, 2)} s')
|
||||
print(f'width {width} s; Integral {np.round(integral, 3)}')
|
||||
|
||||
fig.legend(handles=axs[0,0].get_lines(), loc='upper center', ncol=3)
|
||||
axs[0,0].set_ylabel('frequency [Hz]')
|
||||
axs[1,0].set_ylabel('amplitude [a.u.]')
|
||||
fig.supxlabel('time [s]')
|
||||
fig.align_labels()
|
||||
plt.show()
|
||||
|
||||
|
||||
# ## Chirp kurtosis
|
||||
|
||||
# In[10]:
|
||||
|
||||
|
||||
kurtosiss = np.arange(0, 20, 1.6)
|
||||
fig, axs = plt.subplots(2, len(kurtosiss), figsize=(10, 5), sharex=True, sharey='row')
|
||||
integrals = []
|
||||
|
||||
for i, kurtosis in enumerate(kurtosiss):
|
||||
|
||||
original, broad, narrow = filtered_chirp(600, 100, 0.02, kurtosis, 0.1, 0)
|
||||
|
||||
integral = np.sum(original[1]-600)/(20000)
|
||||
|
||||
plot(original, broad, narrow, axs[:, i])
|
||||
axs[:, i][0].set_xlim(0.06, 0.14)
|
||||
axs[0, i].set_title(f'kurt {np.round(kurtosis, 2)}')
|
||||
print(f'kurt {kurtosis}; Integral {np.round(integral, 3)}')
|
||||
|
||||
fig.legend(handles=axs[0,0].get_lines(), loc='upper center', ncol=3)
|
||||
axs[0,0].set_ylabel('frequency [Hz]')
|
||||
axs[1,0].set_ylabel('amplitude [a.u.]')
|
||||
fig.supxlabel('time [s]')
|
||||
fig.align_labels()
|
||||
plt.show()
|
||||
|
||||
|
||||
# ## Chirp contrast
|
||||
|
||||
# In[11]:
|
||||
|
||||
|
||||
contrasts = np.arange(0.0, 1.1, 0.1)
|
||||
fig, axs = plt.subplots(2, len(sizes), figsize=(10, 5), sharex=True, sharey='row')
|
||||
integrals = []
|
||||
|
||||
for i, contrast in enumerate(contrasts):
|
||||
if i > 9:
|
||||
break
|
||||
original, broad, narrow = filtered_chirp(600, 100, 0.02, 1, contrast, 0)
|
||||
|
||||
integral = np.trapz(original[2], original[0])
|
||||
integrals.append(integral)
|
||||
|
||||
plot(original, broad, narrow, axs[:, i])
|
||||
axs[:, i][0].set_xlim(0.06, 0.14)
|
||||
axs[0, i].set_title(f'contr {np.round(contrast, 2)}')
|
||||
|
||||
fig.legend(handles=axs[0,0].get_lines(), loc='upper center', ncol=3)
|
||||
axs[0,0].set_ylabel('frequency [Hz]')
|
||||
axs[1,0].set_ylabel('amplitude [a.u.]')
|
||||
fig.supxlabel('time [s]')
|
||||
fig.align_labels()
|
||||
plt.show()
|
||||
|
||||
|
||||
# ## Chirp phase
|
||||
|
||||
# In[12]:
|
||||
|
||||
|
||||
phases = np.arange(0.0, 2 * np.pi, 0.2)
|
||||
fig, axs = plt.subplots(2, len(sizes), figsize=(10, 5), sharex=True, sharey='row')
|
||||
integrals = []
|
||||
for i, phase in enumerate(phases):
|
||||
if i > 9:
|
||||
break
|
||||
|
||||
original, broad, narrow = filtered_chirp(600, 100, 0.02, 1, 0.1, phase)
|
||||
|
||||
integral = np.trapz(original[2], original[0])
|
||||
integrals.append(integral)
|
||||
|
||||
plot(original, broad, narrow, axs[:, i])
|
||||
axs[:, i][0].set_xlim(0.06, 0.14)
|
||||
axs[0, i].set_title(f'phase {np.round(phase, 2)}')
|
||||
|
||||
|
||||
fig.legend(handles=axs[0,0].get_lines(), loc='upper center', ncol=3)
|
||||
axs[0,0].set_ylabel('frequency [Hz]')
|
||||
axs[1,0].set_ylabel('amplitude [a.u.]')
|
||||
fig.supxlabel('time [s]')
|
||||
fig.align_labels()
|
||||
plt.show()
|
||||
|
||||
|
||||
# These experiments show, that the narrow filtered instantaneous freuqency only switches its sign, when the integral of the instantaneous frequency (that was used to make the signal)
|
||||
# changes. Specifically, when the instantaneous frequency is 0.57, 1.57, 2.57 etc., the sign swithes.
|
@ -1,200 +0,0 @@
|
||||
from scipy.signal import butter, sosfiltfilt
|
||||
from scipy.ndimage import gaussian_filter1d
|
||||
import numpy as np
|
||||
|
||||
|
||||
def instantaneous_frequency(
|
||||
signal: np.ndarray,
|
||||
samplerate: int,
|
||||
smoothing_window: int,
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""
|
||||
Compute the instantaneous frequency of a signal that is approximately
|
||||
sinusoidal and symmetric around 0.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
signal : np.ndarray
|
||||
Signal to compute the instantaneous frequency from.
|
||||
samplerate : int
|
||||
Samplerate of the signal.
|
||||
smoothing_window : int
|
||||
Window size for the gaussian filter.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[np.ndarray, np.ndarray]
|
||||
|
||||
"""
|
||||
# calculate instantaneous frequency with zero crossings
|
||||
roll_signal = np.roll(signal, shift=1)
|
||||
time_signal = np.arange(len(signal)) / samplerate
|
||||
period_index = np.arange(len(signal))[(roll_signal < 0) & (signal >= 0)][
|
||||
1:-1
|
||||
]
|
||||
|
||||
upper_bound = np.abs(signal[period_index])
|
||||
lower_bound = np.abs(signal[period_index - 1])
|
||||
upper_time = np.abs(time_signal[period_index])
|
||||
lower_time = np.abs(time_signal[period_index - 1])
|
||||
|
||||
# create ratio
|
||||
lower_ratio = lower_bound / (lower_bound + upper_bound)
|
||||
|
||||
# appy to time delta
|
||||
time_delta = upper_time - lower_time
|
||||
true_zero = lower_time + lower_ratio * time_delta
|
||||
|
||||
# create new time array
|
||||
instantaneous_frequency_time = true_zero[:-1] + 0.5 * np.diff(true_zero)
|
||||
|
||||
# compute frequency
|
||||
instantaneous_frequency = gaussian_filter1d(
|
||||
1 / np.diff(true_zero), smoothing_window
|
||||
)
|
||||
|
||||
return instantaneous_frequency_time, instantaneous_frequency
|
||||
|
||||
|
||||
def inst_freq(signal, fs):
|
||||
"""
|
||||
Computes the instantaneous frequency of a periodic signal using zero-crossings.
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
signal : array-like
|
||||
The input signal.
|
||||
fs : float
|
||||
The sampling frequency of the input signal.
|
||||
|
||||
Returns:
|
||||
--------
|
||||
freq : array-like
|
||||
The instantaneous frequency of the input signal.
|
||||
"""
|
||||
# Compute the sign of the signal
|
||||
sign = np.sign(signal)
|
||||
|
||||
# Compute the crossings of the sign signal with a zero line
|
||||
crossings = np.where(np.diff(sign))[0]
|
||||
|
||||
# Compute the time differences between zero crossings
|
||||
dt = np.diff(crossings) / fs
|
||||
|
||||
# Compute the instantaneous frequency as the reciprocal of the time differences
|
||||
freq = 1 / dt
|
||||
|
||||
# Gaussian filter the signal
|
||||
freq = gaussian_filter1d(freq, 10)
|
||||
|
||||
# Pad the frequency vector with zeros to match the length of the input signal
|
||||
freq = np.pad(freq, (0, len(signal) - len(freq)))
|
||||
|
||||
return freq
|
||||
|
||||
|
||||
def bandpass_filter(
|
||||
signal: np.ndarray,
|
||||
samplerate: float,
|
||||
lowf: float,
|
||||
highf: float,
|
||||
) -> np.ndarray:
|
||||
"""Bandpass filter a signal.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
signal : np.ndarray
|
||||
The data to be filtered
|
||||
rate : float
|
||||
The sampling rate
|
||||
lowf : float
|
||||
The low cutoff frequency
|
||||
highf : float
|
||||
The high cutoff frequency
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
The filtered data
|
||||
"""
|
||||
sos = butter(2, (lowf, highf), "bandpass", fs=samplerate, output="sos")
|
||||
filtered_signal = sosfiltfilt(sos, signal)
|
||||
|
||||
return filtered_signal
|
||||
|
||||
|
||||
def highpass_filter(
|
||||
signal: np.ndarray,
|
||||
samplerate: float,
|
||||
cutoff: float,
|
||||
) -> np.ndarray:
|
||||
"""Highpass filter a signal.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
signal : np.ndarray
|
||||
The data to be filtered
|
||||
rate : float
|
||||
The sampling rate
|
||||
cutoff : float
|
||||
The cutoff frequency
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
The filtered data
|
||||
"""
|
||||
sos = butter(2, cutoff, "highpass", fs=samplerate, output="sos")
|
||||
filtered_signal = sosfiltfilt(sos, signal)
|
||||
|
||||
return filtered_signal
|
||||
|
||||
|
||||
def lowpass_filter(
|
||||
signal: np.ndarray, samplerate: float, cutoff: float
|
||||
) -> np.ndarray:
|
||||
"""Lowpass filter a signal.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : np.ndarray
|
||||
The data to be filtered
|
||||
rate : float
|
||||
The sampling rate
|
||||
cutoff : float
|
||||
The cutoff frequency
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
The filtered data
|
||||
"""
|
||||
sos = butter(2, cutoff, "lowpass", fs=samplerate, output="sos")
|
||||
filtered_signal = sosfiltfilt(sos, signal)
|
||||
|
||||
return filtered_signal
|
||||
|
||||
|
||||
def envelope(
|
||||
signal: np.ndarray, samplerate: float, cutoff_frequency: float
|
||||
) -> np.ndarray:
|
||||
"""Calculate the envelope of a signal using a lowpass filter.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
signal : np.ndarray
|
||||
The signal to calculate the envelope of
|
||||
samplingrate : float
|
||||
The sampling rate of the signal
|
||||
cutoff_frequency : float
|
||||
The cutoff frequency of the lowpass filter
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
The envelope of the signal
|
||||
"""
|
||||
sos = butter(2, cutoff_frequency, "lowpass", fs=samplerate, output="sos")
|
||||
envelope = np.sqrt(2) * sosfiltfilt(sos, np.abs(signal))
|
||||
|
||||
return envelope
|
@ -1,557 +0,0 @@
|
||||
import sys
|
||||
from IPython import embed
|
||||
import thunderfish.powerspectrum as ps
|
||||
import numpy as np
|
||||
|
||||
species_name = dict(
|
||||
Sine="Sinewave",
|
||||
Alepto="Apteronotus leptorhynchus",
|
||||
Arostratus="Apteronotus rostratus",
|
||||
Eigenmannia="Eigenmannia spec.",
|
||||
Sternarchella="Sternarchella terminalis",
|
||||
Sternopygus="Sternopygus dariensis",
|
||||
)
|
||||
"""Translate species ids used by wavefish_harmonics and pulsefish_eodpeaks to full species names.
|
||||
"""
|
||||
|
||||
|
||||
def abbrv_genus(name):
|
||||
"""Abbreviate genus in a species name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: string
|
||||
Full species name of the form 'Genus species subspecies'
|
||||
|
||||
Returns
|
||||
-------
|
||||
name: string
|
||||
The species name with abbreviated genus, i.e. 'G. species subspecies'
|
||||
"""
|
||||
ns = name.split()
|
||||
return ns[0][0] + ". " + " ".join(ns[1:])
|
||||
|
||||
|
||||
# Amplitudes and phases of various wavefish species:
|
||||
|
||||
Sine_harmonics = dict(amplitudes=(1.0,), phases=(0.5 * np.pi,))
|
||||
|
||||
Apteronotus_leptorhynchus_harmonics = dict(
|
||||
amplitudes=(0.90062, 0.15311, 0.072049, 0.012609, 0.011708),
|
||||
phases=(1.3623, 2.3246, 0.9869, 2.6492, -2.6885),
|
||||
)
|
||||
|
||||
Apteronotus_rostratus_harmonics = dict(
|
||||
amplitudes=(
|
||||
0.64707,
|
||||
0.43874,
|
||||
0.063592,
|
||||
0.07379,
|
||||
0.040199,
|
||||
0.023073,
|
||||
0.0097678,
|
||||
),
|
||||
phases=(2.2988, 0.78876, -1.316, 2.2416, 2.0413, 1.1022, -2.0513),
|
||||
)
|
||||
|
||||
Eigenmannia_harmonics = dict(
|
||||
amplitudes=(1.0087, 0.23201, 0.060524, 0.020175, 0.010087, 0.0080699),
|
||||
phases=(1.3414, 1.3228, 2.9242, 2.8157, 2.6871, -2.8415),
|
||||
)
|
||||
|
||||
Sternarchella_terminalis_harmonics = dict(
|
||||
amplitudes=(
|
||||
0.11457,
|
||||
0.4401,
|
||||
0.41055,
|
||||
0.20132,
|
||||
0.061364,
|
||||
0.011389,
|
||||
0.0057985,
|
||||
),
|
||||
phases=(-2.7106, 2.4472, 1.6829, 0.79085, 0.119, -0.82355, -1.9956),
|
||||
)
|
||||
|
||||
Sternopygus_dariensis_harmonics = dict(
|
||||
amplitudes=(
|
||||
0.98843,
|
||||
0.41228,
|
||||
0.047848,
|
||||
0.11048,
|
||||
0.022801,
|
||||
0.030706,
|
||||
0.019018,
|
||||
),
|
||||
phases=(1.4153, 1.3141, 3.1062, -2.3961, -1.9524, 0.54321, 1.6844),
|
||||
)
|
||||
|
||||
wavefish_harmonics = dict(
|
||||
Sine=Sine_harmonics,
|
||||
Alepto=Apteronotus_leptorhynchus_harmonics,
|
||||
Arostratus=Apteronotus_rostratus_harmonics,
|
||||
Eigenmannia=Eigenmannia_harmonics,
|
||||
Sternarchella=Sternarchella_terminalis_harmonics,
|
||||
Sternopygus=Sternopygus_dariensis_harmonics,
|
||||
)
|
||||
"""Amplitudes and phases of EOD waveforms of various species of wave-type electric fish."""
|
||||
|
||||
|
||||
def wavefish_spectrum(fish):
|
||||
"""Amplitudes and phases of a wavefish EOD.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fish: string, dict or tuple of lists/arrays
|
||||
Specify relative amplitudes and phases of the fundamental and its harmonics.
|
||||
If string then take amplitudes and phases from the `wavefish_harmonics` dictionary.
|
||||
If dictionary then take amplitudes and phases from the 'amlitudes' and 'phases' keys.
|
||||
If tuple then the first element is the list of amplitudes and
|
||||
the second one the list of relative phases in radians.
|
||||
|
||||
Returns
|
||||
-------
|
||||
amplitudes: array of floats
|
||||
Amplitudes of the fundamental and its harmonics.
|
||||
phases: array of floats
|
||||
Phases in radians of the fundamental and its harmonics.
|
||||
|
||||
Raises
|
||||
------
|
||||
KeyError:
|
||||
Unknown fish.
|
||||
IndexError:
|
||||
Amplitudes and phases differ in length.
|
||||
"""
|
||||
if isinstance(fish, (tuple, list)):
|
||||
amplitudes = fish[0]
|
||||
phases = fish[1]
|
||||
elif isinstance(fish, dict):
|
||||
amplitudes = fish["amplitudes"]
|
||||
phases = fish["phases"]
|
||||
else:
|
||||
if fish not in wavefish_harmonics:
|
||||
raise KeyError(
|
||||
"unknown wavefish. Choose one of "
|
||||
+ ", ".join(wavefish_harmonics.keys())
|
||||
)
|
||||
amplitudes = wavefish_harmonics[fish]["amplitudes"]
|
||||
phases = wavefish_harmonics[fish]["phases"]
|
||||
if len(amplitudes) != len(phases):
|
||||
raise IndexError("need exactly as many phases as amplitudes")
|
||||
# remove NaNs:
|
||||
for k in reversed(range(len(amplitudes))):
|
||||
if np.isfinite(amplitudes[k]) or np.isfinite(phases[k]):
|
||||
amplitudes = amplitudes[: k + 1]
|
||||
phases = phases[: k + 1]
|
||||
break
|
||||
return amplitudes, phases
|
||||
|
||||
|
||||
def wavefish_eods(
|
||||
fish="Eigenmannia",
|
||||
frequency=100.0,
|
||||
samplerate=44100.0,
|
||||
duration=1.0,
|
||||
phase0=0.0,
|
||||
noise_std=0.05,
|
||||
):
|
||||
"""Simulate EOD waveform of a wave-type fish.
|
||||
|
||||
The waveform is constructed by superimposing sinewaves of integral
|
||||
multiples of the fundamental frequency - the fundamental and its
|
||||
harmonics. The fundamental frequency of the EOD (EODf) is given by
|
||||
`frequency`. With `fish` relative amplitudes and phases of the
|
||||
fundamental and its harmonics are specified.
|
||||
|
||||
The generated waveform is `duration` seconds long and is sampled with
|
||||
`samplerate` Hertz. Gaussian white noise with a standard deviation of
|
||||
`noise_std` is added to the generated waveform.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fish: string, dict or tuple of lists/arrays
|
||||
Specify relative amplitudes and phases of the fundamental and its harmonics.
|
||||
If string then take amplitudes and phases from the `wavefish_harmonics` dictionary.
|
||||
If dictionary then take amplitudes and phases from the 'amlitudes' and 'phases' keys.
|
||||
If tuple then the first element is the list of amplitudes and
|
||||
the second one the list of relative phases in radians.
|
||||
frequency: float or array of floats
|
||||
EOD frequency of the fish in Hertz. Either fixed number or array for
|
||||
time-dependent frequencies.
|
||||
samplerate: float
|
||||
Sampling rate in Hertz.
|
||||
duration: float
|
||||
Duration of the generated data in seconds. Only used if frequency is scalar.
|
||||
phase0: float
|
||||
Phase offset of the EOD waveform in radians.
|
||||
noise_std: float
|
||||
Standard deviation of additive Gaussian white noise.
|
||||
|
||||
Returns
|
||||
-------
|
||||
data: array of floats
|
||||
Generated data of a wave-type fish.
|
||||
|
||||
Raises
|
||||
------
|
||||
KeyError:
|
||||
Unknown fish.
|
||||
IndexError:
|
||||
Amplitudes and phases differ in length.
|
||||
"""
|
||||
# get relative amplitude and phases:
|
||||
amplitudes, phases = wavefish_spectrum(fish)
|
||||
# compute phase:
|
||||
if np.isscalar(frequency):
|
||||
phase = np.arange(0, duration, 1.0 / samplerate)
|
||||
phase *= frequency
|
||||
else:
|
||||
phase = np.cumsum(frequency) / samplerate
|
||||
# generate EOD:
|
||||
data = np.zeros(len(phase))
|
||||
for har, (ampl, phi) in enumerate(zip(amplitudes, phases)):
|
||||
if np.isfinite(ampl) and np.isfinite(phi):
|
||||
data += ampl * np.sin(
|
||||
2 * np.pi * (har + 1) * phase + phi - (har + 1) * phase0
|
||||
)
|
||||
# add noise:
|
||||
data += noise_std * np.random.randn(len(data))
|
||||
return data
|
||||
|
||||
|
||||
def normalize_wavefish(fish, mode="peak"):
|
||||
"""Normalize amplitudes and phases of wave-type EOD waveform.
|
||||
|
||||
The amplitudes and phases of the Fourier components are adjusted
|
||||
such that the resulting EOD waveform has a peak-to-peak amplitude
|
||||
of two and the peak of the waveform is at time zero (mode is set
|
||||
to 'peak') or that the fundamental has an amplitude of one and a
|
||||
phase of 0 (mode is set to 'zero').
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fish: string, dict or tuple of lists/arrays
|
||||
Specify relative amplitudes and phases of the fundamental and its harmonics.
|
||||
If string then take amplitudes and phases from the `wavefish_harmonics` dictionary.
|
||||
If dictionary then take amplitudes and phases from the 'amlitudes' and 'phases' keys.
|
||||
If tuple then the first element is the list of amplitudes and
|
||||
the second one the list of relative phases in radians.
|
||||
mode: 'peak' or 'zero'
|
||||
How to normalize amplitude and phases:
|
||||
- 'peak': normalize waveform to a peak-to-peak amplitude of two
|
||||
and shift it such that its peak is at time zero.
|
||||
- 'zero': scale amplitude of fundamental to one and its phase to zero.
|
||||
|
||||
Returns
|
||||
-------
|
||||
amplitudes: array of floats
|
||||
Adjusted amplitudes of the fundamental and its harmonics.
|
||||
phases: array of floats
|
||||
Adjusted phases in radians of the fundamental and its harmonics.
|
||||
|
||||
"""
|
||||
# get relative amplitude and phases:
|
||||
amplitudes, phases = wavefish_spectrum(fish)
|
||||
if mode == "zero":
|
||||
newamplitudes = np.array(amplitudes) / amplitudes[0]
|
||||
newphases = np.array(
|
||||
[p + (k + 1) * (-phases[0]) for k, p in enumerate(phases)]
|
||||
)
|
||||
newphases %= 2.0 * np.pi
|
||||
newphases[newphases > np.pi] -= 2.0 * np.pi
|
||||
return newamplitudes, newphases
|
||||
else:
|
||||
# generate waveform:
|
||||
eodf = 100.0
|
||||
rate = 100000.0
|
||||
data = wavefish_eods(fish, eodf, rate, 2.0 / eodf, noise_std=0.0)
|
||||
# normalize amplitudes:
|
||||
ampl = 0.5 * (np.max(data) - np.min(data))
|
||||
newamplitudes = np.array(amplitudes) / ampl
|
||||
# shift phases:
|
||||
deltat = np.argmax(data[: int(rate / eodf)]) / rate
|
||||
deltap = 2.0 * np.pi * deltat * eodf
|
||||
newphases = np.array(
|
||||
[p + (k + 1) * deltap for k, p in enumerate(phases)]
|
||||
)
|
||||
newphases %= 2.0 * np.pi
|
||||
newphases[newphases > np.pi] -= 2.0 * np.pi
|
||||
# return:
|
||||
return newamplitudes, newphases
|
||||
|
||||
|
||||
def export_wavefish(fish, name="Unknown_harmonics", file=None):
|
||||
"""Serialize wavefish parameter to python code.
|
||||
|
||||
Add output to the wavefish_harmonics dictionary!
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fish: string, dict or tuple of lists/arrays
|
||||
Specify relative amplitudes and phases of the fundamental and its harmonics.
|
||||
If string then take amplitudes and phases from the `wavefish_harmonics` dictionary.
|
||||
If dictionary then take amplitudes and phases from the 'amlitudes' and 'phases' keys.
|
||||
If tuple then the first element is the list of amplitudes and
|
||||
the second one the list of relative phases in radians.
|
||||
name: string
|
||||
Name of the dictionary to be written.
|
||||
file: string or file or None
|
||||
File name or open file object where to write wavefish dictionary.
|
||||
|
||||
Returns
|
||||
-------
|
||||
fish: dict
|
||||
Dictionary with amplitudes and phases.
|
||||
"""
|
||||
# get relative amplitude and phases:
|
||||
amplitudes, phases = wavefish_spectrum(fish)
|
||||
# write out dictionary:
|
||||
if file is None:
|
||||
file = sys.stdout
|
||||
try:
|
||||
file.write("")
|
||||
closeit = False
|
||||
except AttributeError:
|
||||
file = open(file, "w")
|
||||
closeit = True
|
||||
n = 6
|
||||
file.write(name + " = \\\n")
|
||||
file.write(" dict(amplitudes=(")
|
||||
file.write(", ".join(["%.5g" % a for a in amplitudes[:n]]))
|
||||
for k in range(n, len(amplitudes), n):
|
||||
file.write(",\n")
|
||||
file.write(" " * (9 + 12))
|
||||
file.write(", ".join(["%.5g" % a for a in amplitudes[k : k + n]]))
|
||||
file.write("),\n")
|
||||
file.write(" " * 9 + "phases=(")
|
||||
file.write(", ".join(["%.5g" % p for p in phases[:n]]))
|
||||
for k in range(n, len(phases), n):
|
||||
file.write(",\n")
|
||||
file.write(" " * (9 + 8))
|
||||
file.write(", ".join(["%.5g" % p for p in phases[k : k + n]]))
|
||||
file.write("))\n")
|
||||
if closeit:
|
||||
file.close()
|
||||
# return dictionary:
|
||||
harmonics = dict(amplitudes=amplitudes, phases=phases)
|
||||
return harmonics
|
||||
|
||||
|
||||
def chirps(
|
||||
eodf=100.0,
|
||||
samplerate=44100.0,
|
||||
duration=1.0,
|
||||
chirp_times=[0.5],
|
||||
chirp_size=[100.0],
|
||||
chirp_width=[0.01],
|
||||
chirp_kurtosis=[1.0],
|
||||
chirp_contrast=[0.05],
|
||||
):
|
||||
"""Simulate frequency trace with chirps.
|
||||
|
||||
A chirp is modeled as a Gaussian frequency modulation.
|
||||
The first chirp is placed at 0.5/chirp_freq.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
eodf: float
|
||||
EOD frequency of the fish in Hertz.
|
||||
samplerate: float
|
||||
Sampling rate in Hertz.
|
||||
duration: float
|
||||
Duration of the generated data in seconds.
|
||||
chirp_times: float
|
||||
Timestamps of every single chirp in seconds.
|
||||
chirp_size: list
|
||||
Size of each chirp (maximum frequency increase above eodf) in Hertz.
|
||||
chirp_width: list
|
||||
Width of every single chirp at 10% height in seconds.
|
||||
chirp_kurtosis: list:
|
||||
Shape of every single chirp. =1: Gaussian, >1: more rectangular, <1: more peaked.
|
||||
chirp_contrast: float
|
||||
Maximum amplitude reduction of EOD during every respective chirp.
|
||||
|
||||
Returns
|
||||
-------
|
||||
frequency: array of floats
|
||||
Generated frequency trace that can be passed on to wavefish_eods().
|
||||
amplitude: array of floats
|
||||
Generated amplitude modulation that can be used to multiply the trace generated by
|
||||
wavefish_eods().
|
||||
"""
|
||||
# baseline eod frequency and amplitude modulation:
|
||||
n = len(np.arange(0, duration, 1.0 / samplerate))
|
||||
frequency = eodf * np.ones(n)
|
||||
am = np.ones(n)
|
||||
|
||||
for time, width, size, kurtosis, contrast in zip(
|
||||
chirp_times, chirp_width, chirp_size, chirp_kurtosis, chirp_contrast
|
||||
):
|
||||
# chirp frequency waveform:
|
||||
chirp_t = np.arange(-2.0 * width, 2.0 * width, 1.0 / samplerate)
|
||||
chirp_sig = 0.5 * width / (2.0 * np.log(10.0)) ** (0.5 / kurtosis)
|
||||
gauss = np.exp(-0.5 * ((chirp_t / chirp_sig) ** 2.0) ** kurtosis)
|
||||
|
||||
# add chirps on baseline eodf:
|
||||
index = int(time * samplerate)
|
||||
i0 = index - len(gauss) // 2
|
||||
i1 = i0 + len(gauss)
|
||||
gi0 = 0
|
||||
gi1 = len(gauss)
|
||||
if i0 < 0:
|
||||
gi0 -= i0
|
||||
i0 = 0
|
||||
if i1 >= len(frequency):
|
||||
gi1 -= i1 - len(frequency)
|
||||
i1 = len(frequency)
|
||||
frequency[i0:i1] += size * gauss[gi0:gi1]
|
||||
am[i0:i1] -= contrast * gauss[gi0:gi1]
|
||||
|
||||
return frequency, am
|
||||
|
||||
|
||||
def rises(
|
||||
eodf,
|
||||
samplerate,
|
||||
duration,
|
||||
rise_times,
|
||||
rise_size,
|
||||
rise_tau,
|
||||
decay_tau,
|
||||
):
|
||||
"""Simulate frequency trace with rises.
|
||||
|
||||
A rise is modeled as a double exponential frequency modulation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
eodf: float
|
||||
EOD frequency of the fish in Hertz.
|
||||
samplerate: float
|
||||
Sampling rate in Hertz.
|
||||
duration: float
|
||||
Duration of the generated data in seconds.
|
||||
rise_times: list
|
||||
Timestamp of each of the rises in seconds.
|
||||
rise_size: list
|
||||
Size of the respective rise (frequency increase above eodf) in Hertz.
|
||||
rise_tau: list
|
||||
Time constant of the frequency increase of the respective rise in seconds.
|
||||
decay_tau: list
|
||||
Time constant of the frequency decay of the respective rise in seconds.
|
||||
|
||||
Returns
|
||||
-------
|
||||
data: array of floats
|
||||
Generate frequency trace that can be passed on to wavefish_eods().
|
||||
"""
|
||||
n = len(np.arange(0, duration, 1.0 / samplerate))
|
||||
|
||||
# baseline eod frequency:
|
||||
frequency = eodf * np.ones(n)
|
||||
|
||||
for time, size, riset, decayt in zip(
|
||||
rise_times, rise_size, rise_tau, decay_tau
|
||||
):
|
||||
# rise frequency waveform:
|
||||
rise_t = np.arange(0.0, 5.0 * decayt, 1.0 / samplerate)
|
||||
rise = size * (1.0 - np.exp(-rise_t / riset)) * np.exp(-rise_t / decayt)
|
||||
|
||||
# add rises on baseline eodf:
|
||||
index = int(time * samplerate)
|
||||
if index + len(rise) > len(frequency):
|
||||
rise_index = len(frequency) - index
|
||||
frequency[index : index + rise_index] += rise[:rise_index]
|
||||
break
|
||||
else:
|
||||
frequency[index : index + len(rise)] += rise
|
||||
return frequency
|
||||
|
||||
|
||||
class FishSignal:
|
||||
def __init__(self, samplerate, duration, eodf, nchirps, nrises):
|
||||
time = np.arange(0, duration, 1 / samplerate)
|
||||
chirp_times = np.random.uniform(0, duration, nchirps)
|
||||
rise_times = np.random.uniform(0, duration, nrises)
|
||||
|
||||
# pick random parameters for chirps
|
||||
chirp_size = np.random.uniform(60, 200, nchirps)
|
||||
chirp_width = np.random.uniform(0.01, 0.1, nchirps)
|
||||
chirp_kurtosis = np.random.uniform(1, 1, nchirps)
|
||||
chirp_contrast = np.random.uniform(0.1, 0.5, nchirps)
|
||||
|
||||
# pick random parameters for rises
|
||||
rise_size = np.random.uniform(10, 100, nrises)
|
||||
rise_tau = np.random.uniform(0.5, 1.5, nrises)
|
||||
decay_tau = np.random.uniform(5, 15, nrises)
|
||||
|
||||
# generate frequency trace with chirps
|
||||
chirp_trace, chirp_amp = chirps(
|
||||
eodf=0.0,
|
||||
samplerate=samplerate,
|
||||
duration=duration,
|
||||
chirp_times=chirp_times,
|
||||
chirp_size=chirp_size,
|
||||
chirp_width=chirp_width,
|
||||
chirp_kurtosis=chirp_kurtosis,
|
||||
chirp_contrast=chirp_contrast,
|
||||
)
|
||||
|
||||
# generate frequency trace with rises
|
||||
rise_trace = rises(
|
||||
eodf=0.0,
|
||||
samplerate=samplerate,
|
||||
duration=duration,
|
||||
rise_times=rise_times,
|
||||
rise_size=rise_size,
|
||||
rise_tau=rise_tau,
|
||||
decay_tau=decay_tau,
|
||||
)
|
||||
|
||||
# combine traces to one
|
||||
full_trace = chirp_trace + rise_trace + eodf
|
||||
|
||||
# make the EOD from the frequency trace
|
||||
fish = wavefish_eods(
|
||||
fish="Alepto",
|
||||
frequency=full_trace,
|
||||
samplerate=samplerate,
|
||||
duration=duration,
|
||||
phase0=0.0,
|
||||
noise_std=0.05,
|
||||
)
|
||||
|
||||
signal = fish * chirp_amp
|
||||
|
||||
self.signal = signal
|
||||
self.trace = full_trace
|
||||
self.time = time
|
||||
self.samplerate = samplerate
|
||||
self.eodf = eodf
|
||||
|
||||
def visualize(self):
|
||||
spec, freqs, spectime = ps.spectrogram(
|
||||
data=self.signal,
|
||||
ratetime=self.samplerate,
|
||||
freq_resolution=0.5,
|
||||
overlap_frac=0.5,
|
||||
)
|
||||
|
||||
fig, (ax1, ax2) = plt.subplots(2, 1, height_ratios=[1, 4], sharex=True)
|
||||
|
||||
ax1.plot(self.time, self.signal)
|
||||
ax1.set_ylabel("Amplitude")
|
||||
ax1.set_xlabel("Time (s)")
|
||||
ax1.set_title("EOD signal")
|
||||
|
||||
ax2.imshow(
|
||||
ps.decibel(spec),
|
||||
origin="lower",
|
||||
aspect="auto",
|
||||
extent=[spectime[0], spectime[-1], freqs[0], freqs[-1]],
|
||||
)
|
||||
ax2.set_ylabel("Frequency (Hz)")
|
||||
ax2.set_xlabel("Time (s)")
|
||||
ax2.set_title("Spectrogram")
|
||||
ax2.set_ylim(0, 2000)
|
||||
plt.show()
|
@ -1,411 +0,0 @@
|
||||
import cmocean as cmo
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from cycler import cycler
|
||||
from matplotlib.colors import ListedColormap
|
||||
|
||||
|
||||
def PlotStyle() -> None:
|
||||
class style:
|
||||
# lightcmap = cmocean.tools.lighten(cmocean.cm.haline, 0.8)
|
||||
|
||||
# units
|
||||
cm = 1 / 2.54
|
||||
mm = 1 / 25.4
|
||||
|
||||
# colors
|
||||
black = "#111116"
|
||||
white = "#e0e4f7"
|
||||
gray = "#6c6e7d"
|
||||
blue = "#89b4fa"
|
||||
sapphire = "#74c7ec"
|
||||
sky = "#89dceb"
|
||||
teal = "#94e2d5"
|
||||
green = "#a6e3a1"
|
||||
yellow = "#f9d67f"
|
||||
orange = "#faa472"
|
||||
maroon = "#eb8486"
|
||||
red = "#e0e4f7"
|
||||
purple = "#d89bf7"
|
||||
pink = "#f59edb"
|
||||
lavender = "#b4befe"
|
||||
gblue1 = "#f37588"
|
||||
gblue2 = "#faa472"
|
||||
gblue3 = "#f9d67f"
|
||||
g = "#f3626c"
|
||||
|
||||
@classmethod
|
||||
def lims(cls, track1, track2):
|
||||
"""Helper function to get frequency y axis limits from two
|
||||
fundamental frequency tracks.
|
||||
|
||||
Args:
|
||||
track1 (array): First track
|
||||
track2 (array): Second track
|
||||
start (int): Index for first value to be plotted
|
||||
stop (int): Index for second value to be plotted
|
||||
padding (int): Padding for the upper and lower limit
|
||||
|
||||
Returns:
|
||||
lower (float): lower limit
|
||||
upper (float): upper limit
|
||||
|
||||
"""
|
||||
allfunds_tmp = (
|
||||
np.concatenate(
|
||||
[
|
||||
track1,
|
||||
track2,
|
||||
]
|
||||
)
|
||||
.ravel()
|
||||
.tolist()
|
||||
)
|
||||
lower = np.min(allfunds_tmp)
|
||||
upper = np.max(allfunds_tmp)
|
||||
return lower, upper
|
||||
|
||||
@classmethod
|
||||
def circled_annotation(cls, text, axis, xpos, ypos, padding=0.25):
|
||||
axis.text(
|
||||
xpos,
|
||||
ypos,
|
||||
text,
|
||||
ha="center",
|
||||
va="center",
|
||||
zorder=1000,
|
||||
bbox=dict(
|
||||
boxstyle=f"circle, pad={padding}",
|
||||
fc="white",
|
||||
ec="black",
|
||||
lw=1,
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def fade_cmap(cls, cmap):
|
||||
my_cmap = cmap(np.arange(cmap.N))
|
||||
my_cmap[:, -1] = np.linspace(0, 1, cmap.N)
|
||||
my_cmap = ListedColormap(my_cmap)
|
||||
|
||||
return my_cmap
|
||||
|
||||
@classmethod
|
||||
def hide_ax(cls, ax):
|
||||
ax.xaxis.set_visible(False)
|
||||
plt.setp(ax.spines.values(), visible=False)
|
||||
ax.tick_params(left=False, labelleft=False)
|
||||
ax.patch.set_visible(False)
|
||||
|
||||
@classmethod
|
||||
def hide_xax(cls, ax):
|
||||
ax.xaxis.set_visible(False)
|
||||
ax.spines["bottom"].set_visible(False)
|
||||
|
||||
@classmethod
|
||||
def hide_yax(cls, ax):
|
||||
ax.yaxis.set_visible(False)
|
||||
ax.spines["left"].set_visible(False)
|
||||
|
||||
@classmethod
|
||||
def set_boxplot_color(cls, bp, color):
|
||||
plt.setp(bp["boxes"], color=color)
|
||||
plt.setp(bp["whiskers"], color=white)
|
||||
plt.setp(bp["caps"], color=white)
|
||||
plt.setp(bp["medians"], color=black)
|
||||
|
||||
@classmethod
|
||||
def label_subplots(cls, labels, axes, fig):
|
||||
for axis, label in zip(axes, labels):
|
||||
X = axis.get_position().x0
|
||||
Y = axis.get_position().y1
|
||||
fig.text(X, Y, label, weight="bold")
|
||||
|
||||
@classmethod
|
||||
def letter_subplots(
|
||||
cls, axes=None, letters=None, xoffset=-0.1, yoffset=1.0, **kwargs
|
||||
):
|
||||
"""Add letters to the corners of subplots (panels). By default each axis is
|
||||
given an uppercase bold letter label placed in the upper-left corner.
|
||||
Args
|
||||
axes : list of pyplot ax objects. default plt.gcf().axes.
|
||||
letters : list of strings to use as labels, default ["A", "B", "C", ...]
|
||||
xoffset, yoffset : positions of each label relative to plot frame
|
||||
(default -0.1,1.0 = upper left margin). Can also be a list of
|
||||
offsets, in which case it should be the same length as the number of
|
||||
axes.
|
||||
Other keyword arguments will be passed to annotate() when panel letters
|
||||
are added.
|
||||
Returns:
|
||||
list of strings for each label added to the axes
|
||||
Examples:
|
||||
Defaults:
|
||||
>>> fig, axes = plt.subplots(1,3)
|
||||
>>> letter_subplots() # boldfaced A, B, C
|
||||
|
||||
Common labeling schemes inferred from the first letter:
|
||||
>>> fig, axes = plt.subplots(1,4)
|
||||
# panels labeled (a), (b), (c), (d)
|
||||
>>> letter_subplots(letters='(a)')
|
||||
Fully custom lettering:
|
||||
>>> fig, axes = plt.subplots(2,1)
|
||||
>>> letter_subplots(axes, letters=['(a.1)', '(b.2)'], fontweight='normal')
|
||||
Per-axis offsets:
|
||||
>>> fig, axes = plt.subplots(1,2)
|
||||
>>> letter_subplots(axes, xoffset=[-0.1, -0.15])
|
||||
|
||||
Matrix of axes:
|
||||
>>> fig, axes = plt.subplots(2,2, sharex=True, sharey=True)
|
||||
# fig.axes is a list when axes is a 2x2 matrix
|
||||
>>> letter_subplots(fig.axes)
|
||||
"""
|
||||
|
||||
# get axes:
|
||||
if axes is None:
|
||||
axes = plt.gcf().axes
|
||||
# handle single axes:
|
||||
try:
|
||||
iter(axes)
|
||||
except TypeError:
|
||||
axes = [axes]
|
||||
|
||||
# set up letter defaults (and corresponding fontweight):
|
||||
fontweight = "bold"
|
||||
ulets = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"[: len(axes)])
|
||||
llets = list("abcdefghijklmnopqrstuvwxyz"[: len(axes)])
|
||||
if letters is None or letters == "A":
|
||||
letters = ulets
|
||||
elif letters == "(a)":
|
||||
letters = ["({})".format(lett) for lett in llets]
|
||||
fontweight = "normal"
|
||||
elif letters == "(A)":
|
||||
letters = ["({})".format(lett) for lett in ulets]
|
||||
fontweight = "normal"
|
||||
elif letters in ("lower", "lowercase", "a"):
|
||||
letters = llets
|
||||
|
||||
# make sure there are x and y offsets for each ax in axes:
|
||||
if isinstance(xoffset, (int, float)):
|
||||
xoffset = [xoffset] * len(axes)
|
||||
else:
|
||||
assert len(xoffset) == len(axes)
|
||||
if isinstance(yoffset, (int, float)):
|
||||
yoffset = [yoffset] * len(axes)
|
||||
else:
|
||||
assert len(yoffset) == len(axes)
|
||||
|
||||
# defaults for annotate (kwargs is second so it can overwrite these defaults):
|
||||
my_defaults = dict(
|
||||
fontweight=fontweight,
|
||||
fontsize="large",
|
||||
ha="center",
|
||||
va="center",
|
||||
xycoords="axes fraction",
|
||||
annotation_clip=False,
|
||||
)
|
||||
kwargs = dict(list(my_defaults.items()) + list(kwargs.items()))
|
||||
|
||||
list_txts = []
|
||||
for ax, lbl, xoff, yoff in zip(axes, letters, xoffset, yoffset):
|
||||
t = ax.annotate(lbl, xy=(xoff, yoff), **kwargs)
|
||||
list_txts.append(t)
|
||||
return list_txts
|
||||
|
||||
pass
|
||||
|
||||
# rcparams text setup
|
||||
SMALL_SIZE = 12
|
||||
MEDIUM_SIZE = 14
|
||||
BIGGER_SIZE = 16
|
||||
black = "#111116"
|
||||
white = "#e0e4f7"
|
||||
gray = "#6c6e7d"
|
||||
dark_gray = "#2a2a32"
|
||||
|
||||
# rcparams
|
||||
plt.rc("font", size=MEDIUM_SIZE) # controls default text sizes
|
||||
plt.rc("axes", titlesize=MEDIUM_SIZE) # fontsize of the axes title
|
||||
plt.rc("axes", labelsize=MEDIUM_SIZE) # fontsize of the x and y labels
|
||||
plt.rc("xtick", labelsize=SMALL_SIZE) # fontsize of the tick labels
|
||||
plt.rc("ytick", labelsize=SMALL_SIZE) # fontsize of the tick labels
|
||||
plt.rc("legend", fontsize=SMALL_SIZE) # legend fontsize
|
||||
plt.rc("figure", titlesize=BIGGER_SIZE) # fontsize of the figure title
|
||||
|
||||
plt.rcParams["image.cmap"] = "cmo.thermal"
|
||||
plt.rcParams["axes.xmargin"] = 0.05
|
||||
plt.rcParams["axes.ymargin"] = 0.1
|
||||
plt.rcParams["axes.titlelocation"] = "left"
|
||||
plt.rcParams["axes.titlesize"] = BIGGER_SIZE
|
||||
# plt.rcParams["axes.titlepad"] = -10
|
||||
plt.rcParams["legend.frameon"] = False
|
||||
plt.rcParams["legend.loc"] = "best"
|
||||
plt.rcParams["legend.borderpad"] = 0.4
|
||||
plt.rcParams["legend.facecolor"] = black
|
||||
plt.rcParams["legend.edgecolor"] = black
|
||||
plt.rcParams["legend.framealpha"] = 0.7
|
||||
plt.rcParams["legend.borderaxespad"] = 0.5
|
||||
plt.rcParams["legend.fancybox"] = False
|
||||
|
||||
# # specify the custom font to use
|
||||
# plt.rcParams["font.family"] = "sans-serif"
|
||||
# plt.rcParams["font.sans-serif"] = "Helvetica Now Text"
|
||||
|
||||
# dark mode modifications
|
||||
plt.rcParams["boxplot.flierprops.color"] = white
|
||||
plt.rcParams["boxplot.flierprops.markeredgecolor"] = gray
|
||||
plt.rcParams["boxplot.boxprops.color"] = gray
|
||||
plt.rcParams["boxplot.whiskerprops.color"] = gray
|
||||
plt.rcParams["boxplot.capprops.color"] = gray
|
||||
plt.rcParams["boxplot.medianprops.color"] = black
|
||||
plt.rcParams["text.color"] = white
|
||||
plt.rcParams["axes.facecolor"] = black # axes background color
|
||||
plt.rcParams["axes.edgecolor"] = white # axes edge color
|
||||
# plt.rcParams["axes.grid"] = True # display grid or not
|
||||
# plt.rcParams["axes.grid.axis"] = "y" # which axis the grid is applied to
|
||||
plt.rcParams["axes.labelcolor"] = white
|
||||
plt.rcParams["axes.axisbelow"] = True # draw axis gridlines and ticks:
|
||||
plt.rcParams["axes.spines.left"] = True # display axis spines
|
||||
plt.rcParams["axes.spines.bottom"] = True
|
||||
plt.rcParams["axes.spines.top"] = False
|
||||
plt.rcParams["axes.spines.right"] = False
|
||||
plt.rcParams["axes.prop_cycle"] = cycler(
|
||||
"color",
|
||||
[
|
||||
"#b4befe",
|
||||
"#89b4fa",
|
||||
"#74c7ec",
|
||||
"#89dceb",
|
||||
"#94e2d5",
|
||||
"#a6e3a1",
|
||||
"#f9e2af",
|
||||
"#fab387",
|
||||
"#eba0ac",
|
||||
"#f38ba8",
|
||||
"#cba6f7",
|
||||
"#f5c2e7",
|
||||
],
|
||||
)
|
||||
plt.rcParams["xtick.color"] = white # color of the ticks
|
||||
plt.rcParams["ytick.color"] = white # color of the ticks
|
||||
plt.rcParams["grid.color"] = white # grid color
|
||||
plt.rcParams["figure.facecolor"] = black # figure face color
|
||||
plt.rcParams["figure.edgecolor"] = black # figure edge color
|
||||
plt.rcParams["savefig.facecolor"] = black # figure face color when saving
|
||||
|
||||
return style
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
s = PlotStyle()
|
||||
|
||||
import matplotlib.cbook as cbook
|
||||
import matplotlib.cm as cm
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import PathPatch
|
||||
from matplotlib.path import Path
|
||||
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
|
||||
delta = 0.025
|
||||
x = y = np.arange(-3.0, 3.0, delta)
|
||||
X, Y = np.meshgrid(x, y)
|
||||
Z1 = np.exp(-(X**2) - Y**2)
|
||||
Z2 = np.exp(-((X - 1) ** 2) - (Y - 1) ** 2)
|
||||
Z = (Z1 - Z2) * 2
|
||||
|
||||
fig1, ax = plt.subplots()
|
||||
im = ax.imshow(
|
||||
Z,
|
||||
interpolation="bilinear",
|
||||
cmap=cm.RdYlGn,
|
||||
origin="lower",
|
||||
extent=[-3, 3, -3, 3],
|
||||
vmax=abs(Z).max(),
|
||||
vmin=-abs(Z).max(),
|
||||
)
|
||||
|
||||
plt.show()
|
||||
|
||||
fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(9, 4))
|
||||
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
|
||||
# generate some random test data
|
||||
all_data = [np.random.normal(0, std, 100) for std in range(6, 10)]
|
||||
|
||||
# plot violin plot
|
||||
axs[0].violinplot(all_data, showmeans=False, showmedians=True)
|
||||
axs[0].set_title("Violin plot")
|
||||
|
||||
# plot box plot
|
||||
axs[1].boxplot(all_data)
|
||||
axs[1].set_title("Box plot")
|
||||
|
||||
# adding horizontal grid lines
|
||||
for ax in axs:
|
||||
ax.yaxis.grid(True)
|
||||
ax.set_xticks(
|
||||
[y + 1 for y in range(len(all_data))],
|
||||
labels=["x1", "x2", "x3", "x4"],
|
||||
)
|
||||
ax.set_xlabel("Four separate samples")
|
||||
ax.set_ylabel("Observed values")
|
||||
|
||||
plt.show()
|
||||
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
|
||||
# Compute pie slices
|
||||
N = 20
|
||||
theta = np.linspace(0.0, 2 * np.pi, N, endpoint=False)
|
||||
radii = 10 * np.random.rand(N)
|
||||
width = np.pi / 4 * np.random.rand(N)
|
||||
colors = cmo.cm.haline(radii / 10.0)
|
||||
|
||||
ax = plt.subplot(projection="polar")
|
||||
ax.bar(theta, radii, width=width, bottom=0.0, color=colors, alpha=0.5)
|
||||
|
||||
plt.show()
|
||||
|
||||
methods = [
|
||||
None,
|
||||
"none",
|
||||
"nearest",
|
||||
"bilinear",
|
||||
"bicubic",
|
||||
"spline16",
|
||||
"spline36",
|
||||
"hanning",
|
||||
"hamming",
|
||||
"hermite",
|
||||
"kaiser",
|
||||
"quadric",
|
||||
"catrom",
|
||||
"gaussian",
|
||||
"bessel",
|
||||
"mitchell",
|
||||
"sinc",
|
||||
"lanczos",
|
||||
]
|
||||
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
|
||||
grid = np.random.rand(4, 4)
|
||||
|
||||
fig, axs = plt.subplots(
|
||||
nrows=3,
|
||||
ncols=6,
|
||||
figsize=(9, 6),
|
||||
subplot_kw={"xticks": [], "yticks": []},
|
||||
)
|
||||
|
||||
for ax, interp_method in zip(axs.flat, methods):
|
||||
ax.imshow(grid, interpolation=interp_method)
|
||||
ax.set_title(str(interp_method))
|
||||
|
||||
plt.tight_layout()
|
||||
plt.show()
|
@ -1,150 +0,0 @@
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from filters import bandpass_filter, inst_freq, instantaneous_frequency
|
||||
from fish_signal import chirps, wavefish_eods
|
||||
from IPython import embed
|
||||
|
||||
|
||||
def switch_test(test, defaultparams, testparams):
|
||||
if test == "width":
|
||||
defaultparams["chirp_width"] = testparams["chirp_width"]
|
||||
key = "chirp_width"
|
||||
elif test == "size":
|
||||
defaultparams["chirp_size"] = testparams["chirp_size"]
|
||||
key = "chirp_size"
|
||||
elif test == "kurtosis":
|
||||
defaultparams["chirp_kurtosis"] = testparams["chirp_kurtosis"]
|
||||
key = "chirp_kurtosis"
|
||||
elif test == "contrast":
|
||||
defaultparams["chirp_contrast"] = testparams["chirp_contrast"]
|
||||
key = "chirp_contrast"
|
||||
else:
|
||||
raise ValueError("Test not recognized")
|
||||
|
||||
return key, defaultparams
|
||||
|
||||
|
||||
def extract_dict(dict, index):
|
||||
return {key: value[index] for key, value in dict.items()}
|
||||
|
||||
|
||||
def test(test1, test2, resolution=10):
|
||||
assert test1 in [
|
||||
"width",
|
||||
"size",
|
||||
"kurtosis",
|
||||
"contrast",
|
||||
], "Test1 not recognized"
|
||||
|
||||
assert test2 in [
|
||||
"width",
|
||||
"size",
|
||||
"kurtosis",
|
||||
"contrast",
|
||||
], "Test2 not recognized"
|
||||
|
||||
# Define the parameters for the chirp simulations
|
||||
ntest = resolution
|
||||
|
||||
defaultparams = dict(
|
||||
chirp_size=np.ones(ntest) * 100,
|
||||
chirp_width=np.ones(ntest) * 0.1,
|
||||
chirp_kurtosis=np.ones(ntest) * 1.0,
|
||||
chirp_contrast=np.ones(ntest) * 0.5,
|
||||
)
|
||||
|
||||
testparams = dict(
|
||||
chirp_width=np.linspace(0.01, 0.2, ntest),
|
||||
chirp_size=np.linspace(50, 300, ntest),
|
||||
chirp_kurtosis=np.linspace(0.5, 1.5, ntest),
|
||||
chirp_contrast=np.linspace(0.01, 1.0, ntest),
|
||||
)
|
||||
|
||||
key1, chirp_params = switch_test(test1, defaultparams, testparams)
|
||||
key2, chirp_params = switch_test(test2, chirp_params, testparams)
|
||||
|
||||
# make the chirp trace
|
||||
eodf = 500
|
||||
samplerate = 20000
|
||||
duration = 2
|
||||
chirp_times = [0.5, 1, 1.5]
|
||||
|
||||
wide_cutoffs = 200
|
||||
tight_cutoffs = 10
|
||||
|
||||
distances = np.full((ntest, ntest), np.nan)
|
||||
|
||||
fig, axs = plt.subplots(
|
||||
ntest, ntest, figsize=(10, 10), sharex=True, sharey=True
|
||||
)
|
||||
axs = axs.flatten()
|
||||
|
||||
iter0 = 0
|
||||
for iter1, test1_param in enumerate(chirp_params[key1]):
|
||||
for iter2, test2_param in enumerate(chirp_params[key2]):
|
||||
# get the chirp parameters for the current test
|
||||
inner_chirp_params = extract_dict(chirp_params, iter2)
|
||||
inner_chirp_params[key1] = test1_param
|
||||
inner_chirp_params[key2] = test2_param
|
||||
|
||||
# make the chirp trace for the current chirp parameters
|
||||
sizes = np.ones(len(chirp_times)) * inner_chirp_params["chirp_size"]
|
||||
widths = (
|
||||
np.ones(len(chirp_times)) * inner_chirp_params["chirp_width"]
|
||||
)
|
||||
kurtosis = (
|
||||
np.ones(len(chirp_times)) * inner_chirp_params["chirp_kurtosis"]
|
||||
)
|
||||
contrast = (
|
||||
np.ones(len(chirp_times)) * inner_chirp_params["chirp_contrast"]
|
||||
)
|
||||
|
||||
# make the chirp trace
|
||||
chirp_trace, ampmod = chirps(
|
||||
eodf,
|
||||
samplerate,
|
||||
duration,
|
||||
chirp_times,
|
||||
sizes,
|
||||
widths,
|
||||
kurtosis,
|
||||
contrast,
|
||||
)
|
||||
signal = wavefish_eods(
|
||||
fish="Alepto",
|
||||
frequency=chirp_trace,
|
||||
samplerate=samplerate,
|
||||
duration=duration,
|
||||
phase0=0.0,
|
||||
noise_std=0.05,
|
||||
)
|
||||
signal = signal * ampmod
|
||||
|
||||
# apply broadband filter
|
||||
wide_signal = bandpass_filter(
|
||||
signal, samplerate, eodf - wide_cutoffs, eodf + wide_cutoffs
|
||||
)
|
||||
tight_signal = bandpass_filter(
|
||||
signal, samplerate, eodf - tight_cutoffs, eodf + tight_cutoffs
|
||||
)
|
||||
|
||||
# get the instantaneous frequency
|
||||
wide_frequency = inst_freq(wide_signal, samplerate)
|
||||
tight_frequency = inst_freq(tight_signal, samplerate)
|
||||
|
||||
bool_mask = wide_frequency != 0
|
||||
axs[iter0].plot(wide_frequency[bool_mask])
|
||||
axs[iter0].plot(tight_frequency[bool_mask])
|
||||
fig.supylabel(key1)
|
||||
fig.supxlabel(key2)
|
||||
|
||||
iter0 += 1
|
||||
|
||||
plt.show()
|
||||
|
||||
def main():
|
||||
test("contrast", "kurtosis")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -10,84 +10,73 @@ from modules.filters import bandpass_filter
|
||||
|
||||
|
||||
def main(folder):
|
||||
file = os.path.join(folder, "traces-grid.raw")
|
||||
file = os.path.join(folder, 'traces-grid.raw')
|
||||
data = open_data(folder, 60.0, 0, channel=-1)
|
||||
time = np.load(folder + "times.npy", allow_pickle=True)
|
||||
freq = np.load(folder + "fund_v.npy", allow_pickle=True)
|
||||
ident = np.load(folder + "ident_v.npy", allow_pickle=True)
|
||||
idx = np.load(folder + "idx_v.npy", allow_pickle=True)
|
||||
time = np.load(folder + 'times.npy', allow_pickle=True)
|
||||
freq = np.load(folder + 'fund_v.npy', allow_pickle=True)
|
||||
ident = np.load(folder + 'ident_v.npy', allow_pickle=True)
|
||||
idx = np.load(folder + 'idx_v.npy', allow_pickle=True)
|
||||
|
||||
t0 = 3 * 60 * 60 + 6 * 60 + 43.5
|
||||
t0 = 3*60*60 + 6*60 + 43.5
|
||||
dt = 60
|
||||
data_oi = data[t0 * data.samplerate : (t0 + dt) * data.samplerate, :]
|
||||
data_oi = data[t0 * data.samplerate: (t0+dt)*data.samplerate, :]
|
||||
|
||||
for i in [10]:
|
||||
# getting the spectogramm
|
||||
spec_power, spec_freqs, spec_times = spectrogram(
|
||||
data_oi[:, i],
|
||||
ratetime=data.samplerate,
|
||||
freq_resolution=50,
|
||||
overlap_frac=0.0,
|
||||
)
|
||||
fig, ax = plt.subplots(figsize=(20 / 2.54, 12 / 2.54))
|
||||
ax.pcolormesh(
|
||||
spec_times, spec_freqs, decibel(spec_power), vmin=-100, vmax=-50
|
||||
)
|
||||
data_oi[:, i], ratetime=data.samplerate, freq_resolution=50, overlap_frac=0.0)
|
||||
fig, ax = plt.subplots(figsize=(20/2.54, 12/2.54))
|
||||
ax.pcolormesh(spec_times, spec_freqs, decibel(
|
||||
spec_power), vmin=-100, vmax=-50)
|
||||
|
||||
for track_id in np.unique(ident):
|
||||
# window_index for time array in time window
|
||||
window_index = np.arange(len(idx))[
|
||||
(ident == track_id)
|
||||
& (time[idx] >= t0)
|
||||
& (time[idx] <= (t0 + dt))
|
||||
]
|
||||
window_index = np.arange(len(idx))[(ident == track_id) &
|
||||
(time[idx] >= t0) &
|
||||
(time[idx] <= (t0+dt))]
|
||||
freq_temp = freq[window_index]
|
||||
time_temp = time[idx[window_index]]
|
||||
# mean_freq = np.mean(freq_temp)
|
||||
# fdata = bandpass_filter(data_oi[:, track_id], data.samplerate, mean_freq-5, mean_freq+200)
|
||||
#mean_freq = np.mean(freq_temp)
|
||||
#fdata = bandpass_filter(data_oi[:, track_id], data.samplerate, mean_freq-5, mean_freq+200)
|
||||
ax.plot(time_temp - t0, freq_temp)
|
||||
|
||||
ax.set_ylim(500, 1000)
|
||||
plt.show()
|
||||
# filter plot
|
||||
id = 10.0
|
||||
id = 10.
|
||||
i = 10
|
||||
window_index = np.arange(len(idx))[
|
||||
(ident == id) & (time[idx] >= t0) & (time[idx] <= (t0 + dt))
|
||||
]
|
||||
window_index = np.arange(len(idx))[(ident == id) &
|
||||
(time[idx] >= t0) &
|
||||
(time[idx] <= (t0+dt))]
|
||||
freq_temp = freq[window_index]
|
||||
time_temp = time[idx[window_index]]
|
||||
mean_freq = np.mean(freq_temp)
|
||||
fdata = bandpass_filter(
|
||||
data_oi[:, i],
|
||||
rate=data.samplerate,
|
||||
lowf=mean_freq - 5,
|
||||
highf=mean_freq + 200,
|
||||
)
|
||||
data_oi[:, i], rate=data.samplerate, lowf=mean_freq-5, highf=mean_freq+200)
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(np.arange(len(fdata)) / data.samplerate, fdata, marker="*")
|
||||
ax.plot(np.arange(len(fdata))/data.samplerate, fdata, marker='*')
|
||||
# plt.show()
|
||||
# freqency analyis of filtered data
|
||||
|
||||
time_fdata = np.arange(len(fdata)) / data.samplerate
|
||||
time_fdata = np.arange(len(fdata))/data.samplerate
|
||||
roll_fdata = np.roll(fdata, shift=1)
|
||||
period_index = np.arange(len(fdata))[(roll_fdata < 0) & (fdata >= 0)]
|
||||
|
||||
plt.plot(time_fdata, fdata)
|
||||
plt.scatter(time_fdata[period_index], fdata[period_index], c="r")
|
||||
plt.scatter(time_fdata[period_index - 1], fdata[period_index - 1], c="r")
|
||||
plt.scatter(time_fdata[period_index], fdata[period_index], c='r')
|
||||
plt.scatter(time_fdata[period_index-1], fdata[period_index-1], c='r')
|
||||
|
||||
upper_bound = np.abs(fdata[period_index])
|
||||
lower_bound = np.abs(fdata[period_index - 1])
|
||||
lower_bound = np.abs(fdata[period_index-1])
|
||||
|
||||
upper_times = np.abs(time_fdata[period_index])
|
||||
lower_times = np.abs(time_fdata[period_index - 1])
|
||||
lower_times = np.abs(time_fdata[period_index-1])
|
||||
|
||||
lower_ratio = lower_bound / (lower_bound + upper_bound)
|
||||
upper_ratio = upper_bound / (lower_bound + upper_bound)
|
||||
lower_ratio = lower_bound/(lower_bound+upper_bound)
|
||||
upper_ratio = upper_bound/(lower_bound+upper_bound)
|
||||
|
||||
time_delta = upper_times - lower_times
|
||||
true_zero = lower_times + time_delta * lower_ratio
|
||||
time_delta = upper_times-lower_times
|
||||
true_zero = lower_times + time_delta*lower_ratio
|
||||
|
||||
plt.scatter(true_zero, np.zeros(len(true_zero)))
|
||||
|
||||
@ -95,7 +84,7 @@ def main(folder):
|
||||
inst_freq = 1 / np.diff(true_zero)
|
||||
filtered_inst_freq = gaussian_filter1d(inst_freq, 0.005)
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot(filtered_inst_freq, marker=".")
|
||||
ax.plot(filtered_inst_freq, marker='.')
|
||||
# in 5 sekunden welcher fisch auf einer elektrode am
|
||||
|
||||
embed()
|
||||
@ -110,7 +99,5 @@ def main(folder):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(
|
||||
"/Users/acfw/Documents/uni_tuebingen/chirpdetection/gp_benda/data/2022-06-02-10_00/"
|
||||
)
|
||||
if __name__ == '__main__':
|
||||
main('/Users/acfw/Documents/uni_tuebingen/chirpdetection/gp_benda/data/2022-06-02-10_00/')
|
||||
|
@ -1,38 +0,0 @@
|
||||
from thunderfish.dataloader import DataLoader as open_data
|
||||
from thunderfish.powerspectrum import spectrogram, decibel
|
||||
from IPython import embed
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import os
|
||||
from scipy.ndimage import gaussian_filter1d
|
||||
from modules.filters import bandpass_filter
|
||||
from modules.filehandling import LoadData
|
||||
|
||||
|
||||
def main(folder):
|
||||
data = LoadData(folder)
|
||||
|
||||
t0 = 3 * 60 * 60 + 6 * 60 + 43.5
|
||||
dt = 60
|
||||
data_oi = data.raw[t0 * data.raw_rate : (t0 + dt) * data.raw_rate, :]
|
||||
# good electrode
|
||||
electrode = 10
|
||||
data_oi = data_oi[:, electrode]
|
||||
fig, axs = plt.subplots(2, 1)
|
||||
axs[0].plot(np.arange(data_oi.shape[0]) / data.raw_rate, data_oi)
|
||||
for tr, track_id in enumerate(np.unique(data.ident[~np.isnan(data.ident)])):
|
||||
rack_window_index = np.arange(len(data.idx))[
|
||||
(data.ident == track_id)
|
||||
& (data.time[data.idx] >= t0)
|
||||
& (data.time[data.idx] <= (t0 + dt))
|
||||
]
|
||||
freq_fish = data.freq[rack_window_index]
|
||||
axs[1].plot(np.arange(freq_fish.shape[0]) / data.raw_rate, freq_fish)
|
||||
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(
|
||||
"/Users/acfw/Documents/uni_tuebingen/chirpdetection/GP2023_chirp_detection/data/2022-06-02-10_00/"
|
||||
)
|
267
code/behavior.py
@ -1,75 +1,59 @@
|
||||
import os
|
||||
import os
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from IPython import embed
|
||||
from pandas import read_csv
|
||||
from modules.logger import makeLogger
|
||||
from scipy.ndimage import gaussian_filter1d
|
||||
|
||||
logger = makeLogger(__name__)
|
||||
|
||||
|
||||
class Behavior:
|
||||
"""Load behavior data from csv file as class attributes
|
||||
Attributes
|
||||
----------
|
||||
behavior: 0: chasing onset, 1: chasing offset, 2: physical contact
|
||||
behavior_type:
|
||||
behavioral_category:
|
||||
comment_start:
|
||||
comment_stop:
|
||||
dataframe: pandas dataframe with all the data
|
||||
duration_s:
|
||||
media_file:
|
||||
observation_date:
|
||||
observation_id:
|
||||
start_s: start time of the event in seconds
|
||||
stop_s: stop time of the event in seconds
|
||||
total_length:
|
||||
behavior_type:
|
||||
behavioral_category:
|
||||
comment_start:
|
||||
comment_stop:
|
||||
dataframe: pandas dataframe with all the data
|
||||
duration_s:
|
||||
media_file:
|
||||
observation_date:
|
||||
observation_id:
|
||||
start_s: start time of the event in seconds
|
||||
stop_s: stop time of the event in seconds
|
||||
total_length:
|
||||
"""
|
||||
|
||||
def __init__(self, folder_path: str) -> None:
|
||||
LED_on_time_BORIS = np.load(
|
||||
os.path.join(folder_path, "LED_on_time.npy"), allow_pickle=True
|
||||
)
|
||||
self.time = np.load(
|
||||
os.path.join(folder_path, "times.npy"), allow_pickle=True
|
||||
)
|
||||
csv_filename = [
|
||||
f for f in os.listdir(folder_path) if f.endswith(".csv")
|
||||
][
|
||||
0
|
||||
] # check if there are more than one csv file
|
||||
|
||||
|
||||
LED_on_time_BORIS = np.load(os.path.join(folder_path, 'LED_on_time.npy'), allow_pickle=True)
|
||||
self.time = np.load(os.path.join(folder_path, "times.npy"), allow_pickle=True)
|
||||
csv_filename = [f for f in os.listdir(folder_path) if f.endswith('.csv')][0] # check if there are more than one csv file
|
||||
self.dataframe = read_csv(os.path.join(folder_path, csv_filename))
|
||||
self.chirps = np.load(
|
||||
os.path.join(folder_path, "chirps.npy"), allow_pickle=True
|
||||
)
|
||||
self.chirps_ids = np.load(
|
||||
os.path.join(folder_path, "chirps_ids.npy"), allow_pickle=True
|
||||
)
|
||||
self.chirps = np.load(os.path.join(folder_path, 'chirps.npy'), allow_pickle=True)
|
||||
self.chirps_ids = np.load(os.path.join(folder_path, 'chirps_ids.npy'), allow_pickle=True)
|
||||
|
||||
for k, key in enumerate(self.dataframe.keys()):
|
||||
key = key.lower()
|
||||
if " " in key:
|
||||
key = key.replace(" ", "_")
|
||||
if "(" in key:
|
||||
key = key.replace("(", "")
|
||||
key = key.replace(")", "")
|
||||
setattr(
|
||||
self, key, np.array(self.dataframe[self.dataframe.keys()[k]])
|
||||
)
|
||||
|
||||
key = key.lower()
|
||||
if ' ' in key:
|
||||
key = key.replace(' ', '_')
|
||||
if '(' in key:
|
||||
key = key.replace('(', '')
|
||||
key = key.replace(')', '')
|
||||
setattr(self, key, np.array(self.dataframe[self.dataframe.keys()[k]]))
|
||||
|
||||
last_LED_t_BORIS = LED_on_time_BORIS[-1]
|
||||
real_time_range = self.time[-1] - self.time[0]
|
||||
factor = 1.034141
|
||||
shift = last_LED_t_BORIS - real_time_range * factor
|
||||
self.start_s = (self.start_s - shift) / factor
|
||||
self.stop_s = (self.stop_s - shift) / factor
|
||||
|
||||
|
||||
|
||||
"""
|
||||
1 - chasing onset
|
||||
2 - chasing offset
|
||||
@ -97,77 +81,53 @@ temporal encpding needs to be corrected ... not exactly 25FPS.
|
||||
behavior = data['Behavior']
|
||||
"""
|
||||
|
||||
|
||||
def correct_chasing_events(
|
||||
category: np.ndarray, timestamps: np.ndarray
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
onset_ids = np.arange(len(category))[category == 0]
|
||||
offset_ids = np.arange(len(category))[category == 1]
|
||||
category: np.ndarray,
|
||||
timestamps: np.ndarray
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
|
||||
onset_ids = np.arange(
|
||||
len(category))[category == 0]
|
||||
offset_ids = np.arange(
|
||||
len(category))[category == 1]
|
||||
|
||||
# Check whether on- or offset is longer and calculate length difference
|
||||
if len(onset_ids) > len(offset_ids):
|
||||
len_diff = len(onset_ids) - len(offset_ids)
|
||||
longer_array = onset_ids
|
||||
shorter_array = offset_ids
|
||||
logger.info(f"Onsets are greater than offsets by {len_diff}")
|
||||
logger.info(f'Onsets are greater than offsets by {len_diff}')
|
||||
elif len(onset_ids) < len(offset_ids):
|
||||
len_diff = len(offset_ids) - len(onset_ids)
|
||||
longer_array = offset_ids
|
||||
shorter_array = onset_ids
|
||||
logger.info(f"Offsets are greater than offsets by {len_diff}")
|
||||
logger.info(f'Offsets are greater than offsets by {len_diff}')
|
||||
elif len(onset_ids) == len(offset_ids):
|
||||
logger.info("Chasing events are equal")
|
||||
logger.info('Chasing events are equal')
|
||||
return category, timestamps
|
||||
|
||||
|
||||
# Correct the wrong chasing events; delete double events
|
||||
wrong_ids = []
|
||||
for i in range(len(longer_array) - (len_diff + 1)):
|
||||
if (shorter_array[i] > longer_array[i]) & (
|
||||
shorter_array[i] < longer_array[i + 1]
|
||||
):
|
||||
for i in range(len(longer_array)-(len_diff+1)):
|
||||
if (shorter_array[i] > longer_array[i]) & (shorter_array[i] < longer_array[i+1]):
|
||||
pass
|
||||
else:
|
||||
wrong_ids.append(longer_array[i])
|
||||
longer_array = np.delete(longer_array, i)
|
||||
|
||||
category = np.delete(category, wrong_ids)
|
||||
timestamps = np.delete(timestamps, wrong_ids)
|
||||
|
||||
category = np.delete(
|
||||
category, wrong_ids)
|
||||
timestamps = np.delete(
|
||||
timestamps, wrong_ids)
|
||||
return category, timestamps
|
||||
|
||||
|
||||
def event_triggered_chirps(
|
||||
event: np.ndarray,
|
||||
chirps: np.ndarray,
|
||||
time_before_event: int,
|
||||
time_after_event: int,
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
event_chirps = [] # chirps that are in specified window around event
|
||||
centered_chirps = (
|
||||
[]
|
||||
) # timestamps of chirps around event centered on the event timepoint
|
||||
|
||||
for event_timestamp in event:
|
||||
start = event_timestamp - time_before_event # timepoint of window start
|
||||
stop = event_timestamp + time_after_event # timepoint of window ending
|
||||
chirps_around_event = [
|
||||
c for c in chirps if (c >= start) & (c <= stop)
|
||||
] # get chirps that are in a -5 to +5 sec window around event
|
||||
event_chirps.append(chirps_around_event)
|
||||
if len(chirps_around_event) == 0:
|
||||
continue
|
||||
else:
|
||||
centered_chirps.append(chirps_around_event - event_timestamp)
|
||||
centered_chirps = np.concatenate(
|
||||
centered_chirps, axis=0
|
||||
) # convert list of arrays to one array for plotting
|
||||
|
||||
return event_chirps, centered_chirps
|
||||
|
||||
|
||||
def main(datapath: str):
|
||||
|
||||
# behavior is pandas dataframe with all the data
|
||||
bh = Behavior(datapath)
|
||||
|
||||
|
||||
# chirps are not sorted in time (presumably due to prior groupings)
|
||||
# get and sort chirps and corresponding fish_ids of the chirps
|
||||
chirps = bh.chirps[np.argsort(bh.chirps)]
|
||||
@ -184,105 +144,39 @@ def main(datapath: str):
|
||||
chasing_offset = timestamps[category == 1]
|
||||
physical_contact = timestamps[category == 2]
|
||||
|
||||
##### TODO Physical contact-triggered chirps (PTC) mit Rasterplot #####
|
||||
# Wahrscheinlichkeit von Phys auf Ch und vice versa
|
||||
# Chasing-triggered chirps (CTC) mit Rasterplot
|
||||
# Wahrscheinlichkeit von Chase auf Ch und vice versa
|
||||
|
||||
# First overview plot
|
||||
fig1, ax1 = plt.subplots()
|
||||
ax1.scatter(
|
||||
chirps,
|
||||
np.ones_like(chirps),
|
||||
marker="*",
|
||||
color="royalblue",
|
||||
label="Chirps",
|
||||
)
|
||||
ax1.scatter(
|
||||
chasing_onset,
|
||||
np.ones_like(chasing_onset) * 2,
|
||||
marker=".",
|
||||
color="forestgreen",
|
||||
label="Chasing onset",
|
||||
)
|
||||
ax1.scatter(
|
||||
chasing_offset,
|
||||
np.ones_like(chasing_offset) * 2.5,
|
||||
marker=".",
|
||||
color="firebrick",
|
||||
label="Chasing offset",
|
||||
)
|
||||
ax1.scatter(
|
||||
physical_contact,
|
||||
np.ones_like(physical_contact) * 3,
|
||||
marker="x",
|
||||
color="black",
|
||||
label="Physical contact",
|
||||
)
|
||||
ax1.scatter(chirps, np.ones_like(chirps), marker='*', color='royalblue', label='Chirps')
|
||||
ax1.scatter(chasing_onset, np.ones_like(chasing_onset)*2, marker='.', color='forestgreen', label='Chasing onset')
|
||||
ax1.scatter(chasing_offset, np.ones_like(chasing_offset)*2.5, marker='.', color='firebrick', label='Chasing offset')
|
||||
ax1.scatter(physical_contact, np.ones_like(physical_contact)*3, marker='x', color='black', label='Physical contact')
|
||||
plt.legend()
|
||||
# plt.show()
|
||||
plt.close()
|
||||
|
||||
# Get fish ids
|
||||
fish_ids = np.unique(chirps_fish_ids)
|
||||
|
||||
##### Chasing triggered chirps CTC #####
|
||||
# Evaluate how many chirps were emitted in specific time window around the chasing onset events
|
||||
|
||||
# Iterate over chasing onsets (later over fish)
|
||||
time_around_event = 5 # time window around the event in which chirps are counted, 5 = -5 to +5 sec around event
|
||||
#### Loop crashes at concatenate in function ####
|
||||
# for i in range(len(fish_ids)):
|
||||
# fish = fish_ids[i]
|
||||
# chirps = chirps[chirps_fish_ids == fish]
|
||||
# print(fish)
|
||||
|
||||
chasing_chirps, centered_chasing_chirps = event_triggered_chirps(
|
||||
chasing_onset, chirps, time_around_event, time_around_event
|
||||
)
|
||||
physical_chirps, centered_physical_chirps = event_triggered_chirps(
|
||||
physical_contact, chirps, time_around_event, time_around_event
|
||||
)
|
||||
|
||||
# Kernel density estimation ???
|
||||
# centered_chasing_chirps_convolved = gaussian_filter1d(centered_chasing_chirps, 5)
|
||||
|
||||
# centered_chasing = chasing_onset[0] - chasing_onset[0] ## get the 0 timepoint for plotting; set one chasing event to 0
|
||||
offsets = [0.5, 1]
|
||||
fig4, ax4 = plt.subplots(
|
||||
figsize=(20 / 2.54, 12 / 2.54), constrained_layout=True
|
||||
)
|
||||
ax4.eventplot(
|
||||
np.array([centered_chasing_chirps, centered_physical_chirps]),
|
||||
lineoffsets=offsets,
|
||||
linelengths=0.25,
|
||||
colors=["g", "r"],
|
||||
)
|
||||
ax4.vlines(0, 0, 1.5, "tab:grey", "dashed", "Timepoint of event")
|
||||
# ax4.plot(centered_chasing_chirps_convolved)
|
||||
ax4.set_yticks(offsets)
|
||||
ax4.set_yticklabels(["Chasings", "Physical \n contacts"])
|
||||
ax4.set_xlabel("Time[s]")
|
||||
ax4.set_ylabel("Type of event")
|
||||
plt.show()
|
||||
all_fish_ids = np.unique(chirps_fish_ids)
|
||||
|
||||
# Associate chirps to inidividual fish
|
||||
fish1 = chirps[chirps_fish_ids == fish_ids[0]]
|
||||
fish2 = chirps[chirps_fish_ids == fish_ids[1]]
|
||||
fish1 = chirps[chirps_fish_ids == all_fish_ids[0]]
|
||||
fish2 = chirps[chirps_fish_ids == all_fish_ids[1]]
|
||||
fish = [len(fish1), len(fish2)]
|
||||
|
||||
### Plots:
|
||||
# 1. All recordings, all fish, all chirps
|
||||
# One CTC, one PTC
|
||||
# 2. All recordings, only winners
|
||||
# One CTC, one PTC
|
||||
# 3. All recordings, all losers
|
||||
# One CTC, one PTC
|
||||
|
||||
#### Chirp counts per fish general #####
|
||||
fig2, ax2 = plt.subplots()
|
||||
x = ["Fish1", "Fish2"]
|
||||
x = ['Fish1', 'Fish2']
|
||||
width = 0.35
|
||||
ax2.bar(x, fish, width=width)
|
||||
ax2.set_ylabel("Chirp count")
|
||||
ax2.set_ylabel('Chirp count')
|
||||
# plt.show()
|
||||
plt.close()
|
||||
|
||||
|
||||
##### Count chirps emitted during chasing events and chirps emitted out of chasing events #####
|
||||
chirps_in_chasings = []
|
||||
for onset, offset in zip(chasing_onset, chasing_offset):
|
||||
@ -299,24 +193,25 @@ def main(datapath: str):
|
||||
counts_chirps_chasings += 1
|
||||
|
||||
# chirps in chasing events
|
||||
fig3, ax3 = plt.subplots()
|
||||
ax3.bar(
|
||||
["Chirps in chasing events", "Chasing events without Chirps"],
|
||||
[counts_chirps_chasings, chasings_without_chirps],
|
||||
width=width,
|
||||
)
|
||||
plt.ylabel("Count")
|
||||
# plt.show()
|
||||
plt.close()
|
||||
fig3 , ax3 = plt.subplots()
|
||||
ax3.bar(['Chirps in chasing events', 'Chasing events without Chirps'], [counts_chirps_chasings, chasings_without_chirps], width=width)
|
||||
plt.ylabel('Count')
|
||||
plt.show()
|
||||
plt.close()
|
||||
|
||||
# comparison between chasing events with and without chirps
|
||||
|
||||
|
||||
|
||||
|
||||
# comparison between chasing events with and without chirps
|
||||
|
||||
embed()
|
||||
exit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Path to the data
|
||||
datapath = "../data/mount_data/2020-05-13-10_00/"
|
||||
datapath = "../data/mount_data/2020-05-13-10_00/"
|
||||
datapath = '../data/mount_data/2020-05-13-10_00/'
|
||||
main(datapath)
|
||||
|
@ -1,34 +1,24 @@
|
||||
import numpy as np
|
||||
from modules.filters import create_chirp, bandpass_filter
|
||||
import matplotlib.pyplot as plt
|
||||
from chirpdetection import instantaneos_frequency
|
||||
from IPython import embed
|
||||
import matplotlib.pyplot as plt
|
||||
from thunderfish import fakefish
|
||||
# create chirp
|
||||
|
||||
from modules.filters import bandpass_filter
|
||||
from modules.datahandling import instantaneous_frequency
|
||||
from modules.simulations import create_chirp
|
||||
time, signal, ampl, freq = create_chirp(chirptimes=[0.05, 0.2501, 0.38734, 0.48332, 0.73434, 0.823424], )
|
||||
|
||||
# filter signal with bandpass_filter
|
||||
|
||||
# trying thunderfish fakefish chirp simulation ---------------------------------
|
||||
samplerate = 44100
|
||||
freq, ampl = fakefish.chirps(eodf=500, chirp_contrast=0.2)
|
||||
data = fakefish.wavefish_eods(
|
||||
fish="Alepto", frequency=freq, phase0=3, samplerate=samplerate
|
||||
)
|
||||
|
||||
# filter signal with bandpass_filter
|
||||
data_filterd = bandpass_filter(data * ampl + 1, samplerate, 0.01, 1.99)
|
||||
signal = bandpass_filter(signal, 1/0.00001, 495, 505)
|
||||
embed()
|
||||
data_freq_time, data_freq = instantaneous_frequency(data, samplerate, 5)
|
||||
exit()
|
||||
fig, axs = plt.subplots(2, 1, figsize=(10, 10))
|
||||
axs[0].plot(time, signal)
|
||||
|
||||
# plot instatneous frequency
|
||||
|
||||
fig, ax = plt.subplots(4, 1, figsize=(20 / 2.54, 12 / 2.54), sharex=True)
|
||||
baseline_freq_time, baseline_freq = instantaneos_frequency(signal, 1/0.00001)
|
||||
axs[1].plot(baseline_freq_time[1:], baseline_freq[1:])
|
||||
|
||||
ax[0].plot(np.arange(len(data)) / samplerate, data * ampl)
|
||||
# ax[0].scatter(true_zero, np.zeros_like(true_zero), color='red')
|
||||
ax[1].plot(np.arange(len(data_filterd)) / samplerate, data_filterd)
|
||||
ax[2].plot(np.arange(len(freq)) / samplerate, freq)
|
||||
ax[3].plot(data_freq_time, data_freq)
|
||||
|
||||
|
||||
plt.show()
|
||||
embed()
|
||||
|
@ -1,26 +1,24 @@
|
||||
from dataclasses import dataclass
|
||||
from itertools import compress
|
||||
from dataclasses import dataclass
|
||||
|
||||
import matplotlib.gridspec as gr
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from IPython import embed
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.gridspec as gr
|
||||
from scipy.signal import find_peaks
|
||||
from thunderfish.powerspectrum import spectrogram, decibel
|
||||
from sklearn.preprocessing import normalize
|
||||
|
||||
from modules.filters import bandpass_filter, envelope, highpass_filter
|
||||
from modules.filehandling import ConfLoader, LoadData, make_outputdir
|
||||
from modules.plotstyle import PlotStyle
|
||||
from modules.logger import makeLogger
|
||||
from modules.datahandling import (
|
||||
flatten,
|
||||
purge_duplicates,
|
||||
group_timestamps,
|
||||
instantaneous_frequency,
|
||||
minmaxnorm,
|
||||
purge_duplicates,
|
||||
)
|
||||
from modules.filehandling import ConfLoader, LoadData, make_outputdir
|
||||
from modules.filters import bandpass_filter, envelope, highpass_filter
|
||||
from modules.logger import makeLogger
|
||||
from modules.plotstyle import PlotStyle
|
||||
from scipy.signal import find_peaks
|
||||
from thunderfish.powerspectrum import decibel, spectrogram
|
||||
|
||||
# from sklearn.preprocessing import normalize
|
||||
|
||||
|
||||
logger = makeLogger(__name__)
|
||||
|
||||
@ -28,7 +26,7 @@ ps = PlotStyle()
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChirpPlotBuffer:
|
||||
class PlotBuffer:
|
||||
|
||||
"""
|
||||
Buffer to save data that is created in the main detection loop
|
||||
@ -59,6 +57,7 @@ class ChirpPlotBuffer:
|
||||
frequency_peaks: np.ndarray
|
||||
|
||||
def plot_buffer(self, chirps: np.ndarray, plot: str) -> None:
|
||||
|
||||
logger.debug("Starting plotting")
|
||||
|
||||
# make data for plotting
|
||||
@ -84,32 +83,28 @@ class ChirpPlotBuffer:
|
||||
q50 + self.search_frequency + self.config.minimal_bandwidth / 2,
|
||||
q50 + self.search_frequency - self.config.minimal_bandwidth / 2,
|
||||
)
|
||||
print(search_upper, search_lower)
|
||||
|
||||
# get indices on raw data
|
||||
start_idx = int((self.t0 - 5) * self.data.raw_rate)
|
||||
start_idx = (self.t0 - 5) * self.data.raw_rate
|
||||
window_duration = (self.dt + 10) * self.data.raw_rate
|
||||
stop_idx = int(start_idx + window_duration)
|
||||
|
||||
if start_idx < 0:
|
||||
start_idx = 0
|
||||
stop_idx = start_idx + window_duration
|
||||
|
||||
# get raw data
|
||||
try:
|
||||
data_oi = self.data.raw[start_idx:stop_idx, self.electrode]
|
||||
except:
|
||||
embed()
|
||||
data_oi = self.data.raw[start_idx:stop_idx, self.electrode]
|
||||
|
||||
self.time = self.time - self.t0
|
||||
self.frequency_time = self.frequency_time - self.t0
|
||||
if len(chirps) > 0:
|
||||
chirps = np.asarray(chirps) - self.t0
|
||||
chirps = np.asarray(chirps) - self.t0
|
||||
self.t0_old = self.t0
|
||||
self.t0 = 0
|
||||
|
||||
fig = plt.figure(figsize=(14 * ps.cm, 18 * ps.cm))
|
||||
fig = plt.figure(
|
||||
figsize=(14 / 2.54, 20 / 2.54)
|
||||
)
|
||||
|
||||
gs0 = gr.GridSpec(3, 1, figure=fig, height_ratios=[1, 1, 1])
|
||||
gs0 = gr.GridSpec(
|
||||
3, 1, figure=fig, height_ratios=[1, 1, 1]
|
||||
)
|
||||
gs1 = gs0[0].subgridspec(1, 1)
|
||||
gs2 = gs0[1].subgridspec(3, 1, hspace=0.4)
|
||||
gs3 = gs0[2].subgridspec(3, 1, hspace=0.4)
|
||||
@ -135,11 +130,11 @@ class ChirpPlotBuffer:
|
||||
data_oi,
|
||||
self.data.raw_rate,
|
||||
self.t0 - 5,
|
||||
[np.min(self.frequency) - 300, np.max(self.frequency) + 300],
|
||||
[np.max(self.frequency) - 200, np.max(self.frequency) + 200]
|
||||
)
|
||||
ax0.set_ylim(np.min(self.frequency) - 100, np.max(self.frequency) + 200)
|
||||
|
||||
for track_id in self.data.ids:
|
||||
|
||||
t0_track = self.t0_old - 5
|
||||
dt_track = self.dt + 10
|
||||
window_idx = np.arange(len(self.data.idx))[
|
||||
@ -150,163 +145,102 @@ class ChirpPlotBuffer:
|
||||
|
||||
# get tracked frequencies and their times
|
||||
f = self.data.freq[window_idx]
|
||||
# t = self.data.time[
|
||||
# self.data.idx[self.data.ident == self.track_id]]
|
||||
# tmask = (t >= t0_track) & (t <= (t0_track + dt_track))
|
||||
t = self.data.time[self.data.idx[window_idx]]
|
||||
t = self.data.time[
|
||||
self.data.idx[self.data.ident == self.track_id]]
|
||||
tmask = (t >= t0_track) & (t <= (t0_track + dt_track))
|
||||
if track_id == self.track_id:
|
||||
ax0.plot(t - self.t0_old, f, lw=lw, zorder=10, color=ps.gblue1)
|
||||
ax0.plot(t[tmask]-self.t0_old, f, lw=lw,
|
||||
zorder=10, color=ps.gblue1)
|
||||
else:
|
||||
ax0.plot(t - self.t0_old, f, lw=lw, zorder=10, color=ps.black)
|
||||
|
||||
# ax0.fill_between(
|
||||
# np.arange(self.t0, self.t0 + self.dt, 1 / self.data.raw_rate),
|
||||
# q50 - self.config.minimal_bandwidth / 2,
|
||||
# q50 + self.config.minimal_bandwidth / 2,
|
||||
# color=ps.gblue1,
|
||||
# lw=1,
|
||||
# ls="dashed",
|
||||
# alpha=0.5,
|
||||
# )
|
||||
|
||||
# ax0.fill_between(
|
||||
# np.arange(self.t0, self.t0 + self.dt, 1 / self.data.raw_rate),
|
||||
# search_lower,
|
||||
# search_upper,
|
||||
# color=ps.gblue2,
|
||||
# lw=1,
|
||||
# ls="dashed",
|
||||
# alpha=0.5,
|
||||
# )
|
||||
|
||||
ax0.axhline(
|
||||
ax0.plot(t[tmask]-self.t0_old, f, lw=lw,
|
||||
zorder=10, color=ps.gray, alpha=0.5)
|
||||
|
||||
ax0.fill_between(
|
||||
np.arange(self.t0, self.t0 + self.dt, 1 / self.data.raw_rate),
|
||||
q50 - self.config.minimal_bandwidth / 2,
|
||||
q50 + self.config.minimal_bandwidth / 2,
|
||||
color=ps.gblue1,
|
||||
lw=1,
|
||||
ls="dashed",
|
||||
alpha=0.5,
|
||||
)
|
||||
ax0.axhline(
|
||||
q50 + self.config.minimal_bandwidth / 2,
|
||||
color=ps.gblue1,
|
||||
|
||||
ax0.fill_between(
|
||||
np.arange(self.t0, self.t0 + self.dt, 1 / self.data.raw_rate),
|
||||
search_lower,
|
||||
search_upper,
|
||||
color=ps.gblue2,
|
||||
lw=1,
|
||||
ls="dashed",
|
||||
alpha=0.5,
|
||||
)
|
||||
ax0.axhline(search_lower, color=ps.gblue2, lw=1, ls="dashed")
|
||||
ax0.axhline(search_upper, color=ps.gblue2, lw=1, ls="dashed")
|
||||
|
||||
# ax0.axhline(q50, spec_times[0], spec_times[-1],
|
||||
# color=ps.gblue1, lw=2, ls="dashed")
|
||||
# ax0.axhline(q50 + self.search_frequency,
|
||||
# spec_times[0], spec_times[-1],
|
||||
# color=ps.gblue2, lw=2, ls="dashed")
|
||||
|
||||
if len(chirps) > 0:
|
||||
for chirp in chirps:
|
||||
ax0.scatter(
|
||||
chirp,
|
||||
np.median(self.frequency),
|
||||
c=ps.red,
|
||||
marker=".",
|
||||
edgecolors=ps.black,
|
||||
facecolors=ps.red,
|
||||
zorder=10,
|
||||
s=70,
|
||||
)
|
||||
for chirp in chirps:
|
||||
ax0.scatter(
|
||||
chirp, np.median(self.frequency) + 150, c=ps.black, marker="v"
|
||||
)
|
||||
|
||||
# plot waveform of filtered signal
|
||||
ax1.plot(
|
||||
self.time,
|
||||
self.baseline * waveform_scaler,
|
||||
c=ps.gray,
|
||||
lw=lw,
|
||||
alpha=0.5,
|
||||
)
|
||||
ax1.plot(
|
||||
self.time,
|
||||
self.baseline_envelope_unfiltered * waveform_scaler,
|
||||
c=ps.gblue1,
|
||||
lw=lw,
|
||||
label="baseline envelope",
|
||||
)
|
||||
ax1.plot(self.time, self.baseline * waveform_scaler,
|
||||
c=ps.gray, lw=lw, alpha=0.5)
|
||||
ax1.plot(self.time, self.baseline_envelope_unfiltered *
|
||||
waveform_scaler, c=ps.gblue1, lw=lw, label="baseline envelope")
|
||||
|
||||
# plot waveform of filtered search signal
|
||||
ax2.plot(
|
||||
self.time,
|
||||
self.search * waveform_scaler,
|
||||
c=ps.gray,
|
||||
lw=lw,
|
||||
alpha=0.5,
|
||||
)
|
||||
ax2.plot(
|
||||
self.time,
|
||||
self.search_envelope_unfiltered * waveform_scaler,
|
||||
c=ps.gblue2,
|
||||
lw=lw,
|
||||
label="search envelope",
|
||||
)
|
||||
ax2.plot(self.time, self.search * waveform_scaler,
|
||||
c=ps.gray, lw=lw, alpha=0.5)
|
||||
ax2.plot(self.time, self.search_envelope_unfiltered *
|
||||
waveform_scaler, c=ps.gblue2, lw=lw, label="search envelope")
|
||||
|
||||
# plot baseline instantaneous frequency
|
||||
ax3.plot(
|
||||
self.frequency_time,
|
||||
self.frequency,
|
||||
c=ps.gblue3,
|
||||
lw=lw,
|
||||
label="baseline inst. freq.",
|
||||
)
|
||||
ax3.plot(self.frequency_time, self.frequency,
|
||||
c=ps.gblue3, lw=lw, label="baseline inst. freq.")
|
||||
|
||||
# plot filtered and rectified envelope
|
||||
# ax4.plot(
|
||||
# self.time, self.baseline_envelope * waveform_scaler, c=ps.gblue1, lw=lw
|
||||
# )
|
||||
ax4.plot(self.time, self.baseline_envelope, c=ps.gblue1, lw=lw)
|
||||
ax4.scatter(
|
||||
(self.time)[self.baseline_peaks],
|
||||
# (self.baseline_envelope * waveform_scaler)[self.baseline_peaks],
|
||||
(self.baseline_envelope)[self.baseline_peaks],
|
||||
edgecolors=ps.black,
|
||||
facecolors=ps.red,
|
||||
self.baseline_envelope[self.baseline_peaks],
|
||||
edgecolors=ps.red,
|
||||
zorder=10,
|
||||
marker=".",
|
||||
s=70,
|
||||
# facecolors="none",
|
||||
marker="o",
|
||||
facecolors="none",
|
||||
)
|
||||
|
||||
# plot envelope of search signal
|
||||
# ax5.plot(self.time, self.search_envelope * waveform_scaler, c=ps.gblue2, lw=lw)
|
||||
ax5.plot(self.time, self.search_envelope, c=ps.gblue2, lw=lw)
|
||||
ax5.scatter(
|
||||
(self.time)[self.search_peaks],
|
||||
# (self.search_envelope * waveform_scaler)[self.search_peaks],
|
||||
(self.search_envelope)[self.search_peaks],
|
||||
edgecolors=ps.black,
|
||||
facecolors=ps.red,
|
||||
self.search_envelope[self.search_peaks],
|
||||
edgecolors=ps.red,
|
||||
zorder=10,
|
||||
marker=".",
|
||||
s=70,
|
||||
# facecolors="none",
|
||||
marker="o",
|
||||
facecolors="none",
|
||||
)
|
||||
|
||||
# plot filtered instantaneous frequency
|
||||
ax6.plot(self.frequency_time, self.frequency_filtered, c=ps.gblue3, lw=lw)
|
||||
ax6.plot(self.frequency_time,
|
||||
self.frequency_filtered, c=ps.gblue3, lw=lw)
|
||||
ax6.scatter(
|
||||
self.frequency_time[self.frequency_peaks],
|
||||
self.frequency_filtered[self.frequency_peaks],
|
||||
edgecolors=ps.black,
|
||||
facecolors=ps.red,
|
||||
edgecolors=ps.red,
|
||||
zorder=10,
|
||||
marker=".",
|
||||
s=70,
|
||||
# facecolors="none",
|
||||
marker="o",
|
||||
facecolors="none",
|
||||
)
|
||||
|
||||
ax0.set_ylabel("Frequency [Hz]")
|
||||
ax1.set_ylabel(r"$\mu$V")
|
||||
ax2.set_ylabel(r"$\mu$V")
|
||||
ax0.set_ylabel("frequency [Hz]")
|
||||
ax1.set_ylabel("a.u.")
|
||||
ax2.set_ylabel("a.u.")
|
||||
ax3.set_ylabel("Hz")
|
||||
ax4.set_ylabel(r"$\mu$V")
|
||||
ax5.set_ylabel(r"$\mu$V")
|
||||
ax6.set_ylabel("Hz")
|
||||
ax6.set_xlabel("Time [s]")
|
||||
ax5.set_ylabel("a.u.")
|
||||
ax6.set_xlabel("time [s]")
|
||||
|
||||
plt.setp(ax0.get_xticklabels(), visible=False)
|
||||
plt.setp(ax1.get_xticklabels(), visible=False)
|
||||
@ -321,7 +255,8 @@ class ChirpPlotBuffer:
|
||||
# ax7.spines.bottom.set_bounds((0, 5))
|
||||
|
||||
ax0.set_xlim(0, self.config.window)
|
||||
plt.subplots_adjust(left=0.165, right=0.975, top=0.98, bottom=0.074, hspace=0.2)
|
||||
plt.subplots_adjust(left=0.165, right=0.975,
|
||||
top=0.98, bottom=0.074, hspace=0.2)
|
||||
fig.align_labels()
|
||||
|
||||
if plot == "show":
|
||||
@ -332,9 +267,8 @@ class ChirpPlotBuffer:
|
||||
self.config.outputdir + self.data.datapath.split("/")[-2] + "/"
|
||||
)
|
||||
|
||||
# plt.savefig(f"{out}{self.track_id}_{self.t0_old}.pdf")
|
||||
# plt.savefig(f"{out}{self.track_id}_{self.t0_old}.svg")
|
||||
plt.savefig(f"{out}{self.track_id}_{self.t0_old}.png")
|
||||
plt.savefig(f"{out}{self.track_id}_{self.t0_old}.pdf")
|
||||
plt.savefig(f"{out}{self.track_id}_{self.t0_old}.svg")
|
||||
plt.close()
|
||||
|
||||
|
||||
@ -343,7 +277,7 @@ def plot_spectrogram(
|
||||
signal: np.ndarray,
|
||||
samplerate: float,
|
||||
window_start_seconds: float,
|
||||
ylims: list[float],
|
||||
ylims: list[float]
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Plot a spectrogram of a signal.
|
||||
@ -384,7 +318,7 @@ def plot_spectrogram(
|
||||
aspect="auto",
|
||||
origin="lower",
|
||||
interpolation="gaussian",
|
||||
# alpha=0.6,
|
||||
alpha=1,
|
||||
)
|
||||
# axis.use_sticky_edges = False
|
||||
return spec_times
|
||||
@ -427,7 +361,9 @@ def extract_frequency_bands(
|
||||
q25, q75 = q50 - minimal_bandwidth / 2, q50 + minimal_bandwidth / 2
|
||||
|
||||
# filter baseline
|
||||
filtered_baseline = bandpass_filter(raw_data, samplerate, lowf=q25, highf=q75)
|
||||
filtered_baseline = bandpass_filter(
|
||||
raw_data, samplerate, lowf=q25, highf=q75
|
||||
)
|
||||
|
||||
# filter search area
|
||||
filtered_search_freq = bandpass_filter(
|
||||
@ -472,17 +408,20 @@ def window_median_all_track_ids(
|
||||
track_ids = []
|
||||
|
||||
for _, track_id in enumerate(np.unique(data.ident[~np.isnan(data.ident)])):
|
||||
|
||||
# the window index combines the track id and the time window
|
||||
window_idx = np.arange(len(data.idx))[
|
||||
(data.ident == track_id)
|
||||
& (data.time[data.idx] >= window_start_seconds)
|
||||
& (data.time[data.idx] <= (window_start_seconds + window_duration_seconds))
|
||||
& (
|
||||
data.time[data.idx]
|
||||
<= (window_start_seconds + window_duration_seconds)
|
||||
)
|
||||
]
|
||||
|
||||
if len(data.freq[window_idx]) > 0:
|
||||
frequency_percentiles.append(
|
||||
np.percentile(data.freq[window_idx], [25, 50, 75])
|
||||
)
|
||||
np.percentile(data.freq[window_idx], [25, 50, 75]))
|
||||
track_ids.append(track_id)
|
||||
|
||||
# convert to numpy array
|
||||
@ -492,71 +431,6 @@ def window_median_all_track_ids(
|
||||
return frequency_percentiles, track_ids
|
||||
|
||||
|
||||
def array_center(array: np.ndarray) -> float:
|
||||
"""
|
||||
Return the center value of an array.
|
||||
If the array length is even, returns
|
||||
the mean of the two center values.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
array : np.ndarray
|
||||
Array to calculate the center from.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
|
||||
"""
|
||||
if len(array) % 2 == 0:
|
||||
return np.mean(array[int(len(array) / 2) - 1 : int(len(array) / 2) + 1])
|
||||
else:
|
||||
return array[int(len(array) / 2)]
|
||||
|
||||
|
||||
def has_chirp(baseline_frequency: np.ndarray, peak_height: float) -> bool:
|
||||
"""
|
||||
Check if a fish has a chirp.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
baseline_frequency : np.ndarray
|
||||
Baseline frequency of the fish.
|
||||
peak_height : float
|
||||
Minimal peak height of a chirp on the instant. freq.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool: True if the fish has a chirp, False otherwise.
|
||||
"""
|
||||
peaks, _ = find_peaks(baseline_frequency, height=peak_height)
|
||||
if len(peaks) > 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def mask_low_amplitudes(envelope, threshold):
|
||||
"""
|
||||
Mask low amplitudes in the envelope.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
envelope : np.ndarray
|
||||
Envelope of the signal.
|
||||
threshold : float
|
||||
Threshold to mask low amplitudes.
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
|
||||
"""
|
||||
mask = np.ones_like(envelope, dtype=bool)
|
||||
mask[envelope < threshold] = False
|
||||
return mask
|
||||
|
||||
|
||||
def find_searchband(
|
||||
current_frequency: np.ndarray,
|
||||
percentiles_ids: np.ndarray,
|
||||
@ -590,34 +464,39 @@ def find_searchband(
|
||||
# frequency window where second filter filters is potentially allowed
|
||||
# to filter. This is the search window, in which we want to find
|
||||
# a gap in the other fish's EODs.
|
||||
current_median = np.median(current_frequency)
|
||||
|
||||
search_window = np.arange(
|
||||
current_median + config.search_df_lower,
|
||||
current_median + config.search_df_upper,
|
||||
np.median(current_frequency) + config.search_df_lower,
|
||||
np.median(current_frequency) + config.search_df_upper,
|
||||
config.search_res,
|
||||
)
|
||||
|
||||
# search window in boolean
|
||||
bool_lower = np.ones_like(search_window, dtype=bool)
|
||||
bool_upper = np.ones_like(search_window, dtype=bool)
|
||||
search_window_bool = np.ones_like(search_window, dtype=bool)
|
||||
search_window_bool = np.ones_like(len(search_window), dtype=bool)
|
||||
|
||||
# make seperate arrays from the qartiles
|
||||
q25 = np.asarray([i[0] for i in frequency_percentiles])
|
||||
q75 = np.asarray([i[2] for i in frequency_percentiles])
|
||||
|
||||
# get tracks that fall into search window
|
||||
check_track_ids = percentiles_ids[(q25 > current_median) & (q75 < search_window[-1])]
|
||||
check_track_ids = percentiles_ids[
|
||||
(q25 > search_window[0]) & (
|
||||
q75 < search_window[-1])
|
||||
]
|
||||
|
||||
# iterate through theses tracks
|
||||
if check_track_ids.size != 0:
|
||||
|
||||
for j, check_track_id in enumerate(check_track_ids):
|
||||
|
||||
q25_temp = q25[percentiles_ids == check_track_id]
|
||||
q75_temp = q75[percentiles_ids == check_track_id]
|
||||
|
||||
bool_lower[search_window > q25_temp - config.search_res] = False
|
||||
bool_upper[search_window < q75_temp + config.search_res] = False
|
||||
search_window_bool[(bool_lower == False) & (bool_upper == False)] = False
|
||||
print(q25_temp, q75_temp)
|
||||
|
||||
search_window_bool[
|
||||
(search_window > q25_temp) & (search_window < q75_temp)
|
||||
] = False
|
||||
|
||||
# find gaps in search window
|
||||
search_window_indices = np.arange(len(search_window))
|
||||
@ -630,13 +509,12 @@ def find_searchband(
|
||||
nonzeros = search_window_gaps[np.nonzero(search_window_gaps)[0]]
|
||||
nonzeros = nonzeros[~np.isnan(nonzeros)]
|
||||
|
||||
if len(nonzeros) == 0:
|
||||
return config.default_search_freq
|
||||
|
||||
# if the first value is -1, the array starst with true, so a gap
|
||||
if nonzeros[0] == -1:
|
||||
stops = search_window_indices[search_window_gaps == -1]
|
||||
starts = np.append(0, search_window_indices[search_window_gaps == 1])
|
||||
starts = np.append(
|
||||
0, search_window_indices[search_window_gaps == 1]
|
||||
)
|
||||
|
||||
# if the last value is -1, the array ends with true, so a gap
|
||||
if nonzeros[-1] == 1:
|
||||
@ -665,31 +543,24 @@ def find_searchband(
|
||||
# the center of the search frequency band is then the center of
|
||||
# the longest gap
|
||||
|
||||
search_freq = array_center(longest_search_window) - current_median
|
||||
search_freq = (
|
||||
longest_search_window[-1] - longest_search_window[0]
|
||||
) / 2
|
||||
|
||||
return search_freq
|
||||
|
||||
return config.default_search_freq
|
||||
|
||||
|
||||
def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
def main(datapath: str, plot: str) -> None:
|
||||
|
||||
assert plot in [
|
||||
"save",
|
||||
"show",
|
||||
"false",
|
||||
], "plot must be 'save', 'show' or 'false'"
|
||||
|
||||
assert debug in [
|
||||
"false",
|
||||
"electrode",
|
||||
"fish",
|
||||
], "debug must be 'false', 'electrode' or 'fish'"
|
||||
|
||||
if debug != "false":
|
||||
assert plot == "show", "debug mode only runs when plot is 'show'"
|
||||
|
||||
# load raw file
|
||||
print("datapath", datapath)
|
||||
data = LoadData(datapath)
|
||||
|
||||
# load config file
|
||||
@ -718,7 +589,7 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
raw_time = np.arange(data.raw.shape[0]) / data.raw_rate
|
||||
|
||||
# good chirp times for data: 2022-06-02-10_00
|
||||
# window_start_index = (3 * 60 * 60 + 6 * 60 + 43.5) * data.raw_rate
|
||||
# window_start_index = (3 * 60 * 60 + 6 * 60 + 43.5 + 5) * data.raw_rate
|
||||
# window_duration_index = 60 * data.raw_rate
|
||||
|
||||
# t0 = 0
|
||||
@ -742,7 +613,8 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
multiwindow_ids = []
|
||||
|
||||
for st, window_start_index in enumerate(window_start_indices):
|
||||
logger.info(f"Processing window {st} of {len(window_start_indices)}")
|
||||
|
||||
logger.info(f"Processing window {st+1} of {len(window_start_indices)}")
|
||||
|
||||
window_start_seconds = window_start_index / data.raw_rate
|
||||
window_duration_seconds = window_duration / data.raw_rate
|
||||
@ -756,7 +628,10 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
)
|
||||
|
||||
# iterate through all fish
|
||||
for tr, track_id in enumerate(np.unique(data.ident[~np.isnan(data.ident)])):
|
||||
for tr, track_id in enumerate(
|
||||
np.unique(data.ident[~np.isnan(data.ident)])
|
||||
):
|
||||
|
||||
logger.debug(f"Processing track {tr} of {len(data.ids)}")
|
||||
|
||||
# get index of track data in this time window
|
||||
@ -773,25 +648,37 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
current_frequencies = data.freq[track_window_index]
|
||||
current_powers = data.powers[track_window_index, :]
|
||||
|
||||
# approximate sampling rate to compute expected durations if there
|
||||
# is data available for this time window for this fish id
|
||||
|
||||
track_samplerate = np.mean(1 / np.diff(data.time))
|
||||
expected_duration = (
|
||||
(window_start_seconds + window_duration_seconds)
|
||||
- window_start_seconds
|
||||
) * track_samplerate
|
||||
|
||||
# check if tracked data available in this window
|
||||
if len(current_frequencies) < 3:
|
||||
logger.warning(f"Track {track_id} has no data in window {st}, skipping.")
|
||||
if len(current_frequencies) < expected_duration / 2:
|
||||
logger.warning(
|
||||
f"Track {track_id} has no data in window {st}, skipping."
|
||||
)
|
||||
continue
|
||||
|
||||
# check if there are powers available in this window
|
||||
nanchecker = np.unique(np.isnan(current_powers))
|
||||
if (len(nanchecker) == 1) and nanchecker[0] is True:
|
||||
logger.warning(
|
||||
f"No powers available for track {track_id} window {st}," "skipping."
|
||||
f"No powers available for track {track_id} window {st},"
|
||||
"skipping."
|
||||
)
|
||||
continue
|
||||
|
||||
# find the strongest electrodes for the current fish in the current
|
||||
# window
|
||||
|
||||
best_electrode_index = np.argsort(np.nanmean(current_powers, axis=0))[
|
||||
-config.number_electrodes :
|
||||
]
|
||||
best_electrode_index = np.argsort(
|
||||
np.nanmean(current_powers, axis=0)
|
||||
)[-config.number_electrodes:]
|
||||
|
||||
# find a frequency above the baseline of the current fish in which
|
||||
# no other fish is active to search for chirps there
|
||||
@ -811,8 +698,10 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
|
||||
# iterate through electrodes
|
||||
for el, electrode_index in enumerate(best_electrode_index):
|
||||
|
||||
logger.debug(
|
||||
f"Processing electrode {el+1} of " f"{len(best_electrode_index)}"
|
||||
f"Processing electrode {el+1} of "
|
||||
f"{len(best_electrode_index)}"
|
||||
)
|
||||
|
||||
# LOAD DATA FOR CURRENT ELECTRODE AND CURRENT FISH ------------
|
||||
@ -821,7 +710,9 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
current_raw_data = data.raw[
|
||||
window_start_index:window_stop_index, electrode_index
|
||||
]
|
||||
current_raw_time = raw_time[window_start_index:window_stop_index]
|
||||
current_raw_time = raw_time[
|
||||
window_start_index:window_stop_index
|
||||
]
|
||||
|
||||
# EXTRACT FEATURES --------------------------------------------
|
||||
|
||||
@ -843,13 +734,6 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
cutoff_frequency=config.baseline_envelope_cutoff,
|
||||
)
|
||||
|
||||
# create a mask that removes areas where amplitudes are very
|
||||
# because the instantaneous frequency is not reliable there
|
||||
|
||||
amplitude_mask = mask_low_amplitudes(
|
||||
baseline_envelope_unfiltered, config.baseline_min_amplitude
|
||||
)
|
||||
|
||||
# highpass filter baseline envelope to remove slower
|
||||
# fluctuations e.g. due to motion envelope
|
||||
|
||||
@ -860,10 +744,18 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
highf=config.baseline_envelope_bandpass_highf,
|
||||
)
|
||||
|
||||
# invert baseline envelope to find troughs in the baseline
|
||||
# highbass filter introduced filter effects, i.e. oscillations
|
||||
# around peaks. Compute the envelope of the highpass filtered
|
||||
# and inverted baseline envelope to remove these oscillations
|
||||
|
||||
baseline_envelope = -baseline_envelope
|
||||
|
||||
baseline_envelope = envelope(
|
||||
signal=baseline_envelope,
|
||||
samplerate=data.raw_rate,
|
||||
cutoff_frequency=config.baseline_envelope_envelope_cutoff,
|
||||
)
|
||||
|
||||
# compute the envelope of the search band. Peaks in the search
|
||||
# band envelope correspond to troughs in the baseline envelope
|
||||
# during chirps
|
||||
@ -873,7 +765,6 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
samplerate=data.raw_rate,
|
||||
cutoff_frequency=config.search_envelope_cutoff,
|
||||
)
|
||||
|
||||
search_envelope = search_envelope_unfiltered
|
||||
|
||||
# compute instantaneous frequency of the baseline band to find
|
||||
@ -883,34 +774,39 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
# chirp. This phenomenon is only observed on chirps on a narrow
|
||||
# filtered baseline such as the one we are working with.
|
||||
|
||||
baseline_frequency = instantaneous_frequency(
|
||||
baselineband,
|
||||
data.raw_rate,
|
||||
config.baseline_frequency_smoothing,
|
||||
(
|
||||
baseline_frequency_time,
|
||||
baseline_frequency,
|
||||
) = instantaneous_frequency(
|
||||
signal=baselineband,
|
||||
samplerate=data.raw_rate,
|
||||
smoothing_window=config.baseline_frequency_smoothing,
|
||||
)
|
||||
|
||||
# Take the absolute of the instantaneous frequency to invert
|
||||
# troughs into peaks. This is nessecary since the narrow
|
||||
# pass band introduces these anomalies. Also substract by the
|
||||
# median to set it to 0.
|
||||
# bandpass filter the instantaneous frequency to remove slow
|
||||
# fluctuations. Just as with the baseline envelope, we then
|
||||
# compute the envelope of the signal to remove the oscillations
|
||||
# around the peaks
|
||||
|
||||
baseline_frequency_samplerate = np.mean(
|
||||
np.diff(baseline_frequency_time)
|
||||
)
|
||||
|
||||
baseline_frequency_filtered = np.abs(
|
||||
baseline_frequency - np.median(baseline_frequency)
|
||||
)
|
||||
|
||||
# # check if there is at least one superthreshold peak on the
|
||||
# # instantaneous and exit the loop if not. This is used to
|
||||
# # prevent windows that do definetely not include a chirp
|
||||
# # to enter normalization, where small changes due to noise
|
||||
# # would be amplified
|
||||
baseline_frequency_filtered = highpass_filter(
|
||||
signal=baseline_frequency_filtered,
|
||||
samplerate=baseline_frequency_samplerate,
|
||||
cutoff=config.baseline_frequency_highpass_cutoff,
|
||||
)
|
||||
|
||||
# if not has_chirp(
|
||||
# baseline_frequency_filtered[amplitude_mask],
|
||||
# config.baseline_frequency_peakheight,
|
||||
# ):
|
||||
# logger.warning(
|
||||
# f"Amplitude to small for the chirp detection of track {track_id} window {st},")
|
||||
# continue
|
||||
baseline_frequency_filtered = envelope(
|
||||
signal=-baseline_frequency_filtered,
|
||||
samplerate=baseline_frequency_samplerate,
|
||||
cutoff_frequency=config.baseline_frequency_envelope_cutoff,
|
||||
)
|
||||
|
||||
# CUT OFF OVERLAP ---------------------------------------------
|
||||
|
||||
@ -921,7 +817,6 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
no_edges = np.arange(
|
||||
int(window_edge), len(baseline_envelope) - int(window_edge)
|
||||
)
|
||||
|
||||
current_raw_time = current_raw_time[no_edges]
|
||||
baselineband = baselineband[no_edges]
|
||||
baseline_envelope_unfiltered = baseline_envelope_unfiltered[no_edges]
|
||||
@ -930,57 +825,57 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
search_envelope_unfiltered = search_envelope_unfiltered[no_edges]
|
||||
search_envelope = search_envelope[no_edges]
|
||||
|
||||
# get instantaneous frequency withoup edges
|
||||
no_edges_t0 = int(window_edge) / data.raw_rate
|
||||
no_edges_t1 = baseline_frequency_time[-1] - (
|
||||
int(window_edge) / data.raw_rate
|
||||
)
|
||||
no_edges = (baseline_frequency_time >= no_edges_t0) & (
|
||||
baseline_frequency_time <= no_edges_t1
|
||||
)
|
||||
|
||||
baseline_frequency_filtered = baseline_frequency_filtered[
|
||||
no_edges
|
||||
]
|
||||
baseline_frequency = baseline_frequency[no_edges]
|
||||
baseline_frequency_filtered = baseline_frequency_filtered[no_edges]
|
||||
baseline_frequency_time = current_raw_time
|
||||
|
||||
# # get instantaneous frequency withoup edges
|
||||
# no_edges_t0 = int(window_edge) / data.raw_rate
|
||||
# no_edges_t1 = baseline_frequency_time[-1] - (
|
||||
# int(window_edge) / data.raw_rate
|
||||
# )
|
||||
# no_edges = (baseline_frequency_time >= no_edges_t0) & (
|
||||
# baseline_frequency_time <= no_edges_t1
|
||||
# )
|
||||
|
||||
# baseline_frequency_filtered = baseline_frequency_filtered[no_edges]
|
||||
# baseline_frequency = baseline_frequency[no_edges]
|
||||
# baseline_frequency_time = (
|
||||
# baseline_frequency_time[no_edges] + window_start_seconds
|
||||
# )
|
||||
baseline_frequency_time = (
|
||||
baseline_frequency_time[no_edges] + window_start_seconds
|
||||
)
|
||||
|
||||
# NORMALIZE ---------------------------------------------------
|
||||
|
||||
# normalize all three feature arrays to the same range to make
|
||||
# peak detection simpler
|
||||
|
||||
# baseline_envelope = minmaxnorm([baseline_envelope])[0]
|
||||
# search_envelope = minmaxnorm([search_envelope])[0]
|
||||
# baseline_frequency_filtered = minmaxnorm(
|
||||
# [baseline_frequency_filtered]
|
||||
# )[0]
|
||||
baseline_envelope = normalize([baseline_envelope])[0]
|
||||
search_envelope = normalize([search_envelope])[0]
|
||||
baseline_frequency_filtered = normalize(
|
||||
[baseline_frequency_filtered]
|
||||
)[0]
|
||||
|
||||
# PEAK DETECTION ----------------------------------------------
|
||||
|
||||
# detect peaks baseline_enelope
|
||||
baseline_peak_indices, _ = find_peaks(
|
||||
baseline_envelope, prominence=config.baseline_prominence
|
||||
baseline_envelope, prominence=config.prominence
|
||||
)
|
||||
# detect peaks search_envelope
|
||||
search_peak_indices, _ = find_peaks(
|
||||
search_envelope, prominence=config.search_prominence
|
||||
search_envelope, prominence=config.prominence
|
||||
)
|
||||
# detect peaks inst_freq_filtered
|
||||
frequency_peak_indices, _ = find_peaks(
|
||||
baseline_frequency_filtered,
|
||||
prominence=config.frequency_prominence,
|
||||
baseline_frequency_filtered, prominence=config.prominence
|
||||
)
|
||||
|
||||
# DETECT CHIRPS IN SEARCH WINDOW ------------------------------
|
||||
|
||||
# get the peak timestamps from the peak indices
|
||||
baseline_peak_timestamps = current_raw_time[baseline_peak_indices]
|
||||
search_peak_timestamps = current_raw_time[search_peak_indices]
|
||||
baseline_peak_timestamps = current_raw_time[
|
||||
baseline_peak_indices
|
||||
]
|
||||
search_peak_timestamps = current_raw_time[
|
||||
search_peak_indices]
|
||||
|
||||
frequency_peak_timestamps = baseline_frequency_time[
|
||||
frequency_peak_indices
|
||||
@ -988,13 +883,14 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
|
||||
# check if one list is empty and if so, skip to the next
|
||||
# electrode because a chirp cannot be detected if one is empty
|
||||
|
||||
one_feature_empty = (
|
||||
len(baseline_peak_timestamps) == 0
|
||||
or len(search_peak_timestamps) == 0
|
||||
# or len(frequency_peak_timestamps) == 0
|
||||
or len(frequency_peak_timestamps) == 0
|
||||
)
|
||||
|
||||
if one_feature_empty and (debug == "false"):
|
||||
if one_feature_empty:
|
||||
continue
|
||||
|
||||
# group peak across feature arrays but only if they
|
||||
@ -1003,34 +899,37 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
sublists = [
|
||||
list(baseline_peak_timestamps),
|
||||
list(search_peak_timestamps),
|
||||
# list(frequency_peak_timestamps),
|
||||
list(frequency_peak_timestamps),
|
||||
]
|
||||
|
||||
singleelectrode_chirps = group_timestamps(
|
||||
sublists=sublists,
|
||||
at_least_in=2,
|
||||
at_least_in=3,
|
||||
difference_threshold=config.chirp_window_threshold,
|
||||
)
|
||||
|
||||
# check it there are chirps detected after grouping, continue
|
||||
# with the loop if not
|
||||
|
||||
if (len(singleelectrode_chirps) == 0) and (debug == "false"):
|
||||
if len(singleelectrode_chirps) == 0:
|
||||
continue
|
||||
|
||||
# append chirps from this electrode to the multilectrode list
|
||||
multielectrode_chirps.append(singleelectrode_chirps)
|
||||
|
||||
# only initialize the plotting buffer if chirps are detected
|
||||
chirp_detected = el == (config.number_electrodes - 1) & (
|
||||
plot in ["show", "save"]
|
||||
chirp_detected = (
|
||||
(el == config.number_electrodes - 1)
|
||||
& (len(singleelectrode_chirps) > 0)
|
||||
& (plot in ["show", "save"])
|
||||
)
|
||||
|
||||
if chirp_detected or (debug != "elecrode"):
|
||||
if chirp_detected:
|
||||
|
||||
logger.debug("Detected chirp, ititialize buffer ...")
|
||||
|
||||
# save data to Buffer
|
||||
buffer = ChirpPlotBuffer(
|
||||
buffer = PlotBuffer(
|
||||
config=config,
|
||||
t0=window_start_seconds,
|
||||
dt=window_duration_seconds,
|
||||
@ -1055,10 +954,6 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
|
||||
logger.debug("Buffer initialized!")
|
||||
|
||||
if debug == "electrode":
|
||||
logger.info(f"Plotting electrode {el} ...")
|
||||
buffer.plot_buffer(chirps=singleelectrode_chirps, plot=plot)
|
||||
|
||||
logger.debug(
|
||||
f"Processed all electrodes for fish {track_id} for this"
|
||||
"window, sorting chirps ..."
|
||||
@ -1067,7 +962,7 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
# check if there are chirps detected in multiple electrodes and
|
||||
# continue the loop if not
|
||||
|
||||
if (len(multielectrode_chirps) == 0) and (debug == "false"):
|
||||
if len(multielectrode_chirps) == 0:
|
||||
continue
|
||||
|
||||
# validate multielectrode chirps, i.e. check if they are
|
||||
@ -1092,18 +987,9 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
# if chirps are detected and the plot flag is set, plot the
|
||||
# chirps, otheswise try to delete the buffer if it exists
|
||||
|
||||
if debug == "fish":
|
||||
logger.info(f"Plotting fish {track_id} ...")
|
||||
buffer.plot_buffer(multielectrode_chirps_validated, plot)
|
||||
|
||||
if (
|
||||
(len(multielectrode_chirps_validated) > 0)
|
||||
& (plot in ["show", "save"])
|
||||
& (debug == "false")
|
||||
):
|
||||
if len(multielectrode_chirps_validated) > 0:
|
||||
try:
|
||||
buffer.plot_buffer(multielectrode_chirps_validated, plot)
|
||||
del buffer
|
||||
except NameError:
|
||||
pass
|
||||
else:
|
||||
@ -1118,6 +1004,7 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
multiwindow_chirps_flat = []
|
||||
multiwindow_ids_flat = []
|
||||
for track_id in np.unique(multiwindow_ids):
|
||||
|
||||
# get chirps for this fish and flatten the list
|
||||
current_track_bool = np.asarray(multiwindow_ids) == track_id
|
||||
current_track_chirps = flatten(
|
||||
@ -1126,7 +1013,9 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
|
||||
# add flattened chirps to the list
|
||||
multiwindow_chirps_flat.extend(current_track_chirps)
|
||||
multiwindow_ids_flat.extend(list(np.ones_like(current_track_chirps) * track_id))
|
||||
multiwindow_ids_flat.extend(
|
||||
list(np.ones_like(current_track_chirps) * track_id)
|
||||
)
|
||||
|
||||
# purge duplicates, i.e. chirps that are very close to each other
|
||||
# duplites arise due to overlapping windows
|
||||
@ -1138,7 +1027,9 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
np.asarray(multiwindow_ids_flat) == track_id
|
||||
]
|
||||
if len(tr_chirps) > 0:
|
||||
tr_chirps_purged = purge_duplicates(tr_chirps, config.chirp_window_threshold)
|
||||
tr_chirps_purged = purge_duplicates(
|
||||
tr_chirps, config.chirp_window_threshold
|
||||
)
|
||||
purged_chirps.extend(list(tr_chirps_purged))
|
||||
purged_ids.extend(list(np.ones_like(tr_chirps_purged) * track_id))
|
||||
|
||||
@ -1149,14 +1040,13 @@ def chirpdetection(datapath: str, plot: str, debug: str = "false") -> None:
|
||||
purged_chirps = purged_chirps[np.argsort(purged_chirps)]
|
||||
|
||||
# save them into the data directory
|
||||
np.save(datapath + "chirp_times_gp.npy", purged_chirps)
|
||||
np.save(datapath + "chirp_ids_gp.npy", purged_ids)
|
||||
np.save(datapath + "chirps.npy", purged_chirps)
|
||||
np.save(datapath + "chirp_ids.npy", purged_ids)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# datapath = "/home/weygoldt/Data/uni/chirpdetection/GP2023_chirp_detection/data/mount_data/2020-05-13-10_00/"
|
||||
datapath = "../data/2022-06-02-10_00/"
|
||||
# datapath = "/home/weygoldt/Data/uni/efishdata/2016-colombia/fishgrid/2016-04-09-22_25/"
|
||||
# datapath = "/home/weygoldt/Data/uni/chirpdetection/GP2023_chirp_detection/data/mount_data/2020-03-13-10_00/"
|
||||
datapath = "../data/2022-06-02-10_00/"
|
||||
datapath = "../../../local_data/randgrid/"
|
||||
chirpdetection(datapath, plot="save", debug="false")
|
||||
main(datapath, plot="save")
|
||||
|
@ -1,46 +1,47 @@
|
||||
# Path setup ------------------------------------------------------------------
|
||||
# directory setup
|
||||
dataroot: "../data/"
|
||||
outputdir: "../output/"
|
||||
|
||||
dataroot: "../data/" # path to data
|
||||
outputdir: "../output/" # path to save plots to
|
||||
# Duration and overlap of the analysis window in seconds
|
||||
window: 10
|
||||
overlap: 1
|
||||
edge: 0.25
|
||||
|
||||
# Rolling window parameters ---------------------------------------------------
|
||||
# Number of electrodes to go over
|
||||
number_electrodes: 3
|
||||
minimum_electrodes: 2
|
||||
|
||||
window: 5 # rolling window length in seconds
|
||||
overlap: 1 # window overlap in seconds
|
||||
edge: 0.25 # window edge cufoffs to mitigate filter edge effects
|
||||
# Search window bandwidth and minimal baseline bandwidth
|
||||
minimal_bandwidth: 20
|
||||
|
||||
# Electrode iteration parameters ----------------------------------------------
|
||||
# Instantaneous frequency smoothing usint a gaussian kernel of this width
|
||||
baseline_frequency_smoothing: 5
|
||||
|
||||
number_electrodes: 2 # number of electrodes to go over
|
||||
minimum_electrodes: 1 # mimumun number of electrodes a chirp must be on
|
||||
# Baseline processing parameters
|
||||
baseline_envelope_cutoff: 25
|
||||
baseline_envelope_bandpass_lowf: 4
|
||||
baseline_envelope_bandpass_highf: 100
|
||||
baseline_envelope_envelope_cutoff: 4
|
||||
|
||||
# Feature extraction parameters -----------------------------------------------
|
||||
# search envelope processing parameters
|
||||
search_envelope_cutoff: 5
|
||||
|
||||
search_df_lower: 20 # start searching this far above the baseline
|
||||
search_df_upper: 100 # stop searching this far above the baseline
|
||||
search_res: 1 # search window resolution
|
||||
default_search_freq: 60 # search here if no need for a search frequency
|
||||
minimal_bandwidth: 10 # minimal bandpass filter width for baseline
|
||||
search_bandwidth: 10 # minimal bandpass filter width for search frequency
|
||||
baseline_frequency_smoothing: 3 # instantaneous frequency smoothing
|
||||
# Instantaneous frequency bandpass filter cutoff frequencies
|
||||
baseline_frequency_highpass_cutoff: 0.000005
|
||||
baseline_frequency_envelope_cutoff: 0.000005
|
||||
|
||||
# Feature processing parameters -----------------------------------------------
|
||||
# peak detecion parameters
|
||||
prominence: 0.005
|
||||
|
||||
baseline_frequency_peakheight: 5 # the min peak height of the baseline instfreq
|
||||
baseline_min_amplitude: 0.0001 # the minimal value of the baseline envelope
|
||||
baseline_envelope_cutoff: 25 # envelope estimation cutoff
|
||||
baseline_envelope_bandpass_lowf: 2 # envelope badpass lower cutoff
|
||||
baseline_envelope_bandpass_highf: 100 # envelope bandbass higher cutoff
|
||||
search_envelope_cutoff: 10 # search envelope estimation cufoff
|
||||
# search freq parameter
|
||||
search_df_lower: 20
|
||||
search_df_upper: 100
|
||||
search_res: 1
|
||||
search_bandwidth: 10
|
||||
default_search_freq: 50
|
||||
|
||||
# Classify events as chirps if they are less than this time apart
|
||||
chirp_window_threshold: 0.05
|
||||
|
||||
# Peak detecion parameters ----------------------------------------------------
|
||||
# baseline_prominence: 0.00005 # peak prominence threshold for baseline envelope
|
||||
# search_prominence: 0.000004 # peak prominence threshold for search envelope
|
||||
# frequency_prominence: 2 # peak prominence threshold for baseline freq
|
||||
|
||||
baseline_prominence: 0.00005 # peak prominence threshold for baseline envelope
|
||||
search_prominence: 0.000005 # peak prominence threshold for search envelope
|
||||
frequency_prominence: 1 # peak prominence threshold for baseline freq
|
||||
|
||||
# Classify events as chirps if they are less than this time apart
|
||||
chirp_window_threshold: 0.02
|
||||
|
@ -1,669 +0,0 @@
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from tqdm import tqdm
|
||||
from IPython import embed
|
||||
from pandas import read_csv
|
||||
from modules.logger import makeLogger
|
||||
from modules.plotstyle import PlotStyle
|
||||
from modules.datahandling import causal_kde1d, acausal_kde1d, flatten
|
||||
|
||||
logger = makeLogger(__name__)
|
||||
ps = PlotStyle()
|
||||
|
||||
|
||||
class Behavior:
|
||||
"""Load behavior data from csv file as class attributes
|
||||
Attributes
|
||||
----------
|
||||
behavior: 0: chasing onset, 1: chasing offset, 2: physical contact
|
||||
behavior_type:
|
||||
behavioral_category:
|
||||
comment_start:
|
||||
comment_stop:
|
||||
dataframe: pandas dataframe with all the data
|
||||
duration_s:
|
||||
media_file:
|
||||
observation_date:
|
||||
observation_id:
|
||||
start_s: start time of the event in seconds
|
||||
stop_s: stop time of the event in seconds
|
||||
total_length:
|
||||
"""
|
||||
|
||||
def __init__(self, folder_path: str) -> None:
|
||||
print(f"{folder_path}")
|
||||
LED_on_time_BORIS = np.load(
|
||||
os.path.join(folder_path, "LED_on_time.npy"), allow_pickle=True
|
||||
)
|
||||
self.time = np.load(
|
||||
os.path.join(folder_path, "times.npy"), allow_pickle=True
|
||||
)
|
||||
csv_filename = [
|
||||
f for f in os.listdir(folder_path) if f.endswith(".csv")
|
||||
][
|
||||
0
|
||||
] # check if there are more than one csv file
|
||||
self.dataframe = read_csv(os.path.join(folder_path, csv_filename))
|
||||
self.chirps = np.load(
|
||||
os.path.join(folder_path, "chirps.npy"), allow_pickle=True
|
||||
)
|
||||
self.chirps_ids = np.load(
|
||||
os.path.join(folder_path, "chirp_ids.npy"), allow_pickle=True
|
||||
)
|
||||
|
||||
for k, key in enumerate(self.dataframe.keys()):
|
||||
key = key.lower()
|
||||
if " " in key:
|
||||
key = key.replace(" ", "_")
|
||||
if "(" in key:
|
||||
key = key.replace("(", "")
|
||||
key = key.replace(")", "")
|
||||
setattr(
|
||||
self, key, np.array(self.dataframe[self.dataframe.keys()[k]])
|
||||
)
|
||||
|
||||
last_LED_t_BORIS = LED_on_time_BORIS[-1]
|
||||
real_time_range = self.time[-1] - self.time[0]
|
||||
factor = 1.034141
|
||||
shift = last_LED_t_BORIS - real_time_range * factor
|
||||
self.start_s = (self.start_s - shift) / factor
|
||||
self.stop_s = (self.stop_s - shift) / factor
|
||||
|
||||
|
||||
"""
|
||||
1 - chasing onset
|
||||
2 - chasing offset
|
||||
3 - physical contact event
|
||||
|
||||
temporal encpding needs to be corrected ... not exactly 25FPS.
|
||||
|
||||
### correspinding python code ###
|
||||
|
||||
factor = 1.034141
|
||||
LED_on_time_BORIS = np.load(os.path.join(folder_path, 'LED_on_time.npy'), allow_pickle=True)
|
||||
last_LED_t_BORIS = LED_on_time_BORIS[-1]
|
||||
real_time_range = times[-1] - times[0]
|
||||
shift = last_LED_t_BORIS - real_time_range * factor
|
||||
|
||||
data = pd.read_csv(os.path.join(folder_path, file[1:-7] + '.csv'))
|
||||
boris_times = data['Start (s)']
|
||||
data_times = []
|
||||
|
||||
for Cevent_t in boris_times:
|
||||
Cevent_boris_times = (Cevent_t - shift) / factor
|
||||
data_times.append(Cevent_boris_times)
|
||||
|
||||
data_times = np.array(data_times)
|
||||
behavior = data['Behavior']
|
||||
"""
|
||||
|
||||
|
||||
def correct_chasing_events(
|
||||
category: np.ndarray, timestamps: np.ndarray
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
onset_ids = np.arange(len(category))[category == 0]
|
||||
offset_ids = np.arange(len(category))[category == 1]
|
||||
|
||||
wrong_bh = np.arange(len(category))[category != 2][:-1][
|
||||
np.diff(category[category != 2]) == 0
|
||||
]
|
||||
if onset_ids[0] > offset_ids[0]:
|
||||
offset_ids = np.delete(offset_ids, 0)
|
||||
help_index = offset_ids[0]
|
||||
wrong_bh = np.append(wrong_bh[help_index])
|
||||
|
||||
category = np.delete(category, wrong_bh)
|
||||
timestamps = np.delete(timestamps, wrong_bh)
|
||||
|
||||
# Check whether on- or offset is longer and calculate length difference
|
||||
if len(onset_ids) > len(offset_ids):
|
||||
len_diff = len(onset_ids) - len(offset_ids)
|
||||
logger.info(f"Onsets are greater than offsets by {len_diff}")
|
||||
elif len(onset_ids) < len(offset_ids):
|
||||
len_diff = len(offset_ids) - len(onset_ids)
|
||||
logger.info(f"Offsets are greater than onsets by {len_diff}")
|
||||
elif len(onset_ids) == len(offset_ids):
|
||||
logger.info("Chasing events are equal")
|
||||
|
||||
return category, timestamps
|
||||
|
||||
|
||||
def event_triggered_chirps(
|
||||
event: np.ndarray,
|
||||
chirps: np.ndarray,
|
||||
time_before_event: int,
|
||||
time_after_event: int,
|
||||
dt: float,
|
||||
width: float,
|
||||
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
||||
event_chirps = [] # chirps that are in specified window around event
|
||||
# timestamps of chirps around event centered on the event timepoint
|
||||
centered_chirps = []
|
||||
|
||||
for event_timestamp in event:
|
||||
start = event_timestamp - time_before_event
|
||||
stop = event_timestamp + time_after_event
|
||||
chirps_around_event = [c for c in chirps if (c >= start) & (c <= stop)]
|
||||
event_chirps.append(chirps_around_event)
|
||||
if len(chirps_around_event) == 0:
|
||||
continue
|
||||
else:
|
||||
centered_chirps.append(chirps_around_event - event_timestamp)
|
||||
|
||||
time = np.arange(-time_before_event, time_after_event, dt)
|
||||
|
||||
# Kernel density estimation with some if's
|
||||
if len(centered_chirps) == 0:
|
||||
centered_chirps = np.array([])
|
||||
centered_chirps_convolved = np.zeros(len(time))
|
||||
else:
|
||||
# convert list of arrays to one array for plotting
|
||||
centered_chirps = np.concatenate(centered_chirps, axis=0)
|
||||
centered_chirps_convolved = (
|
||||
acausal_kde1d(centered_chirps, time, width)
|
||||
) / len(event)
|
||||
|
||||
return event_chirps, centered_chirps, centered_chirps_convolved
|
||||
|
||||
|
||||
def main(datapath: str):
|
||||
foldernames = [
|
||||
datapath + x + "/"
|
||||
for x in os.listdir(datapath)
|
||||
if os.path.isdir(datapath + x)
|
||||
]
|
||||
|
||||
nrecording_chirps = []
|
||||
nrecording_chirps_fish_ids = []
|
||||
nrecording_chasing_onsets = []
|
||||
nrecording_chasing_offsets = []
|
||||
nrecording_physicals = []
|
||||
|
||||
# Iterate over all recordings and save chirp- and event-timestamps
|
||||
for folder in foldernames:
|
||||
# exclude folder with empty LED_on_time.npy
|
||||
if folder == "../data/mount_data/2020-05-12-10_00/":
|
||||
continue
|
||||
|
||||
bh = Behavior(folder)
|
||||
|
||||
# Chirps are already sorted
|
||||
category = bh.behavior
|
||||
timestamps = bh.start_s
|
||||
chirps = bh.chirps
|
||||
nrecording_chirps.append(chirps)
|
||||
chirps_fish_ids = bh.chirps_ids
|
||||
nrecording_chirps_fish_ids.append(chirps_fish_ids)
|
||||
fish_ids = np.unique(chirps_fish_ids)
|
||||
|
||||
# Correct for doubles in chasing on- and offsets to get the right on-/offset pairs
|
||||
# Get rid of tracking faults (two onsets or two offsets after another)
|
||||
category, timestamps = correct_chasing_events(category, timestamps)
|
||||
|
||||
# Split categories
|
||||
chasing_onsets = timestamps[category == 0]
|
||||
nrecording_chasing_onsets.append(chasing_onsets)
|
||||
chasing_offsets = timestamps[category == 1]
|
||||
nrecording_chasing_offsets.append(chasing_offsets)
|
||||
physical_contacts = timestamps[category == 2]
|
||||
nrecording_physicals.append(physical_contacts)
|
||||
|
||||
# Define time window for chirps around event analysis
|
||||
time_before_event = 30
|
||||
time_after_event = 60
|
||||
dt = 0.01
|
||||
width = 1.5 # width of kernel for all recordings, currently gaussian kernel
|
||||
recording_width = 2 # width of kernel for each recording
|
||||
time = np.arange(-time_before_event, time_after_event, dt)
|
||||
|
||||
##### Chirps around events, all fish, all recordings #####
|
||||
# Centered chirps per event type
|
||||
nrecording_centered_onset_chirps = []
|
||||
nrecording_centered_offset_chirps = []
|
||||
nrecording_centered_physical_chirps = []
|
||||
# Bootstrapped chirps per recording and per event: 27[1000[n]] 27 recs, 1000 shuffles, n chirps
|
||||
nrecording_shuffled_convolved_onset_chirps = []
|
||||
nrecording_shuffled_convolved_offset_chirps = []
|
||||
nrecording_shuffled_convolved_physical_chirps = []
|
||||
|
||||
nbootstrapping = 100
|
||||
|
||||
for i in range(len(nrecording_chirps)):
|
||||
chirps = nrecording_chirps[i]
|
||||
chasing_onsets = nrecording_chasing_onsets[i]
|
||||
chasing_offsets = nrecording_chasing_offsets[i]
|
||||
physical_contacts = nrecording_physicals[i]
|
||||
|
||||
# Chirps around chasing onsets
|
||||
(
|
||||
_,
|
||||
centered_chasing_onset_chirps,
|
||||
cc_chasing_onset_chirps,
|
||||
) = event_triggered_chirps(
|
||||
chasing_onsets,
|
||||
chirps,
|
||||
time_before_event,
|
||||
time_after_event,
|
||||
dt,
|
||||
recording_width,
|
||||
)
|
||||
# Chirps around chasing offsets
|
||||
(
|
||||
_,
|
||||
centered_chasing_offset_chirps,
|
||||
cc_chasing_offset_chirps,
|
||||
) = event_triggered_chirps(
|
||||
chasing_offsets,
|
||||
chirps,
|
||||
time_before_event,
|
||||
time_after_event,
|
||||
dt,
|
||||
recording_width,
|
||||
)
|
||||
# Chirps around physical contacts
|
||||
(
|
||||
_,
|
||||
centered_physical_chirps,
|
||||
cc_physical_chirps,
|
||||
) = event_triggered_chirps(
|
||||
physical_contacts,
|
||||
chirps,
|
||||
time_before_event,
|
||||
time_after_event,
|
||||
dt,
|
||||
recording_width,
|
||||
)
|
||||
|
||||
nrecording_centered_onset_chirps.append(centered_chasing_onset_chirps)
|
||||
nrecording_centered_offset_chirps.append(centered_chasing_offset_chirps)
|
||||
nrecording_centered_physical_chirps.append(centered_physical_chirps)
|
||||
|
||||
## Shuffled chirps ##
|
||||
nshuffled_onset_chirps = []
|
||||
nshuffled_offset_chirps = []
|
||||
nshuffled_physical_chirps = []
|
||||
|
||||
# for j in tqdm(range(nbootstrapping)):
|
||||
# # Calculate interchirp intervals; add first chirp timestamp in beginning to get equal lengths
|
||||
# interchirp_intervals = np.append(np.array([chirps[0]]), np.diff(chirps))
|
||||
# np.random.shuffle(interchirp_intervals)
|
||||
# shuffled_chirps = np.cumsum(interchirp_intervals)
|
||||
# # Shuffled chasing onset chirps
|
||||
# _, _, cc_shuffled_onset_chirps = event_triggered_chirps(chasing_onsets, shuffled_chirps, time_before_event, time_after_event, dt, recording_width)
|
||||
# nshuffled_onset_chirps.append(cc_shuffled_onset_chirps)
|
||||
# # Shuffled chasing offset chirps
|
||||
# _, _, cc_shuffled_offset_chirps = event_triggered_chirps(chasing_offsets, shuffled_chirps, time_before_event, time_after_event, dt, recording_width)
|
||||
# nshuffled_offset_chirps.append(cc_shuffled_offset_chirps)
|
||||
# # Shuffled physical contact chirps
|
||||
# _, _, cc_shuffled_physical_chirps = event_triggered_chirps(physical_contacts, shuffled_chirps, time_before_event, time_after_event, dt, recording_width)
|
||||
# nshuffled_physical_chirps.append(cc_shuffled_physical_chirps)
|
||||
|
||||
# rec_shuffled_q5_onset, rec_shuffled_median_onset, rec_shuffled_q95_onset = np.percentile(
|
||||
# nshuffled_onset_chirps, (5, 50, 95), axis=0)
|
||||
# rec_shuffled_q5_offset, rec_shuffled_median_offset, rec_shuffled_q95_offset = np.percentile(
|
||||
# nshuffled_offset_chirps, (5, 50, 95), axis=0)
|
||||
# rec_shuffled_q5_physical, rec_shuffled_median_physical, rec_shuffled_q95_physical = np.percentile(
|
||||
# nshuffled_physical_chirps, (5, 50, 95), axis=0)
|
||||
|
||||
# #### Recording plots ####
|
||||
# fig, ax = plt.subplots(1, 3, figsize=(28*ps.cm, 16*ps.cm, ), constrained_layout=True, sharey='all')
|
||||
# ax[0].set_xlabel('Time[s]')
|
||||
|
||||
# # Plot chasing onsets
|
||||
# ax[0].set_ylabel('Chirp rate [Hz]')
|
||||
# ax[0].plot(time, cc_chasing_onset_chirps, color=ps.yellow, zorder=2)
|
||||
# ax0 = ax[0].twinx()
|
||||
# ax0.eventplot(centered_chasing_onset_chirps, linelengths=0.2, colors=ps.gray, alpha=0.25, zorder=1)
|
||||
# ax0.vlines(0, 0, 1.5, ps.white, 'dashed')
|
||||
# ax[0].set_zorder(ax0.get_zorder()+1)
|
||||
# ax[0].patch.set_visible(False)
|
||||
# ax0.set_yticklabels([])
|
||||
# ax0.set_yticks([])
|
||||
# ######## median - q5, median + q95
|
||||
# ax[0].fill_between(time, rec_shuffled_q5_onset, rec_shuffled_q95_onset, color=ps.gray, alpha=0.5)
|
||||
# ax[0].plot(time, rec_shuffled_median_onset, color=ps.black)
|
||||
|
||||
# # Plot chasing offets
|
||||
# ax[1].set_xlabel('Time[s]')
|
||||
# ax[1].plot(time, cc_chasing_offset_chirps, color=ps.orange, zorder=2)
|
||||
# ax1 = ax[1].twinx()
|
||||
# ax1.eventplot(centered_chasing_offset_chirps, linelengths=0.2, colors=ps.gray, alpha=0.25, zorder=1)
|
||||
# ax1.vlines(0, 0, 1.5, ps.white, 'dashed')
|
||||
# ax[1].set_zorder(ax1.get_zorder()+1)
|
||||
# ax[1].patch.set_visible(False)
|
||||
# ax1.set_yticklabels([])
|
||||
# ax1.set_yticks([])
|
||||
# ax[1].fill_between(time, rec_shuffled_q5_offset, rec_shuffled_q95_offset, color=ps.gray, alpha=0.5)
|
||||
# ax[1].plot(time, rec_shuffled_median_offset, color=ps.black)
|
||||
|
||||
# # Plot physical contacts
|
||||
# ax[2].set_xlabel('Time[s]')
|
||||
# ax[2].plot(time, cc_physical_chirps, color=ps.maroon, zorder=2)
|
||||
# ax2 = ax[2].twinx()
|
||||
# ax2.eventplot(centered_physical_chirps, linelengths=0.2, colors=ps.gray, alpha=0.25, zorder=1)
|
||||
# ax2.vlines(0, 0, 1.5, ps.white, 'dashed')
|
||||
# ax[2].set_zorder(ax2.get_zorder()+1)
|
||||
# ax[2].patch.set_visible(False)
|
||||
# ax2.set_yticklabels([])
|
||||
# ax2.set_yticks([])
|
||||
# ax[2].fill_between(time, rec_shuffled_q5_physical, rec_shuffled_q95_physical, color=ps.gray, alpha=0.5)
|
||||
# ax[2].plot(time, rec_shuffled_median_physical, ps.black)
|
||||
# fig.suptitle(f'Recording: {i}')
|
||||
# # plt.show()
|
||||
# plt.close()
|
||||
|
||||
# nrecording_shuffled_convolved_onset_chirps.append(nshuffled_onset_chirps)
|
||||
# nrecording_shuffled_convolved_offset_chirps.append(nshuffled_offset_chirps)
|
||||
# nrecording_shuffled_convolved_physical_chirps.append(nshuffled_physical_chirps)
|
||||
|
||||
#### New shuffle approach ####
|
||||
bootstrap_onset = []
|
||||
bootstrap_offset = []
|
||||
bootstrap_physical = []
|
||||
|
||||
# New bootstrapping approach
|
||||
for n in range(nbootstrapping):
|
||||
diff_onset = np.diff(np.sort(flatten(nrecording_centered_onset_chirps)))
|
||||
diff_offset = np.diff(
|
||||
np.sort(flatten(nrecording_centered_offset_chirps))
|
||||
)
|
||||
diff_physical = np.diff(
|
||||
np.sort(flatten(nrecording_centered_physical_chirps))
|
||||
)
|
||||
|
||||
np.random.shuffle(diff_onset)
|
||||
shuffled_onset = np.cumsum(diff_onset)
|
||||
np.random.shuffle(diff_offset)
|
||||
shuffled_offset = np.cumsum(diff_offset)
|
||||
np.random.shuffle(diff_physical)
|
||||
shuffled_physical = np.cumsum(diff_physical)
|
||||
|
||||
kde_onset(acausal_kde1d(shuffled_onset, time, width)) / (27 * 100)
|
||||
kde_offset = (acausal_kde1d(shuffled_offset, time, width)) / (27 * 100)
|
||||
kde_physical = (acausal_kde1d(shuffled_physical, time, width)) / (
|
||||
27 * 100
|
||||
)
|
||||
|
||||
bootstrap_onset.append(kde_onset)
|
||||
bootstrap_offset.append(kde_offset)
|
||||
bootstrap_physical.append(kde_physical)
|
||||
|
||||
# New shuffle approach q5, q50, q95
|
||||
onset_q5, onset_median, onset_q95 = np.percentile(
|
||||
bootstrap_onset, [5, 50, 95], axis=0
|
||||
)
|
||||
offset_q5, offset_median, offset_q95 = np.percentile(
|
||||
bootstrap_offset, [5, 50, 95], axis=0
|
||||
)
|
||||
physical_q5, physical_median, physical_q95 = np.percentile(
|
||||
bootstrap_physical, [5, 50, 95], axis=0
|
||||
)
|
||||
|
||||
# vstack um 1. Dim zu cutten
|
||||
# nrecording_shuffled_convolved_onset_chirps = np.vstack(nrecording_shuffled_convolved_onset_chirps)
|
||||
# nrecording_shuffled_convolved_offset_chirps = np.vstack(nrecording_shuffled_convolved_offset_chirps)
|
||||
# nrecording_shuffled_convolved_physical_chirps = np.vstack(nrecording_shuffled_convolved_physical_chirps)
|
||||
|
||||
# shuffled_q5_onset, shuffled_median_onset, shuffled_q95_onset = np.percentile(
|
||||
# nrecording_shuffled_convolved_onset_chirps, (5, 50, 95), axis=0)
|
||||
# shuffled_q5_offset, shuffled_median_offset, shuffled_q95_offset = np.percentile(
|
||||
# nrecording_shuffled_convolved_offset_chirps, (5, 50, 95), axis=0)
|
||||
# shuffled_q5_physical, shuffled_median_physical, shuffled_q95_physical = np.percentile(
|
||||
# nrecording_shuffled_convolved_physical_chirps, (5, 50, 95), axis=0)
|
||||
|
||||
# Flatten all chirps
|
||||
all_chirps = np.concatenate(nrecording_chirps).ravel() # not centered
|
||||
|
||||
# Flatten event timestamps
|
||||
all_onsets = np.concatenate(
|
||||
nrecording_chasing_onsets
|
||||
).ravel() # not centered
|
||||
all_offsets = np.concatenate(
|
||||
nrecording_chasing_offsets
|
||||
).ravel() # not centered
|
||||
all_physicals = np.concatenate(nrecording_physicals).ravel() # not centered
|
||||
|
||||
# Flatten all chirps around events
|
||||
all_onset_chirps = np.concatenate(
|
||||
nrecording_centered_onset_chirps
|
||||
).ravel() # centered
|
||||
all_offset_chirps = np.concatenate(
|
||||
nrecording_centered_offset_chirps
|
||||
).ravel() # centered
|
||||
all_physical_chirps = np.concatenate(
|
||||
nrecording_centered_physical_chirps
|
||||
).ravel() # centered
|
||||
|
||||
# Convolute all chirps
|
||||
# Divide by total number of each event over all recordings
|
||||
all_onset_chirps_convolved = (
|
||||
acausal_kde1d(all_onset_chirps, time, width)
|
||||
) / len(all_onsets)
|
||||
all_offset_chirps_convolved = (
|
||||
acausal_kde1d(all_offset_chirps, time, width)
|
||||
) / len(all_offsets)
|
||||
all_physical_chirps_convolved = (
|
||||
acausal_kde1d(all_physical_chirps, time, width)
|
||||
) / len(all_physicals)
|
||||
|
||||
# Plot all events with all shuffled
|
||||
fig, ax = plt.subplots(
|
||||
1,
|
||||
3,
|
||||
figsize=(
|
||||
28 * ps.cm,
|
||||
16 * ps.cm,
|
||||
),
|
||||
constrained_layout=True,
|
||||
sharey="all",
|
||||
)
|
||||
# offsets = np.arange(1,28,1)
|
||||
ax[0].set_xlabel("Time[s]")
|
||||
|
||||
# Plot chasing onsets
|
||||
ax[0].set_ylabel("Chirp rate [Hz]")
|
||||
ax[0].plot(time, all_onset_chirps_convolved, color=ps.yellow, zorder=2)
|
||||
ax0 = ax[0].twinx()
|
||||
nrecording_centered_onset_chirps = np.asarray(
|
||||
nrecording_centered_onset_chirps, dtype=object
|
||||
)
|
||||
ax0.eventplot(
|
||||
np.array(nrecording_centered_onset_chirps),
|
||||
linelengths=0.5,
|
||||
colors=ps.gray,
|
||||
alpha=0.25,
|
||||
zorder=1,
|
||||
)
|
||||
ax0.vlines(0, 0, 1.5, ps.white, "dashed")
|
||||
ax[0].set_zorder(ax0.get_zorder() + 1)
|
||||
ax[0].patch.set_visible(False)
|
||||
ax0.set_yticklabels([])
|
||||
ax0.set_yticks([])
|
||||
# ax[0].fill_between(time, shuffled_q5_onset, shuffled_q95_onset, color=ps.gray, alpha=0.5)
|
||||
# ax[0].plot(time, shuffled_median_onset, color=ps.black)
|
||||
ax[0].fill_between(time, onset_q5, onset_q95, color=ps.gray, alpha=0.5)
|
||||
ax[0].plot(time, onset_median, color=ps.black)
|
||||
|
||||
# Plot chasing offets
|
||||
ax[1].set_xlabel("Time[s]")
|
||||
ax[1].plot(time, all_offset_chirps_convolved, color=ps.orange, zorder=2)
|
||||
ax1 = ax[1].twinx()
|
||||
nrecording_centered_offset_chirps = np.asarray(
|
||||
nrecording_centered_offset_chirps, dtype=object
|
||||
)
|
||||
ax1.eventplot(
|
||||
np.array(nrecording_centered_offset_chirps),
|
||||
linelengths=0.5,
|
||||
colors=ps.gray,
|
||||
alpha=0.25,
|
||||
zorder=1,
|
||||
)
|
||||
ax1.vlines(0, 0, 1.5, ps.white, "dashed")
|
||||
ax[1].set_zorder(ax1.get_zorder() + 1)
|
||||
ax[1].patch.set_visible(False)
|
||||
ax1.set_yticklabels([])
|
||||
ax1.set_yticks([])
|
||||
# ax[1].fill_between(time, shuffled_q5_offset, shuffled_q95_offset, color=ps.gray, alpha=0.5)
|
||||
# ax[1].plot(time, shuffled_median_offset, color=ps.black)
|
||||
ax[1].fill_between(time, offset_q5, offset_q95, color=ps.gray, alpha=0.5)
|
||||
ax[1].plot(time, offset_median, color=ps.black)
|
||||
|
||||
# Plot physical contacts
|
||||
ax[2].set_xlabel("Time[s]")
|
||||
ax[2].plot(time, all_physical_chirps_convolved, color=ps.maroon, zorder=2)
|
||||
ax2 = ax[2].twinx()
|
||||
nrecording_centered_physical_chirps = np.asarray(
|
||||
nrecording_centered_physical_chirps, dtype=object
|
||||
)
|
||||
ax2.eventplot(
|
||||
np.array(nrecording_centered_physical_chirps),
|
||||
linelengths=0.5,
|
||||
colors=ps.gray,
|
||||
alpha=0.25,
|
||||
zorder=1,
|
||||
)
|
||||
ax2.vlines(0, 0, 1.5, ps.white, "dashed")
|
||||
ax[2].set_zorder(ax2.get_zorder() + 1)
|
||||
ax[2].patch.set_visible(False)
|
||||
ax2.set_yticklabels([])
|
||||
ax2.set_yticks([])
|
||||
# ax[2].fill_between(time, shuffled_q5_physical, shuffled_q95_physical, color=ps.gray, alpha=0.5)
|
||||
# ax[2].plot(time, shuffled_median_physical, ps.black)
|
||||
ax[2].fill_between(
|
||||
time, physical_q5, physical_q95, color=ps.gray, alpha=0.5
|
||||
)
|
||||
ax[2].plot(time, physical_median, ps.black)
|
||||
fig.suptitle("All recordings")
|
||||
plt.show()
|
||||
plt.close()
|
||||
|
||||
embed()
|
||||
|
||||
# chasing_durations = []
|
||||
# # Calculate chasing duration to evaluate a nice time window for kernel density estimation
|
||||
# for onset, offset in zip(chasing_onsets, chasing_offsets):
|
||||
# duration = offset - onset
|
||||
# chasing_durations.append(duration)
|
||||
|
||||
# fig, ax = plt.subplots()
|
||||
# ax.boxplot(chasing_durations)
|
||||
# plt.show()
|
||||
# plt.close()
|
||||
|
||||
# # Associate chirps to individual fish
|
||||
# fish1 = chirps[chirps_fish_ids == fish_ids[0]]
|
||||
# fish2 = chirps[chirps_fish_ids == fish_ids[1]]
|
||||
# fish = [len(fish1), len(fish2)]
|
||||
|
||||
# Convolution over all recordings
|
||||
# Rasterplot for each recording
|
||||
|
||||
# #### Chirps around events, winner VS loser, one recording ####
|
||||
# # Load file with fish ids and winner/loser info
|
||||
# meta = pd.read_csv('../data/mount_data/order_meta.csv')
|
||||
# current_recording = meta[meta.index == 43]
|
||||
# fish1 = current_recording['rec_id1'].values
|
||||
# fish2 = current_recording['rec_id2'].values
|
||||
# # Implement check if fish_ids from meta and chirp detection are the same???
|
||||
# winner = current_recording['winner'].values
|
||||
|
||||
# if winner == fish1:
|
||||
# loser = fish2
|
||||
# elif winner == fish2:
|
||||
# loser = fish1
|
||||
|
||||
# winner_chirps = chirps[chirps_fish_ids == winner]
|
||||
# loser_chirps = chirps[chirps_fish_ids == loser]
|
||||
|
||||
# # Event triggered winner chirps
|
||||
# _, winner_centered_onset, winner_cc_onset = event_triggered_chirps(chasing_onsets, winner_chirps, time_before_event, time_after_event, dt, width)
|
||||
# _, winner_centered_offset, winner_cc_offset = event_triggered_chirps(chasing_offsets, winner_chirps, time_before_event, time_after_event, dt, width)
|
||||
# _, winner_centered_physical, winner_cc_physical = event_triggered_chirps(physical_contacts, winner_chirps, time_before_event, time_after_event, dt, width)
|
||||
|
||||
# # Event triggered loser chirps
|
||||
# _, loser_centered_onset, loser_cc_onset = event_triggered_chirps(chasing_onsets, loser_chirps, time_before_event, time_after_event, dt, width)
|
||||
# _, loser_centered_offset, loser_cc_offset = event_triggered_chirps(chasing_offsets, loser_chirps, time_before_event, time_after_event, dt, width)
|
||||
# _, loser_centered_physical, loser_cc_physical = event_triggered_chirps(physical_contacts, loser_chirps, time_before_event, time_after_event, dt, width)
|
||||
|
||||
# ########## Winner VS Loser plot ##########
|
||||
# fig, ax = plt.subplots(2, 3, figsize=(50 / 2.54, 15 / 2.54), constrained_layout=True, sharey='row')
|
||||
# offset = [1.35]
|
||||
# ax[1][0].set_xlabel('Time[s]')
|
||||
# ax[1][1].set_xlabel('Time[s]')
|
||||
# ax[1][2].set_xlabel('Time[s]')
|
||||
# # Plot winner chasing onsets
|
||||
# ax[0][0].set_ylabel('Chirp rate [Hz]')
|
||||
# ax[0][0].plot(time, winner_cc_onset, color='tab:blue', zorder=100)
|
||||
# ax0 = ax[0][0].twinx()
|
||||
# ax0.eventplot(np.array([winner_centered_onset]), lineoffsets=offset, linelengths=0.1, colors=['tab:green'], alpha=0.25, zorder=-100)
|
||||
# ax0.set_ylabel('Event')
|
||||
# ax0.vlines(0, 0, 1.5, 'tab:grey', 'dashed')
|
||||
# ax[0][0].set_zorder(ax0.get_zorder()+1)
|
||||
# ax[0][0].patch.set_visible(False)
|
||||
# ax0.set_yticklabels([])
|
||||
# ax0.set_yticks([])
|
||||
# # Plot winner chasing offets
|
||||
# ax[0][1].plot(time, winner_cc_offset, color='tab:blue', zorder=100)
|
||||
# ax1 = ax[0][1].twinx()
|
||||
# ax1.eventplot(np.array([winner_centered_offset]), lineoffsets=offset, linelengths=0.1, colors=['tab:purple'], alpha=0.25, zorder=-100)
|
||||
# ax1.vlines(0, 0, 1.5, 'tab:grey', 'dashed')
|
||||
# ax[0][1].set_zorder(ax1.get_zorder()+1)
|
||||
# ax[0][1].patch.set_visible(False)
|
||||
# ax1.set_yticklabels([])
|
||||
# ax1.set_yticks([])
|
||||
# # Plot winner physical contacts
|
||||
# ax[0][2].plot(time, winner_cc_physical, color='tab:blue', zorder=100)
|
||||
# ax2 = ax[0][2].twinx()
|
||||
# ax2.eventplot(np.array([winner_centered_physical]), lineoffsets=offset, linelengths=0.1, colors=['tab:red'], alpha=0.25, zorder=-100)
|
||||
# ax2.vlines(0, 0, 1.5, 'tab:grey', 'dashed')
|
||||
# ax[0][2].set_zorder(ax2.get_zorder()+1)
|
||||
# ax[0][2].patch.set_visible(False)
|
||||
# ax2.set_yticklabels([])
|
||||
# ax2.set_yticks([])
|
||||
# # Plot loser chasing onsets
|
||||
# ax[1][0].set_ylabel('Chirp rate [Hz]')
|
||||
# ax[1][0].plot(time, loser_cc_onset, color='tab:blue', zorder=100)
|
||||
# ax3 = ax[1][0].twinx()
|
||||
# ax3.eventplot(np.array([loser_centered_onset]), lineoffsets=offset, linelengths=0.1, colors=['tab:green'], alpha=0.25, zorder=-100)
|
||||
# ax3.vlines(0, 0, 1.5, 'tab:grey', 'dashed')
|
||||
# ax[1][0].set_zorder(ax3.get_zorder()+1)
|
||||
# ax[1][0].patch.set_visible(False)
|
||||
# ax3.set_yticklabels([])
|
||||
# ax3.set_yticks([])
|
||||
# # Plot loser chasing offsets
|
||||
# ax[1][1].plot(time, loser_cc_offset, color='tab:blue', zorder=100)
|
||||
# ax4 = ax[1][1].twinx()
|
||||
# ax4.eventplot(np.array([loser_centered_offset]), lineoffsets=offset, linelengths=0.1, colors=['tab:purple'], alpha=0.25, zorder=-100)
|
||||
# ax4.vlines(0, 0, 1.5, 'tab:grey', 'dashed')
|
||||
# ax[1][1].set_zorder(ax4.get_zorder()+1)
|
||||
# ax[1][1].patch.set_visible(False)
|
||||
# ax4.set_yticklabels([])
|
||||
# ax4.set_yticks([])
|
||||
# # Plot loser physical contacts
|
||||
# ax[1][2].plot(time, loser_cc_physical, color='tab:blue', zorder=100)
|
||||
# ax5 = ax[1][2].twinx()
|
||||
# ax5.eventplot(np.array([loser_centered_physical]), lineoffsets=offset, linelengths=0.1, colors=['tab:red'], alpha=0.25, zorder=-100)
|
||||
# ax5.vlines(0, 0, 1.5, 'tab:grey', 'dashed')
|
||||
# ax[1][2].set_zorder(ax5.get_zorder()+1)
|
||||
# ax[1][2].patch.set_visible(False)
|
||||
# ax5.set_yticklabels([])
|
||||
# ax5.set_yticks([])
|
||||
# plt.show()
|
||||
# plt.close()
|
||||
|
||||
# for i in range(len(fish_ids)):
|
||||
# fish = fish_ids[i]
|
||||
# chirps_temp = chirps[chirps_fish_ids == fish]
|
||||
# print(fish)
|
||||
|
||||
#### Chirps around events, only losers, one recording ####
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Path to the data
|
||||
datapath = "../data/mount_data/"
|
||||
main(datapath)
|
@ -1,59 +0,0 @@
|
||||
import os
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from chirpdetection import chirpdetection
|
||||
from IPython import embed
|
||||
|
||||
# check rec ../data/mount_data/2020-03-25-10_00/ starting at 3175
|
||||
|
||||
|
||||
def get_valid_datasets(dataroot):
|
||||
datasets = sorted(
|
||||
[
|
||||
name
|
||||
for name in os.listdir(dataroot)
|
||||
if os.path.isdir(os.path.join(dataroot, name))
|
||||
]
|
||||
)
|
||||
|
||||
valid_datasets = []
|
||||
for dataset in datasets:
|
||||
path = os.path.join(dataroot, dataset)
|
||||
csv_name = "-".join(dataset.split("-")[:3]) + ".csv"
|
||||
|
||||
if os.path.exists(os.path.join(path, csv_name)) is False:
|
||||
continue
|
||||
|
||||
if os.path.exists(os.path.join(path, "ident_v.npy")) is False:
|
||||
continue
|
||||
|
||||
ident = np.load(os.path.join(path, "ident_v.npy"))
|
||||
number_of_fish = len(np.unique(ident[~np.isnan(ident)]))
|
||||
if number_of_fish != 2:
|
||||
continue
|
||||
|
||||
valid_datasets.append(dataset)
|
||||
|
||||
datapaths = [
|
||||
os.path.join(dataroot, dataset) + "/" for dataset in valid_datasets
|
||||
]
|
||||
|
||||
return datapaths, valid_datasets
|
||||
|
||||
|
||||
def main(datapaths):
|
||||
for path in datapaths:
|
||||
chirpdetection(path, plot="show")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
dataroot = "../data/mount_data/"
|
||||
|
||||
datapaths, valid_datasets = get_valid_datasets(dataroot)
|
||||
|
||||
recs = pd.DataFrame(columns=["recording"], data=valid_datasets)
|
||||
recs.to_csv("../recs.csv", index=False)
|
||||
# datapaths = ['../data/mount_data/2020-03-25-10_00/']
|
||||
main(datapaths)
|
||||
|
||||
# window 1524 + 244 in dataset index 4 is nice example
|
@ -1,47 +0,0 @@
|
||||
import os
|
||||
from paramiko import SSHClient
|
||||
from scp import SCPClient
|
||||
from IPython import embed
|
||||
from pandas import read_csv
|
||||
|
||||
ssh = SSHClient()
|
||||
ssh.load_system_host_keys()
|
||||
|
||||
ssh.connect(
|
||||
hostname="kraken",
|
||||
username="efish",
|
||||
password="fwNix4U",
|
||||
)
|
||||
|
||||
|
||||
# SCPCLient takes a paramiko transport as its only argument
|
||||
scp = SCPClient(ssh.get_transport())
|
||||
|
||||
data = read_csv("../recs.csv")
|
||||
foldernames = data["recording"].values
|
||||
|
||||
directory = f"/Users/acfw/Documents/uni_tuebingen/chirpdetection/GP2023_chirp_detection/data/mount_data/"
|
||||
for foldername in foldernames:
|
||||
if not os.path.exists(directory + foldername):
|
||||
os.makedirs(directory + foldername)
|
||||
|
||||
files = [
|
||||
("-").join(foldername.split("-")[:3]) + ".csv",
|
||||
"chirp_ids.npy",
|
||||
"chirps.npy",
|
||||
"fund_v.npy",
|
||||
"ident_v.npy",
|
||||
"idx_v.npy",
|
||||
"times.npy",
|
||||
"spec.npy",
|
||||
"LED_on_time.npy",
|
||||
"sign_v.npy",
|
||||
]
|
||||
|
||||
for f in files:
|
||||
scp.get(
|
||||
f"/home/efish/behavior/2019_tube_competition/{foldername}/{f}",
|
||||
directory + foldername,
|
||||
)
|
||||
|
||||
scp.close()
|
@ -1,171 +0,0 @@
|
||||
import numpy as np
|
||||
import os
|
||||
from IPython import embed
|
||||
|
||||
from pandas import read_csv
|
||||
from modules.logger import makeLogger
|
||||
from modules.datahandling import causal_kde1d, acausal_kde1d, flatten
|
||||
|
||||
|
||||
logger = makeLogger(__name__)
|
||||
|
||||
|
||||
class Behavior:
|
||||
"""Load behavior data from csv file as class attributes
|
||||
Attributes
|
||||
----------
|
||||
behavior: 0: chasing onset, 1: chasing offset, 2: physical contact
|
||||
behavior_type:
|
||||
behavioral_category:
|
||||
comment_start:
|
||||
comment_stop:
|
||||
dataframe: pandas dataframe with all the data
|
||||
duration_s:
|
||||
media_file:
|
||||
observation_date:
|
||||
observation_id:
|
||||
start_s: start time of the event in seconds
|
||||
stop_s: stop time of the event in seconds
|
||||
total_length:
|
||||
"""
|
||||
|
||||
def __init__(self, folder_path: str) -> None:
|
||||
LED_on_time_BORIS = np.load(
|
||||
os.path.join(folder_path, "LED_on_time.npy"), allow_pickle=True
|
||||
)
|
||||
|
||||
csv_filename = os.path.split(folder_path[:-1])[-1]
|
||||
csv_filename = "-".join(csv_filename.split("-")[:-1]) + ".csv"
|
||||
# embed()
|
||||
|
||||
# csv_filename = [f for f in os.listdir(
|
||||
# folder_path) if f.endswith('.csv')][0]
|
||||
# logger.info(f'CSV file: {csv_filename}')
|
||||
self.dataframe = read_csv(os.path.join(folder_path, csv_filename))
|
||||
|
||||
self.chirps = np.load(
|
||||
os.path.join(folder_path, "chirps.npy"), allow_pickle=True
|
||||
)
|
||||
self.chirps_ids = np.load(
|
||||
os.path.join(folder_path, "chirp_ids.npy"), allow_pickle=True
|
||||
)
|
||||
|
||||
self.ident = np.load(
|
||||
os.path.join(folder_path, "ident_v.npy"), allow_pickle=True
|
||||
)
|
||||
self.idx = np.load(
|
||||
os.path.join(folder_path, "idx_v.npy"), allow_pickle=True
|
||||
)
|
||||
self.freq = np.load(
|
||||
os.path.join(folder_path, "fund_v.npy"), allow_pickle=True
|
||||
)
|
||||
self.time = np.load(
|
||||
os.path.join(folder_path, "times.npy"), allow_pickle=True
|
||||
)
|
||||
self.spec = np.load(
|
||||
os.path.join(folder_path, "spec.npy"), allow_pickle=True
|
||||
)
|
||||
|
||||
for k, key in enumerate(self.dataframe.keys()):
|
||||
key = key.lower()
|
||||
if " " in key:
|
||||
key = key.replace(" ", "_")
|
||||
if "(" in key:
|
||||
key = key.replace("(", "")
|
||||
key = key.replace(")", "")
|
||||
setattr(
|
||||
self, key, np.array(self.dataframe[self.dataframe.keys()[k]])
|
||||
)
|
||||
|
||||
last_LED_t_BORIS = LED_on_time_BORIS[-1]
|
||||
real_time_range = self.time[-1] - self.time[0]
|
||||
factor = 1.034141
|
||||
shift = last_LED_t_BORIS - real_time_range * factor
|
||||
self.start_s = (self.start_s - shift) / factor
|
||||
self.stop_s = (self.stop_s - shift) / factor
|
||||
|
||||
|
||||
def correct_chasing_events(
|
||||
category: np.ndarray, timestamps: np.ndarray
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
onset_ids = np.arange(len(category))[category == 0]
|
||||
offset_ids = np.arange(len(category))[category == 1]
|
||||
|
||||
wrong_bh = np.arange(len(category))[category != 2][:-1][
|
||||
np.diff(category[category != 2]) == 0
|
||||
]
|
||||
|
||||
if category[category != 2][-1] == 0:
|
||||
wrong_bh = np.append(
|
||||
wrong_bh, np.arange(len(category))[category != 2][-1]
|
||||
)
|
||||
|
||||
if onset_ids[0] > offset_ids[0]:
|
||||
offset_ids = np.delete(offset_ids, 0)
|
||||
help_index = offset_ids[0]
|
||||
wrong_bh = np.append(wrong_bh[help_index])
|
||||
|
||||
category = np.delete(category, wrong_bh)
|
||||
timestamps = np.delete(timestamps, wrong_bh)
|
||||
|
||||
new_onset_ids = np.arange(len(category))[category == 0]
|
||||
new_offset_ids = np.arange(len(category))[category == 1]
|
||||
|
||||
# Check whether on- or offset is longer and calculate length difference
|
||||
|
||||
if len(new_onset_ids) > len(new_offset_ids):
|
||||
embed()
|
||||
logger.warning("Onsets are greater than offsets")
|
||||
elif len(new_onset_ids) < len(new_offset_ids):
|
||||
logger.warning("Offsets are greater than onsets")
|
||||
elif len(new_onset_ids) == len(new_offset_ids):
|
||||
# logger.info('Chasing events are equal')
|
||||
pass
|
||||
|
||||
return category, timestamps
|
||||
|
||||
|
||||
def center_chirps(
|
||||
events: np.ndarray,
|
||||
chirps: np.ndarray,
|
||||
time_before_event: int,
|
||||
time_after_event: int,
|
||||
# dt: float,
|
||||
# width: float,
|
||||
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
||||
event_chirps = [] # chirps that are in specified window around event
|
||||
# timestamps of chirps around event centered on the event timepoint
|
||||
centered_chirps = []
|
||||
|
||||
for event_timestamp in events:
|
||||
start = event_timestamp - time_before_event
|
||||
stop = event_timestamp + time_after_event
|
||||
chirps_around_event = [c for c in chirps if (c >= start) & (c <= stop)]
|
||||
|
||||
if len(chirps_around_event) == 0:
|
||||
continue
|
||||
|
||||
centered_chirps.append(chirps_around_event - event_timestamp)
|
||||
event_chirps.append(chirps_around_event)
|
||||
|
||||
centered_chirps = np.sort(flatten(centered_chirps))
|
||||
event_chirps = np.sort(flatten(event_chirps))
|
||||
|
||||
if len(centered_chirps) != len(event_chirps):
|
||||
raise ValueError(
|
||||
"Non centered chirps and centered chirps are not equal"
|
||||
)
|
||||
|
||||
# time = np.arange(-time_before_event, time_after_event, dt)
|
||||
|
||||
# # Kernel density estimation with some if's
|
||||
# if len(centered_chirps) == 0:
|
||||
# centered_chirps = np.array([])
|
||||
# centered_chirps_convolved = np.zeros(len(time))
|
||||
# else:
|
||||
# # convert list of arrays to one array for plotting
|
||||
# centered_chirps = np.concatenate(centered_chirps, axis=0)
|
||||
# centered_chirps_convolved = (acausal_kde1d(
|
||||
# centered_chirps, time, width)) / len(event)
|
||||
|
||||
return centered_chirps
|
@ -2,10 +2,9 @@ import numpy as np
|
||||
from typing import List, Any
|
||||
from scipy.ndimage import gaussian_filter1d
|
||||
from scipy.stats import gamma, norm
|
||||
from scipy.signal import resample
|
||||
|
||||
|
||||
def minmaxnorm(data):
|
||||
def scale01(data):
|
||||
"""
|
||||
Normalize data to [0, 1]
|
||||
|
||||
@ -20,57 +19,14 @@ def minmaxnorm(data):
|
||||
Normalized data.
|
||||
|
||||
"""
|
||||
return (data - np.min(data)) / (np.max(data) - np.min(data))
|
||||
|
||||
|
||||
def instantaneous_frequency2(
|
||||
signal: np.ndarray, fs: float, interpolation: str = "linear"
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Compute the instantaneous frequency of a periodic signal using zero crossings and resample the frequency using linear
|
||||
or cubic interpolation to match the dimensions of the input array.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
signal : np.ndarray
|
||||
Input signal.
|
||||
fs : float
|
||||
Sampling frequency of the input signal.
|
||||
interpolation : str, optional
|
||||
Interpolation method to use during resampling. Should be either 'linear' or 'cubic'. Default is 'linear'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
freq : np.ndarray
|
||||
Instantaneous frequency of the input signal, resampled to match the dimensions of the input array.
|
||||
"""
|
||||
# Find zero crossings
|
||||
zero_crossings = np.where(np.diff(np.sign(signal)))[0]
|
||||
|
||||
# Compute time differences between zero crossings
|
||||
time_diff = np.diff(zero_crossings) / fs
|
||||
|
||||
# Compute instantaneous frequency as inverse of time differences
|
||||
freq = 1 / time_diff
|
||||
|
||||
# Resample the frequency using specified interpolation method to match the dimensions of the input array
|
||||
orig_len = len(signal)
|
||||
freq = resample(freq, orig_len)
|
||||
|
||||
if interpolation == "linear":
|
||||
freq = np.interp(np.arange(0, orig_len), np.arange(0, orig_len), freq)
|
||||
elif interpolation == "cubic":
|
||||
freq = resample(freq, orig_len, window="cubic")
|
||||
|
||||
return freq
|
||||
return (2*((data - np.min(data)) / (np.max(data) - np.min(data)))) - 1
|
||||
|
||||
|
||||
def instantaneous_frequency(
|
||||
signal: np.ndarray,
|
||||
samplerate: int,
|
||||
smoothing_window: int,
|
||||
interpolation: str = "linear",
|
||||
) -> np.ndarray:
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""
|
||||
Compute the instantaneous frequency of a signal that is approximately
|
||||
sinusoidal and symmetric around 0.
|
||||
@ -83,8 +39,6 @@ def instantaneous_frequency(
|
||||
Samplerate of the signal.
|
||||
smoothing_window : int
|
||||
Window size for the gaussian filter.
|
||||
interpolation : str, optional
|
||||
Interpolation method to use during resampling. Should be either 'linear' or 'cubic'. Default is 'linear'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@ -118,16 +72,7 @@ def instantaneous_frequency(
|
||||
1 / np.diff(true_zero), smoothing_window
|
||||
)
|
||||
|
||||
# Resample the frequency using specified interpolation method to match the dimensions of the input array
|
||||
orig_len = len(signal)
|
||||
freq = resample(instantaneous_frequency, orig_len)
|
||||
|
||||
if interpolation == "linear":
|
||||
freq = np.interp(np.arange(0, orig_len), np.arange(0, orig_len), freq)
|
||||
elif interpolation == "cubic":
|
||||
freq = resample(freq, orig_len, window="cubic")
|
||||
|
||||
return freq
|
||||
return instantaneous_frequency_time, instantaneous_frequency
|
||||
|
||||
|
||||
def purge_duplicates(
|
||||
@ -161,6 +106,7 @@ def purge_duplicates(
|
||||
group = [timestamps[0]]
|
||||
|
||||
for i in range(1, len(timestamps)):
|
||||
|
||||
# check the difference between current timestamp and previous
|
||||
# timestamp is less than the threshold
|
||||
if timestamps[i] - timestamps[i - 1] < threshold:
|
||||
@ -222,9 +168,6 @@ def group_timestamps(
|
||||
]
|
||||
timestamps.sort()
|
||||
|
||||
if len(timestamps) == 0:
|
||||
return []
|
||||
|
||||
groups = []
|
||||
current_group = [timestamps[0]]
|
||||
|
||||
@ -379,6 +322,7 @@ def acausal_kde1d(spikes, time, width):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
timestamps = [
|
||||
[1.2, 1.5, 1.3],
|
||||
[],
|
||||
|
@ -1,8 +1,7 @@
|
||||
import os
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import yaml
|
||||
import numpy as np
|
||||
from thunderfish.dataloader import DataLoader
|
||||
|
||||
|
||||
@ -35,15 +34,12 @@ class LoadData:
|
||||
"""
|
||||
|
||||
def __init__(self, datapath: str) -> None:
|
||||
|
||||
# load raw data
|
||||
self.datapath = datapath
|
||||
self.file = os.path.join(datapath, "traces-grid1.raw")
|
||||
if os.path.isfile(self.file) == False:
|
||||
self.raw = np.load(os.path.join(datapath, "raw.npy"))
|
||||
self.raw_rate = 20000.0
|
||||
else:
|
||||
self.raw = DataLoader(self.file, 60.0, 0, channel=-1)
|
||||
self.raw_rate = self.raw.samplerate
|
||||
self.raw = DataLoader(self.file, 60.0, 0, channel=-1)
|
||||
self.raw_rate = self.raw.samplerate
|
||||
|
||||
# load wavetracker files
|
||||
self.time = np.load(datapath + "times.npy", allow_pickle=True)
|
||||
|
@ -3,10 +3,10 @@ import numpy as np
|
||||
|
||||
|
||||
def bandpass_filter(
|
||||
signal: np.ndarray,
|
||||
samplerate: float,
|
||||
lowf: float,
|
||||
highf: float,
|
||||
signal: np.ndarray,
|
||||
samplerate: float,
|
||||
lowf: float,
|
||||
highf: float,
|
||||
) -> np.ndarray:
|
||||
"""Bandpass filter a signal.
|
||||
|
||||
@ -60,7 +60,9 @@ def highpass_filter(
|
||||
|
||||
|
||||
def lowpass_filter(
|
||||
signal: np.ndarray, samplerate: float, cutoff: float
|
||||
signal: np.ndarray,
|
||||
samplerate: float,
|
||||
cutoff: float
|
||||
) -> np.ndarray:
|
||||
"""Lowpass filter a signal.
|
||||
|
||||
@ -84,9 +86,10 @@ def lowpass_filter(
|
||||
return filtered_signal
|
||||
|
||||
|
||||
def envelope(
|
||||
signal: np.ndarray, samplerate: float, cutoff_frequency: float
|
||||
) -> np.ndarray:
|
||||
def envelope(signal: np.ndarray,
|
||||
samplerate: float,
|
||||
cutoff_frequency: float
|
||||
) -> np.ndarray:
|
||||
"""Calculate the envelope of a signal using a lowpass filter.
|
||||
|
||||
Parameters
|
||||
|
@ -2,13 +2,12 @@ import logging
|
||||
|
||||
|
||||
def makeLogger(name: str):
|
||||
|
||||
# create logger formats for file and terminal
|
||||
file_formatter = logging.Formatter(
|
||||
"[ %(levelname)s ] ~ %(asctime)s ~ %(module)s.%(funcName)s: %(message)s"
|
||||
)
|
||||
"[ %(levelname)s ] ~ %(asctime)s ~ %(module)s.%(funcName)s: %(message)s")
|
||||
console_formatter = logging.Formatter(
|
||||
"[ %(levelname)s ] in %(module)s.%(funcName)s: %(message)s"
|
||||
)
|
||||
"[ %(levelname)s ] in %(module)s.%(funcName)s: %(message)s")
|
||||
|
||||
# create logging file if loglevel is debug
|
||||
file_handler = logging.FileHandler(f"gridtools_log.log", mode="w")
|
||||
@ -30,6 +29,7 @@ def makeLogger(name: str):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# initiate logger
|
||||
mylogger = makeLogger(__name__)
|
||||
|
||||
|
@ -7,6 +7,7 @@ from matplotlib.colors import ListedColormap
|
||||
|
||||
def PlotStyle() -> None:
|
||||
class style:
|
||||
|
||||
# lightcmap = cmocean.tools.lighten(cmocean.cm.haline, 0.8)
|
||||
|
||||
# units
|
||||
@ -22,17 +23,16 @@ def PlotStyle() -> None:
|
||||
sky = "#89dceb"
|
||||
teal = "#94e2d5"
|
||||
green = "#a6e3a1"
|
||||
yellow = "#f9d67f"
|
||||
orange = "#faa472"
|
||||
maroon = "#eb8486"
|
||||
red = "#e0e4f7"
|
||||
purple = "#d89bf7"
|
||||
pink = "#f59edb"
|
||||
yellow = "#f9e2af"
|
||||
orange = "#fab387"
|
||||
maroon = "#eba0ac"
|
||||
red = "#f38ba8"
|
||||
purple = "#cba6f7"
|
||||
pink = "#f5c2e7"
|
||||
lavender = "#b4befe"
|
||||
gblue1 = "#f37588"
|
||||
gblue2 = "#faa472"
|
||||
gblue3 = "#f9d67f"
|
||||
g = "#f3626c"
|
||||
gblue1 = "#8cb8ff"
|
||||
gblue2 = "#7cdcdc"
|
||||
gblue3 = "#82e896"
|
||||
|
||||
@classmethod
|
||||
def lims(cls, track1, track2):
|
||||
@ -75,15 +75,13 @@ def PlotStyle() -> None:
|
||||
va="center",
|
||||
zorder=1000,
|
||||
bbox=dict(
|
||||
boxstyle=f"circle, pad={padding}",
|
||||
fc="white",
|
||||
ec="black",
|
||||
lw=1,
|
||||
boxstyle=f"circle, pad={padding}", fc="white", ec="black", lw=1
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def fade_cmap(cls, cmap):
|
||||
|
||||
my_cmap = cmap(np.arange(cmap.N))
|
||||
my_cmap[:, -1] = np.linspace(0, 1, cmap.N)
|
||||
my_cmap = ListedColormap(my_cmap)
|
||||
@ -110,9 +108,9 @@ def PlotStyle() -> None:
|
||||
@classmethod
|
||||
def set_boxplot_color(cls, bp, color):
|
||||
plt.setp(bp["boxes"], color=color)
|
||||
plt.setp(bp["whiskers"], color=white)
|
||||
plt.setp(bp["caps"], color=white)
|
||||
plt.setp(bp["medians"], color=black)
|
||||
plt.setp(bp["whiskers"], color=color)
|
||||
plt.setp(bp["caps"], color=color)
|
||||
plt.setp(bp["medians"], color=color)
|
||||
|
||||
@classmethod
|
||||
def label_subplots(cls, labels, axes, fig):
|
||||
@ -231,7 +229,7 @@ def PlotStyle() -> None:
|
||||
plt.rc("legend", fontsize=SMALL_SIZE) # legend fontsize
|
||||
plt.rc("figure", titlesize=BIGGER_SIZE) # fontsize of the figure title
|
||||
|
||||
plt.rcParams["image.cmap"] = "cmo.thermal"
|
||||
plt.rcParams["image.cmap"] = 'cmo.haline'
|
||||
plt.rcParams["axes.xmargin"] = 0.05
|
||||
plt.rcParams["axes.ymargin"] = 0.1
|
||||
plt.rcParams["axes.titlelocation"] = "left"
|
||||
@ -256,53 +254,53 @@ def PlotStyle() -> None:
|
||||
plt.rcParams["boxplot.boxprops.color"] = gray
|
||||
plt.rcParams["boxplot.whiskerprops.color"] = gray
|
||||
plt.rcParams["boxplot.capprops.color"] = gray
|
||||
plt.rcParams["boxplot.medianprops.color"] = black
|
||||
plt.rcParams["boxplot.medianprops.color"] = gray
|
||||
plt.rcParams["text.color"] = white
|
||||
plt.rcParams["axes.facecolor"] = black # axes background color
|
||||
plt.rcParams["axes.edgecolor"] = white # axes edge color
|
||||
plt.rcParams["axes.edgecolor"] = gray # axes edge color
|
||||
# plt.rcParams["axes.grid"] = True # display grid or not
|
||||
# plt.rcParams["axes.grid.axis"] = "y" # which axis the grid is applied to
|
||||
plt.rcParams["axes.labelcolor"] = white
|
||||
plt.rcParams["axes.axisbelow"] = True # draw axis gridlines and ticks:
|
||||
plt.rcParams["axes.axisbelow"] = True # draw axis gridlines and ticks:
|
||||
plt.rcParams["axes.spines.left"] = True # display axis spines
|
||||
plt.rcParams["axes.spines.bottom"] = True
|
||||
plt.rcParams["axes.spines.top"] = False
|
||||
plt.rcParams["axes.spines.right"] = False
|
||||
plt.rcParams["axes.prop_cycle"] = cycler(
|
||||
"color",
|
||||
[
|
||||
"#b4befe",
|
||||
"#89b4fa",
|
||||
"#74c7ec",
|
||||
"#89dceb",
|
||||
"#94e2d5",
|
||||
"#a6e3a1",
|
||||
"#f9e2af",
|
||||
"#fab387",
|
||||
"#eba0ac",
|
||||
"#f38ba8",
|
||||
"#cba6f7",
|
||||
"#f5c2e7",
|
||||
],
|
||||
)
|
||||
plt.rcParams["xtick.color"] = white # color of the ticks
|
||||
plt.rcParams["ytick.color"] = white # color of the ticks
|
||||
plt.rcParams["grid.color"] = white # grid color
|
||||
plt.rcParams["figure.facecolor"] = black # figure face color
|
||||
plt.rcParams["figure.edgecolor"] = black # figure edge color
|
||||
'color', [
|
||||
'#b4befe',
|
||||
'#89b4fa',
|
||||
'#74c7ec',
|
||||
'#89dceb',
|
||||
'#94e2d5',
|
||||
'#a6e3a1',
|
||||
'#f9e2af',
|
||||
'#fab387',
|
||||
'#eba0ac',
|
||||
'#f38ba8',
|
||||
'#cba6f7',
|
||||
'#f5c2e7',
|
||||
])
|
||||
plt.rcParams["xtick.color"] = gray # color of the ticks
|
||||
plt.rcParams["ytick.color"] = gray # color of the ticks
|
||||
plt.rcParams["grid.color"] = dark_gray # grid color
|
||||
plt.rcParams["figure.facecolor"] = black # figure face color
|
||||
plt.rcParams["figure.edgecolor"] = black # figure edge color
|
||||
plt.rcParams["savefig.facecolor"] = black # figure face color when saving
|
||||
|
||||
return style
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
s = PlotStyle()
|
||||
|
||||
import matplotlib.cbook as cbook
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.cm as cm
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import PathPatch
|
||||
import matplotlib.cbook as cbook
|
||||
from matplotlib.path import Path
|
||||
from matplotlib.patches import PathPatch
|
||||
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
@ -310,20 +308,14 @@ if __name__ == "__main__":
|
||||
delta = 0.025
|
||||
x = y = np.arange(-3.0, 3.0, delta)
|
||||
X, Y = np.meshgrid(x, y)
|
||||
Z1 = np.exp(-(X**2) - Y**2)
|
||||
Z2 = np.exp(-((X - 1) ** 2) - (Y - 1) ** 2)
|
||||
Z1 = np.exp(-X**2 - Y**2)
|
||||
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
|
||||
Z = (Z1 - Z2) * 2
|
||||
|
||||
fig1, ax = plt.subplots()
|
||||
im = ax.imshow(
|
||||
Z,
|
||||
interpolation="bilinear",
|
||||
cmap=cm.RdYlGn,
|
||||
origin="lower",
|
||||
extent=[-3, 3, -3, 3],
|
||||
vmax=abs(Z).max(),
|
||||
vmin=-abs(Z).max(),
|
||||
)
|
||||
im = ax.imshow(Z, interpolation='bilinear', cmap=cm.RdYlGn,
|
||||
origin='lower', extent=[-3, 3, -3, 3],
|
||||
vmax=abs(Z).max(), vmin=-abs(Z).max())
|
||||
|
||||
plt.show()
|
||||
|
||||
@ -336,22 +328,22 @@ if __name__ == "__main__":
|
||||
all_data = [np.random.normal(0, std, 100) for std in range(6, 10)]
|
||||
|
||||
# plot violin plot
|
||||
axs[0].violinplot(all_data, showmeans=False, showmedians=True)
|
||||
axs[0].set_title("Violin plot")
|
||||
axs[0].violinplot(all_data,
|
||||
showmeans=False,
|
||||
showmedians=True)
|
||||
axs[0].set_title('Violin plot')
|
||||
|
||||
# plot box plot
|
||||
axs[1].boxplot(all_data)
|
||||
axs[1].set_title("Box plot")
|
||||
axs[1].set_title('Box plot')
|
||||
|
||||
# adding horizontal grid lines
|
||||
for ax in axs:
|
||||
ax.yaxis.grid(True)
|
||||
ax.set_xticks(
|
||||
[y + 1 for y in range(len(all_data))],
|
||||
labels=["x1", "x2", "x3", "x4"],
|
||||
)
|
||||
ax.set_xlabel("Four separate samples")
|
||||
ax.set_ylabel("Observed values")
|
||||
ax.set_xticks([y + 1 for y in range(len(all_data))],
|
||||
labels=['x1', 'x2', 'x3', 'x4'])
|
||||
ax.set_xlabel('Four separate samples')
|
||||
ax.set_ylabel('Observed values')
|
||||
|
||||
plt.show()
|
||||
|
||||
@ -363,45 +355,24 @@ if __name__ == "__main__":
|
||||
theta = np.linspace(0.0, 2 * np.pi, N, endpoint=False)
|
||||
radii = 10 * np.random.rand(N)
|
||||
width = np.pi / 4 * np.random.rand(N)
|
||||
colors = cmo.cm.haline(radii / 10.0)
|
||||
colors = cmo.cm.haline(radii / 10.)
|
||||
|
||||
ax = plt.subplot(projection="polar")
|
||||
ax = plt.subplot(projection='polar')
|
||||
ax.bar(theta, radii, width=width, bottom=0.0, color=colors, alpha=0.5)
|
||||
|
||||
plt.show()
|
||||
|
||||
methods = [
|
||||
None,
|
||||
"none",
|
||||
"nearest",
|
||||
"bilinear",
|
||||
"bicubic",
|
||||
"spline16",
|
||||
"spline36",
|
||||
"hanning",
|
||||
"hamming",
|
||||
"hermite",
|
||||
"kaiser",
|
||||
"quadric",
|
||||
"catrom",
|
||||
"gaussian",
|
||||
"bessel",
|
||||
"mitchell",
|
||||
"sinc",
|
||||
"lanczos",
|
||||
]
|
||||
methods = [None, 'none', 'nearest', 'bilinear', 'bicubic', 'spline16',
|
||||
'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric',
|
||||
'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos']
|
||||
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
|
||||
grid = np.random.rand(4, 4)
|
||||
|
||||
fig, axs = plt.subplots(
|
||||
nrows=3,
|
||||
ncols=6,
|
||||
figsize=(9, 6),
|
||||
subplot_kw={"xticks": [], "yticks": []},
|
||||
)
|
||||
fig, axs = plt.subplots(nrows=3, ncols=6, figsize=(9, 6),
|
||||
subplot_kw={'xticks': [], 'yticks': []})
|
||||
|
||||
for ax, interp_method in zip(axs.flat, methods):
|
||||
ax.imshow(grid, interpolation=interp_method)
|
||||
|
@ -1,411 +0,0 @@
|
||||
import cmocean as cmo
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from cycler import cycler
|
||||
from matplotlib.colors import ListedColormap
|
||||
|
||||
|
||||
def PlotStyle() -> None:
|
||||
class style:
|
||||
# lightcmap = cmocean.tools.lighten(cmocean.cm.haline, 0.8)
|
||||
|
||||
# units
|
||||
cm = 1 / 2.54
|
||||
mm = 1 / 25.4
|
||||
|
||||
# colors
|
||||
black = "#111116"
|
||||
white = "#e0e4f7"
|
||||
gray = "#6c6e7d"
|
||||
blue = "#89b4fa"
|
||||
sapphire = "#74c7ec"
|
||||
sky = "#89dceb"
|
||||
teal = "#94e2d5"
|
||||
green = "#a6e3a1"
|
||||
yellow = "#f9d67f"
|
||||
orange = "#faa472"
|
||||
maroon = "#eb8486"
|
||||
red = "#f37588"
|
||||
purple = "#d89bf7"
|
||||
pink = "#f59edb"
|
||||
lavender = "#b4befe"
|
||||
gblue1 = "#89b4fa"
|
||||
gblue2 = "#89dceb"
|
||||
gblue3 = "#a6e3a1"
|
||||
g = "#76a0fa"
|
||||
|
||||
@classmethod
|
||||
def lims(cls, track1, track2):
|
||||
"""Helper function to get frequency y axis limits from two
|
||||
fundamental frequency tracks.
|
||||
|
||||
Args:
|
||||
track1 (array): First track
|
||||
track2 (array): Second track
|
||||
start (int): Index for first value to be plotted
|
||||
stop (int): Index for second value to be plotted
|
||||
padding (int): Padding for the upper and lower limit
|
||||
|
||||
Returns:
|
||||
lower (float): lower limit
|
||||
upper (float): upper limit
|
||||
|
||||
"""
|
||||
allfunds_tmp = (
|
||||
np.concatenate(
|
||||
[
|
||||
track1,
|
||||
track2,
|
||||
]
|
||||
)
|
||||
.ravel()
|
||||
.tolist()
|
||||
)
|
||||
lower = np.min(allfunds_tmp)
|
||||
upper = np.max(allfunds_tmp)
|
||||
return lower, upper
|
||||
|
||||
@classmethod
|
||||
def circled_annotation(cls, text, axis, xpos, ypos, padding=0.25):
|
||||
axis.text(
|
||||
xpos,
|
||||
ypos,
|
||||
text,
|
||||
ha="center",
|
||||
va="center",
|
||||
zorder=1000,
|
||||
bbox=dict(
|
||||
boxstyle=f"circle, pad={padding}",
|
||||
fc="white",
|
||||
ec="black",
|
||||
lw=1,
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def fade_cmap(cls, cmap):
|
||||
my_cmap = cmap(np.arange(cmap.N))
|
||||
my_cmap[:, -1] = np.linspace(0, 1, cmap.N)
|
||||
my_cmap = ListedColormap(my_cmap)
|
||||
|
||||
return my_cmap
|
||||
|
||||
@classmethod
|
||||
def hide_ax(cls, ax):
|
||||
ax.xaxis.set_visible(False)
|
||||
plt.setp(ax.spines.values(), visible=False)
|
||||
ax.tick_params(left=False, labelleft=False)
|
||||
ax.patch.set_visible(False)
|
||||
|
||||
@classmethod
|
||||
def hide_xax(cls, ax):
|
||||
ax.xaxis.set_visible(False)
|
||||
ax.spines["bottom"].set_visible(False)
|
||||
|
||||
@classmethod
|
||||
def hide_yax(cls, ax):
|
||||
ax.yaxis.set_visible(False)
|
||||
ax.spines["left"].set_visible(False)
|
||||
|
||||
@classmethod
|
||||
def set_boxplot_color(cls, bp, color):
|
||||
plt.setp(bp["boxes"], color=color)
|
||||
plt.setp(bp["whiskers"], color=white)
|
||||
plt.setp(bp["caps"], color=white)
|
||||
plt.setp(bp["medians"], color=black)
|
||||
|
||||
@classmethod
|
||||
def label_subplots(cls, labels, axes, fig):
|
||||
for axis, label in zip(axes, labels):
|
||||
X = axis.get_position().x0
|
||||
Y = axis.get_position().y1
|
||||
fig.text(X, Y, label, weight="bold")
|
||||
|
||||
@classmethod
|
||||
def letter_subplots(
|
||||
cls, axes=None, letters=None, xoffset=-0.1, yoffset=1.0, **kwargs
|
||||
):
|
||||
"""Add letters to the corners of subplots (panels). By default each axis is
|
||||
given an uppercase bold letter label placed in the upper-left corner.
|
||||
Args
|
||||
axes : list of pyplot ax objects. default plt.gcf().axes.
|
||||
letters : list of strings to use as labels, default ["A", "B", "C", ...]
|
||||
xoffset, yoffset : positions of each label relative to plot frame
|
||||
(default -0.1,1.0 = upper left margin). Can also be a list of
|
||||
offsets, in which case it should be the same length as the number of
|
||||
axes.
|
||||
Other keyword arguments will be passed to annotate() when panel letters
|
||||
are added.
|
||||
Returns:
|
||||
list of strings for each label added to the axes
|
||||
Examples:
|
||||
Defaults:
|
||||
>>> fig, axes = plt.subplots(1,3)
|
||||
>>> letter_subplots() # boldfaced A, B, C
|
||||
|
||||
Common labeling schemes inferred from the first letter:
|
||||
>>> fig, axes = plt.subplots(1,4)
|
||||
# panels labeled (a), (b), (c), (d)
|
||||
>>> letter_subplots(letters='(a)')
|
||||
Fully custom lettering:
|
||||
>>> fig, axes = plt.subplots(2,1)
|
||||
>>> letter_subplots(axes, letters=['(a.1)', '(b.2)'], fontweight='normal')
|
||||
Per-axis offsets:
|
||||
>>> fig, axes = plt.subplots(1,2)
|
||||
>>> letter_subplots(axes, xoffset=[-0.1, -0.15])
|
||||
|
||||
Matrix of axes:
|
||||
>>> fig, axes = plt.subplots(2,2, sharex=True, sharey=True)
|
||||
# fig.axes is a list when axes is a 2x2 matrix
|
||||
>>> letter_subplots(fig.axes)
|
||||
"""
|
||||
|
||||
# get axes:
|
||||
if axes is None:
|
||||
axes = plt.gcf().axes
|
||||
# handle single axes:
|
||||
try:
|
||||
iter(axes)
|
||||
except TypeError:
|
||||
axes = [axes]
|
||||
|
||||
# set up letter defaults (and corresponding fontweight):
|
||||
fontweight = "bold"
|
||||
ulets = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"[: len(axes)])
|
||||
llets = list("abcdefghijklmnopqrstuvwxyz"[: len(axes)])
|
||||
if letters is None or letters == "A":
|
||||
letters = ulets
|
||||
elif letters == "(a)":
|
||||
letters = ["({})".format(lett) for lett in llets]
|
||||
fontweight = "normal"
|
||||
elif letters == "(A)":
|
||||
letters = ["({})".format(lett) for lett in ulets]
|
||||
fontweight = "normal"
|
||||
elif letters in ("lower", "lowercase", "a"):
|
||||
letters = llets
|
||||
|
||||
# make sure there are x and y offsets for each ax in axes:
|
||||
if isinstance(xoffset, (int, float)):
|
||||
xoffset = [xoffset] * len(axes)
|
||||
else:
|
||||
assert len(xoffset) == len(axes)
|
||||
if isinstance(yoffset, (int, float)):
|
||||
yoffset = [yoffset] * len(axes)
|
||||
else:
|
||||
assert len(yoffset) == len(axes)
|
||||
|
||||
# defaults for annotate (kwargs is second so it can overwrite these defaults):
|
||||
my_defaults = dict(
|
||||
fontweight=fontweight,
|
||||
fontsize="large",
|
||||
ha="center",
|
||||
va="center",
|
||||
xycoords="axes fraction",
|
||||
annotation_clip=False,
|
||||
)
|
||||
kwargs = dict(list(my_defaults.items()) + list(kwargs.items()))
|
||||
|
||||
list_txts = []
|
||||
for ax, lbl, xoff, yoff in zip(axes, letters, xoffset, yoffset):
|
||||
t = ax.annotate(lbl, xy=(xoff, yoff), **kwargs)
|
||||
list_txts.append(t)
|
||||
return list_txts
|
||||
|
||||
pass
|
||||
|
||||
# rcparams text setup
|
||||
SMALL_SIZE = 12
|
||||
MEDIUM_SIZE = 14
|
||||
BIGGER_SIZE = 16
|
||||
black = "#111116"
|
||||
white = "#e0e4f7"
|
||||
gray = "#6c6e7d"
|
||||
dark_gray = "#2a2a32"
|
||||
|
||||
# rcparams
|
||||
plt.rc("font", size=MEDIUM_SIZE) # controls default text sizes
|
||||
plt.rc("axes", titlesize=MEDIUM_SIZE) # fontsize of the axes title
|
||||
plt.rc("axes", labelsize=MEDIUM_SIZE) # fontsize of the x and y labels
|
||||
plt.rc("xtick", labelsize=SMALL_SIZE) # fontsize of the tick labels
|
||||
plt.rc("ytick", labelsize=SMALL_SIZE) # fontsize of the tick labels
|
||||
plt.rc("legend", fontsize=SMALL_SIZE) # legend fontsize
|
||||
plt.rc("figure", titlesize=BIGGER_SIZE) # fontsize of the figure title
|
||||
|
||||
plt.rcParams["image.cmap"] = "cmo.haline"
|
||||
plt.rcParams["axes.xmargin"] = 0.05
|
||||
plt.rcParams["axes.ymargin"] = 0.1
|
||||
plt.rcParams["axes.titlelocation"] = "left"
|
||||
plt.rcParams["axes.titlesize"] = BIGGER_SIZE
|
||||
# plt.rcParams["axes.titlepad"] = -10
|
||||
plt.rcParams["legend.frameon"] = False
|
||||
plt.rcParams["legend.loc"] = "best"
|
||||
plt.rcParams["legend.borderpad"] = 0.4
|
||||
plt.rcParams["legend.facecolor"] = black
|
||||
plt.rcParams["legend.edgecolor"] = black
|
||||
plt.rcParams["legend.framealpha"] = 0.7
|
||||
plt.rcParams["legend.borderaxespad"] = 0.5
|
||||
plt.rcParams["legend.fancybox"] = False
|
||||
|
||||
# # specify the custom font to use
|
||||
# plt.rcParams["font.family"] = "sans-serif"
|
||||
# plt.rcParams["font.sans-serif"] = "Helvetica Now Text"
|
||||
|
||||
# dark mode modifications
|
||||
plt.rcParams["boxplot.flierprops.color"] = white
|
||||
plt.rcParams["boxplot.flierprops.markeredgecolor"] = gray
|
||||
plt.rcParams["boxplot.boxprops.color"] = gray
|
||||
plt.rcParams["boxplot.whiskerprops.color"] = gray
|
||||
plt.rcParams["boxplot.capprops.color"] = gray
|
||||
plt.rcParams["boxplot.medianprops.color"] = black
|
||||
plt.rcParams["text.color"] = white
|
||||
plt.rcParams["axes.facecolor"] = black # axes background color
|
||||
plt.rcParams["axes.edgecolor"] = white # axes edge color
|
||||
# plt.rcParams["axes.grid"] = True # display grid or not
|
||||
# plt.rcParams["axes.grid.axis"] = "y" # which axis the grid is applied to
|
||||
plt.rcParams["axes.labelcolor"] = white
|
||||
plt.rcParams["axes.axisbelow"] = True # draw axis gridlines and ticks:
|
||||
plt.rcParams["axes.spines.left"] = True # display axis spines
|
||||
plt.rcParams["axes.spines.bottom"] = True
|
||||
plt.rcParams["axes.spines.top"] = False
|
||||
plt.rcParams["axes.spines.right"] = False
|
||||
plt.rcParams["axes.prop_cycle"] = cycler(
|
||||
"color",
|
||||
[
|
||||
"#b4befe",
|
||||
"#89b4fa",
|
||||
"#74c7ec",
|
||||
"#89dceb",
|
||||
"#94e2d5",
|
||||
"#a6e3a1",
|
||||
"#f9e2af",
|
||||
"#fab387",
|
||||
"#eba0ac",
|
||||
"#f38ba8",
|
||||
"#cba6f7",
|
||||
"#f5c2e7",
|
||||
],
|
||||
)
|
||||
plt.rcParams["xtick.color"] = white # color of the ticks
|
||||
plt.rcParams["ytick.color"] = white # color of the ticks
|
||||
plt.rcParams["grid.color"] = white # grid color
|
||||
plt.rcParams["figure.facecolor"] = black # figure face color
|
||||
plt.rcParams["figure.edgecolor"] = black # figure edge color
|
||||
plt.rcParams["savefig.facecolor"] = black # figure face color when saving
|
||||
|
||||
return style
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
s = PlotStyle()
|
||||
|
||||
import matplotlib.cbook as cbook
|
||||
import matplotlib.cm as cm
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import PathPatch
|
||||
from matplotlib.path import Path
|
||||
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
|
||||
delta = 0.025
|
||||
x = y = np.arange(-3.0, 3.0, delta)
|
||||
X, Y = np.meshgrid(x, y)
|
||||
Z1 = np.exp(-(X**2) - Y**2)
|
||||
Z2 = np.exp(-((X - 1) ** 2) - (Y - 1) ** 2)
|
||||
Z = (Z1 - Z2) * 2
|
||||
|
||||
fig1, ax = plt.subplots()
|
||||
im = ax.imshow(
|
||||
Z,
|
||||
interpolation="bilinear",
|
||||
cmap=cm.RdYlGn,
|
||||
origin="lower",
|
||||
extent=[-3, 3, -3, 3],
|
||||
vmax=abs(Z).max(),
|
||||
vmin=-abs(Z).max(),
|
||||
)
|
||||
|
||||
plt.show()
|
||||
|
||||
fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(9, 4))
|
||||
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
|
||||
# generate some random test data
|
||||
all_data = [np.random.normal(0, std, 100) for std in range(6, 10)]
|
||||
|
||||
# plot violin plot
|
||||
axs[0].violinplot(all_data, showmeans=False, showmedians=True)
|
||||
axs[0].set_title("Violin plot")
|
||||
|
||||
# plot box plot
|
||||
axs[1].boxplot(all_data)
|
||||
axs[1].set_title("Box plot")
|
||||
|
||||
# adding horizontal grid lines
|
||||
for ax in axs:
|
||||
ax.yaxis.grid(True)
|
||||
ax.set_xticks(
|
||||
[y + 1 for y in range(len(all_data))],
|
||||
labels=["x1", "x2", "x3", "x4"],
|
||||
)
|
||||
ax.set_xlabel("Four separate samples")
|
||||
ax.set_ylabel("Observed values")
|
||||
|
||||
plt.show()
|
||||
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
|
||||
# Compute pie slices
|
||||
N = 20
|
||||
theta = np.linspace(0.0, 2 * np.pi, N, endpoint=False)
|
||||
radii = 10 * np.random.rand(N)
|
||||
width = np.pi / 4 * np.random.rand(N)
|
||||
colors = cmo.cm.haline(radii / 10.0)
|
||||
|
||||
ax = plt.subplot(projection="polar")
|
||||
ax.bar(theta, radii, width=width, bottom=0.0, color=colors, alpha=0.5)
|
||||
|
||||
plt.show()
|
||||
|
||||
methods = [
|
||||
None,
|
||||
"none",
|
||||
"nearest",
|
||||
"bilinear",
|
||||
"bicubic",
|
||||
"spline16",
|
||||
"spline36",
|
||||
"hanning",
|
||||
"hamming",
|
||||
"hermite",
|
||||
"kaiser",
|
||||
"quadric",
|
||||
"catrom",
|
||||
"gaussian",
|
||||
"bessel",
|
||||
"mitchell",
|
||||
"sinc",
|
||||
"lanczos",
|
||||
]
|
||||
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
|
||||
grid = np.random.rand(4, 4)
|
||||
|
||||
fig, axs = plt.subplots(
|
||||
nrows=3,
|
||||
ncols=6,
|
||||
figsize=(9, 6),
|
||||
subplot_kw={"xticks": [], "yticks": []},
|
||||
)
|
||||
|
||||
for ax, interp_method in zip(axs.flat, methods):
|
||||
ax.imshow(grid, interpolation=interp_method)
|
||||
ax.set_title(str(interp_method))
|
||||
|
||||
plt.tight_layout()
|
||||
plt.show()
|
@ -1,411 +0,0 @@
|
||||
import cmocean as cmo
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from cycler import cycler
|
||||
from matplotlib.colors import ListedColormap
|
||||
|
||||
|
||||
def PlotStyle() -> None:
|
||||
class style:
|
||||
# lightcmap = cmocean.tools.lighten(cmocean.cm.haline, 0.8)
|
||||
|
||||
# units
|
||||
cm = 1 / 2.54
|
||||
mm = 1 / 25.4
|
||||
|
||||
# colors
|
||||
black = "#111116"
|
||||
white = "#111116"
|
||||
gray = "#111116"
|
||||
blue = "#89b4fa"
|
||||
sapphire = "#74c7ec"
|
||||
sky = "#89dceb"
|
||||
teal = "#94e2d5"
|
||||
green = "#a6e3a1"
|
||||
yellow = "#f9d67f"
|
||||
orange = "#faa472"
|
||||
maroon = "#eb8486"
|
||||
red = "#e0e4f7"
|
||||
purple = "#d89bf7"
|
||||
pink = "#f59edb"
|
||||
lavender = "#b4befe"
|
||||
gblue1 = "#f37588"
|
||||
gblue2 = "#faa472"
|
||||
gblue3 = "#f9d67f"
|
||||
g = "#f3626c"
|
||||
|
||||
@classmethod
|
||||
def lims(cls, track1, track2):
|
||||
"""Helper function to get frequency y axis limits from two
|
||||
fundamental frequency tracks.
|
||||
|
||||
Args:
|
||||
track1 (array): First track
|
||||
track2 (array): Second track
|
||||
start (int): Index for first value to be plotted
|
||||
stop (int): Index for second value to be plotted
|
||||
padding (int): Padding for the upper and lower limit
|
||||
|
||||
Returns:
|
||||
lower (float): lower limit
|
||||
upper (float): upper limit
|
||||
|
||||
"""
|
||||
allfunds_tmp = (
|
||||
np.concatenate(
|
||||
[
|
||||
track1,
|
||||
track2,
|
||||
]
|
||||
)
|
||||
.ravel()
|
||||
.tolist()
|
||||
)
|
||||
lower = np.min(allfunds_tmp)
|
||||
upper = np.max(allfunds_tmp)
|
||||
return lower, upper
|
||||
|
||||
@classmethod
|
||||
def circled_annotation(cls, text, axis, xpos, ypos, padding=0.25):
|
||||
axis.text(
|
||||
xpos,
|
||||
ypos,
|
||||
text,
|
||||
ha="center",
|
||||
va="center",
|
||||
zorder=1000,
|
||||
bbox=dict(
|
||||
boxstyle=f"circle, pad={padding}",
|
||||
fc="white",
|
||||
ec="black",
|
||||
lw=1,
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def fade_cmap(cls, cmap):
|
||||
my_cmap = cmap(np.arange(cmap.N))
|
||||
my_cmap[:, -1] = np.linspace(0, 1, cmap.N)
|
||||
my_cmap = ListedColormap(my_cmap)
|
||||
|
||||
return my_cmap
|
||||
|
||||
@classmethod
|
||||
def hide_ax(cls, ax):
|
||||
ax.xaxis.set_visible(False)
|
||||
plt.setp(ax.spines.values(), visible=False)
|
||||
ax.tick_params(left=False, labelleft=False)
|
||||
ax.patch.set_visible(False)
|
||||
|
||||
@classmethod
|
||||
def hide_xax(cls, ax):
|
||||
ax.xaxis.set_visible(False)
|
||||
ax.spines["bottom"].set_visible(False)
|
||||
|
||||
@classmethod
|
||||
def hide_yax(cls, ax):
|
||||
ax.yaxis.set_visible(False)
|
||||
ax.spines["left"].set_visible(False)
|
||||
|
||||
@classmethod
|
||||
def set_boxplot_color(cls, bp, color):
|
||||
plt.setp(bp["boxes"], color=color)
|
||||
plt.setp(bp["whiskers"], color=white)
|
||||
plt.setp(bp["caps"], color=white)
|
||||
plt.setp(bp["medians"], color=black)
|
||||
|
||||
@classmethod
|
||||
def label_subplots(cls, labels, axes, fig):
|
||||
for axis, label in zip(axes, labels):
|
||||
X = axis.get_position().x0
|
||||
Y = axis.get_position().y1
|
||||
fig.text(X, Y, label, weight="bold")
|
||||
|
||||
@classmethod
|
||||
def letter_subplots(
|
||||
cls, axes=None, letters=None, xoffset=-0.1, yoffset=1.0, **kwargs
|
||||
):
|
||||
"""Add letters to the corners of subplots (panels). By default each axis is
|
||||
given an uppercase bold letter label placed in the upper-left corner.
|
||||
Args
|
||||
axes : list of pyplot ax objects. default plt.gcf().axes.
|
||||
letters : list of strings to use as labels, default ["A", "B", "C", ...]
|
||||
xoffset, yoffset : positions of each label relative to plot frame
|
||||
(default -0.1,1.0 = upper left margin). Can also be a list of
|
||||
offsets, in which case it should be the same length as the number of
|
||||
axes.
|
||||
Other keyword arguments will be passed to annotate() when panel letters
|
||||
are added.
|
||||
Returns:
|
||||
list of strings for each label added to the axes
|
||||
Examples:
|
||||
Defaults:
|
||||
>>> fig, axes = plt.subplots(1,3)
|
||||
>>> letter_subplots() # boldfaced A, B, C
|
||||
|
||||
Common labeling schemes inferred from the first letter:
|
||||
>>> fig, axes = plt.subplots(1,4)
|
||||
# panels labeled (a), (b), (c), (d)
|
||||
>>> letter_subplots(letters='(a)')
|
||||
Fully custom lettering:
|
||||
>>> fig, axes = plt.subplots(2,1)
|
||||
>>> letter_subplots(axes, letters=['(a.1)', '(b.2)'], fontweight='normal')
|
||||
Per-axis offsets:
|
||||
>>> fig, axes = plt.subplots(1,2)
|
||||
>>> letter_subplots(axes, xoffset=[-0.1, -0.15])
|
||||
|
||||
Matrix of axes:
|
||||
>>> fig, axes = plt.subplots(2,2, sharex=True, sharey=True)
|
||||
# fig.axes is a list when axes is a 2x2 matrix
|
||||
>>> letter_subplots(fig.axes)
|
||||
"""
|
||||
|
||||
# get axes:
|
||||
if axes is None:
|
||||
axes = plt.gcf().axes
|
||||
# handle single axes:
|
||||
try:
|
||||
iter(axes)
|
||||
except TypeError:
|
||||
axes = [axes]
|
||||
|
||||
# set up letter defaults (and corresponding fontweight):
|
||||
fontweight = "bold"
|
||||
ulets = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"[: len(axes)])
|
||||
llets = list("abcdefghijklmnopqrstuvwxyz"[: len(axes)])
|
||||
if letters is None or letters == "A":
|
||||
letters = ulets
|
||||
elif letters == "(a)":
|
||||
letters = ["({})".format(lett) for lett in llets]
|
||||
fontweight = "normal"
|
||||
elif letters == "(A)":
|
||||
letters = ["({})".format(lett) for lett in ulets]
|
||||
fontweight = "normal"
|
||||
elif letters in ("lower", "lowercase", "a"):
|
||||
letters = llets
|
||||
|
||||
# make sure there are x and y offsets for each ax in axes:
|
||||
if isinstance(xoffset, (int, float)):
|
||||
xoffset = [xoffset] * len(axes)
|
||||
else:
|
||||
assert len(xoffset) == len(axes)
|
||||
if isinstance(yoffset, (int, float)):
|
||||
yoffset = [yoffset] * len(axes)
|
||||
else:
|
||||
assert len(yoffset) == len(axes)
|
||||
|
||||
# defaults for annotate (kwargs is second so it can overwrite these defaults):
|
||||
my_defaults = dict(
|
||||
fontweight=fontweight,
|
||||
fontsize="large",
|
||||
ha="center",
|
||||
va="center",
|
||||
xycoords="axes fraction",
|
||||
annotation_clip=False,
|
||||
)
|
||||
kwargs = dict(list(my_defaults.items()) + list(kwargs.items()))
|
||||
|
||||
list_txts = []
|
||||
for ax, lbl, xoff, yoff in zip(axes, letters, xoffset, yoffset):
|
||||
t = ax.annotate(lbl, xy=(xoff, yoff), **kwargs)
|
||||
list_txts.append(t)
|
||||
return list_txts
|
||||
|
||||
pass
|
||||
|
||||
# rcparams text setup
|
||||
SMALL_SIZE = 12
|
||||
MEDIUM_SIZE = 14
|
||||
BIGGER_SIZE = 16
|
||||
black = "#e0e4f7"
|
||||
white = "#111116"
|
||||
# gray = "#6c6e7d"
|
||||
# dark_gray = "#2a2a32"
|
||||
|
||||
# rcparams
|
||||
plt.rc("font", size=MEDIUM_SIZE) # controls default text sizes
|
||||
plt.rc("axes", titlesize=MEDIUM_SIZE) # fontsize of the axes title
|
||||
plt.rc("axes", labelsize=MEDIUM_SIZE) # fontsize of the x and y labels
|
||||
plt.rc("xtick", labelsize=SMALL_SIZE) # fontsize of the tick labels
|
||||
plt.rc("ytick", labelsize=SMALL_SIZE) # fontsize of the tick labels
|
||||
plt.rc("legend", fontsize=SMALL_SIZE) # legend fontsize
|
||||
plt.rc("figure", titlesize=BIGGER_SIZE) # fontsize of the figure title
|
||||
|
||||
plt.rcParams["image.cmap"] = "cmo.haline"
|
||||
plt.rcParams["axes.xmargin"] = 0.05
|
||||
plt.rcParams["axes.ymargin"] = 0.1
|
||||
plt.rcParams["axes.titlelocation"] = "left"
|
||||
plt.rcParams["axes.titlesize"] = BIGGER_SIZE
|
||||
# plt.rcParams["axes.titlepad"] = -10
|
||||
plt.rcParams["legend.frameon"] = False
|
||||
plt.rcParams["legend.loc"] = "best"
|
||||
plt.rcParams["legend.borderpad"] = 0.4
|
||||
plt.rcParams["legend.facecolor"] = black
|
||||
plt.rcParams["legend.edgecolor"] = black
|
||||
plt.rcParams["legend.framealpha"] = 0.7
|
||||
plt.rcParams["legend.borderaxespad"] = 0.5
|
||||
plt.rcParams["legend.fancybox"] = False
|
||||
|
||||
# # specify the custom font to use
|
||||
# plt.rcParams["font.family"] = "sans-serif"
|
||||
# plt.rcParams["font.sans-serif"] = "Helvetica Now Text"
|
||||
|
||||
# dark mode modifications
|
||||
# plt.rcParams["boxplot.flierprops.color"] = white
|
||||
# plt.rcParams["boxplot.flierprops.markeredgecolor"] = gray
|
||||
# plt.rcParams["boxplot.boxprops.color"] = gray
|
||||
# plt.rcParams["boxplot.whiskerprops.color"] = gray
|
||||
# plt.rcParams["boxplot.capprops.color"] = gray
|
||||
# plt.rcParams["boxplot.medianprops.color"] = black
|
||||
# plt.rcParams["text.color"] = white
|
||||
# plt.rcParams["axes.facecolor"] = black # axes background color
|
||||
# plt.rcParams["axes.edgecolor"] = white # axes edge color
|
||||
# # plt.rcParams["axes.grid"] = True # display grid or not
|
||||
# # plt.rcParams["axes.grid.axis"] = "y" # which axis the grid is applied to
|
||||
# plt.rcParams["axes.labelcolor"] = white
|
||||
# plt.rcParams["axes.axisbelow"] = True # draw axis gridlines and ticks:
|
||||
# plt.rcParams["axes.spines.left"] = True # display axis spines
|
||||
# plt.rcParams["axes.spines.bottom"] = True
|
||||
# plt.rcParams["axes.spines.top"] = False
|
||||
# plt.rcParams["axes.spines.right"] = False
|
||||
# plt.rcParams["axes.prop_cycle"] = cycler(
|
||||
# "color",
|
||||
# [
|
||||
# "#b4befe",
|
||||
# "#89b4fa",
|
||||
# "#74c7ec",
|
||||
# "#89dceb",
|
||||
# "#94e2d5",
|
||||
# "#a6e3a1",
|
||||
# "#f9e2af",
|
||||
# "#fab387",
|
||||
# "#eba0ac",
|
||||
# "#f38ba8",
|
||||
# "#cba6f7",
|
||||
# "#f5c2e7",
|
||||
# ],
|
||||
# )
|
||||
# plt.rcParams["xtick.color"] = white # color of the ticks
|
||||
# plt.rcParams["ytick.color"] = white # color of the ticks
|
||||
# plt.rcParams["grid.color"] = white # grid color
|
||||
# plt.rcParams["figure.facecolor"] = black # figure face color
|
||||
# plt.rcParams["figure.edgecolor"] = black # figure edge color
|
||||
# plt.rcParams["savefig.facecolor"] = black # figure face color when saving
|
||||
|
||||
return style
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
s = PlotStyle()
|
||||
|
||||
import matplotlib.cbook as cbook
|
||||
import matplotlib.cm as cm
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import PathPatch
|
||||
from matplotlib.path import Path
|
||||
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
|
||||
delta = 0.025
|
||||
x = y = np.arange(-3.0, 3.0, delta)
|
||||
X, Y = np.meshgrid(x, y)
|
||||
Z1 = np.exp(-(X**2) - Y**2)
|
||||
Z2 = np.exp(-((X - 1) ** 2) - (Y - 1) ** 2)
|
||||
Z = (Z1 - Z2) * 2
|
||||
|
||||
fig1, ax = plt.subplots()
|
||||
im = ax.imshow(
|
||||
Z,
|
||||
interpolation="bilinear",
|
||||
cmap=cm.RdYlGn,
|
||||
origin="lower",
|
||||
extent=[-3, 3, -3, 3],
|
||||
vmax=abs(Z).max(),
|
||||
vmin=-abs(Z).max(),
|
||||
)
|
||||
|
||||
plt.show()
|
||||
|
||||
fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(9, 4))
|
||||
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
|
||||
# generate some random test data
|
||||
all_data = [np.random.normal(0, std, 100) for std in range(6, 10)]
|
||||
|
||||
# plot violin plot
|
||||
axs[0].violinplot(all_data, showmeans=False, showmedians=True)
|
||||
axs[0].set_title("Violin plot")
|
||||
|
||||
# plot box plot
|
||||
axs[1].boxplot(all_data)
|
||||
axs[1].set_title("Box plot")
|
||||
|
||||
# adding horizontal grid lines
|
||||
for ax in axs:
|
||||
ax.yaxis.grid(True)
|
||||
ax.set_xticks(
|
||||
[y + 1 for y in range(len(all_data))],
|
||||
labels=["x1", "x2", "x3", "x4"],
|
||||
)
|
||||
ax.set_xlabel("Four separate samples")
|
||||
ax.set_ylabel("Observed values")
|
||||
|
||||
plt.show()
|
||||
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
|
||||
# Compute pie slices
|
||||
N = 20
|
||||
theta = np.linspace(0.0, 2 * np.pi, N, endpoint=False)
|
||||
radii = 10 * np.random.rand(N)
|
||||
width = np.pi / 4 * np.random.rand(N)
|
||||
colors = cmo.cm.haline(radii / 10.0)
|
||||
|
||||
ax = plt.subplot(projection="polar")
|
||||
ax.bar(theta, radii, width=width, bottom=0.0, color=colors, alpha=0.5)
|
||||
|
||||
plt.show()
|
||||
|
||||
methods = [
|
||||
None,
|
||||
"none",
|
||||
"nearest",
|
||||
"bilinear",
|
||||
"bicubic",
|
||||
"spline16",
|
||||
"spline36",
|
||||
"hanning",
|
||||
"hamming",
|
||||
"hermite",
|
||||
"kaiser",
|
||||
"quadric",
|
||||
"catrom",
|
||||
"gaussian",
|
||||
"bessel",
|
||||
"mitchell",
|
||||
"sinc",
|
||||
"lanczos",
|
||||
]
|
||||
|
||||
# Fixing random state for reproducibility
|
||||
np.random.seed(19680801)
|
||||
|
||||
grid = np.random.rand(4, 4)
|
||||
|
||||
fig, axs = plt.subplots(
|
||||
nrows=3,
|
||||
ncols=6,
|
||||
figsize=(9, 6),
|
||||
subplot_kw={"xticks": [], "yticks": []},
|
||||
)
|
||||
|
||||
for ax, interp_method in zip(axs.flat, methods):
|
||||
ax.imshow(grid, interpolation=interp_method)
|
||||
ax.set_title(str(interp_method))
|
||||
|
||||
plt.tight_layout()
|
||||
plt.show()
|
@ -37,7 +37,7 @@ def create_chirp(
|
||||
|
||||
ck = 0
|
||||
csig = 0.5 * chirpduration / np.power(2.0 * np.log(10.0), 0.5 / kurtosis)
|
||||
# csig = csig*-1
|
||||
#csig = csig*-1
|
||||
for k, t in enumerate(time):
|
||||
a = 1.0
|
||||
f = eodf
|
||||
|
@ -1,372 +0,0 @@
|
||||
import os
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from extract_chirps import get_valid_datasets
|
||||
from IPython import embed
|
||||
from modules.behaviour_handling import Behavior, correct_chasing_events
|
||||
from modules.logger import makeLogger
|
||||
from modules.plotstyle import PlotStyle
|
||||
from pandas import read_csv
|
||||
from scipy.stats import pearsonr, wilcoxon
|
||||
|
||||
ps = PlotStyle()
|
||||
|
||||
logger = makeLogger(__name__)
|
||||
|
||||
|
||||
def get_chirp_winner_loser(folder_name, Behavior, order_meta_df):
|
||||
foldername = folder_name.split("/")[-2]
|
||||
winner_row = order_meta_df[order_meta_df["recording"] == foldername]
|
||||
winner = winner_row["winner"].values[0].astype(int)
|
||||
winner_fish1 = winner_row["fish1"].values[0].astype(int)
|
||||
winner_fish2 = winner_row["fish2"].values[0].astype(int)
|
||||
|
||||
if winner > 0:
|
||||
if winner == winner_fish1:
|
||||
winner_fish_id = winner_row["rec_id1"].values[0]
|
||||
loser_fish_id = winner_row["rec_id2"].values[0]
|
||||
|
||||
elif winner == winner_fish2:
|
||||
winner_fish_id = winner_row["rec_id2"].values[0]
|
||||
loser_fish_id = winner_row["rec_id1"].values[0]
|
||||
|
||||
chirp_winner = len(
|
||||
Behavior.chirps[Behavior.chirps_ids == winner_fish_id]
|
||||
)
|
||||
chirp_loser = len(Behavior.chirps[Behavior.chirps_ids == loser_fish_id])
|
||||
|
||||
return chirp_winner, chirp_loser
|
||||
else:
|
||||
return np.nan, np.nan
|
||||
|
||||
|
||||
def get_chirp_size(folder_name, Behavior, order_meta_df, id_meta_df):
|
||||
foldername = folder_name.split("/")[-2]
|
||||
folder_row = order_meta_df[order_meta_df["recording"] == foldername]
|
||||
fish1 = folder_row["fish1"].values[0].astype(int)
|
||||
fish2 = folder_row["fish2"].values[0].astype(int)
|
||||
winner = folder_row["winner"].values[0].astype(int)
|
||||
|
||||
groub = folder_row["group"].values[0].astype(int)
|
||||
size_fish1_row = id_meta_df[
|
||||
(id_meta_df["group"] == groub) & (id_meta_df["fish"] == fish1)
|
||||
]
|
||||
size_fish2_row = id_meta_df[
|
||||
(id_meta_df["group"] == groub) & (id_meta_df["fish"] == fish2)
|
||||
]
|
||||
|
||||
size_winners = [size_fish1_row[col].values[0] for col in ["l1", "l2", "l3"]]
|
||||
size_fish1 = np.nanmean(size_winners)
|
||||
|
||||
size_losers = [size_fish2_row[col].values[0] for col in ["l1", "l2", "l3"]]
|
||||
size_fish2 = np.nanmean(size_losers)
|
||||
|
||||
if winner == fish1:
|
||||
if size_fish1 > size_fish2:
|
||||
size_diff_bigger = size_fish1 - size_fish2
|
||||
size_diff_smaller = size_fish2 - size_fish1
|
||||
|
||||
elif size_fish1 < size_fish2:
|
||||
size_diff_bigger = size_fish1 - size_fish2
|
||||
size_diff_smaller = size_fish2 - size_fish1
|
||||
else:
|
||||
size_diff_bigger = 0
|
||||
size_diff_smaller = 0
|
||||
|
||||
winner_fish_id = folder_row["rec_id1"].values[0]
|
||||
loser_fish_id = folder_row["rec_id2"].values[0]
|
||||
|
||||
elif winner == fish2:
|
||||
if size_fish2 > size_fish1:
|
||||
size_diff_bigger = size_fish2 - size_fish1
|
||||
size_diff_smaller = size_fish1 - size_fish2
|
||||
|
||||
elif size_fish2 < size_fish1:
|
||||
size_diff_bigger = size_fish2 - size_fish1
|
||||
size_diff_smaller = size_fish1 - size_fish2
|
||||
else:
|
||||
size_diff_bigger = 0
|
||||
size_diff_smaller = 0
|
||||
|
||||
winner_fish_id = folder_row["rec_id2"].values[0]
|
||||
loser_fish_id = folder_row["rec_id1"].values[0]
|
||||
else:
|
||||
size_diff_bigger = np.nan
|
||||
size_diff_smaller = np.nan
|
||||
winner_fish_id = np.nan
|
||||
loser_fish_id = np.nan
|
||||
|
||||
return (
|
||||
size_diff_bigger,
|
||||
size_diff_smaller,
|
||||
winner_fish_id,
|
||||
loser_fish_id,
|
||||
)
|
||||
|
||||
chirp_winner = len(Behavior.chirps[Behavior.chirps_ids == winner_fish_id])
|
||||
chirp_loser = len(Behavior.chirps[Behavior.chirps_ids == loser_fish_id])
|
||||
|
||||
return size_diff_bigger, chirp_winner, size_diff_smaller, chirp_loser
|
||||
|
||||
|
||||
def get_chirp_freq(folder_name, Behavior, order_meta_df):
|
||||
foldername = folder_name.split("/")[-2]
|
||||
folder_row = order_meta_df[order_meta_df["recording"] == foldername]
|
||||
fish1 = folder_row["fish1"].values[0].astype(int)
|
||||
fish2 = folder_row["fish2"].values[0].astype(int)
|
||||
|
||||
fish1_freq = folder_row["rec_id1"].values[0].astype(int)
|
||||
fish2_freq = folder_row["rec_id2"].values[0].astype(int)
|
||||
|
||||
chirp_freq_fish1 = np.nanmedian(Behavior.freq[Behavior.ident == fish1_freq])
|
||||
chirp_freq_fish2 = np.nanmedian(Behavior.freq[Behavior.ident == fish2_freq])
|
||||
winner = folder_row["winner"].values[0].astype(int)
|
||||
|
||||
if winner == fish1:
|
||||
# if chirp_freq_fish1 > chirp_freq_fish2:
|
||||
# freq_diff_higher = chirp_freq_fish1 - chirp_freq_fish2
|
||||
# freq_diff_lower = chirp_freq_fish2 - chirp_freq_fish1
|
||||
|
||||
# elif chirp_freq_fish1 < chirp_freq_fish2:
|
||||
# freq_diff_higher = chirp_freq_fish1 - chirp_freq_fish2
|
||||
# freq_diff_lower = chirp_freq_fish2 - chirp_freq_fish1
|
||||
# else:
|
||||
# freq_diff_higher = np.nan
|
||||
# freq_diff_lower = np.nan
|
||||
# winner_fish_id = np.nan
|
||||
# loser_fish_id = np.nan
|
||||
|
||||
winner_fish_id = folder_row["rec_id1"].values[0]
|
||||
winner_fish_freq = chirp_freq_fish1
|
||||
loser_fish_id = folder_row["rec_id2"].values[0]
|
||||
loser_fish_freq = chirp_freq_fish2
|
||||
|
||||
elif winner == fish2:
|
||||
# if chirp_freq_fish2 > chirp_freq_fish1:
|
||||
# freq_diff_higher = chirp_freq_fish2 - chirp_freq_fish1
|
||||
# freq_diff_lower = chirp_freq_fish1 - chirp_freq_fish2
|
||||
|
||||
# elif chirp_freq_fish2 < chirp_freq_fish1:
|
||||
# freq_diff_higher = chirp_freq_fish2 - chirp_freq_fish1
|
||||
# freq_diff_lower = chirp_freq_fish1 - chirp_freq_fish2
|
||||
# else:
|
||||
# freq_diff_higher = np.nan
|
||||
# freq_diff_lower = np.nan
|
||||
# winner_fish_id = np.nan
|
||||
# loser_fish_id = np.nan
|
||||
|
||||
winner_fish_id = folder_row["rec_id2"].values[0]
|
||||
winner_fish_freq = chirp_freq_fish2
|
||||
loser_fish_id = folder_row["rec_id1"].values[0]
|
||||
loser_fish_freq = chirp_freq_fish1
|
||||
else:
|
||||
winner_fish_freq = np.nan
|
||||
loser_fish_freq = np.nan
|
||||
winner_fish_id = np.nan
|
||||
loser_fish_id = np.nan
|
||||
return winner_fish_freq, winner_fish_id, loser_fish_freq, loser_fish_id
|
||||
|
||||
chirp_winner = len(Behavior.chirps[Behavior.chirps_ids == winner_fish_id])
|
||||
chirp_loser = len(Behavior.chirps[Behavior.chirps_ids == loser_fish_id])
|
||||
|
||||
return winner_fish_freq, chirp_winner, loser_fish_freq, chirp_loser
|
||||
|
||||
|
||||
def main(datapath: str):
|
||||
foldernames = [
|
||||
datapath + x + "/"
|
||||
for x in os.listdir(datapath)
|
||||
if os.path.isdir(datapath + x)
|
||||
]
|
||||
foldernames, _ = get_valid_datasets(datapath)
|
||||
path_order_meta = ("/").join(
|
||||
foldernames[0].split("/")[:-2]
|
||||
) + "/order_meta.csv"
|
||||
order_meta_df = read_csv(path_order_meta)
|
||||
order_meta_df["recording"] = order_meta_df["recording"].str[1:-1]
|
||||
path_id_meta = ("/").join(foldernames[0].split("/")[:-2]) + "/id_meta.csv"
|
||||
id_meta_df = read_csv(path_id_meta)
|
||||
|
||||
chirps_winner = []
|
||||
chirps_loser = []
|
||||
|
||||
size_diffs_winner = []
|
||||
size_diffs_loser = []
|
||||
size_chirps_winner = []
|
||||
size_chirps_loser = []
|
||||
|
||||
freq_diffs_higher = []
|
||||
freq_diffs_lower = []
|
||||
freq_chirps_winner = []
|
||||
freq_chirps_loser = []
|
||||
|
||||
for foldername in foldernames:
|
||||
# behabvior is pandas dataframe with all the data
|
||||
if foldername == "../data/mount_data/2020-05-12-10_00/":
|
||||
continue
|
||||
bh = Behavior(foldername)
|
||||
# chirps are not sorted in time (presumably due to prior groupings)
|
||||
# get and sort chirps and corresponding fish_ids of the chirps
|
||||
category = bh.behavior
|
||||
timestamps = bh.start_s
|
||||
# Correct for doubles in chasing on- and offsets to get the right on-/offset pairs
|
||||
# Get rid of tracking faults (two onsets or two offsets after another)
|
||||
category, timestamps = correct_chasing_events(category, timestamps)
|
||||
|
||||
winner_chirp, loser_chirp = get_chirp_winner_loser(
|
||||
foldername, bh, order_meta_df
|
||||
)
|
||||
chirps_winner.append(winner_chirp)
|
||||
chirps_loser.append(loser_chirp)
|
||||
|
||||
(
|
||||
size_diff_bigger,
|
||||
chirp_winner,
|
||||
size_diff_smaller,
|
||||
chirp_loser,
|
||||
) = get_chirp_size(foldername, bh, order_meta_df, id_meta_df)
|
||||
|
||||
(
|
||||
freq_winner,
|
||||
chirp_freq_winner,
|
||||
freq_loser,
|
||||
chirp_freq_loser,
|
||||
) = get_chirp_freq(foldername, bh, order_meta_df)
|
||||
|
||||
freq_diffs_higher.append(freq_winner)
|
||||
freq_diffs_lower.append(freq_loser)
|
||||
freq_chirps_winner.append(chirp_freq_winner)
|
||||
freq_chirps_loser.append(chirp_freq_loser)
|
||||
|
||||
if np.isnan(size_diff_bigger):
|
||||
continue
|
||||
size_diffs_winner.append(size_diff_bigger)
|
||||
size_diffs_loser.append(size_diff_smaller)
|
||||
size_chirps_winner.append(chirp_winner)
|
||||
size_chirps_loser.append(chirp_loser)
|
||||
|
||||
pearsonr(size_diffs_winner, size_chirps_winner)
|
||||
pearsonr(size_diffs_loser, size_chirps_loser)
|
||||
|
||||
fig, (ax1, ax2, ax3) = plt.subplots(
|
||||
1,
|
||||
3,
|
||||
figsize=(21 * ps.cm, 7 * ps.cm),
|
||||
width_ratios=[1, 0.8, 0.8],
|
||||
sharey=True,
|
||||
)
|
||||
plt.subplots_adjust(
|
||||
left=0.11, right=0.948, top=0.86, wspace=0.343, bottom=0.198
|
||||
)
|
||||
scatterwinner = 1.15
|
||||
scatterloser = 1.85
|
||||
chirps_winner = np.asarray(chirps_winner)[~np.isnan(chirps_winner)]
|
||||
chirps_loser = np.asarray(chirps_loser)[~np.isnan(chirps_loser)]
|
||||
embed()
|
||||
exit()
|
||||
freq_diffs_higher = np.asarray(freq_diffs_higher)[
|
||||
~np.isnan(freq_diffs_higher)
|
||||
]
|
||||
freq_diffs_lower = np.asarray(freq_diffs_lower)[~np.isnan(freq_diffs_lower)]
|
||||
freq_chirps_winner = np.asarray(freq_chirps_winner)[
|
||||
~np.isnan(freq_chirps_winner)
|
||||
]
|
||||
freq_chirps_loser = np.asarray(freq_chirps_loser)[
|
||||
~np.isnan(freq_chirps_loser)
|
||||
]
|
||||
|
||||
stat = wilcoxon(chirps_winner, chirps_loser)
|
||||
print(stat)
|
||||
winner_color = ps.gblue2
|
||||
loser_color = ps.gblue1
|
||||
|
||||
bplot1 = ax1.boxplot(
|
||||
chirps_winner, positions=[0.9], showfliers=False, patch_artist=True
|
||||
)
|
||||
|
||||
bplot2 = ax1.boxplot(
|
||||
chirps_loser, positions=[2.1], showfliers=False, patch_artist=True
|
||||
)
|
||||
|
||||
ax1.scatter(
|
||||
np.ones(len(chirps_winner)) * scatterwinner,
|
||||
chirps_winner,
|
||||
color=winner_color,
|
||||
)
|
||||
ax1.scatter(
|
||||
np.ones(len(chirps_loser)) * scatterloser,
|
||||
chirps_loser,
|
||||
color=loser_color,
|
||||
)
|
||||
ax1.set_xticklabels(["Winner", "Loser"])
|
||||
|
||||
ax1.text(
|
||||
0.1,
|
||||
0.85,
|
||||
f"n={len(chirps_loser)}",
|
||||
transform=ax1.transAxes,
|
||||
color=ps.white,
|
||||
)
|
||||
|
||||
for w, l in zip(chirps_winner, chirps_loser):
|
||||
ax1.plot(
|
||||
[scatterwinner, scatterloser],
|
||||
[w, l],
|
||||
color=ps.white,
|
||||
alpha=0.6,
|
||||
linewidth=1,
|
||||
zorder=-1,
|
||||
)
|
||||
ax1.set_ylabel("Chirp counts", color=ps.white)
|
||||
ax1.set_xlabel("Competition outcome", color=ps.white)
|
||||
|
||||
ps.set_boxplot_color(bplot1, winner_color)
|
||||
ps.set_boxplot_color(bplot2, loser_color)
|
||||
|
||||
ax2.scatter(
|
||||
size_diffs_winner,
|
||||
size_chirps_winner,
|
||||
color=winner_color,
|
||||
label="Winner",
|
||||
)
|
||||
ax2.scatter(
|
||||
size_diffs_loser, size_chirps_loser, color=loser_color, label="Loser"
|
||||
)
|
||||
|
||||
ax2.text(
|
||||
0.05,
|
||||
0.85,
|
||||
f"n={len(size_chirps_loser)}",
|
||||
transform=ax2.transAxes,
|
||||
color=ps.white,
|
||||
)
|
||||
|
||||
ax2.set_xlabel("Size difference [cm]")
|
||||
# ax2.set_xticks(np.arange(-10, 10.1, 2))
|
||||
ax3.scatter(freq_diffs_higher, freq_chirps_winner, color=winner_color)
|
||||
ax3.scatter(freq_diffs_lower, freq_chirps_loser, color=loser_color)
|
||||
|
||||
ax3.text(
|
||||
0.1,
|
||||
0.85,
|
||||
f"n={len(np.asarray(freq_chirps_winner)[~np.isnan(freq_chirps_loser)])}",
|
||||
transform=ax3.transAxes,
|
||||
color=ps.white,
|
||||
)
|
||||
|
||||
ax3.set_xlabel("EODf [Hz]")
|
||||
handles, labels = ax2.get_legend_handles_labels()
|
||||
fig.legend(
|
||||
handles, labels, loc="upper center", ncol=2, bbox_to_anchor=(0.5, 1.04)
|
||||
)
|
||||
# pearson r
|
||||
plt.savefig("../poster/figs/chirps_winner_loser.pdf")
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Path to the data
|
||||
datapath = "../data/mount_data/"
|
||||
|
||||
main(datapath)
|
@ -1,117 +0,0 @@
|
||||
import numpy as np
|
||||
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from scipy.stats import pearsonr, spearmanr
|
||||
from thunderfish.powerspectrum import decibel
|
||||
|
||||
from IPython import embed
|
||||
from pandas import read_csv
|
||||
from modules.logger import makeLogger
|
||||
from modules.plotstyle import PlotStyle
|
||||
from modules.behaviour_handling import Behavior, correct_chasing_events
|
||||
from modules.datahandling import flatten
|
||||
|
||||
|
||||
ps = PlotStyle()
|
||||
|
||||
logger = makeLogger(__name__)
|
||||
|
||||
|
||||
def main(datapath: str):
|
||||
foldernames = [
|
||||
datapath + x + "/"
|
||||
for x in os.listdir(datapath)
|
||||
if os.path.isdir(datapath + x)
|
||||
]
|
||||
time_precents = []
|
||||
chirps_percents = []
|
||||
for foldername in foldernames:
|
||||
# behabvior is pandas dataframe with all the data
|
||||
if foldername == "../data/mount_data/2020-05-12-10_00/":
|
||||
continue
|
||||
bh = Behavior(foldername)
|
||||
|
||||
category = bh.behavior
|
||||
timestamps = bh.start_s
|
||||
# Correct for doubles in chasing on- and offsets to get the right on-/offset pairs
|
||||
# Get rid of tracking faults (two onsets or two offsets after another)
|
||||
category, timestamps = correct_chasing_events(category, timestamps)
|
||||
|
||||
chasing_onset = timestamps[category == 0]
|
||||
chasing_offset = timestamps[category == 1]
|
||||
if len(chasing_onset) != len(chasing_offset):
|
||||
embed()
|
||||
|
||||
chirps_in_chasings = []
|
||||
for onset, offset in zip(chasing_onset, chasing_offset):
|
||||
chirps_in_chasing = [
|
||||
c for c in bh.chirps if (c > onset) & (c < offset)
|
||||
]
|
||||
chirps_in_chasings.append(chirps_in_chasing)
|
||||
|
||||
try:
|
||||
time_chasing = np.sum(
|
||||
chasing_offset[chasing_offset < 3 * 60 * 60]
|
||||
- chasing_onset[chasing_onset < 3 * 60 * 60]
|
||||
)
|
||||
except:
|
||||
time_chasing = np.sum(
|
||||
chasing_offset[chasing_offset < 3 * 60 * 60]
|
||||
- chasing_onset[chasing_onset < 3 * 60 * 60][:-1]
|
||||
)
|
||||
|
||||
time_chasing_percent = (time_chasing / (3 * 60 * 60)) * 100
|
||||
chirps_chasing = np.asarray(flatten(chirps_in_chasings))
|
||||
chirps_chasing_new = chirps_chasing[chirps_chasing < 3 * 60 * 60]
|
||||
chirps_percent = (
|
||||
len(chirps_chasing_new) / len(bh.chirps[bh.chirps < 3 * 60 * 60])
|
||||
) * 100
|
||||
|
||||
time_precents.append(time_chasing_percent)
|
||||
chirps_percents.append(chirps_percent)
|
||||
|
||||
fig, ax = plt.subplots(1, 1, figsize=(7 * ps.cm, 7 * ps.cm))
|
||||
scatter_time = 1.20
|
||||
scatter_chirps = 1.80
|
||||
size = 10
|
||||
bplot1 = ax.boxplot(
|
||||
[time_precents, chirps_percents], showfliers=False, patch_artist=True
|
||||
)
|
||||
ps.set_boxplot_color(bplot1, ps.gray)
|
||||
ax.set_xticklabels(["Time \nchasing", "Chirps \nin chasing"])
|
||||
ax.set_ylabel("Percent")
|
||||
ax.scatter(
|
||||
np.ones(len(time_precents)) * scatter_time,
|
||||
time_precents,
|
||||
facecolor=ps.white,
|
||||
s=size,
|
||||
)
|
||||
ax.scatter(
|
||||
np.ones(len(chirps_percents)) * scatter_chirps,
|
||||
chirps_percents,
|
||||
facecolor=ps.white,
|
||||
s=size,
|
||||
)
|
||||
|
||||
for i in range(len(time_precents)):
|
||||
ax.plot(
|
||||
[scatter_time, scatter_chirps],
|
||||
[time_precents[i], chirps_percents[i]],
|
||||
alpha=0.6,
|
||||
linewidth=1,
|
||||
color=ps.white,
|
||||
)
|
||||
|
||||
ax.text(0.1, 0.9, f"n={len(time_precents)}", transform=ax.transAxes)
|
||||
plt.subplots_adjust(left=0.221, bottom=0.186, right=0.97, top=0.967)
|
||||
plt.savefig("../poster/figs/chirps_in_chasing.pdf")
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Path to the data
|
||||
datapath = "../data/mount_data/"
|
||||
main(datapath)
|
@ -1,160 +1,203 @@
|
||||
import numpy as np
|
||||
|
||||
import os
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.pyplot as plt
|
||||
from thunderfish.powerspectrum import decibel
|
||||
|
||||
from IPython import embed
|
||||
from pandas import read_csv
|
||||
from modules.logger import makeLogger
|
||||
from modules.plotstyle import PlotStyle
|
||||
from modules.behaviour_handling import Behavior, correct_chasing_events
|
||||
|
||||
from extract_chirps import get_valid_datasets
|
||||
|
||||
ps = PlotStyle()
|
||||
|
||||
logger = makeLogger(__name__)
|
||||
|
||||
|
||||
class Behavior:
|
||||
"""Load behavior data from csv file as class attributes
|
||||
Attributes
|
||||
----------
|
||||
behavior: 0: chasing onset, 1: chasing offset, 2: physical contact
|
||||
behavior_type:
|
||||
behavioral_category:
|
||||
comment_start:
|
||||
comment_stop:
|
||||
dataframe: pandas dataframe with all the data
|
||||
duration_s:
|
||||
media_file:
|
||||
observation_date:
|
||||
observation_id:
|
||||
start_s: start time of the event in seconds
|
||||
stop_s: stop time of the event in seconds
|
||||
total_length:
|
||||
"""
|
||||
|
||||
def __init__(self, folder_path: str) -> None:
|
||||
|
||||
|
||||
LED_on_time_BORIS = np.load(os.path.join(folder_path, 'LED_on_time.npy'), allow_pickle=True)
|
||||
|
||||
csv_filename = [f for f in os.listdir(folder_path) if f.endswith('.csv')][0]
|
||||
logger.info(f'CSV file: {csv_filename}')
|
||||
self.dataframe = read_csv(os.path.join(folder_path, csv_filename))
|
||||
|
||||
self.chirps = np.load(os.path.join(folder_path, 'chirps.npy'), allow_pickle=True)
|
||||
self.chirps_ids = np.load(os.path.join(folder_path, 'chirps_ids.npy'), allow_pickle=True)
|
||||
|
||||
self.ident = np.load(os.path.join(folder_path, 'ident_v.npy'), allow_pickle=True)
|
||||
self.idx = np.load(os.path.join(folder_path, 'idx_v.npy'), allow_pickle=True)
|
||||
self.freq = np.load(os.path.join(folder_path, 'fund_v.npy'), allow_pickle=True)
|
||||
self.time = np.load(os.path.join(folder_path, "times.npy"), allow_pickle=True)
|
||||
self.spec = np.load(os.path.join(folder_path, "spec.npy"), allow_pickle=True)
|
||||
|
||||
for k, key in enumerate(self.dataframe.keys()):
|
||||
key = key.lower()
|
||||
if ' ' in key:
|
||||
key = key.replace(' ', '_')
|
||||
if '(' in key:
|
||||
key = key.replace('(', '')
|
||||
key = key.replace(')', '')
|
||||
setattr(self, key, np.array(self.dataframe[self.dataframe.keys()[k]]))
|
||||
|
||||
last_LED_t_BORIS = LED_on_time_BORIS[-1]
|
||||
real_time_range = self.time[-1] - self.time[0]
|
||||
factor = 1.034141
|
||||
shift = last_LED_t_BORIS - real_time_range * factor
|
||||
self.start_s = (self.start_s - shift) / factor
|
||||
self.stop_s = (self.stop_s - shift) / factor
|
||||
|
||||
def correct_chasing_events(
|
||||
category: np.ndarray,
|
||||
timestamps: np.ndarray
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
|
||||
onset_ids = np.arange(
|
||||
len(category))[category == 0]
|
||||
offset_ids = np.arange(
|
||||
len(category))[category == 1]
|
||||
|
||||
# Check whether on- or offset is longer and calculate length difference
|
||||
if len(onset_ids) > len(offset_ids):
|
||||
len_diff = len(onset_ids) - len(offset_ids)
|
||||
longer_array = onset_ids
|
||||
shorter_array = offset_ids
|
||||
logger.info(f'Onsets are greater than offsets by {len_diff}')
|
||||
elif len(onset_ids) < len(offset_ids):
|
||||
len_diff = len(offset_ids) - len(onset_ids)
|
||||
longer_array = offset_ids
|
||||
shorter_array = onset_ids
|
||||
logger.info(f'Offsets are greater than offsets by {len_diff}')
|
||||
elif len(onset_ids) == len(offset_ids):
|
||||
logger.info('Chasing events are equal')
|
||||
return category, timestamps
|
||||
|
||||
|
||||
# Correct the wrong chasing events; delete double events
|
||||
wrong_ids = []
|
||||
for i in range(len(longer_array)-(len_diff+1)):
|
||||
if (shorter_array[i] > longer_array[i]) & (shorter_array[i] < longer_array[i+1]):
|
||||
pass
|
||||
else:
|
||||
wrong_ids.append(longer_array[i])
|
||||
longer_array = np.delete(longer_array, i)
|
||||
|
||||
category = np.delete(
|
||||
category, wrong_ids)
|
||||
timestamps = np.delete(
|
||||
timestamps, wrong_ids)
|
||||
return category, timestamps
|
||||
|
||||
|
||||
|
||||
def main(datapath: str):
|
||||
foldernames = [
|
||||
datapath + x + "/"
|
||||
for x in os.listdir(datapath)
|
||||
if os.path.isdir(datapath + x)
|
||||
]
|
||||
foldernames, _ = get_valid_datasets(datapath)
|
||||
|
||||
for foldername in foldernames[3:4]:
|
||||
print(foldername)
|
||||
# foldername = foldernames[0]
|
||||
if foldername == "../data/mount_data/2020-05-12-10_00/":
|
||||
continue
|
||||
# behabvior is pandas dataframe with all the data
|
||||
bh = Behavior(foldername)
|
||||
# 2020-06-11-10
|
||||
category = bh.behavior
|
||||
timestamps = bh.start_s
|
||||
# Correct for doubles in chasing on- and offsets to get the right on-/offset pairs
|
||||
# Get rid of tracking faults (two onsets or two offsets after another)
|
||||
category, timestamps = correct_chasing_events(category, timestamps)
|
||||
|
||||
# split categories
|
||||
chasing_onset = (timestamps[category == 0] / 60) / 60
|
||||
chasing_offset = (timestamps[category == 1] / 60) / 60
|
||||
physical_contact = (timestamps[category == 2] / 60) / 60
|
||||
|
||||
all_fish_ids = np.unique(bh.chirps_ids)
|
||||
fish1_id = all_fish_ids[0]
|
||||
fish2_id = all_fish_ids[1]
|
||||
# Associate chirps to inidividual fish
|
||||
fish1 = (bh.chirps[bh.chirps_ids == fish1_id] / 60) / 60
|
||||
fish2 = (bh.chirps[bh.chirps_ids == fish2_id] / 60) / 60
|
||||
embed()
|
||||
exit()
|
||||
fish1_color = ps.gblue2
|
||||
fish2_color = ps.gblue1
|
||||
fig, ax = plt.subplots(
|
||||
5,
|
||||
1,
|
||||
figsize=(21 * ps.cm, 10 * ps.cm),
|
||||
height_ratios=[0.5, 0.5, 0.5, 0.2, 6],
|
||||
sharex=True,
|
||||
)
|
||||
# marker size
|
||||
s = 80
|
||||
ax[0].scatter(
|
||||
physical_contact,
|
||||
np.ones(len(physical_contact)),
|
||||
color=ps.gray,
|
||||
marker="|",
|
||||
s=s,
|
||||
)
|
||||
ax[1].scatter(
|
||||
chasing_onset,
|
||||
np.ones(len(chasing_onset)),
|
||||
color=ps.gray,
|
||||
marker="|",
|
||||
s=s,
|
||||
)
|
||||
ax[2].scatter(
|
||||
fish1,
|
||||
np.ones(len(fish1)) - 0.25,
|
||||
color=fish1_color,
|
||||
marker="|",
|
||||
s=s,
|
||||
)
|
||||
ax[2].scatter(
|
||||
fish2,
|
||||
np.zeros(len(fish2)) + 0.25,
|
||||
color=fish2_color,
|
||||
marker="|",
|
||||
s=s,
|
||||
)
|
||||
|
||||
freq_temp = bh.freq[bh.ident == fish1_id]
|
||||
time_temp = bh.time[bh.idx[bh.ident == fish1_id]]
|
||||
ax[4].plot((time_temp / 60) / 60, freq_temp, color=fish1_color)
|
||||
|
||||
freq_temp = bh.freq[bh.ident == fish2_id]
|
||||
time_temp = bh.time[bh.idx[bh.ident == fish2_id]]
|
||||
ax[4].plot((time_temp / 60) / 60, freq_temp, color=fish2_color)
|
||||
|
||||
# ax[3].imshow(decibel(bh.spec), extent=[bh.time[0]/60/60, bh.time[-1]/60/60, 0, 2000], aspect='auto', origin='lower')
|
||||
# behabvior is pandas dataframe with all the data
|
||||
bh = Behavior(datapath)
|
||||
# chirps are not sorted in time (presumably due to prior groupings)
|
||||
# get and sort chirps and corresponding fish_ids of the chirps
|
||||
chirps = bh.chirps[np.argsort(bh.chirps)]
|
||||
chirps_fish_ids = bh.chirps_ids[np.argsort(bh.chirps)]
|
||||
category = bh.behavior
|
||||
timestamps = bh.start_s
|
||||
# Correct for doubles in chasing on- and offsets to get the right on-/offset pairs
|
||||
# Get rid of tracking faults (two onsets or two offsets after another)
|
||||
category, timestamps = correct_chasing_events(category, timestamps)
|
||||
|
||||
# split categories
|
||||
chasing_onset = (timestamps[category == 0]/ 60) /60
|
||||
chasing_offset = (timestamps[category == 1]/ 60) /60
|
||||
physical_contact = (timestamps[category == 2] / 60) /60
|
||||
|
||||
all_fish_ids = np.unique(chirps_fish_ids)
|
||||
fish1_id = all_fish_ids[0]
|
||||
fish2_id = all_fish_ids[1]
|
||||
# Associate chirps to inidividual fish
|
||||
fish1 = (chirps[chirps_fish_ids == fish1_id] / 60) /60
|
||||
fish2 = (chirps[chirps_fish_ids == fish2_id] / 60) /60
|
||||
fish1_color = ps.red
|
||||
fish2_color = ps.orange
|
||||
|
||||
fig, ax = plt.subplots(4, 1, figsize=(10, 5), height_ratios=[0.5, 0.5, 0.5, 6], sharex=True)
|
||||
# marker size
|
||||
s = 200
|
||||
ax[0].scatter(physical_contact, np.ones(len(physical_contact)), color='firebrick', marker='|', s=s)
|
||||
ax[1].scatter(chasing_onset, np.ones(len(chasing_onset)), color='green', marker='|', s=s )
|
||||
ax[2].scatter(fish1, np.ones(len(fish1))-0.25, color=fish1_color, marker='|', s=s)
|
||||
ax[2].scatter(fish2, np.zeros(len(fish2))+0.25, color=fish2_color, marker='|', s=s)
|
||||
|
||||
|
||||
freq_temp = bh.freq[bh.ident==fish1_id]
|
||||
time_temp = bh.time[bh.idx[bh.ident==fish1_id]]
|
||||
ax[3].plot((time_temp/ 60) /60, freq_temp, color=fish1_color)
|
||||
|
||||
freq_temp = bh.freq[bh.ident==fish2_id]
|
||||
time_temp = bh.time[bh.idx[bh.ident==fish2_id]]
|
||||
ax[3].plot((time_temp/ 60) /60, freq_temp, color=fish2_color)
|
||||
|
||||
#ax[3].imshow(decibel(bh.spec), extent=[bh.time[0]/60/60, bh.time[-1]/60/60, 0, 2000], aspect='auto', origin='lower')
|
||||
|
||||
# Hide grid lines
|
||||
ax[0].grid(False)
|
||||
ax[0].set_frame_on(False)
|
||||
ax[0].set_xticks([])
|
||||
ax[0].set_yticks([])
|
||||
ps.hide_ax(ax[0])
|
||||
|
||||
ax[1].grid(False)
|
||||
ax[1].set_frame_on(False)
|
||||
ax[1].set_xticks([])
|
||||
ax[1].set_yticks([])
|
||||
ps.hide_ax(ax[1])
|
||||
|
||||
ax[2].grid(False)
|
||||
ax[2].set_frame_on(False)
|
||||
ax[2].set_yticks([])
|
||||
ax[2].set_xticks([])
|
||||
ps.hide_ax(ax[2])
|
||||
|
||||
ax[4].axvspan(0, 3, 0, 5, facecolor="grey", alpha=0.5)
|
||||
ax[4].set_xticks(np.arange(0, 6.1, 0.5))
|
||||
ps.hide_ax(ax[3])
|
||||
|
||||
labelpad = 30
|
||||
fsize = 12
|
||||
|
||||
ax[0].set_ylabel(
|
||||
"Contact", rotation=0, labelpad=labelpad, fontsize=fsize
|
||||
)
|
||||
ax[0].yaxis.set_label_coords(-0.062, -0.08)
|
||||
ax[1].set_ylabel(
|
||||
"Chasing", rotation=0, labelpad=labelpad, fontsize=fsize
|
||||
)
|
||||
ax[1].yaxis.set_label_coords(-0.06, -0.08)
|
||||
ax[2].set_ylabel(
|
||||
"Chirps", rotation=0, labelpad=labelpad, fontsize=fsize
|
||||
)
|
||||
ax[2].yaxis.set_label_coords(-0.07, -0.08)
|
||||
ax[4].set_ylabel("EODf")
|
||||
|
||||
ax[4].set_xlabel("Time [h]")
|
||||
# ax[0].set_title(foldername.split('/')[-2])
|
||||
# 2020-03-31-9_59
|
||||
plt.subplots_adjust(left=0.158, right=0.987, top=0.918, bottom=0.136)
|
||||
plt.savefig("../poster/figs/timeline.svg")
|
||||
plt.show()
|
||||
ax[0].grid(False)
|
||||
ax[0].set_frame_on(False)
|
||||
ax[0].set_xticks([])
|
||||
ax[0].set_yticks([])
|
||||
ps.hide_ax(ax[0])
|
||||
|
||||
|
||||
ax[1].grid(False)
|
||||
ax[1].set_frame_on(False)
|
||||
ax[1].set_xticks([])
|
||||
ax[1].set_yticks([])
|
||||
ps.hide_ax(ax[1])
|
||||
|
||||
ax[2].grid(False)
|
||||
ax[2].set_frame_on(False)
|
||||
ax[2].set_yticks([])
|
||||
ax[2].set_xticks([])
|
||||
ps.hide_ax(ax[2])
|
||||
|
||||
|
||||
|
||||
ax[3].axvspan(0, 3, 0, 5, facecolor='grey', alpha=0.5)
|
||||
ax[3].set_xticks(np.arange(0, 6.1, 0.5))
|
||||
|
||||
labelpad = 40
|
||||
ax[0].set_ylabel('Physical contact', rotation=0, labelpad=labelpad)
|
||||
ax[1].set_ylabel('Chasing events', rotation=0, labelpad=labelpad)
|
||||
ax[2].set_ylabel('Chirps', rotation=0, labelpad=labelpad)
|
||||
ax[3].set_ylabel('EODf')
|
||||
|
||||
ax[3].set_xlabel('Time [h]')
|
||||
|
||||
plt.show()
|
||||
embed()
|
||||
|
||||
# plot chirps
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
# Path to the data
|
||||
datapath = "../data/mount_data/"
|
||||
datapath = '../data/mount_data/2020-05-13-10_00/'
|
||||
main(datapath)
|
||||
|
@ -11,45 +11,43 @@ ps = PlotStyle()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
# Load data
|
||||
datapath = "../data/2022-06-02-10_00/"
|
||||
data = LoadData(datapath)
|
||||
|
||||
# good chirp times for data: 2022-06-02-10_00
|
||||
window_start_seconds = 3 * 60 * 60 + 6 * 60 + 43.5 + 9 + 6.20
|
||||
window_start_seconds = 3 * 60 * 60 + 6 * 60 + 43.5 + 9 + 6.25
|
||||
window_start_index = window_start_seconds * data.raw_rate
|
||||
window_duration_seconds = 0.4
|
||||
window_duration_seconds = 0.2
|
||||
window_duration_index = window_duration_seconds * data.raw_rate
|
||||
|
||||
timescaler = 1000
|
||||
|
||||
raw = data.raw[
|
||||
window_start_index : window_start_index + window_duration_index, 10
|
||||
]
|
||||
raw = data.raw[window_start_index:window_start_index +
|
||||
window_duration_index, 10]
|
||||
|
||||
fig, (ax1, ax2) = plt.subplots(
|
||||
1, 2, figsize=(21 * ps.cm, 8 * ps.cm), sharex=True, sharey=True
|
||||
)
|
||||
fig, (ax1, ax2, ax3) = plt.subplots(
|
||||
3, 1, figsize=(12 * ps.cm, 10*ps.cm), sharex=True, sharey=True)
|
||||
|
||||
# plot instantaneous frequency
|
||||
filtered1 = bandpass_filter(
|
||||
signal=raw, lowf=750, highf=1200, samplerate=data.raw_rate
|
||||
)
|
||||
signal=raw, lowf=750, highf=1200, samplerate=data.raw_rate)
|
||||
filtered2 = bandpass_filter(
|
||||
signal=raw, lowf=550, highf=700, samplerate=data.raw_rate
|
||||
)
|
||||
signal=raw, lowf=550, highf=700, samplerate=data.raw_rate)
|
||||
|
||||
freqtime1, freq1 = instantaneous_frequency(
|
||||
filtered1, data.raw_rate, smoothing_window=3
|
||||
)
|
||||
filtered1, data.raw_rate, smoothing_window=3)
|
||||
freqtime2, freq2 = instantaneous_frequency(
|
||||
filtered2, data.raw_rate, smoothing_window=3
|
||||
)
|
||||
filtered2, data.raw_rate, smoothing_window=3)
|
||||
|
||||
ax1.plot(freqtime1 * timescaler, freq1, color=ps.g, lw=2, label="Fish 1")
|
||||
ax1.plot(freqtime2 * timescaler, freq2, color=ps.gray, lw=2, label="Fish 2")
|
||||
# ax.legend(bbox_to_anchor=(1.04, 1), borderaxespad=0)
|
||||
# # ps.hide_xax(ax1)
|
||||
ax1.plot(freqtime1*timescaler, freq1, color=ps.gblue1,
|
||||
lw=2, label=f"fish 1, {np.median(freq1):.0f} Hz")
|
||||
ax1.plot(freqtime2*timescaler, freq2, color=ps.gblue3,
|
||||
lw=2, label=f"fish 2, {np.median(freq2):.0f} Hz")
|
||||
ax1.legend(bbox_to_anchor=(0, 1.02, 1, 0.2), loc="lower center",
|
||||
mode="normal", borderaxespad=0, ncol=2)
|
||||
ps.hide_xax(ax1)
|
||||
|
||||
# plot fine spectrogram
|
||||
spec_power, spec_freqs, spec_times = spectrogram(
|
||||
@ -59,15 +57,15 @@ def main():
|
||||
overlap_frac=0.2,
|
||||
)
|
||||
|
||||
ylims = [300, 1300]
|
||||
ylims = [300, 1200]
|
||||
fmask = np.zeros(spec_freqs.shape, dtype=bool)
|
||||
fmask[(spec_freqs > ylims[0]) & (spec_freqs < ylims[1])] = True
|
||||
|
||||
ax1.imshow(
|
||||
ax2.imshow(
|
||||
decibel(spec_power[fmask, :]),
|
||||
extent=[
|
||||
spec_times[0] * timescaler,
|
||||
spec_times[-1] * timescaler,
|
||||
spec_times[0]*timescaler,
|
||||
spec_times[-1]*timescaler,
|
||||
spec_freqs[fmask][0],
|
||||
spec_freqs[fmask][-1],
|
||||
],
|
||||
@ -75,24 +73,23 @@ def main():
|
||||
origin="lower",
|
||||
interpolation="gaussian",
|
||||
alpha=1,
|
||||
# vmin=-100,
|
||||
# vmax=-80,
|
||||
)
|
||||
ps.hide_xax(ax2)
|
||||
|
||||
# # plot coarse spectrogram
|
||||
# plot coarse spectrogram
|
||||
spec_power, spec_freqs, spec_times = spectrogram(
|
||||
raw,
|
||||
ratetime=data.raw_rate,
|
||||
freq_resolution=15,
|
||||
freq_resolution=10,
|
||||
overlap_frac=0.3,
|
||||
)
|
||||
fmask = np.zeros(spec_freqs.shape, dtype=bool)
|
||||
fmask[(spec_freqs > ylims[0]) & (spec_freqs < ylims[1])] = True
|
||||
ax2.imshow(
|
||||
ax3.imshow(
|
||||
decibel(spec_power[fmask, :]),
|
||||
extent=[
|
||||
spec_times[0] * timescaler,
|
||||
spec_times[-1] * timescaler,
|
||||
spec_times[0]*timescaler,
|
||||
spec_times[-1]*timescaler,
|
||||
spec_freqs[fmask][0],
|
||||
spec_freqs[fmask][-1],
|
||||
],
|
||||
@ -102,32 +99,23 @@ def main():
|
||||
alpha=1,
|
||||
)
|
||||
# ps.hide_xax(ax3)
|
||||
ax2.plot(freqtime1 * timescaler, freq1, color=ps.g, lw=2, label="_")
|
||||
ax2.plot(freqtime2 * timescaler, freq2, color=ps.gray, lw=2, label="_")
|
||||
|
||||
ax2.set_xlim(75, 200)
|
||||
ax1.set_ylim(400, 1200)
|
||||
|
||||
fig.supxlabel("Time [ms]", fontsize=14)
|
||||
fig.supylabel("Frequency [Hz]", fontsize=14)
|
||||
|
||||
handles, labels = ax1.get_legend_handles_labels()
|
||||
ax2.legend(
|
||||
handles,
|
||||
labels,
|
||||
bbox_to_anchor=(1.04, 1),
|
||||
loc="upper left",
|
||||
ncol=1,
|
||||
)
|
||||
|
||||
ps.letter_subplots(xoffset=[-0.27, -0.1], yoffset=1.05)
|
||||
ax3.set_xlabel("time [ms]")
|
||||
ax2.set_ylabel("frequency [Hz]")
|
||||
|
||||
plt.subplots_adjust(
|
||||
left=0.12, right=0.85, top=0.89, bottom=0.18, hspace=0.35
|
||||
)
|
||||
ax1.set_yticks(np.arange(400, 1201, 400))
|
||||
ax1.spines.left.set_bounds((400, 1200))
|
||||
ax2.set_yticks(np.arange(400, 1201, 400))
|
||||
ax2.spines.left.set_bounds((400, 1200))
|
||||
ax3.set_yticks(np.arange(400, 1201, 400))
|
||||
ax3.spines.left.set_bounds((400, 1200))
|
||||
|
||||
plt.subplots_adjust(left=0.17, right=0.98, top=0.9,
|
||||
bottom=0.14, hspace=0.35)
|
||||
|
||||
plt.savefig("../poster/figs/introplot.pdf")
|
||||
plt.savefig('../poster/figs/introplot.pdf')
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@ -1,561 +0,0 @@
|
||||
from modules.plotstyle import PlotStyle
|
||||
from modules.behaviour_handling import (
|
||||
Behavior,
|
||||
correct_chasing_events,
|
||||
center_chirps,
|
||||
)
|
||||
from modules.datahandling import flatten, causal_kde1d, acausal_kde1d
|
||||
from modules.logger import makeLogger
|
||||
from pandas import read_csv
|
||||
from IPython import embed
|
||||
from tqdm import tqdm
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import os
|
||||
from extract_chirps import get_valid_datasets
|
||||
|
||||
|
||||
logger = makeLogger(__name__)
|
||||
ps = PlotStyle()
|
||||
|
||||
|
||||
def bootstrap(
|
||||
data,
|
||||
nresamples,
|
||||
kde_time,
|
||||
kernel_width,
|
||||
event_times,
|
||||
time_before,
|
||||
time_after,
|
||||
):
|
||||
bootstrapped_kdes = []
|
||||
data = data[data <= 3 * 60 * 60] # only night time
|
||||
|
||||
diff_data = np.diff(np.sort(data), prepend=0)
|
||||
# if len(data) != 0:
|
||||
# mean_chirprate = (len(data) - 1) / (data[-1] - data[0])
|
||||
|
||||
for i in tqdm(range(nresamples)):
|
||||
np.random.shuffle(diff_data)
|
||||
|
||||
bootstrapped_data = np.cumsum(diff_data)
|
||||
# bootstrapped_data = data + np.random.randn(len(data)) * 10
|
||||
|
||||
bootstrap_data_centered = center_chirps(
|
||||
bootstrapped_data, event_times, time_before, time_after
|
||||
)
|
||||
|
||||
bootstrapped_kde = acausal_kde1d(
|
||||
bootstrap_data_centered, time=kde_time, width=kernel_width
|
||||
)
|
||||
|
||||
bootstrapped_kde = list(np.asarray(bootstrapped_kde) / len(event_times))
|
||||
|
||||
bootstrapped_kdes.append(bootstrapped_kde)
|
||||
|
||||
return bootstrapped_kdes
|
||||
|
||||
|
||||
def jackknife(
|
||||
data,
|
||||
nresamples,
|
||||
subsetsize,
|
||||
kde_time,
|
||||
kernel_width,
|
||||
event_times,
|
||||
time_before,
|
||||
time_after,
|
||||
):
|
||||
jackknife_kdes = []
|
||||
data = data[data <= 3 * 60 * 60] # only night time
|
||||
subsetsize = int(len(data) * subsetsize)
|
||||
|
||||
diff_data = np.diff(np.sort(data), prepend=0)
|
||||
|
||||
for i in tqdm(range(nresamples)):
|
||||
jackknifed_data = np.random.choice(diff_data, subsetsize, replace=False)
|
||||
|
||||
jackknifed_data = np.cumsum(jackknifed_data)
|
||||
|
||||
jackknifed_data_centered = center_chirps(
|
||||
jackknifed_data, event_times, time_before, time_after
|
||||
)
|
||||
|
||||
jackknifed_kde = acausal_kde1d(
|
||||
jackknifed_data_centered, time=kde_time, width=kernel_width
|
||||
)
|
||||
|
||||
jackknifed_kde = list(np.asarray(jackknifed_kde) / len(event_times))
|
||||
|
||||
jackknife_kdes.append(jackknifed_kde)
|
||||
return jackknife_kdes
|
||||
|
||||
|
||||
def get_chirp_winner_loser(folder_name, Behavior, order_meta_df):
|
||||
foldername = folder_name.split("/")[-2]
|
||||
winner_row = order_meta_df[order_meta_df["recording"] == foldername]
|
||||
winner = winner_row["winner"].values[0].astype(int)
|
||||
winner_fish1 = winner_row["fish1"].values[0].astype(int)
|
||||
winner_fish2 = winner_row["fish2"].values[0].astype(int)
|
||||
|
||||
if winner > 0:
|
||||
if winner == winner_fish1:
|
||||
winner_fish_id = winner_row["rec_id1"].values[0]
|
||||
loser_fish_id = winner_row["rec_id2"].values[0]
|
||||
|
||||
elif winner == winner_fish2:
|
||||
winner_fish_id = winner_row["rec_id2"].values[0]
|
||||
loser_fish_id = winner_row["rec_id1"].values[0]
|
||||
|
||||
chirp_winner = Behavior.chirps[Behavior.chirps_ids == winner_fish_id]
|
||||
chirp_loser = Behavior.chirps[Behavior.chirps_ids == loser_fish_id]
|
||||
|
||||
return chirp_winner, chirp_loser
|
||||
return None, None
|
||||
|
||||
|
||||
def main(dataroot):
|
||||
foldernames, _ = np.asarray(get_valid_datasets(dataroot))
|
||||
plot_all = True
|
||||
time_before = 90
|
||||
time_after = 90
|
||||
dt = 0.001
|
||||
kernel_width = 2
|
||||
kde_time = np.arange(-time_before, time_after, dt)
|
||||
nbootstraps = 50
|
||||
|
||||
meta_path = ("/").join(foldernames[0].split("/")[:-2]) + "/order_meta.csv"
|
||||
meta = pd.read_csv(meta_path)
|
||||
meta["recording"] = meta["recording"].str[1:-1]
|
||||
|
||||
winner_onsets = []
|
||||
winner_offsets = []
|
||||
winner_physicals = []
|
||||
|
||||
loser_onsets = []
|
||||
loser_offsets = []
|
||||
loser_physicals = []
|
||||
|
||||
winner_onsets_boot = []
|
||||
winner_offsets_boot = []
|
||||
winner_physicals_boot = []
|
||||
|
||||
loser_onsets_boot = []
|
||||
loser_offsets_boot = []
|
||||
loser_physicals_boot = []
|
||||
|
||||
onset_count = 0
|
||||
offset_count = 0
|
||||
physical_count = 0
|
||||
# winner_count = 0
|
||||
# loser_count = 0
|
||||
# winner_onset_chirpcount = 0
|
||||
# winner_offset_chirpcount = 0
|
||||
# winner_physical_chirpcount = 0
|
||||
# loser_onset_chirpcount = 0
|
||||
# loser_offset_chirpcount = 0
|
||||
# loser_physical_chirpcount = 0
|
||||
fig, ax = plt.subplots(
|
||||
1, 2, figsize=(14 * ps.cm, 7 * ps.cm), sharey=True, sharex=True
|
||||
)
|
||||
# Iterate over all recordings and save chirp- and event-timestamps
|
||||
good_recs = np.asarray([0, 15])
|
||||
for i, folder in tqdm(enumerate(foldernames[good_recs])):
|
||||
foldername = folder.split("/")[-2]
|
||||
# logger.info('Loading data from folder: {}'.format(foldername))
|
||||
|
||||
broken_folders = ["../data/mount_data/2020-05-12-10_00/"]
|
||||
if folder in broken_folders:
|
||||
continue
|
||||
|
||||
bh = Behavior(folder)
|
||||
category, timestamps = correct_chasing_events(bh.behavior, bh.start_s)
|
||||
|
||||
category = category[timestamps < 3 * 60 * 60] # only night time
|
||||
timestamps = timestamps[timestamps < 3 * 60 * 60] # only night time
|
||||
|
||||
winner, loser = get_chirp_winner_loser(folder, bh, meta)
|
||||
if winner is None:
|
||||
continue
|
||||
# winner_count += len(winner)
|
||||
# loser_count += len(loser)
|
||||
|
||||
onsets = timestamps[category == 0]
|
||||
offsets = timestamps[category == 1]
|
||||
physicals = timestamps[category == 2]
|
||||
|
||||
onset_count += len(onsets)
|
||||
offset_count += len(offsets)
|
||||
physical_count += len(physicals)
|
||||
|
||||
winner_onsets.append(
|
||||
center_chirps(winner, onsets, time_before, time_after)
|
||||
)
|
||||
winner_offsets.append(
|
||||
center_chirps(winner, offsets, time_before, time_after)
|
||||
)
|
||||
winner_physicals.append(
|
||||
center_chirps(winner, physicals, time_before, time_after)
|
||||
)
|
||||
|
||||
loser_onsets.append(
|
||||
center_chirps(loser, onsets, time_before, time_after)
|
||||
)
|
||||
loser_offsets.append(
|
||||
center_chirps(loser, offsets, time_before, time_after)
|
||||
)
|
||||
loser_physicals.append(
|
||||
center_chirps(loser, physicals, time_before, time_after)
|
||||
)
|
||||
|
||||
# winner_onset_chirpcount += len(winner_onsets[-1])
|
||||
# winner_offset_chirpcount += len(winner_offsets[-1])
|
||||
# winner_physical_chirpcount += len(winner_physicals[-1])
|
||||
# loser_onset_chirpcount += len(loser_onsets[-1])
|
||||
# loser_offset_chirpcount += len(loser_offsets[-1])
|
||||
# loser_physical_chirpcount += len(loser_physicals[-1])
|
||||
# bootstrap
|
||||
# chirps = [winner, winner, winner, loser, loser, loser]
|
||||
|
||||
# winner_onsets_boot.append(bootstrap(
|
||||
# winner,
|
||||
# nresamples=nbootstraps,
|
||||
# kde_time=kde_time,
|
||||
# kernel_width=kernel_width,
|
||||
# event_times=onsets,
|
||||
# time_before=time_before,
|
||||
# time_after=time_after))
|
||||
# winner_offsets_boot.append(bootstrap(
|
||||
# winner,
|
||||
# nresamples=nbootstraps,
|
||||
# kde_time=kde_time,
|
||||
# kernel_width=kernel_width,
|
||||
# event_times=offsets,
|
||||
# time_before=time_before,
|
||||
# time_after=time_after))
|
||||
# winner_physicals_boot.append(bootstrap(
|
||||
# winner,
|
||||
# nresamples=nbootstraps,
|
||||
# kde_time=kde_time,
|
||||
# kernel_width=kernel_width,
|
||||
# event_times=physicals,
|
||||
# time_before=time_before,
|
||||
# time_after=time_after))
|
||||
|
||||
# loser_onsets_boot.append(bootstrap(
|
||||
# loser,
|
||||
# nresamples=nbootstraps,
|
||||
# kde_time=kde_time,
|
||||
# kernel_width=kernel_width,
|
||||
# event_times=onsets,
|
||||
# time_before=time_before,
|
||||
# time_after=time_after))
|
||||
loser_offsets_boot.append(
|
||||
bootstrap(
|
||||
loser,
|
||||
nresamples=nbootstraps,
|
||||
kde_time=kde_time,
|
||||
kernel_width=kernel_width,
|
||||
event_times=offsets,
|
||||
time_before=time_before,
|
||||
time_after=time_after,
|
||||
)
|
||||
)
|
||||
# loser_physicals_boot.append(bootstrap(
|
||||
# loser,
|
||||
# nresamples=nbootstraps,
|
||||
# kde_time=kde_time,
|
||||
# kernel_width=kernel_width,
|
||||
# event_times=physicals,
|
||||
# time_before=time_before,
|
||||
# time_after=time_after))
|
||||
|
||||
# loser_offsets_jackknife = jackknife(
|
||||
# loser,
|
||||
# nresamples=nbootstraps,
|
||||
# subsetsize=0.9,
|
||||
# kde_time=kde_time,
|
||||
# kernel_width=kernel_width,
|
||||
# event_times=offsets,
|
||||
# time_before=time_before,
|
||||
# time_after=time_after)
|
||||
|
||||
if plot_all:
|
||||
# winner_onsets_conv = acausal_kde1d(
|
||||
# winner_onsets[-1], kde_time, kernel_width)
|
||||
# winner_offsets_conv = acausal_kde1d(
|
||||
# winner_offsets[-1], kde_time, kernel_width)
|
||||
# winner_physicals_conv = acausal_kde1d(
|
||||
# winner_physicals[-1], kde_time, kernel_width)
|
||||
|
||||
# loser_onsets_conv = acausal_kde1d(
|
||||
# loser_onsets[-1], kde_time, kernel_width)
|
||||
loser_offsets_conv = acausal_kde1d(
|
||||
loser_offsets[-1], kde_time, kernel_width
|
||||
)
|
||||
# loser_physicals_conv = acausal_kde1d(
|
||||
# loser_physicals[-1], kde_time, kernel_width)
|
||||
|
||||
ax[i].plot(
|
||||
kde_time,
|
||||
loser_offsets_conv / len(offsets),
|
||||
lw=2,
|
||||
zorder=100,
|
||||
c=ps.gblue1,
|
||||
)
|
||||
|
||||
ax[i].fill_between(
|
||||
kde_time,
|
||||
np.percentile(loser_offsets_boot[-1], 1, axis=0),
|
||||
np.percentile(loser_offsets_boot[-1], 99, axis=0),
|
||||
color="gray",
|
||||
alpha=0.8,
|
||||
)
|
||||
|
||||
ax[i].plot(
|
||||
kde_time,
|
||||
np.median(loser_offsets_boot[-1], axis=0),
|
||||
color=ps.black,
|
||||
linewidth=2,
|
||||
)
|
||||
|
||||
ax[i].axvline(0, color=ps.gray, linestyle="--")
|
||||
|
||||
# ax[i].fill_between(
|
||||
# kde_time,
|
||||
# np.percentile(loser_offsets_jackknife, 5, axis=0),
|
||||
# np.percentile(loser_offsets_jackknife, 95, axis=0),
|
||||
# color=ps.blue,
|
||||
# alpha=0.5)
|
||||
# ax[i].plot(kde_time, np.median(loser_offsets_jackknife, axis=0),
|
||||
# color=ps.white, linewidth=2)
|
||||
|
||||
ax[i].set_xlim(-60, 60)
|
||||
fig.supylabel("Chirp rate (a.u.)", fontsize=14)
|
||||
fig.supxlabel("Time (s)", fontsize=14)
|
||||
|
||||
# fig, ax = plt.subplots(2, 3, figsize=(
|
||||
# 21*ps.cm, 10*ps.cm), sharey=True, sharex=True)
|
||||
# ax[0, 0].set_title(
|
||||
# f"{foldername}, onsets {len(onsets)}, offsets {len(offsets)}, physicals {len(physicals)},winner {len(winner)}, looser {len(loser)} , onsets")
|
||||
# ax[0, 0].plot(kde_time, winner_onsets_conv/len(onsets))
|
||||
# ax[0, 1].plot(kde_time, winner_offsets_conv /
|
||||
# len(offsets))
|
||||
# ax[0, 2].plot(kde_time, winner_physicals_conv /
|
||||
# len(physicals))
|
||||
# ax[1, 0].plot(kde_time, loser_onsets_conv/len(onsets))
|
||||
# ax[1, 1].plot(kde_time, loser_offsets_conv/len(offsets))
|
||||
# ax[1, 2].plot(kde_time, loser_physicals_conv /
|
||||
# len(physicals))
|
||||
|
||||
# # plot bootstrap lines
|
||||
# for kde in winner_onsets_boot[-1]:
|
||||
# ax[0, 0].plot(kde_time, kde,
|
||||
# color='gray')
|
||||
# for kde in winner_offsets_boot[-1]:
|
||||
# ax[0, 1].plot(kde_time, kde,
|
||||
# color='gray')
|
||||
# for kde in winner_physicals_boot[-1]:
|
||||
# ax[0, 2].plot(kde_time, kde,
|
||||
# color='gray')
|
||||
# for kde in loser_onsets_boot[-1]:
|
||||
# ax[1, 0].plot(kde_time, kde,
|
||||
# color='gray')
|
||||
# for kde in loser_offsets_boot[-1]:
|
||||
# ax[1, 1].plot(kde_time, kde,
|
||||
# color='gray')
|
||||
# for kde in loser_physicals_boot[-1]:
|
||||
# ax[1, 2].plot(kde_time, kde,
|
||||
# color='gray')
|
||||
|
||||
# plot bootstrap percentiles
|
||||
# ax[0, 0].fill_between(
|
||||
# kde_time,
|
||||
# np.percentile(winner_onsets_boot[-1], 5, axis=0),
|
||||
# np.percentile(winner_onsets_boot[-1], 95, axis=0),
|
||||
# color='gray',
|
||||
# alpha=0.5)
|
||||
# ax[0, 1].fill_between(
|
||||
# kde_time,
|
||||
# np.percentile(winner_offsets_boot[-1], 5, axis=0),
|
||||
# np.percentile(
|
||||
# winner_offsets_boot[-1], 95, axis=0),
|
||||
# color='gray',
|
||||
# alpha=0.5)
|
||||
# ax[0, 2].fill_between(
|
||||
# kde_time,
|
||||
# np.percentile(
|
||||
# winner_physicals_boot[-1], 5, axis=0),
|
||||
# np.percentile(
|
||||
# winner_physicals_boot[-1], 95, axis=0),
|
||||
# color='gray',
|
||||
# alpha=0.5)
|
||||
# ax[1, 0].fill_between(
|
||||
# kde_time,
|
||||
# np.percentile(loser_onsets_boot[-1], 5, axis=0),
|
||||
# np.percentile(loser_onsets_boot[-1], 95, axis=0),
|
||||
# color='gray',
|
||||
# alpha=0.5)
|
||||
# ax[1, 1].fill_between(
|
||||
# kde_time,
|
||||
# np.percentile(loser_offsets_boot[-1], 5, axis=0),
|
||||
# np.percentile(loser_offsets_boot[-1], 95, axis=0),
|
||||
# color='gray',
|
||||
# alpha=0.5)
|
||||
# ax[1, 2].fill_between(
|
||||
# kde_time,
|
||||
# np.percentile(
|
||||
# loser_physicals_boot[-1], 5, axis=0),
|
||||
# np.percentile(
|
||||
# loser_physicals_boot[-1], 95, axis=0),
|
||||
# color='gray',
|
||||
# alpha=0.5)
|
||||
|
||||
# ax[0, 0].plot(kde_time, np.median(winner_onsets_boot[-1], axis=0),
|
||||
# color='black', linewidth=2)
|
||||
# ax[0, 1].plot(kde_time, np.median(winner_offsets_boot[-1], axis=0),
|
||||
# color='black', linewidth=2)
|
||||
# ax[0, 2].plot(kde_time, np.median(winner_physicals_boot[-1], axis=0),
|
||||
# color='black', linewidth=2)
|
||||
# ax[1, 0].plot(kde_time, np.median(loser_onsets_boot[-1], axis=0),
|
||||
# color='black', linewidth=2)
|
||||
# ax[1, 1].plot(kde_time, np.median(loser_offsets_boot[-1], axis=0),
|
||||
# color='black', linewidth=2)
|
||||
# ax[1, 2].plot(kde_time, np.median(loser_physicals_boot[-1], axis=0),
|
||||
# color='black', linewidth=2)
|
||||
|
||||
# ax[0, 0].set_xlim(-30, 30)
|
||||
|
||||
# winner_onsets = np.sort(flatten(winner_onsets))
|
||||
# winner_offsets = np.sort(flatten(winner_offsets))
|
||||
# winner_physicals = np.sort(flatten(winner_physicals))
|
||||
# loser_onsets = np.sort(flatten(loser_onsets))
|
||||
# loser_offsets = np.sort(flatten(loser_offsets))
|
||||
# loser_physicals = np.sort(flatten(loser_physicals))
|
||||
|
||||
# winner_onsets_conv = acausal_kde1d(
|
||||
# winner_onsets, kde_time, kernel_width)
|
||||
# winner_offsets_conv = acausal_kde1d(
|
||||
# winner_offsets, kde_time, kernel_width)
|
||||
# winner_physicals_conv = acausal_kde1d(
|
||||
# winner_physicals, kde_time, kernel_width)
|
||||
# loser_onsets_conv = acausal_kde1d(
|
||||
# loser_onsets, kde_time, kernel_width)
|
||||
# loser_offsets_conv = acausal_kde1d(
|
||||
# loser_offsets, kde_time, kernel_width)
|
||||
# loser_physicals_conv = acausal_kde1d(
|
||||
# loser_physicals, kde_time, kernel_width)
|
||||
|
||||
# winner_onsets_conv = winner_onsets_conv / onset_count
|
||||
# winner_offsets_conv = winner_offsets_conv / offset_count
|
||||
# winner_physicals_conv = winner_physicals_conv / physical_count
|
||||
# loser_onsets_conv = loser_onsets_conv / onset_count
|
||||
# loser_offsets_conv = loser_offsets_conv / offset_count
|
||||
# loser_physicals_conv = loser_physicals_conv / physical_count
|
||||
|
||||
# winner_onsets_boot = np.concatenate(
|
||||
# winner_onsets_boot)
|
||||
# winner_offsets_boot = np.concatenate(
|
||||
# winner_offsets_boot)
|
||||
# winner_physicals_boot = np.concatenate(
|
||||
# winner_physicals_boot)
|
||||
# loser_onsets_boot = np.concatenate(
|
||||
# loser_onsets_boot)
|
||||
# loser_offsets_boot = np.concatenate(
|
||||
# loser_offsets_boot)
|
||||
# loser_physicals_boot = np.concatenate(
|
||||
# loser_physicals_boot)
|
||||
|
||||
# percs = [5, 50, 95]
|
||||
# winner_onsets_boot_quarts = np.percentile(
|
||||
# winner_onsets_boot, percs, axis=0)
|
||||
# winner_offsets_boot_quarts = np.percentile(
|
||||
# winner_offsets_boot, percs, axis=0)
|
||||
# winner_physicals_boot_quarts = np.percentile(
|
||||
# winner_physicals_boot, percs, axis=0)
|
||||
# loser_onsets_boot_quarts = np.percentile(
|
||||
# loser_onsets_boot, percs, axis=0)
|
||||
# loser_offsets_boot_quarts = np.percentile(
|
||||
# loser_offsets_boot, percs, axis=0)
|
||||
# loser_physicals_boot_quarts = np.percentile(
|
||||
# loser_physicals_boot, percs, axis=0)
|
||||
|
||||
# fig, ax = plt.subplots(2, 3, figsize=(
|
||||
# 21*ps.cm, 10*ps.cm), sharey=True, sharex=True)
|
||||
|
||||
# ax[0, 0].plot(kde_time, winner_onsets_conv)
|
||||
# ax[0, 1].plot(kde_time, winner_offsets_conv)
|
||||
# ax[0, 2].plot(kde_time, winner_physicals_conv)
|
||||
# ax[1, 0].plot(kde_time, loser_onsets_conv)
|
||||
# ax[1, 1].plot(kde_time, loser_offsets_conv)
|
||||
# ax[1, 2].plot(kde_time, loser_physicals_conv)
|
||||
|
||||
# ax[0, 0].plot(kde_time, winner_onsets_boot_quarts[1], c=ps.black)
|
||||
# ax[0, 1].plot(kde_time, winner_offsets_boot_quarts[1], c=ps.black)
|
||||
# ax[0, 2].plot(kde_time, winner_physicals_boot_quarts[1], c=ps.black)
|
||||
# ax[1, 0].plot(kde_time, loser_onsets_boot_quarts[1], c=ps.black)
|
||||
# ax[1, 1].plot(kde_time, loser_offsets_boot_quarts[1], c=ps.black)
|
||||
# ax[1, 2].plot(kde_time, loser_physicals_boot_quarts[1], c=ps.black)
|
||||
|
||||
# for kde in winner_onsets_boot:
|
||||
# ax[0, 0].plot(kde_time, kde,
|
||||
# color='gray')
|
||||
# for kde in winner_offsets_boot:
|
||||
# ax[0, 1].plot(kde_time, kde,
|
||||
# color='gray')
|
||||
# for kde in winner_physicals_boot:
|
||||
# ax[0, 2].plot(kde_time, kde,
|
||||
# color='gray')
|
||||
# for kde in loser_onsets_boot:
|
||||
# ax[1, 0].plot(kde_time, kde,
|
||||
# color='gray')
|
||||
# for kde in loser_offsets_boot:
|
||||
# ax[1, 1].plot(kde_time, kde,
|
||||
# color='gray')
|
||||
# for kde in loser_physicals_boot:
|
||||
# ax[1, 2].plot(kde_time, kde,
|
||||
# color='gray')
|
||||
|
||||
# ax[0, 0].fill_between(kde_time,
|
||||
# winner_onsets_boot_quarts[0],
|
||||
# winner_onsets_boot_quarts[2],
|
||||
# color=ps.gray,
|
||||
# alpha=0.5)
|
||||
|
||||
# ax[0, 1].fill_between(kde_time,
|
||||
# winner_offsets_boot_quarts[0],
|
||||
# winner_offsets_boot_quarts[2],
|
||||
# color=ps.gray,
|
||||
# alpha=0.5)
|
||||
|
||||
# ax[0, 2].fill_between(kde_time,
|
||||
# loser_physicals_boot_quarts[0],
|
||||
# loser_physicals_boot_quarts[2],
|
||||
# color=ps.gray,
|
||||
# alpha=0.5)
|
||||
|
||||
# ax[1, 0].fill_between(kde_time,
|
||||
# loser_onsets_boot_quarts[0],
|
||||
# loser_onsets_boot_quarts[2],
|
||||
# color=ps.gray,
|
||||
# alpha=0.5)
|
||||
|
||||
# ax[1, 1].fill_between(kde_time,
|
||||
# loser_offsets_boot_quarts[0],
|
||||
# loser_offsets_boot_quarts[2],
|
||||
# color=ps.gray,
|
||||
# alpha=0.5)
|
||||
|
||||
# ax[1, 2].fill_between(kde_time,
|
||||
# loser_physicals_boot_quarts[0],
|
||||
# loser_physicals_boot_quarts[2],
|
||||
# color=ps.gray,
|
||||
# alpha=0.5)
|
||||
plt.subplots_adjust(bottom=0.21, top=0.93)
|
||||
plt.savefig("../poster/figs/kde.pdf")
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main("../data/mount_data/")
|
@ -1,14 +0,0 @@
|
||||
audioio==0.9.5
|
||||
cmocean==2.0
|
||||
cycler==0.11.0
|
||||
ipython==8.10.0
|
||||
matplotlib==3.7.0
|
||||
numpy==1.23.5
|
||||
pandas==1.5.3
|
||||
paramiko==2.11.1
|
||||
PyYAML==6.0
|
||||
scikit_learn==1.2.1
|
||||
scipy==1.10.1
|
||||
scp==0.14.5
|
||||
thunderfish==1.9.9
|
||||
tqdm==4.64.1
|
@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
color='\033[1;91m'
|
||||
nocolor='\033[0m'
|
||||
message='Running scripts in directory: '
|
||||
|
||||
for py_file in $(ls plot_*); do
|
||||
echo -e $message$color$py_file$nocolor
|
||||
python3 $py_file
|
||||
done
|
@ -1,277 +0,0 @@
|
||||
import numpy as np
|
||||
from extract_chirps import get_valid_datasets
|
||||
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from thunderfish.powerspectrum import decibel
|
||||
|
||||
from IPython import embed
|
||||
from pandas import read_csv
|
||||
from modules.logger import makeLogger
|
||||
from modules.plotstyle import PlotStyle
|
||||
from modules.behaviour_handling import Behavior, correct_chasing_events
|
||||
|
||||
ps = PlotStyle()
|
||||
|
||||
logger = makeLogger(__name__)
|
||||
|
||||
|
||||
def get_chirp_winner_loser(folder_name, Behavior, order_meta_df):
|
||||
|
||||
foldername = folder_name.split('/')[-2]
|
||||
winner_row = order_meta_df[order_meta_df['recording'] == foldername]
|
||||
winner = winner_row['winner'].values[0].astype(int)
|
||||
winner_fish1 = winner_row['fish1'].values[0].astype(int)
|
||||
winner_fish2 = winner_row['fish2'].values[0].astype(int)
|
||||
|
||||
if winner > 0:
|
||||
if winner == winner_fish1:
|
||||
winner_fish_id = winner_row['rec_id1'].values[0]
|
||||
loser_fish_id = winner_row['rec_id2'].values[0]
|
||||
|
||||
elif winner == winner_fish2:
|
||||
winner_fish_id = winner_row['rec_id2'].values[0]
|
||||
loser_fish_id = winner_row['rec_id1'].values[0]
|
||||
|
||||
chirp_winner = len(
|
||||
Behavior.chirps[Behavior.chirps_ids == winner_fish_id])
|
||||
chirp_loser = len(
|
||||
Behavior.chirps[Behavior.chirps_ids == loser_fish_id])
|
||||
|
||||
return chirp_winner, chirp_loser
|
||||
else:
|
||||
return np.nan, np.nan
|
||||
|
||||
|
||||
def get_chirp_size(folder_name, Behavior, order_meta_df, id_meta_df):
|
||||
|
||||
foldername = folder_name.split('/')[-2]
|
||||
folder_row = order_meta_df[order_meta_df['recording'] == foldername]
|
||||
fish1 = folder_row['fish1'].values[0].astype(int)
|
||||
fish2 = folder_row['fish2'].values[0].astype(int)
|
||||
|
||||
groub = folder_row['group'].values[0].astype(int)
|
||||
size_fish1_row = id_meta_df[(id_meta_df['group'] == groub) & (
|
||||
id_meta_df['fish'] == fish1)]
|
||||
size_fish2_row = id_meta_df[(id_meta_df['group'] == groub) & (
|
||||
id_meta_df['fish'] == fish2)]
|
||||
|
||||
size_winners = [size_fish1_row[col].values[0]
|
||||
for col in ['l1', 'l2', 'l3']]
|
||||
mean_size_winner = np.nanmean(size_winners)
|
||||
|
||||
size_losers = [size_fish2_row[col].values[0] for col in ['l1', 'l2', 'l3']]
|
||||
mean_size_loser = np.nanmean(size_losers)
|
||||
|
||||
if mean_size_winner > mean_size_loser:
|
||||
size_diff = mean_size_winner - mean_size_loser
|
||||
winner_fish_id = folder_row['rec_id1'].values[0]
|
||||
loser_fish_id = folder_row['rec_id2'].values[0]
|
||||
|
||||
elif mean_size_winner < mean_size_loser:
|
||||
size_diff = mean_size_loser - mean_size_winner
|
||||
winner_fish_id = folder_row['rec_id2'].values[0]
|
||||
loser_fish_id = folder_row['rec_id1'].values[0]
|
||||
|
||||
else:
|
||||
size_diff = np.nan
|
||||
winner_fish_id = np.nan
|
||||
loser_fish_id = np.nan
|
||||
|
||||
chirp_diff = len(Behavior.chirps[Behavior.chirps_ids == winner_fish_id]) - len(
|
||||
Behavior.chirps[Behavior.chirps_ids == loser_fish_id])
|
||||
|
||||
return size_diff, chirp_diff
|
||||
|
||||
|
||||
def get_chirp_freq(folder_name, Behavior, order_meta_df):
|
||||
|
||||
foldername = folder_name.split('/')[-2]
|
||||
folder_row = order_meta_df[order_meta_df['recording'] == foldername]
|
||||
fish1 = folder_row['rec_id1'].values[0].astype(int)
|
||||
fish2 = folder_row['rec_id2'].values[0].astype(int)
|
||||
chirp_freq_fish1 = np.nanmedian(
|
||||
Behavior.freq[Behavior.ident == fish1])
|
||||
chirp_freq_fish2 = np.nanmedian(
|
||||
Behavior.freq[Behavior.ident == fish2])
|
||||
|
||||
if chirp_freq_fish1 > chirp_freq_fish2:
|
||||
freq_diff = chirp_freq_fish1 - chirp_freq_fish2
|
||||
winner_fish_id = folder_row['rec_id1'].values[0]
|
||||
loser_fish_id = folder_row['rec_id2'].values[0]
|
||||
|
||||
elif chirp_freq_fish1 < chirp_freq_fish2:
|
||||
freq_diff = chirp_freq_fish2 - chirp_freq_fish1
|
||||
winner_fish_id = folder_row['rec_id2'].values[0]
|
||||
loser_fish_id = folder_row['rec_id1'].values[0]
|
||||
|
||||
chirp_diff = len(Behavior.chirps[Behavior.chirps_ids == winner_fish_id]) - len(
|
||||
Behavior.chirps[Behavior.chirps_ids == loser_fish_id])
|
||||
|
||||
return freq_diff, chirp_diff
|
||||
|
||||
|
||||
def main(datapath: str):
|
||||
|
||||
foldernames = [
|
||||
datapath + x + '/' for x in os.listdir(datapath) if os.path.isdir(datapath+x)]
|
||||
|
||||
foldernames, _ = get_valid_datasets(datapath)
|
||||
path_order_meta = (
|
||||
'/').join(foldernames[0].split('/')[:-2]) + '/order_meta.csv'
|
||||
order_meta_df = read_csv(path_order_meta)
|
||||
order_meta_df['recording'] = order_meta_df['recording'].str[1:-1]
|
||||
path_id_meta = (
|
||||
'/').join(foldernames[0].split('/')[:-2]) + '/id_meta.csv'
|
||||
id_meta_df = read_csv(path_id_meta)
|
||||
|
||||
chirps_winner = []
|
||||
size_diffs = []
|
||||
size_chirps_diffs = []
|
||||
chirps_loser = []
|
||||
freq_diffs = []
|
||||
freq_chirps_diffs = []
|
||||
|
||||
for foldername in foldernames:
|
||||
# behabvior is pandas dataframe with all the data
|
||||
if foldername == '../data/mount_data/2020-05-12-10_00/':
|
||||
continue
|
||||
bh = Behavior(foldername)
|
||||
# chirps are not sorted in time (presumably due to prior groupings)
|
||||
# get and sort chirps and corresponding fish_ids of the chirps
|
||||
category = bh.behavior
|
||||
timestamps = bh.start_s
|
||||
# Correct for doubles in chasing on- and offsets to get the right on-/offset pairs
|
||||
# Get rid of tracking faults (two onsets or two offsets after another)
|
||||
category, timestamps = correct_chasing_events(category, timestamps)
|
||||
|
||||
# winner_chirp, loser_chirp = get_chirp_winner_loser(
|
||||
# foldername, bh, order_meta_df)
|
||||
# chirps_winner.append(winner_chirp)
|
||||
# chirps_loser.append(loser_chirp)
|
||||
# size_diff, chirp_diff = get_chirp_size(
|
||||
# foldername, bh, order_meta_df, id_meta_df)
|
||||
# size_diffs.append(size_diff)
|
||||
# size_chirps_diffs.append(chirp_diff)
|
||||
|
||||
# freq_diff, freq_chirps_diff = get_chirp_freq(
|
||||
# foldername, bh, order_meta_df)
|
||||
# freq_diffs.append(freq_diff)
|
||||
# freq_chirps_diffs.append(freq_chirps_diff)
|
||||
|
||||
folder_name = foldername.split('/')[-2]
|
||||
winner_row = order_meta_df[order_meta_df['recording'] == folder_name]
|
||||
winner = winner_row['winner'].values[0].astype(int)
|
||||
winner_fish1 = winner_row['fish1'].values[0].astype(int)
|
||||
winner_fish2 = winner_row['fish2'].values[0].astype(int)
|
||||
|
||||
groub = winner_row['group'].values[0].astype(int)
|
||||
size_rows = id_meta_df[id_meta_df['group'] == groub]
|
||||
|
||||
if winner == winner_fish1:
|
||||
winner_fish_id = winner_row['rec_id1'].values[0]
|
||||
loser_fish_id = winner_row['rec_id2'].values[0]
|
||||
|
||||
size_winners = []
|
||||
for l in ['l1', 'l2', 'l3']:
|
||||
size_winner = size_rows[size_rows['fish']
|
||||
== winner_fish1][l].values[0]
|
||||
size_winners.append(size_winner)
|
||||
mean_size_winner = np.nanmean(size_winners)
|
||||
|
||||
size_losers = []
|
||||
for l in ['l1', 'l2', 'l3']:
|
||||
size_loser = size_rows[size_rows['fish']
|
||||
== winner_fish2][l].values[0]
|
||||
size_losers.append(size_loser)
|
||||
mean_size_loser = np.nanmean(size_losers)
|
||||
|
||||
size_diffs.append(mean_size_winner - mean_size_loser)
|
||||
|
||||
elif winner == winner_fish2:
|
||||
winner_fish_id = winner_row['rec_id2'].values[0]
|
||||
loser_fish_id = winner_row['rec_id1'].values[0]
|
||||
|
||||
size_winners = []
|
||||
for l in ['l1', 'l2', 'l3']:
|
||||
size_winner = size_rows[size_rows['fish']
|
||||
== winner_fish2][l].values[0]
|
||||
size_winners.append(size_winner)
|
||||
mean_size_winner = np.nanmean(size_winners)
|
||||
|
||||
size_losers = []
|
||||
for l in ['l1', 'l2', 'l3']:
|
||||
size_loser = size_rows[size_rows['fish']
|
||||
== winner_fish1][l].values[0]
|
||||
size_losers.append(size_loser)
|
||||
mean_size_loser = np.nanmean(size_losers)
|
||||
|
||||
size_diffs.append(mean_size_winner - mean_size_loser)
|
||||
else:
|
||||
continue
|
||||
|
||||
print(foldername)
|
||||
all_fish_ids = np.unique(bh.chirps_ids)
|
||||
chirp_winner = len(bh.chirps[bh.chirps_ids == winner_fish_id])
|
||||
chirp_loser = len(bh.chirps[bh.chirps_ids == loser_fish_id])
|
||||
|
||||
freq_winner = np.nanmedian(bh.freq[bh.ident == winner_fish_id])
|
||||
freq_loser = np.nanmedian(bh.freq[bh.ident == loser_fish_id])
|
||||
|
||||
chirps_winner.append(chirp_winner)
|
||||
chirps_loser.append(chirp_loser)
|
||||
|
||||
size_chirps_diffs.append(chirp_winner - chirp_loser)
|
||||
freq_diffs.append(freq_winner - freq_loser)
|
||||
|
||||
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(
|
||||
22*ps.cm, 12*ps.cm), width_ratios=[1.5, 1, 1])
|
||||
plt.subplots_adjust(left=0.098, right=0.945, top=0.94, wspace=0.343)
|
||||
scatterwinner = 1.15
|
||||
scatterloser = 1.85
|
||||
chirps_winner = np.asarray(chirps_winner)[~np.isnan(chirps_winner)]
|
||||
chirps_loser = np.asarray(chirps_loser)[~np.isnan(chirps_loser)]
|
||||
|
||||
bplot1 = ax1.boxplot(chirps_winner, positions=[
|
||||
1], showfliers=False, patch_artist=True)
|
||||
bplot2 = ax1.boxplot(chirps_loser, positions=[
|
||||
2], showfliers=False, patch_artist=True)
|
||||
ax1.scatter(np.ones(len(chirps_winner)) *
|
||||
scatterwinner, chirps_winner, color='r')
|
||||
ax1.scatter(np.ones(len(chirps_loser)) *
|
||||
scatterloser, chirps_loser, color='r')
|
||||
ax1.set_xticklabels(['winner', 'loser'])
|
||||
ax1.text(0.1, 0.9, f'n = {len(chirps_winner)}',
|
||||
transform=ax1.transAxes, color=ps.white)
|
||||
|
||||
for w, l in zip(chirps_winner, chirps_loser):
|
||||
ax1.plot([scatterwinner, scatterloser], [w, l],
|
||||
color='r', alpha=0.5, linewidth=0.5)
|
||||
ax1.set_ylabel('Chirps [n]', color=ps.white)
|
||||
|
||||
colors1 = ps.red
|
||||
ps.set_boxplot_color(bplot1, colors1)
|
||||
colors1 = ps.orange
|
||||
ps.set_boxplot_color(bplot2, colors1)
|
||||
ax2.scatter(size_diffs, size_chirps_diffs, color='r')
|
||||
ax2.set_xlabel('Size difference [mm]')
|
||||
ax2.set_ylabel('Chirps [n]')
|
||||
|
||||
ax3.scatter(freq_diffs, size_chirps_diffs, color='r')
|
||||
# ax3.scatter(freq_diffs, freq_chirps_diffs, color='r')
|
||||
ax3.set_xlabel('Frequency difference [Hz]')
|
||||
ax3.set_yticklabels([])
|
||||
ax3.set
|
||||
|
||||
plt.savefig('../poster/figs/chirps_winner_loser.pdf')
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Path to the data
|
||||
datapath = '../data/mount_data/'
|
||||
|
||||
main(datapath)
|
Before Width: | Height: | Size: 393 KiB |
BIN
poster/figs/Untitled.png
Normal file
After Width: | Height: | Size: 116 KiB |
BIN
poster/figs/logo.png
Normal file
After Width: | Height: | Size: 40 KiB |
1184
poster/figs/logo.svg
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
poster/figs/placeholder1.png
Normal file
After Width: | Height: | Size: 157 KiB |
16784
poster/figs/timeline.svg
Before Width: | Height: | Size: 462 KiB |
BIN
poster/main.pdf
Normal file
119
poster/main.tex
Normal file
@ -0,0 +1,119 @@
|
||||
\documentclass[25pt, a0paper, landscape, margin=0mm, innermargin=20mm,
|
||||
blockverticalspace=2mm, colspace=20mm, subcolspace=0mm]{tikzposter} %Default values for poster format options.
|
||||
|
||||
\input{packages}
|
||||
\input{style}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\renewcommand{\baselinestretch}{1}
|
||||
\title{\parbox{1900pt}{Pushing the limits of time-frequency uncertainty in the
|
||||
detection of transient communication signals in weakly electric fish}}
|
||||
\author{Sina Prause, Alexander Wendt, Patrick Weygoldt}
|
||||
\institute{Supervised by Till Raab \& Jan Benda, Neurothology Group,
|
||||
University of Tübingen}
|
||||
\usetitlestyle[]{sampletitle}
|
||||
\maketitle
|
||||
\renewcommand{\baselinestretch}{1.4}
|
||||
|
||||
\begin{columns}
|
||||
\column{0.5}
|
||||
\myblock[TranspBlock]{Introduction}{
|
||||
\begin{minipage}[t]{0.55\linewidth}
|
||||
The time-frequency tradeoff makes reliable signal detecion and simultaneous
|
||||
sender identification of freely interacting individuals impossible.
|
||||
This profoundly limits our current understanding of chirps to experiments
|
||||
with single - or physically separated - individuals.
|
||||
\end{minipage} \hfill
|
||||
\begin{minipage}[t]{0.40\linewidth}
|
||||
\vspace{-1.5cm}
|
||||
\begin{tikzfigure}[]
|
||||
\label{tradeoff}
|
||||
\includegraphics[width=\linewidth]{figs/introplot}
|
||||
\end{tikzfigure}
|
||||
\end{minipage}
|
||||
}
|
||||
|
||||
\myblock[TranspBlock]{A chirp detection algorithm}{
|
||||
\begin{tikzfigure}[]
|
||||
\label{modulations}
|
||||
\includegraphics[width=\linewidth]{figs/algorithm}
|
||||
\end{tikzfigure}
|
||||
}
|
||||
|
||||
\column{0.5}
|
||||
\myblock[TranspBlock]{Chirps and diadic competitions}{
|
||||
\begin{minipage}[t]{0.7\linewidth}
|
||||
\begin{tikzfigure}[]
|
||||
\label{modulations}
|
||||
\includegraphics[width=\linewidth]{figs/placeholder1}
|
||||
\end{tikzfigure}
|
||||
\end{minipage} \hfill
|
||||
\begin{minipage}[t]{0.25\linewidth}
|
||||
\lipsum[3][1-3]
|
||||
\end{minipage}
|
||||
|
||||
\begin{minipage}[t]{0.7\linewidth}
|
||||
\begin{tikzfigure}[]
|
||||
\label{modulations}
|
||||
\includegraphics[width=\linewidth]{figs/placeholder1}
|
||||
\end{tikzfigure}
|
||||
\end{minipage} \hfill
|
||||
\begin{minipage}[t]{0.25\linewidth}
|
||||
\lipsum[3][1-3]
|
||||
\end{minipage}
|
||||
|
||||
\begin{minipage}[t]{0.7\linewidth}
|
||||
\begin{tikzfigure}[]
|
||||
\label{modulations}
|
||||
\includegraphics[width=\linewidth]{figs/placeholder1}
|
||||
\end{tikzfigure}
|
||||
\end{minipage} \hfill
|
||||
\begin{minipage}[t]{0.25\linewidth}
|
||||
\lipsum[3][1-3]
|
||||
\end{minipage}
|
||||
|
||||
|
||||
}
|
||||
|
||||
\myblock[TranspBlock]{Conclusion}{
|
||||
\lipsum[3][1-9]
|
||||
}
|
||||
|
||||
% \column{0.3}
|
||||
% \myblock[TranspBlock]{More Results}{
|
||||
% \begin{tikzfigure}[]
|
||||
% \label{results}
|
||||
% \includegraphics[width=\linewidth]{example-image-a}
|
||||
% \end{tikzfigure}
|
||||
|
||||
% \begin{multicols}{2}
|
||||
% \lipsum[5][1-8]
|
||||
% \end{multicols}
|
||||
% \vspace{-1cm}
|
||||
% }
|
||||
|
||||
% \myblock[TranspBlock]{Conclusion}{
|
||||
% \begin{itemize}
|
||||
% \setlength\itemsep{0.5em}
|
||||
% \item \lipsum[1][1]
|
||||
% \item \lipsum[1][1]
|
||||
% \item \lipsum[1][1]
|
||||
% \end{itemize}
|
||||
% \vspace{0.2cm}
|
||||
% }
|
||||
\end{columns}
|
||||
|
||||
\node[
|
||||
above right,
|
||||
text=white,
|
||||
outer sep=45pt,
|
||||
minimum width=\paperwidth,
|
||||
align=center,
|
||||
draw,
|
||||
fill=boxes,
|
||||
color=boxes,
|
||||
] at (-0.51\paperwidth,-43.5) {
|
||||
\textcolor{text}{\normalsize Contact: \{name\}.\{surname\}@student.uni-tuebingen.de}};
|
||||
|
||||
\end{document}
|
@ -1,10 +1,11 @@
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[scaled]{helvet}
|
||||
\renewcommand\familydefault{\sfdefault}
|
||||
\renewcommand\familydefault{\sfdefault}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage{wrapfig}
|
||||
\usepackage{setspace}
|
||||
\usepackage{multicol}
|
||||
\setlength{\columnsep}{1.5cm}
|
||||
\usepackage{xspace}
|
||||
\usepackage{tikz}
|
||||
\usepackage{tikz}
|
||||
\usepackage{lipsum}
|
@ -16,11 +16,10 @@
|
||||
\colorlet{notefgcolor}{background}
|
||||
\colorlet{notebgcolor}{background}
|
||||
|
||||
|
||||
% Title setup
|
||||
\settitle{
|
||||
% Rearrange the order of the minipages to e.g. center the title between the logos
|
||||
\begin{minipage}[c]{0.8\paperwidth}
|
||||
\begin{minipage}[c]{0.6\paperwidth}
|
||||
% \centering
|
||||
\vspace{2.5cm}\hspace{1.5cm}
|
||||
\color{text}{\Huge{\textbf{\@title}} \par}
|
||||
@ -31,28 +30,26 @@
|
||||
\vspace{2.5cm}
|
||||
\end{minipage}
|
||||
\begin{minipage}[c]{0.2\paperwidth}
|
||||
% \centering
|
||||
\vspace{1cm}\hspace{1cm}
|
||||
\includegraphics[scale=1]{example-image-a}
|
||||
\end{minipage}
|
||||
\begin{minipage}[c]{0.2\paperwidth}
|
||||
% \vspace{1cm}\hspace{1cm}
|
||||
\centering
|
||||
% \vspace{1cm}
|
||||
\hspace{-7cm}
|
||||
\includegraphics[width=0.7\linewidth]{figs/efishlogo.pdf}
|
||||
\includegraphics[scale=1]{example-image-a}
|
||||
\end{minipage}}
|
||||
% \begin{minipage}[c]{0.2\paperwidth}
|
||||
% \vspace{1cm}\hspace{1cm}
|
||||
% \centering
|
||||
% \includegraphics[width=\linewidth]{example-image-a}
|
||||
% \end{minipage}}
|
||||
|
||||
% define title style with background box (currently white)
|
||||
% definie title style with background box
|
||||
\definetitlestyle{sampletitle}{
|
||||
width=841mm,
|
||||
width=1189mm,
|
||||
roundedcorners=0,
|
||||
linewidth=0pt,
|
||||
innersep=15pt,
|
||||
titletotopverticalspace=0mm,
|
||||
titletoblockverticalspace=5pt
|
||||
}{
|
||||
\begin{scope}[line width=\titlelinewidth,
|
||||
rounded corners=\titleroundedcorners]
|
||||
\begin{scope}[line width=\titlelinewidth, rounded corners=\titleroundedcorners]
|
||||
\draw[fill=text, color=boxes]
|
||||
(\titleposleft,\titleposbottom)
|
||||
rectangle
|
@ -1,116 +0,0 @@
|
||||
\documentclass[25pt, a0paper, portrait, margin=0mm, innermargin=20mm,
|
||||
blockverticalspace=2mm, colspace=20mm, subcolspace=0mm]{tikzposter} %Default values for poster format options.
|
||||
|
||||
\input{packages}
|
||||
\input{style}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\renewcommand{\baselinestretch}{1}
|
||||
\title{\parbox{1600pt}{Detecting chirps based on dynamic filtering for the \\ analysis of social interactions in weakly electric fish}}
|
||||
\author{Sina Prause, Alexander Wendt, and Patrick Weygoldt}
|
||||
\institute{Supervised by Till Raab \& Jan Benda, Neuroethology Lab, University of Tuebingen}
|
||||
\usetitlestyle[]{sampletitle}
|
||||
\maketitle
|
||||
\renewcommand{\baselinestretch}{1.4}
|
||||
|
||||
\begin{columns}
|
||||
\column{0.4}
|
||||
\myblock[TranspBlock]{Introduction}{
|
||||
\textbf{Chirps} are one type of communication signals in weakly electric fish. They are characterized by \textbf{short frequency excursions} and are emitted during various social contexts. The time-frequency uncertainty of the Fourier transform makes it nearly impossible to reliably \textbf{detect and assign} chirps in freely interacting fish based on spectral methods. To overcome these limits, we developed a method based on \textbf{dynamic filtering} and subsequent \textbf{feature detection}.
|
||||
\vspace{1cm}
|
||||
\begin{tikzfigure}[]
|
||||
\label{griddrawing}
|
||||
\includegraphics[width=\linewidth]{figs/introplot}
|
||||
\end{tikzfigure}
|
||||
}
|
||||
\myblock[TranspBlock]{Chirp detection algorithm}{
|
||||
\begin{tikzfigure}[]
|
||||
\label{fig:alg1}
|
||||
\includegraphics[width=0.9\linewidth]{figs/algorithm1}
|
||||
\end{tikzfigure}
|
||||
\vspace{1cm}
|
||||
\begin{tikzfigure}[]
|
||||
\label{fig:alg2}
|
||||
\includegraphics[width=1\linewidth]{figs/algorithm}
|
||||
\end{tikzfigure}
|
||||
\vspace{0cm}
|
||||
}
|
||||
|
||||
\column{0.6}
|
||||
\myblock[TranspBlock]{Chirps in dyadic competitions (Data by Till Raab, 2020)}{
|
||||
\vspace{-2.75cm}
|
||||
\begin{tikzfigure}[]
|
||||
\label{fig:example_b}
|
||||
\includegraphics[width=\linewidth]{figs/timeline.pdf}
|
||||
\end{tikzfigure}
|
||||
\noindent
|
||||
\begin{multicols}{2}
|
||||
\begin{itemize}
|
||||
% \setlength\itemsep{0.5em}
|
||||
\item The electric behavior of two fish competing for one shelter were recorded in a light and dark condition.
|
||||
\item Using video recordings, behavior was classified as chasings or physical contacts.
|
||||
\end{itemize}
|
||||
\end{multicols}
|
||||
|
||||
\vspace{-2cm}
|
||||
\noindent
|
||||
\begin{tikzfigure}[]
|
||||
\label{fig:example_b}
|
||||
\includegraphics[width=\linewidth]{figs/chirps_winner_loser.pdf}
|
||||
\end{tikzfigure}
|
||||
\noindent
|
||||
\begin{multicols}{2}
|
||||
\begin{itemize}
|
||||
\item Losers tend to chirp more.
|
||||
\item Larger fish usually win. The smaller the size difference the more chirps are emitted.
|
||||
\columnbreak
|
||||
\item EOD frequency has no effect on the competition outcome and the chirp rate.
|
||||
\end{itemize}
|
||||
\end{multicols}
|
||||
}
|
||||
|
||||
\myblock[TranspBlock]{Chirps emitted by loser fish might stop chasing events}{
|
||||
\vspace{-1.2cm}
|
||||
\begin{minipage}{0.6666\linewidth}
|
||||
\begin{tikzfigure}[]
|
||||
\includegraphics[width=1.1\linewidth]{figs/kde.pdf}
|
||||
\end{tikzfigure}
|
||||
\end{minipage}
|
||||
\begin{minipage}{0.3333\linewidth}
|
||||
\begin{tikzfigure}[]
|
||||
\includegraphics[width=1\linewidth]{figs/chirps_in_chasing.pdf}
|
||||
\end{tikzfigure}
|
||||
\end{minipage}
|
||||
\noindent
|
||||
\begin{multicols}{2}
|
||||
\begin{itemize}
|
||||
\item In most cases there is no correlation between chirping and chasing- or physical contact events.
|
||||
\item The chirp rate during chasings only increases for some dyads.
|
||||
\end{itemize}
|
||||
\end{multicols}
|
||||
}
|
||||
|
||||
\myblock[GrayBlock]{Conclusion}{
|
||||
\begin{itemize}
|
||||
\setlength\itemsep{0.5em}
|
||||
\item First tests indicate that our algorithm is able to detect chirps in recordings of multiple fish.
|
||||
\item In some cases the chirp rate drastically increases before chasing stops.
|
||||
\item Behavioral analysis needs to consider more variables, such as sex, size, and interindividual differences in chirping behavior.
|
||||
\end{itemize}
|
||||
\vspace{0.2cm}
|
||||
}
|
||||
\end{columns}
|
||||
|
||||
\node [above right,
|
||||
text=white,
|
||||
outer sep=45pt,
|
||||
minimum width=\paperwidth,
|
||||
align=center,
|
||||
draw,
|
||||
fill=boxes,
|
||||
color=boxes] at (-43.6,-61) {
|
||||
\textcolor{white}{
|
||||
\normalsize Contact: \{name\}.\{surname\}@student.uni-tuebingen.de}};
|
||||
|
||||
\end{document}
|
@ -1,32 +0,0 @@
|
||||
|
||||
We detected 25766 chirps in this dataset which included 27 valid trials of recordings that lasted 6 hours each. The number of detected chirps is close to the currently largest reported dataset by \textcite{obotiWhyBrownGhost2022} which contains multiple different experiments. This was achieved by extending the chirp detection algorithm by \textcite{henningerTrackingActivityPatterns2020}. We added a dynamic search frequency and combined peaks of the envelope in the EOD amplitude, the envelope of the dynamic search frequency, and the instantaneous frequency for chirp detection. The chirps we detected on a dataset published by \textcite{raabElectrocommunicationSignalsIndicate2021} indicate that individuals that win the competition for a superior shelter chirp less than the losers. Moreover, in some of these pairings, chirps emitted by the loser are temporally correlated with the offset of an agonistic interaction. This indicates, that chirps might be used by losers to terminate chasing events.
|
||||
|
||||
\subsection{Assessing detection performance}
|
||||
|
||||
While our chirp detector detected many chirps, the performance of the algorithm was not quantified yet. We only used a small trial dataset to visually asses whether chirps are detected or not. Additionally, the EOD$f$ of the fish in the trial data set were rather far apart, making it easy to correctly assign chirps. To assess the performance of our analysis data set, we visually inspected each iteration of the algorithm (in 5 seconds snippets) for one whole recording. However, for future uses of this detector, it is imperative to quantify the detection performance and tune the parameters accordingly. This is especially important considering that the main innovation of this algorithm is not to detect but to correctly assign chirps. We predict that the performance in the correct assignment will most likely drop with decreasing difference frequencies. If this is the case we have to quantify this parameter as well. In conclusion, future work on this algorithm should include a synthetic data set that reflects the natural variability to quantify the detection performances of multiple chirp detection parameters.
|
||||
|
||||
\subsection{The problem of normalization}
|
||||
|
||||
Currently, a major flaw of the chirp detector is that the peak detection parameters are fixed for each feature, and decided upon by trial and error. We reached the most error-free performance with high thresholds. A side effect is that the great majority of the chirps are only detected on a single electrode. If chirps would be detectable on multiple electrodes, the smallest number of electrodes on which a chirp must be detected could be used as an additional detection parameter. But to do this, ideally, we would need to be able to use the same peak detection parameters across all electrodes and for all features. And to achieve this we should normalize our feature arrays not only across features but also across electrodes. The difficulty with normalization arises with the rolling windows and the changing electrodes. If there is no chirp in a certain window, normalizing across electrodes over just that window would scale up all noise so that the peak detector would find many peaks. Most of the time
|
||||
these peaks do not occur at the same time and thus are not falsely detected as chirps, but sometimes, they are. The current method to fix this is to not normalize at all and hard-code peak detection thresholds. This solution is not flexible and might fail completely for new data sets without manually adjusting the parameters. The seemingly easy approach would be to save the data of all feature arrays for each iteration to disk and normalize across all data before running the chirp detector. But the fact that for each iteration we jump between electrodes makes this step complicated and impractical. Another approach could be, to determine 'chirp-less' windows by a single parameter before normalization. If e.g. there in no peak in the search frequency, we could just skip to the next window instead of running the whole detection routine. Normalization could then be performed only in windows that have a peak in search frequency.
|
||||
|
||||
\subsection{Why can the instantaneous frequency go down during a chirp?} \label{ref:insta}
|
||||
|
||||
While computing the instantaneous frequency we often encountered troughs instead of peaks in frequency during chirps. We simply circumvented this issue by taking the absolute of this instantaneous frequency. This phenomenon is critical because chirps are defined by a positive frequency excursion and not by a decrease in frequency. This decrease was only found after we computed the band pass filter which is filtering \SI{5}{\hertz} around the baseline of the EOD$f$ of the individual fish. If we band pass-filtered in a way so that the chirp was still included in the signal, all peaks in the instantaneous frequency were positive. This indicates that the narrow band pass filter around the signal is causing these frequency drops. One explanation for this issue could be the reduced amplitude during a chirp. The band pass filter removes the high frequency components that a chirp introduces. If the amplitude reduction is high enough, the frequency information shifts to noise. If this noise has only low frequency components the instantaneous frequency drops. If the chirp contrast is low and the amplitude of the baseline does not decrease strongly, the increase in frequency that is common during a chirp might be reflected in a peak. But if the contrast is high and the amplitude in the baseline breaks down this would reflect in a trough in the instantaneous frequency. Against this theory stands the fact that we observed the troughs in frequency in cases where the amplitude of the baseline did not break down strongly.
|
||||
|
||||
\vspace{\baselineskip}
|
||||
|
||||
Another explanation are the properties of the band pass filter which can introduce a phase shift while the fish emits chirps. If the phase is shifted backward the frequency temporarily increases, and vice versa for forward phase shifts. We still do not fully understand this change in frequency yet. Further analysis should include plotting the transfer function of the filter since narrow pass bands could introduce anomalies. Additionally, we would like to simulate chirps with different parameters, such as chirp phase, height, width, and contrast to understand which parameters might result in a trough of the instantaneous frequency.
|
||||
|
||||
\subsection{Losers chirped more than winners}
|
||||
|
||||
The detected chirps in this experiment indicate that winners chirped less than losers in this competition experiment. We can hypothesize that chirps are used as a submissive signal conveying information about the physical condition \parencite{davies1978deep}, and therefore can settle the competition without fights that escalate.
|
||||
This result shows similarities to rises, another signal variation used by \textit{Aperonotus leptorhynchus}. \textcite{raabElectrocommunicationSignalsIndicate2021} showed that the loser of a competition experiment emitted more rises than the winner of the competition. Furthermore, the outcome of the competition is influenced by the body size: A larger body size is a predictor for winning the competition for the shelter \parencite{raabElectrocommunicationSignalsIndicate2021}. The frequencies of the individuals, on the other hand, do not seem to play a role in the outcome of the competition experiment. This needs to be further analyzed because the EOD$f$ is sexually dimorphic \parencite{meyer1987hormone}, and we did not take the sex of the pairings into account for our analysis.
|
||||
|
||||
\subsection{Why chirps might only terminate chasing in some dyads}
|
||||
|
||||
Our results indicated that chirps can terminate chasing, hence we computed the chirp rate to the chasing on- or offset and physical events. Only for the chasing offset we could find a connection for some pairings in the competition experiment in a way that the chirp increases right before the offset of the chasing event. \textcite{raabElectrocommunicationSignalsIndicate2021} showed that rises are used by the subordinate to motivate mutual assessment. In the few cases where we observed increasing chirp frequencies at the offset of a chasing event, chirps may have been used by the loser of the competition to signal submission. Still, the mean for all pairings did not show a correlation of chirps and the chasing offsets. This can be explained by not including factors like sex and size difference in our analysis. The sex of the fish has an important role for the communication behavior in the mating season, where, the female emits a big chirp to signalize the spawning \parencite{hagedornCourtSparkElectric1985a, henningerStatisticsNaturalCommunication2018}. The size difference is also important for the outcome of the competition experiment, so there may be a link between the increase of chirping behavior and the offset of the chasing events with the size difference. This should be dissected further in the analysis of the behavioral data set.
|
||||
|
||||
\subsection{Summary}
|
||||
|
||||
In conclusion, we were able to build the first chirp detector that might be usable on large data sets including multiple and freely moving individuals. Chrips can be detected by combining features from the changes in amplitude, instantaneous frequency, and a dynamic search window that is above the fish's own EOD$f$. We tested this detector on a data set of a competition experiment. We found that more chirps are produced when the size difference between the individuals was small and that the subordinates sometimes drastically increase their chirp frequency before a chasing event ends. This is the first step towards analyzing chirp-correlated behaviors in complex laboratory or field recordings to gain a clearer picture of the potentially multiple meanings of chirps. These first observations could inspire hypotheses that can then be verified in controlled experiments.
|
@ -1,38 +0,0 @@
|
||||
Successful communication depends on the transfer of reliable information \parencite{endler1993some, searcy2010evolution, hebets2016systems}. In the animal kingdom, information is commonly conveyed by signals which are produced or displayed by one individual to be received by another \parencite{hebets2016systems, searcy2010evolution, seyfarth2010central}. The main purpose of these signals is the prediction of upcoming events and the behavior of other animals in order to make suitable decisions \parencite{endler1993some, seyfarth2010central}. Thereby, signal reliability is a necessity for communication to occur in the first place, as neither the sender would produce signals nor the receiver would respond to them if it were not beneficial for both \parencite{seyfarth2017origin}. Thus, signals conveying reliable information are evolutionary stable even if a sender's and receiver's relationship may be cooperative or competitive \parencite{seyfarth2017origin}. However, the success of communication does not only depend on the signal and its properties itself. Any signal possesses an inherent vagueness that limits its specificity in any situation \parencite{seyfarth2003signalers, seyfarth2017origin}. Therefore, a second necessity for successful communication is contextual information. The context limits the possible interpretations of the information conveyed by the signal, thus increasing its specificity \parencite{seyfarth2017origin}. Hence, both the signal properties and the contextual information frame the communicative event whose success is further determined by the underlying degree of reliability. As such, successful communication is one of the main factors for an animal's survival and reproduction which is why it is the active focus of a wide field of research today.
|
||||
|
||||
\subsection{Communication across modalities}
|
||||
|
||||
As diverse as the contexts of animal signaling, as diverse are the modalities of the signals themselves. They range from chemical, tactile, and acoustic to visual and even electric, whereby the majority is effectively multimodal \parencite{bradbury1998principles, seyfarth2010central}. One of the most famous examples is the waggle dance of the honeybee \textit{Apis mellifera}, performed to communicate the location of a potent food source to workers in the hive \parencite{von1965tanze, riley2005flight}. As the performance is mainly a visual display it likewise involves scent \parencite{thom2007scent} as well as sound and air flow \parencite{tsujiuchi2007dynamic}. The former is also used by many ant species to indicate a profitable foraging spot. Thereby, the colony is guided by pheromone trails whose compounds are released via specified glands of individual ants \parencite{sudd1959interaction, david2009trail}. Another type of dance besides the honeybee's can be found in the peacock spider \textit{Maratus volans}. However, instead of dancing their way to a promising food source, male peacock spiders perform to impress females to find a partner to mate with \parencite{girard2011multi}. Not only because of the complex motion patterns and ornament displays but likewise owing to the vibratory signals emitted by the males, the peacock spider dance probably marks one of the most impressing communication signals in the animal kingdom \parencite{girard2011multi}. Considering the above it appears that invertebrates in general exhibit an interesting repertoire of possibilities to communicate, as there are also cases of facial patterns signifying status and rival assessment in paper wasps \parencite{tibbetts2008visual} as well as vibrational signals transmitted through plants by various insects to convey sex and species information together with directional cues for the potential mate \parencite{virant2004vibrational}.
|
||||
|
||||
\vspace{\baselineskip}
|
||||
|
||||
Striking examples of communication are also found among vertebrates. In this group, one of the most prevalent ways of conveying information is active vocalization. For instance, various monkey species exhibit an impressing repertoire of different calls, whereby its type depends on the context of the situation \parencite{SCHLENKER2016894}. One prominent example are the predator-specific calls in vervet monkeys indicating an attack by either a leopard, snake, or eagle \parencite{seyfarth1980}. Moreover, many species are likewise capable of communicating via facial and bodily gestures, which has for example been found in squirrel monkeys \parencite{anderson2010flexibility}, and chimpanzees \parencite{hobaiter2011gestural}. However, not only our closest ancestors but songbirds also use their voices through inter-individual contact. Especially during courtship, males like to show off their song repertoire to convey their qualities to a potential female mate and to repel competitors \parencite{kroodsma1991function, byers2009female}. Whereas a large fraction of animal communication can hardly be overheard, there is an equally large part escaping from human perception. Rodents, for example, emit calls with frequencies way above \SI{20}{\kilo\hertz}: so-called ultrasonic vocalizations (USV) which exceed the human hearing threshold \parencite{wohr2013affective}. These USVs are situation-dependent and differ in frequency. In rats, for instance, three major call types are known: Aversive and appetitive calls emitted by juveniles and adults, and pup calls indicating social isolation \parencite{wohr2013affective, seffer2014pro}. But this is not the only strategy for transferring information in the non-perceivable realm of animal communication. Some groups developed sensory systems enabling them to act both as the sender and receiver of sensory information by using the very same mechanisms \parencite{jonesCommunicationSelfFriends2021}. Such systems are found in pretty distinct species, namely bats \parencite{simmons1979echolocation} and toothed whales \parencite{kamminga1988echolocation}, which rely on echolocation, and weakly-electric fish using electrolocation \parencite{heiligenberg1973electrolocation}. The peculiar thing about these systems is that the animals possessing them actively generate signals to navigate, communicate, and detect prey \parencite{jonesCommunicationSelfFriends2021}. These signals are reflected by objects or other animals and allow for the detection of their properties, distance, and direction. In echolocation, bats and toothed whales emit high-frequency (\SI{20}{\kilo\hertz}) tonal sounds which are produced in the larynx or nose and whose reflections are then gathered by parts of the acoustic system \parencite{schnitzler2003spatial, park2016ultrasonic}. Electrolocation in weakly-electric fish on the other hand is based on the discharge of their electric organ \parencite{heiligenberg1973electrolocation, meyer1987hormone}. They hunt and navigate by perceiving alterations of their electric field caused by other animals or objects \parencite{von1999active, heiligenberg1973electrolocation}. The ability to sense electric fields is not uncommon in aquatic species. Sharks and rays have likewise developed the ability to perceive astoundingly weak electric fields \parencite{kalmijn1971electric}. They use these inputs for prey detection and electrolocation as well, however, they do not produce an electric signal themselves \parencite{kalmijn1971electric, kalmijn1973electro}. The electric eel, for example, is one the few species that is capable of actively producing electricity \parencite{catania2014shocking}. In contrast to weakly-electric fish, however, they do not constantly surround themselves with an electric field, but rather emit a mixture of low- and high-voltage pulses \parencite{catania2014shocking, catania2015electric}. The low-voltage pulses are used for sensing the environment whereas high-voltage pulses serve as a predatory strike to incapacitate prey \parencite{brown1950electric, catania2014shocking, catania2015electric}. The latter can reach strikingly high voltages of up to 600 V \parencite{brown1950electric, catania2014shocking}.
|
||||
|
||||
\subsection{Beyond human perception: Weakly electric fish}
|
||||
As stated above, weakly-electric fish sample their environment by surrounding themselves with an electric field produced by discharging their electric organ. Based on the waveform of the electric organ discharge (EOD), weakly-electric fish are classified into two distinct groups: Wave-type and pulse-type electric fish \parencite{bendaPhysicsElectrosensoryWorlds2020}. Whereas wave-type EODs are characterized by a continuous discharge and a periodic waveform, pulse-type EODs show brief pulses interrupted by intervals of varying length \parencite{bendaPhysicsElectrosensoryWorlds2020}. For the wave-type category, the gymnotiform brown ghost knifefish \textit{Apteronotus leptorhynchus} is one of the best and most extensively studied species \parencite{zupanc1993evoked, meyer1987hormone, malerAtlasBrainElectric1991, hupeEffectDifferenceFrequency2008, englerSpontaneousModulationsElectric2000a, hagedornCourtSparkElectric1985a, dunlapHormonalBodySize2002}. Its EOD has a quasi-sinusoidal waveform and typically lies in a frequency range of \SIrange{600}{1000}{\hertz} (Fig. \ref{fig:Electric fields}B) \parencite{zupanc1993evoked, zakonEODModulationsBrown2002a}. Moreover, \textit{A. leptorhynchus} shows a sexual dimorphism in EOD frequency (EOD$f$) with females having a lower EOD$f$ ($<$ \SI{750}{\hertz}) than male individuals ($>$ \SI{750}{\hertz}) \parencite{meyer1987hormone, zakonEODModulationsBrown2002a}. Communication between two individuals occurs first and foremost because of the physical properties of the electric field. When interacting, the electric fields of two fish superimpose and result in amplitude modulation (AM), also called beat (Fig. \ref{fig:Electric fields}A \& D) \parencite{zupanc1993evoked, walzNeuroethologyElectrocommunicationHow2013, benda2005spike, bendaPhysicsElectrosensoryWorlds2020}. That is, by interacting with another individual, one fish perceives its own EOD being modulated by another electric field. The resulting beat has a specific frequency which is given by the frequency difference of the two interacting fish \parencite{benda2005spike, walzNeuroethologyElectrocommunicationHow2013, bendaPhysicsElectrosensoryWorlds2020}. Because of the frequency dimorphism in \textit{A. leptorhynchus}, this allows for a passive exchange of information since the beat frequency concomitantly transfers the gender relation of the fish. Simply speaking, the greater the beat frequency, the more likely it is that the other fish is of the opposite gender. However, \textit{A. leptorhynchus} is likewise capable of producing active communication signals. One of the most extensively studied are chirps \parencite{zupanc1993evoked, zakonEODModulationsBrown2002a, englerSpontaneousModulationsElectric2000a, hupeElectrocommunicationSignalsFree2009, obotiWhyBrownGhost2022, dunlapDiversityStructureElectrocommunication2003}. Chirps are transient frequency modulations that, depending on the type, are characterized by a duration of ten to a few hundred milliseconds and an increase of EOD$f$ up to several hundred Hertz (Fig. \ref{fig:introplot}, red track) \parencite{bendaPhysicsElectrosensoryWorlds2020, zupanc1993evoked, engler2001differential, zakonEODModulationsBrown2002a}. They have been shown to play a role in courtship and the synchronization of spawning \parencite{hagedornCourtSparkElectric1985a, henningerStatisticsNaturalCommunication2018}, but are also associated with aggressive encounters \parencite{triefenbachChangesSignallingAgonistic2008b}, where they are suggested to serve as a submissive signal \parencite{walzNeuroethologyElectrocommunicationHow2013, henningerStatisticsNaturalCommunication2018}. However, because of the various contexts in which chirps are emitted, their function is still an ongoing debate \parencite{obotiWhyBrownGhost2022}.
|
||||
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=0.7\linewidth]{figures/Fishies_cropped.pdf}
|
||||
\mycaption{Properties of the electric field in weakly-electric fish}{\textbf{A:} The fish surround themselves with a bipolar electric field. The superposition of electric fields alters their spatial properties. Thereby, the phase of the EOD influences the extend and direction of the electric field of each fish (red marker in EOD waveform). \textbf{B:} Quasi-sinusoidal waveform of the EOD of a single wave-type electric fish. \textbf{C:} Amplitude modulations (AM) of an individual fish recorded by external electrodes. The AM is caused by the movements of the fish relative to the recording electrodes. \textbf{D:} Amplitude modulation or beat caused by the superposition of two electric fields. The frequency of the beat is given by the difference in frequency between the individual fish (Figure from \cite{raab2022social}).}
|
||||
\label{fig:Electric fields}
|
||||
\end{figure}
|
||||
|
||||
\subsection{The problem of detection}
|
||||
For the exploration of the chirp function, two requirements have to be met. First, chirps need to be recorded to be analyzed in the first place. Secondly, the analysis depends on the ability to detect chirps in the recordings. Optimally, the method fulfilling these two requirements is suited for deployment in laboratory settings as well as in the field. Chirp recording is the least problematic part. As chirps alter the EOD$f$, they can readily be recorded by a pair of electrodes placed in the vicinity of the fish \parencite{bendaPhysicsElectrosensoryWorlds2020}. A common approach in laboratory settings is to simulate a conspecific with a pair of electrodes which should cause the recorded individual to chirp. These can then be recorded with a second pair of electrodes \parencite{zupanc1993evoked, hagedornCourtSparkElectric1985a, dunlapDiversityStructureElectrocommunication2003, englerSpontaneousModulationsElectric2000a}. However, the laboratory does not reflect the natural habitat of the animals since the behavioral context of chirps emitted in the wild is highly variable \parencite{henningerStatisticsNaturalCommunication2018}. Thus, there is a need of recording fish in their natural habitat while freely behaving and interacting with each other. This is why recently, research is more and more shifted to the field \parencite{henningerStatisticsNaturalCommunication2018, fugere2011electrical, zubizarreta2020seasonal}. With this increasing change in research approach, new methods that allow for the recording of multiple fish in the wild were required. One approach was developed by \textcite{henningerStatisticsNaturalCommunication2018} and successfully tested in Panama, where the authors simultaneously recorded multiple individuals of four species of weakly-electric fish. They used a grid-like array of at least 54 electrodes which was submerged at the cut bank side of a stream. Later, a refined version of the grid was applied for further recordings in Colombia \parencite{raab2022AdvancesNoninvasiveTracking}. As such, the recording problem has been solved for the laboratory as well as the natural setting. The detection of chirps, however, turned out to be the bigger problem. As part of successful detection, it is required to correctly identify the chirp in the EOD track as well as assign the individual chirp to the correct animal. This is particularly difficult in recordings of multiple fish because the detection of a chirp implies that the underlying EOD$f$ is tracked for every animal. One approach for the analysis of electric signals in weakly-electric fish are spectrograms (Fig. \ref{fig:introplot}). Since chirps are very fast changes in the EOD$f$, a sufficiently high resolution in the time domain is necessary to resolve them. But, an increase of the resolution in time is accompanied by a resolution decrease in the frequency domain, which renders the distinction of the EOD$f$s of the chirping fish impossible. Given the situation of two fish with a similar EOD$f$, and the individual with the lower frequency emitting a chirp, it is unfeasible to assign the chirp to the correct individual by only using the spectrogram. Thus, chirp detection is surely not a trivial problem. \textcite{raab2022AdvancesNoninvasiveTracking}, based on previous work by \textcite{henningerTrackingActivityPatterns2020}, developed an algorithm that solves one part of the issue. It is capable of extracting EOD tracks by using both the EOD$f$ in the spectrogram as well as the spatial distribution of EOD power across electrodes \parencite{raab2022AdvancesNoninvasiveTracking}. This way, it is possible to obtain the EOD track of individual fish in recordings with multiple fish in space and time (Fig. \ref{fig:introplot}). Only with this groundwork, it is achievable to detect chirps in the first place.
|
||||
|
||||
\vspace{\baselineskip}
|
||||
|
||||
To overcome the remaining problem of chirp assignment, we refined previous work on this issue \parencite{henningerStatisticsNaturalCommunication2018}. In the following, we describe and test the first draft of a chirp detection algorithm capable of assigning chirps to individual fish in recordings with multiple animals. Our approach was to include a dynamic filter that searches for a frequency range between the individual EOD$f$'s free of interference. In a second step, we tested the algorithm with a data set obtained by \textcite{raabElectrocommunicationSignalsIndicate2021}. Thereby, individuals of the species \textit{Apteronotus leptorhynchus} were recorded while competing for a superior shelter in dyads. We were able to detect 25766 chirps in this data set alone and analyzed the context in which they were emitted. Thereby, we could not fully replicate suggestions for chirp function from the literature. However, some of our results indicate the association of chirps with a specific type of event during aggressive encounters.
|
||||
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=1\linewidth]{figures/introplot.pdf}
|
||||
|
||||
\mycaption{Spectrogram of signal containing the EODs of two fish.}{The line plots indicate the instantaneous frequency of the respective individual, which we obtained by filtering the signal around its frequency component. \textbf{A:} Fish 1 produces a chirp that is visible by the frequency excursion in the instantaneous frequency and spectrogram if the frequency resolution is sufficient (NFFT=133.3, 20\% overlap). \textbf{B:} If the frequency resolution is lower, fish can be distinguished in the spectrogram, but chirp detection and assignment are not reliable (NFFT=1333.3, 20\% overlap). }
|
||||
\label{fig:introplot}
|
||||
\end{figure}
|
||||
|
||||
\todo[inline, color=orange]{Bilder von Lepto und seiner EOD waveform?}
|
||||
\todo[inline, color=orange]{Messy in-text Quellen}
|
||||
\todo[inline, color=orange]{Messy references Quellen}
|
@ -1,59 +0,0 @@
|
||||
\subsection{Chirp detection algorithm}
|
||||
|
||||
We developed and tested the improved algorithm on the raw data from a competition experiment (more information in section \ref{Behaviour}, or in the Paper by \cite{raabElectrocommunicationSignalsIndicate2021}) with 15 electrodes arranged in a grid. To be able to analyze the communication signals of the fish, which are most prominently represented as changes in their EOD$f$, we used the frequency tracks that were already computed by \textcite{raab2022AdvancesNoninvasiveTracking}. A frequency track is an approximation of how the fundamental frequency of the EOD of a single fish evolves. But because they are estimated using a spectrogram, they lack the temporal information needed to resolve transient frequency changes of a chirp. For this reason, we use the raw data, which has the appropriate temporal resolution to resolve chirps. To extract the EOD of single individuals, the frequency tracks are used to build specific band pass filters for every fish. Because the baseline EOD$f$ of a single fish changes with temperature and communication signals, filtering had to be performed in time windows. Additionally, to obtain the best signal for a freely moving fish, we choose the electrode with the highest power for every single rolling window. For each time window, we extract the amplitude of the baseline, the instantaneous frequency, and the amplitude of the search frequency, which all change during the production of a chirp. Simultaneously detected peaks on all three features are classified as a chirp. All signal processing steps that the raw data in a single rolling window snippet goes through are summarized in figure \ref{fig:algorithm}. The following paragraphs describe the processing steps in the same order as they are organized in the code of the algorithm.
|
||||
|
||||
\subsubsection{Loading and preparing the data set}
|
||||
|
||||
The parameters used by the algorithm are all adjustable using a \codeword{yaml} configuration file, which includes sections for the path to the data set as well as to the output directory. The program loads the raw data as well as the frequency tracks for every fish in the recording and generates time windows, in which it iterates across the raw data set. For each window in time, it iterates through the respective windows on the frequency tracks of all the fish in the recording. This approach is arguably less intuitive compared to iterating through the full time of the recording on the frequency track of a single fish and then skipping to the next individual. However, it reduces the number of times the more memory- and hence computationally demanding raw data set needs to be loaded.
|
||||
|
||||
\subsubsection{Rolling windows and their overlap}
|
||||
|
||||
To reduce the edge effects caused by filtering, we overlapped the rolling windows (window duration of \SI{5}{\second}) by one second and discarded the first and last \SI{250}{\milli\second} of a single window. This resulted in a true overlap of \SI{0,5}{\milli\second} in which chirps might be detected twice. To resolve this issue, we grouped all chirps that occurred less than \SI{20}{\milli\second} apart from each other as a single chirp.
|
||||
|
||||
\subsubsection{Following a fish through space}
|
||||
|
||||
The raw data set was recorded using an electrode grid. Hence, the EOD amplitudes of single fish varies between electrodes, because the amplitude decreases with the distance between an electrode and a moving fish. These changes in amplitude convey information on where the individual of interest is located in space. For optimal detection of chirps, we should use the strongest electrode for one fish, since it should have the highest signal-to-noise ratio. Additionally, if multiple fish are further apart, it is advantageous to use the electrode that is closest to one fish, but not the other, to increase the odds of correct sender assignment. For each iteration of the algorithm, we start off with a short (\SI{5}{\second}) snippets of the approximated frequency track and respective powers of the frequencies of an individual fish on the same temporal extent. To decide upon which raw data snippet for this fish across the pool of 15 electrodes the algorithm should load for optimal performance, we first had to determine, to which electrode the fish was the closest in the current window in time. To determine the best electrodes, we simply use the ones that had the highest power in the tracked frequencies, since power is proportional to the amplitude squared. In the current implementation, we repeat the following pipeline for the two electrodes with the highest power for the current fish of interest.
|
||||
|
||||
\subsubsection{Feature extraction}
|
||||
|
||||
After determining the best electrodes, we load the raw data snipped for the respective time window. This results in a data set of the frequency track of a fish (Figure \ref{fig:algorithm}, A, red) and the raw data (Figure \ref{fig:algorithm}, A, spectrogram), which the algorithm uses to extract more information. For each frequency track of an individual fish in each window, the raw signal \SI{5}{\hertz} is then band pass filtered with cut off frequencies above and below the baseline of the frequency track. This effectively provides an approximation of the recorded signal as if the current fish was the only one in the area. However fast frequency excursions, e.g. during chirps, are lost because they exceed the frequency limits of the cutoff frequencies. In other words, the peak of the chirp is filtered out by the band pass filter and that is why we see a trough in the amplitude. We use this to our advantage because the amplitude of the signal drops at theses points in time. To use the amplitudes of this signal, we extract the envelope using a low pass (cut off at \SI{25}{\hertz}) filter multiplied by the square-root of two (Figure \ref{fig:algorithm}, B, red). In addition to the amplitude trough, a chirp should also change the frequency of the signal, even if the actual peak gets lost due to filtering. To detect such transient frequency changes, we also compute the instantaneous frequency of the filtered baseline, which can be achieved by extracting the inverse of every single period using the zero crossings of the signal (Figure \ref{fig:algorithm}, B, orange). But using just the envelope and the instantaneous frequency of the EOD baseline of the fish was not sufficient to determine whether the anomaly we detected is a chirp or is caused e.g. by the movement of the fish. To deal with this issue, we used the notion that chirps are always up-modulations of the frequency that reach values of +\SI{50}{\hertz} to +\SI{300}{\hertz} above the baseline EOD$f$ of the emitting fish \parencite{zakonEODModulationsBrown2002a}. The first approach by \textcite{henningerStatisticsNaturalCommunication2018} was to filter a band at approximately +\SI{10}{\hertz} above the baseline and look for peaks in the power of this filtered band. We call this area the 'search frequency'. However, this method comes with the limitation, that if there are multiple fish and one of them has a baseline EOD$f$ that is coincidentally higher than the frequency of the current analyzed fish, the search frequency will become unusable. If the search frequency is close to- or at the baseline frequency of a fish with higher EOD$f$, chirps from the fish with a lower EOD$f$ would not be correctly assigned to the sender (Figure \ref{fig:henninger}). We adopted the method first documented in \textcite{henningerStatisticsNaturalCommunication2018} but introduce a dynamically adjusted search frequency. In our version of the chirp detection algorithm, the search frequency is still confined to a region of +\SI{20}{\hertz} to +\SI{100}{\hertz} above the baseline. In contrast to \textcite{henningerStatisticsNaturalCommunication2018} in this window, the frequency tracks of all other individuals are evaluated to find a region with the largest frequency difference from all other individuals that is still within the usual frequency peaks of chirps. The search frequency is then extracted in this window. This should make chirp detection and, most importantly, correct assignment to the sender, possible with recordings that include multiple individuals (Figure \ref{fig:dynamic}). The search window chosen in the example in Figure \ref{fig:algorithm}, A is indicated by orange dashed lines on the spectrogram. The envelope of the search frequency is visualized by the orange line in panel B.
|
||||
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=\linewidth]{figures/henninger.pdf}
|
||||
\mycaption{Fixed search frequency}{Schematic sketch of a fixed search frequency used by \textcite{henningerStatisticsNaturalCommunication2018}. The search frequency is placed at a static \SI{10}{\hertz} above the baseline of the lower fish. \textbf{A:} If there is no upper fish with a higher EOD$f$ in the search frequency, a chirp detection can be implemented. The decrease in amplitude and the increase of amplitude in the search frequency can be used for the chirp detection algorithm. \textbf{B:}
|
||||
In the case that there is another fish with an EOD$f$ in the search frequency, chirp detection is impaired. The EOD at the search frequency contains the chirp of the lower fish and the EOD of the upper fish, which is making the detection of the chirp unreliable.}
|
||||
\label{fig:henninger}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=\linewidth]{figures/dynamic.pdf}
|
||||
\mycaption{Dynamic search frequency}{Schematic sketch of a dynamic search frequency implemented in the new algorithm. The search frequency is dynamic in a range of \SIrange{20}{100}{\hertz} above the baseline of the lower fish. \textbf{A:} If there is no upper fish with a higher EOD$f$ in the search frequency, chirp detection can be implemented. The decrease in amplitude and the increase of amplitude in the search frequency can be used for the chirp detection algorithm. \textbf{B:}
|
||||
In the case that there is another fish with an EOD$f$ in the search frequency, the search frequency changes dynamically to a space with the largest difference to the upper fish. The chirp assignment is now possible with a correct sender assignment.}
|
||||
\label{fig:dynamic}
|
||||
\end{figure}
|
||||
|
||||
\subsubsection{Feature processing}
|
||||
|
||||
The features we extracted, particularly the filtered baseline EOD, are also subject to change e.g. when the fish moves. But changes due to movements happen on a larger time scale. To reduce the impact of this noise, we additionally band-pass filtered the baseline envelope with cutoff frequencies \SI{2}{\hertz} (low-cutoff) and \SI{100}{\hertz} (high-cutoff). Additionally, we invert the baseline envelope to turn troughs into peaks for detection purposes. For the instantaneous frequency, we took the absolute and shifted it by the median of the EOD$f$ ($|EODf_{inst} - med({EODf_{inst}}|$). The instantaneous frequency during a chirp was negative in some cases. This irregularity is discussed in section \ref{ref:insta}.
|
||||
The search frequency required no additional processing. The processed features are visualized in the panel C of Figure \ref{fig:algorithm}. We then detected the peaks of all three features using a prominence threshold of 0.00005 for the baseline envelope, 0.000004 for the search frequency, and 2 for the instantaneous frequency.
|
||||
|
||||
\subsubsection{Peak classification}
|
||||
|
||||
Since all three features should coincide temporally with the peak in frequency during a chirp, the first criterion for a chirp was that peaks were detected on all three features simultaneously. More specifically, we chose \SI{20}{\milli\second} as a tolerance window where the peaks must co-occur. Additionally, since we repeated the algorithm on the two electrodes of the highest power for the current time window, we set a threshold of the number of electrodes on which the chirp must appear to be accepted as a chirp. In the current implementation, the chirp must be detected on just a single electrode. If a chirp is detected we compute the mean of the three time points on which the peaks were found and appended the resulting time stamp to the chirp times of the current fish.
|
||||
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=\linewidth]{figures/10.0_11245.5.pdf}
|
||||
\mycaption{Core processing pipeline of the chirp detector.}{The left side shows how the data is modified while it passes the algorithm. The right side is color-coded with respect to the plots and indicates what kind of data is visualized and how it is processed. \textbf{A:} The spectrogram on the top is used to visualize the raw data. The red line on the spectrogram indicates the tracked frequency. \textbf{B:} The subplots show the three features that the algorithm extracts from the raw data using the frequency tracks of the individual fish. Red indicates the envelope of the baseline EOD$f$ for a single fish, which we obtain by filtering the raw signal around the tracked frequency. The envelope of the 'search frequency' we obtain by filtering a narrow band inside a dynamically adjusted window above the baseline EOD$f$ of the respective fish is indicated in orange. The search frequency band is also indicated by dashed lines in the same color on the spectrogram. The yellow line is the instantaneous frequency of the filtered baseline (red), which changes if the signal disappears out of the filtering window during a chirp. \textbf{C} illustrates how the three features appear after they are processed. The dots indicate the detected peaks. The dots on the spectrogram indicate the detected chirps after the peaks are sorted.}
|
||||
\label{fig:algorithm}
|
||||
\end{figure}
|
||||
|
||||
\subsection{Behavioral data used to test the algorithm } \label{Behaviour}
|
||||
|
||||
We tested the algorithm on a data set published by \textcite{raabElectrocommunicationSignalsIndicate2021}. The dataset was recorded using 21 mature \textit{Apteronotus leptorhynchus} from a tropical fish supplier. 9 males and 12 females, all in non-breeding conditions, were used. The experiment was conceptualized to understand the role of rises, another communication signal, during the competition of two fish for a superior shelter. The experiments took place in a \SI{100}{\liter} tank with a superior shelter in the center surrounded by other, less optimal shelters. The bottom of the tank was equipped with 15 mono-polar electrodes with low-noise buffer headstages. A reference electrode was positioned in a corner of the tank. The electric signals were first amplified and then digitized at \SI{20}{\kilo\hertz} per channel. The movement of the fish was recorded by a video camera mounted above the aquarium. Behavior (chasing events and contacts) were annotated manually using the software package BORIS \parencite{https://doi.org/10.1111/2041-210X.12584}.
|
||||
|
||||
\subsection{Data analysis}
|
||||
|
||||
The chirp detection algorithm as well as our subsequent analysis were written in Python 3.10.9 using the packages numpy, scipy, matplotlib, \href{https://github.com/janscience/thunderfish}{thunderfish} and \href{https://github.com/janscience/audioio}{audioio} \parencite{2020SciPy-NMeth, Hunter:2007, harris2020array}. All scripts as well as the required package versions are publically available in a \href{https://whale.am28.uni-tuebingen.de/git/raab/GP2023_chirp_detection}{git repository} (\url{https://whale.am28.uni-tuebingen.de/git/raab/GP2023_chirp_detection}). The temporal relation between chirps and chasing events was analyzed using a cross-correlation analysis. We computed the temporal differences between agonistic events (chasing onset, -offset and contact) and the chirps up to $\pm$ \SI{90}{\second} before and after the event. We then convolved the temporal differences with a Gaussian kernel with a standard deviation of \SI{2}{\milli\second}. To generate a baseline to compare the convolution with we randomly shuffled the chirp intervals in 50 permutations and recomputed the convolution for each. The baseline distribution was then obtained by the median across the permutations with the 5th and 95th percentile respectively.
|
@ -1,43 +0,0 @@
|
||||
\subsection{Chirps during dyadic competitions}
|
||||
|
||||
We used our algorithm to detect chirps in a dyadic competition experiment where two individuals competed for one superior shelter. In this exemplary recording almost all physical contacts, chasing events, and chirps happened during the dark phase of the experiment (Figure \ref{fig:timeline}, top rows in raster plot). We can estimate that the winner (yellow) did not chirp as much as the loser (red) of the competition. In this example, the winner emitted only 10 chirps whereas the loser produced close to 400 chirps(Figure \ref{fig:timeline}).
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=\linewidth]{figures/timeline.pdf}
|
||||
\mycaption{Overview of a single trial of the competition experiment.}{The raster plots indicate the occurrence of the type of event. Rasterplots of chirps are color-coded with respect to the emitter of the chirp. The line plots indicate the EOD$f$ of the two fish over time as it was tracked on a spectrogram. Red was the winner of the competition, and yellow was the loser. The shaded area indicates the phase where lights were turned off.}
|
||||
\label{fig:timeline}
|
||||
\end{figure}
|
||||
|
||||
Losers tended to chirp more than winners (Figure \ref{fig:winnerloser} A). This could be observed in 16 of the 22 valid trials (total n = 28) however this difference was not significant (Wilcoxon-test: z-statistic=67.0, pvalue=0.054). The outcome of the competition experiment was influenced by the size of the individuals. The winner of the dyadic interaction tended to have a greater size than the losers (Figure \ref{fig:winnerloser} B). For small size differences, there was an increase in chirp count, indicating that chirps increased in relevance for competition as the size difference decreased. The frequencies of the emitted EOD for winner and loser did not show any relevance for the outcome of the competition because the distribution for winner and loser is evenly spread over the range of EOD$f$ (Figure \ref{fig:winnerloser} C).
|
||||
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=\linewidth]{figures/chirps_winner_loser.pdf}
|
||||
\mycaption{Competition outcome }{compared to the emitted chirps in 22 valid parings (total n=28). \textbf{A:} Winner and loser of the competition experiment displayed with their quantity of chirps. Connecting lines indicate the pairing in the experiment. \textbf{B:} Winner and Loser compared to their size difference [cm] and the number of chirps they emitted. Losers (orange) are usually smaller than winners ($\Delta$ size $<$ 0) \textbf{C}: EOD$f$s [Hz] of the winner and loser compared to their quantity of chirps.}
|
||||
\label{fig:winnerloser}
|
||||
\end{figure}
|
||||
|
||||
\subsection{Chirps emitted by loser fish might disrupt chasing events}
|
||||
|
||||
\begin{wrapfigure}{R}{0.6\textwidth}
|
||||
\vspace{-1cm}
|
||||
\begin{center}
|
||||
\includegraphics[width=\linewidth]{figures/kde.pdf}
|
||||
\end{center}
|
||||
\mycaption{Chasing-triggered chirps}{for two selected dyads. The time axis is centered around the offset of a chasing event. The black line indicates the bootstrapped baseline. The gray area is the bootstrapped confidence interval. The red line indicates the chirp rate estimated by a convolution with a Gaussian kernel. \textbf{A:} In most cases, there was no change in the chirp rate around an offset of a chasing event. \textbf{B:} But in a subset of the dataset chirping increased just before the chasing stopped.}
|
||||
\label{fig:kde}
|
||||
\end{wrapfigure}
|
||||
|
||||
Losers tending to chirp more often than winners already hints towards a behavioral significance to chirps during competitions. We evaluated the temporal correlation of chirps and agnostic behaviors to further investigate the behavioral significance of chirps in dyadic encounters. For this, we computed the chasing-triggered chirp rate of the on- and offset of chasing events and contacts. The chasing-triggered chirp rate consists of chirps centered around agonistic events. After this sorting, we estimated the distribution of chirps for the events using a convolution with a Gaussian kernel. We focused on the offset of chasing events because the onset and physical contact did not show consistent increases in chirp rate. For approximately a third of all dyadic competitions, there was an increase in chirp rate before the offset occurred (Figure \ref{fig:kde} right). In the other cases, there was no interaction of the offset and chirps (Figure \ref{fig:kde} left). This was compared to a permutated baseline to show a significant increase in chirp rate. The summarized chirp rates across all competitions did not show an increase in chirp rate around the offsets of the chasing events.
|
||||
|
||||
\newpage
|
||||
\FloatBarrier
|
||||
\begin{wrapfigure}[14]{hr}{0.4\textwidth}
|
||||
\centering
|
||||
\includegraphics[width=\linewidth]{figures/chirps_in_chasing.pdf}
|
||||
\mycaption{Proportion of time spent and chirps emitted in chasing events.}{Relative time spent of the dark recording phase (3 h) in chasing events for single trials (\textbf{left}) and the relative quantity of chirps that were emitted during a chasing event for each recording (\textbf{right}). The lines show that the proportion of chirps during chasing events was only elevated for a subset of the competitions.}
|
||||
\label{fig:chasing}
|
||||
\end{wrapfigure}
|
||||
\FloatBarrier
|
||||
|
||||
To summarize this, we compared the percentage of time spent in chasing events with the percentage of chirps emitted in chasing events. This comparison should indicate that when the two fish are in a chasing event, and the chirps are used to carry information specifically related to competition, there should be an increase in the percentage of chirps in these chasing events. In 15 out of 27 pairings the percentage of time spent in chasing events was higher than the percentage of chirps emitted during chasing, showing that chirps may not be used for transferring information about their physical status in those cases. This indicates that chirps are not specifically used for the offset of chasing events because their percentage of occurrences is often lower than the time spent in these chasing events.
|
@ -1,42 +0,0 @@
|
||||
\begin{titlepage}
|
||||
\begin{center}
|
||||
|
||||
% Head block
|
||||
\vspace*{0.5cm}
|
||||
\Large
|
||||
Protokoll\\
|
||||
Neurobiologisches Großpraktikum\\
|
||||
\vspace{1cm}
|
||||
|
||||
% Title and author
|
||||
\Huge
|
||||
\textbf{Detecting chirps in freely interacting weakly electric fish}\\
|
||||
\Large
|
||||
\vspace{1cm}
|
||||
\textbf{Sina Prause, Alexander Wendt and Patrick Weygoldt}
|
||||
\vspace{1cm}
|
||||
|
||||
% Add a nice figure below the authors
|
||||
% \begin{figure}[ht]
|
||||
% \centering
|
||||
% %\includegraphics[scale=0.6]{images/drawing.pdf}
|
||||
% \mycaption{A short bold}{ and a long regular caption.}
|
||||
% \end{figure}
|
||||
\vfill
|
||||
|
||||
% Bottom block
|
||||
\large
|
||||
\vspace{0.5cm}
|
||||
Supervised by:\\
|
||||
\vspace{0.5cm}
|
||||
Till Raab \& Jan Benda\\
|
||||
Neuroethology\\
|
||||
Institute for Neurobiology\\
|
||||
Tuebingen University\\
|
||||
\vspace{1cm}
|
||||
\today \\
|
||||
\vspace{0.5cm}
|
||||
|
||||
|
||||
\end{center}
|
||||
\end{titlepage}
|
@ -1,484 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="157.31206mm"
|
||||
height="54.225082mm"
|
||||
viewBox="0 0 157.31206 54.225082"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
showguides="false" />
|
||||
<defs
|
||||
id="defs2">
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="marker4127"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto-start-reverse"
|
||||
inkscape:stockid="TriangleStart"
|
||||
markerWidth="5.3244081"
|
||||
markerHeight="6.155385"
|
||||
viewBox="0 0 5.3244081 6.1553851"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="xMidYMid">
|
||||
<path
|
||||
transform="scale(0.5)"
|
||||
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path4125" />
|
||||
</marker>
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="TriangleStart"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto-start-reverse"
|
||||
inkscape:stockid="TriangleStart"
|
||||
markerWidth="5.3244081"
|
||||
markerHeight="6.155385"
|
||||
viewBox="0 0 5.3244081 6.1553851"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="xMidYMid">
|
||||
<path
|
||||
transform="scale(0.5)"
|
||||
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path135" />
|
||||
</marker>
|
||||
<mask
|
||||
maskUnits="userSpaceOnUse"
|
||||
id="mask20699">
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.176389;stroke-linecap:round;stroke-dasharray:0.176389, 0.352777;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect20701"
|
||||
width="23.746241"
|
||||
height="29.012896"
|
||||
x="14.455999"
|
||||
y="25.037264" />
|
||||
</mask>
|
||||
<mask
|
||||
maskUnits="userSpaceOnUse"
|
||||
id="mask26463">
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.176389;stroke-linecap:round;stroke-dasharray:0.176389, 0.352777;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect26465"
|
||||
width="24.016085"
|
||||
height="29.408197"
|
||||
x="48.58794"
|
||||
y="24.810217" />
|
||||
</mask>
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="marker4127-3"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto-start-reverse"
|
||||
inkscape:stockid="TriangleStart"
|
||||
markerWidth="5.3244081"
|
||||
markerHeight="6.155385"
|
||||
viewBox="0 0 5.3244081 6.1553851"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="xMidYMid">
|
||||
<path
|
||||
transform="scale(0.5)"
|
||||
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path4125-6" />
|
||||
</marker>
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="TriangleStart-7"
|
||||
refX="0"
|
||||
refY="0"
|
||||
orient="auto-start-reverse"
|
||||
inkscape:stockid="TriangleStart"
|
||||
markerWidth="5.3244081"
|
||||
markerHeight="6.155385"
|
||||
viewBox="0 0 5.3244081 6.1553851"
|
||||
inkscape:isstock="true"
|
||||
inkscape:collect="always"
|
||||
preserveAspectRatio="xMidYMid">
|
||||
<path
|
||||
transform="scale(0.5)"
|
||||
style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
|
||||
d="M 5.77,0 -2.88,5 V -5 Z"
|
||||
id="path135-5" />
|
||||
</marker>
|
||||
<mask
|
||||
maskUnits="userSpaceOnUse"
|
||||
id="mask26463-3">
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.176389;stroke-linecap:round;stroke-dasharray:0.176389, 0.352777;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect26465-5"
|
||||
width="24.016085"
|
||||
height="29.408197"
|
||||
x="48.58794"
|
||||
y="24.810217" />
|
||||
</mask>
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-2.6879387,3.5411841)">
|
||||
<g
|
||||
id="g29069">
|
||||
<g
|
||||
id="g27306"
|
||||
transform="translate(1.6122597,-126.9852)">
|
||||
<g
|
||||
id="g20783"
|
||||
transform="matrix(1.1174167,0,0,1.1174167,-7.4533966,102.29928)">
|
||||
<path
|
||||
id="rect111"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.352778;stroke-dasharray:none;marker-start:url(#marker4127);marker-end:url(#TriangleStart)"
|
||||
d="M 39.393832,54.875322 H 13.679574 V 23.480161"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<g
|
||||
id="g20697"
|
||||
mask="url(#mask20699)">
|
||||
<g
|
||||
id="g17691"
|
||||
transform="translate(-9.0524821,1.5670767)">
|
||||
<path
|
||||
style="fill:none;stroke:#f39ca9;stroke-width:0.386556;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 13.66089,47.485891 c 0,0 3.579334,0.431089 7.210941,0.304518 3.964707,-0.138179 8.838234,-0.560278 13.262189,-0.740152 1.601595,-0.06512 3.205275,-0.179435 4.80719,-0.122713 3.002115,0.106298 6.685233,0.512079 8.970224,0.866597 3.093977,0.480035 4.509419,0.0238 8.155396,0.0238 0.712971,0 1.383337,1.09e-4 1.383337,1.09e-4"
|
||||
id="path6589"
|
||||
sodipodi:nodetypes="csaassc" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:0;stroke:#f39ca9;stroke-width:0.352778;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 33.565113,46.99482 V 25.014953"
|
||||
id="path15375" />
|
||||
</g>
|
||||
<g
|
||||
id="g17695"
|
||||
transform="translate(-9.0524821,1.5670767)">
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:0;stroke:#f39ca9;stroke-width:0.183859;stroke-linecap:round;stroke-dasharray:0.183859, 0.367719;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 13.662696,45.284594 h 43.35043"
|
||||
id="path17685" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:0;stroke:#f39ca9;stroke-width:0.182405;stroke-linecap:round;stroke-dasharray:0.182405, 0.36481;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 13.717208,49.619449 h 42.7493"
|
||||
id="path17687" />
|
||||
</g>
|
||||
<g
|
||||
id="g17701"
|
||||
transform="translate(-9.0524821,-17.067258)">
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:0;stroke:#f9e2ab;stroke-width:0.183859;stroke-linecap:round;stroke-dasharray:0.183859, 0.367719;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 13.662696,45.284594 h 43.35043"
|
||||
id="path17697" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:0;stroke:#f9e2ab;stroke-width:0.182405;stroke-linecap:round;stroke-dasharray:0.182405, 0.36481;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 13.717208,49.619449 h 42.7493"
|
||||
id="path17699" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g26741">
|
||||
<path
|
||||
id="path20843"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.352778;stroke-dasharray:none;marker-start:url(#marker4127);marker-end:url(#TriangleStart)"
|
||||
d="M 73.564746,54.875322 H 47.850488 V 41.044315"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
id="path20847"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.352778;stroke-dasharray:none;marker-end:url(#TriangleStart)"
|
||||
d="M 47.850488,37.52073 V 23.689723"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<g
|
||||
id="g26461"
|
||||
mask="url(#mask26463)">
|
||||
<path
|
||||
id="rect20911"
|
||||
style="fill:#f39ca9;stroke-width:0.176389;stroke-linecap:round;stroke-dasharray:0.176389, 0.352777"
|
||||
d="m 46.803364,43.109017 h 8.530365 c 1.254981,0 1.596628,3.13284 2.088089,3.13284 0.536811,0 0.906944,-3.13284 2.178978,-3.13284 H 75.279593 V 53.15153 c 0,0 -10.291757,0 -15.491839,0 -1.228712,0 -1.81324,-2.829799 -2.362169,-2.829799 -0.504598,0 -0.90127,2.829799 -2.069958,2.829799 -2.533291,0 -8.552263,0 -8.552263,0 z"
|
||||
sodipodi:nodetypes="csssccssscc" />
|
||||
<path
|
||||
style="fill:#f39ca9;fill-opacity:1;stroke:none;stroke-width:0.176389;stroke-linecap:round;stroke-dasharray:0.176389, 0.352777;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 57.086752,43.268426 c 0,0 -0.37156,-3.765766 0,0 z"
|
||||
id="path21858" />
|
||||
<path
|
||||
id="rect24170"
|
||||
style="fill:#f9e2ab;fill-opacity:1;stroke-width:0.176;stroke-linecap:round;stroke-dasharray:0.176, 0.351999;stroke-dashoffset:0"
|
||||
d="m 46.274513,30.194793 h 9.549215 c 1.717363,0 0.878619,-5.049747 1.652599,-5.049747 0.569072,0 0.112537,5.049747 1.842854,5.049747 h 16.286061 v 0.904651 H 59.272796 c -1.758036,0 -1.156495,5.094603 -1.735013,5.094603 -0.56555,0 -0.0031,-5.094603 -1.749022,-5.094603 h -9.514248 z"
|
||||
sodipodi:nodetypes="csssccssscc" />
|
||||
<path
|
||||
style="fill:#f39ca9;fill-opacity:1;stroke:none;stroke-width:0.176389;stroke-linecap:round;stroke-dasharray:0.176389, 0.352777;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="M 57.184587,30.525973 V 30.009484"
|
||||
id="path24173" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g27206"
|
||||
transform="matrix(1.1174167,0,0,1.1174167,88.616705,85.937397)">
|
||||
<g
|
||||
id="g20825"
|
||||
transform="translate(-47.283516,14.100222)">
|
||||
<path
|
||||
id="path20723"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.352778;stroke-dasharray:none;marker-start:url(#marker4127);marker-end:url(#TriangleStart)"
|
||||
d="M 74.083516,55.417696 H 48.369258 V 24.022535"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<g
|
||||
id="g20745"
|
||||
mask="url(#mask20699)"
|
||||
transform="translate(34.689684,0.54237409)">
|
||||
<g
|
||||
id="g20729"
|
||||
transform="translate(-9.0524821,1.5670767)">
|
||||
<path
|
||||
style="fill:none;stroke:#f39ca9;stroke-width:0.386556;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 13.66089,47.485891 c 0,0 3.579334,0.431089 7.210941,0.304518 3.964707,-0.138179 8.838234,-0.560278 13.262189,-0.740152 1.601595,-0.06512 3.205275,-0.179435 4.80719,-0.122713 3.002115,0.106298 6.685233,0.512079 8.970224,0.866597 3.093977,0.480035 4.509419,0.0238 8.155396,0.0238 0.712971,0 1.383337,1.09e-4 1.383337,1.09e-4"
|
||||
id="path20725"
|
||||
sodipodi:nodetypes="csaassc" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:0;stroke:#f39ca9;stroke-width:0.352778;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 33.565113,46.99482 V 25.014953"
|
||||
id="path20727" />
|
||||
</g>
|
||||
<g
|
||||
id="g20735"
|
||||
transform="translate(-9.0524821,1.5670767)">
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:0;stroke:#f39ca9;stroke-width:0.183859;stroke-linecap:round;stroke-dasharray:0.183859, 0.367719;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 13.662696,45.284594 h 43.35043"
|
||||
id="path20731" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:0;stroke:#f39ca9;stroke-width:0.182405;stroke-linecap:round;stroke-dasharray:0.182405, 0.36481;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 13.717208,49.619449 h 42.7493"
|
||||
id="path20733" />
|
||||
</g>
|
||||
<g
|
||||
id="g20741"
|
||||
transform="translate(-9.0524821,-17.067258)">
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:0;stroke:#f9e2ab;stroke-width:0.183859;stroke-linecap:round;stroke-dasharray:0.183859, 0.367719;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 13.662696,45.284594 h 43.35043"
|
||||
id="path20737" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:0;stroke:#f9e2ab;stroke-width:0.182405;stroke-linecap:round;stroke-dasharray:0.182405, 0.36481;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 13.717208,49.619449 h 42.7493"
|
||||
id="path20739" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:none;stroke:#9cc2f3;stroke-width:0.386556;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 48.404425,30.32741 c 0,0 -3.345283,0.189357 -6.97689,0.315928 -3.964707,0.138179 -8.289879,-0.476491 -12.43901,-0.459534 -2.056246,0.0084 -4.106262,0.284197 -6.162517,0.278119 -2.894461,-0.0086 -6.387136,-0.08825 -8.672127,-0.442763 -3.093977,-0.480035 -4.5094191,-0.0238 -8.1553961,-0.0238 -0.712971,0 -1.383337,-1.09e-4 -1.383337,-1.09e-4"
|
||||
id="path20743"
|
||||
sodipodi:nodetypes="csaassc" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g26684"
|
||||
transform="translate(0,-0.00474886)">
|
||||
<path
|
||||
id="path20843-6"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.352778;stroke-dasharray:none;marker-start:url(#marker4127-3);marker-end:url(#TriangleStart-7)"
|
||||
d="M 61.186844,69.522667 H 35.472586 V 55.69166"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
id="path20847-2"
|
||||
style="fill:none;stroke:#000000;stroke-width:0.352778;stroke-dasharray:none;marker-end:url(#TriangleStart-7)"
|
||||
d="M 35.472586,52.168075 V 38.337068"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<g
|
||||
id="g26461-9"
|
||||
mask="url(#mask26463-3)"
|
||||
transform="translate(-12.377903,14.647345)">
|
||||
<path
|
||||
id="rect20911-1"
|
||||
style="fill:#f39ca9;stroke-width:0.176389;stroke-linecap:round;stroke-dasharray:0.176389, 0.352777"
|
||||
d="m 46.803364,43.109017 h 8.530365 c 1.254981,0 1.596628,3.13284 2.088089,3.13284 0.536811,0 0.906944,-3.13284 2.178978,-3.13284 H 75.279593 V 53.15153 c 0,0 -10.291757,0 -15.491839,0 -1.228712,0 -1.81324,-2.829799 -2.362169,-2.829799 -0.504598,0 -0.90127,2.829799 -2.069958,2.829799 -2.533291,0 -8.552263,0 -8.552263,0 z"
|
||||
sodipodi:nodetypes="csssccssscc" />
|
||||
<path
|
||||
style="fill:#f39ca9;fill-opacity:1;stroke:none;stroke-width:0.176389;stroke-linecap:round;stroke-dasharray:0.176389, 0.352777;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 57.086752,43.268426 c 0,0 -0.37156,-3.765766 0,0 z"
|
||||
id="path21858-2" />
|
||||
<path
|
||||
style="fill:#f39ca9;fill-opacity:1;stroke:none;stroke-width:0.176389;stroke-linecap:round;stroke-dasharray:0.176389, 0.352777;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="M 57.184587,30.525973 V 30.009484"
|
||||
id="path24173-0" />
|
||||
<path
|
||||
id="path26795"
|
||||
style="fill:#f9e2ab;fill-opacity:1;stroke-width:0.176389;stroke-linecap:round;stroke-dasharray:0.176389, 0.352777"
|
||||
d="m 46.675328,25.652062 h 8.530365 c 1.254981,0 1.241821,-0.3105 2.182659,-0.253058 0.889795,0.05432 0.812374,0.253058 2.084408,0.253058 H 75.151557 V 35.694575 H 59.659718 c -1.228712,0 -1.76712,0.363128 -2.316049,0.363128 -0.504598,0 -0.94739,-0.363128 -2.116078,-0.363128 h -8.552263 z"
|
||||
sodipodi:nodetypes="csssccssscc" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-weight:bold;font-size:3.88056px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.176;stroke-linecap:round;stroke-dasharray:0.176, 0.351999;stroke-dashoffset:0;stroke-opacity:1"
|
||||
x="79.707397"
|
||||
y="42.263329"
|
||||
id="text27390"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan27388"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.88056px;font-family:sans-serif;-inkscape-font-specification:sans-serif;fill:#000000;fill-opacity:1;stroke-width:0.176"
|
||||
x="79.707397"
|
||||
y="42.263329">Time</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-weight:bold;font-size:3.88056px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.176;stroke-linecap:round;stroke-dasharray:0.176, 0.351999;stroke-dashoffset:0;stroke-opacity:1"
|
||||
x="-28.696152"
|
||||
y="6.4117355"
|
||||
id="text27652"
|
||||
transform="rotate(-90)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan27650"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.88056px;font-family:sans-serif;-inkscape-font-specification:sans-serif;fill:#000000;fill-opacity:1;stroke-width:0.176"
|
||||
x="-28.696152"
|
||||
y="6.4117355">Frequency</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-weight:bold;font-size:3.88056px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.176;stroke-linecap:round;stroke-dasharray:0.176, 0.351999;stroke-dashoffset:0;stroke-opacity:1"
|
||||
x="-28.696152"
|
||||
y="88.128494"
|
||||
id="text27668"
|
||||
transform="rotate(-90)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan27666"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.88056px;font-family:sans-serif;-inkscape-font-specification:sans-serif;fill:#000000;fill-opacity:1;stroke-width:0.176"
|
||||
x="-28.696152"
|
||||
y="88.128494">Frequency</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-weight:bold;font-size:3.88056px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.176;stroke-linecap:round;stroke-dasharray:0.176, 0.351999;stroke-dashoffset:0;stroke-opacity:1"
|
||||
x="-28.33526"
|
||||
y="44.299961"
|
||||
id="text27672"
|
||||
transform="rotate(-90)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan27670"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.88056px;font-family:sans-serif;-inkscape-font-specification:sans-serif;fill:#000000;fill-opacity:1;stroke-width:0.176"
|
||||
x="-28.33526"
|
||||
y="44.299961">Amplitude</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-weight:bold;font-size:3.88056px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.176;stroke-linecap:round;stroke-dasharray:0.176, 0.351999;stroke-dashoffset:0;stroke-opacity:1"
|
||||
x="-28.33526"
|
||||
y="126.89417"
|
||||
id="text27676"
|
||||
transform="rotate(-90)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan27674"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.88056px;font-family:sans-serif;-inkscape-font-specification:sans-serif;fill:#000000;fill-opacity:1;stroke-width:0.176"
|
||||
x="-28.33526"
|
||||
y="126.89417">Amplitude</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.93889px;font-family:sans-serif;-inkscape-font-specification:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.176;stroke-linecap:round;stroke-dasharray:0.176, 0.351999;stroke-dashoffset:0;stroke-opacity:1"
|
||||
x="2.6879387"
|
||||
y="0"
|
||||
id="text27680"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan27678"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93889px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';stroke-width:0.176"
|
||||
x="2.6879387"
|
||||
y="0">A</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.93889px;font-family:sans-serif;-inkscape-font-specification:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.176;stroke-linecap:round;stroke-dasharray:0.176, 0.351999;stroke-dashoffset:0;stroke-opacity:1"
|
||||
x="84.461967"
|
||||
y="-5.6843419e-14"
|
||||
id="text27684"><tspan
|
||||
id="tspan27682"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.93889px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';stroke-width:0.176"
|
||||
x="84.461967"
|
||||
y="-5.6843419e-14"
|
||||
sodipodi:role="line">B</tspan></text>
|
||||
<g
|
||||
id="g28997"
|
||||
transform="translate(-3.629747,0.99082983)">
|
||||
<g
|
||||
id="g27968"
|
||||
transform="translate(-10.561285,-5.4205261)">
|
||||
<rect
|
||||
style="fill:#f39ca9;fill-opacity:1;stroke:none;stroke-width:0.176;stroke-linecap:round;stroke-dasharray:0.176, 0.351999;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect27686"
|
||||
width="3.3779061"
|
||||
height="3.3779061"
|
||||
x="28.749498"
|
||||
y="51.490963" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.52778px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.176;stroke-linecap:round;stroke-dasharray:0.176, 0.351999;stroke-dashoffset:0;stroke-opacity:1"
|
||||
x="32.992668"
|
||||
y="54.511654"
|
||||
id="text27811"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan27809"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52778px;font-family:sans-serif;-inkscape-font-specification:sans-serif;fill:#000000;fill-opacity:1;stroke-width:0.176"
|
||||
x="32.992668"
|
||||
y="54.511654">Lower fish baseline</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
id="g27976"
|
||||
transform="translate(29.373014,-5.6652521)">
|
||||
<rect
|
||||
style="fill:#f9e2ab;fill-opacity:1;stroke:none;stroke-width:0.176;stroke-linecap:round;stroke-dasharray:0.176, 0.351999;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect27970"
|
||||
width="3.3779061"
|
||||
height="3.3779061"
|
||||
x="28.749498"
|
||||
y="51.490963" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.52778px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.176;stroke-linecap:round;stroke-dasharray:0.176, 0.351999;stroke-dashoffset:0;stroke-opacity:1"
|
||||
x="32.992668"
|
||||
y="54.511654"
|
||||
id="text27974"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan27972"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52778px;font-family:sans-serif;-inkscape-font-specification:sans-serif;fill:#000000;fill-opacity:1;stroke-width:0.176"
|
||||
x="32.992668"
|
||||
y="54.511654">Lower fish search frequency</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
id="g28812"
|
||||
transform="translate(84.247459,-5.6652521)">
|
||||
<rect
|
||||
style="fill:#9cc2f3;fill-opacity:0.933333;stroke:none;stroke-width:0.176;stroke-linecap:round;stroke-dasharray:0.176, 0.351999;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect28806"
|
||||
width="3.3779061"
|
||||
height="3.3779061"
|
||||
x="28.749498"
|
||||
y="51.490963" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.52778px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.176;stroke-linecap:round;stroke-dasharray:0.176, 0.351999;stroke-dashoffset:0;stroke-opacity:1"
|
||||
x="32.992668"
|
||||
y="54.511654"
|
||||
id="text28810"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan28808"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.52778px;font-family:sans-serif;-inkscape-font-specification:sans-serif;fill:#000000;fill-opacity:1;stroke-width:0.176"
|
||||
x="32.992668"
|
||||
y="54.511654">Upper fish baseline</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 25 KiB |
@ -1,132 +0,0 @@
|
||||
\documentclass[a4paper]{article} % Default text size and document class
|
||||
|
||||
% Format -----------------------------------------------------------------------------
|
||||
\usepackage[ % To set up the page
|
||||
a4paper, % A4 paper
|
||||
width=160mm, % Text body width
|
||||
top=25mm,bottom=25mm, % Top and bottom margins
|
||||
bindingoffset=6mm % Offset of left and right pages for printing
|
||||
]
|
||||
{geometry}
|
||||
\usepackage{setspace} % So set spacings (e.g. line height)
|
||||
\onehalfspacing % Sets line width to 1.5
|
||||
%\usepackage{sectsty} % To control sectional headers
|
||||
% \chapternumberfont{\huge} % Set size of chapter number
|
||||
% \chaptertitlefont{\huge} % Set size of chapter title
|
||||
|
||||
\usepackage{multicol} % Writes in multiple columns
|
||||
\usepackage{wrapfig}
|
||||
\usepackage{lipsum} % Inserts blind text for previewing
|
||||
\usepackage{placeins} % adding float barriers
|
||||
\setlength{\parindent}{0pt} % remove indentation for new paragraphs
|
||||
|
||||
% Language ---------------------------------------------------------------------------
|
||||
\usepackage[english]{babel} % To enable language support other than us english
|
||||
\usepackage[babel]{csquotes} % Omits spell checking in quotations
|
||||
|
||||
% Images, long tables & append pdf pages ---------------------------------------------
|
||||
\usepackage{float} % Enables some options for image placement
|
||||
\usepackage{graphicx} % To include graphics
|
||||
\usepackage{ % Long table stuff
|
||||
csvsimple, % So a csv can be converted to a table
|
||||
longtable, % To make long tables
|
||||
booktabs % To page break long tables properly
|
||||
}
|
||||
\usepackage{pdfpages} % Appends pdf pages
|
||||
\usepackage{xcolor}
|
||||
|
||||
% Custom fancy image caption ---------------------------------------------------------
|
||||
\usepackage{caption}
|
||||
\captionsetup[figure]{labelsep=space}
|
||||
% \captionsetup[table]{labelsep=space}
|
||||
\newcommand{\mycaption}[2]{\caption[#1]{\textbar\,\textbf{#1} #2}}
|
||||
|
||||
% Math and code support -----------------------------------------------------------------------
|
||||
\usepackage{amsmath, amsfonts} % For fancy math
|
||||
\usepackage[separate-uncertainty = true]{siunitx} % for SI units
|
||||
\sisetup{detect-all}
|
||||
\usepackage{xcolor}
|
||||
\NewDocumentCommand{\codeword}{v}{%
|
||||
\texttt{\textcolor{black}{#1}}%
|
||||
}
|
||||
% Citation setup ---------------------------------------------------------------------
|
||||
\usepackage[]{hyperref} % To make clickable links
|
||||
\hypersetup{hidelinks,} % To draw no boxes around the links
|
||||
\usepackage[ % Citation setup
|
||||
backend=biber, % Citation backend
|
||||
style=apa, % Citation format
|
||||
% minbibnames=1,
|
||||
% maxbibnames=99,
|
||||
%maxnames=2, % Max number of names displayed in text
|
||||
%sortlocale=de_DE, % Sorts by german conventions (ß = ss)
|
||||
%natbib=true, % Enables \citep and \citet. Use \parencite and \textcite in future
|
||||
url=false, % Disaples url
|
||||
doi=true, % To print the doi
|
||||
eprint=false % Disables link to eprint (?)
|
||||
]{biblatex}
|
||||
\DeclareDelimFormat[parencite]{finalnamedelim}{\addspace and\space} % disabels & and uses 'and'
|
||||
|
||||
\DeclareDelimFormat[bib,biblist]{finalnamedelim}{\addspace and\space} % disabels & and uses 'and' in the bibfile
|
||||
|
||||
\addbibresource{chirpdetection.bib} % Where the bibliography file is
|
||||
\renewcommand{\familydefault}{\sfdefault}
|
||||
\usepackage{tgheros}
|
||||
\usepackage{wrapfig}
|
||||
%\setlength{\marginparwidth}{1.8cm}
|
||||
\usepackage[disable]{todonotes}
|
||||
%\DeclareUnicodeCharacter{0301}{\textcolor{red}{*************************************}} % for finding unicode erros in the pdf
|
||||
|
||||
|
||||
\setlength\bibitemsep{2\itemsep}
|
||||
|
||||
% Abstract title same size as sectional headers --------------------------------------
|
||||
% see here: https://tex.stackexchange.com/questions/366169/how-to-change-font-size-for-abstract-title
|
||||
\makeatletter
|
||||
\renewenvironment{abstract}{%
|
||||
\if@twocolumn
|
||||
\section*{\abstractname}%
|
||||
\else %% <- here I've removed \small
|
||||
\begin{center}%
|
||||
{\bfseries \Large\abstractname\vspace{\z@}}% %% <- here I've added \Large
|
||||
\end{center}%
|
||||
\quotation
|
||||
\fi}
|
||||
{\if@twocolumn\else\endquotation\fi}
|
||||
\makeatother
|
||||
|
||||
% Document ---------------------------------------------------------------------------
|
||||
\begin{document}
|
||||
\input{chapters/titlepage.tex} % Add the path to the titlepage here
|
||||
\listoftodos
|
||||
\section{Introduction}
|
||||
\label{chap:introduction}
|
||||
\input{chapters/introduction.tex}
|
||||
|
||||
\pagebreak
|
||||
\section{Methods}
|
||||
\label{chap:methods}
|
||||
\input{chapters/methods.tex}
|
||||
|
||||
\pagebreak
|
||||
\section{Results}
|
||||
\label{chap:results}
|
||||
\input{chapters/results.tex}
|
||||
|
||||
\pagebreak
|
||||
\section{Discussion}
|
||||
\label{chap:discussion}
|
||||
\input{chapters/discussion.tex}
|
||||
|
||||
\pagebreak
|
||||
\printbibliography
|
||||
%\pagebreak
|
||||
%\section{Appendix}
|
||||
% \label{chap:appendix}
|
||||
% \input{chapters/appendix.tex}
|
||||
|
||||
% Append pdf outputs from other programs to the document
|
||||
% \includepdf[pages=-]{appendix/ab.pdf}
|
||||
% \includepdf[pages=-]{appendix/cd.pdf}
|
||||
% \includepdf[pages=-]{appendix/ef.pdf}
|
||||
|
||||
\end{document}
|
180
requirements.txt
@ -1,86 +1,140 @@
|
||||
cmocean==3.0.3
|
||||
asttokens==2.2.1
|
||||
backcall==0.2.0
|
||||
contourpy==1.0.6
|
||||
cycler==0.11.0
|
||||
ipython==8.12.0
|
||||
matplotlib==3.7.1
|
||||
numpy==1.24.2
|
||||
pandas==2.0.0
|
||||
paramiko==3.1.0
|
||||
PyYAML==6.0
|
||||
scipy==1.10.1
|
||||
scp==0.14.5
|
||||
tqdm==4.65.0
|
||||
decorator==5.1.1
|
||||
executing==1.2.0
|
||||
fonttools==4.38.0
|
||||
ipython==8.8.0
|
||||
jedi==0.18.2
|
||||
kiwisolver==1.4.4
|
||||
matplotlib==3.6.2
|
||||
matplotlib-inline==0.1.6
|
||||
numpy==1.24.1
|
||||
packaging==23.0
|
||||
pandas==1.5.2
|
||||
parso==0.8.3
|
||||
pexpect==4.8.0
|
||||
pickleshare==0.7.5
|
||||
Pillow==9.4.0
|
||||
prompt-toolkit==3.0.36
|
||||
ptyprocess==0.7.0
|
||||
pure-eval==0.2.2
|
||||
Pygments==2.14.0
|
||||
pyparsing==3.0.9
|
||||
python-dateutil==2.8.2
|
||||
pytz==2022.7
|
||||
scipy==1.10.0
|
||||
six==1.16.0
|
||||
sklearn==0.0.post1
|
||||
stack-data==0.6.2
|
||||
traitlets==5.8.0
|
||||
wcwidth==0.2.5
|
||||
asttokens==2.2.1
|
||||
audioio==0.10.0
|
||||
backcall==0.2.0
|
||||
contourpy==1.0.6
|
||||
cycler==0.11.0
|
||||
decorator==5.1.1
|
||||
executing==1.2.0
|
||||
fonttools==4.38.0
|
||||
ipython==8.8.0
|
||||
jedi==0.18.2
|
||||
kiwisolver==1.4.4
|
||||
matplotlib==3.6.2
|
||||
matplotlib-inline==0.1.6
|
||||
numpy==1.24.1
|
||||
packaging==23.0
|
||||
pandas==1.5.2
|
||||
parso==0.8.3
|
||||
pexpect==4.8.0
|
||||
pickleshare==0.7.5
|
||||
Pillow==9.4.0
|
||||
prompt-toolkit==3.0.36
|
||||
ptyprocess==0.7.0
|
||||
pure-eval==0.2.2
|
||||
Pygments==2.14.0
|
||||
pyparsing==3.0.9
|
||||
python-dateutil==2.8.2
|
||||
pytz==2022.7
|
||||
scipy==1.10.0
|
||||
six==1.16.0
|
||||
sklearn==0.0.post1
|
||||
stack-data==0.6.2
|
||||
thunderfish==1.9.10
|
||||
traitlets==5.8.0
|
||||
wcwidth==0.2.5
|
||||
appnope==0.1.3
|
||||
asttokens==2.2.1
|
||||
audioio==0.10.0
|
||||
backcall==0.2.0
|
||||
contourpy==1.0.6
|
||||
cycler==0.11.0
|
||||
decorator==5.1.1
|
||||
executing==1.2.0
|
||||
fonttools==4.38.0
|
||||
ipython==8.8.0
|
||||
jedi==0.18.2
|
||||
kiwisolver==1.4.4
|
||||
matplotlib==3.6.2
|
||||
matplotlib-inline==0.1.6
|
||||
numpy==1.24.1
|
||||
packaging==23.0
|
||||
pandas==1.5.2
|
||||
parso==0.8.3
|
||||
pexpect==4.8.0
|
||||
pickleshare==0.7.5
|
||||
Pillow==9.4.0
|
||||
prompt-toolkit==3.0.36
|
||||
ptyprocess==0.7.0
|
||||
pure-eval==0.2.2
|
||||
Pygments==2.14.0
|
||||
pyparsing==3.0.9
|
||||
python-dateutil==2.8.2
|
||||
pytz==2022.7
|
||||
scipy==1.10.0
|
||||
six==1.16.0
|
||||
sklearn==0.0.post1
|
||||
stack-data==0.6.2
|
||||
thunderfish==1.9.10
|
||||
traitlets==5.8.0
|
||||
wcwidth==0.2.5
|
||||
asttokens==2.2.1
|
||||
astunparse==1.6.3
|
||||
audioio==0.10.0
|
||||
backcall==0.2.0
|
||||
bcrypt==4.0.1
|
||||
cffi==1.15.1
|
||||
cmocean==3.0.3
|
||||
comm==0.1.3
|
||||
contourpy==1.0.7
|
||||
cryptography==40.0.2
|
||||
cmocean==2.0
|
||||
contourpy==1.0.6
|
||||
cycler==0.11.0
|
||||
debugpy==1.6.7
|
||||
decorator==5.1.1
|
||||
execnb==0.1.5
|
||||
executing==1.2.0
|
||||
fastcore==1.5.29
|
||||
fonttools==4.39.3
|
||||
ghapi==1.0.3
|
||||
ipykernel==6.22.0
|
||||
ipython==8.12.0
|
||||
fonttools==4.38.0
|
||||
ipython==8.8.0
|
||||
jedi==0.18.2
|
||||
joblib==1.2.0
|
||||
jupyter_client==8.2.0
|
||||
jupyter_core==5.3.0
|
||||
kiwisolver==1.4.4
|
||||
matplotlib==3.7.1
|
||||
matplotlib==3.6.2
|
||||
matplotlib-inline==0.1.6
|
||||
nbdev==2.3.12
|
||||
nest-asyncio==1.5.6
|
||||
numpy==1.24.2
|
||||
packaging==23.1
|
||||
pandas==2.0.0
|
||||
paramiko==3.1.0
|
||||
numpy==1.24.1
|
||||
packaging==23.0
|
||||
pandas==1.5.2
|
||||
parso==0.8.3
|
||||
pexpect==4.8.0
|
||||
pickleshare==0.7.5
|
||||
Pillow==9.5.0
|
||||
platformdirs==3.3.0
|
||||
prompt-toolkit==3.0.38
|
||||
psutil==5.9.5
|
||||
Pillow==9.4.0
|
||||
prompt-toolkit==3.0.36
|
||||
ptyprocess==0.7.0
|
||||
pure-eval==0.2.2
|
||||
pycparser==2.21
|
||||
Pygments==2.15.1
|
||||
PyNaCl==1.5.0
|
||||
Pygments==2.14.0
|
||||
pyparsing==3.0.9
|
||||
PyQt5==5.15.9
|
||||
PyQt5-Qt5==5.15.2
|
||||
PyQt5-sip==12.12.1
|
||||
PyQt6==6.5.0
|
||||
PyQt6-Qt6==6.5.0
|
||||
PyQt6-sip==13.5.1
|
||||
PySide2==5.13.2
|
||||
PySide6==6.5.0
|
||||
PySide6-Addons==6.5.0
|
||||
PySide6-Essentials==6.5.0
|
||||
python-dateutil==2.8.2
|
||||
pytz==2023.3
|
||||
pytz==2022.7
|
||||
PyYAML==6.0
|
||||
pyzmq==25.0.2
|
||||
scikit-learn==1.2.2
|
||||
scipy==1.10.1
|
||||
scp==0.14.5
|
||||
shiboken2==5.13.2
|
||||
shiboken6==6.5.0
|
||||
scikit-learn==1.2.0
|
||||
scipy==1.10.0
|
||||
six==1.16.0
|
||||
sklearn==0.0.post1
|
||||
stack-data==0.6.2
|
||||
threadpoolctl==3.1.0
|
||||
thunderfish==1.9.10
|
||||
tornado==6.3.1
|
||||
tqdm==4.65.0
|
||||
traitlets==5.9.0
|
||||
tzdata==2023.3
|
||||
watchdog==3.0.0
|
||||
wcwidth==0.2.6
|
||||
traitlets==5.8.0
|
||||
wcwidth==0.2.5
|
||||
|