Capturing and recording

This example extends the class defined in Camera streaming example to implement image capturing and video recording.

Before running the example, make sure your scripts are placed in the following directory structure:

examples
├── scripts
│⠀⠀⠀├── camera_capture.py
│⠀⠀⠀├── imagecapture.py
│⠀⠀⠀├── videorecorder.py
│⠀⠀⠀└── camera_stream.py
├── capture.svg
└── record.svg

Running camera_capture.py opens a camera streaming window where you can capture or record the frames.

imagecapture.py

imagecapture.py

import imageio
import vimba  # type: ignore[import]
from PySide6.QtCore import QObject, Signal, Slot, QMutex
from civiq6 import VimbaCaptureSession
from typing import Optional


VIMBA_LOGGER = vimba.Log.get_instance()


class ImageCapture(QObject):
    imageSaved = Signal(int, str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._captureSession = None
        self._id = 0

        self._image = None
        self._lock = QMutex()

    def captureSession(self) -> Optional[VimbaCaptureSession]:
        return self._captureSession

    def _setCaptureSession(self, captureSession: Optional[VimbaCaptureSession]):
        self._captureSession = captureSession

    @Slot(str)
    def captureToFile(self, path: str = "") -> int:
        if self.captureSession() is None or self._image is None:
            return -1

        self._lock.lock()

        try:
            imageio.imwrite(path, self._image)
            self._image = None
        finally:
            self._lock.unlock()

        ret_id = self._id
        self._id += 1
        VIMBA_LOGGER.info("Captured %s" % path)
        self.imageSaved.emit(ret_id, path)

        return ret_id

    def _setFrame(self, frame: vimba.Frame):
        obtained = self._lock.tryLock()
        if obtained:
            try:
                self._image = frame.as_opencv_image().copy()
            finally:
                self._lock.unlock()

videorecorder.py

videorecorder.py

import enum
import imageio
import vimba  # type: ignore[import]
from PySide6.QtCore import QObject, QUrl, Signal, Slot, QMutex
from civiq6 import VimbaCaptureSession
from typing import Optional


VIMBA_LOGGER = vimba.Log.get_instance()


class VideoRecorder(QObject):
    class RecorderState(enum.IntEnum):
        StoppedState = 1
        RecordingState = 2
        PausedState = 3

    recorderStateChanged = Signal(RecorderState)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._captureSession = None
        self._outputLocation = QUrl()
        self._recorderState = self.RecorderState.StoppedState
        self._writer = None
        self._lock = QMutex()

    def captureSession(self) -> Optional[VimbaCaptureSession]:
        return self._captureSession

    def _setCaptureSession(self, captureSession: Optional[VimbaCaptureSession]):
        self._captureSession = captureSession

    def outputLocation(self) -> QUrl:
        return self._outputLocation

    @Slot(QUrl)
    def setOutputLocation(self, outputLocation: QUrl):
        self._outputLocation = outputLocation

    def recorderState(self) -> RecorderState:
        return self._recorderState

    @Slot()
    def record(self):
        session = self._captureSession
        if session is None:
            return
        camera = session.camera()
        if camera is None:
            return
        if not camera.isAvailable():
            return

        if self._recorderState == self.RecorderState.StoppedState:
            path = self._outputLocation.toLocalFile()
            fps = camera.getFeatureByName("AcquisitionFrameRate").get()

            self._lock.lock()
            try:
                self._writer = imageio.get_writer(path, fps=fps)
            finally:
                self._lock.unlock()

            self._recorderState = self.RecorderState.RecordingState
            VIMBA_LOGGER.info(f"Started recording {path}")
            self.recorderStateChanged.emit(self._recorderState)

        elif self._recorderState == self.RecorderState.PausedState:
            self._recorderState = self.RecorderState.RecordingState

            VIMBA_LOGGER.info(f"Resumed recording {self._outputLocation.toLocalFile()}")
            self.recorderStateChanged.emit(self._recorderState)

    @Slot()
    def pause(self):
        if self._recorderState == self.RecorderState.RecordingState:
            self._recorderState = self.RecorderState.PausedState

            VIMBA_LOGGER.info(f"Paused recording {self._outputLocation.toLocalFile()}")
            self.recorderStateChanged.emit(self._recorderState)

    @Slot()
    def stop(self):
        if self._recorderState != self.RecorderState.StoppedState:
            self._lock.lock()
            try:
                self._writer.close()
                self._writer = None
            finally:
                self._lock.unlock()

            self._recorderState = self.RecorderState.StoppedState
            VIMBA_LOGGER.info(f"Stopped recording {self._outputLocation.toLocalFile()}")
            self.recorderStateChanged.emit(self._recorderState)

    def _setFrame(self, frame: vimba.Frame):
        if self._recorderState == self.RecorderState.RecordingState:
            obtained = self._lock.tryLock()
            if obtained:
                try:
                    self._writer.append_data(frame.as_opencv_image())
                finally:
                    self._lock.unlock()

camera_capture.py

camera_capture.py

from PySide6.QtCore import QUrl, Signal, Slot, QSize
from PySide6.QtWidgets import QToolBar, QLineEdit, QToolButton, QStyle
from PySide6.QtGui import QIcon
from imagecapture import ImageCapture
from videorecorder import VideoRecorder
from camera_stream import CameraWindow


class CaptureToolBar(QToolBar):
    captureRequested = Signal(str)
    recordPathChanged = Signal(QUrl)
    recordStartRequested = Signal()
    recordStopRequested = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self._capturePathLineEdit = QLineEdit()
        self._captureButton = QToolButton()
        self._recordPathLineEdit = QLineEdit()
        self._recordButton = QToolButton()
        self._recordState = VideoRecorder.RecorderState.StoppedState

        self._capturePathLineEdit.setPlaceholderText("Image capture path")
        self._captureButton.setToolTip("Click to capture image")
        captureButtonIcon = QIcon()
        captureButtonIcon.addFile("../capture.svg", QSize(24, 24))
        self._captureButton.setIcon(captureButtonIcon)

        self._recordPathLineEdit.setPlaceholderText("Video record path")
        self._recordButton.setToolTip("Click to toggle video recording")
        recordButtonIcon = QIcon()
        recordButtonIcon.addFile("../record.svg", QSize(24, 24))
        self._recordButton.setIcon(recordButtonIcon)

        self.addWidget(self._capturePathLineEdit)
        self.addWidget(self._captureButton)
        self.addSeparator()
        self.addWidget(self._recordPathLineEdit)
        self.addWidget(self._recordButton)

        self._captureButton.clicked.connect(self._onCaptureButtonClick)
        self._recordPathLineEdit.textChanged.connect(self._onRecordPathEdit)
        self._recordButton.clicked.connect(self._onRecordButtonClick)

    def _onCaptureButtonClick(self):
        path = self._capturePathLineEdit.text()
        self.captureRequested.emit(path)

    def _onRecordPathEdit(self, path: str):
        self.recordPathChanged.emit(QUrl.fromLocalFile(path))

    def _onRecordButtonClick(self):
        if self._recordState == VideoRecorder.RecorderState.StoppedState:
            self.recordStartRequested.emit()
        else:
            self.recordStopRequested.emit()

    @Slot(VideoRecorder.RecorderState)
    def setRecorderState(self, state: VideoRecorder.RecorderState):
        if state == VideoRecorder.RecorderState.RecordingState:
            self._recordButton.setCheckable(True)
            self._recordButton.setChecked(True)
            self._recordButton.setIcon(
                self.style().standardIcon(QStyle.StandardPixmap.SP_MediaStop)
            )
        elif state == VideoRecorder.RecorderState.StoppedState:
            self._recordButton.setChecked(False)
            self._recordButton.setCheckable(False)
            icon = QIcon()
            icon.addFile("../record.svg", QSize(24, 24))
            self._recordButton.setIcon(icon)
        self._recordState = state


class CameraCaptureWindow(CameraWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self._imageCapture = ImageCapture()
        self._videoRecorder = VideoRecorder()
        self._captureSession.setImageCapture(self._imageCapture)
        self._captureSession.setRecorder(self._videoRecorder)

        self._toolBar = CaptureToolBar()
        self._toolBar.captureRequested.connect(self._imageCapture.captureToFile)
        self._toolBar.recordPathChanged.connect(self._videoRecorder.setOutputLocation)
        self._toolBar.recordStartRequested.connect(self._videoRecorder.record)
        self._toolBar.recordStopRequested.connect(self._videoRecorder.stop)
        self._videoRecorder.recorderStateChanged.connect(self._toolBar.setRecorderState)

        self.addToolBar(self._toolBar)


if __name__ == "__main__":
    import vimba  # type: ignore[import]
    from PySide6.QtWidgets import QApplication
    import sys

    VIMBA_INST = vimba.Vimba.get_instance()
    VIMBA_INST.enable_log(vimba.LOG_CONFIG_INFO_CONSOLE_ONLY)

    app = QApplication(sys.argv)
    window = CameraCaptureWindow()
    window.camera().start()
    window.show()
    app.exec()
    app.quit()