From ff1f6ccecdcbf8ec7f34117ede8577d00db1af34 Mon Sep 17 00:00:00 2001 From: HanaeRateau Date: Thu, 1 Oct 2026 14:14:42 +0200 Subject: [PATCH 1/7] First version --- emioapi/_camerafeedwindow.py | 190 ++++++++++++++++++++++++++--------- emioapi/_depthcamera.py | 154 +++++++++++----------------- emioapi/_logging_config.py | 23 +++-- pyproject.toml | 2 + uv.lock | 173 +++++++++++++++++++++++++++++++ 5 files changed, 385 insertions(+), 157 deletions(-) diff --git a/emioapi/_camerafeedwindow.py b/emioapi/_camerafeedwindow.py index c29953f..91177d9 100644 --- a/emioapi/_camerafeedwindow.py +++ b/emioapi/_camerafeedwindow.py @@ -1,8 +1,12 @@ -import tkinter as tk -from tkinter import ttk +from typing import Final, Tuple +from types import SimpleNamespace +import dearpygui.dearpygui as dpg import cv2 as cv -from PIL import ImageTk, Image +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. @@ -11,58 +15,144 @@ 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(): +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 - 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() + + @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(trackbarParams=trackbarParams, save_callback=save_callback) + logger.info(f"Starting Emio API GUI inside a thread.") + result.gui_handle.run_loop() + + result = SimpleNamespace(gui_handle=None, gui_thread=None) + result.gui_thread = Thread(target=_gui_worker, daemon=True, args=(result, trackbarParams, save_callback)) + result.gui_thread.start() + startime = time() + while not result.gui_handle: + if time()-startime < EmioAPIGUI._TIMEOUT_THREAD: + logger.info(f"Waiting for thread {result.gui_thread} to start") + sleep(0.5) + else: + raise TimeoutError("Could not open EmioAPIGUI thread in time.") + logger.info(f"Started Emio API GUI inside a thread {result.gui_thread}.") + return result + + + def __init__(self, trackbarParams={}, save_callback=None): + import numpy as np + + self.window_id = str(EmioAPIGUI.windowCount) 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.save_callback = save_callback + + try: + if EmioAPIGUI.windowCount==0: + dpg.create_context() + dpg.create_viewport(title="Camera Feed", width=1150, height=700, resizable=False) + dpg.setup_dearpygui() + + self.context_exists = True + self.images_scale = 0.6 + + # 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.window(tag=EmioAPIGUI.TAG_MAINWINDOW+self.window_id, no_title_bar=True): + with dpg.child_window(width=300): + # params + 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.window(label="RGB Frame", autosize=True, pos=origin, no_move=True, no_close=True): + dpg.add_image(EmioAPIGUI.TAG_RGBTEX+self.window_id, width=width, height=height, label="RGB") + with dpg.window(label="Depth Frame", autosize=True, pos=(origin[0]+width+16, origin[1]), no_close=True, no_move=True): + dpg.add_image(EmioAPIGUI.TAG_DEPTHTEX+self.window_id, width=width, height=height) + with dpg.window(label="Mask Frame", autosize=True, pos=(origin[0], origin[1]+height+35), no_close=True, no_move=True): + dpg.add_image(EmioAPIGUI.TAG_MASKTEX+self.window_id, width=width, height=height) + with dpg.window(label="HSV Frame", autosize=True, pos=(origin[0]+width+16, origin[1]+height+35), no_close=True, no_move=True): + dpg.add_image(EmioAPIGUI.TAG_HSVTEX+self.window_id, width=width, height=height) + + dpg.show_viewport() + dpg.show_item_registry() + dpg.set_primary_window(EmioAPIGUI.TAG_MAINWINDOW+self.window_id, True) + + EmioAPIGUI.windowCount=+1 + + except Exception as e: + print(e) + raise(e) + + + + @property + def image_size(self) -> Tuple[int, int]: + return (self.images_scale * self.TEXWIDTH, self.images_scale*self.TEXHEIGHT) + + + def run_loop(self): + while dpg.is_dearpygui_running(): + dpg.render_dearpygui_frame() + + if EmioAPIGUI.windowCount == 0: + dpg.destroy_context() + self.context_exists = False + + + 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) + + + def is_running(self): + return dpg.is_dearpygui_running() if self.context_exists else False + + + 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 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 + + def save_params(self): + if self.save_callback is None: + logger.warning("Parameters not saved. No callback given at init.") + else: + self.save_callback() \ No newline at end of file diff --git a/emioapi/_depthcamera.py b/emioapi/_depthcamera.py index 693b928..76814bb 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 ._camerafeedwindow 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,8 @@ 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 = EmioAPIGUI.start_in_thread(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 +234,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 +269,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 +286,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/_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/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" From 8994f95c9c8ff01370b0e9ca05d25e1468cafce3 Mon Sep 17 00:00:00 2001 From: HanaeRateau Date: Thu, 1 Oct 2026 17:15:47 +0200 Subject: [PATCH 2/7] Make GUI compatible with mutilple cameras in same process --- emioapi/_camerafeedwindow.py | 199 +++++++++++++++++++---------------- emioapi/_depthcamera.py | 3 +- 2 files changed, 108 insertions(+), 94 deletions(-) diff --git a/emioapi/_camerafeedwindow.py b/emioapi/_camerafeedwindow.py index 91177d9..8aa33ea 100644 --- a/emioapi/_camerafeedwindow.py +++ b/emioapi/_camerafeedwindow.py @@ -28,6 +28,8 @@ class EmioAPIGUI(): _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): @@ -35,90 +37,44 @@ def start_in_thread(trackbarParams={}, save_callback=None): from time import time, sleep def _gui_worker(result: SimpleNamespace, trackbarParams={}, save_callback=None): - result.gui_handle = EmioAPIGUI(trackbarParams=trackbarParams, save_callback=save_callback) - logger.info(f"Starting Emio API GUI inside a thread.") + result.gui_handle = EmioAPIGUI() + logger.debug(f"Starting Emio API GUI inside a thread.") result.gui_handle.run_loop() - result = SimpleNamespace(gui_handle=None, gui_thread=None) - result.gui_thread = Thread(target=_gui_worker, daemon=True, args=(result, trackbarParams, save_callback)) - result.gui_thread.start() - startime = time() - while not result.gui_handle: - if time()-startime < EmioAPIGUI._TIMEOUT_THREAD: - logger.info(f"Waiting for thread {result.gui_thread} to start") - sleep(0.5) - else: - raise TimeoutError("Could not open EmioAPIGUI thread in time.") - logger.info(f"Started Emio API GUI inside a thread {result.gui_thread}.") - return result - - - def __init__(self, trackbarParams={}, save_callback=None): - import numpy as np - - self.window_id = str(EmioAPIGUI.windowCount) + 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.") - self.default_param = trackbarParams.copy() - self.trackbarParams = trackbarParams - self.trackbars = {} + return EmioAPIGUI.gui - self.save_callback = save_callback + @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=700, resizable=False) + dpg.create_viewport(title="Camera Feed", width=1150, height=780, resizable=False) dpg.setup_dearpygui() - - self.context_exists = True - self.images_scale = 0.6 - - # 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.window(tag=EmioAPIGUI.TAG_MAINWINDOW+self.window_id, no_title_bar=True): - with dpg.child_window(width=300): - # params - 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.window(label="RGB Frame", autosize=True, pos=origin, no_move=True, no_close=True): - dpg.add_image(EmioAPIGUI.TAG_RGBTEX+self.window_id, width=width, height=height, label="RGB") - with dpg.window(label="Depth Frame", autosize=True, pos=(origin[0]+width+16, origin[1]), no_close=True, no_move=True): - dpg.add_image(EmioAPIGUI.TAG_DEPTHTEX+self.window_id, width=width, height=height) - with dpg.window(label="Mask Frame", autosize=True, pos=(origin[0], origin[1]+height+35), no_close=True, no_move=True): - dpg.add_image(EmioAPIGUI.TAG_MASKTEX+self.window_id, width=width, height=height) - with dpg.window(label="HSV Frame", autosize=True, pos=(origin[0]+width+16, origin[1]+height+35), no_close=True, no_move=True): - dpg.add_image(EmioAPIGUI.TAG_HSVTEX+self.window_id, width=width, height=height) - - dpg.show_viewport() - dpg.show_item_registry() - dpg.set_primary_window(EmioAPIGUI.TAG_MAINWINDOW+self.window_id, True) - - EmioAPIGUI.windowCount=+1 - + 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) except Exception as e: - print(e) + logger.error(str(e)) raise(e) - - - - @property - def image_size(self) -> Tuple[int, int]: - return (self.images_scale * self.TEXWIDTH, self.images_scale*self.TEXHEIGHT) def run_loop(self): @@ -130,29 +86,86 @@ def run_loop(self): self.context_exists = False - 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) - - def is_running(self): return dpg.is_dearpygui_running() if self.context_exists else False - 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] + 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 = {} - def save_params(self): - if self.save_callback is None: - logger.warning("Parameters not saved. No callback given at init.") - else: - self.save_callback() \ No newline at end of file + 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/_depthcamera.py b/emioapi/_depthcamera.py index 76814bb..8aa7eb5 100644 --- a/emioapi/_depthcamera.py +++ b/emioapi/_depthcamera.py @@ -173,7 +173,8 @@ def set_depth_min(self, new_depth_min: int): def create_feed_windows(self): - self.gui = EmioAPIGUI.start_in_thread(trackbarParams=self.parameter, save_callback=lambda: json.dump(self.parameter, open(CONFIG_FILENAME, 'w'))) + 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 quit(self): From 89f3a936fcc607d4291b5cadc41528750dd8aaa2 Mon Sep 17 00:00:00 2001 From: HanaeRateau Date: Thu, 1 Oct 2026 17:17:06 +0200 Subject: [PATCH 3/7] Renames gui file --- emioapi/_depthcamera.py | 2 +- emioapi/{_camerafeedwindow.py => _gui.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename emioapi/{_camerafeedwindow.py => _gui.py} (100%) diff --git a/emioapi/_depthcamera.py b/emioapi/_depthcamera.py index 8aa7eb5..b009a1d 100644 --- a/emioapi/_depthcamera.py +++ b/emioapi/_depthcamera.py @@ -8,7 +8,7 @@ import cv2 as cv import pyrealsense2 as rs -from ._camerafeedwindow import EmioAPIGUI +from ._gui import EmioAPIGUI from ._positionestimation import PositionEstimation, CONFIG_FILENAME from emioapi._logging_config import logger diff --git a/emioapi/_camerafeedwindow.py b/emioapi/_gui.py similarity index 100% rename from emioapi/_camerafeedwindow.py rename to emioapi/_gui.py From af545478b5a516510a6480b1e2138f3e7c9e5eec Mon Sep 17 00:00:00 2001 From: HanaeRateau Date: Thu, 1 Oct 2026 17:18:09 +0200 Subject: [PATCH 4/7] Adds file for test, To delete when merging --- to_delete.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 to_delete.py diff --git a/to_delete.py b/to_delete.py new file mode 100644 index 0000000..6986162 --- /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) + if ret1: + # blank_data = 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 and gui.gui_handle.is_running(): + update_camera() + +print("out of while") + +cap.release() From 7aa8661454df89b80dbd9fa0464dc488af543f43 Mon Sep 17 00:00:00 2001 From: HanaeRateau Date: Thu, 1 Oct 2026 17:28:16 +0200 Subject: [PATCH 5/7] Fixes context variable --- emioapi/_gui.py | 1 + to_delete.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/emioapi/_gui.py b/emioapi/_gui.py index 8aa33ea..e19f92e 100644 --- a/emioapi/_gui.py +++ b/emioapi/_gui.py @@ -72,6 +72,7 @@ def __init__(self): 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) diff --git a/to_delete.py b/to_delete.py index 6986162..80f8d29 100644 --- a/to_delete.py +++ b/to_delete.py @@ -64,7 +64,7 @@ def update_camera(): 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 and gui.gui_handle.is_running(): +while running : update_camera() print("out of while") From 79badc34388fe6d14a6f687e44a6b6434fa6b842 Mon Sep 17 00:00:00 2001 From: HanaeRateau Date: Thu, 1 Oct 2026 17:48:12 +0200 Subject: [PATCH 6/7] Adds log when deatroying GUI --- emioapi/_gui.py | 9 +++++---- to_delete.py | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/emioapi/_gui.py b/emioapi/_gui.py index e19f92e..7655b80 100644 --- a/emioapi/_gui.py +++ b/emioapi/_gui.py @@ -80,11 +80,12 @@ def __init__(self): def run_loop(self): while dpg.is_dearpygui_running(): - dpg.render_dearpygui_frame() + dpg.render_dearpygui_frame() - if EmioAPIGUI.windowCount == 0: - dpg.destroy_context() - self.context_exists = False + 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): diff --git a/to_delete.py b/to_delete.py index 80f8d29..3fcf5b3 100644 --- a/to_delete.py +++ b/to_delete.py @@ -31,11 +31,11 @@ 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() +# 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 @@ -53,19 +53,19 @@ def update_camera(): global emiogui, gui import numpy as np ret, frame = cap.read() - ret1, frame1 = cap1.read() + # ret1, frame1 = cap1.read() if ret: emiogui.set_frames(frame, frame, frame, frame) - if ret1: - # blank_data = 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) + 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 : - update_camera() + if gui.gui_handle.is_running(): + update_camera() print("out of while") From 840e2c6eb2522af0a8698615d39ed9a52adc724f Mon Sep 17 00:00:00 2001 From: HanaeRateau Date: Wed, 4 Nov 2026 18:33:21 +0100 Subject: [PATCH 7/7] Fixes calibration --- emioapi/__main__.py | 2 +- emioapi/_depthcamera.py | 22 +++++++------- emioapi/_gui.py | 52 ++++++++++++++++++++++++---------- emioapi/_positionestimation.py | 4 +-- examples/camera_example.py | 42 +++++++++++++-------------- pyproject.toml | 2 +- 6 files changed, 73 insertions(+), 51 deletions(-) diff --git a/emioapi/__main__.py b/emioapi/__main__.py index 1a3d874..16c8a97 100644 --- a/emioapi/__main__.py +++ b/emioapi/__main__.py @@ -32,8 +32,8 @@ def calibrate(): import emioapi from emioapi._logging_config import logger - camera = emioapi.EmioCamera(show=True) print("Available cameras:", emioapi.EmioCamera.listCameras()) + camera = emioapi.EmioCamera(show=True, track_markers=True) if camera.open(): print(f"Camera {camera.camera_serial} opened.") diff --git a/emioapi/_depthcamera.py b/emioapi/_depthcamera.py index b009a1d..dbaf5b5 100644 --- a/emioapi/_depthcamera.py +++ b/emioapi/_depthcamera.py @@ -121,8 +121,8 @@ 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.gui_object: SimpleNamespace = None + """`gui_object`: holds references to the EmioAPIGUI instance itself `gui_handle` and to the running thread of the gui `gui_thread`""" self.lock = threading.Lock() if not self.initialized: @@ -173,8 +173,12 @@ def set_depth_min(self, new_depth_min: int): def create_feed_windows(self): + """ + Creates the GUI thread and the camera feed window. The GUI thread is created only once and the camera feed window is created in the GUI thread. The camera feed window is updated in the main thread.""" + 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'))) + """ EmioAPIGUI._CameraFeedWindow object. """ def quit(self): @@ -234,8 +238,9 @@ def calibrate(self): success = False self.calibration_status = CalibrationStatusEnum.CALIBRATING - # Create the windows to display the binrary mask and the HSV frame - calibration_window = EmioAPIGUI(rootWindow=self.rootWindow, name='Calibration') + # Create the windows to display the binary mask and the HSV frame + if self.show_video_feed and self.gui_window: + calibration_window = self.gui_window if self.position_estimator is not None: while self.position_estimator.count_calibration_frames < 200 and time.time() - starttime < 300: @@ -244,16 +249,11 @@ def calibrate(self): if hasFrame: success = self.position_estimator.calibrate(self.frame, self.depth_frame, first, calibration_window) first = success if not first else first - if self.show_video_feed: - self.rootWindow.update() if success: self.position_estimator.compute_camera_to_simulation_transform() logger.info(f"Camera {self.camera_serial} successfully calibrated.") - # Close the calibration window - calibration_window.closed() - self.calibration_status = CalibrationStatusEnum.CALIBRATED if success else CalibrationStatusEnum.NOT_CALIBRATED return success @@ -341,9 +341,9 @@ def process_frame(self): v = points.get_vertices() self.point_cloud = np.asanyarray(v).view(np.float32).reshape(-1, 3) # xyz - if self.show_video_feed and self.gui.gui_handle.is_running(): + if self.show_video_feed and self.gui_window and self.gui_object.gui_handle.is_running(): colorized = np.asanyarray(rs.colorizer().colorize(self.depth_rsframe).get_data()) - self.gui.gui_handle.set_frames(self.frame, colorized, self.maskFrame, self.hsvFrame) + self.gui_window.set_frames(self.frame, colorized, self.maskFrame, self.hsvFrame) def close(self): diff --git a/emioapi/_gui.py b/emioapi/_gui.py index 7655b80..62e2d85 100644 --- a/emioapi/_gui.py +++ b/emioapi/_gui.py @@ -8,14 +8,16 @@ 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(): + """ + This class is used to create a GUI for the Emio API. + The GUI is created in a separate thread and the main thread is used to update the camera frames. + It uses DearPyGui to create a window that displays the camera feed and allows the user to adjust parameters using trackbars. + The camera feed is displayed in tabs, and the user can switch between different camera feeds if multiple cameras are connected. + You need to feed the camera frames to the GUI using the `set_frames` method of the `_CameraFeedWindow` class returned by the `add_camera_feed` method of this class. + + Note: Only one instance of this class can be created per process. This is because DearPyGui does not support multiple contexts in the same process. If you need to create multiple camera feed windows, you can use the `add_camera_feed` method of this class to create additional windows in the same context. + """ TAG_MAINWINDOW:Final[str] = "MainWindow" TAG_DEPTHTEX:Final[str] = "tex_depth" @@ -56,11 +58,12 @@ def _gui_worker(result: SimpleNamespace, trackbarParams={}, save_callback=None): 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: @@ -79,7 +82,7 @@ def __init__(self): def run_loop(self): - while dpg.is_dearpygui_running(): + while self.is_running(): dpg.render_dearpygui_frame() logger.debug("Closing GUI...") @@ -93,9 +96,17 @@ def is_running(self): class _CameraFeedWindow(): - def __init__(self, trackbarParams={}, save_callback=None): + """ + This class is used to create a camera feed window that displays the video feed from a camera + It displays four frames: RGB, Depth, Mask and HSV. + The class also creates trackbars for the parameters passed in the trackbarParams dictionary.""" + + def __init__(self, trackbarParams={}, save_callback=None, label=""): self.window_id = str(EmioAPIGUI.windowCount) - logger.debug(f"Creating CameraFeedWindow {self.window_id}") + self.window_label = label if label else f"Window {self.window_id}" + self.window_tag = f"{self.window_label}_{self.window_id}" + + logger.debug(f"Creating CameraFeedWindow {self.window_label} with tag {self.window_tag}") self.default_param = trackbarParams.copy() self.trackbarParams = trackbarParams @@ -105,6 +116,7 @@ def __init__(self, trackbarParams={}, save_callback=None): 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) @@ -113,15 +125,15 @@ def __init__(self, trackbarParams={}, save_callback=None): 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"): + # Tab window + with dpg.tab(label=self.window_label, 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) + dpg.add_slider_int(tag=f"TAG_SLIDER{self.window_id}_{key}",min_value=0, max_value=255, default_value=val, callback=self.on_change) 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) @@ -162,6 +174,12 @@ def save_params(self): else: self.save_callback() + + def on_change(self, sender_data, app_data): + for key, _ in self.trackbarParams.items(): + self.trackbarParams[key] = int(dpg.get_value(f"TAG_SLIDER{self.window_id}_{key}")) + + 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) @@ -170,4 +188,8 @@ def set_frames(self, rgb, depth, mask, hsv): 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 + dpg.set_value(EmioAPIGUI.TAG_HSVTEX+self.window_id, np.ascontiguousarray(cv.cvtColor(hsv, cv.COLOR_BGR2RGBA), dtype=np.float32)/255) + + + def close(self): + dpg.delete_item(self.window_tag) \ No newline at end of file diff --git a/emioapi/_positionestimation.py b/emioapi/_positionestimation.py index 28bf8c0..314c7b1 100644 --- a/emioapi/_positionestimation.py +++ b/emioapi/_positionestimation.py @@ -337,10 +337,10 @@ def calibrate(self, frame, depth_image, aggregate, window=None)-> bool: (int(corners[0][0][i][0]), int(corners[0][0][i][1])), cv.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) for i in range(len(corners[0][0]))] frame = cv.putText(frame, f"Calibration progress: {self.count_calibration_frames}/200", (10, 30), - cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1) + cv.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 0), 1) if window: - window.set_frame(frame) + window.set_frames(frame, depth_image, None, None) return True diff --git a/examples/camera_example.py b/examples/camera_example.py index 4c63510..addebac 100644 --- a/examples/camera_example.py +++ b/examples/camera_example.py @@ -10,25 +10,25 @@ from emioapi._logging_config import logger -def main(emio: EmioCamera): +def main(camera: EmioCamera): # emio.calibrate() # calibrate the camera if needed - while emio.is_running: + while camera.is_running: try: - emio.update() # update the camera frame and trackers + camera.update() # update the camera frame and trackers print("-"*20) - logger.info(f"Camera parameters: {emio.parameters}") - logger.info(f"Camera show: {emio.show_frames}") - logger.info(f"Camera tracking: {emio.track_markers}") - logger.info(f"Camera compute point cloud: {emio.compute_point_cloud}") - logger.info(f"Camera is running: {emio.is_running}") - logger.info(f"Count tracker: {len(emio.trackers_pos)}") - logger.info(f"Trackers positions: {emio.trackers_pos}") - logger.info(f"Point cloud shape: {emio.point_cloud.shape}") - logger.info(f"HSV Frame shape: {emio.hsv_frame.shape}") - logger.info(f"Mask Frame shape: {emio.mask_frame.shape}") + logger.info(f"Camera parameters: {camera.parameters}") + logger.info(f"Camera show: {camera.show_frames}") + logger.info(f"Camera tracking: {camera.track_markers}") + logger.info(f"Camera compute point cloud: {camera.compute_point_cloud}") + logger.info(f"Camera is running: {camera.is_running}") + logger.info(f"Count tracker: {len(camera.trackers_pos)}") + logger.info(f"Trackers positions: {camera.trackers_pos}") + logger.info(f"Point cloud shape: {camera.point_cloud.shape}") + logger.info(f"HSV Frame shape: {camera.hsv_frame.shape}") + logger.info(f"Mask Frame shape: {camera.mask_frame.shape}") except KeyboardInterrupt: logger.info("Keyboard interrupt received.") @@ -46,21 +46,21 @@ def main(emio: EmioCamera): logger.info("Opening and configuring EMIO Camera...") - emio = EmioCamera(show=True, track_markers=True, compute_point_cloud=True) - emio.fps = 30 # sets the fps to 30. Default is 60 and can only be one of 30. 60 or 90fps - emio.depth_max = 600 # sets the maximum depth to 600mm. Default is 430mm - emio.depth_min = 0 # sets the minimum depth to 0mm. Default is 2mm + emiocamera = EmioCamera(show=True, track_markers=True, compute_point_cloud=True) + emiocamera.fps = 30 # sets the fps to 30. Default is 60 and can only be one of 30. 60 or 90fps + emiocamera.depth_max = 600 # sets the maximum depth to 600mm. Default is 430mm + emiocamera.depth_min = 0 # sets the minimum depth to 0mm. Default is 2mm - if emio.open(): # This will open the first available Realsense camera + if emiocamera.open(): # This will open the first available Realsense camera - logger.info(f"Emio camera {emio.camera_serial} opened.") + logger.info(f"Emio camera {emiocamera.camera_serial} opened.") logger.info("Running main function...") - main(emio) + main(emiocamera) logger.info("Main function completed.") logger.info("Closing Emio API...") - emio.close() + emiocamera.close() logger.info("EMIO API closed.") except Exception as e: diff --git a/pyproject.toml b/pyproject.toml index 0e01d64..5022dd4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ dependencies = [ "pyrealsense2-macosx; platform_system == 'Darwin'", "pillow>=12.3.0", "opencv-python>=4.11.0.86", - "dynamixelmotorsapi", + "dynamixelmotorsapi @ git+https://github.com/SofaComplianceRobotics/DynamixelMotorsAPI@main", "numpy", "dearpygui>=2.3.1", "cv2-enumerate-cameras>=1.4.0",