Metadata-Version: 2.1
Name: saffy
Version: 0.1.12.dev0
Summary: Signal Analysis Framework For You
Home-page: https://github.com/PPierzc/saffy
Author: Paweł A. Pierzchlewicz
Author-email: paul@teacode.io
License: MIT
Description-Content-Type: text/markdown
Keywords: python,signal,analysis,EEG,neuroinformatics
Platform: UNKNOWN
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Build Tools
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Requires-Dist: cycler (==0.10.0)
Requires-Dist: kiwisolver (==1.0.1)
Requires-Dist: matplotlib (==3.0.3)
Requires-Dist: mne (==0.17.1)
Requires-Dist: numpy (==1.16.2)
Requires-Dist: obci-readmanager (==1.1.3)
Requires-Dist: pandas (==0.24.2)
Requires-Dist: pyparsing (==2.3.1)
Requires-Dist: python-dateutil (==2.8.0)
Requires-Dist: pytz (==2018.9)
Requires-Dist: scipy (==1.2.1)
Requires-Dist: seaborn (==0.9.0)
Requires-Dist: six (==1.12.0)

# Saffy
Signal Analysis Framework For You

A simple signal analysis framework, which aims at clarity of code and reproducibility of solutions. The plugin architecture
aims to encourage building modular code among scientists and data analysts. It provides a basic structure for signal
storage and a pipeline for analysis.

## Install

`pip3 install saffy`

###### From Source
Using a virtualenv is recommended! 

Download the package to your project directory

`git clone https://github.com/PPierzc/saffy.git`

Install dependencies

`pip3 install -r ./saffy/requirements.txt`

## Usage
```python
import saffy
sig = saffy.SignalManager(generator=signal_data)
```

#### Basic SignalManager instance structure
| field | description |
|--------|------|
| fs   | sampling frequency  |
| num_channels | number of channels |
| channel_names | name for each channel |
| data | the signal in the structure of (epoch x channel x signal) |
| t | time vector |
| epochs | number of epochs |
| tags | position of tags in signal |
| spectrum | matrix of spectrum |
| spectrum_freqs| vector of frequencies |
| phase | matrix of phase |

#### SignalManager init function
It takes one labelled argument: `generator` or `filename`.

###### Generator
A dictionary of the structure
```
data = {
      'fs': # float,
      'num_channels': # integer,
      'channel_names': # list of strings,
      'epochs': # integer,
      't': # time array,
      'tags': # list,
      'data': # Signal Matrix
  }
```

```python
saffy.SignalManager(generator=data)
```

###### Filename
The name of the file generated by Svarog. 3 files eg. `data.raw`, `data.xml`, `data.tag`

```python
saffy.SignalManager(filename='data')
```

## Plugins
Plugins are classes that inherit from the PluginManager. They extend the functionality of the basic Signal Manager.
Some plugins are provided out of the box

#### Filters
Adds basic filters

#### Graphics
Adds functions to display the signal data

#### Welch
Calculating the Welch Spectrum

#### Hilbert
Calculating the Hilbert Transform

### Creating Custom Plugins
You might want to add some custom features.

The proposed convention for plugin development is the following.
All data that is to be stored extra, should be stored in the form of a dictionary assigned to a variable of the same name
as the plugin.

Plugin functions should be preceded by the plugin name. 

```python
import saffy

class CustomPlugin(saffy.PluginManager):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.custom = {
            'param': 'some value'
        }

    def custom_function(self):
        # do something
        pass

saffy.SignalManager.register_plugin(CustomPlugin)

sig = saffy.SignalManager(generator=signal_data)

sig.custom_function()
```

## Example
A short example of how to use saffy for EEG data analysis.

```python
def generate_signal():
  data = {
      'fs': 512,
      'num_channels': 3,
      'channel_names': ['C3', 'C4', 'd1'],
      'epochs': 1
  }

  T = 20
  t = np.arange(0, T, 1 / data['fs'])

  mu_freq = 10
  beta_freq = 23
  net_freq = 50

  data['data'] = np.zeros((data['epochs'], data['num_channels'], len(t)))

  for epoch in range(data['epochs']):
    data['data'][epoch][0] += (0.1 * t + 0.1) * sin(t, mu_freq)
    data['data'][epoch][0] += (0.1 * t + 0.1) * sin(t, beta_freq)
    data['data'][epoch][0] += sin(t, net_freq)
    data['data'][epoch][0] += 0.3 * noise(t)

    data['data'][epoch][1] += (0.1 * t + 0.1) * sin(t, mu_freq)
    data['data'][epoch][1] += (0.1 * t + 0.1) * sin(t, beta_freq)
    data['data'][epoch][1] += sin(t, net_freq)
    data['data'][epoch][1] += 0.3 * noise(t)

    data['data'][epoch][2][::5*data['fs']] = 1
    data['data'][epoch][2][0] = 0

  data['t'] = t
  data['tags'] = []

  return data

EEG = saffy.SignalManager(generator=generate_signal())

EEG.set_tags_from_channel('d1')
EEG.remove_channel('d1')

PRE_EEG = EEG.copy('pre')
PRE_EEG.set_epochs_from_tags(-4, -2)

PRE_EEG.welch_spectrum()
PRE_EEG.spectrum = np.mean(PRE_EEG.spectrum, axis=0)
PRE_EEG.spectrum = np.reshape(PRE_EEG.spectrum, (1, *PRE_EEG.spectrum.shape))

POST_EEG = EEG.copy('post')
POST_EEG.set_epochs_from_tags(0.5, 2.5)

POST_EEG.welch_spectrum()
POST_EEG.spectrum = np.mean(POST_EEG.spectrum, axis=0)
POST_EEG.spectrum = np.reshape(POST_EEG.spectrum, (1, *POST_EEG.spectrum.shape))

fig, ax = plt.subplots(
    nrows=max([PRE_EEG.num_channels, POST_EEG.num_channels]),
    ncols=1,
    sharex=True,
    sharey=True,
    figsize=(10, 10)
)

PRE_EEG.graphics_spectrum_plot(
    fig,
    ax,
    'Change',
    label='Pre'
)

POST_EEG.graphics_spectrum_plot(
    fig,
    ax,
    color='#0000ff',
    label='Post'
)

for a in ax:
  a.legend()

plt.show()
plt.close()
```
![alt text](examples/example.png)

## Contributing
If you like the project and want to add something to it then please create a pull request.
- The title should shortly summarize the goal of your addition
- In the description go in depth with the changes you have made and why.


