Skip to content

Commit

Permalink
Merge pull request #2 from CINPLA/modernize
Browse files Browse the repository at this point in the history
Modernize and release
  • Loading branch information
alejoe91 authored Nov 28, 2024
2 parents 3109909 + ebb2c57 commit 903bb23
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 71 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/full_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Test on Ubuntu

on:
pull_request:
branches: [dev]
types: [synchronize, opened, reopened]


jobs:
build-and-test:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install package
run: |
python -m pip install --upgrade pip
pip install .[test]
- name: Pytest
run: |
pytest -v
28 changes: 28 additions & 0 deletions .github/workflows/publish-to-pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Release to PyPI

on:
push:
tags:
- '*'
jobs:
release:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install Tools
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine build
- name: Package and Upload
env:
STACKMANAGER_VERSION: ${{ github.event.release.tag_name }}
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
python -m build --sdist --wheel
twine upload dist/*
32 changes: 32 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: fix-encoding-pragma
exclude: tests/test_data
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-docstring-first
- id: debug-statements
- id: check-toml
- id: check-yaml
- id: requirements-txt-fixer
- id: detect-private-key
- id: check-merge-conflict

- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
- id: black-jupyter

- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
args: ["--profile", "black"]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
hooks:
- id: ruff
1 change: 0 additions & 1 deletion head_direction/__init__.py

This file was deleted.

59 changes: 59 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
[project]
name = "head_direction"
version = "0.1.0"
authors = [
{ name = "Mikkel Lepperod", email = "[email protected]" },
{ name = "Alessio Buccino", email = "[email protected]" },
]

description = "Analysis of head direction cells."
readme = "README.md"
requires-python = ">=3.10"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]

dependencies = [
"numpy<2",
"scipy",
"astropy",
"pycircstat",
"pandas",
"elephant",
"matplotlib",
"nose"
]

[project.urls]
homepage = "https://github.com/CINPLA/head-directopm"
repository = "https://github.com/CINPLA/head-direction"

[build-system]
requires = ["setuptools>=62.0"]
build-backend = "setuptools.build_meta"

[tool.setuptools]
include-package-data = true

[tool.setuptools.packages.find]
where = ["src"]
include = ["head_direction*"]
namespaces = false

[project.optional-dependencies]
dev = ["pre-commit", "black[jupyter]", "isort", "ruff"]
test = ["pytest", "pytest-cov", "pytest-dependency", "mountainsort5"]
docs = ["sphinx-gallery", "sphinx_rtd_theme"]
full = [
"head_direction[dev]",
"head_direction[test]",
"head_direction[docs]",
]

[tool.coverage.run]
omit = ["tests/*"]

[tool.black]
line-length = 120
31 changes: 3 additions & 28 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,6 @@
# -*- coding: utf-8 -*-
from setuptools import setup
import os

from setuptools import setup, find_packages
import setuptools

long_description = open("README.md").read()

install_requires = [
'numpy>=1.9',
'scipy',
'astropy',
'pandas>=0.14.1',
'elephant',
'matplotlib']
extras_require = {
'testing': ['pytest'],
'docs': ['numpydoc>=0.5',
'sphinx>=1.2.2',
'sphinx_rtd_theme']
}

setup(
name="head_direction",
install_requires=install_requires,
tests_require=install_requires,
extras_require=extras_require,
packages=find_packages(),
include_package_data=True,
version='0.1',
)
if __name__ == "__main__":
setuptools.setup()
4 changes: 4 additions & 0 deletions src/head_direction/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from .head import head_direction as head_direction
from .head import head_direction_rate as head_direction_rate
from .head import head_direction_score as head_direction_score
13 changes: 7 additions & 6 deletions head_direction/head.py → src/head_direction/head.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
import numpy as np

from .tools import moving_average


def head_direction_rate(spike_train, head_angles, t,
n_bins=36, avg_window=4):
def head_direction_rate(spike_train, head_angles, t, n_bins=36, avg_window=4):
"""
Calculeate firing rate at head direction in binned head angles for time t.
Moving average filter is applied on firing rate
Expand Down Expand Up @@ -37,7 +38,7 @@ def head_direction_rate(spike_train, head_angles, t,
spikes_in_ang, _ = np.histogram(head_angles, weights=spikes_in_bin, bins=ang_bins)
time_in_ang, _ = np.histogram(head_angles, weights=time_in_bin, bins=ang_bins)

with np.errstate(divide='ignore', invalid='ignore'):
with np.errstate(divide="ignore", invalid="ignore"):
rate_in_ang = np.divide(spikes_in_ang, time_in_ang)
rate_in_ang = moving_average(rate_in_ang, avg_window)
return ang_bins[:-1], rate_in_ang
Expand All @@ -59,8 +60,8 @@ def head_direction_score(head_angle_bins, rate):
out : float, float
mean angle, mean vector length
"""
import math
import pycircstat as pc

nanIndices = np.where(np.isnan(rate))
head_angle_bins = np.delete(head_angle_bins, nanIndices)
mean_ang = pc.mean(head_angle_bins, w=rate)
Expand All @@ -69,7 +70,7 @@ def head_direction_score(head_angle_bins, rate):
return mean_ang, mean_vec_len


def head_direction(x1, y1, x2, y2, t, filt=2.):
def head_direction(x1, y1, x2, y2, t, filt=2.0):
"""
Calculeate head direction in angles or radians for time t
Expand Down Expand Up @@ -98,7 +99,7 @@ def head_direction(x1, y1, x2, y2, t, filt=2.):
r = np.linalg.norm(dr, axis=0)
r_mean = np.mean(r)
r_std = np.std(r)
mask = (r > r_mean - filt*r_std)
mask = r > r_mean - filt * r_std
x1 = x1[mask]
y1 = y1[mask]
x2 = x2[mask]
Expand Down
8 changes: 4 additions & 4 deletions head_direction/tools.py → src/head_direction/tools.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
import numpy as np
import scipy.signal as sig


def unit_vector(v):
""" Return unit vector of v
"""Return unit vector of v
modified from David Wolever,
https://stackoverflow.com/questions/2827393/angles
-between-two-n-dimensional-vectors-in-python
Expand Down Expand Up @@ -47,11 +47,11 @@ def moving_average(vector, N):
if N * 2 > len(vector):
raise ValueError('Window must be at least half of "len(vector)"')
vector = np.concatenate((vector[-N:], vector, vector[:N]))
return np.convolve(vector, np.ones((N,)) / N, mode='same')[N:-N]
return np.convolve(vector, np.ones((N,)) / N, mode="same")[N:-N]


def angle_between_vectors(v1, v2):
""" Returns the angle in radians between vectors 'v1' and 'v2'
"""Returns the angle in radians between vectors 'v1' and 'v2'
modified from David Wolever,
https://stackoverflow.com/questions/2827393/angles
-between-two-n-dimensional-vectors-in-python
Expand Down
63 changes: 31 additions & 32 deletions head_direction/tests/test_head.py → tests/test_head.py
Original file line number Diff line number Diff line change
@@ -1,75 +1,74 @@
import pytest
# -*- coding: utf-8 -*-
import numpy as np
import math

from head_direction.head import (
head_direction,
head_direction_rate,
head_direction_score,
)


def test_head_direction_45():
from head_direction.head import head_direction
x1 = np.linspace(.01,1,10)
x1 = np.linspace(0.01, 1, 10)
y1 = x1
x2 = x1 + .01 # 1cm between
y2 = x1 - .01
x2 = x1 + 0.01 # 1cm between
y2 = x1 - 0.01
t = x1
a, t = head_direction(x1, y1, x2, y2, t)
assert np.allclose(a, np.pi / 4)


def test_head_direction_135():
from head_direction.head import head_direction
x1 = np.linspace(.01,1,10)[::-1]
x1 = np.linspace(0.01, 1, 10)[::-1]
y1 = x1[::-1]
x2 = x1 - .01 # 1cm between
y2 = y1 - .01
x2 = x1 - 0.01 # 1cm between
y2 = y1 - 0.01
t = x1
a, t = head_direction(x1, y1, x2, y2, t)
assert np.allclose(a, np.pi - np.pi / 4)


def test_head_direction_225():
from head_direction.head import head_direction
x1 = np.linspace(.01,1,10)[::-1]
x1 = np.linspace(0.01, 1, 10)[::-1]
y1 = x1
x2 = x1 - .01 # 1cm between
y2 = y1 + .01
x2 = x1 - 0.01 # 1cm between
y2 = y1 + 0.01
t = x1
a, t = head_direction(x1, y1, x2, y2, t)
assert np.allclose(a, np.pi + np.pi / 4)


def test_head_direction_reverse_315():
from head_direction.head import head_direction
x1 = np.linspace(.01,1,10)
x1 = np.linspace(0.01, 1, 10)
y1 = x1[::-1]
x2 = x1 + .01 # 1cm between
y2 = y1 + .01
x2 = x1 + 0.01 # 1cm between
y2 = y1 + 0.01
t = x1
a, t = head_direction(x1, y1, x2, y2, t)
assert np.allclose(a, 2 * np.pi - np.pi / 4)


def test_head_rate():
from head_direction.head import head_direction, head_direction_rate
x1 = np.linspace(.01,1,10)
x1 = np.linspace(0.01, 1, 10)
y1 = x1
x2 = x1 + .01 # 1cm between
y2 = x1 - .01
t = np.linspace(0,1,10)
x2 = x1 + 0.01 # 1cm between
y2 = x1 - 0.01
t = np.linspace(0, 1, 10)
a, t = head_direction(x1, y1, x2, y2, t)
sptr = np.linspace(0,1,100)
sptr = np.linspace(0, 1, 100)
bins, rate = head_direction_rate(sptr, a, t, n_bins=8, avg_window=1)
assert bins[1] == np.pi / 4
assert abs(rate[1] - 100) < .5
assert abs(rate[1] - 100) < 0.5


def test_head_score():
from head_direction.head import (
head_direction, head_direction_rate, head_direction_score)
x1 = np.linspace(.01,1,10)
x1 = np.linspace(0.01, 1, 10)
y1 = x1
x2 = x1 + .01 # 1cm between
y2 = x1 - .01
t = np.linspace(0,1,10)
x2 = x1 + 0.01 # 1cm between
y2 = x1 - 0.01
t = np.linspace(0, 1, 10)
a, t = head_direction(x1, y1, x2, y2, t)
sptr = np.linspace(0,1,100)
sptr = np.linspace(0, 1, 100)
bins, rate = head_direction_rate(sptr, a, t, n_bins=100, avg_window=2)
ang, score = head_direction_score(bins, rate)
assert abs(score - 1) < 0.001
Expand Down

0 comments on commit 903bb23

Please sign in to comment.