creates a labeled spike data structure

                `time_signals` is list of lists (or matrix), containing a timestamp in the 
                first column (or first element of each element) and indizes that are to be 
                applied to the data in the remaining columns / elements.

            This function will not add or remove spikes, but only shift spikes according to the 
            preceding time signals.

            If you want to get spikes relative to a time signal with fixed limits, use `label_peri_signals`,
            which will leave out and duplicate spikes, but can manage overlapping time signals.

            To get the absolute spike times back, `.absolute_spike_times_from_labels` can be used
            on the resulting SpikeContainer.
            However, the order and length of the timing signals might not be correct, if eg. 
            the intervals between time signals vary in length.

            If `backup_original_spike_times_to` is set to a string, the original spike times will
            be saved as this dimension as the new (relative) spike times replace the old time dimension.

def label_by_time(self,time_signals,label_names=[],time_units='ms',time_dimension=0,copy=True,backup_original_spike_times_to=None,**kwargs):
        """
            creates a labeled spike data structure

                `time_signals` is list of lists (or matrix), containing a timestamp in the 
                first column (or first element of each element) and indizes that are to be 
                applied to the data in the remaining columns / elements.

            This function will not add or remove spikes, but only shift spikes according to the 
            preceding time signals.

            If you want to get spikes relative to a time signal with fixed limits, use `label_peri_signals`,
            which will leave out and duplicate spikes, but can manage overlapping time signals.

            To get the absolute spike times back, `.absolute_spike_times_from_labels` can be used
            on the resulting SpikeContainer.
            However, the order and length of the timing signals might not be correct, if eg. 
            the intervals between time signals vary in length.

            If `backup_original_spike_times_to` is set to a string, the original spike times will
            be saved as this dimension as the new (relative) spike times replace the old time dimension.
        """
        if self.data_format == 'empty':
            return SpikeContainer(None,units=self.units,copy_from=self)
        spike_times = self.spike_times.get_converted(time_dimension, units=time_units)[1].copy() # this is read only
        time_signals = np.array(time_signals)
        re_zeroed_spike_times = spike_times.copy() # this will be modified
        indizes = np.zeros((len(spike_times),time_signals.shape[0] -1 ))
        maximal_time_gap = np.max(np.diff(time_signals[0]))
        for t in range(len(time_signals[0])):
            if t + 1 < len(time_signals[0]):
                # we are past the last time signal
                spike_range = (spike_times > time_signals[0][t]) * (spike_times <= time_signals[0][t+1])
                indizes[spike_range,:] = [time_signals[_i][t] for _i in range(1,time_signals.shape[0])]
                re_zeroed_spike_times[spike_range] = (
                        spike_times[spike_range] - time_signals[0][t]
                    )
            else:
                # we move all spikes in the future back by this time signal
                # (this will overwrite the spike times multiple times)
                indizes[spike_times > time_signals[0][t],:] = [time_signals[_i][t] for _i in range(1,time_signals.shape[0])]
                re_zeroed_spike_times[spike_times > time_signals[0][t]] = (
                        spike_times[spike_times > time_signals[0][t]] - time_signals[0][t]
                    )
        new_spike_times = LabeledMatrix(self.spike_times.matrix,self.spike_times.labels)
        new_spike_times.add_label_dimension(label_names,indizes)
        new_spike_times.labels[0].units = time_units
        new_spike_times.matrix[:,0] = re_zeroed_spike_times
        new_spike_times.labels[0].min = 0
        new_spike_times.labels[0].max = maximal_time_gap
        if kwargs.get('recalculate_time_extent',False):
            new_spike_times.labels[0].min = np.min(re_zeroed_spike_times)
            new_spike_times.labels[0].max = np.max(re_zeroed_spike_times)
        if backup_original_spike_times_to is not None:
            new_spike_times.add_label_dimension(backup_original_spike_times_to,self[time_dimension])
        if copy:
            s = SpikeContainer(new_spike_times, copy_from=self)
            #if backup_original_spike_times_to is not None:
            #    # copying spikes
            #    time_label = self.get_label(time_dimension)
            #    s[backup_original_spike_times_to] = {'data':self[time_dimension],'label': time_label}
            return s
        else:
            #if backup_original_spike_times_to is not None:
            #    # copying spikes
            #    time_label = self.get_label(time_dimension)
            #    self[backup_original_spike_times_to] = {'data':self[time_dimension],'label': time_label}
            self.set_spike_times(new_spike_times)
            return self