diff --git a/Makefile b/Makefile index c988ba6..69d9c68 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,9 @@ +TEST_REQUIREMENTS=nose coverage + all: install install: clean python setup.py sdist pip install dist/*.tar.gz --upgrade -install3: clean - python3 setup.py sdist - pip3 install dist/*.tar.gz --upgrade doc_efeatures: rm -rf docs/build_efeatures && \ mkdir docs/build_efeatures && \ @@ -23,20 +22,17 @@ doc_upload: doc git commit -m "Updating docs" && \ git push "git@github.com:BlueBrain/eFEL.git" master:gh-pages --force && \ rm -rf .git +install_test_requirements: + pip install -q $(TEST_REQUIREMENTS) --upgrade update_version: cd efel && \ python -c 'import version; version._get_version_number()' && \ git add GITHASH.txt && \ git add VERSION.txt && \ git commit -m 'Updated version number' -test: install - pip install nose coverage --upgrade +test: install install_test_requirements cd efel/tests; nosetests -s -v -x --with-coverage --cover-xml \ --cover-package efel -test3: install3 - pip3 install nose coverage --upgrade - cd efel/tests; nosetests-3.4 -s -v -x --with-coverage --cover-xml \ - --cover-package efel pypi: test pip install twine --upgrade rm -rf dist diff --git a/docs/source/neoIO_example.rst b/docs/source/neoIO_example.rst new file mode 100644 index 0000000..b2b29be --- /dev/null +++ b/docs/source/neoIO_example.rst @@ -0,0 +1,39 @@ +Reading different file formats +============================== + +Neo is a Python package which provides support for reading a wide range of neurophysiology file +formats, including Spike2, NeuroExplorer, AlphaOmega, Axon, Blackrock, Plexon and Tdt. + +The function :func:`efel.io.load_neo_file()` reads data from any of the file formats supported by +Neo and formats it for use in eFEL. + +As an example, suppose we have an .abf file containing a single trace. Since eFEL requires +information about the start and end times of the current injection stimulus, we provide these +times as well as the filename:: + + import efel + + data = efel.io.load_neo_file("path/first_file.abf", stim_start=200, stim_end=700) + +Since some file formats can contain multiple recording episodes (e.g. trials) and multiple +signals per episode, the function returns traces in a list of lists, like this:: + + data : [Segment_1, Segment_2, ..., Segment_n] + with Segment_1 = [Trace_1, Trace_2, ..., Trace_n] + +Since our file contains only a single recording episode, our list of traces is:: + + traces = data[0] + +which we pass to eFEL as follows:: + + features = efel.getFeatureValues(traces, ['AP_amplitude', 'voltage_base']) + +Stimulus information within the file +------------------------------------ + +Some file formats can store information about the current injection stimulus. In this second +example, the file contains an :class:`Epoch` object named "stimulation", so we don't need to +explicitly specify `stim_start` and `stim_end`:: + + data2 = efel.io.load_neo_file("path/second_file.h5") diff --git a/docs/source/python_examples.rst b/docs/source/python_examples.rst index 43e084c..444adde 100644 --- a/docs/source/python_examples.rst +++ b/docs/source/python_examples.rst @@ -5,4 +5,5 @@ Python python_example1 deap_optimisation + neoIO_example diff --git a/efel/io.py b/efel/io.py index b822e95..7e1d182 100644 --- a/efel/io.py +++ b/efel/io.py @@ -80,3 +80,174 @@ def load_fragment(fragment_url, mime_type=None): return fragment_content else: raise TypeError('load_fragment: unknown mime type %s' % mime_type) + + +def extract_stim_times_from_neo_data(blocks, stim_start, stim_end): + """ + Seeks for the stim_start and stim_end parameters inside the Neo data. + + Parameters + ========== + blocks : Neo object blocks + stim_start : numerical value (ms) or None + stim_end : numerical value (ms) or None + + Epoch.name should be one of "stim", "stimulus", "stimulation", + "current_injection" + First Event.name should be "stim_start", "stimulus_start", + "stimulation_start", "current_injection_start" + Second Event.name should be one of "stim_end", + "stimulus_end", "stimulation_end", "current_injection_end" + + Returned objects + ==================== + stim_start : numerical value (ms) or None + stim_end : numerical value (ms) or None + + """ + # this part code aims to find informations about stimulations, if + # stimulation time are None. + if stim_start is None and stim_end is None: + for bl in blocks: + for seg in bl.segments: + event_start_rescaled = False + event_end_rescaled = False + epoch_rescaled = False + for epoch in seg.epochs: + if epoch.name in ( + "stim", + "stimulus", + "stimulation", + "current_injection"): + if stim_start is None: + epoch = epoch.rescale('ms').magnitude + stim_start = epoch[0] + stim_end = epoch[-1] + epoch_rescaled = True + else: + raise ValueError( + 'It seems that there are two epochs related ' + 'to stimulation, the program does not know ' + 'which one to chose') + + for event in seg.events: + # Test event stim_start + if event.name in ( + "stim_start", + "stimulus_start", + "stimulation_start", + "current_injection_start"): + # tests if not already found once + if event_start_rescaled: + raise ValueError( + 'It seems that stimulation start time is ' + 'defined more than one event.' + ' The program does not know which one ' + 'to choose') + if epoch_rescaled: + raise ValueError( + 'It seems that stim_start is defined in both ' + 'epoch and an event.' + + ' The program does not know which one ' + 'to choose') + + if stim_start is None: + event = event.rescale('ms').magnitude + + try: + stim_start = event[0] + except: + stim_start = event + + event_start_rescaled = True + + # Test event stim_end + elif event.name in ("stim_end", "stimulus_end", + "stimulation_end", + "current_injection_end"): + # tests if not already found once + if event_end_rescaled: + raise ValueError( + 'It seems that stimulation end time is ' + 'defined more than one event.' + ' The program does not know which one ' + 'to choose') + if epoch_rescaled: + raise ValueError( + 'It seems that stim_end is defined in ' + 'both epoch and an event.' + ' The program does not know which one ' + 'to choose') + + if stim_end is None: + event = event.rescale('ms').magnitude + + try: + stim_end = event[-1] + except: + stim_end = event + + event_end_rescaled = True + + return stim_start, stim_end + + +def load_neo_file(file_name, stim_start=None, stim_end=None): + """ + Use neo to load a data file and convert it to be readable by eFEL. + + Parameters + ========== + file_name : string + path to the location of a Dependency file + stim_start : numerical value (ms) + Optional if there is an Epoch or two Events in the file + stim_end : numerical value (ms) + Optional if there is an Epoch or two Events in the file + + Epoch.name should be one of "stim", "stimulus", "stimulation", + "current_injection" + First Event.name should be "stim_start", "stimulus_start", + "stimulation_start", "current_injection_start" + Second Event.name should be one of "stim_end", "stimulus_end", + "stimulation_end", "current_injection_end" + + The returned object is presented like this : + returned object : [Segments_1, Segments_2, ..., Segments_n] + Segments_1 = [Traces_1, Traces_2, ..., Traces_n] + """ + + import neo + + reader = neo.io.get_io(file_name) + blocks = reader.read() + + stim_start, stim_end = extract_stim_times_from_neo_data( + blocks, stim_start, stim_end) + if stim_start is None or stim_end is None: + raise ValueError( + 'No stim_start or stim_end has been found inside epochs or events.' + ' You can directly specify their value as argument "stim_start"' + ' and "stim_end"') + + # this part of the code transforms the data format. + efel_blocks = [] + for bl in blocks: + efel_segments = [] + for seg in bl.segments: + traces = [] + count_traces = 0 + analogsignals = seg.analogsignals + + for sig in analogsignals: + traces.append({}) + traces[count_traces]['T'] = sig.times.rescale('ms').magnitude + traces[count_traces]['V'] = sig.rescale('mV').magnitude + traces[count_traces]['stim_start'] = [stim_start] + traces[count_traces]['stim_end'] = [stim_end] + count_traces += 1 + + efel_segments.append(traces) + efel_blocks.append(efel_segments) + + return efel_blocks diff --git a/efel/tests/neo_test_files/create_neo_files.py b/efel/tests/neo_test_files/create_neo_files.py new file mode 100644 index 0000000..63f3dca --- /dev/null +++ b/efel/tests/neo_test_files/create_neo_files.py @@ -0,0 +1,66 @@ + +import neo +import efel +import quantities as pq + + +#first file +file_name = "neo_test_file_no_times.mat" + +bl = neo.core.Block() +seg = neo.core.Segment() +data = range(10) +rate = 1000*pq.Hz +signal = neo.core.AnalogSignal(data, sampling_rate=rate, units="mV") +seg.analogsignals.append(signal) +bl.segments.append(seg) +neo.io.NeoMatlabIO(filename=file_name).write_block(bl) + + +#second with times in epoch +file_name = "neo_test_file_epoch_times.mat" + +bl = neo.core.Block() +seg = neo.core.Segment() +data = range(10) +rate = 1000*pq.Hz +signal = neo.core.AnalogSignal(data, sampling_rate=rate, units="mV") +seg.analogsignals.append(signal) +seg.epochs.append(neo.core.Epoch(times=pq.Quantity([0.0,20.0], units=pq.ms), name="stim")) +bl.segments.append(seg) +neo.io.NeoMatlabIO(filename=file_name).write_block(bl) + + + + + + +#events complete +file_name = "neo_test_file_events_time.mat" + +bl = neo.core.Block() +seg = neo.core.Segment() +data = range(10) +rate = 1000*pq.Hz +signal = neo.core.AnalogSignal(data, sampling_rate=rate, units="mV") +seg.analogsignals.append(signal) +seg.events.append(neo.core.Event(times=[0.0]*pq.ms, units=pq.ms, name="stim_start" )) +seg.events.append(neo.core.Event(times=[20.0]*pq.ms, units=pq.ms, name="stim_end" )) +bl.segments.append(seg) +neo.io.NeoMatlabIO(filename=file_name).write_block(bl) + +#events incomplete +file_name = "neo_test_file_events_time_incomplete.mat" + +bl = neo.core.Block() +seg = neo.core.Segment() +data = range(10) +rate = 1000*pq.Hz +signal = neo.core.AnalogSignal(data, sampling_rate=rate, units="mV") +seg.analogsignals.append(signal) +seg.events.append(neo.core.Event(times=[0.0]*pq.ms, units=pq.ms , name="stim_start" )) +bl.segments.append(seg) +neo.io.NeoMatlabIO(filename=file_name).write_block(bl) + + + diff --git a/efel/tests/neo_test_files/neo_test_file_epoch_times.mat b/efel/tests/neo_test_files/neo_test_file_epoch_times.mat new file mode 100644 index 0000000..6ad8e04 Binary files /dev/null and b/efel/tests/neo_test_files/neo_test_file_epoch_times.mat differ diff --git a/efel/tests/neo_test_files/neo_test_file_events_time.mat b/efel/tests/neo_test_files/neo_test_file_events_time.mat new file mode 100644 index 0000000..23c24bb Binary files /dev/null and b/efel/tests/neo_test_files/neo_test_file_events_time.mat differ diff --git a/efel/tests/neo_test_files/neo_test_file_events_time_incomplete.mat b/efel/tests/neo_test_files/neo_test_file_events_time_incomplete.mat new file mode 100644 index 0000000..a1a56cd Binary files /dev/null and b/efel/tests/neo_test_files/neo_test_file_events_time_incomplete.mat differ diff --git a/efel/tests/neo_test_files/neo_test_file_no_times.mat b/efel/tests/neo_test_files/neo_test_file_no_times.mat new file mode 100644 index 0000000..f0a1a05 Binary files /dev/null and b/efel/tests/neo_test_files/neo_test_file_no_times.mat differ diff --git a/efel/tests/test_io.py b/efel/tests/test_io.py index 7002750..513df95 100644 --- a/efel/tests/test_io.py +++ b/efel/tests/test_io.py @@ -10,6 +10,11 @@ os.path.abspath(__file__)), 'testdata') +neo_test_files_dir = os.path.join( + os.path.dirname( + os.path.abspath(__file__)), + 'neo_test_files') + meanfrequency1_filename = os.path.join(testdata_dir, 'basic', 'mean_frequency_1.txt') @@ -118,3 +123,246 @@ def test_load_fragment_allcolumns(): time_numpy = numpy.loadtxt(meanfrequency1_filename) numpy.testing.assert_array_equal(time_io, time_numpy) + + +def test_load_neo_file_stim_time_arg(): + import efel + file_name = os.path.join(neo_test_files_dir, "neo_test_file_no_times.mat") + + # test load_neo_file without stim time + nt.assert_raises(ValueError, efel.io.load_neo_file, file_name) + # test load_neo_file with stim time arguments + result = efel.io.load_neo_file(file_name, stim_start=0, stim_end=20) + # test load_neo_file with stim time incomplete arguments + nt.assert_raises( + ValueError, + efel.io.load_neo_file, + file_name, + stim_start=0) + nt.assert_equal( + True, all( + result[0][0][0]['T'] == [ + 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])) + nt.assert_equal( + True, all( + result[0][0][0]['V'] == [ + [0], [1], [2], [3], [4], [5], [6], [7], [8], [9]])) + nt.assert_equal(result[0][0][0]['stim_start'], [0.0]) + nt.assert_equal(result[0][0][0]['stim_end'], [20.0]) + + +def test_extract_stim_times_from_neo_data_two_epochs(): + import neo + import efel + import quantities as pq + + bl = neo.core.Block() + seg = neo.core.Segment() + data = range(10) + rate = 1000 * pq.Hz + signal = neo.core.AnalogSignal(data, sampling_rate=rate, units="mV") + seg.analogsignals.append(signal) + seg.epochs.append( + neo.core.Epoch( + times=pq.Quantity([0.0, 20.0], + units=pq.ms), + name="stim")) + seg.epochs.append( + neo.core.Epoch( + times=pq.Quantity([0.0, 20.0], + units=pq.ms), + name="stim")) + bl.segments.append(seg) + + nt.assert_raises( + ValueError, + efel.io.extract_stim_times_from_neo_data, + [bl], + None, + None) + + +def test_extract_stim_times_from_neo_data_two_events_start(): + import neo + import efel + import quantities as pq + + bl = neo.core.Block() + seg = neo.core.Segment() + data = range(10) + rate = 1000 * pq.Hz + signal = neo.core.AnalogSignal(data, sampling_rate=rate, units="mV") + seg.analogsignals.append(signal) + seg.events.append( + neo.core.Event( + times=[0.0] * pq.ms, + units=pq.ms, + name="stim_start")) + seg.events.append( + neo.core.Event( + times=[20.0] * pq.ms, + units=pq.ms, + name="stim_start")) + bl.segments.append(seg) + + nt.assert_raises( + ValueError, + efel.io.extract_stim_times_from_neo_data, + [bl], + None, + None) + + +def test_extract_stim_times_from_neo_data_two_events_end(): + import neo + import efel + import quantities as pq + + bl = neo.core.Block() + seg = neo.core.Segment() + data = range(10) + rate = 1000 * pq.Hz + signal = neo.core.AnalogSignal(data, sampling_rate=rate, units="mV") + seg.analogsignals.append(signal) + seg.events.append( + neo.core.Event( + times=[0.0] * pq.ms, + units=pq.ms, + name="stim_end")) + seg.events.append( + neo.core.Event( + times=[20.0] * pq.ms, + units=pq.ms, + name="stim_end")) + bl.segments.append(seg) + + nt.assert_raises( + ValueError, + efel.io.extract_stim_times_from_neo_data, + [bl], + None, + None) + + +def test_extract_stim_times_from_neo_data_start_in_epoch_event(): + import neo + import efel + import quantities as pq + + bl = neo.core.Block() + seg = neo.core.Segment() + data = range(10) + rate = 1000 * pq.Hz + signal = neo.core.AnalogSignal(data, sampling_rate=rate, units="mV") + seg.analogsignals.append(signal) + seg.epochs.append( + neo.core.Epoch( + times=pq.Quantity([0.0, 20.0], + units=pq.ms), + name="stim")) + seg.events.append( + neo.core.Event( + times=[0.0] * pq.ms, + units=pq.ms, + name="stim_start")) + bl.segments.append(seg) + + nt.assert_raises( + ValueError, + efel.io.extract_stim_times_from_neo_data, + [bl], + None, + None) + + +def test_extract_stim_times_from_neo_data_end_in_epoch_event(): + import neo + import efel + import quantities as pq + + bl = neo.core.Block() + seg = neo.core.Segment() + data = range(10) + rate = 1000 * pq.Hz + signal = neo.core.AnalogSignal(data, sampling_rate=rate, units="mV") + seg.analogsignals.append(signal) + seg.epochs.append( + neo.core.Epoch( + times=pq.Quantity([0.0, 20.0], + units=pq.ms), + name="stim")) + seg.events.append( + neo.core.Event( + times=[0.0] * pq.ms, + units=pq.ms, + name="stim_end")) + bl.segments.append(seg) + + nt.assert_raises( + ValueError, + efel.io.extract_stim_times_from_neo_data, + [bl], + None, + None) + + +def test_extract_stim_times_from_neo_data_event_time_list(): + import neo + import efel + import quantities as pq + + bl = neo.core.Block() + seg = neo.core.Segment() + data = range(10) + rate = 1000 * pq.Hz + signal = neo.core.AnalogSignal(data, sampling_rate=rate, units="mV") + seg.analogsignals.append(signal) + seg.events.append( + neo.core.Event( + times=[ + 0.0, + 1.0] * pq.ms, + units=pq.ms, + name="stim_start")) + seg.events.append( + neo.core.Event( + times=[ + 10.0, + 20.0] * pq.ms, + units=pq.ms, + name="stim_end")) + bl.segments.append(seg) + + nt.assert_equal( + (0.0, 20.0), efel.io.extract_stim_times_from_neo_data( + [bl], None, None)) + + +def test_load_neo_file_stim_time_epoch(): + import efel + file_name = os.path.join( + neo_test_files_dir, + "neo_test_file_epoch_times.mat") + + result = efel.io.load_neo_file(file_name) + nt.assert_equal(result[0][0][0]['stim_start'], [0.0]) + nt.assert_equal(result[0][0][0]['stim_end'], [20.0]) + + +def test_load_neo_file_stim_time_events(): + import efel + file_name = os.path.join( + neo_test_files_dir, + "neo_test_file_events_time.mat") + + result = efel.io.load_neo_file(file_name) + nt.assert_equal(result[0][0][0]['stim_start'], [0.0]) + nt.assert_equal(result[0][0][0]['stim_end'], [20.0]) + + +def test_load_neo_file_stim_time_events_incomplete(): + import efel + file_name = os.path.join(neo_test_files_dir, + "neo_test_file_events_time_incomplete.mat") + + nt.assert_raises(ValueError, efel.io.load_neo_file, file_name) diff --git a/setup.py b/setup.py index 069f76d..7255902 100644 --- a/setup.py +++ b/setup.py @@ -74,7 +74,8 @@ name="efel", version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), - install_requires=['numpy>=1.6', 'six'], + install_requires=['numpy>=1.6', 'six', 'neo>=0.5.0', 'quantities', + 'scipy'], packages=['efel'], author="BlueBrain Project, EPFL", maintainer="Werner Van Geit",