Exemplo n.º 1
0
class SpectrogramViewer(QtWidgets.QWidget, Ui_SpectrogramViewer):

    seek = QtCore.Signal(float)
    spectrogram_drawn = QtCore.Signal()

    def __init__(self, parent=None, audio=None, settings=None):
        super().__init__(parent)
        self.setupUi(self)
        self.bg_image = None
        self.spectrogram_scene = QGraphicsScene(self)

        self._audio = None
        self._spectrogram = None
        self._image_generator = None

        self.sound_marker = None
        self.marker_position = 0
        self.yscale = 1

        self.settings = settings

        self.audio = audio
        self.setup_graphics_view()
        self.define_shortcuts()
        self.install_filters()

    def setup_graphics_view(self):
        self.spectrogram_view.setScene(self.spectrogram_scene)

    def define_shortcuts(self):
        QtGui.QShortcut(
            QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_Plus), self,
            self.zoom_in)
        QtGui.QShortcut(
            QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_Minus),
            self,
            self.zoom_out,
        )

    def install_filters(self):
        self.mouse_filter = SpectrogramMouseFilter(self)
        self.spectrogram_scene.installEventFilter(self.mouse_filter)

    @property
    def audio(self):
        return self._audio

    @audio.setter
    def audio(self, audio):
        if audio is not None:
            self._audio = audio
            self.spectrogram = Spectrogram(audio, self.spectrogram_options)
            self.marker_position = 0

    @property
    def spectrogram(self):
        return self._spectrogram

    @spectrogram.setter
    def spectrogram(self, spectrogram):
        self._spectrogram = spectrogram
        self.display_spectrogram()

    @property
    def image_options(self):
        return self.settings.image_options if self.settings else {}

    @property
    def spectrogram_options(self):
        return self.settings.spectrogram_options if self.settings else {}

    @property
    def image_generator(self):
        if self._image_generator is None:
            self._image_generator = ImageGenerator(self.image_options)
        return self._image_generator

    def display_spectrogram(self):
        # TODO: save image somewhere
        im = self.image_generator.spec2img(self.spectrogram)
        # TODO: change events when checkbox is checked
        # if self.checkbox_draw_events.isChecked():
        #     im = self.draw_events(im, max_duration)
        img = ImageQt.ImageQt(im)
        pixmap = QtGui.QPixmap.fromImage(img)

        # Change Qt array to a Qt graphic
        self.bg_image = QGraphicsPixmapItem(pixmap)
        self.spectrogram_scene.clear()
        self.spectrogram_scene.addItem(self.bg_image)
        # Ensure spectrogram graphic is displayed as background
        self.bg_image.setZValue(-100)
        self.bg_image.setPos(0, 0)
        self.sound_marker = None
        if self.marker_position:
            self.update_sound_marker(None)
        self.spectrogram_drawn.emit()

    def display_text(self, text):
        text_item = QGraphicsTextItem()
        text_item.setPos(150, 100)
        text_item.setPlainText(text)
        self.spectrogram_scene.clear()
        self.spectrogram_scene.addItem(text_item)

    def update_sound_marker(self, position_sec):
        # 100 # multiply by step-size in SpecGen()
        if position_sec is not None:
            self.marker_position = self.image_generator.sec2pixels(
                position_sec)
        line = QtCore.QLineF(
            self.marker_position,
            0,
            self.marker_position,
            self.image_generator["height"],
        )
        if not self.sound_marker:
            penCol = QtGui.QColor()
            penCol.setRgb(255, 0, 0)
            self.sound_marker = self.spectrogram_scene.addLine(
                line, QtGui.QPen(penCol))
        else:
            self.sound_marker.setLine(line)

        self.spectrogram_scene.update()

        if self.spectrogram_options["follow_sound"]:
            self.center_view()

    def center_view(self):
        self.spectrogram_view.centerOn(self.marker_position,
                                       self.get_center().y())

    def zoom(self, scale, scene_pos=None):
        self.yscale *= scale
        self.spectrogram_view.scale(scale, scale)
        if scene_pos:
            self.spectrogram_view.centerOn(scene_pos)

    def zoom_in(self):
        self.zoom(1.5)

    def zoom_out(self):
        self.zoom(0.75)

    def seek_sound(self, pos):
        self.seek.emit(self.image_generator.pixels2sec(pos))

    def update_spectrogram(self, option, redraw):
        if redraw:
            self.spectrogram = Spectrogram(self.audio,
                                           self.spectrogram_options)
            self.freq2pixels(6000)

    def update_image(self, option, redraw):
        if redraw:
            self.display_spectrogram()

    def get_center(self):
        return self.spectrogram_view.mapToScene(
            self.spectrogram_view.viewport().rect().center())

    def clear_rects(self):
        items = self.spectrogram_scene.items()
        for item in items:
            if isinstance(item, AnnotatedRectItem):
                self.spectrogram_scene.removeItem(item)

    def freq2pixels(self, freq):
        res = 0
        if self.spectrogram["scale"] == "Linear":
            height = self.image_generator["height"]
            max_freq = self.audio.sr / 2
            freq_step = height / max_freq
            res = height - (freq * freq_step)
        else:
            print("Only linear scale is supported so far")

        return res

    def draw_annotation(self, opts):
        x1 = self.image_generator.sec2pixels(opts.get("start", 0))
        x2 = self.image_generator.sec2pixels(opts.get("end", 0))
        y1 = self.freq2pixels(opts.get("max_freq", 0))
        y2 = self.freq2pixels(opts.get("min_freq", 0))
        text = opts.get("text", "")
        buffer = opts.get("vertical_buffer", 1)
        top_offset = opts.get("top_offset", 0)
        bottom_offset = opts.get("bottom_offset", 0)

        if y2 - y1 <= 0:
            y1 = 0
            y2 = self.spectrogram_scene.height() - 2
            if buffer:
                v_offset = buffer * y2 / 100
                y1 += v_offset + top_offset
                y2 -= v_offset - bottom_offset
                if text:
                    font = QtGui.QFont(opts.get("text_font", ""),
                                       opts.get("text_fontsize", 12))
                    font_height = QtGui.QFontMetrics(font).height()
                    y1 += font_height

        coords = (x1, y1, x2 - x1, y2 - y1)

        opts["coords"] = coords

        rect = AnnotatedRectItem(opts)

        self.spectrogram_scene.addItem(rect)

    def draw_rect(self,
                  start,
                  end,
                  y=0,
                  height=-1,
                  color="#ffffff",
                  fill="",
                  buffer=1):
        x = self.image_generator.sec2pixels(start)
        width = self.image_generator.sec2pixels(end - start)
        if height == -1:
            height = self.spectrogram_scene.height() - 2
        rect = QtWidgets.QGraphicsRectItem()
        rect.setPen(QtGui.QPen(color))
        if fill is not None:
            if not fill:
                fill = color
            rect.setBrush(QtGui.QBrush(fill, QtCore.Qt.SolidPattern))
        if buffer:
            y += buffer * height / 100
            height -= buffer * height / 100
        rect.setRect(x, y, width, height)
        self.spectrogram_scene.addItem(rect)
Exemplo n.º 2
0
 def delete_selected(scene: QGraphicsScene, gim: GraphicItemModel):
     gim.delete_all_from_references(scene.selectedItems())
     for item in scene.selectedItems():
         scene.removeItem(item)
Exemplo n.º 3
0
class QRScanner(QWidget):
    QR_SIZE = 0.75  # Size of rectangle for QR capture
    readyForCapture = Signal(bool)
    decodedQR = Signal(str)

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.processing = False
        self.rectangle = None

        self.setMinimumHeight(405)
        self.layout = QVBoxLayout()
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.scene = QGraphicsScene(self)
        self.scene.setBackgroundBrush(QBrush(Qt.black))
        self.view = QGraphicsView(self.scene)
        self.viewfinder = QGraphicsVideoItem()
        self.scene.addItem(self.viewfinder)
        self.layout.addWidget(self.view)
        self.setLayout(self.layout)

        self.camera = None
        self.captureSession = None
        self.imageCapture = None
        self.captureTimer = None

    def startScan(self):
        if len(QMediaDevices.videoInputs()) == 0:
            logging.warning(self.tr("There are no cameras available"))
            return

        self.processing = True  # disable any capture while camera is starting
        self.camera = QCamera(QMediaDevices.defaultVideoInput())
        self.captureSession = QMediaCaptureSession()
        self.imageCapture = QImageCapture(self.camera)
        self.captureSession.setCamera(self.camera)
        self.captureSession.setVideoOutput(self.viewfinder)
        self.captureSession.setImageCapture(self.imageCapture)

        self.camera.errorOccurred.connect(self.onCameraError)
        self.readyForCapture.connect(self.onReadyForCapture)
        self.imageCapture.errorOccurred.connect(self.onCaptureError)
        self.imageCapture.readyForCaptureChanged.connect(
            self.onReadyForCapture)
        self.imageCapture.imageCaptured.connect(self.onImageCaptured)
        self.viewfinder.nativeSizeChanged.connect(self.onVideoSizeChanged)

        self.camera.start()
        self.processing = False
        self.readyForCapture.emit(self.imageCapture.isReadyForCapture())

    def stopScan(self):
        if self.camera is None:
            return
        self.processing = True  # disable capture
        self.camera.stop()

        self.camera = None
        self.captureSession = None
        self.imageCapture = None
        self.captureTimer = None

    def onVideoSizeChanged(self, _size):
        self.resizeEvent(None)

    # Take QImage or QRect (object with 'width' and 'height' properties and calculate position and size
    # of the square with side of self.QR_SIZE from minimum of height or width
    def calculate_center_square(self, img_rect) -> QRectF:
        a = self.QR_SIZE * min(img_rect.height(),
                               img_rect.width())  # Size of square side
        x = (img_rect.width() -
             a) / 2  # Position of the square inside rectangle
        y = (img_rect.height() - a) / 2
        if type(img_rect
                ) != QImage:  # if we have a bounding rectangle, not an image
            x += img_rect.left(
            )  # then we need to shift our square inside this rectangle
            y += img_rect.top()
        return QRectF(x, y, a, a)

    def resizeEvent(self, event):
        bounds = self.scene.itemsBoundingRect()
        if bounds.width() <= 0 or bounds.height() <= 0:
            return  # do nothing if size is zero
        self.view.fitInView(bounds, Qt.KeepAspectRatio)
        if self.rectangle is not None:
            self.scene.removeItem(self.rectangle)
        pen = QPen(Qt.green)
        pen.setWidth(0)
        pen.setStyle(Qt.DashLine)
        self.rectangle = self.scene.addRect(
            self.calculate_center_square(bounds), pen)
        self.view.centerOn(0, 0)
        self.view.raise_()

    def onCaptureError(self, _id, error, error_str):
        self.processing = False
        self.onCameraError(error, error_str)

    def onCameraError(self, error, error_str):
        logging.error(
            self.tr("Camera error: " + str(error) + " / " + error_str))

    def onReadyForCapture(self, ready: bool):
        if ready and not self.processing:
            self.imageCapture.capture()
            self.processing = True

    def onImageCaptured(self, _id: int, img: QImage):
        self.decodeQR(img)
        self.processing = False
        if self.imageCapture is not None:
            self.readyForCapture.emit(self.imageCapture.isReadyForCapture())

    def decodeQR(self, qr_image: QImage):
        cropped = qr_image.copy(
            self.calculate_center_square(qr_image).toRect())
        # TODO: the same code is present in slips.py -> move to one place
        buffer = QBuffer()
        buffer.open(QBuffer.ReadWrite)
        cropped.save(buffer, "BMP")
        try:
            pillow_image = Image.open(io.BytesIO(buffer.data()))
        except UnidentifiedImageError:
            print("Image format isn't supported")
            return
        barcodes = pyzbar.decode(pillow_image,
                                 symbols=[pyzbar.ZBarSymbol.QRCODE])
        if barcodes:
            self.decodedQR.emit(barcodes[0].data.decode('utf-8'))