diff --git a/config/machine_settings/defaults.ini b/config/machine_settings/defaults.ini index 673f63b..35b065f 100644 --- a/config/machine_settings/defaults.ini +++ b/config/machine_settings/defaults.ini @@ -1,6 +1,7 @@ [hardware_config] ; archive_synchronizer: present ; galaxy_camera: present +; uvc_camera: absent ; label_printer: present ; multicomp: present ; neo_pixels: present @@ -25,6 +26,12 @@ balance_red: 1.85 balance_green: 1.0 balance_blue: 1.5 +[uvc_camera] +horizontal_resolution: 2448 +vertical_resolution: 2048 +exposure_time: 10000 +focus: 100 + [vision_saver] time_format: %Y-%m-%d_%H-%M-%S path: ./data/images diff --git a/config/machine_settings/hostnames.ini b/config/machine_settings/hostnames.ini index 6ca9b6d..9ffbc45 100644 --- a/config/machine_settings/hostnames.ini +++ b/config/machine_settings/hostnames.ini @@ -1,7 +1,7 @@ [hostnames] DESKTOP-VOCOB38: vm mino: mino -neodl-MS-7A62: stten3 +neodl-MS-7A62: stten1 ST-TEN-1: stten1 ST-TEN-2: stten2 stten3: stten3 diff --git a/config/machine_settings/stten1.ini b/config/machine_settings/stten1.ini index 3a4564a..8b44655 100644 --- a/config/machine_settings/stten1.ini +++ b/config/machine_settings/stten1.ini @@ -1,6 +1,7 @@ [hardware_config] archive_synchronizer: absent -galaxy_camera: present +#galaxy_camera: present +uvc_camera: present label_printer: present neo_pixels: present remote_api: absent diff --git a/src/components/neo-ringlight/neo-ringlight.ino b/src/components/neo-ringlight/neo-ringlight.ino index eca4221..41d769d 100644 --- a/src/components/neo-ringlight/neo-ringlight.ino +++ b/src/components/neo-ringlight/neo-ringlight.ino @@ -58,6 +58,11 @@ void setup() { set_color(-1, 0, 0, 0); delay(1000); set_color(-1, 255, 255, 255); + delay(2000); + for(int cnt=255;cnt>=0;cnt--){ + set_color(-1, cnt, cnt,cnt); + delay(20); + } } uint8_t parse_message(uint8_t *message, Address *address, Color color) { diff --git a/src/components/uvc_camera.py b/src/components/uvc_camera.py new file mode 100644 index 0000000..cfc04a1 --- /dev/null +++ b/src/components/uvc_camera.py @@ -0,0 +1,213 @@ +import pathlib +import sys +from itertools import cycle + +import cv2 +import imutils +import numpy as np +from PyQt5.QtCore import QMutex, Qt, QThread, pyqtSignal +from PyQt5.QtGui import QImage, QPixmap +from PyQt5.QtWidgets import (QDialog, QFormLayout, QLabel, QMessageBox, + QPushButton, QSizePolicy, QSlider) + + +if "--sim-camera" in sys.argv: + from components.dummies.gxpy import DummyCamera + +from datetime import datetime + +from .component import Component + + +class UVCCamera(Component): + _edits_new_frame = pyqtSignal(object) + + def __init__(self, config=None, name=None, period=1, lazy=True, paused=False, threaded=True, registers=None): + super().__init__(config=config, name=name, period=period, lazy=lazy, paused=paused, threaded=threaded) + self.lock = QMutex() + self.simulate = "--sim-camera" in sys.argv + self._last_frame = None + + def config_changed(self): + if self.simulate: + self.device_manager = None + self.camera = DummyCamera() + self.sim_imgs = cycle(sorted(pathlib.Path("data/simulation_images/").glob("*.png"))) + else: + self.camera = cv2.VideoCapture(0) + + self.resolution = { + "w": int(self.round_4(self.config[self.name]["horizontal_resolution"])), + "h": int(self.round_4(self.config[self.name]["vertical_resolution"])), + } + self._period = int(self.config[self.name].get("frame_time_ms", 300)) / 1000 + self.exposure_time = int(self.config[self.name]["exposure_time"]) + self.roi = { + "x": int(self.round_4(self.config[self.name].get("horizontal_crop_offset", 0))), + "w": int(self.round_4(self.config[self.name].get("horizontal_crop_resolution", self.resolution["w"]))), + "y": int(self.round_4(self.config[self.name].get("vertical_crop_offset", 0))), + "h": int(self.round_4(self.config[self.name].get("vertical_crop_resolution", self.resolution["h"]))), + "r": int(self.config[self.name].get("rotate_90_clockwise_times", 0)), + } + self.auto_white_balance = self.config[self.name].get("auto_white_balance", "").lower() in {"on", "1", "y", "yes", "true", "enable", "enabled", } + self.balance = { + "r": float(self.config[self.name].get("balance_red", 1)), + "g": float(self.config[self.name].get("balance_green", 1)), + "b": float(self.config[self.name].get("balance_blue", 1)), + } + self.lock.lock() + + self.edits = None + self.lock.unlock() + self.edits_enabled = "--camera-edits" in sys.argv + if self.edits_enabled: + self.init_edits() + + @staticmethod + def round_4(x): + return 4 * round(int(x) / 4) + + @Component.reconfig_on_error + def _get(self): + frame = None + self.lock.lock() + if self.simulate: + img_path = next(self.sim_imgs) + self.log.debug(f"loading image {img_path}") + frame = cv2.cvtColor(cv2.imread(str(img_path)), cv2.COLOR_BGR2RGB) + QThread.msleep(1000) + else: + self.camera.TriggerSoftware.send_command() + frame = self.camera.read() + if frame is not None: + frame = np.rot90(frame, self.roi["r"]) + self.lock.unlock() + if frame is None: + self.log.error("failed to get frame") + elif self.edits_enabled: + frame = self.edit(frame, self.edits) + self._edits_new_frame.emit([frame]) + super()._get([frame]) + + def __del__(self, event=None): + if self.camera is not None: + self.camera.stream_off() + self.camera.close_device() + + def init_edits(self): + self.edits_enabled = True + self.edits_dialog = EditsDialog(self.roi) + self.edits_dialog.edits_changed.connect(self.set_edits) + self._edits_new_frame.connect(self.edits_dialog.save_and_show_edits_new_frame) + self.edits_dialog.edits_pause.connect(self.toggle_paused) + + def toggle_paused(self): + if self.running: + self.pause() + else: + self.resume() + + def set_edits(self, edits=None): + self.edits = edits + + def edit(self, img, edits=None): + if edits is None: + return img + # BRIGHTNESS AND CONTRAST + contrast = float(edits.get("contrast", 0)) + brightness = float(edits.get("brightness", 0)) + if not (contrast == 0 and brightness == 0): + img = imutils.adjust_brightness_contrast(img, brightness=brightness, contrast=contrast / 255 * 500) + # ROTATE AND SCALE + rotation = -float(edits.get("rotation", 0)) + scale = float(edits.get("scale", 1)) + if not (rotation == 0 and scale == 1): + img = imutils.rotate(img, rotation, scale=scale) + # TRANSLATE + translation_x = float(edits.get("translation_x", 0)) + translation_y = float(edits.get("translation_y", 0)) + if not (translation_x == 0 and translation_y == 0): + img = imutils.translate(img, translation_x, translation_y) + return img + + +class EditsDialog(QDialog): + edits_changed = pyqtSignal(object) + edits_pause = pyqtSignal() + + def __init__(self, roi): + super().__init__() + self.frame = None + self.edits = { + "brightness": 0, + "contrast": 0, + "rotation": 0, + "scale": 1, + "translation_x": 0, + "translation_y": 0, + } + self.edits_specs = { + "brightness": [[-255, 255], 1, QSlider(Qt.Horizontal), QLabel()], + "contrast": [[-255, 255], 1, QSlider(Qt.Horizontal), QLabel()], + "rotation": [[-180, 180], 1, QSlider(Qt.Horizontal), QLabel()], + "scale": [[0, 5], 100, QSlider(Qt.Horizontal), QLabel()], + "translation_x": [[-roi["w"], roi["w"]], 1, QSlider(Qt.Horizontal), QLabel()], + "translation_y": [[-roi["h"], roi["h"]], 1, QSlider(Qt.Horizontal), QLabel()], + } + layout = QFormLayout() + self.edits_frame_l = QLabel() + self.edits_frame_l.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + layout.addRow(self.edits_frame_l) + self.edits_pause_b = QPushButton("toggle paused") + layout.addRow(self.edits_pause_b) + self.edits_pause_b.clicked.connect(self.edits_pause) + for edit, limits in self.edits_specs.items(): + limit, multiplier, slider, label = limits + slider.setRange(limit[0] * multiplier, limit[1] * multiplier) + slider.setSingleStep(1) + slider.setValue(self.edits[edit] * multiplier) + label.setText(f"{edit}: {self.edits[edit]}") + layout.addRow(label, slider) + slider.valueChanged.connect(self.update_edits) + self.edits_save_frame_b = QPushButton("save frame") + layout.addRow(self.edits_save_frame_b) + self.edits_save_frame_b.clicked.connect(self.edits_save_frame) + self.setLayout(layout) + self.update_edits() + self.show() + + def update_edits(self): + for edit, limits in self.edits_specs.items(): + limit, multiplier, slider, label = limits + self.edits[edit] = slider.value() / multiplier + label.setText(f"{edit}: {self.edits[edit]}") + self.edits_changed.emit(self.edits) + + def save_and_show_edits_new_frame(self, frame): + self.frame = frame[0] + if self.frame is not None: + self.edits_frame_l.setPixmap( + QPixmap.fromImage( + QImage( + self.frame.data, + self.frame.shape[1], # width + self.frame.shape[0], # height + self.frame.shape[2] * self.frame.shape[1], # width * channels + QImage.Format_RGB888 + ) + ).scaled( + max(self.edits_frame_l.width(), 640), + max(self.edits_frame_l.height(), 480), + Qt.KeepAspectRatio, + Qt.SmoothTransformation + ) + ) + + def edits_save_frame(self): + if self.frame is None: + return + out_path = f"./{datetime.now().isoformat()}.png" + self.log.info(f"saving frame: {out_path!r}") + img = cv2.cvtColor(self.frame, cv2.COLOR_RGB2BGR) + cv2.imwrite(out_path, img) + return out_path diff --git a/src/test/camera_windows.py b/src/test/galaxy_camera_windows.py similarity index 100% rename from src/test/camera_windows.py rename to src/test/galaxy_camera_windows.py diff --git a/src/test/uvc_camera.py b/src/test/uvc_camera.py new file mode 100644 index 0000000..e11efb4 --- /dev/null +++ b/src/test/uvc_camera.py @@ -0,0 +1,22 @@ +import time + +import cv2 +from PIL import Image + + +def main(): + print("Initializing......") + + cap = cv2.VideoCapture(0) + num = 10 + for i in range(num): + # Capture frame-by-frame + ret, numpy_image = cap.read() + img = Image.fromarray(numpy_image, 'RGB') + img.show() + + + cap.release() + +if __name__ == "__main__": + main()