st-ten-1/src/components/galaxy_camera.py

267 lines
11 KiB
Python
Raw Normal View History

2022-06-21 12:10:52 +00:00
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
2025-01-23 15:49:51 +00:00
from PyQt5.QtWidgets import (QDialog, QFormLayout, QLabel,
2022-06-21 12:10:52 +00:00
QPushButton, QSizePolicy, QSlider)
2025-01-23 15:49:51 +00:00
try:
from . import gxipy as gx
except Exception:
from dummies import gxpy as gx
2022-07-18 12:48:37 +00:00
2022-06-21 12:10:52 +00:00
if "--sim-camera" in sys.argv:
from components.dummies.gxpy import DummyCamera
from datetime import datetime
from .component import Component
class GalaxyCamera(Component):
2022-09-13 11:01:00 +00:00
_edits_new_frame = pyqtSignal(object)
2022-06-21 12:10:52 +00:00
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):
2022-10-05 12:57:36 +00:00
if self.simulate:
self.device_manager = None
self.camera = DummyCamera()
self.sim_imgs = cycle(sorted(pathlib.Path("data/simulation_images/").glob("*.png")))
else:
self.device_manager = gx.DeviceManager()
# create a device manager
dev_num, dev_info_list = self.device_manager.update_device_list()
if dev_num == 0:
2022-10-05 13:28:40 +00:00
raise AssertionError(f"camera not detected: dev_num = {dev_num!r}")
2022-10-05 12:57:36 +00:00
self.camera = self.device_manager.open_device_by_index(1)
2022-06-29 11:12:09 +00:00
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"])
2022-06-21 12:10:52 +00:00
self.roi = {
2022-06-29 11:12:09 +00:00
"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)),
2022-06-21 12:10:52 +00:00
}
2022-06-29 11:12:09 +00:00
self.auto_white_balance = self.config[self.name].get("auto_white_balance", "").lower() in {"on", "1", "y", "yes", "true", "enable", "enabled", }
2022-06-21 12:10:52 +00:00
self.balance = {
2022-06-29 11:12:09 +00:00
"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)),
2022-06-21 12:10:52 +00:00
}
self.lock.lock()
self.camera.stream_off()
self.camera.TriggerMode.set(gx.GxSwitchEntry.OFF)
self.camera.ExposureTime.set(self.exposure_time)
self.camera.Gain.set(1.0)
self.camera.OffsetX.set(self.roi["x"])
self.camera.Width.set(self.roi["w"])
self.camera.OffsetY.set(self.roi["y"])
self.camera.Height.set(self.roi["h"])
if self.auto_white_balance:
self.camera.BalanceWhiteAuto.set(gx.GxAutoEntry.ONCE)
QThread.msleep(3000)
self.camera.BalanceRatioSelector.set(gx.GxBalanceRatioSelectorEntry.RED)
self.balance["r"] = self.camera.BalanceRatio.get()
self.camera.BalanceRatioSelector.set(gx.GxBalanceRatioSelectorEntry.GREEN)
self.balance["g"] = self.camera.BalanceRatio.get()
self.camera.BalanceRatioSelector.set(gx.GxBalanceRatioSelectorEntry.BLUE)
self.balance["b"] = self.camera.BalanceRatio.get()
else:
self.camera.BalanceRatioSelector.set(gx.GxBalanceRatioSelectorEntry.RED)
self.camera.BalanceRatio.set(self.balance["r"])
self.camera.BalanceRatioSelector.set(gx.GxBalanceRatioSelectorEntry.GREEN)
self.camera.BalanceRatio.set(self.balance["g"])
self.camera.BalanceRatioSelector.set(gx.GxBalanceRatioSelectorEntry.BLUE)
self.camera.BalanceRatio.set(self.balance["b"])
if self.camera.GammaParam.is_readable():
self.gamma_lut = gx.Utility.get_gamma_lut(self.camera.GammaParam.get())
else:
self.gamma_lut = None
if self.camera.ContrastParam.is_readable():
self.contrast_lut = gx.Utility.get_contrast_lut(self.camera.ContrastParam.get())
else:
self.contrast_lut = None
if self.camera.ColorCorrectionParam.is_readable():
self.color_correction = self.camera.ColorCorrectionParam.get()
else:
self.color_correction = 0
self.camera.TriggerMode.set(gx.GxSwitchEntry.ON)
self.camera.TriggerSource.set(gx.GxTriggerSourceEntry.SOFTWARE)
self.camera.stream_on()
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
2022-06-21 12:10:52 +00:00
def _get(self):
# print("GALAXY CAMERA", str(int(QThread.currentThreadId())), flush=True)
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)
2022-07-20 17:29:11 +00:00
QThread.msleep(1000)
2022-06-21 12:10:52 +00:00
else:
self.camera.TriggerSoftware.send_command()
frame = self.camera.data_stream[0].get_image()
if frame is not None:
if frame.get_status() == gx.GxFrameStatusList.INCOMPLETE:
self.log.error("incomplete frame")
frame = None
else:
frame = frame.convert("RGB")
frame.image_improvement(self.color_correction, self.contrast_lut, self.gamma_lut)
frame = frame.get_numpy_array()
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):
2022-10-05 12:57:36 +00:00
if self.camera is not None:
self.camera.stream_off()
self.camera.close_device()
2022-06-21 12:10:52 +00:00
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)
2022-06-22 15:18:29 +00:00
self.edits_dialog.edits_pause.connect(self.toggle_paused)
def toggle_paused(self):
if self.running:
self.pause()
else:
self.resume()
2022-06-21 12:10:52 +00:00
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):
2022-09-13 11:01:00 +00:00
edits_changed = pyqtSignal(object)
2022-06-22 15:18:29 +00:00
edits_pause = pyqtSignal()
2022-06-21 12:10:52 +00:00
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)
2022-06-22 15:18:29 +00:00
self.edits_pause_b = QPushButton("toggle paused")
layout.addRow(self.edits_pause_b)
self.edits_pause_b.clicked.connect(self.edits_pause)
2022-06-21 12:10:52 +00:00
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"
2022-07-13 08:23:43 +00:00
self.log.info(f"saving frame: {out_path!r}")
2022-06-21 12:10:52 +00:00
img = cv2.cvtColor(self.frame, cv2.COLOR_RGB2BGR)
cv2.imwrite(out_path, img)
return out_path