Introduction

Here is a documentation for people who want to write scripts using OpenElectrophy framework.

Basic knowledge of Python, SciPy, and Matplotlib are requested for fully understanding what will follow but the syntax language is clear enough so that it can nearly be understood without knowing Python at all.

This tutorial starts when the reader has assimilated the concept of databases and when events of interest, spikes and/or oscillations, have been detected.

First: import

Any script will begin with this magic sequence:

# if OpenElectrophy is installed in the user home directory, add it to the python path
import sys
sys.path.append(r'/home/myname/installpath/OpenElectrophy/trunk')

# import all classes from OpenElectrophy
from pyssdh.OpenElectrophy.core_classes import *

# connect to a particular database
host, login , password = 'localhost' , 'test_OE' , 'secret'
nom_base = 'local_test_OE'
init_connection(host, login, password,nom_base=nom_base)

Basic OpenElectrophy class mapper

The only thing to remind in OpenElectrophy is that everything is stored in a database. So each electrode signal, spike, spike train or oscillation can be accessed with a standard SQL query. In some cases SQL is very easy and efficient: for finding a data, for ordering, for filtering... But for other operations, writing SQL queries is repetitive and time consuming. Thus OpenElectrophy provides a mapper class to access mySQL values directly in Python without writing any SQL query.

In practice, to each MySQL table corresponds a Python class. For example OpenElectrophy provides the classes: Serie, Trial, Electrode, Epoch, SpikeTrain, Spike, Oscillation and Cell. Each instance of this class will map a row of a table and all field of a SQL table will be held by a a member of a instance.

All these classes have 2 basic methods: save_to_db() and load_to_db() to save or load all fields to/from the database.

By consequence, to manipulate fields of a MySQL row, you just need to read/write a Python variable (class member).

Example:

elec1 = Electrode()

# Load a row from the table electrode with the id 52
id_electrode = 52
elec1.load_from_db(id_electrode)
print elec1.label
print elec1.num_channel

# modify 2 fields and save them
elec1.label = "CH5"
elec1.num_channel = 5
elec1.save_to_db()

Numpy array in mySQL

The MySQL mapper is a very simple one. Its main interest come from the mapping of numpy.array. A Python type numpy.array is transparently mapped in 3 fields in a MySQL table: the buffer (in a BLOB field), the shape (in a TEXT field), and the dtype (in a TEXT field). This mechanism works only for continuous array (as opposed to masked array). This presents the advantage to be portable to other languages that support BLOB field.

For example, the Electrode class has a signal member which is a numpy.array :

id_electrode = 52
elec1.load_from_db(id_electrode)
print elec1.signal
print type(elec1.signal)
print elec1.signal.shape

which gives

array([ 0.59884206,  0.16633085,  0.74639272, ...,  0.96646823,
        0.77549852,  0.29886275])

<type 'numpy.ndarray'>

(5000,)

Of course for these fields, if the recording is long (hours) and the sampling frequency is high, loading the BLOB field from the MySQL server can be quite long... The problem is however exactly the same with a classical data storage based on files. Consequently the choice of chunk size of an electrode signal is important for the promptness of loading and saving.

At the moment, the whole array has to be loaded in the python variable. You can't load just parts of this array. This is because MySQL does not support directly BLOB streaming. Third party developers implement this feature: http://blobstreaming.org/. OpenElectrophy will take advantage of this technology in a near future.

INSERT or UPDATE

Internally, the method save_to_db can perform two SQL functions: INSERT or REPLACE. It depends on the state of the member id_table of the instance.

For example, in the class Electrode. If id_electrode is set to None', save_to_db will do an INSERT in the ""electrode table. If id_electrode is an interger that correspond to a PRIMARY KEY that already exists in the electrode table, save_to_db will perform an UPDATE

Example:

elec = Electrode(
   fs = 100.,
   signal = numpy.zeros((50000)),
   name = 'electrode5',
   num_channel = 5)

print elec.id_electrode
# this give None

# this will perform a INSERT and return the PRIMARY KEY
id = elec.save_to_db()

elec2 = Electrode()
elec2.load_from_db(id)

print elec2.id_electrode
# this gives the id

elec2.name = 'electrode6'
elec2.num_channel = 6


# this will perform an UPDATE
id = elec2.save_to_db()

Instance time

When you instance an OpenElectrophy class you have 3 behavior possibilities depending on the parameters:

Either the class is empty:

elec = Electrode()

Or you can directly load a specific row with its id at the instance time:

elec = Electrode(id_electrode = 425)

Or you can fill fields directly for example for creating a new row:

elec = Electrode(fs = 10000.,
    shift_t0 = 0.,
    signal = numpy.zeros(1000)
)
elec.save_to_db()

Specific methods for classes

Some classes have some basic plotting methods. They use the matplotlib module, consequently after plotting if you are not in interactive mode you need to do a pylab.show()

Electrode

Plotting the raw signal:

import pylab

elec = Electrode(id_electrode = 4233)

elec.plot_bandwidth()
pylab.show()

Plotting the filtered signal (here with a high pass filter):

import pylab

elec = Electrode(id_electrode = 4233)

elec.plot_filtered(f_low = 300., f_hight = 5000.)
pylab.show()

Plotting the filtered (low pass) signal :

import pylab

elec = Electrode(id_electrode = 4233)

elec.plot_filtered(f_low = None, f_hight = 30.)
pylab.show()

Plotting the time frequency map with Morlet scalogram:

import pylab

elec = Electrode(id_electrode = 4233)

elec.plot_timefrequency(f_start = 40.,
         f_stop = 100,
         f0 = 2.5)
pylab.show()

You can specify already existing axes:

import pylab

fig = pylab.figure()
ax1 = fig.add_subplot(2,2,1)
ax2 = fig.add_subplot(2,2,2)
ax3 = fig.add_subplot(2,2,3)
ax4 = fig.add_subplot(2,2,4)

elec = Electrode(id_electrode = 4233 )

elec.plot_bandwidth( ax = ax1)

elec.plot_filtered(f_low = 300., f_hight = 5000., ax = ax2)

elec.plot_filtered(f_low = None, f_hight = 30., ax = ax3)


elec.plot_timefrequency(f_start = 40.,
                                        f_stop = 100,
                                        f0 = 2.5, 
                                        ax = ax4)
pylab.show()

SpikeTrain

The table spiketrain does not hold spike position, there are saved in spike table. But SpikeTrain class can reconstruct a spiketrain in 5 ways:

  • pos_spike() return the position on the electrode isgnal in sample for each spike
    import pylab
    sp = SpikeTrain(id_spiketrain = 815)
    print sp.pos_spike()
    

give

[ 1992  2497  3079  3168  3791  3900  7726  9137 11339 11610 12055 14205
 14732 14818 14978 18279 20422 20625 21992 24392 24707 25644 25807 26381
 26499 31168 31356 32412 32501 33378 33703 35543 37562 37836 38036 38629
 42086 42822 43306 43617 45143 45352 47810 49690 50080 51088]
  • time_spike() return the absolute time for each spike (depend of shift_t0, fs and pos_spike) :
    import pylab
    sp = SpikeTrain(id_spiketrain = 815)
    print sp.time_spike()
    

give

[-4.8008 -4.7503 -4.6921 -4.6832 -4.6209 -4.61   -4.2274 -4.0863 -3.8661
 -3.839  -3.7945 -3.5795 -3.5268 -3.5182 -3.5022 -3.1721 -2.9578 -2.9375
 -2.8008 -2.5608 -2.5293 -2.4356 -2.4193 -2.3619 -2.3501 -1.8832 -1.8644
 -1.7588 -1.7499 -1.6622 -1.6297 -1.4457 -1.2438 -1.2164 -1.1964 -1.1371
 -0.7914 -0.7178 -0.6694 -0.6383 -0.4857 -0.4648 -0.219  -0.031   0.008
  0.1088]
  • isi() return all intervals inter spike (equivalent of diff(time_spike()))
    import pylab
    sp = SpikeTrain(id_spiketrain = 815)
    print sp.isi()
    

give

[ 0.0505  0.0582  0.0089  0.0623  0.0109  0.3826  0.1411  0.2202  0.0271
  0.0445  0.215   0.0527  0.0086  0.016   0.3301  0.2143  0.0203  0.1367
  0.24    0.0315  0.0937  0.0163  0.0574  0.0118  0.4669  0.0188  0.1056
  0.0089  0.0877  0.0325  0.184   0.2019  0.0274  0.02    0.0593  0.3457
  0.0736  0.0484  0.0311  0.1526  0.0209  0.2458  0.188   0.039   0.1008]
  • binary() return a bined representation of spike in a window, presence of one spike = 1
    import pylab
    sp = SpikeTrain(id_spiketrain = 815)
    print sp.binary(bin_size = .001 , t_start = -4.5 , t_stop = -4. )
    

give

[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
  • list_spike() return a list of all Sike() object :
    import pylab
    sp = SpikeTrain(id_spiketrain = 815)
    print sp.list_spike()
    

give

[<pyssdh.OpenElectrophy.core_classes.spike.Spike instance at 0x22403b0>, <pyssdh.OpenElectrophy.core_classes.spike.Spike instance at 0x2240518>, <pyssdh.OpenElectrophy.core_classes.spike.Spike instance at 0x2240710>, <pyssdh.OpenElectrophy.core_classes.spike.Spike instance at 0x22405f0>, <pyssdh.OpenElectrophy.core_classes.spike.Spike instance at 0x2240680>, <pyssdh.OpenElectrophy.core_classes.spike.Spike instance at 0x2240cb0>, <pyssdh.OpenElectrophy.core_classes.spike.Spike instance at 0x2240ea8>, <pyssdh.OpenElectrophy.core_classes.spike.Spike instance at 0x2240fc8>, <pyssdh.OpenElectrophy.core_classes.spike.Spike instance at 0x2241f38>, <pyssdh.OpenElectrophy.core_classes.spike.Spike instance at 0x2241518>, ... ]

SpikeTrain has also plotting methods:

You can either plot the raster:

import pylab

sp = SpikeTrain(id_spiketrain = 815)
sp.plot_raster()
pylab.show()

or the superposition of all spike waveforms:

import pylab

sp = SpikeTrain(id_spiketrain = 815)
sp.plot_waveform()

pylab.show()

Cell

For the Cell object you can plot the Interval Inter Spike (ISI) distribution:

import pylab

ce = Cell(id_cell = 14)

ce.plot_isi_hist()

pylab.show()

All the waveforms of all spikes:

import pylab

ce = Cell(id_cell = 14)

ce.plot_waveform()

pylab.show()

The autocorrelogram computed from all spiketrain:

import pylab

ce = Cell(id_cell = 14)
ce.plot_autocorr()

pylab.show()

Writing a new class/table

For your own needs you can create a new table. This is quite easy with the simple SQL mapper included in OpenElectrophy.

Here is a short example to understand the syntax: we want to create a table that manage the position of the animal captured by a video track system. For doing this we create a class that inherits database_storage and that declares its name, fields and fields type. Note that you can use standard MySQL fields but also 'NUMPY' field. Note also that the id_trial is an 'INDEX' that attach this class as a son of the table trial.

from pyssdh.core.sql_util import database_storage

class AnimalPosition(database_storage):
        #------------------------------------------------------------------------------
        def __init__(self,**karg) :
                database_storage.__init__(self,**karg)
                
        table_name = 'animalposition'
        list_field = [  'id_trial',
                        'info'
                        'duration',
                        'numRun',
                        'xPosition',
                        'yPosition',
                                ]
        field_type = [  'INDEX',
                        'TEXT',
                        'FLOAT',
                        'INT',
                        'NUMPY',
                        'NUMPY',
                                ]
        list_table_child = [  ]

Now you can use it like any other OpenElectrophy classes. If the table does not exist it will be created at the first INSERT. If you add new fields in a new version of this Python class, they will be inserted in the corresponding SQL table at the next use.

Attachments