From 718637a4fd4326b234aa9cea6d9108ed516f5a60 Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:19:35 +0200 Subject: [PATCH 01/23] add include_t_stop flag and implement False behavior --- elephant/spike_train_synchrony.py | 23 +++++++++++++++++++-- elephant/statistics.py | 4 ++-- elephant/test/test_spike_train_synchrony.py | 2 -- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/elephant/spike_train_synchrony.py b/elephant/spike_train_synchrony.py index 946a24ae2..b56c37e8b 100644 --- a/elephant/spike_train_synchrony.py +++ b/elephant/spike_train_synchrony.py @@ -266,9 +266,11 @@ def __init__(self, spiketrains, bin_size=None, binary=True, spread=0, - tolerance=1e-8): + tolerance=1e-8, + include_t_stop=True): self.annotated = False + self.include_t_stop = include_t_stop super(Synchrotool, self).__init__(spiketrains=spiketrains, bin_size=bin_size, @@ -391,6 +393,12 @@ def annotate_synchrofacts(self): """ Annotate the complexity of each spike in the ``self.epoch.array_annotations`` *in-place*. + + Warns + ----- + UserWarning + If spikes fall too close to `t_stop`. + Those spikes will be annotated with `NaN`. """ epoch_complexities = self.epoch.array_annotations['complexity'] right_edges = ( @@ -407,7 +415,18 @@ def annotate_synchrofacts(self): spike_to_epoch_idx = np.searchsorted( right_edges, st.times.rescale(self.epoch.times.units).magnitude.flatten()) - complexity_per_spike = epoch_complexities[spike_to_epoch_idx] + + # Initialize complexity_per_spike with NaNs + complexity_per_spike = np.full(spike_to_epoch_idx.shape, np.nan) + # Iterate through spike_to_epoch_idx and assign values or NaN + for i, idx in enumerate(spike_to_epoch_idx): + if 0 <= idx < len(epoch_complexities): + complexity_per_spike[i] = epoch_complexities[idx] + else: + warnings.warn( + "Some spikes in the input Spike Train are too close to t_stop and will be annotated with NaN." + "Consider setting include_t_stop=True in the Synchrotool class to address this." + ) st.array_annotate(complexity=complexity_per_spike) diff --git a/elephant/statistics.py b/elephant/statistics.py index 0ab389572..f9822160b 100644 --- a/elephant/statistics.py +++ b/elephant/statistics.py @@ -1434,8 +1434,8 @@ def __init__(self, spiketrains, raise ValueError('Spread must be >=0') self.input_spiketrains = spiketrains - self.t_start = spiketrains[0].t_start - self.t_stop = spiketrains[0].t_stop + self.t_start = spiketrains[0].t_start.copy() + self.t_stop = spiketrains[0].t_stop.copy() self.sampling_rate = sampling_rate self.bin_size = bin_size self.binary = binary diff --git a/elephant/test/test_spike_train_synchrony.py b/elephant/test/test_spike_train_synchrony.py index 58be525eb..1bbd78546 100644 --- a/elephant/test/test_spike_train_synchrony.py +++ b/elephant/test/test_spike_train_synchrony.py @@ -206,8 +206,6 @@ def _test_template(self, spiketrains, correct_complexities, sampling_rate, for st in spiketrains] assert_array_equal(annotations, correct_complexities) - for a in annotations: - self.assertEqual(a.dtype, np.dtype(np.uint16).type) if mode == 'extract': correct_spike_times = [ From 960690388389130244fdbc47da0fc74ff77ebfc2 Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:56:08 +0200 Subject: [PATCH 02/23] add tests --- elephant/spike_train_synchrony.py | 13 +++++++- elephant/statistics.py | 4 +-- elephant/test/test_spike_train_synchrony.py | 37 +++++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/elephant/spike_train_synchrony.py b/elephant/spike_train_synchrony.py index b56c37e8b..7d5042cbc 100644 --- a/elephant/spike_train_synchrony.py +++ b/elephant/spike_train_synchrony.py @@ -267,7 +267,7 @@ def __init__(self, spiketrains, binary=True, spread=0, tolerance=1e-8, - include_t_stop=True): + include_t_stop=False): self.annotated = False self.include_t_stop = include_t_stop @@ -279,6 +279,17 @@ def __init__(self, spiketrains, spread=spread, tolerance=tolerance) + if self.include_t_stop: + self.t_stop += self.bin_size + if spread == 0: + self.time_histogram, self.complexity_histogram = \ + self._histogram_no_spread() + self.epoch = self._epoch_no_spread() + else: + self.epoch = self._epoch_with_spread() + self.time_histogram, self.complexity_histogram = \ + self._histogram_with_spread() + def delete_synchrofacts(self, threshold, in_place=False, mode='delete'): """ Delete or extract synchronous spiking events. diff --git a/elephant/statistics.py b/elephant/statistics.py index f9822160b..0ab389572 100644 --- a/elephant/statistics.py +++ b/elephant/statistics.py @@ -1434,8 +1434,8 @@ def __init__(self, spiketrains, raise ValueError('Spread must be >=0') self.input_spiketrains = spiketrains - self.t_start = spiketrains[0].t_start.copy() - self.t_stop = spiketrains[0].t_stop.copy() + self.t_start = spiketrains[0].t_start + self.t_stop = spiketrains[0].t_stop self.sampling_rate = sampling_rate self.bin_size = bin_size self.binary = binary diff --git a/elephant/test/test_spike_train_synchrony.py b/elephant/test/test_spike_train_synchrony.py index 1bbd78546..471430f1d 100644 --- a/elephant/test/test_spike_train_synchrony.py +++ b/elephant/test/test_spike_train_synchrony.py @@ -486,6 +486,43 @@ def test_wrong_input_errors(self): synchrofact_obj.delete_synchrofacts, -1) + def test_regression_PR_612_index_out_of_bounds_raise_warning(self): + """ + https://github.com/NeuralEnsemble/elephant/pull/612 + """ + sampling_rate = 1/pq.ms + st = neo.SpikeTrain(np.arange(0, 11)*pq.ms, t_start=0*pq.ms, t_stop=10*pq.ms) + + synchrotool_instance = Synchrotool([st, st], sampling_rate, spread=0) + + with self.assertWarns(UserWarning) as cm: + synchrotool_instance.annotate_synchrofacts() + + self.assertIn("Some spikes in the input Spike Train are too close to t_stop", str(cm.warning)) + + def test_regression_PR_612_index_out_of_bounds_annotate_nan(self): + """ + https://github.com/NeuralEnsemble/elephant/pull/612 + """ + sampling_rate = 1/pq.ms + st = neo.SpikeTrain(np.arange(0, 11)*pq.ms, t_start=0*pq.ms, t_stop=10*pq.ms) + + synchrotool_instance = Synchrotool([st, st], sampling_rate, spread=0) + synchrotool_instance.annotate_synchrofacts() + self.assertTrue(np.isnan(st.array_annotations['complexity'][-1])) + + def test_regression_PR_612_index_out_of_bounds_annotate_include_t_stop(self): + """ + https://github.com/NeuralEnsemble/elephant/pull/612 + """ + sampling_rate = 1/pq.ms + st = neo.SpikeTrain(np.arange(0, 11)*pq.ms, t_start=0*pq.ms, t_stop=10*pq.ms) + + synchrotool_instance = Synchrotool([st, st], sampling_rate, spread=0, include_t_stop=True) + synchrotool_instance.annotate_synchrofacts() + self.assertFalse(np.isnan(st.array_annotations['complexity'][-1])) # non NaN + self.assertEqual(len(st.array_annotations['complexity']), len(st)) # all spikes annotated + if __name__ == '__main__': unittest.main() From 52fae9cbde175f9a970c71e54f4c1f2beb719e4d Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:56:31 +0200 Subject: [PATCH 03/23] fix pep8 --- elephant/test/test_spike_train_synchrony.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elephant/test/test_spike_train_synchrony.py b/elephant/test/test_spike_train_synchrony.py index 471430f1d..cf644104e 100644 --- a/elephant/test/test_spike_train_synchrony.py +++ b/elephant/test/test_spike_train_synchrony.py @@ -520,8 +520,8 @@ def test_regression_PR_612_index_out_of_bounds_annotate_include_t_stop(self): synchrotool_instance = Synchrotool([st, st], sampling_rate, spread=0, include_t_stop=True) synchrotool_instance.annotate_synchrofacts() - self.assertFalse(np.isnan(st.array_annotations['complexity'][-1])) # non NaN - self.assertEqual(len(st.array_annotations['complexity']), len(st)) # all spikes annotated + self.assertFalse(np.isnan(st.array_annotations['complexity'][-1])) # non NaN + self.assertEqual(len(st.array_annotations['complexity']), len(st)) # all spikes annotated if __name__ == '__main__': From f2e84b129703bc421c622a7ba5348183f4a6bf57 Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Wed, 17 Jul 2024 09:49:43 +0200 Subject: [PATCH 04/23] add parameter to docstring --- elephant/spike_train_synchrony.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/elephant/spike_train_synchrony.py b/elephant/spike_train_synchrony.py index 7d5042cbc..ab2fffd64 100644 --- a/elephant/spike_train_synchrony.py +++ b/elephant/spike_train_synchrony.py @@ -255,6 +255,15 @@ class Synchrotool(Complexity): This class inherits from :class:`elephant.statistics.Complexity`, see its documentation for more details and input parameters description. + Parameters + ---------- + include_t_stop : bool, optional + If True, the end of the spike train (`t_stop`) is included in the + analysis, ensuring that any spikes close to `t_stop` are properly + annotated. + Default is False. + + See also -------- elephant.statistics.Complexity From 59f283a7346d29c86f029b7c77cf33210f4ed117 Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:35:13 +0200 Subject: [PATCH 05/23] implement changes --- elephant/spike_train_synchrony.py | 54 ++++++++------------- elephant/statistics.py | 11 +++-- elephant/test/test_spike_train_synchrony.py | 20 ++------ 3 files changed, 31 insertions(+), 54 deletions(-) diff --git a/elephant/spike_train_synchrony.py b/elephant/spike_train_synchrony.py index ab2fffd64..57a6cb229 100644 --- a/elephant/spike_train_synchrony.py +++ b/elephant/spike_train_synchrony.py @@ -261,7 +261,7 @@ class Synchrotool(Complexity): If True, the end of the spike train (`t_stop`) is included in the analysis, ensuring that any spikes close to `t_stop` are properly annotated. - Default is False. + Default is True. See also @@ -276,28 +276,19 @@ def __init__(self, spiketrains, binary=True, spread=0, tolerance=1e-8, - include_t_stop=False): + include_t_stop=True): self.annotated = False - self.include_t_stop = include_t_stop - - super(Synchrotool, self).__init__(spiketrains=spiketrains, - bin_size=bin_size, - sampling_rate=sampling_rate, - binary=binary, - spread=spread, - tolerance=tolerance) - - if self.include_t_stop: - self.t_stop += self.bin_size - if spread == 0: - self.time_histogram, self.complexity_histogram = \ - self._histogram_no_spread() - self.epoch = self._epoch_no_spread() - else: - self.epoch = self._epoch_with_spread() - self.time_histogram, self.complexity_histogram = \ - self._histogram_with_spread() + + super(Synchrotool, self).__init__( + spiketrains=spiketrains, + bin_size=bin_size, + sampling_rate=sampling_rate, + binary=binary, + spread=spread, + tolerance=tolerance, + t_stop=spiketrains[0].t_stop + (1 / sampling_rate) if include_t_stop else None, + ) def delete_synchrofacts(self, threshold, in_place=False, mode='delete'): """ @@ -427,7 +418,7 @@ def annotate_synchrofacts(self): self.epoch.times.units).magnitude.flatten() ) - for idx, st in enumerate(self.input_spiketrains): + for st in self.input_spiketrains: # all indices of spikes that are within the half-open intervals # defined by the boundaries @@ -435,18 +426,13 @@ def annotate_synchrofacts(self): spike_to_epoch_idx = np.searchsorted( right_edges, st.times.rescale(self.epoch.times.units).magnitude.flatten()) - - # Initialize complexity_per_spike with NaNs - complexity_per_spike = np.full(spike_to_epoch_idx.shape, np.nan) - # Iterate through spike_to_epoch_idx and assign values or NaN - for i, idx in enumerate(spike_to_epoch_idx): - if 0 <= idx < len(epoch_complexities): - complexity_per_spike[i] = epoch_complexities[idx] - else: - warnings.warn( - "Some spikes in the input Spike Train are too close to t_stop and will be annotated with NaN." - "Consider setting include_t_stop=True in the Synchrotool class to address this." - ) + try: + complexity_per_spike = epoch_complexities[spike_to_epoch_idx] + except IndexError: + raise ValueError( + "Some spikes in the input Spike Train may be too close or right at t_stop, they can not be binned " + "and therefore are not annotated. " + "Consider setting include_t_stop=True in the Synchrotool class to address this.") st.array_annotate(complexity=complexity_per_spike) diff --git a/elephant/statistics.py b/elephant/statistics.py index 0ab389572..346768bc0 100644 --- a/elephant/statistics.py +++ b/elephant/statistics.py @@ -1423,7 +1423,10 @@ def __init__(self, spiketrains, bin_size=None, binary=True, spread=0, - tolerance=1e-8): + tolerance=1e-8, + t_start=None, + t_stop=None, + ): check_neo_consistency(spiketrains, object_type=neo.SpikeTrain) @@ -1434,8 +1437,10 @@ def __init__(self, spiketrains, raise ValueError('Spread must be >=0') self.input_spiketrains = spiketrains - self.t_start = spiketrains[0].t_start - self.t_stop = spiketrains[0].t_stop + self.t_start = spiketrains[0].t_start if t_start is None else t_start + self.t_stop = spiketrains[0].t_stop if t_stop is None else t_stop + for st in self.input_spiketrains: + st.t_stop = self.t_stop self.sampling_rate = sampling_rate self.bin_size = bin_size self.binary = binary diff --git a/elephant/test/test_spike_train_synchrony.py b/elephant/test/test_spike_train_synchrony.py index cf644104e..2439be78c 100644 --- a/elephant/test/test_spike_train_synchrony.py +++ b/elephant/test/test_spike_train_synchrony.py @@ -493,25 +493,12 @@ def test_regression_PR_612_index_out_of_bounds_raise_warning(self): sampling_rate = 1/pq.ms st = neo.SpikeTrain(np.arange(0, 11)*pq.ms, t_start=0*pq.ms, t_stop=10*pq.ms) - synchrotool_instance = Synchrotool([st, st], sampling_rate, spread=0) + synchrotool_instance = Synchrotool([st, st], sampling_rate, spread=0, include_t_stop=False) - with self.assertWarns(UserWarning) as cm: + with self.assertRaises(ValueError): synchrotool_instance.annotate_synchrofacts() - self.assertIn("Some spikes in the input Spike Train are too close to t_stop", str(cm.warning)) - - def test_regression_PR_612_index_out_of_bounds_annotate_nan(self): - """ - https://github.com/NeuralEnsemble/elephant/pull/612 - """ - sampling_rate = 1/pq.ms - st = neo.SpikeTrain(np.arange(0, 11)*pq.ms, t_start=0*pq.ms, t_stop=10*pq.ms) - - synchrotool_instance = Synchrotool([st, st], sampling_rate, spread=0) - synchrotool_instance.annotate_synchrofacts() - self.assertTrue(np.isnan(st.array_annotations['complexity'][-1])) - - def test_regression_PR_612_index_out_of_bounds_annotate_include_t_stop(self): + def test_regression_PR_612_index_out_of_bounds(self): """ https://github.com/NeuralEnsemble/elephant/pull/612 """ @@ -520,7 +507,6 @@ def test_regression_PR_612_index_out_of_bounds_annotate_include_t_stop(self): synchrotool_instance = Synchrotool([st, st], sampling_rate, spread=0, include_t_stop=True) synchrotool_instance.annotate_synchrofacts() - self.assertFalse(np.isnan(st.array_annotations['complexity'][-1])) # non NaN self.assertEqual(len(st.array_annotations['complexity']), len(st)) # all spikes annotated From 805ad796ef4aee72f97d486b3678eaaa6c2d9cb6 Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:38:17 +0200 Subject: [PATCH 06/23] update docstring --- elephant/spike_train_synchrony.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/elephant/spike_train_synchrony.py b/elephant/spike_train_synchrony.py index 57a6cb229..4f7f2df1b 100644 --- a/elephant/spike_train_synchrony.py +++ b/elephant/spike_train_synchrony.py @@ -405,11 +405,10 @@ def annotate_synchrofacts(self): Annotate the complexity of each spike in the ``self.epoch.array_annotations`` *in-place*. - Warns + Raises ----- - UserWarning - If spikes fall too close to `t_stop`. - Those spikes will be annotated with `NaN`. + ValueError + If spikes fall too close to `t_stop` and can not be associated with a bin. """ epoch_complexities = self.epoch.array_annotations['complexity'] right_edges = ( From 025e22b689d973ef5340d737282addc8cb01642c Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:55:47 +0200 Subject: [PATCH 07/23] add flag for tests --- elephant/test/test_spike_train_synchrony.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/elephant/test/test_spike_train_synchrony.py b/elephant/test/test_spike_train_synchrony.py index 2439be78c..cd45da4c8 100644 --- a/elephant/test/test_spike_train_synchrony.py +++ b/elephant/test/test_spike_train_synchrony.py @@ -197,7 +197,8 @@ def _test_template(self, spiketrains, correct_complexities, sampling_rate, spiketrains, sampling_rate=sampling_rate, binary=binary, - spread=spread) + spread=spread, + include_t_stop=False) # test annotation synchrofact_obj.annotate_synchrofacts() @@ -448,7 +449,8 @@ def test_correct_transfer_of_spiketrain_attributes(self): [spiketrain], spread=0, sampling_rate=sampling_rate, - binary=False) + binary=False, + include_t_stop=False) synchrofact_obj.delete_synchrofacts( mode='delete', in_place=True, From 05f0414107529e7de69bc44543e0ccb160c4968d Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:02:00 +0200 Subject: [PATCH 08/23] fix docstring --- elephant/spike_train_synchrony.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elephant/spike_train_synchrony.py b/elephant/spike_train_synchrony.py index 4f7f2df1b..13e19a26b 100644 --- a/elephant/spike_train_synchrony.py +++ b/elephant/spike_train_synchrony.py @@ -261,7 +261,7 @@ class Synchrotool(Complexity): If True, the end of the spike train (`t_stop`) is included in the analysis, ensuring that any spikes close to `t_stop` are properly annotated. - Default is True. + Default: True. See also From a2d2a9317bd5a148586d2726170c6a4d172d3365 Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Fri, 26 Jul 2024 14:40:42 +0200 Subject: [PATCH 09/23] change t_stop back --- elephant/spike_train_synchrony.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/elephant/spike_train_synchrony.py b/elephant/spike_train_synchrony.py index 13e19a26b..5bb38a53b 100644 --- a/elephant/spike_train_synchrony.py +++ b/elephant/spike_train_synchrony.py @@ -20,7 +20,7 @@ import warnings from collections import namedtuple -from copy import deepcopy +from copy import deepcopy, copy import neo import numpy as np @@ -279,6 +279,7 @@ def __init__(self, spiketrains, include_t_stop=True): self.annotated = False + initial_t_stop = copy(spiketrains[0].t_stop) super(Synchrotool, self).__init__( spiketrains=spiketrains, @@ -290,6 +291,9 @@ def __init__(self, spiketrains, t_stop=spiketrains[0].t_stop + (1 / sampling_rate) if include_t_stop else None, ) + for st in spiketrains: + st.t_stop = initial_t_stop + def delete_synchrofacts(self, threshold, in_place=False, mode='delete'): """ Delete or extract synchronous spiking events. From a02a0d8c598afb9416035a3092269569f39967a1 Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:48:19 +0200 Subject: [PATCH 10/23] add new flag ignore_shared_time to BinnedSpiketrain --- elephant/conversion.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/elephant/conversion.py b/elephant/conversion.py index f3686d643..2b9f0c7d1 100644 --- a/elephant/conversion.py +++ b/elephant/conversion.py @@ -293,6 +293,8 @@ class BinnedSpikeTrain(object): The sparse matrix format. By default, CSR format is used to perform slicing and computations efficiently. Default: 'csr' + ignore_shared_time : bool, optional + If True, check for binning outside of common interval is possible. Raises ------ @@ -335,7 +337,8 @@ class BinnedSpikeTrain(object): """ def __init__(self, spiketrains, bin_size=None, n_bins=None, t_start=None, - t_stop=None, tolerance=1e-8, sparse_format="csr"): + t_stop=None, tolerance=1e-8, sparse_format="csr", + ignore_shared_time=False): if sparse_format not in ("csr", "csc"): raise ValueError(f"Invalid 'sparse_format': {sparse_format}. " "Available: 'csr' and 'csc'") @@ -352,6 +355,7 @@ def __init__(self, spiketrains, bin_size=None, n_bins=None, t_start=None, self.n_bins = n_bins self._bin_size = bin_size self.units = None # will be set later + self.ignore_shared_time = ignore_shared_time # Check all parameter, set also missing values self._resolve_input_parameters(spiketrains) # Now create the sparse matrix @@ -531,14 +535,15 @@ def check_consistency(): tolerance = self.tolerance if tolerance is None: tolerance = 0 - if self._t_start < start_shared - tolerance \ - or self._t_stop > stop_shared + tolerance: - raise ValueError("'t_start' ({t_start}) or 't_stop' ({t_stop}) is " - "outside of the shared [{start_shared}, " - "{stop_shared}] interval".format( - t_start=self.t_start, t_stop=self.t_stop, - start_shared=start_shared, - stop_shared=stop_shared)) + if not self.ignore_shared_time: + if self._t_start < start_shared - tolerance \ + or self._t_stop > stop_shared + tolerance: + raise ValueError("'t_start' ({t_start}) or 't_stop' ({t_stop}) is " + "outside of the shared [{start_shared}, " + "{stop_shared}] interval".format( + t_start=self.t_start, t_stop=self.t_stop, + start_shared=start_shared, + stop_shared=stop_shared)) if self.n_bins is None: # bin_size is provided From b0537b7349bb51ce1f18216d88454202d5fdbadb Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:48:46 +0200 Subject: [PATCH 11/23] make use of new flag in Complexity class no_spread --- elephant/spike_train_synchrony.py | 4 ---- elephant/statistics.py | 11 ++++++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/elephant/spike_train_synchrony.py b/elephant/spike_train_synchrony.py index 5bb38a53b..78a688a63 100644 --- a/elephant/spike_train_synchrony.py +++ b/elephant/spike_train_synchrony.py @@ -279,7 +279,6 @@ def __init__(self, spiketrains, include_t_stop=True): self.annotated = False - initial_t_stop = copy(spiketrains[0].t_stop) super(Synchrotool, self).__init__( spiketrains=spiketrains, @@ -291,9 +290,6 @@ def __init__(self, spiketrains, t_stop=spiketrains[0].t_stop + (1 / sampling_rate) if include_t_stop else None, ) - for st in spiketrains: - st.t_stop = initial_t_stop - def delete_synchrofacts(self, threshold, in_place=False, mode='delete'): """ Delete or extract synchronous spiking events. diff --git a/elephant/statistics.py b/elephant/statistics.py index 346768bc0..8bc558bae 100644 --- a/elephant/statistics.py +++ b/elephant/statistics.py @@ -1162,12 +1162,14 @@ def time_histogram(spiketrains, bin_size, t_start=None, t_stop=None, if binary: binned_spiketrain = BinnedSpikeTrain(spiketrains, t_start=t_start, - t_stop=t_stop, bin_size=bin_size + t_stop=t_stop, bin_size=bin_size, + ignore_shared_time=True ).binarize(copy=False) else: binned_spiketrain = BinnedSpikeTrain(spiketrains, t_start=t_start, - t_stop=t_stop, bin_size=bin_size + t_stop=t_stop, bin_size=bin_size, + ignore_shared_time=True ) bin_hist: Union[int, ndarray] = binned_spiketrain.get_num_of_spikes(axis=0) @@ -1439,8 +1441,6 @@ def __init__(self, spiketrains, self.input_spiketrains = spiketrains self.t_start = spiketrains[0].t_start if t_start is None else t_start self.t_stop = spiketrains[0].t_stop if t_stop is None else t_stop - for st in self.input_spiketrains: - st.t_stop = self.t_stop self.sampling_rate = sampling_rate self.bin_size = bin_size self.binary = binary @@ -1487,7 +1487,8 @@ def _histogram_no_spread(self): # clip the spike trains before summing time_hist = time_histogram(self.input_spiketrains, self.bin_size, - binary=self.binary) + binary=self.binary, + t_stop=self.t_stop) time_hist_magnitude = time_hist.magnitude.flatten() From 7f83ba748a3b8c7d50fcfaaf8d7b82f5b6564840 Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:50:19 +0200 Subject: [PATCH 12/23] remove unnecessary import --- elephant/spike_train_synchrony.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elephant/spike_train_synchrony.py b/elephant/spike_train_synchrony.py index 78a688a63..13e19a26b 100644 --- a/elephant/spike_train_synchrony.py +++ b/elephant/spike_train_synchrony.py @@ -20,7 +20,7 @@ import warnings from collections import namedtuple -from copy import deepcopy, copy +from copy import deepcopy import neo import numpy as np From a62f302a36ae3c3865d03abed217e700abcf4a2e Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:53:02 +0200 Subject: [PATCH 13/23] readd type check of for annotations --- elephant/test/test_spike_train_synchrony.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/elephant/test/test_spike_train_synchrony.py b/elephant/test/test_spike_train_synchrony.py index cd45da4c8..9e7f879fa 100644 --- a/elephant/test/test_spike_train_synchrony.py +++ b/elephant/test/test_spike_train_synchrony.py @@ -207,6 +207,8 @@ def _test_template(self, spiketrains, correct_complexities, sampling_rate, for st in spiketrains] assert_array_equal(annotations, correct_complexities) + for a in annotations: + self.assertEqual(a.dtype, np.dtype(np.uint16).type) if mode == 'extract': correct_spike_times = [ From 88db22e5b8d8b0d8c19897caf386795ff78e386a Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:54:58 +0200 Subject: [PATCH 14/23] no need to pass parameter in other tests --- elephant/test/test_spike_train_synchrony.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/elephant/test/test_spike_train_synchrony.py b/elephant/test/test_spike_train_synchrony.py index 9e7f879fa..421a47e79 100644 --- a/elephant/test/test_spike_train_synchrony.py +++ b/elephant/test/test_spike_train_synchrony.py @@ -197,8 +197,7 @@ def _test_template(self, spiketrains, correct_complexities, sampling_rate, spiketrains, sampling_rate=sampling_rate, binary=binary, - spread=spread, - include_t_stop=False) + spread=spread) # test annotation synchrofact_obj.annotate_synchrofacts() @@ -451,8 +450,7 @@ def test_correct_transfer_of_spiketrain_attributes(self): [spiketrain], spread=0, sampling_rate=sampling_rate, - binary=False, - include_t_stop=False) + binary=False) synchrofact_obj.delete_synchrofacts( mode='delete', in_place=True, From 70b9e9e3668732eb144da639cf9b866e77098bdd Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:40:01 +0200 Subject: [PATCH 15/23] pass t_start to time_histogram --- elephant/statistics.py | 1 + 1 file changed, 1 insertion(+) diff --git a/elephant/statistics.py b/elephant/statistics.py index 8bc558bae..92e0c5090 100644 --- a/elephant/statistics.py +++ b/elephant/statistics.py @@ -1488,6 +1488,7 @@ def _histogram_no_spread(self): time_hist = time_histogram(self.input_spiketrains, self.bin_size, binary=self.binary, + t_start=self.t_start, t_stop=self.t_stop) time_hist_magnitude = time_hist.magnitude.flatten() From 118d3fb28262f52966bd527814a0345d6b03ae49 Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:49:18 +0200 Subject: [PATCH 16/23] update docstring for ignore_shared_time --- elephant/conversion.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/elephant/conversion.py b/elephant/conversion.py index 2b9f0c7d1..1bba94713 100644 --- a/elephant/conversion.py +++ b/elephant/conversion.py @@ -294,7 +294,18 @@ class BinnedSpikeTrain(object): slicing and computations efficiently. Default: 'csr' ignore_shared_time : bool, optional - If True, check for binning outside of common interval is possible. + If `True`, the method allows `t_start` and `t_stop` to extend beyond + the shared time interval across all spike trains. This means that the + binning process can include spikes that occur outside the common + time range. + If `False` (default), the method enforces that `t_start` and `t_stop` + must fall within the shared time interval of all spike trains. If + either `t_start` or `t_stop` lies outside this range, a `ValueError` + is raised, ensuring that only the time period where all spike trains + overlap is considered for binning. + Use this parameter when you want to include spikes outside the common + time interval, understanding that it may result in bins that do not + have contributions from all spike trains. Raises ------ @@ -1235,18 +1246,7 @@ def discretise_spiketimes(spiketrains, sampling_rate): Examples -------- - >>> import neo - >>> import numpy as np - >>> import quantities as pq - >>> from elephant import conversion - >>> - >>> np.random.seed(1) - >>> times = (np.arange(10) + np.random.uniform(size=10)) * pq.ms - >>> spiketrain = neo.SpikeTrain(times, t_stop=10*pq.ms) - >>> - >>> spiketrain.times - array([0.417022 , 1.72032449, 2.00011437, 3.30233257, 4.14675589, - 5.09233859, 6.18626021, 7.34556073, 8.39676747, 9.53881673]) * ms + >>> import neo: >>> >>> discretised_spiketrain = conversion.discretise_spiketimes(spiketrain, ... 1 / pq.ms) From f1e0202820d71d61775972e7438515e51c0652e0 Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:49:35 +0200 Subject: [PATCH 17/23] add tests for ignore_shared_time_interval --- elephant/test/test_conversion.py | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/elephant/test/test_conversion.py b/elephant/test/test_conversion.py index 21e8a0b29..7e14e0ecb 100644 --- a/elephant/test/test_conversion.py +++ b/elephant/test/test_conversion.py @@ -218,6 +218,13 @@ def setUp(self): self.bin_size = 1 * pq.s self.tolerance = 1e-8 + # Create some sample spike trains with different start and stop times + self.spiketrains = ( + neo.SpikeTrain([0.1, 0.5, 1.0, 1.5, 2.0] * pq.s, t_start=0.0 * pq.s, t_stop=2.5 * pq.s), + neo.SpikeTrain([0.2, 0.6, 1.1, 1.6, 2.0] * pq.s, t_start=0.1 * pq.s, t_stop=2.0 * pq.s), + neo.SpikeTrain([0.3, 0.7, 1.2, 1.7, 2.1] * pq.s, t_start=0.2 * pq.s, t_stop=2.1 * pq.s), + ) + def test_binarize(self): spiketrains = [self.spiketrain_a, self.spiketrain_b, self.spiketrain_a, self.spiketrain_b] @@ -723,6 +730,34 @@ def test_binned_spiketrain_rounding(self): assert_array_equal(bst.to_array().nonzero()[1], np.arange(120000)) + def test_binned_spiketrain_ignore_shared_time_false_raises_error(self): + """ + Test that a ValueError is raised when ignore_shared_time is False and + t_start or t_stop is outside the shared interval. + """ + t_start = 0.0 * pq.s # Outside shared interval (shared start is 0.2 s) + t_stop = 2.5 * pq.s # Outside shared interval (shared stop is 2.0 s) + + with self.assertRaises(ValueError): + cv.BinnedSpikeTrain(spiketrains=self.spiketrains, bin_size=self.bin_size, + t_start=t_start, t_stop=t_stop, ignore_shared_time=False) + + def test_binned_spiketrain_ignore_shared_time_true_allows_outside_interval(self): + """ + Test that no error is raised when ignore_shared_time is True, even if + t_start or t_stop is outside the shared interval. + """ + t_start = 0.0 * pq.s # Outside shared interval (shared start is 0.2 s) + t_stop = 2.5 * pq.s # Outside shared interval (shared stop is 2.0 s) + + try: + _ = cv.BinnedSpikeTrain(spiketrains=self.spiketrains, bin_size=self.bin_size, + t_start=t_start, t_stop=t_stop, ignore_shared_time=True) + # If we reach this point, the test should pass. + self.assertTrue(True) + except ValueError: + self.fail("BinnedSpikeTrain raised ValueError unexpectedly when ignore_shared_time=True") + class DiscretiseSpiketrainsTestCase(unittest.TestCase): def setUp(self): From 7d821f74f79405f572aa29ad7473b74d9216ea4b Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:06:31 +0200 Subject: [PATCH 18/23] fix doctest --- elephant/conversion.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/elephant/conversion.py b/elephant/conversion.py index 1bba94713..45cbf906d 100644 --- a/elephant/conversion.py +++ b/elephant/conversion.py @@ -1246,7 +1246,18 @@ def discretise_spiketimes(spiketrains, sampling_rate): Examples -------- - >>> import neo: + >>> import neo + >>> import numpy as np + >>> import quantities as pq + >>> from elephant import conversion + >>> + >>> np.random.seed(1) + >>> times = (np.arange(10) + np.random.uniform(size=10)) * pq.ms + >>> spiketrain = neo.SpikeTrain(times, t_stop=10*pq.ms) + >>> + >>> spiketrain.times + array([0.417022 , 1.72032449, 2.00011437, 3.30233257, 4.14675589, + 5.09233859, 6.18626021, 7.34556073, 8.39676747, 9.53881673]) * ms >>> >>> discretised_spiketrain = conversion.discretise_spiketimes(spiketrain, ... 1 / pq.ms) From 3779ff5e2d30b5a7f16ddb519e721813d6086f9e Mon Sep 17 00:00:00 2001 From: Moritz-Alexander-Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:35:13 +0200 Subject: [PATCH 19/23] fix pep8 --- elephant/conversion.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/elephant/conversion.py b/elephant/conversion.py index 45cbf906d..16b2dbf27 100644 --- a/elephant/conversion.py +++ b/elephant/conversion.py @@ -294,17 +294,17 @@ class BinnedSpikeTrain(object): slicing and computations efficiently. Default: 'csr' ignore_shared_time : bool, optional - If `True`, the method allows `t_start` and `t_stop` to extend beyond - the shared time interval across all spike trains. This means that the - binning process can include spikes that occur outside the common + If `True`, the method allows `t_start` and `t_stop` to extend beyond + the shared time interval across all spike trains. This means that the + binning process can include spikes that occur outside the common time range. - If `False` (default), the method enforces that `t_start` and `t_stop` - must fall within the shared time interval of all spike trains. If - either `t_start` or `t_stop` lies outside this range, a `ValueError` - is raised, ensuring that only the time period where all spike trains + If `False` (default), the method enforces that `t_start` and `t_stop` + must fall within the shared time interval of all spike trains. If + either `t_start` or `t_stop` lies outside this range, a `ValueError` + is raised, ensuring that only the time period where all spike trains overlap is considered for binning. - Use this parameter when you want to include spikes outside the common - time interval, understanding that it may result in bins that do not + Use this parameter when you want to include spikes outside the common + time interval, understanding that it may result in bins that do not have contributions from all spike trains. Raises From a65c14ff894df75e8d0c7aa06e4a89a3cf8ee89f Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Wed, 18 Sep 2024 11:29:49 +0200 Subject: [PATCH 20/23] typo --- elephant/conversion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elephant/conversion.py b/elephant/conversion.py index 16b2dbf27..184d1af10 100644 --- a/elephant/conversion.py +++ b/elephant/conversion.py @@ -369,7 +369,7 @@ def __init__(self, spiketrains, bin_size=None, n_bins=None, t_start=None, self.ignore_shared_time = ignore_shared_time # Check all parameter, set also missing values self._resolve_input_parameters(spiketrains) - # Now create the sparse matrix + # Now create the sparse matrix. self.sparse_matrix = self._create_sparse_matrix( spiketrains, sparse_format=sparse_format) From 54e7e8c165b0ad407bd0cbc8cea90ba286d3b217 Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:15:04 +0200 Subject: [PATCH 21/23] edit typo --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6f6651146..dc887fb9f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ nosetests.xml *.tmp* .idea/ venv/ +.venv env/ .pytest_cache/ **/*/__pycache__ From 6284e795b60bd3fd769215bb96e1d4232643354b Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:22:06 +0200 Subject: [PATCH 22/23] edit gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index dc887fb9f..6d7e89ace 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ ######################################### -# Editor temporary/working/backup files # +# Editor temporary/working/backup files .#* [#]*# *~ From b2e32ab22653c10665eda4bda2e1f33e945f282d Mon Sep 17 00:00:00 2001 From: Moritz Kern <92092328+Moritz-Alexander-Kern@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:25:52 +0200 Subject: [PATCH 23/23] add test checking correct binning --- elephant/test/test_conversion.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/elephant/test/test_conversion.py b/elephant/test/test_conversion.py index 7e14e0ecb..6a9a1cb04 100644 --- a/elephant/test/test_conversion.py +++ b/elephant/test/test_conversion.py @@ -758,6 +758,29 @@ def test_binned_spiketrain_ignore_shared_time_true_allows_outside_interval(self) except ValueError: self.fail("BinnedSpikeTrain raised ValueError unexpectedly when ignore_shared_time=True") + def test_ignore_shared_time_correct_binning(self): + # Create spike trains with different time ranges + st1 = neo.SpikeTrain([0.5, 1.5, 2.5, 3.5] * pq.s, t_start=0.0 * pq.s, t_stop=4.0 * pq.s) + st2 = neo.SpikeTrain([1.0, 2.0, 3.0, 4.0] * pq.s, t_start=1.0 * pq.s, t_stop=5.0 * pq.s) + st3 = neo.SpikeTrain([1.5, 2.5, 3.5, 5.5] * pq.s, t_start=1.5 * pq.s, t_stop=5.5 * pq.s) + + spiketrains = [st1, st2, st3] + bin_size = 1 * pq.s + + # Test with ignore_shared_time=True + bst_ignore = cv.BinnedSpikeTrain(spiketrains, bin_size=bin_size, + t_start=0 * pq.s, t_stop=6 * pq.s, + ignore_shared_time=True) + self.assertEqual(bst_ignore.t_start, 0 * pq.s) + self.assertEqual(bst_ignore.t_stop, 6 * pq.s) + self.assertEqual(bst_ignore.n_bins, 6) + expected_array_ignore = np.array([ + [1, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 0], + [0, 1, 1, 1, 0, 1] + ]) + assert_array_equal(bst_ignore.to_array(), expected_array_ignore) + class DiscretiseSpiketrainsTestCase(unittest.TestCase): def setUp(self):