def load_stimulus(self, recording): """Return an instance of stimuli.Stimulus""" #### I don't know whether I should try to parse this from metadata, or just find square pulses in the command waveform. ### I think finding square pulses would be simpler, but makes the assumption that pulses are square. Which is probably usually true. ### what if I check the wavegenerator widget data for the function name (pulse) and then findSquarepulses, or raise an exception if it's a different function? if isinstance(recording, PatchClampRecording): items = [] fh = DataManager.getFileHandle(recording.meta['file_name']) seqDir = PatchEPhys.getParent(fh, 'ProtocolSequence') if seqDir is not None: dev_info = seqDir.info()['devices'][recording.device_id] if dev_info['mode'].lower() == 'vc': units = 'V' elif dev_info['mode'].lower() == 'ic': units = 'A' else: units = None if dev_info['holdingCheck']: items.append(stimuli.Offset(dev_info['holdingSpin'])) stim_pulses = PatchEPhys.getStimParams(fh) for p in stim_pulses: if p['function_type'] == 'pulse': items.append( stimuli.SquarePulse(p['start'], p['length'], p['amplitude'])) elif p['function_type'] == 'pulseTrain': items.append( stimuli.SquarePulseTrain(p['start'], p['pulse_number'], p['length'], p['amplitude'], p['period'])) desc = seqDir.shortName()[:-4] return stimuli.Stimulus(desc, items=items, units=units) else: raise Exception('not implemented yet')
def test_pulse_train(): s1 = stimuli.Stimulus("stimulus 1") sp1 = stimuli.SquarePulse(start_time=0.1, duration=0.2, amplitude=10, parent=s1) sp2 = stimuli.SquarePulse(start_time=0.2, duration=0.2, amplitude=-10, description="square pulse 2", units='A', parent=s1) # test PulseTrain pt1 = stimuli.SquarePulseTrain(start_time=0.5, n_pulses=3, pulse_duration=0.02, interval=0.1, amplitude=1.0, parent=s1, units='A') assert s1.items == (sp1, sp2, pt1) # check sub-pulse start times assert pt1.global_start_time == 0.5 assert pt1.start_time == 0.5 assert pt1.items[0].global_start_time == 0.5 assert pt1.items[0].start_time == 0.0 assert pt1.items[1].global_start_time == 0.6 assert pt1.items[1].start_time == 0.1 assert pt1.items[2].global_start_time == 0.7 assert pt1.items[2].start_time == 0.2 assert pt1.pulse_times == [0.0, 0.1, 0.2] assert pt1.global_pulse_times == [0.5, 0.6, 0.7] # test waveform eval with all three items s1_data = s1.eval(n_pts=1000, dt=0.001).data test_data = np.zeros(1000) test_data[100:300] = 10 test_data[200:400] -= 10 test_data[500:520] += 1 test_data[600:620] += 1 test_data[700:720] += 1 assert np.all(s1_data == test_data) test_mask = (s1_data != 0) test_mask[100:400] = True assert np.all(test_mask == s1.mask(n_pts=1000, dt=0.001).data) # test save/load state = s1.save() assert state == OrderedDict([ ('type', 'Stimulus'), ('args', OrderedDict([ ('start_time', 0), ('description', 'stimulus 1'), ('units', None), ])), ('items', [ OrderedDict([ ('type', 'SquarePulse'), ('args', OrderedDict([ ('start_time', 0.1), ('description', 'square pulse'), ('units', None), ('duration', 0.2), ('amplitude', 10), ])), ('items', []), ]), OrderedDict([ ('type', 'SquarePulse'), ('args', OrderedDict([ ('start_time', 0.2), ('description', 'square pulse 2'), ('units', 'A'), ('duration', 0.2), ('amplitude', -10), ])), ('items', []), ]), OrderedDict([ ('type', 'SquarePulseTrain'), ('args', OrderedDict([ ('start_time', 0.5), ('description', 'square pulse train'), ('units', 'A'), ('n_pulses', 3), ('pulse_duration', 0.02), ('amplitude', 1.0), ('interval', 0.1), ])), ('items', []), ]), ]) ]) s2 = stimuli.load_stimulus(state) assert isinstance(s2, stimuli.Stimulus) assert s2.items[0] == s1.items[0] assert s2.items[1] == s1.items[1] assert s2.items[2] == s1.items[2] assert s2.items[2].items[0] == s1.items[2].items[0] assert s2.items[2].items[1] == s1.items[2].items[1] assert s2.items[2].items[2] == s1.items[2].items[2]
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