Exemple #1
0
    def create_hierarchy(cls, many_to_many):
        b = neo.Block()

        for ns in range(cls.SEGMENTS):
            b.segments.append(neo.Segment())

        channels = []
        if many_to_many:
            channels = [
                neo.RecordingChannel(name='Shared %d' % i,
                                     index=i + cls.CHANNELS)
                for i in range(cls.CHANNELS / 2)
            ]

        for ng in range(cls.CHANNEL_GROUPS):
            rcg = neo.RecordingChannelGroup()
            for nu in range(cls.UNITS):
                unit = neo.Unit()
                for ns in range(cls.SEGMENTS):
                    spike = neo.Spike(0 * pq.s)
                    unit.spikes.append(spike)
                    b.segments[ns].spikes.append(spike)

                    st = neo.SpikeTrain([] * pq.s, 0 * pq.s)
                    unit.spiketrains.append(st)
                    b.segments[ns].spiketrains.append(st)

                rcg.units.append(unit)

            if not many_to_many:
                for nc in range(cls.CHANNELS):
                    rc = neo.RecordingChannel(name='Single %d' % nc, index=nc)
                    rc.recordingchannelgroups.append(rcg)
                    rcg.recordingchannels.append(rc)
            else:
                for nc in range(cls.CHANNELS):
                    if nc % 2 == 0:
                        rc = neo.RecordingChannel(name='Single %d' % (nc / 2),
                                                  index=nc / 2)
                    else:
                        rc = channels[nc / 2]
                    rc.recordingchannelgroups.append(rcg)
                    rcg.recordingchannels.append(rc)
            rcg.channel_indexes = sp.array(
                [c.index for c in rcg.recordingchannels])
            rcg.channel_names = sp.array(
                [c.name for c in rcg.recordingchannels])

            b.recordingchannelgroups.append(rcg)

        try:
            neo.io.tools.create_many_to_one_relationship(b)
        except AttributeError:
            b.create_many_to_one_relationship()
        return b
def create_big_neo_block(name='a block'):
    t1 = time.time()
    
    print 'b', b
    bl = neo.Block(name = name)
    
    rcg = neo.RecordingChannelGroup(name = 'big group')
    bl.recordingchannelgroups.append(rcg)
    for a in range(nb_sig):
        rc = neo.RecordingChannel(name = 'rec channel {}'.format(a), index = a)
        rcg.recordingchannels.append(rc)
    
    for s in range(nb_seg):
        print ' s', s
        seg = neo.Segment(name = 'seg {}'.format(s))
        bl.segments.append(seg)
        for a in range(nb_sig):
            #~ print '  a', a
            #~ ana = neo.AnalogSignal(signal = np.empty(sig_size)*pq.mV, sampling_rate = 10*pq.kHz, t_start = -2.*pq.s,
                                #~ name = 'signal {}'.format(a), description = 'this is a big signal')
            ana = neo.AnalogSignal(signal = np.random.rand(sig_size)*pq.mV, sampling_rate = 10*pq.kHz, t_start = -2.*pq.s,
                                name = 'signal {}'.format(a), description = 'this is a big signal')
            seg.analogsignals.append(ana)
            rcg.recordingchannels[a].analogsignals.append(ana)
        
    t2 = time.time()
    print 'time for creating neo block', t2 - t1
    
    return bl
Exemple #3
0
def generate_block(n_segments=3,
                   n_channels=8,
                   n_units=3,
                   data_samples=1000,
                   feature_samples=100):
    """
    Generate a block with a single recording channel group and a number of
    segments, recording channels and units with associated analog signals
    and spike trains.
    """
    feature_len = feature_samples / data_samples

    # Create container and grouping objects
    segments = [neo.Segment(index=i) for i in range(n_segments)]

    chx = neo.ChannelIndex(index=1, name='T0')

    for i in range(n_channels):
        rc = neo.RecordingChannel(name='C%d' % i, index=i)
        rc.channelindexes = [chx]
        chx.recordingchannels.append(rc)

    units = [neo.Unit('U%d' % i) for i in range(n_units)]
    chx.units = units

    block = neo.Block()
    block.segments = segments
    block.channel_indexes = [chx]

    # Create synthetic data
    for seg in segments:
        feature_pos = np.random.randint(0, data_samples - feature_samples)

        # Analog signals: Noise with a single sinewave feature
        wave = 3 * np.sin(np.linspace(0, 2 * np.pi, feature_samples))
        for rc in chx.recordingchannels:
            sig = np.random.randn(data_samples)
            sig[feature_pos:feature_pos + feature_samples] += wave

            signal = neo.AnalogSignal(sig * pq.mV, sampling_rate=1 * pq.kHz)
            seg.analogsignals.append(signal)
            rc.analogsignals.append(signal)

        # Spike trains: Random spike times with elevated rate in short period
        feature_time = feature_pos / data_samples
        for u in units:
            random_spikes = np.random.rand(20)
            feature_spikes = np.random.rand(5) * feature_len + feature_time
            spikes = np.hstack([random_spikes, feature_spikes])

            train = neo.SpikeTrain(spikes * pq.s, 1 * pq.s)
            seg.spiketrains.append(train)
            u.spiketrains.append(train)

    block.create_many_to_one_relationship()
    return block
Exemple #4
0
def export_as_neo(ele_pos, pots):
    '''This fuction exports the potentials recorded into neo object list
    this can then be used in third party software such as elephant

    elephant provides rich additions such as spike sorting, csd
    analysis etc.'''
    import neo
    import quantities as pq
    an_sigs = []
    for ii in range(len(pots)):
        rc = neo.RecordingChannel()
        rc.coordinate = ele_pos[ii] * pq.um
        asig = neo.AnalogSignal(pots[ii] * pq.mV, sampling_rate=10000 * pq.Hz)
        rc.analogsignals = [asig]
        rc.create_relationship()
        an_sigs.append(asig)
    print 'Finished converting to neo object list'
    return an_sigs
def generate_lfp(csd_profile,
                 ele_xx,
                 ele_yy=None,
                 ele_zz=None,
                 xlims=[0., 1.],
                 ylims=[0., 1.],
                 zlims=[0., 1.],
                 res=50):
    """Forward modelling for the getting the potentials for testing CSD

        Parameters
        ----------
        csd_profile : fuction that computes True CSD profile
            Available options are (see ./csd/utility_functions.py)
            1D : gauss_1d_dipole
            2D : large_source_2D and small_source_2D
            3D : gauss_3d_dipole
        ele_xx : np.array
            Positions of the x coordinates of the electrodes
        ele_yy : np.array
            Positions of the y coordinates of the electrodes
            Defaults ot None, use in 2D or 3D cases only
        ele_zz : np.array
            Positions of the z coordinates of the electrodes
            Defaults ot None, use in 3D case only
        x_lims : [start, end]
            The starting spatial coordinate and the ending for integration
            Defaults to [0.,1.]
        y_lims : [start, end]
            The starting spatial coordinate and the ending for integration
            Defaults to [0.,1.], use only in 2D and 3D case
        z_lims : [start, end]
            The starting spatial coordinate and the ending for integration
            Defaults to [0.,1.], use only in 3D case
        res : int
            The resolution of the integration
            Defaults to 50

        Returns
        -------
        LFP : list(neo.AnalogSignal type objects)
           The potentials created by the csd profile at the electrode positions
           The electrode postions are attached as RecordingChannel's coordinate
    """
    def integrate_1D(x0, csd_x, csd, h):
        m = np.sqrt((csd_x - x0)**2 + h**2) - abs(csd_x - x0)
        y = csd * m
        I = simps(y, csd_x)
        return I

    def integrate_2D(x, y, xlin, ylin, csd, h, X, Y):
        Ny = ylin.shape[0]
        m = np.sqrt((x - X)**2 + (y - Y)**2)
        m[m < 0.0000001] = 0.0000001
        y = np.arcsinh(2 * h / m) * csd
        I = np.zeros(Ny)
        for i in range(Ny):
            I[i] = simps(y[:, i], ylin)
        F = simps(I, xlin)
        return F

    def integrate_3D(x, y, z, xlim, ylim, zlim, csd, xlin, ylin, zlin, X, Y,
                     Z):
        Nz = zlin.shape[0]
        Ny = ylin.shape[0]
        m = np.sqrt((x - X)**2 + (y - Y)**2 + (z - Z)**2)
        m[m < 0.0000001] = 0.0000001
        z = csd / m
        Iy = np.zeros(Ny)
        for j in range(Ny):
            Iz = np.zeros(Nz)
            for i in range(Nz):
                Iz[i] = simps(z[:, j, i], zlin)
            Iy[j] = simps(Iz, ylin)
        F = simps(Iy, xlin)
        return F

    dim = 1
    if ele_zz is not None:
        dim = 3
    elif ele_yy is not None:
        dim = 2
    x = np.linspace(xlims[0], xlims[1], res)
    if dim >= 2:
        y = np.linspace(ylims[0], ylims[1], res)
    if dim == 3:
        z = np.linspace(zlims[0], zlims[1], res)
    sigma = 1.0
    h = 50.
    pots = np.zeros(len(ele_xx))
    if dim == 1:
        chrg_x = np.linspace(xlims[0], xlims[1], res)
        csd = csd_profile(chrg_x)
        for ii in range(len(ele_xx)):
            pots[ii] = integrate_1D(ele_xx[ii], chrg_x, csd, h)
        pots /= 2. * sigma  # eq.: 26 from Potworowski et al
        ele_pos = ele_xx
    elif dim == 2:
        chrg_x, chrg_y = np.mgrid[xlims[0]:xlims[1]:np.complex(0, res),
                                  ylims[0]:ylims[1]:np.complex(0, res)]
        csd = csd_profile(chrg_x, chrg_y)
        for ii in range(len(ele_xx)):
            pots[ii] = integrate_2D(ele_xx[ii], ele_yy[ii], x, y, csd, h,
                                    chrg_x, chrg_y)
        pots /= 2 * np.pi * sigma
        ele_pos = np.vstack((ele_xx, ele_yy)).T
    elif dim == 3:
        chrg_x, chrg_y, chrg_z = np.mgrid[xlims[0]:xlims[1]:np.complex(0, res),
                                          ylims[0]:ylims[1]:np.complex(0, res),
                                          zlims[0]:zlims[1]:np.complex(0, res)]
        csd = csd_profile(chrg_x, chrg_y, chrg_z)
        xlin = chrg_x[:, 0, 0]
        ylin = chrg_y[0, :, 0]
        zlin = chrg_z[0, 0, :]
        for ii in range(len(ele_xx)):
            pots[ii] = integrate_3D(ele_xx[ii], ele_yy[ii], ele_zz[ii], xlims,
                                    ylims, zlims, csd, xlin, ylin, zlin,
                                    chrg_x, chrg_y, chrg_z)
        pots /= 4 * np.pi * sigma
        ele_pos = np.vstack((ele_xx, ele_yy, ele_zz)).T
    pots = np.reshape(pots, (-1, 1)) * pq.mV
    ele_pos = ele_pos * pq.mm
    lfp = []
    for ii in range(len(pots)):
        rc = neo.RecordingChannel()
        rc.coordinate = ele_pos[ii]
        asig = neo.AnalogSignal(pots[ii], sampling_rate=pq.kHz)
        rc.analogsignals = [asig]
        rc.create_relationship()
        lfp.append(asig)
    # lfp = neo.AnalogSignalArray(lfp, sampling_rate=1000*pq.Hz, units='mV')
    return lfp
class DataProvider(object):
    """ Defines all methods that should be implemented by a
    selection/data provider class.

    A `DataProvider` encapsulates
    access to a selection of data. It can be used by plugins to
    acesss data currently selected in the GUI or in saved selections.
    It also contains an attribute `progress`, a
    :class:`spykeutils.progress_indicator.ProgressIndicator` that
    can be used to report the progress of an operation (and is used
    by methods of this class if they can lead to processing times
    of half a second or more).

    This class serves as an abstract base class and should not be
    instantiated."""
    _factories = {}
    no_unit = neo.Unit(name='No Unit')
    no_segment = neo.Segment(name='No segment')
    no_channel = neo.RecordingChannel(name='No recording channel')
    no_channelgroup = neo.RecordingChannelGroup(
        name='No recording channel group')
    no_unit.annotate(unique_id=-1)
    no_segment.annotate(unique_id=-1)
    no_channel.annotate(unique_id=-1)
    no_channelgroup.annotate(unique_id=-1)

    def __init__(self, name, progress):
        self.name = name
        self.progress = progress

    def _invert_indices(self, dictionary):
        """ Invert the indices of a dictionary of dictionaries.
        """
        dict_type = type(dictionary)
        ret = dict_type()
        for i1 in dictionary:
            for i2 in dictionary[i1]:
                if not i2 in ret:
                    ret[i2] = dict_type()
                ret[i2][i1] = dictionary[i1][i2]
        return ret

    def blocks(self):
        """ Return a list of selected Block objects.

        The returned objects will contain all regular references, not just to
        selected objects.
        """
        return []

    def segments(self):
        """ Return a list of selected Segment objects.

        The returned objects will contain all regular references, not just to
        selected objects.
        """
        return []

    def recording_channel_groups(self):
        """ Return a list of selected RecordingChannelGroup objects.

        The returned objects will contain all regular references, not just to
        selected objects.
        """
        return []

    def recording_channels(self):
        """ Return a list of selected RecordingChannel objects.

        The returned objects will contain all regular references, not just to
        selected objects.
        """
        return []

    def units(self):
        """ Return a list of selected Unit objects.

        The returned objects will contain all regular references, not just to
        selected objects.
        """
        return []

    def selection_blocks(self):
        """ Return a list of selected blocks.

        The returned blocks will contain references to all other selected
        elements further down in the object hierarchy, but no references to
        elements which are not selected. The returned hierarchy is a copy,
        so changes made to it will not persist. The main purpose
        of this function is to provide an object hierarchy that can be
        saved to a neo file. It is not recommended to use it for data
        processing, the respective functions that return objects lower
        in the hierarchy are better suited for that purpose.
        """
        return []

    def spike_trains(self):
        """ Return a list of :class:`neo.core.SpikeTrain` objects.
        """
        return []

    def spike_trains_by_unit(self):
        """ Return a dictionary (indexed by Unit) of lists of
        :class:`neo.core.SpikeTrain` objects.

        If spike trains not attached to a Unit are selected, their
        dicionary key will be ``DataProvider.no_unit``.
        """
        return {}

    def spike_trains_by_segment(self):
        """ Return a dictionary (indexed by Segment) of lists of
        :class:`neo.core.SpikeTrain` objects.

        If spike trains not attached to a Segment are selected, their
        dictionary key will be ``DataProvider.no_segment``.
        """
        return {}

    def spike_trains_by_unit_and_segment(self):
        """ Return a dictionary (indexed by Unit) of dictionaries
        (indexed by Segment) of :class:`neo.core.SpikeTrain` objects.

        If there are multiple spike trains in one Segment for the same Unit,
        only the first will be contained in the returned dictionary. If spike
        trains not attached to a Unit or Segment are selected, their
        dictionary key will be ``DataProvider.no_unit`` or
        ``DataProvider.no_segment``, respectively.
        """
        return {}

    def spike_trains_by_segment_and_unit(self):
        """ Return a dictionary (indexed by Unit) of dictionaries
        (indexed by Segment) of :class:`neo.core.SpikeTrain` objects.

        If there are multiple spike trains in one Segment for the same Unit,
        only the first will be contained in the returned dictionary. If spike
        trains not attached to a Unit or Segment are selected, their
        dictionary key will be ``DataProvider.no_unit`` or
        ``DataProvider.no_segment``, respectively.
        """
        return self._invert_indices(self.spike_trains_by_unit_and_segment())

    def spikes(self):
        """ Return a list of :class:`neo.core.Spike` objects.
        """
        return []

    def spikes_by_unit(self):
        """ Return a dictionary (indexed by Unit) of lists of
        :class:`neo.core.Spike` objects.

        If spikes not attached to a Unit are selected, their
        dicionary key will be ``DataProvider.no_unit``.
        """
        return {}

    def spikes_by_segment(self):
        """ Return a dictionary (indexed by Segment) of lists of
        :class:`neo.core.Spike` objects.

        If spikes not attached to a Segment are selected, their
        dictionary key will be ``DataProvider.no_segment``.
        """
        return {}

    def spikes_by_unit_and_segment(self):
        """ Return a dictionary (indexed by Unit) of dictionaries
        (indexed by Segment) of :class:`neo.core.Spike` lists.

        If there are multiple spikes in one Segment for the same Unit,
        only the first will be contained in the returned dictionary. If
        spikes not attached to a Unit or Segment are selected, their
        dictionary key will be ``DataProvider.no_unit`` or
        ``DataProvider.no_segment``, respectively.
        """
        return {}

    def spikes_by_segment_and_unit(self):
        """ Return a dictionary (indexed by Segment) of dictionaries
        (indexed by Unit) of lists of :class:`neo.core.Spike` lists.

        If spikes not attached to a Unit or Segment are selected, their
        dictionary key will be ``DataProvider.no_unit`` or
        ``DataProvider.no_segment``, respectively.
        """
        return self._invert_indices(self.spikes_by_unit_and_segment())

    def events(self, include_array_events=True):
        """ Return a dictionary (indexed by Segment) of lists of
        Event objects.

        :param bool include_array_events: Determines if EventArray objects
            should be converted to Event objects and included in the returned
            list.
        """
        return {}

    def labeled_events(self, label, include_array_events=True):
        """ Return a dictionary (indexed by Segment) of lists of Event
        objects with the given label.

        :param str label: The name of the Event objects to be returnded
        :param bool include_array_events: Determines if EventArray objects
            should be converted to Event objects and included in the returned
            list.
        """
        return []

    def event_arrays(self):
        """ Return a dictionary (indexed by Segment) of lists of
        EventArray objects.
        """
        return {}

    def epochs(self, include_array_epochs=True):
        """ Return a dictionary (indexed by Segment) of lists of
        Epoch objects.

        :param bool include_array_epochs: Determines if EpochArray objects
            should be converted to Epoch objects and included in the returned
            list.
        """
        return {}

    def labeled_epochs(self, label, include_array_epochs=True):
        """ Return a dictionary (indexed by Segment) of lists of Epoch
        objects with the given label.

        :param str label: The name of the Epoch objects to be returnded
        :param bool include_array_epochs: Determines if EpochArray objects
            should be converted to Epoch objects and included in the returned
            list.
        """
        return []

    def epoch_arrays(self):
        """ Return a dictionary (indexed by Segment) of lists of
        EpochArray objects.
        """
        return {}

    def analog_signals(self, conversion_mode=1):
        """ Return a list of :class:`neo.core.AnalogSignal` objects.

        :param int conversion_mode: Determines what signals are returned:

            1. AnalogSignal objects only
            2. AnalogSignal objects extracted from AnalogSignalArrays only
            3. Both AnalogSignal objects and extracted AnalogSignalArrays
        """
        return []

    def analog_signals_by_segment(self, conversion_mode=1):
        """ Return a dictionary (indexed by Segment) of lists of
        :class:`neo.core.AnalogSignal` objects.

        If analog signals not attached to a Segment are selected, their
        dictionary key will be ``DataProvider.no_segment``.

        :param int conversion_mode: Determines what signals are returned:

            1. AnalogSignal objects only
            2. AnalogSignal objects extracted from AnalogSignalArrays only
            3. Both AnalogSignal objects and extracted AnalogSignalArrays
        """
        return {}

    def analog_signals_by_channel(self, conversion_mode=1):
        """ Return a dictionary (indexed by RecordingChannel) of lists
        of :class:`neo.core.AnalogSignal` objects.

        If analog signals not attached to a RecordingChannel are selected,
        their dictionary key will be ``DataProvider.no_channel``.

        :param int conversion_mode: Determines what signals are returned:

            1. AnalogSignal objects only
            2. AnalogSignal objects extracted from AnalogSignalArrays only
            3. Both AnalogSignal objects and extracted AnalogSignalArrays
        """
        return {}

    def analog_signals_by_channel_and_segment(self, conversion_mode=1):
        """ Return a dictionary (indexed by RecordingChannel) of
        dictionaries (indexed by Segment) of :class:`neo.core.AnalogSignal`
        lists.

        If analog signals not attached to a Segment or
        RecordingChannel are selected, their dictionary key will be
        ``DataProvider.no_segment`` or ``DataProvider.no_channel``,
        respectively.

        :param int conversion_mode: Determines what signals are returned:

            1. AnalogSignal objects only
            2. AnalogSignal objects extracted from AnalogSignalArrays only
            3. Both AnalogSignal objects and extracted AnalogSignalArrays
        """
        return {}

    def analog_signals_by_segment_and_channel(self, conversion_mode=1):
        """ Return a dictionary (indexed by Segment) of
        dictionaries (indexed by RecordingChannel) of :class:`neo.core.AnalogSignal`
        lists.

        If analog signals not attached to a Segment or
        RecordingChannel are selected, their dictionary key will be
        ``DataProvider.no_segment`` or ``DataProvider.no_channel``,
        respectively.

        :param int conversion_mode: Determines what signals are returned:

            1. AnalogSignal objects only
            2. AnalogSignal objects extracted from AnalogSignalArrays only
            3. Both AnalogSignal objects and extracted AnalogSignalArrays
        """
        return self._invert_indices(
            self.analog_signals_by_channel_and_segment(conversion_mode))

    def analog_signal_arrays(self):
        """ Return a list of :class:`neo.core.AnalogSignalArray` objects.
        """
        return []

    def analog_signal_arrays_by_segment(self):
        """ Return a dictionary (indexed by Segment) of lists of
        :class:`neo.core.AnalogSignalArray` objects.

        If analog signals arrays not attached to a Segment are selected,
        their dictionary key will be ``DataProvider.no_segment``.
        """
        return {}

    def analog_signal_arrays_by_channelgroup(self):
        """ Return a dictionary (indexed by RecordingChannelGroup) of
        lists of :class:`neo.core.AnalogSignalArray` objects.

        If analog signals arrays not attached to a RecordingChannel are
        selected, their dictionary key will be
        ``DataProvider.no_channelgroup``.
        """
        return {}

    def analog_signal_arrays_by_channelgroup_and_segment(self):
        """ Return a dictionary (indexed by RecordingChannelGroup) of
        dictionaries (indexed by Segment) of
        :class:`neo.core.AnalogSignalArray` objects.

        If there are multiple analog signals in one RecordingChannel for
        the same Segment, only the first will be contained in the returned
        dictionary. If analog signal arrays not attached to a Segment or
        RecordingChannelGroup are selected, their dictionary key will be
        ``DataProvider.no_segment`` or ``DataProvider.no_channelgroup``,
        respectively.
        """
        return {}

    def analog_signal_arrays_by_segment_and_channelgroup(self):
        """ Return a dictionary (indexed by RecordingChannelGroup) of
        dictionaries (indexed by Segment) of
        :class:`neo.core.AnalogSignalArray` objects.

        If there are multiple analog signals in one RecordingChannel for
        the same Segment, only the first will be contained in the returned
        dictionary. If analog signal arrays not attached to a Segment or
        RecordingChannelGroup are selected, their dictionary key will be
        ``DataProvider.no_segment`` or ``DataProvider.no_channelgroup``,
        respectively.
        """
        return self._invert_indices(
            self.analog_signal_arrays_by_channelgroup_and_segment())

    def refresh_view(self):
        """ Refresh associated views of the data.

        Use this method if when you change the neo hierarchy on which the
        selection is based (e.g. adding or removing objects). It will ensure
        that all current views on the data are updated, for example in
        Spyke Viewer.
        """
        pass

    def data_dict(self):
        """ Return a dictionary with all information to serialize the
        object.
        """
        return {}

    @classmethod
    def from_data(cls, data, progress=None):
        """ Create a new `DataProvider` object from a dictionary. This
        method is mostly for internal use.

        The respective type of `DataProvider` (e.g.
        :class:`spykeviewer.plugin_framework.data_provider_neo.DataProviderNeo`
        has to be imported in the environment where this function is
        called.

        :param dict data: A dictionary containing data from a `DataProvider`
            object, as returned by :func:`data_dict`.
        :param ProgressIndicator progress: The object where loading progress
            will be indicated.
        """
        if progress:
            return cls._factories[data['type']](data, progress)
        return cls._factories[data['type']](data)
def generate_block_for_sorting(
    nb_segment=5,
    nb_recordingchannel=4,
    sampling_rate=10.e3 * pq.Hz,
    duration=6. * pq.s,
    nb_unit=6,
    spikerate_range=[.5 * pq.Hz, 12 * pq.Hz],
    t_start=0. * pq.s,
    noise_ratio=0.2,
    use_memmap_path=None,
):

    bl = neo.Block()

    for s in range(nb_segment):
        bl.segments.append(neo.Segment(name='Segment {}'.format(s)))

    rcg = neo.RecordingChannelGroup(name='polytrode', )
    rcg.block = bl
    bl.recordingchannelgroups.append(rcg)
    for r in range(nb_recordingchannel):
        rc = neo.RecordingChannel(name='RC {}'.format(r), index=r)
        rcg.recordingchannels.append(rc)
        rc.recordingchannelgroups.append(rcg)

    # generate spiketrain
    for u in range(nb_unit):
        unit = neo.Unit(name='Unit #{}'.format(u))
        rcg.units.append(unit)
        spikerate = np.random.rand() * np.diff(
            spikerate_range) + spikerate_range[0].magnitude
        spike_times = []
        for seg in bl.segments:
            spike_times.append(
                np.random.rand(int((spikerate *
                                    (duration)).simplified)) * duration +
                t_start)
        n_total = np.sum(times.size for times in spike_times)
        all_waveforms = stupid_waveform_generator(n_total, nb_recordingchannel,
                                                  sampling_rate) * pq.mV

        n = 0
        for s, seg in enumerate(bl.segments):
            #~ print u, 's', s, spike_times[s].size
            sptr = neo.SpikeTrain(spike_times[s],
                                  t_start=t_start,
                                  t_stop=t_start + duration)
            sptr.waveforms = all_waveforms[n:n + spike_times[s].size, :, :]
            sptr.sampling_rate = sampling_rate
            sptr.left_sweep = (sptr.waveforms.shape[2] /
                               sampling_rate).rescale('ms') / 2.
            seg.spiketrains.append(sptr)
            unit.spiketrains.append(sptr)
            n += spike_times[s].size

    # generate signal = noise + spike waveform
    for i, seg in enumerate(bl.segments):
        for j, rc in enumerate(rcg.recordingchannels):
            sig_size = int((duration * sampling_rate).simplified)

            if use_memmap_path:
                filename = os.path.join(use_memmap_path,
                                        'signal {} {}'.format(i, j))
                signal = np.memmap(
                    filename,
                    dtype=float,
                    mode='w+',
                    offset=0,
                    shape=sig_size,
                )
                signal[:] = noise_ratio * np.random.randn(sig_size)
            else:
                signal = noise_ratio * np.random.randn(sig_size)

            #~ anasig = neo.AnalogSignal(signal = signal, units = 'mV', sampling_rate = sampling_rate, t_start = t_start, channel_index = j)

            for sptr in seg.spiketrains:
                for k, time in enumerate(sptr):
                    wf = sptr.waveforms[k, j, :]
                    pos = int(((time - t_start) * sampling_rate).simplified)
                    if pos + wf.size < signal.size:
                        signal[pos:pos + wf.size] += wf
            anasig = neo.AnalogSignal(signal=signal,
                                      units='mV',
                                      sampling_rate=sampling_rate,
                                      t_start=t_start,
                                      channel_index=j)
            seg.analogsignals.append(anasig)
            rc.analogsignals.append(anasig)

    try:
        neo.io.tools.create_many_to_one_relationship(bl)
    except:
        bl.create_many_to_one_relationship()

    return bl