def test_chirp():
    f0 = 13.5
    f1 = f0 * 100
    dur = 0.8
    amp = 0.1
    off = 0.5
    ph = 2.1
    stim = stimuli.Chirp(start_time=0.1,
                         start_frequency=f0,
                         end_frequency=f1,
                         duration=dur,
                         amplitude=amp,
                         offset=off,
                         phase=ph)
    npts = 10000
    dt = 0.0001
    eval_data = stim.eval(t0=0, n_pts=npts, dt=dt).data
    mask_data = stim.mask(t0=0, n_pts=npts, dt=dt).data
    test_data = np.zeros(npts)

    # sin[2 pi f(0) d (f(d) / f(0))^(t / d) / ln(f(d) / f(0)]
    t = np.arange(8000) * dt
    w = 2 * np.pi * f0 * dur * (f1 / f0)**(t / dur) / np.log(f1 / f0)
    test_data[1000:9000] = off + amp * np.sin(w + ph - w[0])
    assert np.allclose(eval_data, test_data)
    assert np.all(mask_data == (eval_data != 0))

    # test eval with first timepoint not at beginning of chirp
    t2 = np.arange(5000, 10000) * dt
    assert np.allclose(stim.eval(time_values=t2).data, eval_data[5000:])

    # measure approximate frequency by interpolated zero-crossings
    d = eval_data[1000:9000] - off
    inds = np.where(np.diff(np.sign(d)))[0]
    d0 = d[inds]
    d1 = d[inds + 1]
    s = abs(d0) / abs(d1 - d0)
    zeros = (inds + s) * dt
    freqs = 0.5 / np.diff(zeros)

    # check endpoints
    assert (abs(freqs[0] / f0) - 1.0) < 0.3
    assert (abs(freqs[-1] / f1) - 1.0) < 0.1

    # check center
    ind = np.argwhere(zeros > 0.4)[0, 0]
    assert (abs(freqs[ind] / (f0 * 10)) - 1.0) < 0.1

    # test analytically determined frequencies
    freqs = f0**np.linspace(1, np.log(f1) / np.log(f0), len(t) + 1)[:-1]
    assert np.allclose(freqs, stim.frequency_at(t))
Example #2
0
    def load_stimulus_items(self, rec):
        items = []

        # Add holding offset, determine units
        if rec.clamp_mode == 'ic':
            units = 'A'
            items.append(
                stimuli.Offset(
                    start_time=0,
                    amplitude=rec.holding_current,
                    description="holding current",
                    units=units,
                ))
        elif rec.clamp_mode == 'vc':
            units = 'V'
            items.append(
                stimuli.Offset(
                    start_time=0,
                    amplitude=rec.holding_potential,
                    description="holding potential",
                    units=units,
                ))
        else:
            units = None

        # inserted test pulse?
        #if rec.has_inserted_test_pulse:
        #    self.append_item(rec.inserted_test_pulse.stimulus)
        if rec.test_pulse is not None:
            items.append(rec.test_pulse.stimulus)

        notebook = rec.meta['notebook']

        if 'Stim Wave Note' in notebook:
            # Stim Wave Note format is explained here:
            # https://alleninstitute.github.io/MIES/file/_m_i_e_s___wave_builder_8ipf.html#_CPPv319WB_GetWaveNoteEntry4wave8variable6string8variable8variable

            # read stimulus structure from notebook
            #version, epochs = rec._stim_wave_note()
            version, epochs = parser.parse_stim_wave_note(notebook)
            assert len(epochs) > 0
            scale = (1e-3 if rec.clamp_mode == 'vc' else
                     1e-12) * notebook['Stim Scale Factor']
            t = (notebook['Delay onset oodDAQ'] + notebook['Delay onset user']
                 + notebook['Delay onset auto']) * 1e-3

            # if dDAQ is active, add delay from previous channels
            if notebook['Distributed DAQ'] == 1.0:
                ddaq_delay = notebook['Delay distributed DAQ'] * 1e-3
                for dev in rec.parent.devices:
                    other_rec = rec.parent[dev]
                    if other_rec is rec:
                        break
                    #_, epochs = rec._stim_wave_note()
                    if 'Stim Wave Note' in other_rec.meta['notebook']:
                        _, other_epochs = parser.parse_stim_wave_note(
                            other_rec.meta['notebook'])
                        for ep in other_epochs:
                            dt = float(ep.get('Duration', 0)) * 1e-3
                            t += dt
                        t += ddaq_delay

            for epoch_n, epoch in enumerate(epochs):
                try:
                    if epoch['Epoch'] == 'nan':
                        # Sweep-specific entry; not sure if we need to do anything with this.
                        continue

                    stim_type = epoch.get('Type')
                    duration = float(epoch.get('Duration', 0)) * 1e-3
                    name = "Epoch %d" % int(epoch['Epoch'])
                    if stim_type == 'Square pulse':
                        item = stimuli.SquarePulse(
                            start_time=t,
                            amplitude=float(epoch['Amplitude']) * scale,
                            duration=duration,
                            description=name,
                            units=units,
                        )
                    elif stim_type == 'Pulse Train':
                        assert epoch[
                            'Poisson distribution'] == 'False', "Poisson distributed pulse train not supported"
                        assert epoch[
                            'Mixed frequency'] == 'False', "Mixed frequency pulse train not supported"
                        assert epoch[
                            'Pulse Type'] == 'Square', "Pulse train with %s pulse type not supported"
                        item = stimuli.SquarePulseTrain(
                            start_time=t,
                            n_pulses=int(epoch['Number of pulses']),
                            pulse_duration=float(epoch['Pulse duration']) *
                            1e-3,
                            amplitude=float(epoch['Amplitude']) * scale,
                            interval=float(epoch['Pulse To Pulse Length']) *
                            1e-3,
                            description=name,
                            units=units,
                        )
                    elif stim_type == 'Sin Wave':
                        # bug in stim wave note version 2: log chirp field is inverted
                        is_chirp = epoch['Log chirp'] == (
                            'False' if version <= 2 else 'True')
                        if is_chirp:
                            assert epoch[
                                'FunctionType'] == 'Sin', "Chirp wave function type %s not supported" % epoch[
                                    'Function type']
                            item = stimuli.Chirp(
                                start_time=t,
                                start_frequency=float(epoch['Frequency']),
                                end_frequency=float(epoch['End frequency']),
                                duration=duration,
                                amplitude=float(epoch['Amplitude']) * scale,
                                phase=0,
                                offset=float(epoch['Offset']) * scale,
                                description=name,
                                units=units,
                            )
                        else:
                            if epoch['FunctionType'] == 'Sin':
                                phase = 0
                            elif epoch['FunctionType'] == 'Cos':
                                phase = np.pi / 2.0
                            else:
                                raise ValueError(
                                    "Unsupported sine wave function type: %r" %
                                    epoch['FunctionType'])

                            item = stimuli.Sine(
                                start_time=t,
                                frequency=float(epoch['Frequency']),
                                duration=duration,
                                amplitude=float(epoch['Amplitude']) * scale,
                                phase=phase,
                                offset=float(epoch['Offset']) * scale,
                                description=name,
                                units=units,
                            )
                    else:
                        print(epoch)
                        print(
                            "Warning: unknown stimulus type %s in %s sweep %s"
                            % (stim_type, self._file_path,
                               rec.meta['sweep_name']))
                        item = None
                except Exception as exc:
                    print(
                        "Warning: error reading stimulus epoch %d in %s sweep %s: %s"
                        % (epoch_n, self._file_path, rec.meta['sweep_name'],
                           str(exc)))

                t += duration
                if item is not None:
                    items.append(item)

        return items