3~4์ ๊ตฌํํ matplotlib ๊ธฐ๋ฐ ์ค์๊ฐ ๊ทธ๋ํ ์๊ฐํ๋ ์ฝ 1์ด์ ํ ๋ฒ ์ ๋ ์ ๋ฐ์ดํธ ๋์๊ณ , ์ด๋ ๊ฐ๊ด์ ์ผ๋ก ๋ดค์ ๋ ๋น ๋ฅด์ง ์์๋ค. 1์ด์ 25000๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ ๊ฒ์ ๊ฐ์ํ์ ๋ 10๋ง ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ํ ๋ฒ์ ๋ณด์ฌ์ฃผ๋ฏ๋ก ์ฝ 4๋ฒ ์ ๋ฐ์ดํธํ๋ฉด ๋ค๋ฅธ ๊ทธ๋ํ๊ฐ ๋์๊ณ , ๋๋ ๋๊ธฐ๋ ๊ฒ๋ ์ฒด๊ฐ์ด ๋์๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ๋ฐฉ์์ ์ฐพ๋ ์ค, ๋ ๋น ๋ฅธ plotting ๋ฐฉ์ ๋ช ๊ฐ์ง๋ฅผ ์ถ์ฒ๋ฐ์๋๋ฐ ๊ทธ ์ค ํ๋๊ฐ ๋ฐ๋ก pyqtgraph์ด๋ค.
๊ฐ์ง๊ณ ์๋ ๋ค๋ฅธ daq ์ฅ์น ์ค SpectraDAQ-200์ด ์๋๋ฐ, ๊ฐ์ ํ์ฌ์์ ์ง์ํ๋ Spectra PLUS ๋ชจ๋ํฐ๋ง ์์คํ ๊ณผ ๊ฑฐ์ ๋น์ทํ ์์ค์ ์๊ฐํ ๋ฅ๋ ฅ์ ๋ณด์ฌ์ฃผ์๋ค! 3๋ฒ ๊ธ์์ ๊ตฌํํ ๋ถ๋ถ์ pyqtgraph ๊ธฐ๋ฐ์ผ๋ก ๋ค์ ๋ง๋ค์๋ค. ํ ๋ฒ ๋ณด๋๋ก ํ๊ฒ ๋ค.
import sys
from threading import Lock, Thread
import csv, socket, array
import pyqtgraph as pg
from PyQt5.QtWidgets import *
from PyQt5.QtCore import pyqtSlot, QTimer
from PyQt5.QtGui import QPalette, QColor, QFont
import torch
import numpy as np
from stft import show_result
pyqtgraph๋ PyQt5~6์ ๊ธฐ๋ฐ์ผ๋ก ๋์ํ๋ฏ๋ก PyQt5 ์ค์น๊ฐ ํ์์ ์ด๋ค. windows์ ๊ฒฝ์ฐ pip๋ก ์ค์นํ๋ฉด ๋๊ณ , Ubuntu ๋ฑ์์๋ ์ข ๋ ๋ณตํฉ์ ์ธ ๋ฐฉ๋ฒ์ด ํ์ํ๋ฐ ๊ทธ ๋ถ๋ถ์ ๋งํฌ ์ฐธ์กฐ.
1. UI ๊ตฌ์ฑ
class Main(QWidget):
def __init__(self):
super().__init__()
self.paused = False
self.label = QLabel("Real-Time Graph")
self.label.setFont(QFont("Arial", 20))
# define plot
self.orig = pg.PlotWidget(title="Original")
self.orig.plotItem.setLabels(bottom='Samples', left="IEPE (mV)")
self.orig.plotItem.getAxis('bottom').setPen(pg.mkPen(color='#000000'))
self.orig.plotItem.getAxis('left').setPen(pg.mkPen(color='#000000'))
self.orig.setBackground('w')
self.orig.setStyleSheet("border: 1px solid black; padding-left:10px; padding-right:10px; background-color: white;")
self.fft = pg.PlotWidget(title="FFT")
self.fft.plotItem.setLabels(bottom="Frequency (Hz)", left="Amplitude")
self.fft.plotItem.getAxis('bottom').setPen(pg.mkPen(color='#000000'))
self.fft.plotItem.getAxis('left').setPen(pg.mkPen(color='#000000'))
self.fft.setBackground('w')
self.fft.setStyleSheet("border: 1px solid black; padding-left:10px; padding-right:10px; background-color: white;")
# plot control button
self.pause_button = QPushButton()
self.pause_button.setText("Pause/Restart")
self.pause_button.clicked.connect(self.toggle_event)
self.pause_button.setMinimumSize(300, 50)
self.pause_button.setStyleSheet("background-color: white; padding-left:10px; padding-right:10px; font-size: 15px; font-family: Arial;")
# start record button
self.record_button = QPushButton()
self.record_button.setText("Start Recording")
self.record_button.clicked.connect(self.start_recording)
self.record_button.setMinimumSize(300, 50)
self.record_button.setStyleSheet("background-color: white; font-size: 15px;font-family: Arial;")
# data save button
self.save_button = QPushButton()
self.save_button.setText("Save Data")
self.save_button.clicked.connect(self.save_data)
self.save_button.setMinimumSize(300, 50)
self.save_button.setStyleSheet("background-color: white; font-size: 15px;font-family: Arial;")
# show entire data analysis button
self.show_button = QPushButton()
self.show_button.setText("Show Analysis")
self.show_button.clicked.connect(show_result)
self.show_button.setMinimumSize(300, 50)
self.show_button.setStyleSheet("background-color: white; font-size: 15px;font-family: Arial;")
buttons = QHBoxLayout()
buttons.addWidget(self.pause_button)
buttons.addWidget(self.record_button)
buttons.addWidget(self.save_button)
buttons.addWidget(self.show_button)
box = QVBoxLayout()
box.addWidget(self.label)
box.addWidget(self.orig)
box.addWidget(self.fft)
box.addLayout(buttons)
box.setSpacing(30)
box.setContentsMargins(50,50,50,50)
self.setLayout(box)
# set Window
self.setGeometry(800, 300, 1500, 1000)
self.setWindowTitle("Real Time Data Graphs")
๋๋ถ๋ถ style sheet ์ ์ฉ๊ณผ ๊ด๋ จ๋ ๋ด์ฉ์ด๋ฏ๋ก ์์ธํ ๋ด์ผํ ๊ฒ์ ์ด๊ฒ ๋ฟ์ด๋ค.
self.orig = pg.PlotWidget(title="Original")
self.fft = pg.PlotWidget(title="FFT")
self.pause_button = QPushButton()
self.pause_button.setText("Pause/Restart")
self.pause_button.clicked.connect(self.toggle_event)
pg.PlotWidget ์ matplotlib ์ ์ผ์ข ์ subplot ๊ฐ์ ์ญํ ์ ํ๋ค. ๋ฒํผ์ ๊ฑฐ์ ์ ์ฌํ๋ฏ๋ก ๋์ด๊ฐ๋ค.
๊ทธ๋ฆฌ๊ณ HBox Layout์ ๋ฒํผ๋ค์ ๊ฐ๋ก๋ก ์ญ ๋ฃ์ด์ฃผ๊ณ , VBox Layout์ ๊ทธ๋ํ์ ๋ฒํผ๋ค์ ์น ๋ฃ์ด์ฃผ๋ฉด ์ด๋ฐ ๊ทธ๋ํ๊ฐ ์๊ธด๋ค.
์ ๋ฒ ๊ทธ๋ด๋ฏํ๋ค(๋ฟ๋ฏ)
original signal graph์ fft graph ๋ชจ๋ ์ ๊ทธ๋ํ ์ด๋ฏ๋ก ์๋์ ๊ฐ์ด ์ ์ธํด์ค ์ ์๋ค.
# set line graph
self.orig_p = self.orig.plot(pen='b')
self.fft_p = self.fft.plot(pen='r')
pyqtgraph์์ matplotlib์ animation๊ณผ ์ ์ฌํ ๊ฒฐ๊ณผ๋ฅผ ๋ด๋๋ก ํด์ฃผ๋ ๊ฒ์ด ๋ฐ๋ก Timer์ด๋ค. ์ค์ ํ ์๊ฐ๋ง๋ค ํจ์๋ฅผ ํธ์ถํด์ ๋ฐ์ดํฐ๋ฅผ ๊ฐฑ์ ํ๋๋ก ํ๊ณ , ์ด ๊ฐฑ์ ํ ๋ฐ์ดํฐ๋ฅผ ๊ทธ ํจ์์์ ๊ทธ๋ํ๋ฅผ ๊ทธ๋ฆฌ๋๋ก ๋ค๋ฅธ ํจ์๋ฅผ ํธ์ถํด์ ๊ทธ๋ํ๋ฅผ ๊ฐฑ์ ํ๋ค.
# set timer - animation controller
self.mytimer = QTimer()
self.mytimer.start(25)
self.mytimer.timeout.connect(self.get_data)
self.draw_chart()
self.show()
2. ๋ฐ์ดํฐ ๊ฐฑ์ / ๊ทธ๋ํ ๊ทธ๋ฆฌ๊ธฐ
def draw_chart(self):
"""
Update plot data and calculate FFT.
"""
# update original signal graph
self.orig_p.setData(self.graph_data)
# update fft graph
Fs = 25600.0
n = len(self.graph_data)
# pytorch fft
graph_data = torch.Tensor(self.graph_data)
graph_data.to("cuda:0")
fft_data = torch.fft.rfft(graph_data) / n
frequency = torch.arange(0.0, Fs/2.0, Fs/n)
self.fft_p.setData(frequency[:len(fft_data)//2], np.abs(fft_data[:len(fft_data)//2]))
@pyqtSlot()
def get_data(self):
"""
Separate plotting data from entire data.
"""
data_lock.acquire()
if len(entire_data) > 110000:
self.graph_data = entire_data[len(entire_data) - 100000:]
else:
self.graph_data = entire_data
data_lock.release()
self.draw_chart()
์ด ๋ถ๋ถ์ matplotlib์ผ๋ก ๊ตฌํํ์ ๋์ ๊ฑฐ์ ๊ฐ๋ค. ๊ทธ๋๋ set์ด์๋ค๋ฉด ์ด๋ฒ์๋ setData๋ฅผ ์ผ๋ค๋ ์ ๋์ ์ฐจ์ด๋ง ์๋ค.
@pyqtSlot()์ signal์ ์์ ํ์์ ๋ ์คํ๋๋ signal controller๋ผ๋ ๋ป์ด๋ค. ์์์ timer๋ฅผ ์ ์ธํ ๋ timeout์ด ๋์๋ค๋ ์๊ทธ๋์ด ๋ฐ์ํ๋ฉด get_data ํจ์๊ฐ ๋ถ๋ฆฌ๋ ์์ด๋ค.
3. ๋ฒํผ ํจ์ ์ ์ธ
def toggle_event(self):
"""
Control animation. Pause/Restart.
"""
if self.paused:
self.mytimer.start()
else:
self.mytimer.stop()
self.paused = not self.paused
def start_recording(self) -> None:
"""
Set start point of saving data.
"""
data_lock.acquire()
self.pos = len(entire_data) - 1
data_lock.release()
print("Recording started.")
def save_data(self) -> None:
"""
Save entire voltage data in csv.
During save data, graph plotting will be paused.
Using lock.
"""
self.mytimer.stop()
print("Saving...")
data_lock.acquire()
f = open("data.csv", "w")
target = csv.writer(f)
target.writerow(entire_data[self.pos:])
data_lock.release()
print("Saved {0} Data. Restart plotting.".format(len(entire_data)))
self.mytimer.start()
animation.stop์ด timer์ stop๊ณผ start๋ก ๋ณํ ๊ฒ์ ์ ์ธํ๋ฉด ๋ฌ๋ผ์ง ์ ์ด ์๋ค. ์๊ฒ๋ ๋์ด๊ฐ๋ค. ๋น์ฐํ UDP ํต์ ์ ํ๋ Thread ์ฝ๋๋ ๋ฌ๋ผ์ง ๊ฒ์ด ์๋ค.
def run() -> None:
"""
UDP data receive function.
Using lock.
"""
print("Connected", flush=True)
while True:
data = serverSocket.recv(32768)
signals = array.array('d')
signals.frombytes(data)
data_lock.acquire()
entire_data.extend(signals)
data_lock.release()
4. Main ํจ์ ์์ฑ
if __name__ == "__main__":
app = QApplication(sys.argv)
pal = app.palette()
pal.setColor(QPalette.Window, QColor(245,245,245))
app.setPalette(pal)
ex = Main()
# define socket setting and bind
server = ('YOUR_IP_ADDRESS', 2001)
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
serverSocket.bind(server)
# set data / data lock
entire_data = [0, 0]
data_lock = Lock()
# start UDP in another thread
t = Thread(target=run, args=())
t.start()
sys.exit(app.exec_())
PyQt ์ฐฝ์ ์คํ์ํค๊ธฐ ์ํ ์ฝ๋๋ง ์กฐ๊ธ ์ถ๊ฐ๋์๋ค. ์ด๋ ๊ฒ ๋ฐ๊ฟ์ฃผ๋ฉด ๋! ์ฌ์ค matplotlib์์ pyqt๋ก ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง ๊ฐ์ํ ๊ฑด๋ฐ๋ ์ฑ๋ฅ์ฐจ์ด๊ฐ ์์ฒญ๋๊ฒ ๋ฌ๋ค. ์ง์ฐ์๊ฐ์ด ์ฒด๊ฐ 20๋ฐฐ๋ ์ค์ด๋ ๋๋... ์ง์ง ์ค์๊ฐ์ด๋ผ๋ ๋๋์ด ๊ฐํด์ก๋ค.
STFT๋ฅผ ๊ณ์ฐํด์ ์ ์ฅํ ๋ฐ์ดํฐ๋ฅผ ์๊ฐํํ๋ ๋ถ๋ถ์ matplotlib์ผ๋ก ๋จ๊ฒจ๋๋ค. ๊ทธ์ชฝ์ด ๋ ๋ณด๊ธฐ๋ ์ข๊ธฐ๋ ํ๊ณ , ์ฑ๋ฅ๋ ์ค์ํ์ง ์๊ธฐ ๋๋ฌธ์ด๋ค.
์ฌ๋ด์ผ๋ก pyqtgraph๋ฅผ ์ฌ์ฉํ๋ STFT๋ ์ค์๊ฐ์ผ๋ก ์๊ฐํ๊ฐ ๊ฐ๋ฅํด์ก๋๋ฐ, ๋ฐ์ดํฐ๊ฐ ๋ฐ์ดํฐ๋ค ๋ณด๋ ๋น์ฅ์ ์ค์๊ฐ์ผ๋ก ๋ณธ๋ค๊ณ ๋ฌ๋ผ์ง์ง ์์ ๊ฒ ๊ฐ์ ์ ์ฉํ์ง ์์๋ค.
์๋ฌดํผ ์์ฑ๋ณธ์ ์๋ ๊ฒ~