Exemple #1
0
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()
Exemple #2
0
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()
Exemple #4
0
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}
Exemple #6
0
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
Exemple #7
0
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
Exemple #11
0
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
Exemple #12
0
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
Exemple #15
0
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
Exemple #16
0
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