# SpikeData Package

[![CI](https://github.com/braingeneers/SpikeData/actions/workflows/ci.yml/badge.svg)](https://github.com/braingeneers/SpikeData/actions/workflows/ci.yml)

## Overview

SpikeData is a Python package designed for handling and analyzing neuronal spike data. It provides a suite of tools for loading, processing, and analyzing spike data from various in-memory sources such as NEST simulation outputs, lists of indices and times, spike raster matrices, and more.

## Features

- **Flexible Data Loading**: Load spike data from various formats including NEST Simulator, raster matrices, raw data via filtering & thresholding, and custom event lists.
- **Data Processing**: Process spike trains with functions for binning, resampling, thresholding, and filtering.
- **Analysis Tools**: Perform detailed analyses such as burst detection, cumulative moving averages, Fano factors, and population firing rates.
- **Customization**: Add metadata and neuron attributes for comprehensive data management.
- **Utilities**: Generate matched pairs of unit indices and times, iterate through spike events, and create subwindows of spike data.

## Installation

For regular use, just install with pip from PyPI.

```bash
pip install spikedata
```

## Working with SpikeData Objects

This section describes the usage of a few key methods via simple examples. There are a lot of usage examples in the unit test code as well.

### Constructors

The main constructor for `SpikeData` takes a list of arrays of spike times, but there are various other constructors that take other in-memory formats implemented as static methods for convenience.

All of the constructors also take a variety of metadata parameters.

- **From indices and times:**

    ```python
    idces = [0, 1, 0, 1]
    times = [10, 20, 30, 40]
    spike_data = SpikeData.from_idces_times(idces, times)
    ```

- **From raster:**

    ```python
    raster = np.array([[1, 0, 2], [0, 1, 1]])
    spike_data = SpikeData.from_raster(raster, bin_size_ms=10)
    ```

- **From NEST spike recorder:**

    ```python
    nodes = nest.Create(...)
    other_nodes = nest.Create(...)
    spike_recorder = nest.Create('spike_recorder')
    nest.Connect(spike_recorder, nodes)
    nest.Simulate(...)
    spike_data = SpikeData.from_nest(spike_recorder, nodes, other_nodes)
    ```

You can also get a `SpikeData` object from a list of spike trains represented using Neo (`neo.SpikeTrain` via `SpikeData.from_neo_spiketrains`) or MuscleBeachTools (`mbt.Neuron` via `SpikeData.from_mbt_neurons`).

### Accessing Spike Data

- **Spike times of a particular unit:**

    ```python
    for time in spike_data.train[i]:
        print(f"Unit {i} fired at {time} ms")
    ```

- **Spike times of all units:**

    ```python
    for time in spike_data.times:
        print(f"Some neuron fired at {time} ms")
    ```

- **Events from all units:**

    ```python
    for index, time in spike_data.events:
        print(f"Neuron {index} fired at {time} ms")
    ```

- **Binned population activity:**

    ```python
    binned_data = spike_data.binned(bin_size=40)
    print(f"There were {binned_data[1]} firings between 40 and 80 ms")
    ```

### Firing Rates

- **Mean firing rate in each time bin:**

    ```python
    rate = spike_data.binned_meanrate(bin_size=40, unit='Hz')
    print(rate)
    ```

- **Firing rate of each neuron:**

    ```python
    rates = spike_data.rates(unit='Hz')
    print(rates)
    ```

- **Instantaneous firing rates of every neuron via ISI resampling:**

    ```python
    times = np.linspace(0, 1000, 100)  # Example times
    resampled_isi = spike_data.resampled_isi(times)
    print(resampled_isi)
    ```

- **Spike raster, in N×T format:**

    ```python
    raster = spike_data.raster(bin_size=20.0)
    ```

### Slicing and Combining Spike Data Objects

- **Appending in time:**

    ```python
    spike_data2 = SpikeData.from_idces_times([2, 3], [50, 60])
    combined_data = spike_data.append(spike_data2, offset=10)
    ```

- **Subsetting neurons:**

    ```python
    subset_data = spike_data.subset({0, 1})
    subset_data = spike_data[{0, 1}]
    ```

- **Slicing time windows:**

    ```python
    window_data = spike_data.subtime(0, 100)
    subset_data = spike_data[0:100]
    ```

## Analysis Methods

🚧 Various other analysis methods are provided, but there aren't usage examples written up yet.

## Contributing

Contributions to SpikeData are welcome. Please fork the repository and submit pull requests. Ensure that your code adheres to the PEP 8 style guide and includes appropriate tests.

## License

SpikeData is licensed under the MIT License. See the LICENSE file for more details.

## Acknowledgements

This package utilizes `numpy` for numerical operations and `scipy` for signal processing. There is also an optional dependency on `powerlaw`, which is used for calculating the deviation from criticality coefficient (DCC).

For any questions or issues, please open an issue on the [GitHub repository](https://github.com/your-repo/spikedata).
