def __init__(self, logger, audiobackend): Qwt.QwtPlotItem.__init__(self) self.canvasscaledspectrogram = CanvasScaledSpectrogram(logger) self.T = 0. self.dT = 1. self.audiobackend = audiobackend #self.previous_time = self.audiobackend.get_stream_time() self.offset = 0 #self.audiobackend.get_stream_time()/self.dT self.sfft_rate_frac = Fraction(1, 1) self.frequency_resampler = Frequency_Resampler() self.resampler = Online_Linear_2D_resampler()
class PlotImage(Qwt.QwtPlotItem): def __init__(self, logger, audiobackend): Qwt.QwtPlotItem.__init__(self) self.canvasscaledspectrogram = CanvasScaledSpectrogram(logger) self.T = 0. self.dT = 1. self.audiobackend = audiobackend #self.previous_time = self.audiobackend.get_stream_time() self.offset = 0 #self.audiobackend.get_stream_time()/self.dT self.sfft_rate_frac = Fraction(1, 1) self.frequency_resampler = Frequency_Resampler() self.resampler = Online_Linear_2D_resampler() def addData(self, freq, xyzs, logfreqscale): self.frequency_resampler.setlogfreqscale(logfreqscale) # Note: both the frequency and the time resampler work # only on 1D arrays, so we loop on the columns of data. # However, we reassemble the 2D output before drawing # on the widget's pixmap, because the drawing operation # seems to have a costly warmup phase, so it is better # to invoke it the fewer number of times possible. n = self.resampler.processable(xyzs.shape[1]) resampled_data = np.zeros((self.frequency_resampler.nsamples, n)) i = 0 for j in range(xyzs.shape[1]): freq_resampled_data = self.frequency_resampler.process(freq, xyzs[:, j]) data = self.resampler.process(freq_resampled_data) resampled_data[:,i:i+data.shape[1]] = data i += data.shape[1] self.canvasscaledspectrogram.addData(resampled_data) def draw(self, painter, xMap, yMap, rect): # update the spectrogram according to possibly new canvas dimensions self.frequency_resampler.setnsamples(rect.height()) self.resampler.set_height(rect.height()) self.canvasscaledspectrogram.setcanvas_height(rect.height()) self.canvasscaledspectrogram.setcanvas_width(rect.width()) screen_rate_frac = Fraction(rect.width(), int(self.T*1000)) self.resampler.set_ratio(self.sfft_rate_frac, screen_rate_frac) pixmap = self.canvasscaledspectrogram.getpixmap() offset = self.canvasscaledspectrogram.getpixmapoffset() rolling = True if rolling: # draw the whole canvas with a selected portion of the pixmap hints = painter.renderHints() # enable bilinear pixmap transformation painter.setRenderHints(hints|QtGui.QPainter.SmoothPixmapTransform) #FIXME instead of a generic bilinear transformation, I need a specialized one # since no transformation is needed in y, and the sampling rate is already known to be ok in x sw = rect.width() sh = rect.height() # this function should be called by repaint, for better time sync # FIXME the following is wrong when the display is paused ! # and even when not paused, it does not improve the smoothness # and has a problem of offset #current_time = self.audiobackend.get_stream_time() #delay = sw*(current_time - self.previous_time)/self.T #self.previous_time = current_time #self.offset += delay #self.offset = (self.offset % sw) #offset = self.offset source_rect = QtCore.QRectF(offset, 0, sw, sh) # QRectF since the offset and width may be non-integer painter.drawPixmap(QtCore.QRectF(rect), pixmap, source_rect) else: sw = rect.width() sh = rect.height() source_rect = QtCore.QRectF(0, 0, sw, sh) painter.drawPixmap(QtCore.QRectF(rect), pixmap, source_rect) def settimerange(self, timerange_seconds, dT): self.T = timerange_seconds self.dT = dT def setfreqrange(self, minfreq, maxfreq): self.frequency_resampler.setfreqrange(minfreq, maxfreq) def set_sfft_rate(self, rate_frac): self.sfft_rate_frac = rate_frac def setlogfreqscale(self, logfreqscale): self.frequency_resampler.setlogfreqscale(logfreqscale) def erase(self): self.canvasscaledspectrogram.erase()
class PlotImage(Qwt.QwtPlotItem): def __init__(self, logger, audiobackend): Qwt.QwtPlotItem.__init__(self) self.canvasscaledspectrogram = CanvasScaledSpectrogram(logger) self.T = 0. self.dT = 1. self.audiobackend = audiobackend #self.previous_time = self.audiobackend.get_stream_time() self.offset = 0 #self.audiobackend.get_stream_time()/self.dT self.sfft_rate_frac = Fraction(1, 1) self.frequency_resampler = Frequency_Resampler() self.resampler = Online_Linear_2D_resampler() def addData(self, freq, xyzs, logfreqscale): self.frequency_resampler.setlogfreqscale(logfreqscale) # Note: both the frequency and the time resampler work # only on 1D arrays, so we loop on the columns of data. # However, we reassemble the 2D output before drawing # on the widget's pixmap, because the drawing operation # seems to have a costly warmup phase, so it is better # to invoke it the fewer number of times possible. n = self.resampler.processable(xyzs.shape[1]) resampled_data = np.zeros((self.frequency_resampler.nsamples, n)) i = 0 for j in range(xyzs.shape[1]): freq_resampled_data = self.frequency_resampler.process( freq, xyzs[:, j]) data = self.resampler.process(freq_resampled_data) resampled_data[:, i:i + data.shape[1]] = data i += data.shape[1] self.canvasscaledspectrogram.addData(resampled_data) def draw(self, painter, xMap, yMap, rect): # update the spectrogram according to possibly new canvas dimensions self.frequency_resampler.setnsamples(rect.height()) self.resampler.set_height(rect.height()) self.canvasscaledspectrogram.setcanvas_height(rect.height()) self.canvasscaledspectrogram.setcanvas_width(rect.width()) screen_rate_frac = Fraction(rect.width(), int(self.T * 1000)) self.resampler.set_ratio(self.sfft_rate_frac, screen_rate_frac) pixmap = self.canvasscaledspectrogram.getpixmap() offset = self.canvasscaledspectrogram.getpixmapoffset() rolling = True if rolling: # draw the whole canvas with a selected portion of the pixmap hints = painter.renderHints() # enable bilinear pixmap transformation painter.setRenderHints(hints | QtGui.QPainter.SmoothPixmapTransform) #FIXME instead of a generic bilinear transformation, I need a specialized one # since no transformation is needed in y, and the sampling rate is already known to be ok in x sw = rect.width() sh = rect.height() # this function should be called by repaint, for better time sync # FIXME the following is wrong when the display is paused ! # and even when not paused, it does not improve the smoothness # and has a problem of offset #current_time = self.audiobackend.get_stream_time() #delay = sw*(current_time - self.previous_time)/self.T #self.previous_time = current_time #self.offset += delay #self.offset = (self.offset % sw) #offset = self.offset source_rect = QtCore.QRectF(offset, 0, sw, sh) # QRectF since the offset and width may be non-integer painter.drawPixmap(QtCore.QRectF(rect), pixmap, source_rect) else: sw = rect.width() sh = rect.height() source_rect = QtCore.QRectF(0, 0, sw, sh) painter.drawPixmap(QtCore.QRectF(rect), pixmap, source_rect) def settimerange(self, timerange_seconds, dT): self.T = timerange_seconds self.dT = dT def setfreqrange(self, minfreq, maxfreq): self.frequency_resampler.setfreqrange(minfreq, maxfreq) def set_sfft_rate(self, rate_frac): self.sfft_rate_frac = rate_frac def setlogfreqscale(self, logfreqscale): self.frequency_resampler.setlogfreqscale(logfreqscale) def erase(self): self.canvasscaledspectrogram.erase()
class PlotImage(Qwt.QwtPlotItem): def __init__(self, logger, audiobackend): Qwt.QwtPlotItem.__init__(self) self.canvasscaledspectrogram = CanvasScaledSpectrogram(logger) self.T = 0. self.dT = 1. self.audiobackend = audiobackend #self.previous_time = self.audiobackend.get_stream_time() self.offset = 0 #self.audiobackend.get_stream_time()/self.dT self.jitter_s = 0. self.isPlaying = True self.sfft_rate_frac = Fraction(1, 1) self.frequency_resampler = Frequency_Resampler() self.resampler = Online_Linear_2D_resampler() self.timer = QtCore.QElapsedTimer() self.timer.start() def addData(self, freq, xyzs, logfreqscale): self.frequency_resampler.setlogfreqscale(logfreqscale) # Note: both the frequency and the time resampler work # only on 1D arrays, so we loop on the columns of data. # However, we reassemble the 2D output before drawing # on the widget's pixmap, because the drawing operation # seems to have a costly warmup phase, so it is better # to invoke it the fewer number of times possible. n = self.resampler.processable(xyzs.shape[1]) resampled_data = np.zeros((self.frequency_resampler.nsamples, n)) i = 0 for j in range(xyzs.shape[1]): freq_resampled_data = self.frequency_resampler.process(freq, xyzs[:, j]) data = self.resampler.process(freq_resampled_data) resampled_data[:,i:i+data.shape[1]] = data i += data.shape[1] self.canvasscaledspectrogram.addData(resampled_data) def pause(self): self.isPlaying = False def restart(self): self.isPlaying = True self.timer.restart() def draw(self, painter, xMap, yMap, rect): # update the spectrogram according to possibly new canvas dimensions self.frequency_resampler.setnsamples(rect.height()) self.resampler.set_height(rect.height()) self.canvasscaledspectrogram.setcanvas_height(rect.height()) #print self.jitter_s, self.T, rect.width(), rect.width()*(1 + self.jitter_s/self.T) jitter_pix = rect.width()*self.jitter_s/self.T self.canvasscaledspectrogram.setcanvas_width(rect.width() + jitter_pix) screen_rate_frac = Fraction(rect.width(), int(self.T*1000)) self.resampler.set_ratio(self.sfft_rate_frac, screen_rate_frac) # time advance # FIXME ideally this function should be called at paintevent time, for better time sync # but I'm not sure it is... maybe qwt does some sort of double-buffering # and repaints its items outside of paintevents # solution: look at PaintEvent # FIXME there is a small bands of columns with jitter (on both sides of the spectrogram) # solution: grow the rolling-canvas by a couple of columns, # and slightly delay the spectrogram by the same number of columns if self.isPlaying: delta_t = self.timer.nsecsElapsed()*1e-9 self.timer.restart() pixel_advance = delta_t/(self.T + self.jitter_s)*rect.width() self.canvasscaledspectrogram.addPixelAdvance(pixel_advance) pixmap = self.canvasscaledspectrogram.getpixmap() offset = self.canvasscaledspectrogram.getpixmapoffset(delay=jitter_pix/2) rolling = True if rolling: # draw the whole canvas with a selected portion of the pixmap hints = painter.renderHints() # enable bilinear pixmap transformation painter.setRenderHints(hints|QtGui.QPainter.SmoothPixmapTransform) #FIXME instead of a generic bilinear transformation, I need a specialized one # since no transformation is needed in y, and the sampling rate is already known to be ok in x sw = rect.width() sh = rect.height() source_rect = QtCore.QRectF(offset, 0, sw, sh) # QRectF since the offset and width may be non-integer painter.drawPixmap(QtCore.QRectF(rect), pixmap, source_rect) else: sw = rect.width() sh = rect.height() source_rect = QtCore.QRectF(0, 0, sw, sh) painter.drawPixmap(QtCore.QRectF(rect), pixmap, source_rect) def settimerange(self, timerange_seconds, dT): self.T = timerange_seconds self.dT = dT def setfreqrange(self, minfreq, maxfreq): self.frequency_resampler.setfreqrange(minfreq, maxfreq) def set_sfft_rate(self, rate_frac): self.sfft_rate_frac = rate_frac def setlogfreqscale(self, logfreqscale): self.frequency_resampler.setlogfreqscale(logfreqscale) def erase(self): self.canvasscaledspectrogram.erase() def set_jitter(self, jitter_s): self.jitter_s = jitter_s