feat: add camera window
This commit is contained in:
parent
1485c9446c
commit
b6115bbb18
0
draft/camera.py
Normal file
0
draft/camera.py
Normal file
131
draft/pyqt_avif.py
Normal file
131
draft/pyqt_avif.py
Normal file
@ -0,0 +1,131 @@
|
||||
import io
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QImage, QPixmap
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication,
|
||||
QGraphicsPixmapItem,
|
||||
QGraphicsScene,
|
||||
QGraphicsView,
|
||||
QMainWindow,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
# For PyQt5 users, change the imports above to this:
|
||||
# from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QVBoxLayout, QWidget, QPushButton, QFileDialog
|
||||
# from PyQt5.QtGui import QPixmap
|
||||
# from PyQt5.QtCore import Qt
|
||||
|
||||
|
||||
class ZoomableView(QGraphicsView):
|
||||
"""
|
||||
A custom QGraphicsView that provides zoom functionality with the mouse wheel.
|
||||
"""
|
||||
|
||||
def __init__(self, scene):
|
||||
super().__init__(scene)
|
||||
# Set anchor points for zooming and resizing
|
||||
self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
|
||||
self.setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorViewCenter)
|
||||
|
||||
def wheelEvent(self, event):
|
||||
"""
|
||||
Handles mouse wheel events to zoom in or out.
|
||||
"""
|
||||
# Get the amount of scroll
|
||||
angle = event.angleDelta().y()
|
||||
|
||||
if angle > 0:
|
||||
# Zoom in
|
||||
factor = 1.25
|
||||
else:
|
||||
# Zoom out
|
||||
factor = 0.8
|
||||
|
||||
self.scale(factor, factor)
|
||||
|
||||
|
||||
class ImageViewer(QMainWindow):
|
||||
"""
|
||||
Main application window for viewing an image.
|
||||
Includes loading, zooming, and panning functionality.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.setWindowTitle("PyQt Image Viewer (Load, Zoom, Pan)")
|
||||
self.setGeometry(100, 100, 800, 700)
|
||||
|
||||
# Main widget and layout
|
||||
central_widget = QWidget()
|
||||
self.setCentralWidget(central_widget)
|
||||
layout = QVBoxLayout(central_widget)
|
||||
|
||||
# 1. Create the QGraphicsScene
|
||||
# The scene is the container for all 2D graphical items
|
||||
self.scene = QGraphicsScene()
|
||||
|
||||
# 2. Create the custom QGraphicsView
|
||||
# This is our custom view with zoom capabilities
|
||||
self.view = ZoomableView(self.scene)
|
||||
|
||||
# 3. Enable panning (drag the scene with the mouse)
|
||||
# The hand cursor will appear when you click and drag.
|
||||
self.view.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
|
||||
|
||||
# Add a button to load an image
|
||||
self.load_button = QPushButton("Load Image")
|
||||
self.load_button.clicked.connect(self.load_image)
|
||||
|
||||
# Add widgets to the layout
|
||||
layout.addWidget(self.load_button)
|
||||
layout.addWidget(self.view)
|
||||
|
||||
# A variable to hold the currently displayed image item
|
||||
self.pixmap_item = None
|
||||
|
||||
def load_image(self):
|
||||
file_path = Path(
|
||||
"/home/lambda/Downloads/f546ed6d35ee05338b8403d57dda10103ac3b1b8.jpg@672w_378h_1c_!web-home-common-cover.avif"
|
||||
)
|
||||
image_data = file_path.read_bytes()
|
||||
im = Image.open(io.BytesIO(image_data))
|
||||
pixmap = QPixmap.fromImage(
|
||||
QImage(im.tobytes(), im.size[0], im.size[1], QImage.Format.Format_RGB888)
|
||||
)
|
||||
if pixmap.isNull():
|
||||
print(f"Error: Failed to load image from {file_path}")
|
||||
return
|
||||
# If an image is already loaded, remove the old one first
|
||||
if self.pixmap_item:
|
||||
self.scene.removeItem(self.pixmap_item)
|
||||
|
||||
# 5. Create a QGraphicsPixmapItem to hold the image
|
||||
self.pixmap_item = QGraphicsPixmapItem(pixmap)
|
||||
|
||||
# 6. Add the item to the scene
|
||||
self.scene.addItem(self.pixmap_item)
|
||||
|
||||
# --- Optional: Improve the viewing experience ---
|
||||
# Reset any previous transformations (like zoom/pan)
|
||||
self.view.resetTransform()
|
||||
# Fit the entire image within the view, maintaining aspect ratio
|
||||
self.view.fitInView(self.pixmap_item, Qt.AspectRatioMode.KeepAspectRatio)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Create the application instance
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# Create and show the main window
|
||||
viewer = ImageViewer()
|
||||
viewer.show()
|
||||
|
||||
# Start the application's event loop
|
||||
sys.exit(app.exec())
|
||||
@ -1,16 +1,15 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
from tqdm import tqdm
|
||||
|
||||
from flandre.utils.archive import to_zip
|
||||
from flandre.utils.RfFrame import b2t
|
||||
from flandre.utils.RfMeta import RfFrameMeta
|
||||
from flandre.utils.archive import to_zip
|
||||
from flandre.utils.robot import rtsi_float2int
|
||||
|
||||
|
||||
|
||||
@ -44,6 +44,7 @@ class LaunchComponent(Enum):
|
||||
Mi = auto()
|
||||
Web = auto()
|
||||
VideoQt = auto()
|
||||
CameraQt = auto()
|
||||
|
||||
|
||||
def launch(arg: dict[LaunchComponent, dict]):
|
||||
|
||||
133
flandre/nodes/CameraQt.py
Normal file
133
flandre/nodes/CameraQt.py
Normal file
@ -0,0 +1,133 @@
|
||||
import io
|
||||
import logging
|
||||
import sys
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
import pillow_avif # type: ignore
|
||||
from PIL import Image
|
||||
from PyQt6 import QtCore
|
||||
from PyQt6.QtCore import QByteArray, Qt
|
||||
from PyQt6.QtGui import QImage, QKeyEvent, QPixmap, QResizeEvent, QWheelEvent
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication,
|
||||
QGraphicsPixmapItem,
|
||||
QGraphicsScene,
|
||||
QMainWindow,
|
||||
)
|
||||
|
||||
from flandre.nodes.Node import Node
|
||||
from flandre.pyqt.Image import Ui_MainWindow
|
||||
from flandre.pyqt.ZMQReceiver import ZMQReceiver
|
||||
from flandre.utils.archive import zip_to_bytes
|
||||
from flandre.utils.Msg import KillMsg, Msg, RfFrameMsg
|
||||
from flandre.utils.RfFrame import RfFrameFile
|
||||
|
||||
pillow_avif.__all__
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Adv(QMainWindow, Ui_MainWindow):
|
||||
ee2 = QtCore.pyqtSignal("QByteArray")
|
||||
|
||||
def __init__(self, p: Node, parent=None):
|
||||
super(Adv, self).__init__(parent)
|
||||
self.p = p
|
||||
self.setupUi(self)
|
||||
zmq_receiver = ZMQReceiver(self)
|
||||
zmq_receiver.zmq_event.connect(self.on_zmq_event)
|
||||
zmq_receiver.start()
|
||||
self.g = QGraphicsPixmapItem()
|
||||
self.s = QGraphicsScene()
|
||||
self.s.addItem(self.g)
|
||||
self.graphicsView.setScene(self.s)
|
||||
self.grey = False
|
||||
self.scale = False
|
||||
self.watermark = True
|
||||
self.zoom = 1.0
|
||||
self.need_fit = False
|
||||
self.frame1: RfFrameFile | None = None
|
||||
self.ee2.connect(self.draw1)
|
||||
threading.Thread(target=self.e1, daemon=True).start()
|
||||
|
||||
def keyPressEvent(self, a0: QKeyEvent):
|
||||
t = a0.text()
|
||||
match t:
|
||||
case "m":
|
||||
pass
|
||||
case _:
|
||||
pass
|
||||
|
||||
def resizeEvent(self, a0: QResizeEvent | None) -> None:
|
||||
self.graphicsView.fitInView(
|
||||
self.s.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio
|
||||
)
|
||||
|
||||
def wheelEvent(self, a0: QWheelEvent | None) -> None:
|
||||
if a0 is None:
|
||||
return
|
||||
if a0.angleDelta().y() > 0:
|
||||
self.zoom += 0.1
|
||||
if a0.angleDelta().y() < 0:
|
||||
self.zoom -= 0.1
|
||||
|
||||
def draw1(self, d: QByteArray):
|
||||
image_data = d.data()
|
||||
im = Image.open(io.BytesIO(image_data))
|
||||
pixmap = QPixmap.fromImage(
|
||||
QImage(im.tobytes(), im.size[0], im.size[1], QImage.Format.Format_RGB888)
|
||||
)
|
||||
self.g.setPixmap(pixmap)
|
||||
# self.s.setSceneRect(0.0, 0.0, im.size[0], im.size[1])
|
||||
self.graphicsView.fitInView(
|
||||
self.s.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio
|
||||
)
|
||||
|
||||
def e1(self):
|
||||
last_frame = self.frame1
|
||||
while True:
|
||||
if last_frame != self.frame1 and self.frame1 is not None:
|
||||
b = zip_to_bytes(
|
||||
self.frame1.seq.path,
|
||||
Path(self.frame1.filename).with_suffix(".avif").__str__(),
|
||||
)
|
||||
self.ee2.emit(b)
|
||||
last_frame = self.frame1
|
||||
|
||||
def on_zmq_event(self, raw_msg: QByteArray):
|
||||
try:
|
||||
msg: Msg = Msg.decode_msg(raw_msg.data())
|
||||
match msg:
|
||||
case KillMsg():
|
||||
if msg.name == "":
|
||||
self.close()
|
||||
case RfFrameMsg():
|
||||
frame = msg.rf_frame
|
||||
match frame:
|
||||
case RfFrameFile():
|
||||
if frame.seq.type == "zip":
|
||||
self.frame1 = frame
|
||||
pass
|
||||
case _:
|
||||
pass
|
||||
case _:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"{e}")
|
||||
|
||||
|
||||
class CameraQt(Node):
|
||||
topics = [RfFrameMsg]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def loop(self):
|
||||
app = QApplication(sys.argv)
|
||||
MainWindow = Adv(self)
|
||||
# MainWindow.move(int(px), int(py))
|
||||
# MainWindow.resize(int(sx), int(sy))
|
||||
MainWindow.show()
|
||||
app.exec()
|
||||
@ -353,7 +353,7 @@ class BMMsg(Msg):
|
||||
class RfFrameMsg(HeaderByteMsg):
|
||||
def __init__(self, sender: int, rf_frame: RfFrame):
|
||||
self.sender = sender
|
||||
self.rf_frame = rf_frame
|
||||
self.rf_frame: RfFrame = rf_frame
|
||||
if isinstance(rf_frame, RfFrameFile):
|
||||
super().__init__(
|
||||
dict(
|
||||
|
||||
@ -28,10 +28,10 @@ class RfFrameFile(RfFrame):
|
||||
return self.data
|
||||
match self.seq.type:
|
||||
case "zip":
|
||||
from flandre.utils.archive import zip_to_bytes
|
||||
from flandre.utils.archive import zip_to_rfmat
|
||||
|
||||
if self.filename is not None:
|
||||
return zip_to_bytes(self.seq.path, int(Path(self.filename).stem))
|
||||
return zip_to_rfmat(self.seq.path, int(Path(self.filename).stem))
|
||||
case "dir":
|
||||
return (self.seq.path / self.filename).read_bytes()
|
||||
raise NotImplementedError()
|
||||
|
||||
@ -4,8 +4,8 @@ import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from tqdm import tqdm
|
||||
import zstd
|
||||
from tqdm import tqdm
|
||||
|
||||
TEMP_FOLDER = Path("/mnt/16T/private_dataset/New Folder/temp")
|
||||
|
||||
@ -66,12 +66,12 @@ def to_zip(
|
||||
subprocess.run(["zip", "-0", "-j", "-r", zipdst, temp_dst])
|
||||
|
||||
|
||||
def zip_to_bytes(file: Path, name: int):
|
||||
return zstd.loads(zipfile.ZipFile(file).open(f"{name}.zst").read())
|
||||
def zip_to_bytes(file: Path, name: str):
|
||||
return zipfile.ZipFile(file).open(name).read()
|
||||
|
||||
|
||||
def zip_to_bytes2(file: Path, name: str):
|
||||
return zstd.loads(zipfile.ZipFile(file).open(name).read())
|
||||
def zip_to_rfmat(file: Path, frame_index: int):
|
||||
return zstd.loads(zip_to_bytes(file, f"{frame_index}.zst"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
from typing import Mapping
|
||||
|
||||
|
||||
arg_map: dict[str, tuple[int, str]] = dict(
|
||||
x=(100000, "{v:.2f}"),
|
||||
y=(100000, "{v:.2f}"),
|
||||
|
||||
@ -25,6 +25,7 @@ dependencies = [
|
||||
"matplotlib>=3.10.1",
|
||||
"fastapi[standard]>=0.115.12",
|
||||
"websockets>=15.0.1",
|
||||
"pillow-avif-plugin>=1.5.2",
|
||||
]
|
||||
|
||||
[tool.setuptools] # configuration specific to the `setuptools` build backend.
|
||||
|
||||
19
uv.lock
19
uv.lock
@ -490,6 +490,7 @@ dependencies = [
|
||||
{ name = "matplotlib" },
|
||||
{ name = "mido", extra = ["ports-rtmidi"] },
|
||||
{ name = "opencv-python" },
|
||||
{ name = "pillow-avif-plugin" },
|
||||
{ name = "platformdirs" },
|
||||
{ name = "pyjoystick" },
|
||||
{ name = "pyqt6" },
|
||||
@ -521,6 +522,7 @@ requires-dist = [
|
||||
{ name = "mido", extras = ["ports-rtmidi"], specifier = ">=1.3.3" },
|
||||
{ name = "nvidia-nvimgcodec-cu12", marker = "extra == 'to'", specifier = ">=0.5.0.13" },
|
||||
{ name = "opencv-python", specifier = ">=4.10.0.84" },
|
||||
{ name = "pillow-avif-plugin", specifier = ">=1.5.2" },
|
||||
{ name = "platformdirs", specifier = ">=4.3.6" },
|
||||
{ name = "pyjoystick", specifier = ">=1.2.4" },
|
||||
{ name = "pyqt6", specifier = ">=6.8.0" },
|
||||
@ -1331,6 +1333,23 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087, upload-time = "2025-04-12T17:48:21.991Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow-avif-plugin"
|
||||
version = "1.5.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/32/a3bfad0537ba6f2accc6a8a2e53e09b266418347f58898f811ca2fb70bd9/pillow_avif_plugin-1.5.2.tar.gz", hash = "sha256:811e0dc8be1e44393d2e3865ec330a8a8a1194b94eb8cfca6fa778e3f476d649", size = 20571, upload-time = "2025-04-24T14:11:49.163Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/df/75/0ee9a2a55142183fd881d912bae912df36cf8fe84ba3be92c4530558732f/pillow_avif_plugin-1.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ec28185f536857965be2156e13231274d58a154504db3da4dcda772e391cf5f", size = 3900276, upload-time = "2025-04-24T14:10:32.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/7e/f28e53b8c9e31e015c8923ff3dca4c577e38013d4dd5fb005182cd4631a8/pillow_avif_plugin-1.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e36b3cbf5e61b15d2fa213508cf348251e7830ebeff1d97e4e18c55cc0c3b784", size = 2805833, upload-time = "2025-04-24T14:10:33.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/f8/3bf477c4d32470819f10c1163c79f3e0a14c9c3f3ffd20d2d051a6a1dd9b/pillow_avif_plugin-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8098ceed3f76c38b61a35221f0db0205d81d06ff87f4ec11175c90bc66f8c1d", size = 2991435, upload-time = "2025-04-24T14:10:35.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/e3/c08f2346d04f0969fec9a6751af8c23dfffd02a55ca91715cb3ee86b53be/pillow_avif_plugin-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2075069ba6c00236cf7aed66c15863e9ea44b280c20e270aa6f56173b7a380c3", size = 6365698, upload-time = "2025-04-24T14:10:36.678Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/96/7676079b7305a6cc31f284c7036c33f5131886fdc074f204b3b3cdef1886/pillow_avif_plugin-1.5.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:db7d4fc23d8f80ef4f9fdac8e0071856c4c8b9acfc3c4d5cb125d5299d6130b2", size = 3003962, upload-time = "2025-04-24T14:10:37.924Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/34/058a6e5e0794e2fa6e012a350097b989fb0aa9ecc704e9da2cafddbb7a22/pillow_avif_plugin-1.5.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:8174501ed895508d5801d61fa9518252693125688dadac7fba79612f9bba623a", size = 4173892, upload-time = "2025-04-24T14:10:39.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/52/054bd0c363a5ff6e35d0e763c75cc64eb2408d19a7de34fc6448cdf57fbc/pillow_avif_plugin-1.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:25eeb443636a1c7f0bd0a6994436ad44de09e87648574a797cbd8620b8dcb52d", size = 3101308, upload-time = "2025-04-24T14:10:40.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/83/508f425b4e5e42e6c165b819b4953c482f05b38bc18958b1e11c3bfa7ebb/pillow_avif_plugin-1.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5004a099d52a9a23dfa5b19382a48c0f0930de6231deb013222867fea8948712", size = 4195525, upload-time = "2025-04-24T14:10:41.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/01/0f9cbc4fbb7345790379fb5321776150043d6b6e5674a196a4ba552b570c/pillow_avif_plugin-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:0bc8cec59b5d9c2020cdc6d218081b4d7b0dd60270a24f9174d6b91438a3aa5a", size = 9857569, upload-time = "2025-04-24T14:10:43.099Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.3.6"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user