def plotsig(x, samplingfreq_hz=None, hold=False, axis=0, welch=0, **kwargs): """ Makes two subplots, showing time-series <x> in the upper panel and its amplitude spectrum in the lower panel. Set <hold> in order to re-use a previous figure. Any additional keyword arguments are passed through to pylab.plot for both subplots. """### fs = getfs(samplingfreq_hz) if fs == None: fs = getfs(x, 2.0) if hasattr(x, 'x'): x = x.x elif hasattr(x, 'y'): x = x.y if not isnumpyarray(x): axis = 0 if isinstance(x[0], list) or isinstance(x[0], tuple): axis = 1 x = numpy.array(x, dtype='float') xwin = x = project(x, axis).swapaxes(0, axis) nsamp = x.shape[0] class Unfinished(Exception): pass if welch == 1: xwin = x * project(hanning(nsamp), len(x.shape) - 1) elif welch > 0: raise Unfinished, "Welch periodogram not yet implemented" t = numpy.arange(0, nsamp) / float(fs) ap = fft2ap(fft(xwin, axis=0), samplingfreq_hz=fs, axis=0) f = ap['freq_hz'] a = 20.0 * numpy.log10(ap['amplitude']) pylab = load_pylab() if not hold: pylab.clf() pylab.subplot(2, 1, 1) h1 = pylab.plot(t, x, **kwargs) ax = pylab.gca() ax.set_xlim(t[0], t[-1]) ax.xaxis.grid(True) ax.yaxis.grid(True) pylab.subplot(2, 1, 2) a[numpy.isinf( a )] = numpy.nan # crude workaround---pylab.plot can't cope with infinite values h2 = pylab.plot(f, a, **kwargs) ax = pylab.gca() ax.set_xlim(f[0], f[-1]) ax.xaxis.grid(True) ax.yaxis.grid(True) pylab.draw()
def ap2fft(amplitude, phase_rad=None, phase_deg=None, samplingfreq_hz=2.0, axis=0, freq_hz=None, fullfreq_hz=None, nsamp=None): """ Keyword arguments match the fields of the dict output by that fft2ap() . The inverse of d=fft2ap(X) is X = ap2fft(**d) """### fs = getfs(samplingfreq_hz) if nsamp == None: if fullfreq_hz != None: nsamp = len(fullfreq_hz) elif freq_hz != None: nsamp = len(freq_hz) * 2 - 2 else: nsamp = amplitude.shape[axis] * 2 - 2 amplitude = project(numpy.array(amplitude, dtype='float'), axis) if phase_rad == None and phase_deg == None: phase_rad = numpy.zeros(shape=amplitude.shape, dtype='float') if phase_rad != None: if not isnumpyarray(phase_rad) or phase_rad.dtype != 'float': phase_rad = numpy.array(phase_rad, dtype='float') phase_rad = project(phase_rad, axis) if phase_deg != None: if not isnumpyarray(phase_deg) or phase_deg.dtype != 'float': phase_deg = numpy.array(phase_deg, dtype='float') phase_deg = project(phase_deg, axis) if phase_rad != None and phase_deg != None: if phase_rad.shape != phase_deg.shape: raise ArgConflictError, "conflicting phase_rad and phase_deg arguments" if numpy.max( numpy.abs(phase_rad * (180.0 / numpy.pi) - phase_deg) > 1e-10): raise ArgConflictError, "conflicting phase_rad and phase_deg arguments" if phase_rad == None: phase_rad = phase_deg * (numpy.pi / 180.0) f = phase_rad * 1j f = numpy.exp(f) f = f * amplitude f *= float(nsamp) / 2.0 sub = [slice(None)] * max(axis + 1, len(f.shape)) if nsamp % 2 == 0: sub[axis] = -1 f[sub] *= 2.0 sub[axis] = slice((nsamp % 2) - 2, 0, -1) f = numpy.concatenate((f, numpy.conj(f[sub])), axis=axis) return f
def plotsig(x, samplingfreq_hz=None, hold=False, axis=0, welch=0, **kwargs): """ Makes two subplots, showing time-series <x> in the upper panel and its amplitude spectrum in the lower panel. Set <hold> in order to re-use a previous figure. Any additional keyword arguments are passed through to pylab.plot for both subplots. """### fs = getfs(samplingfreq_hz) if fs==None: fs = getfs(x,2.0) if hasattr(x, 'x'): x = x.x elif hasattr(x, 'y'): x = x.y if not isnumpyarray(x): axis = 0 if isinstance(x[0], list) or isinstance(x[0], tuple): axis = 1 x = numpy.array(x,dtype='float') xwin = x = project(x,axis).swapaxes(0, axis) nsamp = x.shape[0] class Unfinished(Exception): pass if welch==1: xwin = x * project(hanning(nsamp),len(x.shape)-1) elif welch > 0: raise Unfinished, "Welch periodogram not yet implemented" t = numpy.arange(0, nsamp) / float(fs) ap = fft2ap(fft(xwin,axis=0),samplingfreq_hz=fs,axis=0) f = ap['freq_hz'] a = 20.0 * numpy.log10(ap['amplitude']) pylab = load_pylab() if not hold: pylab.clf() pylab.subplot(2,1,1) h1 = pylab.plot(t,x,**kwargs) ax = pylab.gca() ax.set_xlim(t[0], t[-1]) ax.xaxis.grid(True) ax.yaxis.grid(True) pylab.subplot(2,1,2) a[numpy.isinf(a)] = numpy.nan # crude workaround---pylab.plot can't cope with infinite values h2 = pylab.plot(f,a,**kwargs) ax = pylab.gca() ax.set_xlim(f[0], f[-1]) ax.xaxis.grid(True) ax.yaxis.grid(True) pylab.draw()
def ampmod(w, freq_hz=1.0, phase_rad=None, phase_deg=None, amplitude=0.5, dc=0.5, samplingfreq_hz=None, duration_msec=None, duration_samples=None, axis=None, waveform=numpy.sin, **kwargs): """ Return a copy of <w> (a numpy.ndarray or a WavTools.wav object) in which the amplitude is modulated sinusoidally along the specified time <axis>. Default phase is such that amplitude is 0 at time 0, which corresponds to phase_deg=-90 if <waveform> follows sine phase, since the modulator is a raised waveform. To change this, specify either <phase_rad> or <phase_deg>. Uses wavegen() """### if isnumpyarray(w): y = w elif hasattr(w, 'y'): w = w.copy() y = w.y else: raise TypeError, "don't know how to handle this kind of carrier object" if samplingfreq_hz == None: samplingfreq_hz = getfs(w) if phase_rad == None and phase_deg == None: phase_deg = -90.0 if duration_samples == None and duration_msec == None: duration_samples = project(y, 0).shape[0] envelope = wavegen(freq_hz=freq_hz, phase_rad=phase_rad, phase_deg=phase_deg, amplitude=amplitude, dc=dc, samplingfreq_hz=samplingfreq_hz, duration_msec=duration_msec, duration_samples=duration_samples, axis=axis, waveform=waveform, **kwargs) envelope = project(envelope, len(y.shape) - 1) y = y * envelope if isnumpyarray(w): w = y else: w.y = y return w
def fft2ap(X, samplingfreq_hz=2.0, axis=0): """ Given discrete Fourier transform(s) <X> (with time along the specified <axis>), return a dict containing a properly scaled amplitude spectrum, a phase spectrum in degrees and in radians, and a frequency axis (coping with all the fiddly edge conditions). The inverse of d=fft2ap(X) is X = ap2fft(**d) """### fs = getfs(samplingfreq_hz) nsamp = int(X.shape[axis]) biggest_pos_freq = float(numpy.floor(nsamp/2)) # floor(nsamp/2) biggest_neg_freq = -float(numpy.floor((nsamp-1)/2)) # -floor((nsamp-1)/2) posfreq = numpy.arange(0.0, biggest_pos_freq+1.0) * (float(fs) / float(nsamp)) negfreq = numpy.arange(biggest_neg_freq, 0.0) * (float(fs) / float(nsamp)) fullfreq = numpy.concatenate((posfreq,negfreq)) sub = [slice(None)] * max(axis+1, len(X.shape)) sub[axis] = slice(0,len(posfreq)) X = project(X, axis)[sub] ph = numpy.angle(X) amp = numpy.abs(X) * (2.0 / float(nsamp)) if nsamp%2 == 0: sub[axis] = -1 amp[sub] /= 2.0 return {'amplitude':amp, 'phase_rad':ph, 'phase_deg':ph*(180.0/numpy.pi), 'freq_hz':posfreq, 'fullfreq_hz':fullfreq, 'samplingfreq_hz':fs, 'axis':axis}
def applyfilter(x,b=1.0,a=1.0,axis=-1,zi=None): """ Apply a causal filter (b,a) to a signal x along a specified axis, optionally given the specified initial filter state zi. Return (y,zf): the filtered signal, and the final filter state. The causalfilter object wraps this function more conveniently. """### matrixformat = isinstance(x, numpy.matrix) b = numpy.asarray(b, dtype=numpy.float64) a = numpy.asarray(a, dtype=numpy.float64) x = numpy.asarray(x, dtype=numpy.float64).view() if len(x.shape) == 0: x.shape = [1] shape = list(x.shape) if axis < 0: axis = len(x.shape) + axis if zi==None: shape[axis]=max(len(b),len(a))-1 zi=numpy.zeros(shape=shape,dtype='complex') zi = project(numpy.array(zi, dtype='complex'), axis) b = b.flatten() a = a.flatten() shape[axis] = 1 y = numpy.zeros(shape=x.shape) zf = numpy.array(zi) colon = slice(None) for sub in numpy.ndindex(tuple(shape)): # have to do it this way because filtering of non-vector signals is still bugged in scipy (non-deterministic output, intermittent crashes) sub = list(sub) sub[axis] = colon v,zf[sub].flat[:]=lfilter(b=b,a=a,x=x[sub].flatten(),zi=zi[sub].flatten()) y[sub].flat[:]=v.real if matrixformat: y = numpy.matrix(y, copy=False) return y,zf
def fft2ap(X, samplingfreq_hz=2.0, axis=0): """ Given discrete Fourier transform(s) <X> (with time along the specified <axis>), return a dict containing a properly scaled amplitude spectrum, a phase spectrum in degrees and in radians, and a frequency axis (coping with all the fiddly edge conditions). The inverse of d=fft2ap(X) is X = ap2fft(**d) """### fs = getfs(samplingfreq_hz) nsamp = int(X.shape[axis]) biggest_pos_freq = float(numpy.floor(nsamp / 2)) # floor(nsamp/2) biggest_neg_freq = -float(numpy.floor( (nsamp - 1) / 2)) # -floor((nsamp-1)/2) posfreq = numpy.arange(0.0, biggest_pos_freq + 1.0) * (float(fs) / float(nsamp)) negfreq = numpy.arange(biggest_neg_freq, 0.0) * (float(fs) / float(nsamp)) fullfreq = numpy.concatenate((posfreq, negfreq)) sub = [slice(None)] * max(axis + 1, len(X.shape)) sub[axis] = slice(0, len(posfreq)) X = project(X, axis)[sub] ph = numpy.angle(X) amp = numpy.abs(X) * (2.0 / float(nsamp)) if nsamp % 2 == 0: sub[axis] = -1 amp[sub] /= 2.0 return { 'amplitude': amp, 'phase_rad': ph, 'phase_deg': ph * (180.0 / numpy.pi), 'freq_hz': posfreq, 'fullfreq_hz': fullfreq, 'samplingfreq_hz': fs, 'axis': axis }
def ap2fft(amplitude,phase_rad=None,phase_deg=None,samplingfreq_hz=2.0,axis=0,freq_hz=None,fullfreq_hz=None,nsamp=None): """ Keyword arguments match the fields of the dict output by that fft2ap() . The inverse of d=fft2ap(X) is X = ap2fft(**d) """### fs = getfs(samplingfreq_hz) if nsamp==None: if fullfreq_hz != None: nsamp = len(fullfreq_hz) elif freq_hz != None: nsamp = len(freq_hz) * 2 - 2 else: nsamp = amplitude.shape[axis] * 2 - 2 amplitude = project(numpy.array(amplitude,dtype='float'), axis) if phase_rad == None and phase_deg == None: phase_rad = numpy.zeros(shape=amplitude.shape,dtype='float') if phase_rad != None: if not isnumpyarray(phase_rad) or phase_rad.dtype != 'float': phase_rad = numpy.array(phase_rad,dtype='float') phase_rad = project(phase_rad, axis) if phase_deg != None: if not isnumpyarray(phase_deg) or phase_deg.dtype != 'float': phase_deg = numpy.array(phase_deg,dtype='float') phase_deg = project(phase_deg, axis) if phase_rad != None and phase_deg != None: if phase_rad.shape != phase_deg.shape: raise ArgConflictError, "conflicting phase_rad and phase_deg arguments" if numpy.max(numpy.abs(phase_rad * (180.0/numpy.pi) - phase_deg) > 1e-10): raise ArgConflictError, "conflicting phase_rad and phase_deg arguments" if phase_rad == None: phase_rad = phase_deg * (numpy.pi/180.0) f = phase_rad * 1j f = numpy.exp(f) f = f * amplitude f *= float(nsamp)/2.0 sub = [slice(None)] * max(axis+1, len(f.shape)) if nsamp%2 == 0: sub[axis] = -1 f[sub] *= 2.0 sub[axis] = slice((nsamp%2)-2, 0, -1) f = numpy.concatenate((f, numpy.conj(f[sub])), axis=axis) return f
def ampmod(w, freq_hz=1.0,phase_rad=None,phase_deg=None,amplitude=0.5,dc=0.5,samplingfreq_hz=None,duration_msec=None,duration_samples=None,axis=None,waveform=numpy.sin,**kwargs): """ Return a copy of <w> (a numpy.ndarray or a WavTools.wav object) in which the amplitude is modulated sinusoidally along the specified time <axis>. Default phase is such that amplitude is 0 at time 0, which corresponds to phase_deg=-90 if <waveform> follows sine phase, since the modulator is a raised waveform. To change this, specify either <phase_rad> or <phase_deg>. Uses wavegen() """### if isnumpyarray(w): y = w elif hasattr(w, 'y'): w = w.copy(); y = w.y else: raise TypeError, "don't know how to handle this kind of carrier object" if samplingfreq_hz==None: samplingfreq_hz = getfs(w) if phase_rad==None and phase_deg==None: phase_deg = -90.0 if duration_samples==None and duration_msec==None: duration_samples = project(y,0).shape[0] envelope = wavegen(freq_hz=freq_hz,phase_rad=phase_rad,phase_deg=phase_deg,amplitude=amplitude,dc=dc,samplingfreq_hz=samplingfreq_hz,duration_msec=duration_msec,duration_samples=duration_samples,axis=axis,waveform=waveform,**kwargs) envelope = project(envelope, len(y.shape)-1) y = y * envelope if isnumpyarray(w): w = y else: w.y = y return w
def reconstruct(ap,**kwargs): """ Check the accuracy of fft2ap() and wavegen() by reconstructing a signal as the sum of cosine waves with amplitudes and phases specified in dict ap, which is of the form output by fft2ap. """### ap = dict(ap) # makes a copy, at least of the container dict ap['duration_samples'] = len(ap.pop('fullfreq_hz')) ap.update(kwargs) axis=ap.pop('axis', -1) extra_axis = axis+1 for v in ap.values(): extra_axis = max([extra_axis, len(getattr(v, 'shape', []))]) ap['freq_hz'] = project(ap['freq_hz'], extra_axis).swapaxes(axis,0) ap['axis'] = extra_axis r = wavegen(**ap) r = r.swapaxes(extra_axis, axis) r = r.sum(axis=extra_axis) return r
def plot(*pargs,**kwargs): """ A wrapper around pylab.plot that reduces dimensionality-related fiddliness. plot(x, y) plot(y) where either x or y, independent of each other, can specify multiple lines. Additional options and their defaults: axis = 0 Along which dimension can one step while staying on the same graphical line? hold = False If false, clear the axes before plotting. drawnow = True If true, execute pylab.draw() after plotting. """### hold = kwargs.pop('hold', False) axis = kwargs.pop('axis', 0) drawnow = kwargs.pop('drawnow', True) pargs = list(pargs) # makes a copy, at least of the list container allvec = True for i in range(len(pargs)): if isinstance(pargs[i], (tuple,list)): pargs[i] = numpy.array(pargs[i],dtype=numpy.float64) if isinstance(pargs[i], numpy.ndarray): allvec &= (max(pargs[i].shape) == numpy.prod(pargs[i].shape)) if len(pargs[i].shape) > 1: pargs[i] = project(pargs[i],axis).swapaxes(0,axis) isinf = numpy.isinf(pargs[i]) if numpy.any(isinf): pargs[i] = pargs[i].copy() pargs[i][isinf] = numpy.nan # crude workaround---pylab.plot can't cope with infinite values if allvec: for i in range(len(pargs)): if isinstance(pargs[i], numpy.matrix): pargs[i] = pargs[i].A if isinstance(pargs[i], numpy.ndarray): pargs[i] = pargs[i].flatten() pargs = tuple(pargs) pylab = load_pylab() if not hold: pylab.cla() p = pylab.plot(*pargs,**kwargs) if drawnow: pylab.draw() return p
def reconstruct(ap, **kwargs): """ Check the accuracy of fft2ap() and wavegen() by reconstructing a signal as the sum of cosine waves with amplitudes and phases specified in dict ap, which is of the form output by fft2ap. """### ap = dict(ap) # makes a copy, at least of the container dict ap['duration_samples'] = len(ap.pop('fullfreq_hz')) ap.update(kwargs) axis = ap.pop('axis', -1) extra_axis = axis + 1 for v in ap.values(): extra_axis = max([extra_axis, len(getattr(v, 'shape', []))]) ap['freq_hz'] = project(ap['freq_hz'], extra_axis).swapaxes(axis, 0) ap['axis'] = extra_axis r = wavegen(**ap) r = r.swapaxes(extra_axis, axis) r = r.sum(axis=extra_axis) return r
def plot(*pargs,**kwargs): """ A wrapper around pylab.plot that reduces dimensionality-related fiddliness. plot(x, y) plot(y) where either x or y, independent of each other, can specify multiple lines. Additional options and their defaults: axis = 0 Along which dimension can one step while staying on the same graphical line? stem = False If True, do a stem plot instead of an ordinary line grid = True Whether to turn on the grid. balance = None If None, do nothing. If a numeric value, balance the y axis limits around this value. aspect = None If 'auto', 'image' or 'equal', apply that aspect mode hold = False If False, clear the axes before plotting. drawnow = True If True, execute pylab.draw() after plotting. """### hold = kwargs.pop('hold', False) axis = kwargs.pop('axis', 0) drawnow = kwargs.pop('drawnow', True) grid = kwargs.pop('grid', True) balance = kwargs.pop('balance', None) stem = kwargs.pop('stem', False) aspect = kwargs.pop('aspect', None) axkwargs = filterdict(kwargs, ['title', 'xlabel', 'ylabel', 'ylim', 'xlim']) pargs = list(pargs) # makes a copy, at least of the list container allvec = True for i in range(len(pargs)): if isinstance(pargs[i], (tuple,list)): pargs[i] = numpy.array(pargs[i],dtype=numpy.float64) if isinstance(pargs[i], numpy.ndarray): allvec &= (max(pargs[i].shape) == numpy.prod(pargs[i].shape)) if len(pargs[i].shape) > 1: pargs[i] = project(pargs[i],axis).swapaxes(0,axis) isinf = numpy.isinf(pargs[i]) if numpy.any(isinf): pargs[i] = pargs[i].copy() pargs[i][isinf] = numpy.nan # crude workaround---pylab.plot can't cope with infinite values if allvec: for i in range(len(pargs)): if isinstance(pargs[i], numpy.matrix): pargs[i] = pargs[i].A if isinstance(pargs[i], numpy.ndarray): pargs[i] = pargs[i].flatten() pargs = tuple(pargs) pylab = load_pylab() if not hold: # undo a few known hangovers before clearing ax = pylab.gca() ax.set_aspect('auto') # image aspect ratio ax.set_ylim(sorted(ax.get_ylim())) # ydir reversed rmcolorbar(drawnow=False) # colorbar pylab.cla() if stem: if len(pargs) == 1: pargs = (range(pargs[0].shape[axis]),) + pargs p = pylab.stem(*pargs, **kwargs) x = pargs[0] xl = numpy.r_[float(x[0]), float(x[-1])] if len(x) == 1: xl += [-0.5, 0.5] else: xl += numpy.r_[float(x[0]-x[1]), float(x[-1]-x[-2])] / 2.0 pylab.gca().set_xlim(xl) else: p = pylab.plot(*pargs,**kwargs) pylab.grid(grid) ax = pylab.gca() if balance != None: yl = numpy.array(ax.get_ylim()) yl = balance + numpy.array([-1,+1]) * max(abs(yl - balance)) ax.set_ylim(yl) if aspect != None: ax.set_aspect({'image':'equal'}.get(aspect, aspect)) ax.set(**axkwargs) if drawnow: pylab.draw() return p
def wavegen(freq_hz=1.0,phase_rad=None,phase_deg=None,amplitude=1.0,dc=0.0,samplingfreq_hz=None,duration_msec=None,duration_samples=None,axis=None,waveform=numpy.cos,container=None,**kwargs): """ Create a signal (or multiple signals, if the input arguments are arrays) which is a sine function of time (time being defined along the specified <axis>). Default phase is 0, but may be changed by either <phase_deg> or <phase_rad> (or both, as long as the values are consistent). Default duration is 1000 msec, but may be changed by either <duration_samples> or <duration_msec> (or both, as long as the values are consistent). A <container> object may be supplied: if so, it should be a WavTools.wav object. <axis> is set then set to 0, and the container object's duration (if non-zero), sampling frequency, and number of channels are used as fallback values if these are not specified elsewhere. The resulting signal is put into container.y and the pointer to the container is returned. If <duration_samples> is specified and <samplingfreq_hz> is not, then the sampling frequency is chosen such that the duration is 1 second, so <freq_hz> can be interpreted as cycles per signal. The default <waveform> function is numpy.cos which means that amplitude, phase and frequency arguments can be taken straight from the kind of dictionary returned by fft2ap() for an accurate reconstruction. """### fs = getfs(samplingfreq_hz) default_duration_msec = 1000.0 nrep = 1 if container != None: if fs == None: fs = getfs(container) if hasattr(container,'duration') and container.duration(): default_duration_msec = container.duration() * 1000.0 if hasattr(container,'channels') and container.channels() and container.y.size: nrep = container.channels() for j in range(0,2): for i in range(0,2): if duration_msec==None: duration_msec = samples2msec(duration_samples, fs) if duration_samples==None: duration_samples = msec2samples(duration_msec, fs) if duration_samples != None: duration_msec = samples2msec(duration_samples, fs) if fs==None and duration_samples!=None and duration_msec!=None: fs = 1000.0 * float(duration_samples) / float(duration_msec) if fs==None and duration_samples!=None: fs = float(duration_samples) if fs==None and duration_msec!=None: fs = float(duration_msec) if duration_msec==None: duration_msec = default_duration_msec duration_sec = duration_msec / 1000.0 duration_samples = float(round(duration_samples)) if duration_msec != samples2msec(duration_samples,fs) or duration_samples != msec2samples(duration_msec,fs): raise ArgConflictError, "conflicting duration_samples and duration_msec arguments" x = numpy.arange(0.0,duration_samples) * (2.0 * numpy.pi / duration_samples) freq_hz = trimtrailingdims(numpy.array(freq_hz,dtype='float')) if phase_rad == None and phase_deg == None: phase_rad = [0.0] if phase_rad != None: if not isnumpyarray(phase_rad) or phase_rad.dtype != 'float': phase_rad = numpy.array(phase_rad,dtype='float') phase_rad = trimtrailingdims(phase_rad) if phase_deg != None: if not isnumpyarray(phase_deg) or phase_deg.dtype != 'float': phase_deg = numpy.array(phase_deg,dtype='float') phase_deg = trimtrailingdims(phase_deg) if phase_rad != None and phase_deg != None: if phase_rad.shape != phase_deg.shape: raise ArgConflictError, "conflicting phase_rad and phase_deg arguments" if numpy.max(numpy.abs(phase_rad * (180.0/numpy.pi) - phase_deg) > 1e-10): raise ArgConflictError, "conflicting phase_rad and phase_deg arguments" if phase_rad == None: phase_rad = numpy.array(phase_deg * (numpy.pi/180.0)) amplitude = trimtrailingdims(numpy.array(amplitude,dtype='float')) dc = trimtrailingdims(numpy.array(dc,dtype='float')) maxaxis = max(len(freq_hz.shape), len(phase_rad.shape), len(amplitude.shape), len(dc.shape)) - 1 if axis==None: if project(freq_hz,0).shape[0]==1 and project(phase_rad,0).shape[0]==1 and project(amplitude,0).shape[0]==1 and project(dc,0).shape[0]==1: axis=0 else: axis = maxaxis + 1 maxaxis = max(axis, maxaxis) x = project(x,maxaxis).swapaxes(0,axis) x = x * (project(freq_hz,maxaxis) * duration_sec) # *= won't work for broadcasting here # if you get an error here, try setting axis=1 and transposing the return value ;-) x = x + (project(phase_rad,maxaxis)) # += won't work for broadcasting here x = waveform(x, **kwargs) x = x * project(amplitude,maxaxis) # *= won't work for broadcasting here if numpy.any(dc.flatten()): x = x + project(dc,maxaxis) # += won't work for broadcasting here if container != None: across_channels = 1 x = project(x, across_channels) if x.shape[across_channels] == 1 and nrep > 1: x = x.repeat(nrep, across_channels) container.y = x container.fs = int(round(fs)) x = container return x
def plot(*pargs, **kwargs): """ A wrapper around pylab.plot that reduces dimensionality-related fiddliness. plot(x, y) plot(y) where either x or y, independent of each other, can specify multiple lines. Additional options and their defaults: axis = 0 Along which dimension can one step while staying on the same graphical line? stem = False If True, do a stem plot instead of an ordinary line grid = True Whether to turn on the grid. balance = None If None, do nothing. If a numeric value, balance the y axis limits around this value. aspect = None If 'auto', 'image' or 'equal', apply that aspect mode hold = False If False, clear the axes before plotting. drawnow = True If True, execute pylab.draw() after plotting. """### hold = kwargs.pop('hold', False) axis = kwargs.pop('axis', 0) drawnow = kwargs.pop('drawnow', True) grid = kwargs.pop('grid', True) balance = kwargs.pop('balance', None) stem = kwargs.pop('stem', False) aspect = kwargs.pop('aspect', None) axkwargs = filterdict(kwargs, ['title', 'xlabel', 'ylabel', 'ylim', 'xlim']) pargs = list(pargs) # makes a copy, at least of the list container allvec = True for i in range(len(pargs)): if isinstance(pargs[i], (tuple, list)): pargs[i] = numpy.array(pargs[i], dtype=numpy.float64) if isinstance(pargs[i], numpy.ndarray): allvec &= (max(pargs[i].shape) == numpy.prod(pargs[i].shape)) if len(pargs[i].shape) > 1: pargs[i] = project(pargs[i], axis).swapaxes(0, axis) isinf = numpy.isinf(pargs[i]) if numpy.any(isinf): pargs[i] = pargs[i].copy() pargs[i][ isinf] = numpy.nan # crude workaround---pylab.plot can't cope with infinite values if allvec: for i in range(len(pargs)): if isinstance(pargs[i], numpy.matrix): pargs[i] = pargs[i].A if isinstance(pargs[i], numpy.ndarray): pargs[i] = pargs[i].flatten() pargs = tuple(pargs) pylab = load_pylab() if not hold: # undo a few known hangovers before clearing ax = pylab.gca() ax.set_aspect('auto') # image aspect ratio ax.set_ylim(sorted(ax.get_ylim())) # ydir reversed rmcolorbar(drawnow=False) # colorbar pylab.cla() if stem: if len(pargs) == 1: pargs = (range(pargs[0].shape[axis]), ) + pargs p = pylab.stem(*pargs, **kwargs) x = pargs[0] xl = numpy.r_[float(x[0]), float(x[-1])] if len(x) == 1: xl += [-0.5, 0.5] else: xl += numpy.r_[float(x[0] - x[1]), float(x[-1] - x[-2])] / 2.0 pylab.gca().set_xlim(xl) else: p = pylab.plot(*pargs, **kwargs) pylab.grid(grid) ax = pylab.gca() if balance != None: yl = numpy.array(ax.get_ylim()) yl = balance + numpy.array([-1, +1]) * max(abs(yl - balance)) ax.set_ylim(yl) if aspect != None: ax.set_aspect({'image': 'equal'}.get(aspect, aspect)) ax.set(**axkwargs) if drawnow: pylab.draw() return p
def wavegen(freq_hz=1.0, phase_rad=None, phase_deg=None, amplitude=1.0, dc=0.0, samplingfreq_hz=None, duration_msec=None, duration_samples=None, axis=None, waveform=numpy.cos, container=None, **kwargs): """ Create a signal (or multiple signals, if the input arguments are arrays) which is a sine function of time (time being defined along the specified <axis>). Default phase is 0, but may be changed by either <phase_deg> or <phase_rad> (or both, as long as the values are consistent). Default duration is 1000 msec, but may be changed by either <duration_samples> or <duration_msec> (or both, as long as the values are consistent). A <container> object may be supplied: if so, it should be a WavTools.wav object. <axis> is set then set to 0, and the container object's duration (if non-zero), sampling frequency, and number of channels are used as fallback values if these are not specified elsewhere. The resulting signal is put into container.y and the pointer to the container is returned. If <duration_samples> is specified and <samplingfreq_hz> is not, then the sampling frequency is chosen such that the duration is 1 second, so <freq_hz> can be interpreted as cycles per signal. The default <waveform> function is numpy.cos which means that amplitude, phase and frequency arguments can be taken straight from the kind of dictionary returned by fft2ap() for an accurate reconstruction. """### fs = getfs(samplingfreq_hz) default_duration_msec = 1000.0 nrep = 1 if container != None: if fs == None: fs = getfs(container) if hasattr(container, 'duration') and container.duration(): default_duration_msec = container.duration() * 1000.0 if hasattr(container, 'channels') and container.channels() and container.y.size: nrep = container.channels() for j in range(0, 2): for i in range(0, 2): if duration_msec == None: duration_msec = samples2msec(duration_samples, fs) if duration_samples == None: duration_samples = msec2samples(duration_msec, fs) if duration_samples != None: duration_msec = samples2msec(duration_samples, fs) if fs == None and duration_samples != None and duration_msec != None: fs = 1000.0 * float(duration_samples) / float(duration_msec) if fs == None and duration_samples != None: fs = float(duration_samples) if fs == None and duration_msec != None: fs = float(duration_msec) if duration_msec == None: duration_msec = default_duration_msec duration_sec = duration_msec / 1000.0 duration_samples = float(round(duration_samples)) if duration_msec != samples2msec(duration_samples, fs) or duration_samples != msec2samples( duration_msec, fs): raise ArgConflictError, "conflicting duration_samples and duration_msec arguments" x = numpy.arange(0.0, duration_samples) * (2.0 * numpy.pi / duration_samples) freq_hz = trimtrailingdims(numpy.array(freq_hz, dtype='float')) if phase_rad == None and phase_deg == None: phase_rad = [0.0] if phase_rad != None: if not isnumpyarray(phase_rad) or phase_rad.dtype != 'float': phase_rad = numpy.array(phase_rad, dtype='float') phase_rad = trimtrailingdims(phase_rad) if phase_deg != None: if not isnumpyarray(phase_deg) or phase_deg.dtype != 'float': phase_deg = numpy.array(phase_deg, dtype='float') phase_deg = trimtrailingdims(phase_deg) if phase_rad != None and phase_deg != None: if phase_rad.shape != phase_deg.shape: raise ArgConflictError, "conflicting phase_rad and phase_deg arguments" if numpy.max( numpy.abs(phase_rad * (180.0 / numpy.pi) - phase_deg) > 1e-10): raise ArgConflictError, "conflicting phase_rad and phase_deg arguments" if phase_rad == None: phase_rad = numpy.array(phase_deg * (numpy.pi / 180.0)) amplitude = trimtrailingdims(numpy.array(amplitude, dtype='float')) dc = trimtrailingdims(numpy.array(dc, dtype='float')) maxaxis = max(len(freq_hz.shape), len(phase_rad.shape), len( amplitude.shape), len(dc.shape)) - 1 if axis == None: if project(freq_hz, 0).shape[0] == 1 and project( phase_rad, 0).shape[0] == 1 and project( amplitude, 0).shape[0] == 1 and project(dc, 0).shape[0] == 1: axis = 0 else: axis = maxaxis + 1 maxaxis = max(axis, maxaxis) x = project(x, maxaxis).swapaxes(0, axis) x = x * (project(freq_hz, maxaxis) * duration_sec ) # *= won't work for broadcasting here # if you get an error here, try setting axis=1 and transposing the return value ;-) x = x + (project(phase_rad, maxaxis) ) # += won't work for broadcasting here x = waveform(x, **kwargs) x = x * project(amplitude, maxaxis) # *= won't work for broadcasting here if numpy.any(dc.flatten()): x = x + project(dc, maxaxis) # += won't work for broadcasting here if container != None: across_channels = 1 x = project(x, across_channels) if x.shape[across_channels] == 1 and nrep > 1: x = x.repeat(nrep, across_channels) container.y = x container.fs = int(round(fs)) x = container return x