merge master
This commit is contained in:
commit
e19ddcb391
312
README.md
312
README.md
@ -1,64 +1,248 @@
|
||||
# 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
|
||||
```
|
||||
<!-- 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>
|
||||
|
||||
|
||||
|
||||
<!-- 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>
|
||||
|
||||
|
||||
|
||||
<!-- ABOUT THE PROJECT -->
|
||||
## About The Project
|
||||
|
||||
[![Product Name Screen Shot][product-screenshot]](https://example.com)
|
||||
|
||||
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`
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
<!-- ### Built With -->
|
||||
|
||||
<!-- * [![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] -->
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
<!-- GETTING STARTED -->
|
||||
## Getting Started
|
||||
|
||||
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.
|
||||
|
||||
<!-- ### 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 -->
|
||||
<!-- ``` -->
|
||||
|
||||
### 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
|
||||
|
||||
|
64
README1.md
Normal file
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
|
||||
```
|
BIN
assets/logo.png
Normal file
BIN
assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
1184
assets/logo.svg
Normal file
1184
assets/logo.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 84 KiB |
@ -1,4 +1,5 @@
|
||||
import os
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
@ -268,4 +269,5 @@ def main(datapath: str):
|
||||
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/'
|
||||
main(datapath)
|
||||
|
1095
code/chirpdetection.py
Normal file → Executable file
1095
code/chirpdetection.py
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
@ -1,48 +1,46 @@
|
||||
# directory setup
|
||||
dataroot: "../data/"
|
||||
outputdir: "../output/"
|
||||
|
||||
# Duration and overlap of the analysis window in seconds
|
||||
window: 5
|
||||
window: 10
|
||||
overlap: 1
|
||||
edge: 0.25
|
||||
|
||||
# Number of electrodes to go over
|
||||
number_electrodes: 3
|
||||
minimum_electrodes: 2
|
||||
|
||||
# Boundary for search frequency in Hz
|
||||
search_boundary: 100
|
||||
# Search window bandwidth and minimal baseline bandwidth
|
||||
minimal_bandwidth: 20
|
||||
|
||||
# Cutoff frequency for envelope estimation by lowpass filter
|
||||
envelope_cutoff: 25
|
||||
# Instantaneous frequency smoothing usint a gaussian kernel of this width
|
||||
baseline_frequency_smoothing: 5
|
||||
|
||||
# Cutoff frequency for envelope highpass filter
|
||||
envelope_highpass_cutoff: 3
|
||||
# Baseline processing parameters
|
||||
baseline_envelope_cutoff: 25
|
||||
baseline_envelope_bandpass_lowf: 4
|
||||
baseline_envelope_bandpass_highf: 100
|
||||
baseline_envelope_envelope_cutoff: 4
|
||||
|
||||
# Cutoff frequency for envelope of envelope
|
||||
envelope_envelope_cutoff: 5
|
||||
# search envelope processing parameters
|
||||
search_envelope_cutoff: 5
|
||||
|
||||
# Instantaneous frequency bandpass filter cutoff frequencies
|
||||
instantaneous_lowf: 15
|
||||
instantaneous_highf: 8000
|
||||
baseline_frequency_highpass_cutoff: 0.000005
|
||||
baseline_frequency_envelope_cutoff: 0.000005
|
||||
|
||||
# Baseline envelope peak detection parameters
|
||||
baseline_prominence_percentile: 90
|
||||
|
||||
# Search envelope peak detection parameters
|
||||
search_prominence_percentile: 90
|
||||
|
||||
# Instantaneous frequency peak detection parameters
|
||||
instantaneous_prominence_percentile: 90
|
||||
# peak detecion parameters
|
||||
prominence: 0.005
|
||||
|
||||
# search freq parameter
|
||||
search_df_lower: 25
|
||||
search_df_lower: 20
|
||||
search_df_upper: 100
|
||||
search_res: 1
|
||||
search_freq_percentiles:
|
||||
- 5
|
||||
- 95
|
||||
search_bandwidth: 10
|
||||
default_search_freq: 50
|
||||
|
||||
# Classify events as chirps if they are less than this time apart
|
||||
chirp_window_threshold: 0.05
|
||||
|
||||
|
||||
|
@ -1,5 +1,78 @@
|
||||
import numpy as np
|
||||
from typing import List, Any
|
||||
from scipy.ndimage import gaussian_filter1d
|
||||
from scipy.stats import gamma, norm
|
||||
|
||||
|
||||
def scale01(data):
|
||||
"""
|
||||
Normalize data to [0, 1]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : np.ndarray
|
||||
Data to normalize.
|
||||
|
||||
Returns
|
||||
-------
|
||||
np.ndarray
|
||||
Normalized data.
|
||||
|
||||
"""
|
||||
return (2*((data - np.min(data)) / (np.max(data) - np.min(data)))) - 1
|
||||
|
||||
|
||||
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 purge_duplicates(
|
||||
@ -64,7 +137,7 @@ def purge_duplicates(
|
||||
|
||||
|
||||
def group_timestamps(
|
||||
sublists: List[List[float]], n: int, threshold: float
|
||||
sublists: List[List[float]], at_least_in: int, difference_threshold: float
|
||||
) -> List[float]:
|
||||
"""
|
||||
Groups timestamps that are less than `threshold` milliseconds apart from
|
||||
@ -100,7 +173,7 @@ def group_timestamps(
|
||||
|
||||
# Group timestamps that are less than threshold milliseconds apart
|
||||
for i in range(1, len(timestamps)):
|
||||
if timestamps[i] - timestamps[i - 1] < threshold:
|
||||
if timestamps[i] - timestamps[i - 1] < difference_threshold:
|
||||
current_group.append(timestamps[i])
|
||||
else:
|
||||
groups.append(current_group)
|
||||
@ -111,7 +184,7 @@ def group_timestamps(
|
||||
# Retain only groups that contain at least n timestamps
|
||||
final_groups = []
|
||||
for group in groups:
|
||||
if len(group) >= n:
|
||||
if len(group) >= at_least_in:
|
||||
final_groups.append(group)
|
||||
|
||||
# Calculate the mean of each group
|
||||
@ -137,6 +210,117 @@ def flatten(list: List[List[Any]]) -> List:
|
||||
return [item for sublist in list for item in sublist]
|
||||
|
||||
|
||||
def causal_kde1d(spikes, time, width, shape=2):
|
||||
"""
|
||||
causalkde computes a kernel density estimate using a causal kernel (i.e. exponential or gamma distribution).
|
||||
A shape of 1 turns the gamma distribution into an exponential.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
spikes : array-like
|
||||
spike times
|
||||
time : array-like
|
||||
sampling time
|
||||
width : float
|
||||
kernel width
|
||||
shape : int, optional
|
||||
shape of gamma distribution, by default 1
|
||||
|
||||
Returns
|
||||
-------
|
||||
rate : array-like
|
||||
instantaneous firing rate
|
||||
"""
|
||||
|
||||
# compute dt
|
||||
dt = time[1] - time[0]
|
||||
|
||||
# time on which to compute kernel:
|
||||
tmax = 10 * width
|
||||
|
||||
# kernel not wider than time
|
||||
if 2 * tmax > time[-1] - time[0]:
|
||||
tmax = 0.5 * (time[-1] - time[0])
|
||||
|
||||
# kernel time
|
||||
ktime = np.arange(-tmax, tmax, dt)
|
||||
|
||||
# gamma kernel centered in ktime:
|
||||
kernel = gamma.pdf(
|
||||
x=ktime,
|
||||
a=shape,
|
||||
loc=0,
|
||||
scale=width,
|
||||
)
|
||||
|
||||
# indices of spikes in time array:
|
||||
indices = np.asarray((spikes - time[0]) / dt, dtype=int)
|
||||
|
||||
# binary spike train:
|
||||
brate = np.zeros(len(time))
|
||||
brate[indices[(indices >= 0) & (indices < len(time))]] = 1.0
|
||||
|
||||
# convolution with kernel:
|
||||
rate = np.convolve(brate, kernel, mode="same")
|
||||
|
||||
return rate
|
||||
|
||||
|
||||
def acausal_kde1d(spikes, time, width):
|
||||
"""
|
||||
causalkde computes a kernel density estimate using a causal kernel (i.e. exponential or gamma distribution).
|
||||
A shape of 1 turns the gamma distribution into an exponential.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
spikes : array-like
|
||||
spike times
|
||||
time : array-like
|
||||
sampling time
|
||||
width : float
|
||||
kernel width
|
||||
shape : int, optional
|
||||
shape of gamma distribution, by default 1
|
||||
|
||||
Returns
|
||||
-------
|
||||
rate : array-like
|
||||
instantaneous firing rate
|
||||
"""
|
||||
|
||||
# compute dt
|
||||
dt = time[1] - time[0]
|
||||
|
||||
# time on which to compute kernel:
|
||||
tmax = 10 * width
|
||||
|
||||
# kernel not wider than time
|
||||
if 2 * tmax > time[-1] - time[0]:
|
||||
tmax = 0.5 * (time[-1] - time[0])
|
||||
|
||||
# kernel time
|
||||
ktime = np.arange(-tmax, tmax, dt)
|
||||
|
||||
# gamma kernel centered in ktime:
|
||||
kernel = norm.pdf(
|
||||
x=ktime,
|
||||
loc=0,
|
||||
scale=width,
|
||||
)
|
||||
|
||||
# indices of spikes in time array:
|
||||
indices = np.asarray((spikes - time[0]) / dt, dtype=int)
|
||||
|
||||
# binary spike train:
|
||||
brate = np.zeros(len(time))
|
||||
brate[indices[(indices >= 0) & (indices < len(time))]] = 1.0
|
||||
|
||||
# convolution with kernel:
|
||||
rate = np.convolve(brate, kernel, mode="same")
|
||||
|
||||
return rate
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
timestamps = [
|
||||
|
@ -3,8 +3,8 @@ import numpy as np
|
||||
|
||||
|
||||
def bandpass_filter(
|
||||
data: np.ndarray,
|
||||
rate: float,
|
||||
signal: np.ndarray,
|
||||
samplerate: float,
|
||||
lowf: float,
|
||||
highf: float,
|
||||
) -> np.ndarray:
|
||||
@ -12,7 +12,7 @@ def bandpass_filter(
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : np.ndarray
|
||||
signal : np.ndarray
|
||||
The data to be filtered
|
||||
rate : float
|
||||
The sampling rate
|
||||
@ -26,21 +26,22 @@ def bandpass_filter(
|
||||
np.ndarray
|
||||
The filtered data
|
||||
"""
|
||||
sos = butter(2, (lowf, highf), "bandpass", fs=rate, output="sos")
|
||||
fdata = sosfiltfilt(sos, data)
|
||||
return fdata
|
||||
sos = butter(2, (lowf, highf), "bandpass", fs=samplerate, output="sos")
|
||||
filtered_signal = sosfiltfilt(sos, signal)
|
||||
|
||||
return filtered_signal
|
||||
|
||||
|
||||
def highpass_filter(
|
||||
data: np.ndarray,
|
||||
rate: float,
|
||||
signal: np.ndarray,
|
||||
samplerate: float,
|
||||
cutoff: float,
|
||||
) -> np.ndarray:
|
||||
"""Highpass filter a signal.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : np.ndarray
|
||||
signal : np.ndarray
|
||||
The data to be filtered
|
||||
rate : float
|
||||
The sampling rate
|
||||
@ -52,14 +53,15 @@ def highpass_filter(
|
||||
np.ndarray
|
||||
The filtered data
|
||||
"""
|
||||
sos = butter(2, cutoff, "highpass", fs=rate, output="sos")
|
||||
fdata = sosfiltfilt(sos, data)
|
||||
return fdata
|
||||
sos = butter(2, cutoff, "highpass", fs=samplerate, output="sos")
|
||||
filtered_signal = sosfiltfilt(sos, signal)
|
||||
|
||||
return filtered_signal
|
||||
|
||||
|
||||
def lowpass_filter(
|
||||
data: np.ndarray,
|
||||
rate: float,
|
||||
signal: np.ndarray,
|
||||
samplerate: float,
|
||||
cutoff: float
|
||||
) -> np.ndarray:
|
||||
"""Lowpass filter a signal.
|
||||
@ -78,21 +80,25 @@ def lowpass_filter(
|
||||
np.ndarray
|
||||
The filtered data
|
||||
"""
|
||||
sos = butter(2, cutoff, "lowpass", fs=rate, output="sos")
|
||||
fdata = sosfiltfilt(sos, data)
|
||||
return fdata
|
||||
sos = butter(2, cutoff, "lowpass", fs=samplerate, output="sos")
|
||||
filtered_signal = sosfiltfilt(sos, signal)
|
||||
|
||||
return filtered_signal
|
||||
|
||||
def envelope(data: np.ndarray, rate: float, freq: 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
|
||||
----------
|
||||
data : np.ndarray
|
||||
signal : np.ndarray
|
||||
The signal to calculate the envelope of
|
||||
rate : float
|
||||
samplingrate : float
|
||||
The sampling rate of the signal
|
||||
freq : float
|
||||
cutoff_frequency : float
|
||||
The cutoff frequency of the lowpass filter
|
||||
|
||||
Returns
|
||||
@ -100,6 +106,7 @@ def envelope(data: np.ndarray, rate: float, freq: float) -> np.ndarray:
|
||||
np.ndarray
|
||||
The envelope of the signal
|
||||
"""
|
||||
sos = butter(2, freq, "lowpass", fs=rate, output="sos")
|
||||
envelope = np.sqrt(2) * sosfiltfilt(sos, np.abs(data))
|
||||
sos = butter(2, cutoff_frequency, "lowpass", fs=samplerate, output="sos")
|
||||
envelope = np.sqrt(2) * sosfiltfilt(sos, np.abs(signal))
|
||||
|
||||
return envelope
|
||||
|
@ -30,10 +30,14 @@ def PlotStyle() -> None:
|
||||
purple = "#cba6f7"
|
||||
pink = "#f5c2e7"
|
||||
lavender = "#b4befe"
|
||||
gblue1 = "#8cb8ff"
|
||||
gblue2 = "#7cdcdc"
|
||||
gblue3 = "#82e896"
|
||||
|
||||
@classmethod
|
||||
def lims(cls, track1, track2):
|
||||
"""Helper function to get frequency y axis limits from two fundamental frequency tracks.
|
||||
"""Helper function to get frequency y axis limits from two
|
||||
fundamental frequency tracks.
|
||||
|
||||
Args:
|
||||
track1 (array): First track
|
||||
@ -91,6 +95,16 @@ def PlotStyle() -> None:
|
||||
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)
|
||||
@ -216,8 +230,8 @@ def PlotStyle() -> None:
|
||||
plt.rc("figure", titlesize=BIGGER_SIZE) # fontsize of the figure title
|
||||
|
||||
plt.rcParams["image.cmap"] = 'cmo.haline'
|
||||
# plt.rcParams["axes.xmargin"] = 0.1
|
||||
# plt.rcParams["axes.ymargin"] = 0.15
|
||||
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
|
||||
@ -230,9 +244,9 @@ def PlotStyle() -> None:
|
||||
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"
|
||||
# # 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
|
||||
@ -271,7 +285,7 @@ def PlotStyle() -> None:
|
||||
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"] = "#555169" # figure edge color
|
||||
plt.rcParams["figure.edgecolor"] = black # figure edge color
|
||||
plt.rcParams["savefig.facecolor"] = black # figure face color when saving
|
||||
|
||||
return style
|
||||
|
121
code/plot_introduction_specs.py
Normal file
121
code/plot_introduction_specs.py
Normal file
@ -0,0 +1,121 @@
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from thunderfish.powerspectrum import spectrogram, decibel
|
||||
|
||||
from modules.filehandling import LoadData
|
||||
from modules.datahandling import instantaneous_frequency
|
||||
from modules.filters import bandpass_filter
|
||||
from modules.plotstyle import PlotStyle
|
||||
|
||||
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.25
|
||||
window_start_index = window_start_seconds * data.raw_rate
|
||||
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]
|
||||
|
||||
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)
|
||||
filtered2 = bandpass_filter(
|
||||
signal=raw, lowf=550, highf=700, samplerate=data.raw_rate)
|
||||
|
||||
freqtime1, freq1 = instantaneous_frequency(
|
||||
filtered1, data.raw_rate, smoothing_window=3)
|
||||
freqtime2, freq2 = instantaneous_frequency(
|
||||
filtered2, data.raw_rate, smoothing_window=3)
|
||||
|
||||
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(
|
||||
raw,
|
||||
ratetime=data.raw_rate,
|
||||
freq_resolution=150,
|
||||
overlap_frac=0.2,
|
||||
)
|
||||
|
||||
ylims = [300, 1200]
|
||||
fmask = np.zeros(spec_freqs.shape, dtype=bool)
|
||||
fmask[(spec_freqs > ylims[0]) & (spec_freqs < ylims[1])] = True
|
||||
|
||||
ax2.imshow(
|
||||
decibel(spec_power[fmask, :]),
|
||||
extent=[
|
||||
spec_times[0]*timescaler,
|
||||
spec_times[-1]*timescaler,
|
||||
spec_freqs[fmask][0],
|
||||
spec_freqs[fmask][-1],
|
||||
],
|
||||
aspect="auto",
|
||||
origin="lower",
|
||||
interpolation="gaussian",
|
||||
alpha=1,
|
||||
)
|
||||
ps.hide_xax(ax2)
|
||||
|
||||
# plot coarse spectrogram
|
||||
spec_power, spec_freqs, spec_times = spectrogram(
|
||||
raw,
|
||||
ratetime=data.raw_rate,
|
||||
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
|
||||
ax3.imshow(
|
||||
decibel(spec_power[fmask, :]),
|
||||
extent=[
|
||||
spec_times[0]*timescaler,
|
||||
spec_times[-1]*timescaler,
|
||||
spec_freqs[fmask][0],
|
||||
spec_freqs[fmask][-1],
|
||||
],
|
||||
aspect="auto",
|
||||
origin="lower",
|
||||
interpolation="gaussian",
|
||||
alpha=1,
|
||||
)
|
||||
# ps.hide_xax(ax3)
|
||||
|
||||
ax3.set_xlabel("time [ms]")
|
||||
ax2.set_ylabel("frequency [Hz]")
|
||||
|
||||
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.show()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
BIN
poster/figs/Untitled.png
Normal file
BIN
poster/figs/Untitled.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 116 KiB |
BIN
poster/figs/algorithm.pdf
Normal file
BIN
poster/figs/algorithm.pdf
Normal file
Binary file not shown.
BIN
poster/figs/introplot.pdf
Normal file
BIN
poster/figs/introplot.pdf
Normal file
Binary file not shown.
BIN
poster/figs/logo.png
Normal file
BIN
poster/figs/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
1184
poster/figs/logo.svg
Normal file
1184
poster/figs/logo.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 84 KiB |
BIN
poster/figs/placeholder1.png
Normal file
BIN
poster/figs/placeholder1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 157 KiB |
BIN
poster/main.pdf
BIN
poster/main.pdf
Binary file not shown.
108
poster/main.tex
108
poster/main.tex
@ -7,65 +7,101 @@ blockverticalspace=2mm, colspace=20mm, subcolspace=0mm]{tikzposter} %Default val
|
||||
\begin{document}
|
||||
|
||||
\renewcommand{\baselinestretch}{1}
|
||||
\title{\parbox{1900pt}{A dark template to make colorful figures pop}}
|
||||
\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}
|
||||
\institute{Supervised by Till Raab \& Jan Benda, Neurothology Group,
|
||||
University of Tübingen}
|
||||
\usetitlestyle[]{sampletitle}
|
||||
\maketitle
|
||||
\renewcommand{\baselinestretch}{1.4}
|
||||
|
||||
\begin{columns}
|
||||
\column{0.3}
|
||||
\column{0.5}
|
||||
\myblock[TranspBlock]{Introduction}{
|
||||
\lipsum[1][1-5]
|
||||
\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{griddrawing}
|
||||
\includegraphics[width=\linewidth]{example-image-a}
|
||||
\label{tradeoff}
|
||||
\includegraphics[width=\linewidth]{figs/introplot}
|
||||
\end{tikzfigure}
|
||||
\end{minipage}
|
||||
}
|
||||
|
||||
\myblock[TranspBlock]{Methods}{
|
||||
\begin{tikzfigure}[]
|
||||
\label{detector}
|
||||
\includegraphics[width=\linewidth]{example-image-b}
|
||||
\end{tikzfigure}
|
||||
\myblock[TranspBlock]{A chirp detection algorithm}{
|
||||
\begin{tikzfigure}[]
|
||||
\label{modulations}
|
||||
\includegraphics[width=\linewidth]{figs/algorithm}
|
||||
\end{tikzfigure}
|
||||
}
|
||||
|
||||
\column{0.4}
|
||||
\myblock[TranspBlock]{Results}{
|
||||
\lipsum[3][1-5]
|
||||
\column{0.5}
|
||||
\myblock[TranspBlock]{Chirps and diadic competitions}{
|
||||
\begin{minipage}[t]{0.7\linewidth}
|
||||
\begin{tikzfigure}[]
|
||||
\label{modulations}
|
||||
\includegraphics[width=\linewidth]{example-image-c}
|
||||
\includegraphics[width=\linewidth]{figs/placeholder1}
|
||||
\end{tikzfigure}
|
||||
}
|
||||
\end{minipage} \hfill
|
||||
\begin{minipage}[t]{0.25\linewidth}
|
||||
\lipsum[3][1-3]
|
||||
\end{minipage}
|
||||
|
||||
\myblock[TranspBlock]{More Stuff}{
|
||||
\lipsum[3][1-9]
|
||||
}
|
||||
\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}
|
||||
|
||||
\column{0.3}
|
||||
\myblock[TranspBlock]{More Results}{
|
||||
\begin{minipage}[t]{0.7\linewidth}
|
||||
\begin{tikzfigure}[]
|
||||
\label{results}
|
||||
\includegraphics[width=\linewidth]{example-image-a}
|
||||
\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{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}
|
||||
}
|
||||
\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[
|
||||
@ -78,6 +114,6 @@ blockverticalspace=2mm, colspace=20mm, subcolspace=0mm]{tikzposter} %Default val
|
||||
fill=boxes,
|
||||
color=boxes,
|
||||
] at (-0.51\paperwidth,-43.5) {
|
||||
\textcolor{text}{\normalsize Contact: name.surname@student.uni-tuebingen.de}};
|
||||
\textcolor{text}{\normalsize Contact: \{name\}.\{surname\}@student.uni-tuebingen.de}};
|
||||
|
||||
\end{document}
|
||||
|
Loading…
Reference in New Issue
Block a user