diff --git a/emioapi/_camerafeedwindow.py b/emioapi/_camerafeedwindow.py deleted file mode 100644 index c29953f..0000000 --- a/emioapi/_camerafeedwindow.py +++ /dev/null @@ -1,68 +0,0 @@ -import tkinter as tk -from tkinter import ttk - -import cv2 as cv -from PIL import ImageTk, Image - -""" - This class is used to create a camera feed window that displays the video feed from a camera. - It runs in a separate thread and can be started and stopped using the start() and stop() methods. - The camera feed is displayed in a window named 'mask'. - The class uses OpenCV to create the window and display the video feed. - The camera feed is passed to the class as a frame parameter. -""" -class CameraFeedWindow(): - windowCount = 0 - def __init__(self, rootWindow, name=f"Camera Feed Window", trackbarParams={}, on_change=None, *args, **kwargs): - super().__init__(*args, **kwargs) - CameraFeedWindow.windowCount += 1 - self.rootWindow = rootWindow - self.name = name - self.running = True - self.window = tk.Toplevel(self.rootWindow) - self.image = None - - self.window.title(self.name) - # self.window.geometry("640x480") - self.window.resizable(False, False) - self.canvas = tk.Canvas(self.window, width=640, height=480) - self.canvasImage = self.canvas.create_image(320, 240, anchor="center") - self.canvas.pack() - - self.default_param = trackbarParams.copy() - self.trackbarParams = trackbarParams - self.trackbars = {} - if self.trackbarParams: - for key, _ in self.trackbarParams.items(): - self.trackbars[key] = ttk.Scale(self.window, from_=0, to=255, orient='horizontal') - self.trackbars[key].set(self.default_param[key]) - self.trackbars[key].configure(command = self.on_change if on_change is None else on_change) - ttk.Label(self.window, text=key).pack() - self.trackbars[key].pack(fill="x", padx=5) - self.resetButton = ttk.Button(self.window, text="Reset From File", command=self.reset) - self.resetButton.pack(pady=5) - - self.window.protocol("WM_DELETE_WINDOW", self.closed) - - def closed(self): - CameraFeedWindow.windowCount -= 1 - self.window.destroy() - self.running = False - - def on_change(self, value): - for key, _ in self.trackbarParams.items(): - self.trackbarParams[key] = int(self.trackbars[key].get()) - - def reset(self): - for key, val in self.trackbarParams.items(): - self.trackbars[key].set(self.default_param[key]) - self.trackbarParams[key] = self.default_param[key] - - def set_frame(self, frame): - # Convert the frame to a format that can be displayed in the Tkinter window - frame = cv.cvtColor(frame, cv.COLOR_BGR2RGB) - frame = cv.resize(frame, (640, 480)) - if self.rootWindow and self.window and self.window.winfo_exists(): - self.image = ImageTk.PhotoImage(image=Image.fromarray(frame)) - self.canvas.imgref = self.image - self.canvas.itemconfig(self.canvasImage, image=self.image) \ No newline at end of file diff --git a/emioapi/_depthcamera.py b/emioapi/_depthcamera.py index 693b928..b009a1d 100644 --- a/emioapi/_depthcamera.py +++ b/emioapi/_depthcamera.py @@ -1,12 +1,14 @@ import json import time from enum import Enum +import threading +from types import SimpleNamespace import numpy as np import cv2 as cv import pyrealsense2 as rs -from ._camerafeedwindow import CameraFeedWindow +from ._gui import EmioAPIGUI from ._positionestimation import PositionEstimation, CONFIG_FILENAME from emioapi._logging_config import logger @@ -119,6 +121,10 @@ def __init__(self, self._camera_serial = camera_serial self.initialized = True + """`gui` holds references to the gui itself `gui_handle` and to the running thread of the gui `gui_thread`""" + self.gui: SimpleNamespace = None + self.lock = threading.Lock() + if not self.initialized: return @@ -167,40 +173,9 @@ def set_depth_min(self, new_depth_min: int): def create_feed_windows(self): - import tkinter as tk - from tkinter import ttk - self.rootWindow = tk.Tk() - self.rootWindow.resizable(False, False) - - self.rootWindow.title("Camera Feed Manager") - ttk.Button(self.rootWindow, text="Close Windows", command=self.quit).pack(side=tk.BOTTOM, padx=5, pady=5) - ttk.Button(self.rootWindow, text="Save", command=lambda: json.dump(self.parameter, open(CONFIG_FILENAME, 'w'))).pack(side=tk.BOTTOM, padx=5, pady=5) - ttk.Button(self.rootWindow, text="Mask Window", command=self.create_mask_window).pack(side=tk.BOTTOM, padx=5, pady=5) - ttk.Button(self.rootWindow, text="Frame Window", command=self.create_frame_window).pack(side=tk.BOTTOM, padx=5, pady=5) - ttk.Button(self.rootWindow, text="HSV Window", command=self.create_HSV_window).pack(side=tk.BOTTOM, padx=5, pady=5) - ttk.Button(self.rootWindow, text="Depth Window", command=self.createDepthWindow).pack(side=tk.BOTTOM, padx=5, pady=5) - - self.create_mask_window() - self.create_frame_window() - - self.rootWindow.protocol("WM_DELETE_WINDOW", self.quit) - self.rootWindow.update_idletasks() - - def create_mask_window(self): - if self.maskWindow is None or not self.maskWindow.running: - self.maskWindow = CameraFeedWindow(rootWindow=self.rootWindow, trackbarParams=self.parameter, name='Mask') - - def create_frame_window(self): - if self.frameWindow is None or not self.frameWindow.running: - self.frameWindow = CameraFeedWindow(rootWindow=self.rootWindow, name='RGB Frame') - - def create_HSV_window(self): - if self.hsvWindow is None or not self.hsvWindow.running: - self.hsvWindow = CameraFeedWindow(rootWindow=self.rootWindow, name='HSV Frame') + self.gui_object = EmioAPIGUI.start_in_thread() + self.gui_window = EmioAPIGUI.add_camera_feed(trackbarParams=self.parameter, save_callback=lambda: json.dump(self.parameter, open(CONFIG_FILENAME, 'w'))) - def createDepthWindow(self): - if self.depthWindow is None or not self.depthWindow.running: - self.depthWindow = CameraFeedWindow(rootWindow=self.rootWindow, name='Depth Frame') def quit(self): for window in [self.maskWindow, self.frameWindow, self.hsvWindow, self.depthWindow]: @@ -260,7 +235,7 @@ def calibrate(self): self.calibration_status = CalibrationStatusEnum.CALIBRATING # Create the windows to display the binrary mask and the HSV frame - calibration_window = CameraFeedWindow(rootWindow=self.rootWindow, name='Calibration') + calibration_window = EmioAPIGUI(rootWindow=self.rootWindow, name='Calibration') if self.position_estimator is not None: while self.position_estimator.count_calibration_frames < 200 and time.time() - starttime < 300: @@ -295,11 +270,12 @@ def get_frame(self): return False # Convert images to numpy arrays - self.depth_frame = np.asanyarray(depth_frame.get_data()) - self.frame = np.asanyarray(color_frame.get_data()) - self.depth_rsframe = depth_frame + with self.lock: + self.depth_frame = np.asanyarray(depth_frame.get_data()) + self.frame = np.asanyarray(color_frame.get_data()) + self.depth_rsframe = depth_frame - return True + return True def update(self): @@ -311,78 +287,63 @@ def update(self): def process_frame(self): # if frame is read correctly ret is True + with self.lock: + self.hsvFrame = cv.cvtColor(self.frame, cv.COLOR_BGR2HSV) - self.hsvFrame = cv.cvtColor(self.frame, cv.COLOR_BGR2HSV) + # color definition + red_lower = np.array([self.parameter['hue_l'], self.parameter['sat_l'], self.parameter['value_l']]) + red_upper = np.array([self.parameter['hue_h'], self.parameter['sat_h'], self.parameter['value_h']]) - # color definition - red_lower = np.array([self.parameter['hue_l'], self.parameter['sat_l'], self.parameter['value_l']]) - red_upper = np.array([self.parameter['hue_h'], self.parameter['sat_h'], self.parameter['value_h']]) + # red color mask (sort of thresholding, actually segmentation) + mask = cv.inRange(self.hsvFrame, red_lower, red_upper) + mask2 = cv.inRange(self.depth_frame, self.depth_min, self.depth_max) - # red color mask (sort of thresholding, actually segmentation) - mask = cv.inRange(self.hsvFrame, red_lower, red_upper) - mask2 = cv.inRange(self.depth_frame, self.depth_min, self.depth_max) + mask = cv.bitwise_and(mask, mask2, mask=mask) - mask = cv.bitwise_and(mask, mask2, mask=mask) + erosion_shape = cv.MORPH_RECT + erosion_size = self.parameter['erosion_size'] + element = cv.getStructuringElement(erosion_shape, (2 * erosion_size + 1, 2 * erosion_size + 1), + (erosion_size, erosion_size)) - erosion_shape = cv.MORPH_RECT - erosion_size = self.parameter['erosion_size'] - element = cv.getStructuringElement(erosion_shape, (2 * erosion_size + 1, 2 * erosion_size + 1), - (erosion_size, erosion_size)) + mask = cv.erode(mask, element, iterations=3) + mask = cv.dilate(mask, element, iterations=3) - mask = cv.erode(mask, element, iterations=3) - mask = cv.dilate(mask, element, iterations=3) + self.maskFrame = cv.bitwise_and(self.frame, self.frame, mask=mask) - self.maskFrame = cv.bitwise_and(self.frame, self.frame, mask=mask) + if self.tracking: + contours, _ = cv.findContours(mask, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) + if len(contours) != 0: + areas = [cv.contourArea(cnt) for cnt in contours] - if self.tracking: - contours, _ = cv.findContours(mask, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) - if len(contours) != 0: - areas = [cv.contourArea(cnt) for cnt in contours] + self.trackers_pos = [] + self.trackers_pos_image = [] + for i, a in enumerate(areas): + if a > self.parameter['area']: + x, y = compute_contour_center(contours[i]) + marker_mask = np.zeros_like(mask) - self.trackers_pos = [] - self.trackers_pos_image = [] - for i, a in enumerate(areas): - if a > self.parameter['area']: - x, y = compute_contour_center(contours[i]) - marker_mask = np.zeros_like(mask) + depth = compute_median_depth(contours[i], self.depth_frame) if self.depth_frame[y, x] == 0 else self.depth_frame[y, x] + worldx, worldy, worldz = self.position_estimator.camera_image_to_simulation(x, y, depth) + self.trackers_pos.append([worldx, worldy, worldz]) + self.trackers_pos_image.append([x, y, depth]) - depth = compute_median_depth(contours[i], self.depth_frame) if self.depth_frame[y, x] == 0 else self.depth_frame[y, x] - worldx, worldy, worldz = self.position_estimator.camera_image_to_simulation(x, y, depth) - self.trackers_pos.append([worldx, worldy, worldz]) - self.trackers_pos_image.append([x, y, depth]) + cv.drawContours(marker_mask, [contours[i]], -1, color=255, thickness=-1) + for frame in [self.hsvFrame, self.frame]: + cv.circle(frame, (x, y), 2, color=255, thickness=-1) + cv.putText(frame, f"{i} ({x}, {y}, {depth})", (x, y), cv.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1) + cv.putText(frame, f"{i} ({worldx:.2f}, {worldy:.2f}, {worldz:.2f})", (x, y + 15), cv.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1) - cv.drawContours(marker_mask, [contours[i]], -1, color=255, thickness=-1) - for frame in [self.hsvFrame, self.frame]: - cv.circle(frame, (x, y), 2, color=255, thickness=-1) - cv.putText(frame, f"{i} ({x}, {y}, {depth})", (x, y), cv.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1) - cv.putText(frame, f"{i} ({worldx:.2f}, {worldy:.2f}, {worldz:.2f})", (x, y + 15), cv.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1) + if self.show_video_feed: + cv.drawContours(self.frame, [contours[i]], -1, (255, 255, 0), 3) - if self.show_video_feed: - cv.drawContours(self.frame, [contours[i]], -1, (255, 255, 0), 3) + if self.compute_point_cloud: + points = self.pc.calculate(self.depth_rsframe) + v = points.get_vertices() + self.point_cloud = np.asanyarray(v).view(np.float32).reshape(-1, 3) # xyz - if self.compute_point_cloud: - points = self.pc.calculate(self.depth_rsframe) - v = points.get_vertices() - self.point_cloud = np.asanyarray(v).view(np.float32).reshape(-1, 3) # xyz - - if self.show_video_feed: - if self.rootWindow is None: - self.create_feed_windows() - - if self.maskWindow is not None and self.maskWindow.running: - self.maskWindow.set_frame(self.maskFrame) - - if self.frameWindow is not None and self.frameWindow.running: - self.frameWindow.set_frame(self.frame) - - if self.hsvWindow is not None and self.hsvWindow.running: - self.hsvWindow.set_frame(self.hsvFrame) - - if self.depthWindow is not None and self.depthWindow.running: + if self.show_video_feed and self.gui.gui_handle.is_running(): colorized = np.asanyarray(rs.colorizer().colorize(self.depth_rsframe).get_data()) - self.depthWindow.set_frame(colorized) - - self.rootWindow.update() + self.gui.gui_handle.set_frames(self.frame, colorized, self.maskFrame, self.hsvFrame) def close(self): diff --git a/emioapi/_gui.py b/emioapi/_gui.py new file mode 100644 index 0000000..7655b80 --- /dev/null +++ b/emioapi/_gui.py @@ -0,0 +1,173 @@ +from typing import Final, Tuple +from types import SimpleNamespace + +import dearpygui.dearpygui as dpg +import cv2 as cv +import numpy as np + +from emioapi._logging_config import logger + + +""" + This class is used to create a camera feed window that displays the video feed from a camera. + It runs in a separate thread and can be started and stopped using the start() and stop() methods. + The camera feed is displayed in a window named 'mask'. + The class uses OpenCV to create the window and display the video feed. + The camera feed is passed to the class as a frame parameter. +""" +class EmioAPIGUI(): + + TAG_MAINWINDOW:Final[str] = "MainWindow" + TAG_DEPTHTEX:Final[str] = "tex_depth" + TAG_RGBTEX:Final[str] = "tex_rgb" + TAG_HSVTEX:Final[str] = "tex_hsv" + TAG_MASKTEX:Final[str] = "tex_mask" + TEXWIDTH:Final[int] = 640 + TEXHEIGHT:Final[int] = 480 + + _TIMEOUT_THREAD: Final[int]=5 + + windowCount = 0 + main_window = None + gui = SimpleNamespace(gui_handle=None, gui_thread=None) + + @staticmethod + def start_in_thread(trackbarParams={}, save_callback=None): + from threading import Thread + from time import time, sleep + + def _gui_worker(result: SimpleNamespace, trackbarParams={}, save_callback=None): + result.gui_handle = EmioAPIGUI() + logger.debug(f"Starting Emio API GUI inside a thread.") + result.gui_handle.run_loop() + + if EmioAPIGUI.windowCount==0: + EmioAPIGUI.gui.gui_thread = Thread(target=_gui_worker, daemon=True, args=(EmioAPIGUI.gui, trackbarParams, save_callback)) + EmioAPIGUI.gui.gui_thread.start() + startime = time() + while not EmioAPIGUI.gui.gui_handle: + if time()-startime < EmioAPIGUI._TIMEOUT_THREAD: + logger.info(f"Waiting for thread {EmioAPIGUI.gui.gui_thread} to start") + sleep(0.5) + else: + raise TimeoutError("Could not open EmioAPIGUI thread in time.") + logger.debug(f"Started Emio API GUI inside a thread {EmioAPIGUI.gui.gui_thread}.") + else: + logger.warning(f"EmioAPIGUI thread is already running in a thread.") + + return EmioAPIGUI.gui + + @staticmethod + def add_camera_feed(trackbarParams={}, save_callback=None): + return EmioAPIGUI._CameraFeedWindow(trackbarParams, save_callback) + + + def __init__(self): + try: + if EmioAPIGUI.windowCount==0: + dpg.create_context() + dpg.create_viewport(title="Camera Feed", width=1150, height=780, resizable=False) + dpg.setup_dearpygui() + EmioAPIGUI.main_window = dpg.add_window(tag=EmioAPIGUI.TAG_MAINWINDOW, no_title_bar=True) + dpg.add_tab_bar(tag="TAG_TABBAR", parent=EmioAPIGUI.main_window) + dpg.show_viewport() + dpg.set_primary_window(EmioAPIGUI.TAG_MAINWINDOW, True) + self.context_exists = True + except Exception as e: + logger.error(str(e)) + raise(e) + + + def run_loop(self): + while dpg.is_dearpygui_running(): + dpg.render_dearpygui_frame() + + logger.debug("Closing GUI...") + self.context_exists = False + dpg.destroy_context() + logger.warning("GUI is closed but EmioAPI is still running!") + + + def is_running(self): + return dpg.is_dearpygui_running() if self.context_exists else False + + + class _CameraFeedWindow(): + def __init__(self, trackbarParams={}, save_callback=None): + self.window_id = str(EmioAPIGUI.windowCount) + logger.debug(f"Creating CameraFeedWindow {self.window_id}") + + self.default_param = trackbarParams.copy() + self.trackbarParams = trackbarParams + self.trackbars = {} + + self.images_scale = 0.6 + + self.save_callback = save_callback + + # Textures for camera frames + with dpg.texture_registry(show=False): + blank_data = np.zeros((EmioAPIGUI.TEXWIDTH * EmioAPIGUI.TEXHEIGHT * 4,), dtype=np.float32) + dpg.add_dynamic_texture(width=EmioAPIGUI.TEXWIDTH, height=EmioAPIGUI.TEXHEIGHT, default_value=blank_data, tag=EmioAPIGUI.TAG_RGBTEX+self.window_id) + dpg.add_dynamic_texture(width=EmioAPIGUI.TEXWIDTH, height=EmioAPIGUI.TEXHEIGHT, default_value=blank_data, tag=EmioAPIGUI.TAG_DEPTHTEX+self.window_id) + dpg.add_dynamic_texture(width=EmioAPIGUI.TEXWIDTH, height=EmioAPIGUI.TEXHEIGHT, default_value=blank_data, tag=EmioAPIGUI.TAG_MASKTEX+self.window_id) + dpg.add_dynamic_texture(width=EmioAPIGUI.TEXWIDTH, height=EmioAPIGUI.TEXHEIGHT, default_value=blank_data, tag=EmioAPIGUI.TAG_HSVTEX+self.window_id) + + # with dpg.tab_bar(parent=EmioAPIGUI.main_window): + with dpg.tab(label=f"Window {self.window_id}", parent="TAG_TABBAR"): + with dpg.child_window(no_scroll_with_mouse=True, no_scrollbar=True): + # params + with dpg.child_window(width=300): + if self.trackbarParams: + for key, val in self.trackbarParams.items(): + dpg.add_text(default_value=str(key)) + dpg.add_slider_double(tag=f"TAG_SLIDER{self.window_id}_{key}",min_value=0, max_value=255, default_value=val) + with dpg.group(horizontal=True): + dpg.add_button(label="Save Params", callback=self.save_params) + dpg.add_button(label="Reset Params", callback=self.reset_params) + + # frames + width, height = self.image_size + origin = (316, 8) + with dpg.child_window(label="RGB Frame", pos=origin, width=width, height=height+62): + dpg.add_text(default_value="RGB") + dpg.add_image(EmioAPIGUI.TAG_RGBTEX+self.window_id, label="RGB", width=width, height=height) + with dpg.child_window(label="Depth Frame", pos=(origin[0]+width+16, origin[1]), width=width, height=height+62): + dpg.add_text(default_value="Depth") + dpg.add_image(EmioAPIGUI.TAG_DEPTHTEX+self.window_id, width=width, height=height) + with dpg.child_window(label="Mask Frame", pos=(origin[0], origin[1]+height+35), width=width, height=height+62): + dpg.add_text(default_value="Mask") + dpg.add_image(EmioAPIGUI.TAG_MASKTEX+self.window_id, width=width, height=height) + with dpg.child_window(label="HSV Frame", pos=(origin[0]+width+16, origin[1]+height+35), width=width, height=height+62): + dpg.add_text(default_value="HSV") + dpg.add_image(EmioAPIGUI.TAG_HSVTEX+self.window_id, width=width, height=height) + + EmioAPIGUI.windowCount=+1 + + + @property + def image_size(self) -> Tuple[int, int]: + return (self.images_scale * EmioAPIGUI.TEXWIDTH, self.images_scale*EmioAPIGUI.TEXHEIGHT) + + + def reset_params(self): + for key, _ in self.trackbarParams.items(): + dpg.set_value(f"TAG_SLIDER{self.window_id}_{key}", self.default_param[key]) + self.trackbarParams[key] = self.default_param[key] + + + def save_params(self): + if self.save_callback is None: + logger.warning("Parameters not saved. No callback given at init.") + else: + self.save_callback() + + def set_frames(self, rgb, depth, mask, hsv): + if rgb is not None: + dpg.set_value(EmioAPIGUI.TAG_RGBTEX+self.window_id, np.ascontiguousarray(cv.cvtColor(rgb, cv.COLOR_BGR2RGBA), dtype=np.float32)/255) + if depth is not None: + dpg.set_value(EmioAPIGUI.TAG_DEPTHTEX+self.window_id, np.ascontiguousarray(cv.cvtColor(depth, cv.COLOR_BGR2RGBA), dtype=np.float32)/255) + if mask is not None: + dpg.set_value(EmioAPIGUI.TAG_MASKTEX+self.window_id, np.ascontiguousarray(cv.cvtColor(mask, cv.COLOR_BGR2RGBA), dtype=np.float32)/255) + if hsv is not None: + dpg.set_value(EmioAPIGUI.TAG_HSVTEX+self.window_id, np.ascontiguousarray(cv.cvtColor(hsv, cv.COLOR_BGR2RGBA), dtype=np.float32)/255) \ No newline at end of file diff --git a/emioapi/_logging_config.py b/emioapi/_logging_config.py index 62a69a3..3bd7bbc 100644 --- a/emioapi/_logging_config.py +++ b/emioapi/_logging_config.py @@ -1,18 +1,21 @@ import logging +import sys # based on https://docs.python.org/3/howto/logging-cookbook.html logger = logging.getLogger('emioapi_logger') logger.setLevel(logging.INFO) # Log everything (DEBUG level or higher) -# Prevent multiple handlers if the logger is configured multiple times -# if not logger.handlers: -# Create a console handler to log to the console -ch = logging.StreamHandler() -ch.setLevel(logging.INFO) +if not logger.handlers: + # Prevent multiple handlers if the logger is configured multiple times + # if not logger.handlers: + # Create a console handler to log to the console + ch = logging.StreamHandler() + ch.setLevel(logging.INFO) -# Define log format -FORMAT = "[%(levelname)s]\t[%(filename)s:%(lineno)s - %(funcName)s() ] %(message)s" -formatter = logging.Formatter(FORMAT) -ch.setFormatter(formatter) + # Define log format + FORMAT = "[%(levelname)s]\t[%(filename)s:%(lineno)s - %(funcName)s() ] %(message)s" + formatter = logging.Formatter(FORMAT) + ch.setFormatter(formatter) -logger.addHandler(ch) \ No newline at end of file + ch.flush = sys.stdout.flush + logger.addHandler(ch) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 0414c24..0e01d64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,8 @@ dependencies = [ "opencv-python>=4.11.0.86", "dynamixelmotorsapi", "numpy", + "dearpygui>=2.3.1", + "cv2-enumerate-cameras>=1.4.0", ] description = "An API for controlling the motors of the EMIO robot." keywords = ["roboticsc", "emio", "api"] diff --git a/to_delete.py b/to_delete.py new file mode 100644 index 0000000..3fcf5b3 --- /dev/null +++ b/to_delete.py @@ -0,0 +1,72 @@ +import threading + +import cv2 +from cv2_enumerate_cameras import enumerate_cameras +import dearpygui.dearpygui as dpg +import dearpygui.demo as demo + +import dearpygui.dearpygui as dpg +import dearpygui.demo as demo + +# dpg.create_context() +# dpg.create_viewport(title='Custom Title', width=600, height=600) + +# demo.show_demo() + +# dpg.setup_dearpygui() +# dpg.show_viewport() +# dpg.start_dearpygui() +# dpg.destroy_context() + + +from emioapi._gui import EmioAPIGUI + +# for camera_info in enumerate_cameras(): +# print(f"Index: {camera_info.index}, Name: {camera_info.name}, Path: {camera_info.path}") + +# Initialisation de la caméra (0 = caméra par défaut) +cap = cv2.VideoCapture(0) +ret, frame = cap.read() +if not ret: + print("Erreur : Impossible d'accéder à la caméra 0.") + exit() + +# cap1 = cv2.VideoCapture(1) +# ret1, frame1 = cap1.read() +# if not ret1: +# print("Erreur : Impossible d'accéder à la caméra 1.") +# exit() + +# Récupération des dimensions de la capture caméra +height, width, _ = frame.shape + +running = True +gui = None +lock = threading.Lock() +mockparams = { + "a": 10, + "b": 128 +} + + +def update_camera(): + global emiogui, gui + import numpy as np + ret, frame = cap.read() + # ret1, frame1 = cap1.read() + if ret: + emiogui.set_frames(frame, frame, frame, frame) + frame1 = np.random.randint(0, 255, size=(EmioAPIGUI.TEXWIDTH, EmioAPIGUI.TEXHEIGHT, 3), dtype=np.uint8) # Uncomment if not extra camera + emiogui1.set_frames(frame1, frame1, frame1, frame1) + +gui = EmioAPIGUI.start_in_thread() +emiogui = EmioAPIGUI.add_camera_feed(trackbarParams=mockparams, save_callback=lambda: print("Saved params")) +emiogui1 = EmioAPIGUI.add_camera_feed(trackbarParams=mockparams, save_callback=lambda: print("Saved params")) + +while running : + if gui.gui_handle.is_running(): + update_camera() + +print("out of while") + +cap.release() diff --git a/uv.lock b/uv.lock index d5c0108..9b07475 100644 --- a/uv.lock +++ b/uv.lock @@ -247,6 +247,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, ] +[[package]] +name = "cv2-enumerate-cameras" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/61/65ea4f16f211b6bd90c18384296a6a79e7d5f49f56082ee36a2185a8f5d6/cv2_enumerate_cameras-1.4.0.tar.gz", hash = "sha256:d6f39a236e15cc1bddf00e852a4c6bdfea94d3eebb2d3213848bb5c4036b0689", size = 12315, upload-time = "2026-09-26T14:09:42.754Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/2f/5277bcabd62e675b85f00825d8ebc3ceaa23c7fd6362a4dc09f3accc716e/cv2_enumerate_cameras-1.4.0-cp310-abi3-win32.whl", hash = "sha256:9c5cd79dd6e8d2b6450e40bb208d968f50976cf15f276c5e10c9880498e23b25", size = 211333, upload-time = "2026-09-26T14:09:32.664Z" }, + { url = "https://files.pythonhosted.org/packages/e8/6d/15b6f504b79749a45a53304310c1665044f03d1b79ec2b00598924fc0d99/cv2_enumerate_cameras-1.4.0-cp310-abi3-win_amd64.whl", hash = "sha256:0dcc60c0cc1638bbfb381d0430391d3aabb70324e5eef338d018831207813060", size = 209221, upload-time = "2026-09-26T14:09:34.228Z" }, + { url = "https://files.pythonhosted.org/packages/74/e6/96981e59cd724bad4155949b14bbb67d45d0502c15b3cf5cc522a4cedb8d/cv2_enumerate_cameras-1.4.0-cp314-cp314t-win32.whl", hash = "sha256:fb6312124060093227ff82f0c4a0ff4161b576e9bfcd6bbbafc63b5b0c4788c3", size = 217218, upload-time = "2026-09-26T14:09:35.636Z" }, + { url = "https://files.pythonhosted.org/packages/e6/be/933593c37b616a63df1491250b7b999bc0297399a69ac616c019a01d5e4b/cv2_enumerate_cameras-1.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d7282a37bd340bd63b65950b9fb8e6c2fc7dd5b9141f6cfbb77bf37507932bbe", size = 216375, upload-time = "2026-09-26T14:09:37.274Z" }, + { url = "https://files.pythonhosted.org/packages/41/4c/b2b96a9f88d0b406d36cf5d20af8137f1cb2c271cb13ec5b5e9df82714c0/cv2_enumerate_cameras-1.4.0-cp315-cp315t-win32.whl", hash = "sha256:c6bb01c203813ad3009f504a1bd914efb691174fecd9e99b42e21b8b9d522039", size = 217220, upload-time = "2026-09-26T14:09:38.851Z" }, + { url = "https://files.pythonhosted.org/packages/02/4e/bd22f6430faea2f47b11fd92c18a847b4d60cd707712cd913bd5df9b681a/cv2_enumerate_cameras-1.4.0-cp315-cp315t-win_amd64.whl", hash = "sha256:791e7f68c5d62c3c22861c53f87b1174f6aa663eeceef8a43fab17ca80fb099c", size = 216376, upload-time = "2026-09-26T14:09:40.31Z" }, + { url = "https://files.pythonhosted.org/packages/5f/99/e7ab30db5eb4f2f87be9f419eda101ba1cdba6192c5cfdc7ef60305866e7/cv2_enumerate_cameras-1.4.0-py3-none-any.whl", hash = "sha256:ef3db06a004ada2e82b62efaac69f37223f1505b64954bf0f838d21f4cf237cc", size = 13334, upload-time = "2026-09-26T14:09:41.693Z" }, +] + [[package]] name = "cycler" version = "0.12.1" @@ -296,6 +314,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/85/ef/318fd032781bbdd6fa92b4c545b00f71c7a7e0f12b1475177d048f2362e6/databind_json-4.5.5-py3-none-any.whl", hash = "sha256:b5d7934d2c407be16550bc97de905035b6a20a58b291cf781b9981d743db0a96", size = 1653, upload-time = "2026-05-22T17:23:49.584Z" }, ] +[[package]] +name = "dearpygui" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/eb/78ba8feb7fa01ae3b2eafe94783e5051ec5cf997463c4181840fc89b80c8/dearpygui-2.3.1-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:8c3db42d9496e1baed8117148718bb69cab671066435d9f312cafb90d0df530c", size = 2003069, upload-time = "2026-05-01T22:45:57.452Z" }, + { url = "https://files.pythonhosted.org/packages/3b/20/bab626e3e02645fdeba9b67dd1f6296e624a82d023fe2fa186fc4444c4dd/dearpygui-2.3.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:55ce713722583f1c874bfdb458cc3c0c09200c200419a31128534f7a86f9ccff", size = 2678634, upload-time = "2026-05-01T22:46:04.902Z" }, + { url = "https://files.pythonhosted.org/packages/d3/5c/e894ea047be360910320501b4283a2079bd84b1cc03e66bd6999862ee1cc/dearpygui-2.3.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:853e912c6d1d555711c002f0e1b7d3355b118fe56a42dda92b30e0dbd6528b76", size = 2502216, upload-time = "2026-05-01T22:46:15.506Z" }, + { url = "https://files.pythonhosted.org/packages/22/bb/86c6e8125c7d8b106624bf9c537abdd0f723ca94bb11c18bf25fb16487d8/dearpygui-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:36389e45dda45f91efb1d2d693b2c7198eb987db4799e6442862eba6e5b086bb", size = 1881760, upload-time = "2026-05-01T22:45:46.418Z" }, + { url = "https://files.pythonhosted.org/packages/6c/40/22c00a3b3e47c609ece39b0e1970b8e031ac613201fee065dd93c9cbcb6c/dearpygui-2.3.1-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:e1a9ffbb845600ab4a171daf2d7e7a1895f5b4433eed248d4cab42766b55fdff", size = 2002951, upload-time = "2026-05-01T22:45:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/31/7d/b2b26e75437a6173e44a89a816d5f1df76537ca2f4b3206a2a787ac9d479/dearpygui-2.3.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:686423b4f8a30aaf57f1044ec4e3b087beb9cc9b25be812cf92f4e7feff44fd6", size = 2678639, upload-time = "2026-05-01T22:46:06.246Z" }, + { url = "https://files.pythonhosted.org/packages/00/90/efaa3dbcdab267638b0b65551c88ea1e994235b74345c8d995b8a0a9d59e/dearpygui-2.3.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:0259a2db7b6c82459ac1402b0d42388a7cd180cd97d7dcb31a5210725c3171d3", size = 2501986, upload-time = "2026-05-01T22:46:17.044Z" }, + { url = "https://files.pythonhosted.org/packages/b4/db/8f15dfe06d104746591c4763a5bba31369b86d861aa916101da8f633bfc6/dearpygui-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:76ee38696a8f1f3805751b1588c98d9bb6567b033b91cb0f58111fb9fe907b5b", size = 1881816, upload-time = "2026-05-01T22:45:48.044Z" }, + { url = "https://files.pythonhosted.org/packages/20/f1/6f30c7eafa25755105d0c26049a95c89cc5c82b68971e1cc8ffea36b748a/dearpygui-2.3.1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:6b1efd675a358176b3b76c039587a543d407d8fbb2cc3643a527e0ab7c0f5577", size = 2002717, upload-time = "2026-05-01T22:46:00.242Z" }, + { url = "https://files.pythonhosted.org/packages/cc/03/fa76c31087b6ebbc13948de6d9d3bc9ed9946824b13d9d1089356b54249c/dearpygui-2.3.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:364b5b6a0953999fec764d529d9bb0b2cc6ab6ed06188e0ded28237c1475ca98", size = 2678552, upload-time = "2026-05-01T22:46:07.502Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ef/657ec68a79664cd28d98d590eed85103c6ffc1e23e7d3c4e5aedb7ee887d/dearpygui-2.3.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:199dffebda245c9f59aa7c10c3b7f62963237c5f744150ad9be409b27a2661d3", size = 2501224, upload-time = "2026-05-01T22:46:18.8Z" }, + { url = "https://files.pythonhosted.org/packages/77/be/e67e5a04439f15e60cf596092a31b2a907272288954e47f5ced43c0b1678/dearpygui-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:26ed9197cdb30099c665bd1b23724e0dba60ad06b71344ecca7e52748c21874b", size = 1882080, upload-time = "2026-05-01T22:45:49.366Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/56a293ad392b8dafdd3eb1a0c4d367efbde99aa6930359685fe270984dd1/dearpygui-2.3.1-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:d2d29e72b031886893b32269e1478eff1fd651512e6c2bceda91059c3bc8ee0e", size = 2002694, upload-time = "2026-05-01T22:46:01.425Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e0/df598671f40a7fffb8390946115d9e9cf12d3f4a52f5b9381bddc5699aee/dearpygui-2.3.1-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:e3f64c8d4cae68a3b5be1b7531d08400ccbf1e4ff03c7acfc85954f98993f864", size = 2678564, upload-time = "2026-05-01T22:46:08.706Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d1/87cfb577d4a1615e809e2f6e0fd720bd4ad6cd25e365ef2fd08a6e09c86e/dearpygui-2.3.1-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:0d8b7a5a04cd4a6e25dbf162aa0405cf12143a1c82b33e371c20e5ba8d9349f3", size = 2503196, upload-time = "2026-05-01T22:46:20.276Z" }, + { url = "https://files.pythonhosted.org/packages/b3/38/05f74181a59353bdc927b6ec46333714f633c5755f3d4bcda34d6eb100a5/dearpygui-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:7946722c50f1e08866dfdb78eb33dd4e67dea1a3cfe4400b159e1c96861116dc", size = 1882082, upload-time = "2026-05-01T22:45:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/4a/08/68ce3ba941cf09bcf288f2603d7178c936d473172fd545e0665b885cb840/dearpygui-2.3.1-cp314-cp314-macosx_13_0_arm64.whl", hash = "sha256:e2cbabd8d383308ea3520360e100d1e95c9a957cd26247431cd4920b23c3b56e", size = 2003034, upload-time = "2026-05-01T22:46:02.736Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3f/ac003b6d636c9f5aae910a62d00b6c119403212cf2d7772d7c59ca8ef6dd/dearpygui-2.3.1-cp314-cp314-manylinux1_x86_64.whl", hash = "sha256:953442c7272e57f3686e393edc5cf6ce73cc2f47142739735dfabed403e33770", size = 2678545, upload-time = "2026-05-01T22:46:10.181Z" }, + { url = "https://files.pythonhosted.org/packages/1a/6b/657a2f1e2f604f356c39ed5b870aaed5d07fdeac3139fae2a455473b64ca/dearpygui-2.3.1-cp314-cp314-manylinux2014_aarch64.whl", hash = "sha256:43a561b5dc589944a3a2b469e5e68f1ab35ae38b3dcdf1f813ed9d6e024153f8", size = 2501699, upload-time = "2026-05-01T22:46:21.497Z" }, + { url = "https://files.pythonhosted.org/packages/8c/56/93b2310891589063e0226cb07fe05e08fbdb3f409f80f9edefb1cebea84e/dearpygui-2.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:272cc52c66bc5a4a332f13a3af5e603070d47ca7cc4976e6a8c247594e2b3dd0", size = 1943067, upload-time = "2026-05-01T22:45:52.255Z" }, +] + [[package]] name = "deprecated" version = "1.3.1" @@ -371,6 +416,8 @@ name = "emioapi" version = "0.3.0" source = { editable = "." } dependencies = [ + { name = "cv2-enumerate-cameras" }, + { name = "dearpygui" }, { name = "dynamixelmotorsapi" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, @@ -389,6 +436,8 @@ dev = [ [package.metadata] requires-dist = [ + { name = "cv2-enumerate-cameras", specifier = ">=1.4.0" }, + { name = "dearpygui", specifier = ">=2.3.1" }, { name = "dynamixelmotorsapi", git = "https://github.com/SofaComplianceRobotics/DynamixelMotorsAPI?rev=main" }, { name = "numpy" }, { name = "opencv-python", specifier = ">=4.11.0.86" }, @@ -1199,6 +1248,130 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/5a/ce0b056d9a95fd0c06a6cfa5972477d79353392d19230c748a7ba5a9df04/pydoc_markdown-4.8.2-py3-none-any.whl", hash = "sha256:203f74119e6bb2f9deba43d452422de7c8ec31955b61e0620fa4dd8c2611715f", size = 67830, upload-time = "2023-06-26T12:36:59.502Z" }, ] +[[package]] +name = "pyobjc-core" +version = "12.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/78/abc4ce5920305780aeb36b4067a86253378b36e29ba96673a3deb02eb03a/pyobjc_core-12.2.2.tar.gz", hash = "sha256:3906452339cd06a3bb07df103c2511d4cb0f7a22d8771c0b802eba15d9a642b6", size = 1067701, upload-time = "2026-08-11T19:43:39.059Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/1d/baf7197cee12f32a8eb9f8633093da1ec1ea702b0e1346bc1c7bfe022673/pyobjc_core-12.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:56c6c39f1de059fcbb174ebca5525505fc8feaa89be2a28c329bf09b6b25ee75", size = 6483051, upload-time = "2026-08-11T13:55:45.262Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8e/18284fec7913ef78b25a1c97f9689ebef98bc14038386191491516abeb25/pyobjc_core-12.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b9cdd686e32db8e451feb19f8a85bc4cd52c2893103881d04aca51e1f35371d1", size = 6481574, upload-time = "2026-08-11T14:13:27.153Z" }, + { url = "https://files.pythonhosted.org/packages/86/b2/bbf7f049880ab40d110e66f25122342a1f6c98d6fe3c59bb98985503c660/pyobjc_core-12.2.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:122e6ad302a2abf5d4d4adb0156db751600ddf2768441696cba17b31323085e7", size = 6427637, upload-time = "2026-08-11T14:51:36.038Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ed/a8bf040caf3704023d74086b7fb96cf4ed2e844e24bd94e5248ba214b700/pyobjc_core-12.2.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:950bd2d9c74634398c4e3d24ef2f213d4e23d705083697464fa67afedc53c1ad", size = 6427113, upload-time = "2026-08-11T15:04:39.424Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5a/760f8b9e116edd43c57e33844dc17619158fbdd311250d4209910192d72d/pyobjc_core-12.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3772b406edb3ff78171530a17cda1c4a7817f87b87ded0d8715b3fa664df16db", size = 6666817, upload-time = "2026-08-11T19:30:17.01Z" }, + { url = "https://files.pythonhosted.org/packages/13/37/486d38a173b0b8dce973a3e13c74cf402ed1b8621586b5963bc9efd49a48/pyobjc_core-12.2.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2062e8ad30a310441cd022544a897553408bebeaa7820d5edba3c96fd7fd693b", size = 6421184, upload-time = "2026-08-11T19:30:21.081Z" }, + { url = "https://files.pythonhosted.org/packages/04/f1/d138fd9b9a66ea8db56a8138b77d3413b85da3defe13363a19f364f85529/pyobjc_core-12.2.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2c7ef3d2f865b4b3ebb14ec3556f7a3e8abb6d130c67275cd9daa08dbd6e4e4e", size = 6671824, upload-time = "2026-08-11T19:30:25.005Z" }, + { url = "https://files.pythonhosted.org/packages/d5/85/577e2265cccf59daf48c460f0a8deeaf7dbe2991227a8859ab1eeab4945e/pyobjc_core-12.2.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:89acc6bc13aaa6e3f52b0ce652ede7e201edb6bf062741b246b0c5a44582f25f", size = 6477694, upload-time = "2026-08-11T19:30:28.821Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/bd9f830c64c6f334530831e75c01bfe0a770a3fbb00fddc70329223118b3/pyobjc_core-12.2.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:59a77038ebe0ab1240f61c341e7fb67b8674f2b4cd41bc71a6472511a12b50f7", size = 6712233, upload-time = "2026-08-11T19:30:33.032Z" }, +] + +[[package]] +name = "pyobjc-framework-avfoundation" +version = "12.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/48/917494d86ea02372e922bc96ffb76a6161f847db21ae7885fe7e0c3d7755/pyobjc_framework_avfoundation-12.2.2.tar.gz", hash = "sha256:5b9f33bf382af9a84b2394c98e3603a8abf03f29b0d2036e29698806ea22851a", size = 411684, upload-time = "2026-08-11T19:43:51.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/0e/d8d75eef124963283756342ea6a6e5ef2c18329f00671c13cc8ff878fa93/pyobjc_framework_avfoundation-12.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5feb1265d7a259f92e75c4bef791e047f2d4aa6b7faa3fd46077f135947adaf", size = 85548, upload-time = "2026-08-11T19:31:35.418Z" }, + { url = "https://files.pythonhosted.org/packages/97/4f/9d53f29d06f57de1398b56a0e535a3c9e85ca3ecb83607b83cafce318df6/pyobjc_framework_avfoundation-12.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a1baf030b9c7997b99e38450e84ea7398c8ca9e762660ba9019c20837e733a0", size = 85543, upload-time = "2026-08-11T19:31:36.611Z" }, + { url = "https://files.pythonhosted.org/packages/33/8b/ea58df10fcf060b0a8a3fb56cbc0b06722b62cab6ef013b0ef346735761b/pyobjc_framework_avfoundation-12.2.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:248fd714f7a63535577cde3a5bd097f1e309011586b799c110fb9bab27e88f55", size = 85590, upload-time = "2026-08-11T19:31:37.654Z" }, + { url = "https://files.pythonhosted.org/packages/90/ef/ab3b86852704afd33de70f803c814af80a1d84b7dfafc51404d315d0921f/pyobjc_framework_avfoundation-12.2.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:73f34a9e7a5cc0ec6cc496ee10c133d14c4ddc768e17f09c684f95ac5b127408", size = 85625, upload-time = "2026-08-11T19:31:38.576Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c1/eda88125b7e4ae32dea2c831c5ab92705502355c80ce3d35e8eb8615d555/pyobjc_framework_avfoundation-12.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ca4976b18bee82142655b9dc1d6c0cd665627d9ef413694434e2ba5a187443f4", size = 86073, upload-time = "2026-08-11T19:31:39.533Z" }, + { url = "https://files.pythonhosted.org/packages/cf/61/a0969bfebcdddcaf1f8ccf721bf747e4e6ec803c742c6afd17faf0397f68/pyobjc_framework_avfoundation-12.2.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21a3c326455e5dce805e7440a3b7808c1e833e6d961279de88ab39cba70b821f", size = 85834, upload-time = "2026-08-11T19:31:40.455Z" }, + { url = "https://files.pythonhosted.org/packages/81/dc/14e479e8f6159d69d5fac942b2e0d0335529f43da0b9b0dac7a80662b46d/pyobjc_framework_avfoundation-12.2.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:45b915a35d7fddb6c29a7a43e8b221bd2f2fe73eb0b1c174e843cfa6cbba57ac", size = 86175, upload-time = "2026-08-11T19:31:41.338Z" }, + { url = "https://files.pythonhosted.org/packages/17/ec/ffb940584befa2d3a42e8169b1269ab492f77b40ed0183d37089f97626f6/pyobjc_framework_avfoundation-12.2.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:60199c8271f28dbf3f1b1f4fcaec3fbd7d6787571be95b0a291e70f1dbd13807", size = 85895, upload-time = "2026-08-11T19:31:42.161Z" }, + { url = "https://files.pythonhosted.org/packages/ea/64/7b789e97ce5675e43628166a2ff17bf39ef081e62a5725d51fbfc11c197a/pyobjc_framework_avfoundation-12.2.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:9f9394180a7891085f8981ba2fcb2da292ee0608b46fa53d231d8a646a6a6bb8", size = 86241, upload-time = "2026-08-11T19:31:43.258Z" }, +] + +[[package]] +name = "pyobjc-framework-cocoa" +version = "12.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/76/49c6da2c6a831020b4854ba20079d5a1030474bffc776b7b73c2eeff8c15/pyobjc_framework_cocoa-12.2.2.tar.gz", hash = "sha256:c96c0ef69a71afbbb0e6a7d594b455c5fe47d62e0db376ee7a2b4b828c16ace9", size = 3132831, upload-time = "2026-08-11T19:44:02.288Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/dd/aba439652cae293a736680ef5ed5cc29419adbbac1c6d4555b910741d516/pyobjc_framework_cocoa-12.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5a751c8033a3b51f7996f0327e0675eb44dcfdfe7920fae01e3d78b662723fff", size = 387296, upload-time = "2026-08-11T19:32:40.684Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a7/370f12143661dff66f2c68a735938afab6530aa3b153f6a7a6f12b5eabab/pyobjc_framework_cocoa-12.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:851dca4c16e70b405e5cd5a8c166cf7c445ae54a4cdd95ce9a523803172f32d1", size = 387300, upload-time = "2026-08-11T19:32:42.043Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2f/b67e73d8bc367e03fe7861cd9c49fff9dcfa6db83bc0630c0adcfb25b7fa/pyobjc_framework_cocoa-12.2.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e106f395531e67694376b0f1184612cbeea3ec8b9bf56b55ef41d026171d2a2d", size = 388117, upload-time = "2026-08-11T19:32:43.161Z" }, + { url = "https://files.pythonhosted.org/packages/db/e1/5d9b04ebb60042b9cb49adc2d33115e2f2c2e4ff7d548017bfaff8b7f536/pyobjc_framework_cocoa-12.2.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:600b1723184ca094931330e79355274949965460e23de38628d601b5a967baf9", size = 388181, upload-time = "2026-08-11T19:32:44.537Z" }, + { url = "https://files.pythonhosted.org/packages/b4/25/2a343357d5fe09bbe9c0e294dc03450866a0d6c1792fad36b6bcc00174c0/pyobjc_framework_cocoa-12.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:875f2aad73963faa81a6b36ae674fd494a4658d6d999e1075e0e2aca3d2391df", size = 392275, upload-time = "2026-08-11T19:32:45.631Z" }, + { url = "https://files.pythonhosted.org/packages/1f/1a/b99521999b9f54b89aad928ddff0faad507abfe33bc46599454bfa48a4b2/pyobjc_framework_cocoa-12.2.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:889d7bbd4ba2d4941078bfbbfb882138e51dbead27df006abfe0f2e0d49b5b2e", size = 388368, upload-time = "2026-08-11T19:32:46.781Z" }, + { url = "https://files.pythonhosted.org/packages/6d/26/0c697dbc73dcc76bc0f68ea5aeed25bf7b05217df5102659e878501b2d5f/pyobjc_framework_cocoa-12.2.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:de69c5933750f3a4599ed962eccd92b6a71914c7e4318dacc7895738a8ae60d7", size = 392404, upload-time = "2026-08-11T19:32:47.918Z" }, + { url = "https://files.pythonhosted.org/packages/df/82/502f740fd8f4e9ef741c9d40ba67467ab2c8196f2c09dcba12936d28a4fd/pyobjc_framework_cocoa-12.2.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:0e8ace0d44a00d281281a723d17fcd05eea7544a38a6a512e1fd018ddb7aece2", size = 388585, upload-time = "2026-08-11T19:32:49.171Z" }, + { url = "https://files.pythonhosted.org/packages/7d/3b/07ce3c0ab8d1e9e1bed74fea1bf1cce73527a365a7a23c755051d3be9865/pyobjc_framework_cocoa-12.2.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:8fe5b2e79c9530f667b4e58a87a3a15ea62f86a5d19eec405517ecbd4f454868", size = 392693, upload-time = "2026-08-11T19:32:50.283Z" }, +] + +[[package]] +name = "pyobjc-framework-coreaudio" +version = "12.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/71/bfbf1dffe9f08f31b444685e9d194e25cc3506db9f7a9d6ffdb56cc4408e/pyobjc_framework_coreaudio-12.2.2.tar.gz", hash = "sha256:880214f7328d6502a0c60ee744fa1eabe77a0d0c9ae276009453b69474eacf38", size = 79573, upload-time = "2026-08-11T19:44:07.04Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/d3/6adf297241bb976ea85a087501915cccc758c2030fbb71612c611f2dbf23/pyobjc_framework_coreaudio-12.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:950e16f8c1cc21a76ca879d820b49795959fc6099eb8a41ccd1376951f42941e", size = 34986, upload-time = "2026-08-11T19:33:09.921Z" }, + { url = "https://files.pythonhosted.org/packages/10/75/556b8f887a8b68c3125aa64e8281c26cce42d5e7338b90ef5474d8a519b1/pyobjc_framework_coreaudio-12.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2217911fb21cd6d8002d0895f6353a15fd6ce2c4e191b3f4cb6e16ad7a428591", size = 34980, upload-time = "2026-08-11T19:33:10.982Z" }, + { url = "https://files.pythonhosted.org/packages/f0/89/bd18309c59eaaf694253ca932844743d6429023b99260684181367ba0564/pyobjc_framework_coreaudio-12.2.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7222c2b2317a6a350e08239b3b7ba7b4570b06ec8697b9bc53793bd6f85e3134", size = 35242, upload-time = "2026-08-11T19:33:11.787Z" }, + { url = "https://files.pythonhosted.org/packages/ac/2a/d16c2f043c4e12274aa9c4444f9e00abc3fb2c5155a3b12b097eeffb6cdd/pyobjc_framework_coreaudio-12.2.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a19cc902a1eadb1b30d191d008e960768aaeeb7c027001820ef0cc01a4791422", size = 35271, upload-time = "2026-08-11T19:33:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/63/84/3c05b951ff16525bf32d6df1d4fe436df25ea93581ff257d4693205b4030/pyobjc_framework_coreaudio-12.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4cdc2be16ee481dea8ba1bad43c657bc02fcaeeb4a502d64e6bc28719b7cc103", size = 38026, upload-time = "2026-08-11T19:33:13.851Z" }, + { url = "https://files.pythonhosted.org/packages/7e/87/7bb04033a75abecc03642bcac7c23175e8c1d7a6c14be561a18378f23046/pyobjc_framework_coreaudio-12.2.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:4e332b0acb976e84b3730d90f1bce8c92f1e52009a07d35ee7d3f8d494055d8a", size = 36520, upload-time = "2026-08-11T19:33:14.656Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/36436a40e253dd8005bed9d6ffb31e5be1fd4acccacdfd8e36b1af1b4542/pyobjc_framework_coreaudio-12.2.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:29b6d2b0d415246348717e501b424da5ad4cfadc8bb3c465c16851446e6ff590", size = 38131, upload-time = "2026-08-11T19:33:15.462Z" }, + { url = "https://files.pythonhosted.org/packages/23/14/424a6b90658b5ac597986b6b8dcb2ae2f9452840a28149c400f458f33d5a/pyobjc_framework_coreaudio-12.2.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:97ef7efc7f5c5d66ecb114050f76f029803a203b953103859bdfe1d4756de50d", size = 36552, upload-time = "2026-08-11T19:33:16.339Z" }, + { url = "https://files.pythonhosted.org/packages/f5/a1/f276ed48473dd8d7f070517a5dbba40d689af3261127799b41ef17861464/pyobjc_framework_coreaudio-12.2.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:75bfc0de2b8f94521c9becb55d5dcf749b6ff479b6163cd1355b1f7c578166e4", size = 38162, upload-time = "2026-08-11T19:33:17.1Z" }, +] + +[[package]] +name = "pyobjc-framework-coremedia" +version = "12.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/12/0b3896ea04f0fbbe3f2cb37802cadce334f9aef204d911a81934013eba8c/pyobjc_framework_coremedia-12.2.2.tar.gz", hash = "sha256:fe9f972438674893e941e9db5b62f8ba99a33f94a7f1c2ce49b14416f154cd10", size = 98580, upload-time = "2026-08-11T19:44:11.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/7a/de17f374501561a30f50a9c194bfaf39607c717cad062057ba16b209e79e/pyobjc_framework_coremedia-12.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41142c919c557165df4a53ffe218e600ad0ccb7691bc9aed58a7839a88ba7c8b", size = 29520, upload-time = "2026-08-11T19:33:48.193Z" }, + { url = "https://files.pythonhosted.org/packages/38/18/5931478200f803db87be5cd6cb9af492d9ef2f2ac07cbfe019ecfa97db5a/pyobjc_framework_coremedia-12.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f6f8150428e6ae40d6b97714f6933b8c4322f3fb94de9bfcb206d00cb021f464", size = 29518, upload-time = "2026-08-11T19:33:49.074Z" }, + { url = "https://files.pythonhosted.org/packages/cd/26/de907d6ead970916b39787e52bed679945328161f2abac533515b2ac8934/pyobjc_framework_coremedia-12.2.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:84f5deee8d366bf73069a0525509637aa578ccd0e6688923b09999a8bc3077a8", size = 29421, upload-time = "2026-08-11T19:33:49.848Z" }, + { url = "https://files.pythonhosted.org/packages/36/65/6fe4c97d3442abda385f8798becd5bc82b73b4dfd979194e5850c5246dea/pyobjc_framework_coremedia-12.2.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c4430bda640c5427bf572f8b2a3281d8f4f16b962a499251cf553a0979d88a4e", size = 29431, upload-time = "2026-08-11T19:33:50.621Z" }, + { url = "https://files.pythonhosted.org/packages/5f/32/3c023f6b26fffb8c7d07cd6327e892bd546da7cfa8b3bbdaed113f5a59d3/pyobjc_framework_coremedia-12.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b80285a65465a5340f5806e9e293b46e22ac6f9c3132e79c8139a79b53b3f8c2", size = 29496, upload-time = "2026-08-11T19:33:51.396Z" }, + { url = "https://files.pythonhosted.org/packages/74/bb/0676dabebfbf9a89ea268ca26bd978a6c0e5dcf9b2baca86709bd8228708/pyobjc_framework_coremedia-12.2.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:edc9d03e5230123c92d4f0413c48449a7f08fa747b9f304b2cc396239acfefe3", size = 29465, upload-time = "2026-08-11T19:33:52.149Z" }, + { url = "https://files.pythonhosted.org/packages/17/33/1e2ab0438a546cc33ac18dcdb939c0a7cbbfcc070744f6451d08ccd80d6b/pyobjc_framework_coremedia-12.2.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:bb4c56edd411a5c0152a0a9eeb0138f9c173820041beff0cfaff291b9376bade", size = 29525, upload-time = "2026-08-11T19:33:52.943Z" }, + { url = "https://files.pythonhosted.org/packages/99/7f/1a5e92a337e924f4cf3781f61b30a0e9fba84eb6fbf2c377959bb842d1e8/pyobjc_framework_coremedia-12.2.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:49b53bbe1eea893f94bbf05f1f5220fcef3ccac7310a96d78434c0df6318dc79", size = 29503, upload-time = "2026-08-11T19:33:53.724Z" }, + { url = "https://files.pythonhosted.org/packages/db/f6/09e85dc8bffbafdcf74c53d8495db1411f9cc03b32a10593832f4fa81318/pyobjc_framework_coremedia-12.2.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:0bf4cd65f87bd073e74b3ede2381dc9355529e6f1b12184da22090f016e68bf7", size = 29560, upload-time = "2026-08-11T19:33:54.712Z" }, +] + +[[package]] +name = "pyobjc-framework-quartz" +version = "12.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/b1/426a37c7ae37280b3ffca2571fb48f211946aee2f4ca31a603ed1943c4a7/pyobjc_framework_quartz-12.2.2.tar.gz", hash = "sha256:810f97b210cfd93704d240860286dfd6df09f9f1c52525fc5c2166723aea3f9e", size = 3218295, upload-time = "2026-08-11T19:45:15.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/94/2c9839958b0a2a040e22cca145040a8fc2612f099eda7524900d741f560d/pyobjc_framework_quartz-12.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d89a5f47c079b5c340d2b1cbb83eb6c4c92d4bb17cd4daf7d8c02c91a49f5399", size = 218007, upload-time = "2026-08-11T19:40:22.077Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e8/16d07170d4e1bd182a8e6084abdf283fac90979384b8f53107be9eb110e6/pyobjc_framework_quartz-12.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4b01e325b0cdc121e78730dde9756e971b23069bf141cd62efbcaac76d7b6dbb", size = 218008, upload-time = "2026-08-11T19:40:23.36Z" }, + { url = "https://files.pythonhosted.org/packages/ed/e4/8be95d2ff850f82fb55b44c63333a00a920bf8a73642e7d9c2f3638a26d2/pyobjc_framework_quartz-12.2.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7f668979d0c7320bf8f7ed6e030da578f93ab0f5dd619b295ec735cd8d5faa34", size = 219003, upload-time = "2026-08-11T19:40:24.425Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ae/b515852dbe491171f2f2e2eb7739588a5eb7f36720a739545337b8c0d706/pyobjc_framework_quartz-12.2.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0ec9751904ef975bf0789d760dc4fadcb400edc4ffe4a736eb54971968babe5c", size = 219404, upload-time = "2026-08-11T19:40:25.461Z" }, + { url = "https://files.pythonhosted.org/packages/ff/5f/c7ee66f4483385396d91f65036c62f3dd20bccaa07354643ef17b259aa75/pyobjc_framework_quartz-12.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:63f6f0f3233dcf650aac1374781e78961b0b17b33e3351953bacf8bd0c430593", size = 224462, upload-time = "2026-08-11T19:40:26.647Z" }, + { url = "https://files.pythonhosted.org/packages/b1/33/230ae7777b0909fe2c24f28413c51c860faebf762d824b097a0e2fb48304/pyobjc_framework_quartz-12.2.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:1f7f3d9010e38f03ea1fa266664c10ea349cd7492bd603b403584f49d713dbed", size = 219773, upload-time = "2026-08-11T19:40:27.64Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/7482fdd384521916e98a6164be220494b1f7794792b1d60fa3f5207d84a4/pyobjc_framework_quartz-12.2.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ebf8167ca2096cf3a05199decfa517be0df4c56048f49cf132bd6b1a6ab9c086", size = 224726, upload-time = "2026-08-11T19:40:28.718Z" }, + { url = "https://files.pythonhosted.org/packages/33/67/b4b0ffc486b08492bfef4b33731d044b295f1696c8fb7d119c8367079139/pyobjc_framework_quartz-12.2.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:cee63b891c2b6b7ccf98f233175411529f3e80286f58438793b3634af79858f1", size = 219827, upload-time = "2026-08-11T19:40:29.764Z" }, + { url = "https://files.pythonhosted.org/packages/aa/eb/5fb627c2457046883c6fd12d25c44db40c12bcde4622dc0f30851108e106/pyobjc_framework_quartz-12.2.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:8f58c589b5a76ba98f186b1f3b19fb1c8b730e82351f81fb62e6194f64a71622", size = 224767, upload-time = "2026-08-11T19:40:30.991Z" }, +] + [[package]] name = "pyparsing" version = "3.3.2"