Multiple Spectrograms in the differents plots.

118 views
Skip to first unread message

Aldrich Cabrera

unread,
Dec 16, 2019, 1:53:42 PM12/16/19
to pyqtgraph
Hi guys,

I'm trying to plot in real-time several spectrograms in different plots. When I plot only one channel works great but after adding the others channels the result is shown in the same plot and I'd want to show the result by channel in different plots.

I'm getting the data from a thread. and the code I'm using to plot looks like this:




import numpy as np
import pyqtgraph as pg
import pyqtgraph.exporters
import pyaudio
from PyQt4 import QtCore, QtGui
import cv2

FS = 48000 #Hz
CHUNKSZ = 1024 #samples


class MicrophoneRecorder():
    def __init__(self, signal):
        self.signal = signal
        #self.signal2 = signal2
        self.p = pyaudio.PyAudio()  # Create pyaudio instantiation
        self.stream = self.p.open(format=pyaudio.paInt16,
                            rate=FS,
                            channels=8,
                            input_device_index=4,
                            input=True,
                            frames_per_buffer=CHUNKSZ)

    def read(self):
        self.stream.start_stream()
        data = self.stream.read(CHUNKSZ)
        y = np.fromstring(data, dtype=np.int16)

        yC1 = y[0::8]   # Channel 1 
        yC2 = y[1::8]   # Channel 2 
        yC3 = y[2::8]   # Channel 3 
        yC4 = y[3::8]   # Channel 4 
        yC5 = y[4::8]   # Channel 5 
        yC6 = y[5::8]   # Channel 6 
        yC7 = y[6::8]   # Channel 7 
        yC8 = y[7::8]   # Channel 8 
        self.signal.emit(yC1)
        self.signal.emit(yC2)

    def close(self):
        self.stream.stop_stream()
        self.stream.close()
        self.p.terminate()


class SpectrogramWidget(pg.PlotWidget):
    read_collected = QtCore.pyqtSignal(np.ndarray)
    #print(read_collected)
    #read_collected2 = QtCore.pyqtSignal(np.ndarray)
    
    def __init__(self):
        super(SpectrogramWidget, self).__init__(parent=None, background='default')
        
        self.img = pg.ImageItem() # Displaying an image
        self.img2 = pg.ImageItem()   # Displaying an image
        self.addItem(self.img) # Add a graphics item to the view box
        self.addItem(self.img2)      # Add a graphics item to the view box

        #self.img_array = np.zeros((1000, CHUNKSZ/2+1))
        self.img_array = np.zeros((94, CHUNKSZ/2+1)) # Return new array
        print(self.img_array)

        # bipolar colormap
        pos = np.array([0., 1., 0.5, 0.25, 0.75]) # Object, dtype, copy, order, subok, ndmin
        color = np.array([[0,255,255,255], [255,255,0,255], [0,0,0,255], (0, 0, 255, 255), (255, 0, 0, 255)], dtype=np.ubyte)
        cmap = pg.ColorMap(pos, color) # pos, color - relationship between scalar value and range of color
        lut = cmap.getLookupTable(0.0, 1.0, 256) # Start value, final value, number of points 

        self.img.setLookupTable(lut) # Accepts currents images 
        self.img.setLevels([-50,40]) # Blacklevel, Whitelevel

        freq = np.arange((CHUNKSZ/2)+1)/(float(CHUNKSZ)/FS) # Spaced values within a given interval
        yscale = 1.0/(self.img_array.shape[1]/freq[-1]) # Scale the y-axis for intervals of freq
        self.img.scale((1./FS)*CHUNKSZ, yscale) # Scale between the time and freq

        self.setLabel('left', 'Frequency', units='Hz') # Set y-axi label
        self.setLabel('bottom', 'Time', units='sec') # Set x-axi label

        self.win = np.hanning(CHUNKSZ) # Return Hanning Window
        self.show() # Show the image generated

    def update(self, chunk):
        # normalized, windowed frequencies in data chunk
        spec = np.fft.rfft(chunk*self.win) / CHUNKSZ # Compute discrete FT for real input
        
        # get magnitude 
        psd = abs(spec)
        
        # convert to dB scale
        psd = 20 * np.log10(psd)

        # roll down one and replace leading edge with new data
        self.img_array = np.roll(self.img_array, -1, 0)
        self.img_array[-1:] = psd

        self.img.setImage(self.img_array, autoLevels=False)
        
        #self.imagewindow.clear()
        #self.imagewindow.addItem(self.img)
        
        #exporter = pg.exporters.ImageExporter(self.img_array)
        #exporter.parameters()['width'] = 100
        #exporter.export('test.png')
            

if __name__ == '__main__':
    app = QtGui.QApplication([])
    w = SpectrogramWidget()
    w.read_collected.connect(w.update)
    #w.read_collected2.connect(w.update)

    mic = MicrophoneRecorder(w.read_collected)

    # time (seconds) between reads

    interval = FS/CHUNKSZ
    t = QtCore.QTimer()
    t.timeout.connect(mic.read)
    t.start(100/interval) #QTimer takes ms

    app.exec_()
    mic.close()


I appreciate any suggestions.


Grant R

unread,
Dec 16, 2019, 10:19:14 PM12/16/19
to pyqtgraph
Are you trying to plot eight different images in eight different plot views? In your update method you are only setting the image for self.img which is the one image view. One way is to have eight different image views and pass the index of the one you want to update via your signal. If this is your layout, then I think you should use a GraphicsLayoutWidget with a GraphicsLayout and your items inside (http://www.pyqtgraph.org/documentation/plotting.html).

Aldrich Cabrera

unread,
Dec 17, 2019, 1:30:06 PM12/17/19
to pyqtgraph
Hi Grant R,

Yeah, I'm trying to plot eight different images. Thanks for the information was useful. 
Already I combination these eight views using "GraphicsLayoutWidget" in a single plot, but about the index in the signal to show the right channel, I'm confused. I have created an image (Attached image) with eight plot, but it shows the same channel in the eight views, and I want to show each channel by separate.

I apologise for the inconvenience, but I'm new in this and is very complicated to me. The design was taken: https://gist.github.com/boylea/1a0b5442171f9afbf372.

And my update method thus looks :

class SpectrogramWidget(pg.PlotWidget):
    read_collected = QtCore.pyqtSignal(np.ndarray)
    read_collected2 = QtCore.pyqtSignal(np.ndarray)
    
    def __init__(self):
        super(SpectrogramWidget, self).__init__(parent=None, background='default')

        self.win = pg.GraphicsLayoutWidget(show=True, title="Spectrograms")
        self.win.resize(1280,720)
        self.win.setWindowTitle('Spectrograms')

        self.p1 = self.win.addPlot(title="Channel 1")
        self.p2 = self.win.addPlot(title="Channel 2")
        self.p3 = self.win.addPlot(title="Channel 3")
        self.win.nextRow()

        self.p4 = self.win.addPlot(title="Channel 4")
        self.p5 = self.win.addPlot(title="Channel 5")
        self.p6 = self.win.addPlot(title="Channel 6")
        self.win.nextRow()

        self.p7 = self.win.addPlot(title="Channel 7")
        self.p8 = self.win.addPlot(title="Channel 8")

        self.img = pg.ImageItem() # Displaying an image
        self.img2 = pg.ImageItem()   # Displaying an image
        self.img3 = pg.ImageItem()   # Displaying an image
        self.img4 = pg.ImageItem()   # Displaying an image
        self.img5 = pg.ImageItem()   # Displaying an image
        self.img6 = pg.ImageItem()   # Displaying an image
        self.img7 = pg.ImageItem()   # Displaying an image
        self.img8 = pg.ImageItem()   # Displaying an image
        
        self.p1.addItem(self.img) # Add a graphics item to the view box
        self.p2.addItem(self.img2)       # Add a graphics item to the view box
        self.p3.addItem(self.img3)       # Add a graphics item to the view box
        self.p4.addItem(self.img4)       # Add a graphics item to the view box
        self.p5.addItem(self.img5)       # Add a graphics item to the view box
        self.p6.addItem(self.img6)       # Add a graphics item to the view box
        self.p7.addItem(self.img7)       # Add a graphics item to the view box
        self.p8.addItem(self.img8)       # Add a graphics item to the view box

        #self.img_array = np.zeros((1000, CHUNKSZ/2+1))
        self.img_array = np.zeros((94, CHUNKSZ/2+1)) # Return new array

        # bipolar colormap
        pos = np.array([0., 1., 0.5, 0.25, 0.75]) # Object, dtype, copy, order, subok, ndmin
        color = np.array([[0,255,255,255], [255,255,0,255], [0,0,0,255], (0, 0, 255, 255), (255, 0, 0, 255)], dtype=np.ubyte)
        cmap = pg.ColorMap(pos, color) # pos, color - relationship between scalar value and range of color
        lut = cmap.getLookupTable(0.0, 1.0, 256) # Start value, final value, number of points 

        self.img.setLookupTable(lut) # Accepts currents images 
        self.img.setLevels([-50,40]) # Blacklevel, Whitelevel

        self.img2.setLookupTable(lut)    # Accepts currents images 
        self.img2.setLevels([-50,40])    # Blacklevel, Whitelevel

        self.img3.setLookupTable(lut)    # Accepts currents images 
        self.img3.setLevels([-50,40])    # Blacklevel, Whitelevel

        self.img4.setLookupTable(lut)    # Accepts currents images 
        self.img4.setLevels([-50,40])    # Blacklevel, Whitelevel

        self.img5.setLookupTable(lut)    # Accepts currents images 
        self.img5.setLevels([-50,40])    # Blacklevel, Whitelevel

        self.img6.setLookupTable(lut)    # Accepts currents images 
        self.img6.setLevels([-50,40])    # Blacklevel, Whitelevel

        self.img7.setLookupTable(lut)    # Accepts currents images 
        self.img7.setLevels([-50,40])    # Blacklevel, Whitelevel

        self.img8.setLookupTable(lut)    # Accepts currents images 
        self.img8.setLevels([-50,40])    # Blacklevel, Whitelevel

        freq = np.arange((CHUNKSZ/2)+1)/(float(CHUNKSZ)/FS) # Spaced values within a given interval
        yscale = 1.0/(self.img_array.shape[1]/freq[-1]) # Scale the y-axis for intervals of freq
        
        self.img.scale((1./FS)*CHUNKSZ, yscale) # Scale between the time and freq
        self.img2.scale((1./FS)*CHUNKSZ, yscale) # Scale between the time and freq
        self.img3.scale((1./FS)*CHUNKSZ, yscale) # Scale between the time and freq
        self.img4.scale((1./FS)*CHUNKSZ, yscale) # Scale between the time and freq
        self.img5.scale((1./FS)*CHUNKSZ, yscale) # Scale between the time and freq
        self.img6.scale((1./FS)*CHUNKSZ, yscale) # Scale between the time and freq
        self.img7.scale((1./FS)*CHUNKSZ, yscale) # Scale between the time and freq
        self.img8.scale((1./FS)*CHUNKSZ, yscale) # Scale between the time and freq

        self.p1.setLabel('left', 'Frequency', units='Hz') # Set y-axi label
        self.p1.setLabel('bottom', 'Time', units='sec') # Set x-axi label

        self.p2.setLabel('left', 'Frequency', units='Hz')   # Set y-axi label
        self.p2.setLabel('bottom', 'Time', units='sec') # Set x-axi label

        self.p3.setLabel('left', 'Frequency', units='Hz')   # Set y-axi label
        self.p3.setLabel('bottom', 'Time', units='sec') # Set x-axi label

        self.p4.setLabel('left', 'Frequency', units='Hz')   # Set y-axi label
        self.p4.setLabel('bottom', 'Time', units='sec') # Set x-axi label

        self.p5.setLabel('left', 'Frequency', units='Hz')   # Set y-axi label
        self.p5.setLabel('bottom', 'Time', units='sec') # Set x-axi label

        self.p6.setLabel('left', 'Frequency', units='Hz')   # Set y-axi label
        self.p6.setLabel('bottom', 'Time', units='sec') # Set x-axi label

        self.p7.setLabel('left', 'Frequency', units='Hz')   # Set y-axi label
        self.p7.setLabel('bottom', 'Time', units='sec') # Set x-axi label

        self.p8.setLabel('left', 'Frequency', units='Hz')   # Set y-axi label
        self.p8.setLabel('bottom', 'Time', units='sec') # Set x-axi label

        self.win2 = np.hanning(CHUNKSZ) # Return Hanning Window
        #print(self.win)
        self.p1.show() # Show the image generated

    def update(self, chunk):
        # normalized, windowed frequencies in data chunk
        spec = np.fft.rfft(chunk*self.win2) / CHUNKSZ # Compute discrete FT for real input
        #print(spec)
        # get magnitude 
        psd = abs(spec)
        
        # convert to dB scale
        psd = 20 * np.log10(psd)

        # roll down one and replace leading edge with new data
        self.img_array = np.roll(self.img_array, -1, 0)
        self.img_array[-1:] = psd

        self.img.setImage(self.img_array, autoLevels=False)
        self.img2.setImage(self.img_array, autoLevels=False)
        self.img3.setImage(self.img_array, autoLevels=False)
        self.img4.setImage(self.img_array, autoLevels=False)
        self.img5.setImage(self.img_array, autoLevels=False)
        self.img6.setImage(self.img_array, autoLevels=False)
        self.img7.setImage(self.img_array, autoLevels=False)
        self.img8.setImage(self.img_array, autoLevels=False)
spectro.png

Grant R

unread,
Dec 17, 2019, 3:41:10 PM12/17/19
to pyqtgraph
I think it would be much cleaner if you had a list of image items, so self.imageItems = [self.img, self.img1, ...]. Then you can set all your labels, scale, etc in one loop rather than repeating the code. You can pass the complete 'y' from read into your update method. Your img_array isn't indexed by channel but needs to be. Every ImageItem is being passed the same img_array so the plots will be the same.


def read(self):
        self.stream.start_stream()
        data = self.stream.read(CHUNKSZ)
        y = np.fromstring(data, dtype=np.int16)
        self.signal.emit(y)

...
def update(self, all_chunk):
        # normalized, windowed frequencies in data chunk
   for i in len(self.imageItems):
        chunk = all_chunk[i::8]   # Channel i
        spec = np.fft.rfft(chunk*self.win2) / CHUNKSZ # Compute discrete FT for real input
        #print(spec)
        # get magnitude 
        psd = abs(spec)
        
        # convert to dB scale
        psd = 20 * np.log10(psd)

        #### img_array needs to be indexed by channel. Right now it will just repeat the same data across all plots
        # roll down one and replace leading edge with new data
        self.img_array = np.roll(self.img_array, -1, 0)
        self.img_array[-1:] = psd

        self.imageItems[i].setImage(self.img_array_by_channel, autoLevels=False)

Aldrich Cabrera

unread,
Dec 19, 2019, 2:38:48 PM12/19/19
to pyqtgraph

It is working now. :) Thank you very much for the support! I appreciate your help Grant R.

Now I need to save images every 2 seconds. However the computer is slow, I suppose because I am reading all the channels and plotting at the same time.
Maybe it will be comfortable and fast in OpenCV or maybe no. What do you think?

For now, I will have to save the images. Thanks for the support!


Grant Roch

unread,
Dec 19, 2019, 3:23:36 PM12/19/19
to pyqt...@googlegroups.com
What are you having trouble doing? Plotting or plotting and saving? The plotting should be quite fast and you can do the saving similarly to how you plot. Have another qtimer and iterate through the images saving each time. 

--
You received this message because you are subscribed to a topic in the Google Groups "pyqtgraph" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/pyqtgraph/_RjdXBDKRJg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to pyqtgraph+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pyqtgraph/1fde7c22-5c5c-406d-91f8-2647d1d27ce6%40googlegroups.com.

Aldrich Cabrera

unread,
Dec 19, 2019, 5:01:29 PM12/19/19
to pyqtgraph
Yes, I want to plot and saving, but when I am doing this, the computer becomes slow. 

You suggest that I will add another Qtimer to save the images while plotting the channels, I will do that.


El jueves, 19 de diciembre de 2019, 14:23:36 (UTC-6), Grant R escribió:
What are you having trouble doing? Plotting or plotting and saving? The plotting should be quite fast and you can do the saving similarly to how you plot. Have another qtimer and iterate through the images saving each time. 

On Fri, Dec 20, 2019 at 8:38 AM Aldrich Cabrera <alucar...@gmail.com> wrote:

It is working now. :) Thank you very much for the support! I appreciate your help Grant R.

Now I need to save images every 2 seconds. However the computer is slow, I suppose because I am reading all the channels and plotting at the same time.
Maybe it will be comfortable and fast in OpenCV or maybe no. What do you think?

For now, I will have to save the images. Thanks for the support!


--
You received this message because you are subscribed to a topic in the Google Groups "pyqtgraph" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/pyqtgraph/_RjdXBDKRJg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to pyqt...@googlegroups.com.

Aldrich Cabrera

unread,
Feb 14, 2020, 8:02:52 PM2/14/20
to pyqtgraph
HI. I'm again. I have i question about the plotting and saving images using another qtimer. I would like to save images in the same time that plotting the spectrograms. Actually, I save the images using this:

def saveFig(self):
fname1 = "/home/oyuki/Downloads/spectro_Real_time/audios/mic1/result_%s.png"%time.strftime("%H:%M:%S", gmtime())
self.exporter1 = pg.exporters.ImageExporter(self.imageItems[0])
self.exporter2 = pg.exporters.ImageExporter(self.imageItems[1])
self.exporter3 = pg.exporters.ImageExporter(self.imageItems[2])
self.exporter4 = pg.exporters.ImageExporter(self.imageItems[3])
self.exporter5 = pg.exporters.ImageExporter(self.imageItems[4])
self.exporter6 = pg.exporters.ImageExporter(self.imageItems[5])
self.exporter7 = pg.exporters.ImageExporter(self.imageItems[6])
self.exporter8 = pg.exporters.ImageExporter(self.imageItems[7])

self.exporter = [self.exporter1,self.exporter2,self.exporter3,self.exporter4,self.exporter5,self.exporter6,self.exporter7,self.exporter8]

for i in range(len(self.pItems)):
self.exporter[i].params.param('width').setValue(304, blockSignal=self.exporter[i].widthChanged)
self.exporter[i].params.param('height').setValue(163, blockSignal=self.exporter[i].heightChanged)

self.exporter[0].export(fname1)



El jueves, 19 de diciembre de 2019, 14:23:36 (UTC-6), Grant R escribió:
What are you having trouble doing? Plotting or plotting and saving? The plotting should be quite fast and you can do the saving similarly to how you plot. Have another qtimer and iterate through the images saving each time. 

On Fri, Dec 20, 2019 at 8:38 AM Aldrich Cabrera <alucar...@gmail.com> wrote:

It is working now. :) Thank you very much for the support! I appreciate your help Grant R.

Now I need to save images every 2 seconds. However the computer is slow, I suppose because I am reading all the channels and plotting at the same time.
Maybe it will be comfortable and fast in OpenCV or maybe no. What do you think?

For now, I will have to save the images. Thanks for the support!


--
You received this message because you are subscribed to a topic in the Google Groups "pyqtgraph" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/pyqtgraph/_RjdXBDKRJg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to pyqt...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages