From 9f4a6409c1df4aab5b1d886ed56966b6416abae9 Mon Sep 17 00:00:00 2001 From: Julia Sprenger Date: Fri, 24 Jun 2016 12:02:04 +0200 Subject: [PATCH 1/3] Add "duplicate with new data" feature for spiketrain, events and epochs --- neo/core/epoch.py | 17 ++++++++++ neo/core/event.py | 17 ++++++++++ neo/core/spiketrain.py | 51 ++++++++++++++++++++++++++++ neo/test/coretest/test_epoch.py | 20 ++++++++++- neo/test/coretest/test_event.py | 16 ++++++++- neo/test/coretest/test_spiketrain.py | 36 +++++++++++++++++++- 6 files changed, 154 insertions(+), 3 deletions(-) diff --git a/neo/core/epoch.py b/neo/core/epoch.py index a02eb752b..befc9633a 100644 --- a/neo/core/epoch.py +++ b/neo/core/epoch.py @@ -162,3 +162,20 @@ def merge(self, other): other.annotations) kwargs.update(merged_annotations) return Epoch(times=times, durations=durations, labels=labels, **kwargs) + + def _copy_data_complement(self, other): + ''' + Copy the metadata from another :class:`Epoch`. + ''' + for attr in ("labels", "durations", "name", "file_origin", + "description", "annotations"): + setattr(self, attr, getattr(other, attr, None)) + + def duplicate_with_new_data(self, signal): + ''' + Create a new :class:`Epoch` with the same metadata + but different data (times, durations) + ''' + new = self.__class__(times=signal) + new._copy_data_complement(self) + return new \ No newline at end of file diff --git a/neo/core/event.py b/neo/core/event.py index 3efc8fc4a..b9baad387 100644 --- a/neo/core/event.py +++ b/neo/core/event.py @@ -149,3 +149,20 @@ def merge(self, other): other.annotations) kwargs.update(merged_annotations) return Event(times=times, labels=labels, **kwargs) + + def _copy_data_complement(self, other): + ''' + Copy the metadata from another :class:`Event`. + ''' + for attr in ("labels", "name", "file_origin", "description", + "annotations"): + setattr(self, attr, getattr(other, attr, None)) + + def duplicate_with_new_data(self, signal): + ''' + Create a new :class:`Event` with the same metadata + but different data + ''' + new = self.__class__(times=signal) + new._copy_data_complement(self) + return new \ No newline at end of file diff --git a/neo/core/spiketrain.py b/neo/core/spiketrain.py index 636708b48..67aa6e4a3 100644 --- a/neo/core/spiketrain.py +++ b/neo/core/spiketrain.py @@ -68,6 +68,21 @@ def _check_time_in_range(value, t_start, t_stop, view=False): raise ValueError("The last spike (%s) is after t_stop (%s)" % (value, t_stop)) +def _check_waveform_dimensions(spiketrain): + ''' + Verify that waveform is compliant with the waveform definition as + quantity array 3D (spike, channel_index, time) + ''' + waveforms = spiketrain.waveforms + + if not (spiketrain.size and waveforms.size): + return + + if waveforms.shape[0] != len(spiketrain): + raise ValueError("Spiketrain length (%s) does not match to number of " + "waveforms present (%s)" % (len(spiketrain), + waveforms.shape[0])) + def _new_spiketrain(cls, signal, t_stop, units=None, dtype=None, copy=True, sampling_rate=1.0 * pq.Hz, @@ -435,6 +450,42 @@ def __setslice__(self, i, j, value): _check_time_in_range(value, self.t_start, self.t_stop) super(SpikeTrain, self).__setslice__(i, j, value) + def _copy_data_complement(self, other): + ''' + Copy the metadata from another :class:`SpikeTrain`. + ''' + for attr in ("t_start", "t_stop", "waveforms", "left_sweep", + "sampling_rate", "name", "file_origin", "description", + "annotations"): + setattr(self, attr, getattr(other, attr, None)) + + def duplicate_with_new_data(self, signal, t_start=None, t_stop=None, + waveforms=None): + ''' + Create a new :class:`SpikeTrain` with the same metadata + but different data (times, t_start, t_stop) + ''' + # using previous t_start and t_stop if no values are provided + if t_start is None: + t_start = self.t_start + if t_stop is None: + t_stop = self.t_stop + if waveforms is None: + waveforms = self.waveforms + + new_st = self.__class__(signal, t_stop,waveforms=waveforms, + units=self.units) + new_st._copy_data_complement(self) + + # overwriting t_start and t_stop with new values + new_st.t_start = t_start + new_st.t_stop = t_stop + + # consistency check + _check_time_in_range(new_st, new_st.t_start, new_st.t_stop, view=True) + _check_waveform_dimensions(new_st) + return new_st + def time_slice(self, t_start, t_stop): ''' Creates a new :class:`SpikeTrain` corresponding to the time slice of diff --git a/neo/test/coretest/test_epoch.py b/neo/test/coretest/test_epoch.py index 223239950..4f9525c70 100644 --- a/neo/test/coretest/test_epoch.py +++ b/neo/test/coretest/test_epoch.py @@ -21,7 +21,8 @@ from neo.core.epoch import Epoch from neo.core import Segment from neo.test.tools import (assert_neo_object_is_compliant, - assert_arrays_equal, assert_same_sub_schema) + assert_arrays_equal, assert_arrays_almost_equal, + assert_same_sub_schema) from neo.test.generate_datasets import (get_fake_value, get_fake_values, fake_neo, TEST_ANNOTATIONS) @@ -228,6 +229,23 @@ def test__pretty(self): self.assertEqual(prepr, targ) +class TestDuplicateWithNewData(unittest.TestCase): + def setUp(self): + self.data = np.array([0.1, 0.5, 1.2, 3.3, 6.4, 7]) + self.durations = np.array([0.2, 0.4, 1.1, 2.4, 0.2, 2.0]) + self.quant = pq.ms + self.epoch = Epoch(self.data*self.quant, + durations=self.durations*self.quant) + + def test_duplicate_with_new_data(self): + signal1 = self.epoch + new_data = np.sort(np.random.uniform(0, 100, (self.epoch))) * pq.ms + signal1b = signal1.duplicate_with_new_data(new_data) + assert_arrays_almost_equal(np.asarray(signal1b), + np.asarray(new_data), 1e-12) + assert_arrays_almost_equal(np.asarray(signal1b.durations), + np.asarray(signal1.durations), 1e-12) + if __name__ == "__main__": unittest.main() diff --git a/neo/test/coretest/test_event.py b/neo/test/coretest/test_event.py index a6b7ad84c..a948cce58 100644 --- a/neo/test/coretest/test_event.py +++ b/neo/test/coretest/test_event.py @@ -21,7 +21,9 @@ from neo.core.event import Event from neo.core import Segment from neo.test.tools import (assert_neo_object_is_compliant, - assert_arrays_equal, assert_same_sub_schema) + assert_arrays_equal, + assert_arrays_almost_equal, + assert_same_sub_schema) from neo.test.generate_datasets import (get_fake_value, get_fake_values, fake_neo, TEST_ANNOTATIONS) @@ -217,6 +219,18 @@ def test__pretty(self): self.assertEqual(prepr, targ) +class TestDuplicateWithNewData(unittest.TestCase): + def setUp(self): + self.data = np.array([0.1, 0.5, 1.2, 3.3, 6.4, 7]) + self.dataquant = self.data*pq.ms + self.event = Event(self.dataquant) + + def test_duplicate_with_new_data(self): + signal1 = self.event + new_data = np.sort(np.random.uniform(0, 100, (self.event))) * pq.ms + signal1b = signal1.duplicate_with_new_data(new_data) + assert_arrays_almost_equal(np.asarray(signal1b), + np.asarray(new_data), 1e-12) if __name__ == "__main__": unittest.main() diff --git a/neo/test/coretest/test_spiketrain.py b/neo/test/coretest/test_spiketrain.py index c6b34198e..ecc139a5a 100644 --- a/neo/test/coretest/test_spiketrain.py +++ b/neo/test/coretest/test_spiketrain.py @@ -26,7 +26,9 @@ from neo.core.spiketrain import (check_has_dimensions_time, SpikeTrain, _check_time_in_range, _new_spiketrain) from neo.core import Segment, Unit -from neo.test.tools import assert_arrays_equal, assert_neo_object_is_compliant +from neo.test.tools import (assert_arrays_equal, + assert_arrays_almost_equal, + assert_neo_object_is_compliant) from neo.test.generate_datasets import (get_fake_value, get_fake_values, fake_neo, TEST_ANNOTATIONS) @@ -1135,6 +1137,38 @@ def test_time_slice_none_both(self): self.assertEqual(self.train1.t_start, result.t_start) self.assertEqual(self.train1.t_stop, result.t_stop) +class TestDuplicateWithNewData(unittest.TestCase): + def setUp(self): + self.waveforms = np.array([[[0., 1.], + [0.1, 1.1]], + [[2., 3.], + [2.1, 3.1]], + [[4., 5.], + [4.1, 5.1]], + [[6., 7.], + [6.1, 7.1]], + [[8., 9.], + [8.1, 9.1]], + [[10., 11.], + [10.1, 11.1]]]) * pq.mV + self.data = np.array([0.1, 0.5, 1.2, 3.3, 6.4, 7]) + self.dataquant = self.data*pq.ms + self.train = SpikeTrain(self.dataquant, t_stop=10.0*pq.ms, + waveforms=self.waveforms) + + def test_duplicate_with_new_data(self): + signal1 = self.train + new_data = np.sort(np.random.uniform(0, 100, (self.train))) * pq.ms + new_t_start = -10*pq.s + new_t_stop = 10*pq.s + signal1b = signal1.duplicate_with_new_data(new_data, + t_start=new_t_start, + t_stop=new_t_stop) + assert_arrays_almost_equal(np.asarray(signal1b), + np.asarray(new_data), 1e-12) + self.assertEqual(signal1b.t_start, new_t_start) + self.assertEqual(signal1b.t_stop, new_t_stop) + self.assertEqual(signal1b.sampling_rate, signal1.sampling_rate) class TestAttributesAnnotations(unittest.TestCase): def test_set_universally_recommended_attributes(self): From 6d06f436dbedf8ca8b9de99e32e7f62e92d9a309 Mon Sep 17 00:00:00 2001 From: Julia Sprenger Date: Fri, 24 Jun 2016 14:41:05 +0200 Subject: [PATCH 2/3] Minor bugfix --- neo/core/spiketrain.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/neo/core/spiketrain.py b/neo/core/spiketrain.py index 67aa6e4a3..d90a420ba 100644 --- a/neo/core/spiketrain.py +++ b/neo/core/spiketrain.py @@ -73,9 +73,13 @@ def _check_waveform_dimensions(spiketrain): Verify that waveform is compliant with the waveform definition as quantity array 3D (spike, channel_index, time) ''' + + if not spiketrain.size: + return + waveforms = spiketrain.waveforms - if not (spiketrain.size and waveforms.size): + if not (waveforms and waveforms.size): return if waveforms.shape[0] != len(spiketrain): From 4456cac5188790ddd1c52baff0f79b019c275527 Mon Sep 17 00:00:00 2001 From: John Kirkham Date: Fri, 29 Jul 2016 13:23:37 -0400 Subject: [PATCH 3/3] LICENSE: Add a bullet point for the third clause. --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index 711b7c46f..d3a0464bd 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -5,6 +5,6 @@ Redistribution and use in source and binary forms, with or without modification, * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -Neither the names of the copyright holders nor the names of the contributors may be used to endorse or promote products derived from this software without specific prior written permission. +* Neither the names of the copyright holders nor the names of the contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.