temporary commit

This commit is contained in:
2025-09-18 20:18:50 +02:00
parent 2187536c7d
commit 3841b44a0a
7 changed files with 349 additions and 84 deletions

View File

@@ -1,97 +1,31 @@
# import gphoto2 as gp
import numpy as np
import cv2
from PySide6.QtCore import QObject, QThread, Signal from PySide6.QtCore import QObject, QThread, Signal
from PySide6.QtGui import QImage, QPixmap from ..core.base import BaseImageSource, BaseControlSource
# try:
# import gphoto2 as gp
# except:
from . import mock_gphoto as gp
class CameraWorker(QObject):
frameReady = Signal(QPixmap)
errorOccurred = Signal(str)
def __init__(self, fps: int = 15, parent=None):
super().__init__(parent)
self.fps = fps
self.running = False
self.camera = None
def start_camera(self):
"""Uruchom kamerę i zacznij pobierać klatki"""
try:
self.camera = gp.Camera() # type: ignore
self.camera.init()
self.running = True
self._capture_loop()
except gp.GPhoto2Error as e:
self.errorOccurred.emit(f"Błąd inicjalizacji kamery: {e}")
def stop_camera(self):
"""Zatrzymaj pobieranie"""
self.running = False
if self.camera:
try:
self.camera.exit()
except gp.GPhoto2Error:
pass
self.camera = None
def _capture_loop(self):
"""Pętla odczytu klatek w osobnym wątku"""
import time
delay = 1.0 / self.fps
while self.running:
try:
file = self.camera.capture_preview() # type: ignore
data = file.get_data_and_size()
frame = np.frombuffer(data, dtype=np.uint8)
frame = cv2.imdecode(frame, cv2.IMREAD_COLOR)
if frame is not None:
rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgb_image.shape
qimg = QImage(rgb_image.data, w, h, ch * w, QImage.Format.Format_RGB888)
pixmap = QPixmap.fromImage(qimg)
self.frameReady.emit(pixmap)
except gp.GPhoto2Error as e:
self.errorOccurred.emit(f"Błąd odczytu LiveView: {e}")
break
except Exception as e:
self.errorOccurred.emit(f"Nieoczekiwany błąd: {e}")
break
time.sleep(delay)
class CameraController(QObject): class CameraController(QObject):
frameReady = Signal(QPixmap) new_frame = Signal(object)
errorOccurred = Signal(str) errorOccurred = Signal(str)
def __init__(self, fps: int = 15, parent=None): def __init__(self, image_source: BaseImageSource, control_source: BaseControlSource, parent=None):
super().__init__(parent) super().__init__(parent)
self.image_source = image_source
self.control_source = control_source
self.camera_thread = QThread() self.camera_thread = QThread()
self.worker = CameraWorker(fps) self.moveToThread(self.camera_thread)
self.worker.moveToThread(self.camera_thread )
# sygnały z workera self.image_source.moveToThread(self.camera_thread)
self.worker.frameReady.connect(self.frameReady) self.control_source.moveToThread(self.camera_thread)
self.worker.errorOccurred.connect(self.errorOccurred)
# sygnały start/stop self.image_source.new_frame.connect(self.new_frame)
self.camera_thread.started.connect(self.worker.start_camera) self.image_source.errorOccurred.connect(self.errorOccurred)
self.control_source.errorOccurred.connect(self.errorOccurred)
def start(self): def start(self):
"""Start kamery w osobnym wątku"""
self.camera_thread.start() self.camera_thread.start()
self.image_source.start()
def stop(self): def stop(self):
"""Stop kamery i zakończenie wątku""" self.image_source.stop()
self.worker.stop_camera()
self.camera_thread.quit() self.camera_thread.quit()
self.camera_thread.wait() self.camera_thread.wait()

View File

@@ -6,7 +6,8 @@ from ui.widgets.color_list_widget import ColorListWidget
from ui.widgets.thumbnail_list_widget import ThumbnailListWidget from ui.widgets.thumbnail_list_widget import ThumbnailListWidget
from ui.widgets.split_view_widget import SplitView from ui.widgets.split_view_widget import SplitView
from .camera_controller import CameraController from .camera_controller import CameraController
from ..core.gphoto_adapter import GPhotoImageSource, GPhotoControlSource
import gphoto2 as gp
class MainController: class MainController:
def __init__(self, view): def __init__(self, view):
@@ -15,7 +16,11 @@ class MainController:
self.media_repo = MediaRepository(self.db) self.media_repo = MediaRepository(self.db)
self.media_repo.sync_media() self.media_repo.sync_media()
self.camera_controller = CameraController() camera = gp.Camera()
camera.init()
stream = GPhotoImageSource(camera=camera, fps=15)
controll = GPhotoControlSource(camera=camera)
self.camera_controller = CameraController(stream, controll)
self.view = view self.view = view
self.color_list: ColorListWidget = view.color_list_widget self.color_list: ColorListWidget = view.color_list_widget
@@ -30,7 +35,7 @@ class MainController:
self.thumbnail_list.selectedThumbnail.connect(self.on_thumbnail_selected) self.thumbnail_list.selectedThumbnail.connect(self.on_thumbnail_selected)
self.camera_controller.errorOccurred.connect(self.split_view.widget_start.set_info_text) self.camera_controller.errorOccurred.connect(self.split_view.widget_start.set_info_text)
self.camera_controller.frameReady.connect(self.split_view.set_live_image) self.camera_controller.new_frame.connect(self.split_view.set_live_image)
self.split_view.widget_start.camera_start_btn.clicked.connect(self.camera_controller.start) self.split_view.widget_start.camera_start_btn.clicked.connect(self.camera_controller.start)
def start_camera(self): def start_camera(self):

26
core/base.py Normal file
View File

@@ -0,0 +1,26 @@
from PySide6.QtCore import QObject, Signal
from PySide6.QtGui import QPixmap
class BaseImageSource(QObject):
new_frame = Signal(QPixmap)
errorOccurred = Signal(str)
def start(self):
raise NotImplementedError
def stop(self):
raise NotImplementedError
class BaseControlSource(QObject):
errorOccurred = Signal(str)
parameterChanged = Signal(str, object)
def set_parameter(self, name: str, value):
raise NotImplementedError
def get_parameter(self, name: str):
raise NotImplementedError
def list_parameters(self) -> dict:
raise NotImplementedError

69
core/camera_manager.py Normal file
View File

@@ -0,0 +1,69 @@
import cv2
import gphoto2 as gp
from controllers.camera_controller import CameraController
from .gphoto_adapter import GPhotoImageSource, GPhotoControlSource
from .opencv_adapter import OpenCVImageSource, OpenCVControlSource
class CameraManager:
def __init__(self):
self.devices = [] # lista wykrytych kamer
def detect_devices(self):
self.devices.clear()
# --- Wykrywanie webcamów / grabberów HDMI
for index in range(5): # sprawdź kilka indeksów
cap = cv2.VideoCapture(index)
if cap.isOpened():
self.devices.append({
"id": f"opencv:{index}",
"name": f"Webcam / HDMI Grabber #{index}",
"type": "opencv",
"index": index
})
cap.release()
# --- Wykrywanie kamer gphoto2
cameras = gp.Camera.autodetect() # type: ignore
for i, (name, addr) in enumerate(cameras):
self.devices.append({
"id": f"gphoto:{i}",
"name": f"{name} ({addr})",
"type": "gphoto",
"addr": addr
})
return self.devices
def create_controller(self, device_id, hybrid_with=None):
"""
Tworzy CameraController na podstawie id urządzenia.
Można podać hybrid_with="opencv" albo "gphoto" żeby zbudować hybrydę.
"""
device = next((d for d in self.devices if d["id"] == device_id), None)
if not device:
raise ValueError(f"Nie znaleziono urządzenia {device_id}")
# Webcam / grabber
if device["type"] == "opencv":
cap = cv2.VideoCapture(device["index"])
img = OpenCVImageSource(device["index"])
ctrl = OpenCVControlSource(cap)
return CameraController(img, ctrl)
# GPhoto camera
elif device["type"] == "gphoto":
cam = gp.Camera() # type: ignore
cam.init()
img = GPhotoImageSource(cam)
ctrl = GPhotoControlSource(cam)
return CameraController(img, ctrl)
# Hybrydowy tryb
elif device["type"] == "hybrid":
raise NotImplementedError("Tu możesz połączyć OpenCV + GPhoto w hybrydę")
else:
raise ValueError(f"Nieobsługiwany typ urządzenia: {device['type']}")

84
core/gphoto_adapter.py Normal file
View File

@@ -0,0 +1,84 @@
import numpy as np
import cv2
from PySide6.QtCore import QObject, QThread, Signal, QTimer
from PySide6.QtGui import QImage, QPixmap
from .base import BaseImageSource, BaseControlSource
import gphoto2 as gp
# try:
# import gphoto2 as gp
# except:
# from . import mock_gphoto as gp
class GPhotoImageSource(BaseImageSource):
def __init__(self, camera: gp.Camera, fps=10, parent=None): # type: ignore
super().__init__(parent)
self.camera = camera
self.fps = fps
self.timer = None
def start(self):
self.timer = QTimer()
self.timer.timeout.connect(self._grab_frame)
self.timer.start(int(1000 / self.fps))
def stop(self):
if self.timer:
self.timer.stop()
def _grab_frame(self):
try:
file = self.camera.capture_preview()
data = file.get_data_and_size()
frame = np.frombuffer(data, dtype=np.uint8)
frame = cv2.imdecode(frame, cv2.IMREAD_COLOR)
if frame is None:
return
rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgb_image.shape
qimg = QImage(rgb_image.data, w, h, ch * w, QImage.Format.Format_RGB888)
pixmap = QPixmap.fromImage(qimg)
self.new_frame.emit(pixmap)
except gp.GPhoto2Error as e:
self.errorOccurred.emit(f"GPhoto2 error: {e}")
class GPhotoControlSource(BaseControlSource):
def __init__(self, camera: gp.Camera, parent=None): # type: ignore
super().__init__(parent)
self.camera = camera
def set_parameter(self, name, value):
try:
config = self.camera.get_config()
child = config.get_child_by_name(name)
child.set_value(value)
self.camera.set_config(config)
self.parameterChanged.emit(name, value)
except gp.GPhoto2Error as e:
self.errorOccurred.emit(str(e))
def get_parameter(self, name):
try:
config = self.camera.get_config()
child = config.get_child_by_name(name)
return child.get_value()
except gp.GPhoto2Error as e:
self.errorOccurred.emit(str(e))
return None
def list_parameters(self):
params = {}
try:
config = self.camera.get_config()
for child in config.get_children():
params[child.get_name()] = child.get_value()
except gp.GPhoto2Error as e:
self.errorOccurred.emit(str(e))
return params

64
core/mock_gphoto.py Normal file
View File

@@ -0,0 +1,64 @@
import cv2
import numpy as np
class GPhoto2Error(Exception):
pass
class CameraFileMock:
"""Mock obiektu zwracanego przez gphoto2.Camera.capture_preview()"""
def __init__(self, frame: np.ndarray):
# Kodowanie do JPEG, żeby symulować prawdziwe dane z kamery
success, buf = cv2.imencode(".jpg", frame)
if not success:
raise GPhoto2Error("Nie udało się zakodować ramki testowej.")
self._data = buf.tobytes()
def get_data_and_size(self):
return self._data
return self._data, len(self._data)
class Camera:
def __init__(self):
self._frame_counter = 0
self._running = False
def init(self):
self._running = True
print("[my_gphoto] Kamera MOCK zainicjalizowana")
def exit(self):
self._running = False
print("[my_gphoto] Kamera MOCK wyłączona")
def capture_preview(self):
if not self._running:
raise GPhoto2Error("Kamera MOCK nie jest uruchomiona")
# przykład 1: wczytaj stały obrazek z pliku
# frame = cv2.imread("test_frame.jpg")
# if frame is None:
# raise GPhoto2Error("Nie znaleziono test_frame.jpg")
# przykład 2: wygeneruj kolorową planszę
h, w = 480, 640
color = (self._frame_counter % 255, 100, 200)
frame = np.full((h, w, 3), color, dtype=np.uint8)
# dodanie napisu
text = "OBRAZ TESTOWY"
font = cv2.FONT_HERSHEY_SIMPLEX
scale = 1.5
thickness = 3
color_text = (255, 255, 255)
(text_w, text_h), _ = cv2.getTextSize(text, font, scale, thickness)
x = (w - text_w) // 2
y = (h + text_h) // 2
cv2.putText(frame, text, (x, y), font, scale, color_text, thickness, cv2.LINE_AA)
self._frame_counter += 1
return CameraFileMock(frame)

83
core/opencv_adapter.py Normal file
View File

@@ -0,0 +1,83 @@
from PySide6.QtCore import QObject, Signal, QTimer
from PySide6.QtGui import QImage, QPixmap
import cv2
import numpy as np
from .base import BaseImageSource, BaseControlSource
class OpenCVImageSource(BaseImageSource):
def __init__(self, device_index=0, fps=30, parent=None):
super().__init__(parent)
self.device_index = device_index
self.fps = fps
self.cap = None
self.timer = None
def start(self):
self.cap = cv2.VideoCapture(self.device_index)
if not self.cap.isOpened():
self.errorOccurred.emit(f"Nie mogę otworzyć kamery {self.device_index}")
return
self.timer = QTimer()
self.timer.timeout.connect(self._grab_frame)
self.timer.start(int(1000 / self.fps))
def stop(self):
if self.timer:
self.timer.stop()
if self.cap:
self.cap.release()
def _grab_frame(self):
if self.cap is None:
self.errorOccurred.emit(f"Kamera niezaincjalizowana!")
return
ret, frame = self.cap.read()
if not ret:
self.errorOccurred.emit("Brak obrazu z kamery OpenCV")
return
rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgb_image.shape
qimg = QImage(rgb_image.data, w, h, ch * w, QImage.Format.Format_RGB888)
pixmap = QPixmap.fromImage(qimg)
self.new_frame.emit(pixmap)
class OpenCVControlSource(BaseControlSource):
def __init__(self, cap: cv2.VideoCapture, parent=None):
super().__init__(parent)
self.cap = cap
def set_parameter(self, name, value):
prop_id = getattr(cv2, name, None)
if prop_id is None:
self.errorOccurred.emit(f"Nieznany parametr {name}")
return
self.cap.set(prop_id, value)
self.parameterChanged.emit(name, value)
def get_parameter(self, name):
prop_id = getattr(cv2, name, None)
if prop_id is None:
self.errorOccurred.emit(f"Nieznany parametr {name}")
return None
return self.cap.get(prop_id)
def list_parameters(self):
return {
"CAP_PROP_BRIGHTNESS": self.cap.get(cv2.CAP_PROP_BRIGHTNESS),
"CAP_PROP_CONTRAST": self.cap.get(cv2.CAP_PROP_CONTRAST),
"CAP_PROP_SATURATION": self.cap.get(cv2.CAP_PROP_SATURATION),
"CAP_PROP_GAIN": self.cap.get(cv2.CAP_PROP_GAIN),
"CAP_PROP_EXPOSURE": self.cap.get(cv2.CAP_PROP_EXPOSURE),
}