13 Commits

Author SHA1 Message Date
c815762f72 refactor: enhance mock camera classes and update camera detection logic 2025-10-09 18:47:17 +02:00
ca25b06f99 refactor: implement CameraManager class with methods for detecting GPhoto and OpenCV cameras 2025-10-01 18:26:41 +02:00
dea17b8b26 refactor: update detect method in camera classes to return dictionaries instead of lists 2025-10-01 18:26:02 +02:00
324ab2e016 refactor: update camera classes to improve initialization and connection handling 2025-09-30 21:50:30 +02:00
196eff7fd8 refacot: change name from CameraManager to CameraController. Add set_camera nad cleanup code. 2025-09-30 18:58:56 +02:00
e2c8352c44 fix: correct config attribute from 'config' to 'widget' in set_config method 2025-09-30 18:56:47 +02:00
dcf4ef0f0a refactor: implement set_dark_theme function for application theming 2025-09-27 12:26:57 +02:00
1ff5091250 refactor: update set_config methods to specify return type as None
feat: implement CvCamera class for OpenCV camera handling
2025-09-27 12:26:43 +02:00
373e01310e refactor: update GPhotoCamera configuration methods for consistency 2025-09-21 22:01:46 +02:00
abc07fd08d refactor: replace CameraWidget with dictionary-based config handling in GPhotoCamera 2025-09-21 21:43:44 +02:00
35576986c9 refactor gphoto_camera 2025-09-21 20:51:37 +02:00
19e2c7977c feat: read gphoto config 2025-09-21 18:46:38 +02:00
508930ae39 feat: implement camera management with GPhotoCamera and CameraManager classes 2025-09-21 08:38:26 +02:00
19 changed files with 653 additions and 509 deletions

View File

@@ -5,9 +5,14 @@ from core.media import MediaRepository
from ui.widgets.color_list_widget import ColorListWidget
from ui.widgets.thumbnail_list_widget import ThumbnailListWidget
from ui.widgets.split_view_widget import SplitView
from .camera_controller import CameraController
# from .camera_controller import CameraController
from core.camera.camera_controller import CameraController
from core.camera.camera_manager import CameraManager
from core.camera.gphoto_camera import GPhotoCamera
from core.camera.camera_controller import CameraController
class MainController:
def __init__(self, view):
self.db = DatabaseManager()
@@ -15,7 +20,13 @@ class MainController:
self.media_repo = MediaRepository(self.db)
self.media_repo.sync_media()
self.camera_controller = CameraController()
# camera = GPhotoCamera()
# self.manager = CameraController(camera)
manager = CameraManager()
manager.detect_gphoto()
manager.detect_opencv()
# self.camera_controller = CameraController()
self.view = view
self.color_list: ColorListWidget = view.color_list_widget
@@ -25,13 +36,20 @@ class MainController:
self.photo_button: QPushButton = view.photo_button
self.photo_button.clicked.connect(self.take_photo)
self.record_button: QPushButton = view.record_button
# self.record_button.clicked.connect(self.fun_test)
self.color_list.colorSelected.connect(self.on_color_selected)
self.color_list.editColor.connect(self.on_edit_color)
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.frameReady.connect(self.split_view.set_live_image)
self.split_view.widget_start.camera_start_btn.clicked.connect(self.camera_controller.start)
# self.camera_controller.errorOccurred.connect(self.split_view.widget_start.set_info_text)
# self.manager.error_occurred.connect(self.split_view.widget_start.set_info_text)
# self.camera_controller.frameReady.connect(self.split_view.set_live_image)
# self.manager.frame_ready.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.start_liveview)
def start_camera(self):
pass
@@ -71,3 +89,12 @@ class MainController:
def take_photo(self):
print("Robienie zdjęcia...")
self.split_view.toglle_live_view()
def start_liveview(self):
pass
# self.manager.start_camera()
# self.manager.start_stream()
def shutdown(self):
pass
# self.manager.stop()

View File

@@ -1,6 +1,16 @@
import cv2
import numpy as np
GP_WIDGET_WINDOW = 0
GP_WIDGET_SECTION = 0
GP_WIDGET_TEXT = 0
GP_WIDGET_RANGE = 0
GP_WIDGET_TOGGLE = 0
GP_WIDGET_RADIO = 0
GP_WIDGET_MENU = 0
GP_WIDGET_BUTTON = 0
GP_WIDGET_DATE = 0
class GPhoto2Error(Exception):
pass
@@ -19,6 +29,65 @@ class CameraFileMock:
return self._data
return self._data, len(self._data)
class CameraListMock:
def count(self):
return 1
def get_name(self, idx):
return f"mock_name {idx}"
def get_value(self, idx):
return f"mock_value {idx}"
class MockPortInfo:
def __init__(self, address):
self.address = address
class PortInfoList:
def __init__(self):
self._ports = []
def load(self):
# Dodaj przykładowe porty
self._ports = [MockPortInfo("usb:001,002"), MockPortInfo("usb:001,003")]
def lookup_path(self, port_address):
for idx, port in enumerate(self._ports):
if port.address == port_address:
return idx
raise ValueError("Port not found")
def __getitem__(self, idx):
return self._ports[idx]
class ConfigMock:
def get_id(self):
return 0
def get_name(self):
return "name"
def get_label(self):
return "label"
def get_type(self):
return 0
def get_value(self):
return "value"
def get_choices(self):
return []
def count_children(self):
return 0
def get_child(self):
return ConfigMock()
class CameraAbilitiesList:
def __init__(self) -> None:
self.abilities = []
def load(self):
return
def lookup_model(self, name):
return 1
def get_abilities(self, abilities_index):
return 0
class Camera:
def __init__(self):
@@ -62,3 +131,18 @@ class Camera:
self._frame_counter += 1
return CameraFileMock(frame)
def set_port_info(self, obj):
return False
def get_config(self):
return ConfigMock()
def set_single_config(self, name, widget):
return True
def gp_camera_autodetect():
return CameraListMock()
def check_result(obj):
return obj

View File

@@ -0,0 +1,42 @@
from abc import ABC, abstractmethod
class BaseCamera(ABC):
def __init__(self) -> None:
self.error_msg = None
@staticmethod
@abstractmethod
def detect() -> dict:
raise NotImplementedError
@abstractmethod
def connect(self, index: int | None = None) -> bool:
raise NotImplementedError
@abstractmethod
def disconnect(self) -> None:
raise NotImplementedError
@abstractmethod
def get_frame(self):
raise NotImplementedError
@abstractmethod
def get_config_by_id(self, id: int) -> dict:
raise NotImplementedError
@abstractmethod
def get_config_by_name(self, name: str) -> dict:
raise NotImplementedError
@abstractmethod
def set_config_by_id(self, id: int, value) -> None:
raise NotImplementedError
@abstractmethod
def set_config_by_name(self, name: str, value) -> None:
raise NotImplementedError
def get_error_msg(self):
return str(self.error_msg)

View File

@@ -0,0 +1,115 @@
from PySide6.QtCore import QObject, QThread, QTimer, Signal, Slot, QMutex, QMutexLocker
from PySide6.QtGui import QImage, QPixmap
import cv2
from .base_camera import BaseCamera
class CameraController(QThread):
frame_ready = Signal(QPixmap)
photo_ready = Signal(QPixmap)
error_occurred = Signal(str)
_enable_timer = Signal(bool)
def __init__(self, parent: QObject | None = None) -> None:
super().__init__(parent)
self.camera = None
self.timer = None
self.fps = 15
self.is_streaming = False
self.is_connected = False
self._camera_mutex = QMutex()
self.start()
def run(self) -> None:
self.timer = QTimer()
self.timer.timeout.connect(self._update_frame)
self._enable_timer.connect(self._set_timer)
self.exec()
def stop(self):
self.stop_camera()
self.quit()
self.wait()
def set_camera(self, camera: BaseCamera, fps: int = 15) -> None:
with QMutexLocker(self._camera_mutex):
self.stop_stream()
self.stop_camera()
self.camera = camera
self.fps = fps
def start_camera(self) -> None:
if self.camera is None or self.is_connected:
return
if self.camera.connect():
self.is_connected = True
else:
self.is_connected = False
self.error_occurred.emit(self.camera.get_error_msg())
def stop_camera(self) -> None:
if self.is_streaming:
self.stop_stream()
if self.camera is not None:
self.camera.disconnect()
self.is_connected = False
def start_stream(self):
if not self.is_connected:
return
if self.is_streaming:
return
if self.timer:
self.is_streaming = True
# self.timer.start()
self._enable_timer.emit(True)
def stop_stream(self) -> None:
if self.is_streaming:
self.is_streaming = False
if self.timer:
# self.timer.stop()
self._enable_timer.emit(False)
def _update_frame(self) -> None:
with QMutexLocker(self._camera_mutex):
if self.camera is None or not self.is_connected:
return
if not self.is_streaming:
return
ret, frame = self.camera.get_frame()
if not ret:
self.error_occurred.emit(self.camera.get_error_msg())
return
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.frame_ready.emit(pixmap)
def _set_timer(self, enable: bool):
if not self.timer:
return
if enable:
self.timer.setInterval(int(1000 / self.fps))
self.timer.start()
else:
self.timer.stop()

View File

@@ -0,0 +1,20 @@
from .gphoto_camera import GPhotoCamera
from .opencv_camera import OpenCvCamera
from .camera_controller import CameraController
class CameraManager:
def __init__(self) -> None:
pass
def detect_gphoto(self):
camera_list = GPhotoCamera.detect()
print(camera_list)
return camera_list
def detect_opencv(self):
camera_list = OpenCvCamera.detect()
print(camera_list)
return camera_list

View File

@@ -0,0 +1,173 @@
from typing import Optional, List
from dataclasses import dataclass, field
import cv2
import numpy as np
from .base_camera import BaseCamera
try:
import gphoto2 as gp # type: ignore
except:
import controllers.mock_gphoto as gp
camera_widget_types = {
gp.GP_WIDGET_WINDOW: "GP_WIDGET_WINDOW", # type: ignore
gp.GP_WIDGET_SECTION: "GP_WIDGET_SECTION", # type: ignore
gp.GP_WIDGET_TEXT: "GP_WIDGET_TEXT", # type: ignore
gp.GP_WIDGET_RANGE: "GP_WIDGET_RANGE", # type: ignore
gp.GP_WIDGET_TOGGLE: "GP_WIDGET_TOGGLE", # type: ignore
gp.GP_WIDGET_RADIO: "GP_WIDGET_RADIO", # type: ignore
gp.GP_WIDGET_MENU: "GP_WIDGET_MENU", # type: ignore
gp.GP_WIDGET_BUTTON: "GP_WIDGET_BUTTON", # type: ignore
gp.GP_WIDGET_DATE: "GP_WIDGET_DATE", # type: ignore
}
operations = [
("GP_OPERATION_NONE", gp.GP_OPERATION_NONE), # type: ignore
("GP_OPERATION_CAPTURE_IMAGE", gp.GP_OPERATION_CAPTURE_IMAGE), # type: ignore
("GP_OPERATION_CAPTURE_VIDEO", gp.GP_OPERATION_CAPTURE_VIDEO), # type: ignore
("GP_OPERATION_CAPTURE_AUDIO", gp.GP_OPERATION_CAPTURE_AUDIO), # type: ignore
("GP_OPERATION_CAPTURE_PREVIEW", gp.GP_OPERATION_CAPTURE_PREVIEW), # type: ignore
("GP_OPERATION_CONFIG", gp.GP_OPERATION_CONFIG), # type: ignore
("GP_OPERATION_TRIGGER_CAPTURE", gp.GP_OPERATION_TRIGGER_CAPTURE), # type: ignore
]
class GPhotoCamera(BaseCamera):
def __init__(self) -> None:
super().__init__()
self.camera = None
self.configs: List[dict] = []
self.camera_index = 0
@staticmethod
def detect() -> dict:
cameras = gp.check_result(gp.gp_camera_autodetect()) # type: ignore
# cameras = gp.Camera().autodetect()
if not cameras or cameras.count() == 0: # type: ignore
return {}
abilities_list = gp.CameraAbilitiesList() # type: ignore
abilities_list.load()
camera_list = {}
for i in range(cameras.count()): # type: ignore
name = cameras.get_name(i) # type: ignore
port = cameras.get_value(i) # type: ignore
abilities_index = abilities_list.lookup_model(name)
abilities = abilities_list.get_abilities(abilities_index)
abilities_name = []
for name, bit in operations:
if abilities.operations & bit: # type: ignore
abilities_name.append(name)
camera_list[i] = {"name": name, "port": port, "abilities": abilities_name}
return camera_list
def connect(self, index: int | None = None) -> bool:
self.error_msg = None
self.camera = gp.Camera() # type: ignore
try:
if index:
self.camera_index = index
camera_list = GPhotoCamera.detect()
port_info_list = gp.PortInfoList()
port_info_list.load()
port_address = camera_list[index]["port"]
port_index = port_info_list.lookup_path(port_address)
self.camera.set_port_info(port_info_list[port_index])
self.camera.init()
config = self.camera.get_config()
self.read_config(config)
return True
except Exception as e:
self.error_msg = f"[GPHOTO2] {e}"
self.camera = None
return False
def disconnect(self) -> None:
if self.camera:
self.camera.exit()
self.camera = None
self.configs.clear()
def get_frame(self):
self.error_msg = None
if self.camera is None:
self.error_msg = "[GPHOTO2] Camera is not initialized."
return (False, None)
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)
return (True, frame)
except Exception as e:
self.error_msg = f"[GPHOTO2] {e}"
return (False, None)
def get_config_by_id(self, id: int):
return next(w for w in self.configs if w['id'] == id)
def get_config_by_name(self, name: str):
return next(w for w in self.configs if w['name'] == name)
def set_config(self, config, value):
if value not in config['choices']:
return
config['widget'].set_value(value) # type: ignore
if self._save_config(config):
config['value'] = value
def set_config_by_id(self, id: int, value: str):
config = self.get_config_by_id(id)
self.set_config(config, value)
def set_config_by_name(self, name: str, value: str):
config = self.get_config_by_name(name)
self.set_config(config, value)
def _save_config(self, config):
if not self.camera:
return False
self.camera.set_single_config(config['name'], config['widget'])
return True
def parse_config(self, config):
new_config = {
"id": config.get_id(),
"name": config.get_name(),
"label": config.get_label(),
"type": camera_widget_types[config.get_type()],
"widget": config
}
try:
new_config["value"] = config.get_value()
except gp.GPhoto2Error:
pass
try:
new_config["choices"] = list(config.get_choices())
except gp.GPhoto2Error:
pass
return new_config
def read_config(self, config):
self.configs.append(self.parse_config(config))
for i in range(config.count_children()):
child = config.get_child(i)
self.read_config(child)

View File

@@ -0,0 +1,122 @@
import cv2
from cv2_enumerate_cameras import enumerate_cameras
from typing import List
from .base_camera import BaseCamera
class OpenCvCamera(BaseCamera):
"""Kamera oparta na cv2.VideoCapture"""
config_map = {
0: {"name": "frame_width", "cv_prop": cv2.CAP_PROP_FRAME_WIDTH, "default": 640},
1: {"name": "frame_height", "cv_prop": cv2.CAP_PROP_FRAME_HEIGHT, "default": 480},
2: {"name": "fps", "cv_prop": cv2.CAP_PROP_FPS, "default": 30},
3: {"name": "brightness", "cv_prop": cv2.CAP_PROP_BRIGHTNESS, "default": 0.5},
4: {"name": "contrast", "cv_prop": cv2.CAP_PROP_CONTRAST, "default": 0.5},
5: {"name": "saturation", "cv_prop": cv2.CAP_PROP_SATURATION, "default": 0.5},
}
def __init__(self) -> None:
super().__init__()
self.cap = None
self.configs: List[dict] = []
self.camera_list = []
self.camera_index = 0
@staticmethod
def detect():
camera_list = enumerate_cameras(cv2.CAP_ANY)
result = {}
for camera in camera_list:
cap = cv2.VideoCapture(camera.index, camera.backend)
# ret, frame = cap.read()
cap.release()
# if ret and frame is not None and frame.size > 0:
result[camera.index] = {
"name": camera.name,
"port": camera.path,
"backend": camera.backend,
}
return result
def connect(self, index: int | None = None) -> bool:
self.error_msg = None
try:
if index:
self.camera_index = index
self.cap = cv2.VideoCapture(self.camera_index)
if not self.cap.isOpened():
self.error_msg = f"[CV2] Could not open camera {self.camera_index}"
return False
self.configs.clear()
for id, conf in self.config_map.items():
value = self.cap.get(conf["cv_prop"])
self.configs.append(
{
"id": id,
"name": conf["name"],
"label": conf["name"].capitalize(),
"value": value,
"choices": None, # brak predefiniowanych wyborów
"cv_prop": conf["cv_prop"],
}
)
return True
except Exception as e:
self.error_msg = f"[CV2] {e}"
self.cap = None
return False
def disconnect(self) -> None:
if self.cap:
self.cap.release()
self.cap = None
self.configs.clear()
def get_frame(self):
self.error_msg = None
if self.cap is None or not self.cap.isOpened():
self.error_msg = "[CV2] Camera is not initialized."
return (False, None)
try:
ret, frame = self.cap.read()
if not ret:
self.error_msg = "[CV2] Failed to read frame."
return (False, None)
return (True, frame)
except Exception as e:
self.error_msg = f"[CV2] {e}"
return (False, None)
def get_config_by_id(self, id: int):
return next(w for w in self.configs if w["id"] == id)
def get_config_by_name(self, name: str):
return next(w for w in self.configs if w["name"] == name)
def set_config(self, config, value: float):
if not self.cap:
return
try:
self.cap.set(config["cv_prop"], value)
config["value"] = self.cap.get(
config["cv_prop"]) # sprawdz co ustawiło
except Exception as e:
self.error_msg = f"[CV2] {e}"
def set_config_by_id(self, id: int, value: float):
config = self.get_config_by_id(id)
self.set_config(config, value)
def set_config_by_name(self, name: str, value: float):
config = self.get_config_by_name(name)
self.set_config(config, value)

View File

@@ -14,6 +14,8 @@ def main():
window = MainWindow()
controller = MainController(window)
controller.load_colors()
app.aboutToQuit.connect(controller.shutdown)
window.show()
sys.exit(app.exec())

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="-4.5 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>arrow_left [#335]</title>
<desc>Created with Sketch.</desc>
<defs>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Dribbble-Light-Preview" transform="translate(-345.000000, -6679.000000)" fill="#000000">
<g id="icons" transform="translate(56.000000, 160.000000)">
<path d="M299.633777,6519.29231 L299.633777,6519.29231 C299.228878,6518.90256 298.573377,6518.90256 298.169513,6519.29231 L289.606572,6527.55587 C288.797809,6528.33636 288.797809,6529.60253 289.606572,6530.38301 L298.231646,6538.70754 C298.632403,6539.09329 299.27962,6539.09828 299.685554,6538.71753 L299.685554,6538.71753 C300.100809,6538.32879 300.104951,6537.68821 299.696945,6537.29347 L291.802968,6529.67648 C291.398069,6529.28574 291.398069,6528.65315 291.802968,6528.26241 L299.633777,6520.70538 C300.038676,6520.31563 300.038676,6519.68305 299.633777,6519.29231" id="arrow_left-[#335]">
</path>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="-4.5 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>arrow_right [#336]</title>
<desc>Created with Sketch.</desc>
<defs>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Dribbble-Light-Preview" transform="translate(-305.000000, -6679.000000)" fill="#000000">
<g id="icons" transform="translate(56.000000, 160.000000)">
<path d="M249.365851,6538.70769 L249.365851,6538.70769 C249.770764,6539.09744 250.426289,6539.09744 250.830166,6538.70769 L259.393407,6530.44413 C260.202198,6529.66364 260.202198,6528.39747 259.393407,6527.61699 L250.768031,6519.29246 C250.367261,6518.90671 249.720021,6518.90172 249.314072,6519.28247 L249.314072,6519.28247 C248.899839,6519.67121 248.894661,6520.31179 249.302681,6520.70653 L257.196934,6528.32352 C257.601847,6528.71426 257.601847,6529.34685 257.196934,6529.73759 L249.365851,6537.29462 C248.960938,6537.68437 248.960938,6538.31795 249.365851,6538.70769" id="arrow_right-[#336]">
</path>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.079 3.46209C15.3762 3.17355 15.851 3.18054 16.1396 3.47771L19.538 6.9777C19.8205 7.26871 19.8205 7.73162 19.538 8.02263L16.1396 11.5226C15.851 11.8198 15.3762 11.8268 15.079 11.5382C14.7819 11.2497 14.7749 10.7749 15.0634 10.4777L17.2263 8.25015L4.99989 8.25015C4.58567 8.25015 4.24989 7.91437 4.24989 7.50015C4.24989 7.08594 4.58567 6.75015 4.99989 6.75015L17.2263 6.75015L15.0634 4.52264C14.7749 4.22546 14.7819 3.75064 15.079 3.46209ZM8.92071 12.4618C9.21788 12.7504 9.22488 13.2252 8.93633 13.5224L6.77327 15.7501L18.9999 15.7501C19.4141 15.7501 19.7499 16.0859 19.7499 16.5001C19.7499 16.9143 19.4141 17.2501 18.9999 17.2501L6.77366 17.2501L8.93633 19.4774C9.22488 19.7746 9.21788 20.2494 8.92071 20.538C8.62353 20.8265 8.14871 20.8195 7.86016 20.5224L4.46177 17.0224C4.17922 16.7314 4.17922 16.2685 4.46177 15.9774L7.86016 12.4775C8.14871 12.1803 8.62353 12.1733 8.92071 12.4618Z" fill="#000000"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.49976 4.25001C7.91398 4.25001 8.24976 4.5858 8.24976 5.00001L8.24976 17.2266L10.4775 15.0636C10.7747 14.775 11.2495 14.782 11.538 15.0792C11.8266 15.3763 11.8196 15.8512 11.5224 16.1397L8.02243 19.5381C7.73142 19.8207 7.26851 19.8207 6.9775 19.5381L3.47751 16.1397C3.18034 15.8512 3.17335 15.3763 3.4619 15.0792C3.75044 14.782 4.22527 14.775 4.52244 15.0636L6.74976 17.2262L6.74976 5.00001C6.74976 4.5858 7.08555 4.25001 7.49976 4.25001Z" fill="#000000"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.9773 4.4619C16.2683 4.17934 16.7312 4.17934 17.0222 4.4619L20.5222 7.86029C20.8193 8.14884 20.8263 8.62366 20.5378 8.92083C20.2492 9.21801 19.7744 9.225 19.4772 8.93645L17.2497 6.7736L17.2497 19C17.2497 19.4142 16.9139 19.75 16.4997 19.75C16.0855 19.75 15.7497 19.4142 15.7497 19L15.7497 6.77358L13.5222 8.93645C13.225 9.225 12.7502 9.21801 12.4616 8.92083C12.1731 8.62366 12.1801 8.14884 12.4773 7.86029L15.9773 4.4619Z" fill="#000000"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
fill="#000000"
width="800px"
height="800px"
viewBox="0 0 16 16"
version="1.1"
id="svg1"
sodipodi:docname="horizontal-stacks-svgrepo-com.svg"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.98875"
inkscape:cx="579.5196"
inkscape:cy="342.35145"
inkscape:window-width="3440"
inkscape:window-height="1369"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="M 13.5,14.82 V 1.18 C 13.5,0.53 12.97,0 12.32,0 H 9.8 C 9.15,0 8.62,0.53 8.62,1.18 V 14.82 C 8.62,15.47 9.15,16 9.8,16 h 2.52 c 0.65,0 1.18,-0.53 1.18,-1.18 z M 9.88,14.75 V 1.25 h 2.37 v 13.5 z m -2.5,0.07 V 1.18 C 7.38,0.53 6.85,0 6.2,0 H 3.68 C 3.03,0 2.5,0.53 2.5,1.18 V 14.82 C 2.5,15.47 3.03,16 3.68,16 H 6.2 c 0.65,0 1.18,-0.53 1.18,-1.18 z M 3.75,14.75 V 1.25 h 2.38 v 13.5 z"
id="path1" />
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5 20.5C17.1944 20.5 21 16.6944 21 12C21 7.30558 17.1944 3.5 12.5 3.5C7.80558 3.5 4 7.30558 4 12C4 13.5433 4.41128 14.9905 5.13022 16.238M1.5 15L5.13022 16.238M6.82531 12.3832L5.47107 16.3542L5.13022 16.238" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 533 B

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.5 20.5C6.80558 20.5 3 16.6944 3 12C3 7.30558 6.80558 3.5 11.5 3.5C16.1944 3.5 20 7.30558 20 12C20 13.5433 19.5887 14.9905 18.8698 16.238M22.5 15L18.8698 16.238M17.1747 12.3832L18.5289 16.3542L18.8698 16.238" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 534 B

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.82 2.5H1.18C.53 2.5 0 3.03 0 3.68V6.2c0 .65.53 1.18 1.18 1.18h13.64c.65 0 1.18-.53 1.18-1.18V3.68c0-.65-.53-1.18-1.18-1.18zm-.07 3.62H1.25V3.75h13.5v2.37zm.07 2.5H1.18C.53 8.62 0 9.15 0 9.8v2.52c0 .65.53 1.18 1.18 1.18h13.64c.65 0 1.18-.53 1.18-1.18V9.8c0-.65-.53-1.18-1.18-1.18zm-.07 3.63H1.25V9.87h13.5v2.38z"/></svg>

Before

Width:  |  Height:  |  Size: 556 B

View File

@@ -10,7 +10,6 @@ from ui.widgets.placeholder_widget import PlaceholderWidget
from ui.widgets.color_list_widget import ColorListWidget
from ui.widgets.thumbnail_list_widget import ThumbnailListWidget
from ui.widgets.split_view_widget import SplitView
from ui.view_settings_dialog import ViewSettingsDialog
class MainWindow(QMainWindow):
def __init__(self):
@@ -43,14 +42,6 @@ class MainWindow(QMainWindow):
histogram_view = PlaceholderWidget("Histogram View", "#FF5733")
histogram_view.setFixedHeight(200)
self.view_settings_button = QPushButton("Ustawienia widoku")
self.view_settings_button.setMinimumHeight(40)
self.view_settings_button.setStyleSheet("font-size: 12pt;")
self.view_settings_dialog = ViewSettingsDialog(self)
self.view_settings_button.clicked.connect(self.view_settings_dialog.show)
self.color_list_widget = ColorListWidget(self.control_widget)
self.record_button = QPushButton("Nagraj Wideo")
@@ -62,7 +53,6 @@ class MainWindow(QMainWindow):
self.photo_button.setStyleSheet("font-size: 12pt;")
control_layout.addWidget(histogram_view)
control_layout.addWidget(self.view_settings_button)
control_layout.addWidget(self.color_list_widget)
control_layout.addWidget(self.record_button)
control_layout.addWidget(self.photo_button)

View File

@@ -1,209 +0,0 @@
from PySide6.QtWidgets import QDialog, QHBoxLayout ,QVBoxLayout, QPushButton, QGroupBox, QLabel, QRadioButton, QWidget, QToolButton, QSlider, QButtonGroup
from PySide6.QtGui import QIcon
from PySide6.QtCore import Qt, QSize, Signal
ISO_ARR = ["AUTO","100", "200", "400", "800", "1600", "3200"]
SPEED_ARR = ["30", "25", "20", "15", "13", "10.3", "8", "6.3", "5", "4", "3.2", "2.5", "2", "1.6", "1.3", "1", "0.8", "0.6", "0.5", "0.4", "0.3", "1/4", "1/5", "1/6", "1/8", "1/10", "1/13", "1/15", "1/20", "1/25", "1/30", "1/40", "1/50", "1/60", "1/80", "1/100", "1/125", "1/160", "1/200", "1/250", "1/320", "1/400", "1/500", "1/640", "1/800", "1/1000", "1/1250", "1/1600", "1/2000", "1/2500", "1/3200", "1/4000"]
class LabeledSpinSelector(QWidget):
indexChanged = Signal(int)
def __init__(self, title: str, values: list[str], show_slider: bool = False, parent=None):
super().__init__(parent)
self.values = values
self.current_index = 0
self.show_slider = show_slider
self._init_ui(title)
def _init_ui(self, title: str, button_size: int = 24, icon_size: int = 16):
self.title_label = QLabel(title)
decrement_button = QToolButton()
decrement_button.setIcon(QIcon("ui/icons/arrow-left-335-svgrepo-com.svg"))
decrement_button.setFixedSize(button_size, button_size)
decrement_button.setIconSize(QSize(icon_size, icon_size))
decrement_button.clicked.connect(self._decrement)
increment_button = QToolButton()
increment_button.setIcon(QIcon("ui/icons/arrow-right-336-svgrepo-com.svg"))
increment_button.setFixedSize(button_size, button_size)
increment_button.setIconSize(QSize(icon_size, icon_size))
increment_button.clicked.connect(self._increment)
self.value_label = QLabel(self.values[self.current_index] if self.values else "N/A")
self.value_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.value_label.setStyleSheet("background-color: rgb(48, 48, 48);")
self.value_label.setFixedHeight(button_size - 2)
spin_layout = QHBoxLayout()
spin_layout.addWidget(decrement_button)
spin_layout.addWidget(self.value_label)
spin_layout.addWidget(increment_button)
top_layout = QHBoxLayout()
top_layout.addWidget(self.title_label)
top_layout.addLayout(spin_layout)
self.slider = QSlider(Qt.Orientation.Horizontal)
self.slider.setRange(0, max(0, len(self.values) - 1))
self.slider.setTickPosition(QSlider.TickPosition.TicksBelow)
self.slider.valueChanged.connect(self._slider_changed)
main_layout = QVBoxLayout()
main_layout.addLayout(top_layout)
if self.show_slider:
main_layout.addWidget(self.slider)
main_layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(main_layout)
def _increment(self):
if not self.values:
return
new_index = min(self.current_index + 1, len(self.values) - 1)
self.set_index(new_index)
def _decrement(self):
if not self.values:
return
new_index = max(self.current_index - 1, 0)
self.set_index(new_index)
def _slider_changed(self, index):
if not self.values:
return
self.set_index(index)
def set_label(self, label: str):
self.title_label.setText(label)
def set_index(self, index: int):
if not self.values or not (0 <= index < len(self.values)):
return
if self.current_index != index:
self.current_index = index
self.value_label.setText(self.values[index])
if self.show_slider:
self.slider.setValue(index)
self.indexChanged.emit(index)
else:
# Always update UI even if index is the same (for initial set)
self.value_label.setText(self.values[index])
if self.show_slider:
self.slider.setValue(index)
class ViewSettingsDialog(QDialog):
detectDevice = Signal(str)
connectionChanged = Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Ustawienia widoku")
self.setFixedSize(640, 480)
self.setWindowFlags(self.windowFlags() | Qt.WindowType.WindowStaysOnTopHint)
# self.setup_ui()
camera_frame = self._create_devices_frame("camera")
hdmi_frame = self._create_devices_frame("hdmi")
conn_frame = self._create_connection_frame()
camera_settings = self._create_settings_frame()
hdmi_settings = self._create_settings_frame()
dialog_buttons = self._create_dialog_buttons()
hdmi_settings.setEnabled(False)
settings_layout = QHBoxLayout()
settings_layout.addWidget(camera_settings, 1)
settings_layout.addWidget(hdmi_settings, 1)
# main layout
main_layout = QVBoxLayout(self)
main_layout.addWidget(camera_frame)
main_layout.addWidget(hdmi_frame)
main_layout.addWidget(conn_frame)
main_layout.addLayout(settings_layout)
main_layout.addLayout(dialog_buttons)
main_layout.setStretch(3, 1)
self.last_choice = None
def _create_dialog_buttons(self):
ok_btn = QPushButton("OK")
ok_btn.clicked.connect(self.accept)
cancel_btn = QPushButton("Anuluj")
cancel_btn.setDefault(True)
cancel_btn.clicked.connect(self.reject)
layout = QHBoxLayout()
layout.addStretch()
layout.addWidget(ok_btn)
layout.addWidget(cancel_btn)
return layout
def _create_devices_frame(self, name):
frame = QGroupBox()
frame.setTitle("Wykryte aparaty")
frame.setContentsMargins(6, 20, 6, 10)
device_label = QLabel("Nie wykryto podłączonych urządzeń.")
detect_button = QPushButton("Wykryj...")
detect_button.clicked.connect(lambda: self.detectDevice.emit(name))
layout = QHBoxLayout()
layout.addWidget(device_label)
layout.addStretch()
layout.addWidget(detect_button)
frame.setLayout(layout)
return frame
def _create_connection_frame(self):
frame = QGroupBox()
frame.setTitle("Wybór połączenia")
frame.setContentsMargins(6, 20, 6, 10)
radio_usb = QRadioButton("USB")
radio_hybrid = QRadioButton("USB + HDMI")
radio_hdmi = QRadioButton("HDMI")
radio_hdmi.setEnabled(False)
radio_usb.clicked.connect(lambda: self.radio_toggle("usb"))
radio_hybrid.clicked.connect(lambda: self.radio_toggle("hybrid"))
radio_hdmi.clicked.connect(lambda: self.radio_toggle("hdmi"))
radio_layout = QHBoxLayout()
radio_layout.addStretch()
radio_layout.addWidget(radio_usb)
radio_layout.addStretch()
radio_layout.addWidget(radio_hybrid)
radio_layout.addStretch()
radio_layout.addWidget(radio_hdmi)
radio_layout.addStretch()
frame.setLayout(radio_layout)
return frame
# def _create_settings_frame(self, settings: dict[str, list[str]]):
def _create_settings_frame(self):
frame = QGroupBox()
frame.setTitle("Ustawienia aparatu")
layout = QVBoxLayout()
# for key, value in settings.items():
# layout.addWidget(LabeledSpinSelector(key, value))
layout.addStretch()
frame.setLayout(layout)
return frame
def radio_toggle(self, value):
if self.last_choice != value:
self.last_choice = value
self.connectionChanged.emit(value)

View File

@@ -1,10 +1,9 @@
from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QApplication, QMainWindow, QWidget, QVBoxLayout, QSplitter, QStackedWidget, QPushButton, QLabel, QToolButton
from PySide6.QtGui import QEnterEvent, QPixmap, QWheelEvent, QPainter, QBrush, QColor, QIcon
from PySide6.QtCore import Qt, QSize, Signal, QEvent
from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QApplication, QMainWindow, QWidget, QVBoxLayout, QSplitter, QStackedWidget, QPushButton, QLabel
from PySide6.QtGui import QPixmap, QWheelEvent, QPainter, QBrush, QColor
from PySide6.QtCore import Qt
import sys
from ui.widgets.placeholder_widget import PlaceholderWidget
class ZoomableImageView(QGraphicsView):
def __init__(self, parent=None):
super().__init__(parent)
@@ -12,21 +11,18 @@ class ZoomableImageView(QGraphicsView):
# Scena i element obrazu
self._scene = QGraphicsScene(self)
self.setScene(self._scene)
self._scene.setBackgroundBrush(
QBrush(QColor(20, 20, 20))) # ciemne tło
self._scene.setBackgroundBrush(QBrush(QColor(20, 20, 20))) # ciemne tło
self._pixmap_item = QGraphicsPixmapItem()
self._scene.addItem(self._pixmap_item)
# Ustawienia widoku
# przesuwanie myszą
self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag) # przesuwanie myszą
self.setRenderHint(QPainter.RenderHint.Antialiasing)
self.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
# Wyłączenie suwaków
self.setHorizontalScrollBarPolicy(
Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
# Parametry zoomu
self._zoom_factor = 1.25
@@ -57,7 +53,6 @@ class ZoomableImageView(QGraphicsView):
return
super().wheelEvent(event) # normalne przewijanie
class CameraPlaceholder(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
@@ -96,13 +91,11 @@ class CameraPlaceholder(QWidget):
self.camera_start_btn.setStyleSheet(style_sheet)
self.info_label = QLabel("Kliknij, aby uruchomić kamerę")
self.info_label.setStyleSheet(
"background-color: transparent; color: #CECECE; font-size: 18px;")
self.info_label.setStyleSheet("background-color: transparent; color: #CECECE; font-size: 18px;")
self.info_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addStretch()
layout.addWidget(self.camera_start_btn,
alignment=Qt.AlignmentFlag.AlignCenter)
layout.addWidget(self.camera_start_btn, alignment=Qt.AlignmentFlag.AlignCenter)
layout.addWidget(self.info_label)
layout.addStretch()
self.setLayout(layout)
@@ -110,112 +103,6 @@ class CameraPlaceholder(QWidget):
def set_info_text(self, text: str):
self.info_label.setText(text)
class ViewWithOverlay(QWidget):
toggleOrientation = Signal()
swapViews = Signal()
rotateCW = Signal()
rotateCCW = Signal()
def __init__(self):
super().__init__()
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
self.viewer = ZoomableImageView()
layout.addWidget(self.viewer)
icon_size = QSize(32, 32)
btn_size = (48, 48)
btn_style = """
background-color: rgba(255, 255, 255, 0.5);
border-radius: 8px;
border: 2px solid #1f1f1f;
"""
self.cw_btn = QToolButton(self)
self.cw_btn.setIcon(QIcon("ui/icons/rotate-cw-svgrepo-com.svg"))
self.cw_btn.setIconSize(icon_size)
self.cw_btn.setStyleSheet(btn_style)
self.cw_btn.setFixedSize(*btn_size)
move_x = self.cw_btn.width() + 10
self.cw_btn.move(self.width() - move_x, 10)
self.cw_btn.clicked.connect(self.rotateCW)
self.ccw_btn = QToolButton(self)
self.ccw_btn.setIcon(QIcon("ui/icons/rotate-ccw-svgrepo-com.svg"))
self.ccw_btn.setIconSize(icon_size)
self.ccw_btn.setStyleSheet(btn_style)
self.ccw_btn.setFixedSize(*btn_size)
move_x += self.ccw_btn.width() + 10
self.ccw_btn.move(self.width() - move_x, 10)
self.ccw_btn.clicked.connect(self.rotateCCW)
self.flip_btn = QToolButton(self)
# self.flip_btn.setIcon(QIcon("ui/icons/flip-vertical-svgrepo-com.svg"))
self.flip_btn.setIconSize(icon_size)
self.flip_btn.setStyleSheet(btn_style)
self.flip_btn.setFixedSize(*btn_size)
move_x += self.flip_btn.width() + 10
self.flip_btn.move(self.width() - move_x, 10)
self.flip_btn.clicked.connect(self.swapViews)
self.orient_btn = QToolButton(self)
# self.orient_btn.setIcon(QIcon("ui/icons/horizontal-stacks-svgrepo-com.svg"))
self.orient_btn.setIconSize(icon_size)
self.orient_btn.setStyleSheet(btn_style)
self.orient_btn.setFixedSize(*btn_size)
move_x += self.orient_btn.width() + 10
self.orient_btn.move(self.width() - move_x, 10)
self.orient_btn.clicked.connect(self.toggleOrientation)
self.cw_btn.raise_()
self.ccw_btn.raise_()
self.flip_btn.raise_()
self.orient_btn.raise_()
self.toggle_orientation(Qt.Orientation.Vertical)
def set_image(self, pixmap: QPixmap):
self.viewer.set_image(pixmap)
def resizeEvent(self, event):
super().resizeEvent(event)
# Aktualizacja pozycji przycisku przy zmianie rozmiaru
move_x = self.cw_btn.width() + 10
self.cw_btn.move(self.width() - move_x, 10)
move_x += self.ccw_btn.width() + 10
self.ccw_btn.move(self.width() - move_x, 10)
move_x += self.flip_btn.width() + 10
self.flip_btn.move(self.width() - move_x, 10)
move_x += self.orient_btn.width() + 10
self.orient_btn.move(self.width() - move_x, 10)
def toggle_orientation(self, orientation):
if orientation == Qt.Orientation.Vertical:
self.flip_btn.setIcon(QIcon("ui/icons/flip-vertical-svgrepo-com.svg"))
self.orient_btn.setIcon(QIcon("ui/icons/horizontal-stacks-svgrepo-com.svg"))
else:
self.flip_btn.setIcon(QIcon("ui/icons/flip-horizontal-svgrepo-com.svg"))
self.orient_btn.setIcon(QIcon("ui/icons/vertical-stacks-svgrepo-com.svg"))
def enterEvent(self, event: QEnterEvent) -> None:
self.orient_btn.show()
self.flip_btn.show()
self.ccw_btn.show()
self.cw_btn.show()
return super().enterEvent(event)
def leaveEvent(self, event: QEvent) -> None:
self.orient_btn.hide()
self.flip_btn.hide()
self.ccw_btn.hide()
self.cw_btn.hide()
return super().leaveEvent(event)
class SplitView(QSplitter):
def __init__(self, parent=None):
super().__init__(parent)
@@ -223,11 +110,9 @@ class SplitView(QSplitter):
self.setOrientation(Qt.Orientation.Vertical)
self.widget_start = CameraPlaceholder()
# self.widget_live = ZoomableImageView()
self.widget_live = ViewWithOverlay()
self.widget_live = ZoomableImageView()
# self.widget_live = PlaceholderWidget("Camera View", "#750466")
# self.widget_ref = ZoomableImageView()
self.widget_ref = ViewWithOverlay()
self.widget_ref = ZoomableImageView()
# self.widget_ref = PlaceholderWidget("Image View", "#007981")
self.stack = QStackedWidget()
@@ -244,31 +129,18 @@ class SplitView(QSplitter):
# pixmap.fill(Qt.GlobalColor.lightGray)
self.widget_live.set_image(pixmap)
self.widget_live.toggleOrientation.connect(self.toggle_orientation)
self.widget_ref.toggleOrientation.connect(self.toggle_orientation)
self.widget_live.swapViews.connect(self.swap_views)
self.widget_ref.swapViews.connect(self.swap_views)
def toggle_orientation(self):
if self.orientation() == Qt.Orientation.Vertical:
self.setOrientation(Qt.Orientation.Horizontal)
self.setSizes([self.width()//2, self.width()//2])
self.widget_live.toggle_orientation(Qt.Orientation.Horizontal)
self.widget_ref.toggle_orientation(Qt.Orientation.Horizontal)
else:
self.setOrientation(Qt.Orientation.Vertical)
self.setSizes([self.height()//2, self.height()//2])
self.widget_live.toggle_orientation(Qt.Orientation.Vertical)
self.widget_ref.toggle_orientation(Qt.Orientation.Vertical)
def swap_views(self):
"""Zamiana widoków miejscami"""
index_live = self.indexOf(self.stack)
index_ref = self.indexOf(self.widget_ref)
sizes = self.sizes()
self.insertWidget(index_live, self.widget_ref)
self.insertWidget(index_ref, self.stack)
self.setSizes(sizes)
# def set_live_image(self, path_image: str):
# """Ustawienie obrazu na żywo"""
# pixmap = QPixmap(path_image)
# self.widget_live.set_image(pixmap)
def set_live_image(self, pixmap: QPixmap):
"""Ustawienie obrazu na żywo"""
@@ -287,3 +159,6 @@ class SplitView(QSplitter):
self.stack.setCurrentWidget(self.widget_live)
else:
self.stack.setCurrentWidget(self.widget_start)