대학원 연구를 시작하면서, 음성 파일을 다루는 어플리케이션을 하나 만들고 있습니다. WAV파일과 FFT 함수를 visualziation하는 방법이 matplotlib에서 워낙 쉽게 이루어지기 때문에, 이를 바로 띄울 수 있으면 좋겠다고 생각했는데, 예제들을 찾아보니 일반적으로 그냥 chart 만 띄우는 방식이었습니다.
좀 더 복잡한 GUI 구조에서는 어떤 식으로 진행해야 할지 싶어 이런저런 방법을 쓰다가 발견해낸 방법을 올려봅니다.
좀더 chart 를 NavigationToolbar 를 이용한 예제는 [여기] 에서 확인할 수 있습니다.
위의 소스코드를 살펴보면, 다음의 항목들이 있습니다.
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
WidgetPlot 이라는 클래스가 정의 되고, 이 Widget은 PlotCanvas와 NavigationToolbar를 갖고 있습니다. 만약 FigureCanvas만 쓸 것이라면 처음부터 FigureCanvas를 상속받은 클래스만 사용해도 괜찮을 것 같습니다.
class WidgetPlot(QWidget): def __init__(self, *args, **kwargs): QWidget.__init__(self, *args, **kwargs) self.setLayout(QVBoxLayout()) self.canvas = PlotCanvas(self, width=10, height=8) self.toolbar = NavigationToolbar(self.canvas, self) self.layout().addWidget(self.toolbar) self.layout().addWidget(self.canvas) class PlotCanvas(FigureCanvas): def __init__(self, parent=None, width=10, height=8, dpi=100): fig = Figure(figsize=(width, height), dpi=dpi) FigureCanvas.__init__(self, fig) self.setParent(parent) FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) FigureCanvas.updateGeometry(self) self.plot() def plot(self): data = [random.random() for i in range(250)] ax = self.figure.add_subplot(111) ax.plot(data, 'r-', linewidth = 0.5) ax.set_title('PyQt Matplotlib Example') self.draw()
m = WidgetPlot(self) m.setMaximumHeight(300) vlay.addWidget(m)
NavigationToolbar를 사용하지 않는다면, WidgetPlot의 self.toolbar 가 적혀있는 부분을 모두 주석처리하면 되겠습니다.
나중에 목적에 맞춰 plot 함수를 다시 짜주면 되는 것이고, 이를 어떻게 Qt Designer와 함께 맞춰갈 것인가에 고민이 됩니다. 이를 저는 다음과 같은 순서로 해결하였습니다.
- Qt Designer에서 해당 위젯이 들어갈 부분을 label로 만들어 비워둔다.
- 실행하는 윈도우가 load되면, 그 다음에 해당 label을 숨기고 그 위치에 WidgetPlot을 넣어준다.
1. Qt Designer로 영역을 정해둠
아래 그림처럼 일단 그려둡니다. 결국 윈도우 size policy에 관련된 부분을 직관적으로 확인하려면 Qt Designer는 피할수 없는 부분인듯 합니다.
2. 소스 코드 처리
if __name__ == "__main__": app = QApplication(sys.argv) myWindow = MyWindow() myWindow.show() myWindow.firstAction() app.exec_()
보통 myWindow.show() 만 하는데, 그 이후에 firstAction() 이라는 함수를 만들었습니다.
class MyWindow(QMainWindow, Ui_MainWindow): def firstAction(self): self.layout().removeWidget(self.lblAreaWAV) self.layout().removeWidget(self.lblAreaFFT) self.layout().removeWidget(self.lblAreaMel) self.layout().removeWidget(self.lblAreaEtc) self.lblAreaWAV.setParent(None) self.lblAreaFFT.setParent(None) self.lblAreaMel.setParent(None) self.lblAreaEtc.setParent(None) self.plotWav = WidgetPlot(self.centralwidget) self.plotFft = WidgetPlot(self.centralwidget) self.plotMel = WidgetPlot(self.centralwidget) self.plotEtc = WidgetPlot(self.centralwidget) self.VLULayout.addWidget(self.plotWav) self.VLULayout.addWidget(self.plotFft) self.VLULayout.addWidget(self.plotMel) self.VLULayout.addWidget(self.plotEtc) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.plotWav.sizePolicy().hasHeightForWidth()) self.plotWav.setSizePolicy(sizePolicy) self.plotWav.setMinimumSize(QtCore.QSize(0, 200)) self.plotFft.setSizePolicy(sizePolicy) self.plotFft.setMinimumSize(QtCore.QSize(0, 200)) self.plotMel.setSizePolicy(sizePolicy) self.plotMel.setMinimumSize(QtCore.QSize(0, 200)) self.plotEtc.setSizePolicy(sizePolicy) self.plotEtc.setMinimumSize(QtCore.QSize(0, 200))
그리고 위의 소스코드처럼, 원래 있던 4개의 label을 지우고, 새로 4개의 WidgetPlot 을 새로 생성하여, 원래의 layout에 넣도록 합니다.
3. 결과물
다음과 같이 출력 됩니다. 일단 동일한 plot 함수를 썼기 때문에 같은 스타일로 출력 될 수 밖에 없습니다. 다만 plot 함수 안에 random을 이용해 값을 생성하기 때문에 그래프 자체는 다르게 출력 됩니다.
세부적인 조정은 개발하면서 바꿔나가야 하겠습니다.