diff options
Diffstat (limited to 'src/py/cam/cam_qt.py')
-rw-r--r-- | src/py/cam/cam_qt.py | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py new file mode 100644 index 00000000..22d8c4da --- /dev/null +++ b/src/py/cam/cam_qt.py @@ -0,0 +1,182 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com> + +from helpers import mfb_to_rgb +from PyQt6 import QtCore, QtGui, QtWidgets +import libcamera as libcam +import libcamera.utils +import sys + + +# Loading MJPEG to a QPixmap produces corrupt JPEG data warnings. Ignore these. +def qt_message_handler(msg_type, msg_log_context, msg_string): + if msg_string.startswith("Corrupt JPEG data"): + return + + # For some reason qInstallMessageHandler returns None, so we won't + # call the old handler + if old_msg_handler is not None: + old_msg_handler(msg_type, msg_log_context, msg_string) + else: + print(msg_string) + + +old_msg_handler = QtCore.qInstallMessageHandler(qt_message_handler) + + +def rgb_to_pix(rgb): + w = rgb.shape[1] + h = rgb.shape[0] + qim = QtGui.QImage(rgb, w, h, QtGui.QImage.Format.Format_RGB888) + pix = QtGui.QPixmap.fromImage(qim) + return pix + + +class QtRenderer: + def __init__(self, state): + self.state = state + + self.cm = state.cm + self.contexts = state.contexts + + def setup(self): + self.app = QtWidgets.QApplication([]) + + windows = [] + + for ctx in self.contexts: + for stream in ctx.streams: + window = MainWindow(ctx, stream) + window.show() + windows.append(window) + + self.windows = windows + + buf_mmap_map = {} + + for ctx in self.contexts: + for stream in ctx.streams: + for buf in ctx.allocator.buffers(stream): + mfb = libcamera.utils.MappedFrameBuffer(buf).mmap() + buf_mmap_map[buf] = mfb + + self.buf_mmap_map = buf_mmap_map + + def run(self): + camnotif = QtCore.QSocketNotifier(self.cm.event_fd, QtCore.QSocketNotifier.Type.Read) + camnotif.activated.connect(lambda _: self.readcam()) + + keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Type.Read) + keynotif.activated.connect(lambda _: self.readkey()) + + print('Capturing...') + + self.app.exec() + + print('Exiting...') + + def readcam(self): + running = self.state.event_handler() + + if not running: + self.app.quit() + + def readkey(self): + sys.stdin.readline() + self.app.quit() + + def request_handler(self, ctx, req): + buffers = req.buffers + + for stream, fb in buffers.items(): + wnd = next(wnd for wnd in self.windows if wnd.stream == stream) + + mfb = self.buf_mmap_map[fb] + + wnd.handle_request(stream, mfb) + + self.state.request_processed(ctx, req) + + def cleanup(self): + for w in self.windows: + w.close() + + +class MainWindow(QtWidgets.QWidget): + def __init__(self, ctx, stream): + super().__init__() + + self.ctx = ctx + self.stream = stream + + self.label = QtWidgets.QLabel() + + windowLayout = QtWidgets.QHBoxLayout() + self.setLayout(windowLayout) + + windowLayout.addWidget(self.label) + + controlsLayout = QtWidgets.QVBoxLayout() + windowLayout.addLayout(controlsLayout) + + windowLayout.addStretch() + + group = QtWidgets.QGroupBox('Info') + groupLayout = QtWidgets.QVBoxLayout() + group.setLayout(groupLayout) + controlsLayout.addWidget(group) + + lab = QtWidgets.QLabel(ctx.id) + groupLayout.addWidget(lab) + + self.frameLabel = QtWidgets.QLabel() + groupLayout.addWidget(self.frameLabel) + + group = QtWidgets.QGroupBox('Properties') + groupLayout = QtWidgets.QVBoxLayout() + group.setLayout(groupLayout) + controlsLayout.addWidget(group) + + camera = ctx.camera + + for cid, cv in camera.properties.items(): + lab = QtWidgets.QLabel() + lab.setText('{} = {}'.format(cid, cv)) + groupLayout.addWidget(lab) + + group = QtWidgets.QGroupBox('Controls') + groupLayout = QtWidgets.QVBoxLayout() + group.setLayout(groupLayout) + controlsLayout.addWidget(group) + + for cid, cinfo in camera.controls.items(): + lab = QtWidgets.QLabel() + lab.setText('{} = {}/{}/{}' + .format(cid, cinfo.min, cinfo.max, cinfo.default)) + groupLayout.addWidget(lab) + + controlsLayout.addStretch() + + def buf_to_qpixmap(self, stream, mfb): + cfg = stream.configuration + + if cfg.pixel_format == libcam.formats.MJPEG: + pix = QtGui.QPixmap(cfg.size.width, cfg.size.height) + pix.loadFromData(mfb.planes[0]) + else: + rgb = mfb_to_rgb(mfb, cfg) + if rgb is None: + raise Exception('Format not supported: ' + cfg.pixel_format) + + pix = rgb_to_pix(rgb) + + return pix + + def handle_request(self, stream, mfb): + ctx = self.ctx + + pix = self.buf_to_qpixmap(stream, mfb) + self.label.setPixmap(pix) + + self.frameLabel.setText('Queued: {}\nDone: {}\nFps: {:.2f}' + .format(ctx.reqs_queued, ctx.reqs_completed, ctx.fps)) |