diff --git a/doc/source/authors.rst b/doc/source/authors.rst index 031523c60..f850324f4 100644 --- a/doc/source/authors.rst +++ b/doc/source/authors.rst @@ -104,6 +104,7 @@ and may not be the current affiliation of a contributor. * Sai Asish Yamani [51] * Kevin Doran [52] * Aditya Singh (github) +* Arthur031221 (github) 1. Centre de Recherche en Neuroscience de Lyon, CNRS UMR5292 - INSERM U1028 - Université Claude Bernard Lyon 1 2. Unité de Neuroscience, Information et Complexité, CNRS UPR 3293, Gif-sur-Yvette, France diff --git a/neo/core/analogsignal.py b/neo/core/analogsignal.py index 252224f16..d7e64355e 100644 --- a/neo/core/analogsignal.py +++ b/neo/core/analogsignal.py @@ -506,13 +506,16 @@ def time_slice(self, t_start, t_stop): the nearest sampling bin. The time bin for t_stop will be chosen to make the duration of the resultant signal as close as possible to t_stop - t_start. This means that for a given duration, the size - of the slice will always be the same. + of the slice will always be the same. Either parameter can also be + None, in which case that end of the signal is not cut. """ # checking start time and transforming to start index if t_start is None: i = 0 - t_start = 0 * pq.s + # index 0 is self.t_start, and the stop index below is an offset + # from t_start, so the signal's own start is the origin here + t_start = self.t_start else: i = self.time_index(t_start) diff --git a/neo/test/coretest/test_analogsignal.py b/neo/test/coretest/test_analogsignal.py index b79c17f32..09b5e94dc 100644 --- a/neo/test/coretest/test_analogsignal.py +++ b/neo/test/coretest/test_analogsignal.py @@ -542,6 +542,43 @@ def test__time_slice__no_explicit_time(self): assert_array_equal(result.magnitude, targ.magnitude) assert_same_sub_schema(result, targ) + def test__time_slice__no_explicit_start_measures_from_signal_start(self): + # t_start=None means "do not cut the start", so the stop index has to be + # counted from the signal's own start. Counting it from 0 s instead + # lands in several different ways: too many samples, too few, a negative + # index that slices from the end, and an index past the end that raises. + # The last row is a sub-sample offset, where the two origins round to + # different indices even though the offset is under half a period. + cases = [ + (20, 1.0 * pq.Hz, 5.0 * pq.s, 7.0 * pq.s, 2), + (20, 1.0 * pq.Hz, -2.0 * pq.s, 1.0 * pq.s, 3), + (20, 1.0 * pq.Hz, -2.0 * pq.s, -1.0 * pq.s, 1), + (20, 1.0 * pq.Hz, 30.0 * pq.s, 33.0 * pq.s, 3), + (50, 1.0 * pq.kHz, 0.4 * pq.ms, 10.6 * pq.ms, 10), + ] + for n, rate, t_start, t_stop, n_expected in cases: + data = np.arange(float(n)).reshape(n, 1) * pq.mV + signal = AnalogSignal(data, sampling_rate=rate, t_start=t_start) + + result = signal.time_slice(None, t_stop) + + self.assertIsInstance(result, AnalogSignal) + assert_neo_object_is_compliant(result) + self.assertEqual(len(result), n_expected, f"t_start={t_start}, t_stop={t_stop}") + self.assertEqual(result.t_start, t_start) + assert_array_equal(result.magnitude, signal.magnitude[:n_expected]) + assert_arrays_almost_equal(result.times, signal.time_slice(t_start, t_stop).times, 1e-12 * pq.ms) + + def test__time_slice__no_explicit_start_refuses_what_explicit_start_refuses(self): + # None has to behave as passing the signal's own t_start does, which + # includes refusing a t_stop past the end rather than returning a slice + # measured from zero. This call returns 1600 samples without the fix. + signal = AnalogSignal( + np.arange(2000.0).reshape(2000, 1) * pq.mV, sampling_rate=1.0 * pq.kHz, t_start=-0.5 * pq.s + ) + self.assertRaises(ValueError, signal.time_slice, signal.t_start, 1.6 * pq.s) + self.assertRaises(ValueError, signal.time_slice, None, 1.6 * pq.s) + def test__time_slice_deepcopy_data(self): result = self.signal1.time_slice(None, None)