Skip to content

Commit

Permalink
Brainbox (int-brain-lab#103)
Browse files Browse the repository at this point in the history
* env file for conda, more TimeSeries docs, misc

* bincount2D import fix

* WIP: xcorr and acorr functions

* Sig change for bb.processing.sync to take times/vals first

* Create raster_cluster_ordered.py

* first pass on plotting psths

* Added conda env files for core packages and complete dev including jupyter etc

* Modified env.yml files to contain only minimal requirements. Other sub-dependencies will be resolved by conda

* env file for conda, more TimeSeries docs, misc

* bincount2D import fix

* WIP: xcorr and acorr functions

* Create raster_cluster_ordered.py

* first pass on plotting psths

* Added conda env files for core packages and complete dev including jupyter etc

* Modified env.yml files to contain only minimal requirements. Other sub-dependencies will be resolved by conda

* renamed conda env

* minimalized env .yml for cross-platform use

* typo fix

* flake

* modelbox first commit

* cleanup filenames

* readme

* oops

* Added plot module to brainbox. Currently empty.

* Flakify and make unittest pass
  • Loading branch information
oliche authored Sep 26, 2019
1 parent 24c4df8 commit bb267ba
Show file tree
Hide file tree
Showing 18 changed files with 769 additions and 80 deletions.
16 changes: 6 additions & 10 deletions brainbox/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ We suggest using [Anaconda](https://www.anaconda.com/distribution/), which is de
Once you have installed Anaconda, the next step is to create an environment for working with brainbox. This requires you to have the `environment.yml` file which lives in the top directory of this repository. We will just clone the whole repository now though, since you will need it later, using the following command on *nix systems:

```bash
git clone https://github.com/int-brain-lab/brainbox
git clone https://github.com/int-brain-lab/ibllib/
cd ./ibllib
git checkout brainbox
```

Note: please navigate to the folder where you want to run this command beforehand, e.g. `/home/username/Documents` if you want the `brainbox` repository to live in your Documents folder
Expand All @@ -47,7 +49,7 @@ if you are developing from the terminal, in order to activate the environment yo

# Git, GitFlow, and you (15 minutes)

**TL;DR: We use [Git](https://rogerdudler.github.io/git-guide/) with a [GitFlow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) workflow to develop Brainbox. Please create new feature branches of `develop` for writing code and then make a pull request to have it added to `develop`.**
**TL;DR: We use [Git](https://rogerdudler.github.io/git-guide/) with a [GitFlow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) workflow to develop Brainbox. Please create new feature branches of `brainbox` for writing code and then make a pull request to have it added to `brainbox`.**

For those unfamiliar with it, Git is a system for *version control*, which allows you to make changes to whatever you put into it (Git isn't limited to just code!) that are:

Expand All @@ -64,17 +66,11 @@ This way you can keep a version of your code that you know works (called `master

For an explanation of the basics of Git, [this guide by Roger Dudler](http://git.huit.harvard.edu/guide/) is a necessary five-minute read on the basics of manipulating a repository.

Brainbox uses [GitFlow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) as a model for how to organize our repository. This means that there are two main branches that always exist, `master` and `develop`, the latter of which is the basis for all development of the toolbox. If you want to incorporate a new feature into the repository, e.g. a raster plot, you can run the following git command in your repository:

```bash
git flow feature start rasterplot
```

and Git will automatically create a new branch for you to work on and make it active. Once you've created a few commits and feel confident that your code is working well, you can create a pull request on GitHub so we can incorporate your code into the `develop` branch for future release.
Brainbox uses [GitFlow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) as a model for how to organize our repository. This means that there are two main branches that always exist, `master` and `develop`, the latter of which we have renamed `brainbox`, as brainbox is a part of `ibllib`. `brainbox` is the basis for all development of the toolbox.


# Writing code for Brainbox

We require all code in Brainbox to conform to [PEP8](https://www.python.org/dev/peps/pep-0008/) guidelines, with [Numpy-style](https://numpydoc.readthedocs.io/en/latest/format.html) docstrings. We require all contributors to use `flake8` as a linter to check their code before a pull request.
We require all code in Brainbox to conform to [PEP8](https://www.python.org/dev/peps/pep-0008/) guidelines, with [Numpy-style](https://numpydoc.readthedocs.io/en/latest/format.html) docstrings. We require all contributors to use `flake8` as a linter to check their code before a pull request. Please check the `.flake8` file in the top level of `ibllib` for an exact specification for how to set up your particular instance of flake8.

[MORE GUIDELINES HERE PLEASE]
3 changes: 2 additions & 1 deletion brainbox/core/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ def __init__(self, times, values, columns=None, *args, **kwargs):
a time stamp associated. TS objects have obligatory 'times' and 'values' entries which
must be passed at construction, the length of both of which must match. TimeSeries takes an
optional 'columns' argument, which defaults to None, that is a set of labels for the
columns in 'values'.
columns in 'values'. These are also exposed via the dot syntax as pointers to the specific
columns which they reference.
:param times: an ordered object containing a list of timestamps for the time series data
:param values: an ordered object containing the associated measurements for each time stamp
Expand Down
190 changes: 190 additions & 0 deletions brainbox/environment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
name: brainboxdev
channels:
- defaults
dependencies:
- _libgcc_mutex=0.1=main
- _tflow_select=2.3.0=mkl
- absl-py=0.7.1=py37_0
- alabaster=0.7.12=py37_0
- asn1crypto=0.24.0=py37_0
- astor=0.7.1=py37_0
- astroid=2.2.5=py37_0
- attrs=19.1.0=py37_1
- babel=2.7.0=py_0
- backcall=0.1.0=py37_0
- blas=1.0=mkl
- bleach=3.1.0=py37_0
- bzip2=1.0.8=h7b6447c_0
- c-ares=1.15.0=h7b6447c_1
- ca-certificates=2019.5.15=0
- certifi=2019.6.16=py37_0
- cffi=1.12.2=py37h2e261b9_1
- chardet=3.0.4=py37_1
- cloudpickle=1.2.1=py_0
- conda-package-handling=1.3.11=py37_0
- cryptography=2.6.1=py37h1ba5d50_0
- cycler=0.10.0=py37_0
- dbus=1.13.6=h746ee38_0
- decorator=4.4.0=py37_1
- defusedxml=0.6.0=py_0
- docutils=0.14=py37_0
- entrypoints=0.3=py37_0
- expat=2.2.6=he6710b0_0
- fontconfig=2.13.0=h9420a91_0
- freetype=2.9.1=h8a8886c_1
- gast=0.2.2=py37_0
- glib=2.56.2=hd408876_0
- gmp=6.1.2=h6c8ec71_1
- google-pasta=0.1.7=py_0
- grpcio=1.16.1=py37hf8bcb03_1
- gst-plugins-base=1.14.0=hbbd80ab_1
- gstreamer=1.14.0=hb453b48_1
- h5py=2.9.0=py37h7918eee_0
- hdf5=1.10.4=hb1b8bf9_0
- icu=58.2=h9c2bf20_1
- idna=2.8=py37_0
- imagesize=1.1.0=py37_0
- intel-openmp=2019.4=243
- ipykernel=5.1.1=py37h39e3cac_0
- ipython=7.6.1=py37h39e3cac_0
- ipython_genutils=0.2.0=py37_0
- ipywidgets=7.5.0=py_0
- isort=4.3.21=py37_0
- jedi=0.13.3=py37_0
- jeepney=0.4=py37_0
- jinja2=2.10.1=py37_0
- joblib=0.13.2=py37_0
- jpeg=9b=h024ee3a_2
- jsonschema=3.0.1=py37_0
- jupyter=1.0.0=py37_7
- jupyter_client=5.3.1=py_0
- jupyter_console=6.0.0=py37_0
- jupyter_core=4.5.0=py_0
- keras-applications=1.0.8=py_0
- keras-preprocessing=1.1.0=py_1
- keyring=18.0.0=py37_0
- kiwisolver=1.1.0=py37he6710b0_0
- lazy-object-proxy=1.4.1=py37h7b6447c_0
- libarchive=3.3.3=h5d8350f_5
- libedit=3.1.20181209=hc058e9b_0
- libffi=3.2.1=hd88cf55_4
- libgcc-ng=8.2.0=hdf63c60_1
- libgfortran-ng=7.3.0=hdf63c60_0
- libpng=1.6.37=hbc83047_0
- libprotobuf=3.8.0=hd408876_0
- libsodium=1.0.16=h1bed415_0
- libstdcxx-ng=8.2.0=hdf63c60_1
- libuuid=1.0.3=h1bed415_2
- libxcb=1.13=h1bed415_1
- libxml2=2.9.9=hea5a465_1
- lz4-c=1.8.1.2=h14c3975_0
- lzo=2.10=h49e0be7_2
- markdown=3.1.1=py37_0
- markupsafe=1.1.1=py37h7b6447c_0
- matplotlib=3.1.0=py37h5429711_0
- mccabe=0.6.1=py37_1
- mistune=0.8.4=py37h7b6447c_0
- mkl=2019.4=243
- mkl-service=2.0.2=py37h7b6447c_0
- mkl_fft=1.0.12=py37ha843d7b_0
- mkl_random=1.0.2=py37hd81dba3_0
- nbconvert=5.5.0=py_0
- nbformat=4.4.0=py37_0
- ncurses=6.1=he6710b0_1
- notebook=5.7.8=py37_0
- numpy=1.16.4=py37h7e9f1db_0
- numpy-base=1.16.4=py37hde5b4d6_0
- numpydoc=0.9.1=py_0
- openssl=1.1.1c=h7b6447c_1
- packaging=19.0=py37_0
- pandas=0.24.2=py37he6710b0_0
- pandoc=2.2.3.2=0
- pandocfilters=1.4.2=py37_1
- parso=0.5.0=py_0
- patsy=0.5.1=py37_0
- pcre=8.43=he6710b0_0
- pexpect=4.7.0=py37_0
- pickleshare=0.7.5=py37_0
- pip=19.0.3=py37_0
- prometheus_client=0.7.1=py_0
- prompt_toolkit=2.0.9=py37_0
- protobuf=3.8.0=py37he6710b0_0
- psutil=5.6.3=py37h7b6447c_0
- ptyprocess=0.6.0=py37_0
- pycodestyle=2.5.0=py37_0
- pycosat=0.6.3=py37h14c3975_0
- pycparser=2.19=py37_0
- pyflakes=2.1.1=py37_0
- pygments=2.4.2=py_0
- pylint=2.3.1=py37_0
- pyopenssl=19.0.0=py37_0
- pyparsing=2.4.0=py_0
- pyqt=5.9.2=py37h05f1152_2
- pyrsistent=0.14.11=py37h7b6447c_0
- pysocks=1.6.8=py37_0
- python=3.7.3=h0371630_0
- python-dateutil=2.8.0=py37_0
- python-libarchive-c=2.8=py37_10
- pytz=2019.1=py_0
- pyzmq=18.0.0=py37he6710b0_0
- qt=5.9.7=h5867ecd_1
- qtawesome=0.5.7=py37_1
- qtconsole=4.5.1=py_0
- qtpy=1.8.0=py_0
- readline=7.0=h7b6447c_5
- requests=2.22.0=py37_0
- rope=0.14.0=py_0
- ruamel_yaml=0.15.46=py37h14c3975_0
- scikit-learn=0.21.2=py37hd81dba3_0
- seaborn=0.9.0=py37_0
- secretstorage=3.1.1=py37_0
- send2trash=1.5.0=py37_0
- setuptools=41.0.0=py37_0
- sip=4.19.8=py37hf484d3e_0
- six=1.12.0=py37_0
- snowballstemmer=1.9.0=py_0
- sphinx=2.1.2=py_0
- sphinxcontrib-applehelp=1.0.1=py_0
- sphinxcontrib-devhelp=1.0.1=py_0
- sphinxcontrib-htmlhelp=1.0.2=py_0
- sphinxcontrib-jsmath=1.0.1=py_0
- sphinxcontrib-qthelp=1.0.2=py_0
- sphinxcontrib-serializinghtml=1.1.3=py_0
- spyder=3.3.6=py37_0
- spyder-kernels=0.5.1=py37_0
- sqlite=3.27.2=h7b6447c_0
- statsmodels=0.10.0=py37hdd07704_0
- tensorboard=1.14.0=py37hf484d3e_0
- tensorflow=1.14.0=mkl_py37h45c423b_0
- tensorflow-base=1.14.0=mkl_py37h7ce6ba3_0
- tensorflow-estimator=1.14.0=py_0
- termcolor=1.1.0=py37_1
- terminado=0.8.2=py37_0
- testpath=0.4.2=py37_0
- tk=8.6.8=hbc83047_0
- tornado=6.0.3=py37h7b6447c_0
- tqdm=4.32.1=py_0
- traitlets=4.3.2=py37_0
- urllib3=1.24.1=py37_0
- wcwidth=0.1.7=py37_0
- webencodings=0.5.1=py37_1
- werkzeug=0.15.4=py_0
- wheel=0.33.1=py37_0
- widgetsnbextension=3.5.0=py37_0
- wrapt=1.11.2=py37h7b6447c_0
- wurlitzer=1.0.2=py37_0
- xz=5.2.4=h14c3975_4
- yaml=0.1.7=had09818_2
- zeromq=4.3.1=he6710b0_3
- zlib=1.2.11=h7b6447c_3
- zstd=1.3.7=h0b5b093_0
- pip:
- autopep8==1.4.4
- colorlog==4.0.2
- dataclasses==0.6
- flake8==3.7.8
- globus-sdk==1.8.0
- ibllib==1.0.6
- pyjwt==1.7.1
- scipy==1.3.0

Empty file added brainbox/plot/__init__.py
Empty file.
1 change: 1 addition & 0 deletions brainbox/population/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .population import *
141 changes: 141 additions & 0 deletions brainbox/population/population.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
'''
Population functions.
Code from https://github.com/cortex-lab/phylib/blob/master/phylib/stats/ccg.py by C. Rossant.
'''

import numpy as np


def _index_of(arr, lookup):
"""Replace scalars in an array by their indices in a lookup table.
Implicitely assume that:
* All elements of arr and lookup are non-negative integers.
* All elements or arr belong to lookup.
This is not checked for performance reasons.
"""
# Equivalent of np.digitize(arr, lookup) - 1, but much faster.
# TODO: assertions to disable in production for performance reasons.
# TODO: np.searchsorted(lookup, arr) is faster on small arrays with large
# values
lookup = np.asarray(lookup, dtype=np.int32)
m = (lookup.max() if len(lookup) else 0) + 1
tmp = np.zeros(m + 1, dtype=np.int)
# Ensure that -1 values are kept.
tmp[-1] = -1
if len(lookup):
tmp[lookup] = np.arange(len(lookup))
return tmp[arr]


def _increment(arr, indices):
"""Increment some indices in a 1D vector of non-negative integers.
Repeated indices are taken into account."""
bbins = np.bincount(indices)
arr[:len(bbins)] += bbins
return arr


def _diff_shifted(arr, steps=1):
return arr[steps:] - arr[:len(arr) - steps]


def _create_correlograms_array(n_clusters, winsize_bins):
return np.zeros((n_clusters, n_clusters, winsize_bins // 2 + 1), dtype=np.int32)


def _symmetrize_correlograms(correlograms):
"""Return the symmetrized version of the CCG arrays."""

n_clusters, _, n_bins = correlograms.shape
assert n_clusters == _

# We symmetrize c[i, j, 0].
# This is necessary because the algorithm in correlograms()
# is sensitive to the order of identical spikes.
correlograms[..., 0] = np.maximum(
correlograms[..., 0], correlograms[..., 0].T)

sym = correlograms[..., 1:][..., ::-1]
sym = np.transpose(sym, (1, 0, 2))

return np.dstack((sym, correlograms))


def xcorr(spike_times, spike_clusters, bin_size=None, window_size=None):
"""Compute all pairwise cross-correlograms among the clusters appearing in `spike_clusters`.
Parameters
----------
:param spike_times: Spike times in seconds.
:type spike_times: array-like
:param spike_clusters: Spike-cluster mapping.
:type spike_clusters: array-like
:param bin_size: Size of the bin, in seconds.
:type bin_size: float
:param window_size: Size of the window, in seconds.
:type window_size: float
Returns an `(n_clusters, n_clusters, winsize_samples)` array with all pairwise
cross-correlograms.
"""
assert np.all(np.diff(spike_times) >= 0), ("The spike times must be increasing.")
assert spike_times.ndim == 1
assert spike_times.shape == spike_clusters.shape

# Find `binsize`.
bin_size = np.clip(bin_size, 1e-5, 1e5) # in seconds

# Find `winsize_bins`.
window_size = np.clip(window_size, 1e-5, 1e5) # in seconds
winsize_bins = 2 * int(.5 * window_size / bin_size) + 1

# Take the cluster order into account.
clusters = np.unique(spike_clusters)
n_clusters = len(clusters)

# Like spike_clusters, but with 0..n_clusters-1 indices.
spike_clusters_i = _index_of(spike_clusters, clusters)

# Shift between the two copies of the spike trains.
shift = 1

# At a given shift, the mask precises which spikes have matching spikes
# within the correlogram time window.
mask = np.ones_like(spike_times, dtype=np.bool)

correlograms = _create_correlograms_array(n_clusters, winsize_bins)

# The loop continues as long as there is at least one spike with
# a matching spike.
while mask[:-shift].any():
# Interval between spike i and spike i+shift.
spike_diff = _diff_shifted(spike_times, shift)

# Binarize the delays between spike i and spike i+shift.
spike_diff_b = np.round(spike_diff / bin_size).astype(np.int64)

# Spikes with no matching spikes are masked.
mask[:-shift][spike_diff_b > (winsize_bins / 2)] = False

# Cache the masked spike delays.
m = mask[:-shift].copy()
d = spike_diff_b[m]

# Find the indices in the raveled correlograms array that need
# to be incremented, taking into account the spike clusters.
indices = np.ravel_multi_index(
(spike_clusters_i[:-shift][m], spike_clusters_i[+shift:][m], d), correlograms.shape)

# Increment the matching spikes in the correlograms array.
_increment(correlograms.ravel(), indices)

shift += 1

return _symmetrize_correlograms(correlograms)
2 changes: 1 addition & 1 deletion brainbox/processing/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from brainbox import core


def sync(dt, timeseries=None, times=None, values=None, offsets=None, interp='zero',
def sync(dt, times=None, values=None, timeseries=None, offsets=None, interp='zero',
fillval=np.nan):
"""
Function for resampling a single or multiple time series to a single, evenly-spaced, delta t
Expand Down
1 change: 1 addition & 0 deletions brainbox/singlecell/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .singlecell import *
Loading

0 comments on commit bb267ba

Please sign in to comment.