예제 #1
0
class SpectrogramWidget(QtGui.QWidget):
    """
    Widget that visualize and interacts with a spectrogram graph
    of an audio signal.
    """
    # SIGNALS

    # CONSTANTS
    # Value of the axis lines opacity
    AXIS_LINES_OPACITY = 255

    # Max amount of columns to show on the visual widget.
    MAX_SPECGRAM_COLUMNS = 800

    def __init__(self, *args, **kwargs):
        QtGui.QWidget.__init__(self)
        # variable that handles the manipulation of the spectrogram
        # create, recompute etc
        self.specgramHandler = Spectrogram()

        # create the histogram widget
        self.imageItem = ImageItem()
        self.__histogram = HorizontalHistogramWidget()
        self.__histogram.item.setImageItem(self.imageItem)

        # internal vieBox for image graph
        self.viewBox = ViewBox()
        self.viewBox.addItem(self.imageItem)
        self.viewBox.setMouseEnabled(x=False, y=False)
        self.viewBox.setMenuEnabled(False)
        self.viewBox.setAspectLocked(False)

        # set the X and Y axis for the graph
        self.xAxis = OscXAxis(self, orientation='bottom')
        self.yAxis = SpecYAxis(self, orientation='left')

        self.xAxis.linkToView(self.viewBox)
        self.yAxis.linkToView(self.viewBox)

        self.xAxis.setGrid(self.AXIS_LINES_OPACITY)
        self.yAxis.setGrid(self.AXIS_LINES_OPACITY)

        # adjust the graph zone in the control
        horizontal_histogram = kwargs[
            "horizontal_histogram"] if "horizontal_histogram" in kwargs else True
        visible_histogram = kwargs[
            "visible_histogram"] if "visible_histogram" in kwargs else False
        self.__updateLayout(histogramHorizontal=(
            horizontal_histogram if visible_histogram else None))

    def __updateLayout(self, histogramHorizontal=None):
        """
        Updates the layout of the widget to change the histogram
        position (horizontal-vertical) and visibility
        :param histogramHorizontal: None if invisible, true if horizontal
        false if vertical
        :return:
        """
        # graph_control_layout

        self.graphics_view = pg.GraphicsView()

        # set the layout of the graphics view internal
        # that contains the axis and the view box to graph the spectrogram
        graphics_view_grid_layout = QtGui.QGraphicsGridLayout()
        graphics_view_grid_layout.setContentsMargins(0, 0, 0, 0)
        graphics_view_grid_layout.setHorizontalSpacing(0)
        graphics_view_grid_layout.setVerticalSpacing(0)

        graphics_view_grid_layout.addItem(self.xAxis, 1, 1)
        graphics_view_grid_layout.addItem(self.yAxis, 0, 0)
        graphics_view_grid_layout.addItem(self.viewBox, 0, 1)

        self.graphics_view.centralWidget.setLayout(graphics_view_grid_layout)
        layout = QtGui.QGridLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.graphics_view, 0, 0)
        # add the elements in row,col

        if histogramHorizontal is not None:
            row = 1 if histogramHorizontal else 0
            col = 1 - row
            self.__histogram = HorizontalHistogramWidget() if histogramHorizontal else\
                               HistogramLUTWidget()
            self.__histogram.item.gradient.loadPreset('thermal')
            self.__histogram.item.setImageItem(self.imageItem)
            layout.addWidget(self.histogram, row, col)

        self.setLayout(layout)

    # region Signal Property
    @property
    def signal(self):
        return self.specgramHandler.signal

    @signal.setter
    def signal(self, signal):
        if signal is None or not isinstance(signal, AudioSignal):
            raise ValueError(
                "Invalid assignation. Must be of type AudioSignal")

        self.specgramHandler.signal = signal

        max_columns = max(self.viewBox.width(), self.MAX_SPECGRAM_COLUMNS)

        self.specgramHandler.recomputeSpectrogram(maxCol=max_columns)
        # updating the axis scales
        self.xAxis.scale = self.specgramHandler.signal.samplingRate

        # updating the frequencies present in the signal
        # samplingRate/2 by Nyquist theorem. After update the spectrogram handler
        # there is an array in the specgramHandler with the freqs returned by the specgram
        # freqs is an array with the values of the frequency of each row of the specgram matriz
        self.yAxis.frequencies = self.specgramHandler.freqs

    # endregion

    # region Histogram Prop
    @property
    def histogram(self):
        """
        Histogram widget with the values of the image.
        interacts with the image graph to change its color,
        threshold etc. Allow to visualize it outside the spectrogram widget
        :return:
        """
        return self.__histogram

    # endregion

    def getInfo(self, x, y):
        """
        Gets the values at x, y in the spectrogram
        :param x: the time position in rows of the spectrogram matriz
        :param y: the freq position in columns of the spectrogram matriz
        :return: a tuple with (time, freq, intensity)
        """
        return self.specgramHandler.getInfo(x, y)

    def graph(self, indexFrom=0, indexTo=-1, padding=0):

        indexTo = indexTo if (indexTo >= 0
                              and indexTo > indexFrom) else self.signal.length

        # avoid to set the max columns too high that the spectrogram cant be computed
        # and do not consume more resources that necessary
        # show at most as many columns as pixels in the widget's width
        max_columns = max(self.viewBox.width(), self.MAX_SPECGRAM_COLUMNS)

        self.specgramHandler.recomputeSpectrogram(indexFrom, indexTo,
                                                  max_columns)
        self.yAxis.frequencies = self.specgramHandler.freqs

        # set the new spectrogram image computed
        self.imageItem.setImage(self.specgramHandler.matriz)
        self.viewBox.setRange(
            xRange=(self.specgramHandler.from_osc_to_spec(indexFrom),
                    self.specgramHandler.from_osc_to_spec(indexTo - 1)),
            padding=padding)

        # update the histogram colors of the spectrogram
        self.histogram.item.region.lineMoved()
        self.histogram.item.region.lineMoveFinished()
        self.xAxis.setRange(indexFrom, indexTo)

        self.update()