def _parse_cutargs(self, *args): """Takes set of args as passed to cut or cutrel and returns (tstart, tend)""" print("WARNING: Neuron._parse_cutargs should be deprecated, use spikes.searchsorted " "directly instead") tstart = None tend = None if len(args) == 0: # passed nothing pass elif len(args) == 1: # passed None, or just tstart, or a (tstart, tend) sequence if not iterable(args[0]): if args[0] == None: pass else: # just tstart was passed tstart = args[0] elif len(args[0]) == 2: # it's a sequence tstart = args[0][0] tend = args[0][1] else: raise ValueError('sequence is too long') elif len(args) == 2: # passed tstart and tend as separate args tstart = args[0] tend = args[1] else: raise ValueError('too many arguments') if tstart in [None, 0]: # shorthand for "from first spike" - would be problematic if a spike existed at t=0 tstart = self.spikes[0] if tend in [None, -1]: # shorthand for "to last spike" - would be problematic if a spike existed at t=-1 tend = self.spikes[-1] return (tstart, tend)
def _verifyParsing(self): """Make sure timestamps of all records are in causal (increasing) order. If not, sort them""" for attrname, attr in self.__dict__.items(): if attrname.endswith('records') and iterable(attr): ts = get_record_timestamps(attr) if not issorted(ts): print('sorting %s' % attrname) if type(attr) == list: attr = list(np.asarray(attr)[ts.argsort()]) else: attr = attr[ts.argsort()] ts = get_record_timestamps(attr) assert issorted(ts) self.__dict__[attrname] = attr # update
def specgram(self, t0=None, t1=None, f0=0.1, f1=100, p0=-60, p1=None, chanis=-1, width=None, tres=None, cm='jet', colorbar=False, showstates=False, lw=4, alpha=1, relative2t0=False, lim2stim=False, title=True, reclabel=True, swapaxes=False, figsize=None): """Plot a spectrogram from t0 to t1 in sec, from f0 to f1 in Hz, and clip power values from p0 to p1 in dB, based on channel index chani of LFP data. chanis=0 uses most superficial channel, chanis=-1 uses deepest channel. If len(chanis) > 1, take mean of specified chanis. width and tres are in sec. As an alternative to cm.jet (the default), cm.gray, cm.hsv cm.terrain, and cm.cubehelix_r colormaps seem to bring out the most structure in the spectrogram. showstates controls whether to plot lines demarcating desynchronized and synchronized periods. relative2t0 controls whether to plot relative to t0, or relative to start of ADC clock. lim2stim limits the time range only to when a stimulus was on screen, i.e. to the outermost times of non-NULL din""" uns = get_ipython().user_ns self.get_data() ts = self.get_tssec() # full set of timestamps, in sec if t0 == None: t0, t1 = ts[0], ts[-1] # full duration if t1 == None: t1 = t0 + 10 # 10 sec window if lim2stim: t0, t1 = self.apply_lim2stim(t0, t1) dt = t1 - t0 if width == None: width = uns['LFPSPECGRAMWIDTH'] # sec if tres == None: tres = uns['LFPSPECGRAMTRES'] # sec assert tres <= width NFFT = intround(width * self.sampfreq) noverlap = intround(NFFT - tres * self.sampfreq) t0i, t1i = ts.searchsorted((t0, t1)) #ts = ts[t0i:t1i] # constrained set of timestamps, in sec data = self.data[:, t0i:t1i] # slice data if figsize == None: # convert from recording duration time to width in inches, 0.87 accommodates # padding around the specgram: figwidth = (dt / 1000) * 5 + 0.87 figheight = 2.5 # inches figsize = figwidth, figheight f = pl.figure(figsize=figsize) a = f.add_subplot(111) if iterable(chanis): data = data[chanis].mean(axis=0) # take mean of data on chanis else: data = data[chanis] # get single row of data at chanis #data = filter.notch(data)[0] # remove 60 Hz mains noise # convert data from uV to mV, returned t is midpoints of time bins in sec from # start of data. I think P is in mV^2?: P, freqs, t = mpl.mlab.specgram(data/1e3, NFFT=NFFT, Fs=self.sampfreq, noverlap=noverlap) if not relative2t0: t += t0 # convert t to time from start of ADC clock: # keep only freqs between f0 and f1: if f0 == None: f0 = freqs[0] if f1 == None: f1 = freqs[-1] df = f1 - f0 lo, hi = freqs.searchsorted([f0, f1]) P, freqs = P[lo:hi], freqs[lo:hi] # check for and replace zero power values (ostensibly due to gaps in recording) # before attempting to convert to dB: zis = np.where(P == 0.0) # row and column indices where P has zero power if len(zis[0]) > 0: # at least one hit P[zis] = np.finfo(np.float64).max # temporarily replace zeros with max float minnzval = P.min() # get minimum nonzero value P[zis] = minnzval # replace with min nonzero values P = 10. * np.log10(P) # convert power to dB wrt 1 mV^2? # for better visualization, clip power values to within (p0, p1) dB if p0 != None: P[P < p0] = p0 if p1 != None: P[P > p1] = p1 #self.P = P # plot horizontal bars over time demarcating different ranges of SI values, # or manually defined desynched and synched periods: statelinepos = f0 - df*0.015 # plot horizontal bars just below x axis if showstates: if showstates in [True, 'auto']: print("TODO: there's an offset plotting bug for 'auto', compare with 'manual'") si, t = self.si(plot=False) stranges, states = self.si_split(si, t) # sec STATECOLOURS = uns['LFPPRBINCOLOURS'] elif showstates == 'manual': stranges, states = [], [] for state in uns['MANUALSTATES']: for strange in uns['REC2STATE2TRANGES'][self.r.absname][state]: stranges.append(strange) states.append(state) stranges = np.vstack(stranges) # 2D array STATECOLOURS = uns['MANUALSTATECOLOURS'] else: raise ValueError('invalid value showstates=%r' % showstates) # clip stranges to t0, t1: stranges[0, 0] = max(stranges[0, 0], t0) stranges[-1, 1] = min(stranges[-1, 1], t1) if swapaxes: lines = a.vlines else: lines = a.hlines for strange, state in zip(stranges, states): clr = STATECOLOURS[state] lines(statelinepos, strange[0], strange[1], colors=clr, lw=lw, alpha=alpha, clip_on=False) # Label far left, right, top and bottom edges of imshow image. imshow interpolates # between these to place the axes ticks. Time limits are # set from midpoints of specgram time bins extent = t[0], t[-1], freqs[0], freqs[-1] #print('specgram extent: %r' % (extent,)) # flip P vertically for compatibility with imshow: im = a.imshow(P[::-1], extent=extent, cmap=cm) a.autoscale(enable=True, tight=True) a.axis('tight') # depending on relative2t0 above, x=0 represents either t0 or time ADC clock started: a.set_xlim(xmin=0, xmax=t[-1]) a.set_ylim(ymin=freqs[0], ymax=freqs[-1]) # turn off annoying "+2.41e3" type offset on x axis: formatter = mpl.ticker.ScalarFormatter(useOffset=False) a.xaxis.set_major_formatter(formatter) a.set_xlabel("time (s)") a.set_ylabel("frequency (Hz)") titlestr = lastcmd() gcfm().window.setWindowTitle(titlestr) if title: a.set_title(titlestr) if reclabel: a.text(0.994, 0.95, '%s' % self.r.absname, color='w', transform=a.transAxes, horizontalalignment='right', verticalalignment='top') f.tight_layout(pad=0.3) # crop figure to contents if colorbar: f.colorbar(im, pad=0) # creates big whitespace to the right for some reason self.f = f return P, freqs, t
def psd(self, t0=None, t1=None, f0=0.2, f1=110, p0=None, p1=None, chanis=-1, width=None, tres=None, xscale='log', figsize=(5, 5)): """Plot power spectral density from t0 to t1 in sec, from f0 to f1 in Hz, and clip power values from p0 to p1 in dB, based on channel index chani of LFP data. chanis=0 uses most superficial channel, chanis=-1 uses deepest channel. If len(chanis) > 1, take mean of specified chanis. width and tres are in sec.""" uns = get_ipython().user_ns self.get_data() ts = self.get_tssec() # full set of timestamps, in sec if t0 == None: t0, t1 = ts[0], ts[-1] # full duration if t1 == None: t1 = t0 + 10 # 10 sec window if width == None: width = uns['LFPSPECGRAMWIDTH'] # sec if tres == None: tres = uns['LFPSPECGRAMTRES'] # sec assert tres <= width NFFT = intround(width * self.sampfreq) noverlap = intround(NFFT - tres * self.sampfreq) t0i, t1i = ts.searchsorted((t0, t1)) #ts = ts[t0i:t1i] # constrained set of timestamps, in sec data = self.data[:, t0i:t1i] # slice data f = pl.figure(figsize=figsize) a = f.add_subplot(111) if iterable(chanis): data = data[chanis].mean(axis=0) # take mean of data on chanis else: data = data[chanis] # get single row of data at chanis #data = filter.notch(data)[0] # remove 60 Hz mains noise # convert data from uV to mV. I think P is in mV^2?: P, freqs = mpl.mlab.psd(data/1e3, NFFT=NFFT, Fs=self.sampfreq, noverlap=noverlap) # keep only freqs between f0 and f1: if f0 == None: f0 = freqs[0] if f1 == None: f1 = freqs[-1] lo, hi = freqs.searchsorted([f0, f1]) P, freqs = P[lo:hi], freqs[lo:hi] # check for and replace zero power values (ostensibly due to gaps in recording) # before attempting to convert to dB: zis = np.where(P == 0.0) # row and column indices where P has zero power if len(zis[0]) > 0: # at least one hit P[zis] = np.finfo(np.float64).max # temporarily replace zeros with max float minnzval = P.min() # get minimum nonzero value P[zis] = minnzval # replace with min nonzero values P = 10. * np.log10(P) # convert power to dB wrt 1 mV^2? # for better visualization, clip power values to within (p0, p1) dB if p0 != None: P[P < p0] = p0 if p1 != None: P[P > p1] = p1 #self.P = P a.plot(freqs, P, 'k-') # add SI frequency band limits: LFPPRLOBAND, LFPPRHIBAND = uns['LFPPRLOBAND'], uns['LFPPRHIBAND'] a.axvline(x=LFPPRLOBAND[0], c='r', ls='--') a.axvline(x=LFPPRLOBAND[1], c='r', ls='--') a.axvline(x=LFPPRHIBAND[0], c='b', ls='--') a.axvline(x=LFPPRHIBAND[1], c='b', ls='--') a.axis('tight') a.set_xscale(xscale) a.set_xlabel("frequency (Hz)") a.set_ylabel("power (dB)") titlestr = lastcmd() gcfm().window.setWindowTitle(titlestr) a.set_title(titlestr) a.text(0.998, 0.99, '%s' % self.r.name, color='k', transform=a.transAxes, horizontalalignment='right', verticalalignment='top') f.tight_layout(pad=0.3) # crop figure to contents self.f = f return P, freqs
def specgram(self, t0=None, t1=None, f0=0.1, f1=100, p0=-60, p1=None, chanis=-1, width=None, tres=None, cm='jet', colorbar=False, showstates=False, lw=4, alpha=1, relative2t0=False, lim2stim=False, title=True, reclabel=True, swapaxes=False, figsize=None): """Plot a spectrogram from t0 to t1 in sec, from f0 to f1 in Hz, and clip power values from p0 to p1 in dB, based on channel index chani of LFP data. chanis=0 uses most superficial channel, chanis=-1 uses deepest channel. If len(chanis) > 1, take mean of specified chanis. width and tres are in sec. As an alternative to cm.jet (the default), cm.gray, cm.hsv cm.terrain, and cm.cubehelix_r colormaps seem to bring out the most structure in the spectrogram. showstates controls whether to plot lines demarcating desynchronized and synchronized periods. relative2t0 controls whether to plot relative to t0, or relative to start of ADC clock. lim2stim limits the time range only to when a stimulus was on screen, i.e. to the outermost times of non-NULL din""" uns = get_ipython().user_ns self.get_data() ts = self.get_tssec() # full set of timestamps, in sec if t0 == None: t0, t1 = ts[0], ts[-1] # full duration if t1 == None: t1 = t0 + 10 # 10 sec window if lim2stim: t0, t1 = self.apply_lim2stim(t0, t1) dt = t1 - t0 if width == None: width = uns['LFPSPECGRAMWIDTH'] # sec if tres == None: tres = uns['LFPSPECGRAMTRES'] # sec assert tres <= width NFFT = intround(width * self.sampfreq) noverlap = intround(NFFT - tres * self.sampfreq) t0i, t1i = ts.searchsorted((t0, t1)) #ts = ts[t0i:t1i] # constrained set of timestamps, in sec data = self.data[:, t0i:t1i] # slice data if figsize == None: # convert from recording duration time to width in inches, 0.87 accommodates # padding around the specgram: figwidth = (dt / 1000) * 5 + 0.87 figheight = 2.5 # inches figsize = figwidth, figheight f = pl.figure(figsize=figsize) a = f.add_subplot(111) if iterable(chanis): data = data[chanis].mean(axis=0) # take mean of data on chanis else: data = data[chanis] # get single row of data at chanis #data = filter.notch(data)[0] # remove 60 Hz mains noise # convert data from uV to mV, returned t is midpoints of time bins in sec from # start of data. I think P is in mV^2?: P, freqs, t = mpl.mlab.specgram(data / 1e3, NFFT=NFFT, Fs=self.sampfreq, noverlap=noverlap) if not relative2t0: t += t0 # convert t to time from start of ADC clock: # keep only freqs between f0 and f1: if f0 == None: f0 = freqs[0] if f1 == None: f1 = freqs[-1] df = f1 - f0 lo, hi = freqs.searchsorted([f0, f1]) P, freqs = P[lo:hi], freqs[lo:hi] # check for and replace zero power values (ostensibly due to gaps in recording) # before attempting to convert to dB: zis = np.where( P == 0.0) # row and column indices where P has zero power if len(zis[0]) > 0: # at least one hit P[zis] = np.finfo( np.float64).max # temporarily replace zeros with max float minnzval = P.min() # get minimum nonzero value P[zis] = minnzval # replace with min nonzero values P = 10. * np.log10(P) # convert power to dB wrt 1 mV^2? # for better visualization, clip power values to within (p0, p1) dB if p0 != None: P[P < p0] = p0 if p1 != None: P[P > p1] = p1 #self.P = P # plot horizontal bars over time demarcating different ranges of SI values, # or manually defined desynched and synched periods: statelinepos = f0 - df * 0.015 # plot horizontal bars just below x axis if showstates: if showstates in [True, 'auto']: print( "TODO: there's an offset plotting bug for 'auto', compare with 'manual'" ) si, t = self.si(plot=False) stranges, states = self.si_split(si, t) # sec STATECOLOURS = uns['LFPPRBINCOLOURS'] elif showstates == 'manual': stranges, states = [], [] for state in uns['MANUALSTATES']: for strange in uns['REC2STATE2TRANGES'][ self.r.absname][state]: stranges.append(strange) states.append(state) stranges = np.vstack(stranges) # 2D array STATECOLOURS = uns['MANUALSTATECOLOURS'] else: raise ValueError('invalid value showstates=%r' % showstates) # clip stranges to t0, t1: stranges[0, 0] = max(stranges[0, 0], t0) stranges[-1, 1] = min(stranges[-1, 1], t1) if swapaxes: lines = a.vlines else: lines = a.hlines for strange, state in zip(stranges, states): clr = STATECOLOURS[state] lines(statelinepos, strange[0], strange[1], colors=clr, lw=lw, alpha=alpha, clip_on=False) # Label far left, right, top and bottom edges of imshow image. imshow interpolates # between these to place the axes ticks. Time limits are # set from midpoints of specgram time bins extent = t[0], t[-1], freqs[0], freqs[-1] #print('specgram extent: %r' % (extent,)) # flip P vertically for compatibility with imshow: im = a.imshow(P[::-1], extent=extent, cmap=cm) a.autoscale(enable=True, tight=True) a.axis('tight') # depending on relative2t0 above, x=0 represents either t0 or time ADC clock started: a.set_xlim(xmin=0, xmax=t[-1]) a.set_ylim(ymin=freqs[0], ymax=freqs[-1]) # turn off annoying "+2.41e3" type offset on x axis: formatter = mpl.ticker.ScalarFormatter(useOffset=False) a.xaxis.set_major_formatter(formatter) a.set_xlabel("time (s)") a.set_ylabel("frequency (Hz)") titlestr = lastcmd() gcfm().window.setWindowTitle(titlestr) if title: a.set_title(titlestr) if reclabel: a.text(0.994, 0.95, '%s' % self.r.absname, color='w', transform=a.transAxes, horizontalalignment='right', verticalalignment='top') f.tight_layout(pad=0.3) # crop figure to contents if colorbar: f.colorbar( im, pad=0) # creates big whitespace to the right for some reason self.f = f return P, freqs, t
def psd(self, t0=None, t1=None, f0=0.2, f1=110, p0=None, p1=None, chanis=-1, width=None, tres=None, xscale='log', figsize=(5, 5)): """Plot power spectral density from t0 to t1 in sec, from f0 to f1 in Hz, and clip power values from p0 to p1 in dB, based on channel index chani of LFP data. chanis=0 uses most superficial channel, chanis=-1 uses deepest channel. If len(chanis) > 1, take mean of specified chanis. width and tres are in sec.""" uns = get_ipython().user_ns self.get_data() ts = self.get_tssec() # full set of timestamps, in sec if t0 == None: t0, t1 = ts[0], ts[-1] # full duration if t1 == None: t1 = t0 + 10 # 10 sec window if width == None: width = uns['LFPSPECGRAMWIDTH'] # sec if tres == None: tres = uns['LFPSPECGRAMTRES'] # sec assert tres <= width NFFT = intround(width * self.sampfreq) noverlap = intround(NFFT - tres * self.sampfreq) t0i, t1i = ts.searchsorted((t0, t1)) #ts = ts[t0i:t1i] # constrained set of timestamps, in sec data = self.data[:, t0i:t1i] # slice data f = pl.figure(figsize=figsize) a = f.add_subplot(111) if iterable(chanis): data = data[chanis].mean(axis=0) # take mean of data on chanis else: data = data[chanis] # get single row of data at chanis #data = filter.notch(data)[0] # remove 60 Hz mains noise # convert data from uV to mV. I think P is in mV^2?: P, freqs = mpl.mlab.psd(data / 1e3, NFFT=NFFT, Fs=self.sampfreq, noverlap=noverlap) # keep only freqs between f0 and f1: if f0 == None: f0 = freqs[0] if f1 == None: f1 = freqs[-1] lo, hi = freqs.searchsorted([f0, f1]) P, freqs = P[lo:hi], freqs[lo:hi] # check for and replace zero power values (ostensibly due to gaps in recording) # before attempting to convert to dB: zis = np.where( P == 0.0) # row and column indices where P has zero power if len(zis[0]) > 0: # at least one hit P[zis] = np.finfo( np.float64).max # temporarily replace zeros with max float minnzval = P.min() # get minimum nonzero value P[zis] = minnzval # replace with min nonzero values P = 10. * np.log10(P) # convert power to dB wrt 1 mV^2? # for better visualization, clip power values to within (p0, p1) dB if p0 != None: P[P < p0] = p0 if p1 != None: P[P > p1] = p1 #self.P = P a.plot(freqs, P, 'k-') # add SI frequency band limits: LFPPRLOBAND, LFPPRHIBAND = uns['LFPPRLOBAND'], uns['LFPPRHIBAND'] a.axvline(x=LFPPRLOBAND[0], c='r', ls='--') a.axvline(x=LFPPRLOBAND[1], c='r', ls='--') a.axvline(x=LFPPRHIBAND[0], c='b', ls='--') a.axvline(x=LFPPRHIBAND[1], c='b', ls='--') a.axis('tight') a.set_xscale(xscale) a.set_xlabel("frequency (Hz)") a.set_ylabel("power (dB)") titlestr = lastcmd() gcfm().window.setWindowTitle(titlestr) a.set_title(titlestr) a.text(0.998, 0.99, '%s' % self.r.name, color='k', transform=a.transAxes, horizontalalignment='right', verticalalignment='top') f.tight_layout(pad=0.3) # crop figure to contents self.f = f return P, freqs