Skip to content

Commit

Permalink
Merge pull request #390 from pynapple-org/bump_0.8
Browse files Browse the repository at this point in the history
Bump 0.8
  • Loading branch information
gviejo authored Jan 15, 2025
2 parents 0688345 + bb41b89 commit 86805f4
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 27 deletions.
8 changes: 1 addition & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
<!-- ![pic1](banner_logo.png) -->
<p align="center">
<img width="60%" src="docs/images/banner_logo.png">
</p>


<!-- ========================== -->

[![image](https://img.shields.io/pypi/v/pynapple.svg)](https://pypi.python.org/pypi/pynapple)
[![pynapple CI](https://github.com/pynapple-org/pynapple/actions/workflows/main.yml/badge.svg)](https://github.com/pynapple-org/pynapple/actions/workflows/main.yml)
Expand Down Expand Up @@ -157,7 +151,7 @@ Shown below, the final figure from the example code displays the firing rate of

<!-- ![pic1](readme_figure.png) -->
<p align="center">
<img width="80%" src="docs/images/readme_figure.png">
<img width="80%" src="doc/_static/readme_figure.png">
</p>


Expand Down
Binary file added doc/_static/readme_figure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions doc/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ of the Flatiron institute.

## Releases

### 0.8.0 (2025-01-15)

- New private class: `_MetadataMixin` (core/metadata_class.py). Can be inherited by `IntervalSet`, `TsdFrame` and `TsGroup`.
- `decode_1d` and `decode_2d` now accepts `TsdFrame` as input.

### 0.7.1 (2024-09-24)

- Fixing nan issue when computing 1d tuning curve (See issue #334).
Expand Down
4 changes: 3 additions & 1 deletion doc/user_guide/11_wavelets.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
custom_params = {"axes.spines.right": False, "axes.spines.top": False}
sns.set_theme(style="ticks", palette="colorblind", font_scale=1.5, rc=custom_params)
sns.set_theme(style="ticks", palette="colorblind", font_scale=1.0, rc=custom_params)
```

***
Expand Down Expand Up @@ -121,6 +121,7 @@ underlying wavelets.
window_lengths = [1.0, 3.0]
gaussian_widths = [1.0, 3.0]
colors = np.array([["r", "g"], ["b", "y"]])
fig = plt.figure()
fig, ax = plt.subplots(
len(window_lengths) + 1,
len(gaussian_widths) + 1,
Expand Down Expand Up @@ -153,6 +154,7 @@ for i in range(len(gaussian_widths)):
ax[i, -1].set(
xlim=(0, 2), yticks=[], ylabel="Frequency Response", xlabel="Frequency (Hz)"
)
```

Increasing `window_length` increases the number of wavelet cycles present in the oscillations (cycles), and
Expand Down
2 changes: 1 addition & 1 deletion pynapple/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.7.1"
__version__ = "0.8.0"
from .core import (
IntervalSet,
Ts,
Expand Down
90 changes: 90 additions & 0 deletions pynapple/core/time_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,47 @@ class TsdTensor(_BaseTsd):
Frequency of the time series (Hz) computed over the time support
time_support : IntervalSet
The time support of the time series
Examples
--------
Initialize a TsdTensor:
>>> import pynapple as nap
>>> import numpy as np
>>> t = np.arange(10)
>>> d = np.random.randn(10, 2, 3)
>>> tsdtensor = nap.TsdTensor(t=t, d=d)
>>> tsdtensor
Time (s)
---------- -------------------------------
0 [[-1.493178 ... -1.281017] ...]
1 [[0.230829 ... 0.437679] ...]
2 [[-0.462031 ... 0.344506] ...]
3 [[0.497019 ... 0.469494] ...]
4 [[0.065921 ... 1.012917] ...]
5 [[0.158534 ... 1.455523] ...]
6 [[-2.567728 ... 0.61182 ] ...]
7 [[0.940799 ... 0.109203] ...]
8 [[2.340077 ... 0.21885 ] ...]
9 [[-0.306175 ... -0.447414] ...]
dtype: float64, shape: (10, 2, 3)
Initialize a TsdTensor with `time_support`:
>>> t = np.arange(10)
>>> d = np.random.randn(10, 2, 3)
>>> time_support = nap.IntervalSet(start=0, end=4)
>>> tsdtensor = nap.TsdTensor(t=t, d=d, time_support=time_support)
>>> tsdtensor
Time (s)
---------- -------------------------------
0 [[-1.493178 ... -1.281017] ...]
1 [[0.230829 ... 0.437679] ...]
2 [[-0.462031 ... 0.344506] ...]
3 [[0.497019 ... 0.469494] ...]
4 [[0.065921 ... 1.012917] ...]
dtype: float64, shape: (5, 2, 3)
"""

def __init__(
Expand Down Expand Up @@ -1519,6 +1560,55 @@ class Tsd(_BaseTsd):
Frequency of the time series (Hz) computed over the time support
time_support : IntervalSet
The time support of the time series
Examples
--------
Initialize a Tsd:
>>> import pynapple as nap
>>> import numpy as np
>>> t = np.arange(100)
>>> d = np.ones(100)
>>> tsd = nap.Tsd(t=t, d=d)
>>> tsd
Time (s)
---------- --
0.0 1
1.0 1
2.0 1
3.0 1
4.0 1
5.0 1
6.0 1
...
93.0 1
94.0 1
95.0 1
96.0 1
97.0 1
98.0 1
99.0 1
dtype: float64, shape: (100,)
Initialize a Tsd with `time_support`:
>>> t = np.arange(100)
>>> d = np.ones(100)
>>> time_support = nap.IntervalSet(start=0.5, end=8)
>>> tsd = nap.Tsd(t=t, d=d, time_support=time_support)
>>> tsd
Time (s)
---------- --
1 1
2 1
3 1
4 1
5 1
6 1
7 1
8 1
dtype: float64, shape: (8,)
"""

def __init__(
Expand Down
13 changes: 7 additions & 6 deletions pynapple/process/wavelets.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def compute_wavelet_transform(
if fs is None:
fs = sig.rate

output_shape = (sig.shape[0], len(freqs), *sig.shape[1:])
output_shape = (sig.shape[0], *sig.shape[1:], len(freqs))
sig = np.reshape(sig, (sig.shape[0], -1))

filter_bank = generate_morlet_filterbank(
Expand All @@ -122,16 +122,17 @@ def compute_wavelet_transform(
coef = convolved / (fs / np.sqrt(freqs))
else:
coef = convolved

cwt = np.expand_dims(coef, -1) if len(coef.shape) == 2 else coef

if len(output_shape) == 2:
return nap.TsdFrame(
t=sig.index, d=cwt.reshape(output_shape), time_support=sig.time_support
t=sig.index, d=np.squeeze(cwt, axis=1), time_support=sig.time_support
)
else:
return nap.TsdTensor(
t=sig.index, d=np.reshape(cwt, output_shape), time_support=sig.time_support
)

return nap.TsdTensor(
t=sig.index, d=cwt.reshape(output_shape), time_support=sig.time_support
)


def generate_morlet_filterbank(
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "pynapple"
version = "0.7.1"
version = "0.8.0"
description = "PYthon Neural Analysis Package Pour Laboratoires d’Excellence"
readme = "README.md"
authors = [{ name = "Guillaume Viejo", email = "[email protected]" }]
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/pynapple-org/pynapple',
version='v0.7.1',
version='v0.8.0',
zip_safe=False,
long_description_content_type='text/markdown',
download_url='https://github.com/pynapple-org/pynapple/archive/refs/tags/v0.7.1.tar.gz'
download_url='https://github.com/pynapple-org/pynapple/archive/refs/tags/v0.8.0.tar.gz'
)
46 changes: 37 additions & 9 deletions tests/test_signal_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,17 @@ def get_1d_signal(fs=1000, fc=50):
def get_2d_signal(fs=1000, fc=50):
t = np.arange(0, 2, 1 / fs)
d = np.sin(t * fc * np.pi * 2) * np.interp(t, [0, 1, 2], [0, 1, 0])
return nap.TsdFrame(t, d[:, np.newaxis], time_support=nap.IntervalSet(0, 2))
return nap.TsdFrame(
t, np.repeat(d[:, np.newaxis], 2, axis=1), time_support=nap.IntervalSet(0, 2)
)


def get_3d_signal(fs=1000, fc=50):
t = np.arange(0, 2, 1 / fs)
d = np.sin(t * fc * np.pi * 2) * np.interp(t, [0, 1, 2], [0, 1, 0])
d = d[:, np.newaxis, np.newaxis]
d = np.repeat(np.repeat(d, 2, axis=1), 3, axis=2)
return nap.TsdTensor(t, d, time_support=nap.IntervalSet(0, 2))


def get_output_1d(sig, wavelets):
Expand All @@ -308,15 +318,30 @@ def get_output_1d(sig, wavelets):
def get_output_2d(sig, wavelets):
T, K = sig.shape
M, N = wavelets.shape
out = []
for k in range(K):
tmp = []
for n in range(N):
tmp.append(np.convolve(sig[:, k], wavelets[:, n], mode="full"))
out.append(np.array(tmp))
out = np.array(out).T
cut = ((M - 1) // 2, T + M - 1 - ((M - 1) // 2) - (1 - M % 2))
return out[cut[0] : cut[1]]
out = np.zeros((T, K, N), dtype=np.complex128)
for n in range(N): # wavelet
for k in range(K):
out[:, k, n] = np.convolve(sig[:, k], wavelets[:, n], mode="full")[
cut[0] : cut[1]
]

return out


def get_output_3d(sig, wavelets):
T, K, L = sig.shape
M, N = wavelets.shape
cut = ((M - 1) // 2, T + M - 1 - ((M - 1) // 2) - (1 - M % 2))
out = np.zeros((T, K, L, N), dtype=np.complex128)
for n in range(N): # wavelet
for k in range(K):
for l in range(L):
out[:, k, l, n] = np.convolve(
sig[:, k, l], wavelets[:, n], mode="full"
)[cut[0] : cut[1]]

return out


@pytest.mark.parametrize(
Expand All @@ -331,6 +356,7 @@ def get_output_2d(sig, wavelets):
(get_1d_signal, np.linspace(10, 100, 10), 1000, 1.5, 1.0, 16, "l2", 50, 1000),
(get_1d_signal, np.linspace(10, 100, 10), 1000, 1.5, 1.0, 16, None, 20, 1000),
(get_2d_signal, np.linspace(10, 100, 10), 1000, 1.5, 1.0, 16, None, 20, 1000),
(get_3d_signal, np.linspace(10, 100, 10), 1000, 1.5, 1.0, 16, None, 20, 1000),
],
)
def test_compute_wavelet_transform(
Expand All @@ -344,6 +370,8 @@ def test_compute_wavelet_transform(
output = get_output_1d(sig.d, wavelets.values)
if sig.ndim == 2:
output = get_output_2d(sig.d, wavelets.values)
if sig.ndim == 3:
output = get_output_3d(sig.d, wavelets.values)

if norm == "l1":
output = output / (1000 / freqs)
Expand Down

0 comments on commit 86805f4

Please sign in to comment.