Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions neo/core/analogsignal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
'''
This module implements :class:`AnalogSignal`, an array of analog signals.

:class:`AnalogSignal` inherits from :class:`basesignal.BaseSignal` which
derives from :class:`BaseNeo`, and from :class:`quantites.Quantity`which
:class:`AnalogSignal` inherits from :class:`basesignal.BaseSignal` which
derives from :class:`BaseNeo`, and from :class:`quantites.Quantity`which
in turn inherits from :class:`numpy.array`.

Inheritance from :class:`numpy.array` is explained here:
Expand Down Expand Up @@ -490,6 +490,7 @@ def time_slice(self, t_start, t_stop):
# sliced data
obj = super(AnalogSignal, self).__getitem__(np.arange(i, j, 1))
obj.t_start = self.t_start + i * self.sampling_period
obj._copy_data_complement(self)

return obj

Expand All @@ -506,7 +507,7 @@ def splice(self, signal, copy=False):

If `copy` is False (the default), modify the current signal in place.
If `copy` is True, return a new signal and leave the current one untouched.
In this case, the new signal will not be linked to any parent objects.
In this case, the new signal will not be linked to any parent objects.
"""
if signal.t_start < self.t_start:
raise ValueError("Cannot splice earlier than the start of the signal")
Expand Down
15 changes: 8 additions & 7 deletions neo/core/basesignal.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
http://docs.scipy.org/doc/numpy/user/basics.subclassing.html

In brief:
* Constructor :meth:`__new__` for :class:`BaseSignal` doesn't exist.
* Constructor :meth:`__new__` for :class:`BaseSignal` doesn't exist.
Only child objects :class:`AnalogSignal` and :class:`IrregularlySampledSignal`
can be created.
'''
Expand All @@ -19,6 +19,7 @@
from __future__ import absolute_import, division, print_function

import logging
from copy import deepcopy

import numpy as np
import quantities as pq
Expand All @@ -37,9 +38,9 @@ class BaseSignal(BaseNeo, pq.Quantity):
This class contains all common methods of both child classes.
It uses the following child class attributes:

:_necessary_attrs: a list of the attributes that the class must have.
:_necessary_attrs: a list of the attributes that the class must have.

:_recommended_attrs: a list of the attributes that the class may
:_recommended_attrs: a list of the attributes that the class may
optionally have.
'''

Expand All @@ -58,9 +59,9 @@ def __array_finalize__(self, obj):

User-specified values are only relevant for construction from
constructor, and these are set in __new__ in the child object.
Then they are just copied over here. Default values for the
Then they are just copied over here. Default values for the
specific attributes for subclasses (:class:`AnalogSignal`
and :class:`IrregularlySampledSignal`) are set in
and :class:`IrregularlySampledSignal`) are set in
:meth:`_array_finalize_spec`
'''
super(BaseSignal, self).__array_finalize__(obj)
Expand All @@ -83,7 +84,7 @@ def _rescale(self, signal, units=None):
'''
Check that units are present, and rescale the signal if necessary.
This is called whenever a new signal is
created from the constructor. See :meth:`__new__' in
created from the constructor. See :meth:`__new__' in
:class:`AnalogSignal` and :class:`IrregularlySampledSignal`
'''
if units is None:
Expand Down Expand Up @@ -182,7 +183,7 @@ def _copy_data_complement(self, other):
for attr in sub_at:
if attr[0] != 'signal':
setattr(self, attr[0], getattr(other, attr[0], None))
setattr(self, 'annotations', getattr(other, 'annotations', None))
setattr(self, 'annotations', deepcopy(getattr(other, 'annotations', None)))

def __rsub__(self, other, *args):
'''
Expand Down
7 changes: 6 additions & 1 deletion neo/core/epoch.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,12 @@ def _copy_data_complement(self, other):
Copy the metadata from another :class:`Epoch`.
'''
for attr in ("labels", "durations", "name", "file_origin",
"description", "annotations"):
"description"):
setattr(self, attr, getattr(other, attr, None))
self._copy_annotations(other)

def _copy_annotations(self, other):
self.annotations = deepcopy(other.annotations)

def __deepcopy__(self, memo):
cls = self.__class__
Expand Down Expand Up @@ -261,6 +265,7 @@ def time_slice(self, t_start, t_stop):

indices = (self >= _t_start) & (self <= _t_stop)
new_epc = self[indices]
new_epc._copy_annotations(self)
return new_epc

def as_array(self, units=None):
Expand Down
3 changes: 2 additions & 1 deletion neo/core/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def _copy_data_complement(self, other):
'''
for attr in ("labels", "name", "file_origin", "description",
"annotations"):
setattr(self, attr, getattr(other, attr, None))
setattr(self, attr, deepcopy(getattr(other, attr, None)))

def __deepcopy__(self, memo):
cls = self.__class__
Expand Down Expand Up @@ -237,6 +237,7 @@ def time_slice(self, t_start, t_stop):

indices = (self >= _t_start) & (self <= _t_stop)
new_evt = self[indices]
new_evt._copy_data_complement(self)

return new_evt

Expand Down
5 changes: 3 additions & 2 deletions neo/core/irregularlysampledsignal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
signals with samples taken at arbitrary time points.

:class:`IrregularlySampledSignal` inherits from :class:`basesignal.BaseSignal`
which derives from :class:`BaseNeo`, from :module:`neo.core.baseneo`,
and from :class:`quantities.Quantity`, which in turn inherits from
which derives from :class:`BaseNeo`, from :module:`neo.core.baseneo`,
and from :class:`quantities.Quantity`, which in turn inherits from
:class:`numpy.ndarray`.

Inheritance from :class:`numpy.array` is explained here:
Expand Down Expand Up @@ -401,6 +401,7 @@ def time_slice(self, t_start, t_stop):
count += 1

new_st = self[id_start:id_stop]
new_st.annotations = deepcopy(self.annotations)

return new_st

Expand Down
2 changes: 1 addition & 1 deletion neo/core/spiketrain.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ def time_slice(self, t_start, t_stop):
new_st.t_stop = min(_t_stop, self.t_stop)
if self.waveforms is not None:
new_st.waveforms = self.waveforms[indices]

new_st._copy_data_complement(self, deep_copy=True)
return new_st

def merge(self, other):
Expand Down
12 changes: 12 additions & 0 deletions neo/test/coretest/test_analogsignal.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,18 @@ def test__slice_should_let_access_to_parents_objects(self):
self.assertEqual(result.segment, self.signal1.segment)
self.assertEqual(result.channel_index, self.signal1.channel_index)

def test_time_slice_deepcopy_annotations(self):
params1 = {'test0': 'y1', 'test1': ['deeptest'], 'test2': True}
self.signal1.annotate(**params1)
result = self.signal1.time_slice(None,None)
params2 = {'test0': 'y2', 'test2': False}
self.signal1.annotate(**params2)
self.signal1.annotations['test1'][0] = 'shallowtest'

self.assertNotEqual(self.signal1.annotations['test0'],result.annotations['test0'])
self.assertNotEqual(self.signal1.annotations['test1'],result.annotations['test1'])
self.assertNotEqual(self.signal1.annotations['test2'],result.annotations['test2'])

def test__slice_should_change_sampling_period(self):
result1 = self.signal1[:2, 0]
result2 = self.signal1[::2, 0]
Expand Down
13 changes: 13 additions & 0 deletions neo/test/coretest/test_epoch.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,19 @@ def test_time_slice2(self):
self.assertEqual(result.annotations['test1'], targ.annotations['test1'])
self.assertEqual(result.annotations['test2'], targ.annotations['test2'])

def test__time_slice_deepcopy_annotations(self):
params = {'test0': 'y1', 'test1': ['deeptest'], 'test2': True}
epc = Epoch(times=[10, 20, 30] * pq.s, durations=[10, 5, 7] * pq.ms,
labels=np.array(['btn0', 'btn1', 'btn2'], dtype='S'), **params)

result = epc.time_slice(10 * pq.s, 20 * pq.s)
epc.annotate(test0='y2', test2=False)
epc.annotations['test1'][0] = 'shallowtest'

self.assertNotEqual(result.annotations['test0'], epc.annotations['test0'])
self.assertNotEqual(result.annotations['test1'], epc.annotations['test1'])
self.assertNotEqual(result.annotations['test2'], epc.annotations['test2'])

def test_time_slice_out_of_boundries(self):
params = {'test2': 'y1', 'test3': True}
epc = Epoch([1.1, 1.5, 1.7] * pq.ms, durations=[20, 40, 60] * pq.ns,
Expand Down
13 changes: 13 additions & 0 deletions neo/test/coretest/test_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,19 @@ def tests_time_slice(self):
self.assertEqual(evt.annotations['test1'], result.annotations['test1'])
self.assertEqual(evt.annotations['test2'], result.annotations['test2'])

def tests_time_slice_deepcopy_annotations(self):
params = {'test0': 'y1', 'test1': ['deeptest'], 'test2': True}
evt = Event([0.1, 0.5, 1.1, 1.5, 1.7, 2.2, 2.9, 3.0, 3.1, 3.3] * pq.ms,
name='test', description='tester',
file_origin='test.file', **params)
result = evt.time_slice(t_start=2.0, t_stop=3.0)
evt.annotate(test0='y2', test2=False)
evt.annotations['test1'][0] = 'shallowtest'

self.assertNotEqual(evt.annotations['test0'], result.annotations['test0'])
self.assertNotEqual(evt.annotations['test1'], result.annotations['test1'])
self.assertNotEqual(evt.annotations['test2'], result.annotations['test2'])

def test_time_slice_out_of_boundries(self):
params = {'test2': 'y1', 'test3': True}
evt = Event([0.1, 0.5, 1.1, 1.5, 1.7, 2.2, 2.9, 3.0, 3.1, 3.3] * pq.ms,
Expand Down
12 changes: 12 additions & 0 deletions neo/test/coretest/test_irregularysampledsignal.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,18 @@ def test_time_slice(self):
self.assertEqual(result.file_origin, 'testfile.txt')
self.assertEqual(result.annotations, {'arg1': 'test'})

def test_time_slice_deepcopy_annotations(self):
params1 = {'test0': 'y1', 'test1': ['deeptest'], 'test2': True}
self.signal1.annotate(**params1)
result = self.signal1.time_slice(None,None)
params2 = {'test0': 'y2', 'test2': False}
self.signal1.annotate(**params2)
self.signal1.annotations['test1'][0] = 'shallowtest'

self.assertNotEqual(self.signal1.annotations['test0'], result.annotations['test0'])
self.assertNotEqual(self.signal1.annotations['test1'], result.annotations['test1'])
self.assertNotEqual(self.signal1.annotations['test2'], result.annotations['test2'])

def test_time_slice_out_of_boundries(self):
targdataquant = self.data1quant
targtimequant = self.time1quant
Expand Down
17 changes: 17 additions & 0 deletions neo/test/coretest/test_spiketrain.py
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,23 @@ def test_time_slice_differnt_units(self):
self.assertEqual(t_start, result.t_start)
self.assertEqual(t_stop, result.t_stop)

def test_time_slice_deepcopy_annotations(self):
params1 = {'test0': 'y1', 'test1': ['deeptest'], 'test2': True}
self.train1.annotate(**params1)
# time_slice spike train, keep sliced spike times
t_start = 0.00012 * pq.s
t_stop = 0.0035 * pq.s
result = self.train1.time_slice(t_start, t_stop)

params2 = {'test0': 'y2', 'test2': False}
self.train1.annotate(**params2)
self.train1.annotations['test1'][0] = 'shallowtest'

self.assertNotEqual(self.train1.annotations['test0'], result.annotations['test0'])
self.assertNotEqual(self.train1.annotations['test1'], result.annotations['test1'])
self.assertNotEqual(self.train1.annotations['test2'], result.annotations['test2'])


def test_time_slice_matching_ends(self):
# time_slice spike train, keep sliced spike times
t_start = 0.1 * pq.ms
Expand Down