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))
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