This commit is contained in:
neo-dl 2022-06-29 11:07:13 +02:00
parent 7505dea99a
commit 281e7b09ec
4 changed files with 1071 additions and 3 deletions

View File

@ -0,0 +1,46 @@
// COMMUNICATION PROTOCOL:
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
#define PIN 21
#define NUMPIXELS 12
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
#define DELAYVAL 50
void setup() {
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
clock_prescale_set(clock_div_1);
#endif
pixels.begin();
}
void setcolor(byte r,byte g,byte b){
pixels.clear();
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, pixels.Color(r,g,b));
}
pixels.show();
}
void loop() {
pixels.clear();
setcolor(255,0,0);
delay(1000);
setcolor(0,255,0);
delay(1000);
setcolor(0,0,255);
delay(1000);
setcolor(0,0,0);
delay(1000);
setcolor(255,155,55);
while(1){
1;
}
}

View File

@ -38,12 +38,10 @@ logging.basicConfig(
mode="a",
encoding="utf-8",
delay=False,
errors="surrogateescape"
),
],
force=True,
encoding="utf-8",
errors="surrogateescape",
)
try:

461
src/test/camera.py Normal file
View File

@ -0,0 +1,461 @@
# version:1.0.1905.9051
import faulthandler
import signal
import sys
from typing import Optional, Union
import cv2
import numpy
# from PIL import Image
from PyQt5 import QtGui, QtWidgets, uic
from PyQt5.QtCore import QObject, Qt, QThread, pyqtSignal
from PyQt5.QtGui import QColor, QImage, QPixmap
from PyQt5.QtWidgets import *
import qimage2ndarray
import src.test.vision_test.libvision as libvision
import src.test.vision_test.object_detection.utils.visualization_utils as vis_util
from src.lib.watcher import Watcher
from src.test.vision_test.libvision import *
TMP_DIR = "/tmp/vt40b"
faulthandler.enable()
signal.signal(signal.SIGINT, lambda a, b: quit())
if "--sim-camera" in sys.argv:
simulate = True
else:
simulate = False
if "--cpu" in sys.argv:
os.environ["CUDA_VISIBLE_DEVICES"] = ""
if simulate:
from galaxy_camera_dummy import *
else:
from galaxy_camera import *
light_red = QColor(255, 230, 230)
light_green = QColor(230, 255, 230)
class MainWindow(QtWidgets.QMainWindow):
keyPressed = QtCore.pyqtSignal(QtCore.QEvent)
new_frame_for_detect_signal = pyqtSignal(numpy.ndarray)
new_frame_for_render_signal = pyqtSignal(numpy.ndarray)
def __init__(self):
super().__init__()
self.video_frames = {}
self.ui = uic.loadUi("src/test/camera_test/camera_test.ui", self)
self.ICON_OK = QtGui.QPixmap("data/icons/ok.png")
self.ICON_KO = QtGui.QPixmap("data/icons/ko.png")
self.ICON_WARNING = QtGui.QPixmap("data/icons/warning.png")
# INIT WATCHER
self.watcher = Watcher(self.watch_table)
self.vision_test = libvision()
# INIT DETECTIONS TABLE
self.table_detections.setColumnCount(5)
self.table_detections.setColumnWidth(0, 50)
self.table_detections.setColumnWidth(1, 100)
self.table_detections.setColumnWidth(2, 50)
self.table_detections.setColumnWidth(3, 100)
self.table_detections.horizontalHeader().setStretchLastSection(True)
self.table_detections.setHorizontalHeaderLabels(["Zone", "Class", "Score", "Expected", "Result"])
self.table_detections.setRowCount(len(self.vision_test.wires_zones) + len(self.vision_test.terminals_zones))
self.table_detections.verticalHeader().setSectionResizeMode(QHeaderView.Fixed)
self.table_detections.verticalHeader().setDefaultSectionSize(20)
# DETECTION TABLE SETUP
i = 0
for zone in self.vision_test.wires_zones.keys():
self.table_detections.setItem(i, 0, QTableWidgetItem(zone))
i += 1
for zone in self.vision_test.terminals_zones.keys():
self.table_detections.setItem(i, 0, QTableWidgetItem(zone))
i += 1
self.wires_output_dict = None
self.terminals_output_dict = None
self.current_global_test_result = "UNDEF"
self.label_global_test_result.setScaledContents(True)
self.datamatrix = None
self.flag_detect_wires_on = False
self.flag_detect_terminals_on = False
self.flag_detect_datamatrix_on = False
self.flag_save_video_on = False
self.raw_image = gx.RawImage
self.qt_image = QImage
self.CameraThread = QThread()
self.DetectThread = QThread()
self.RenderThread = QThread()
self.CameraClass = CameraClass(self)
self.DetectClass = DetectClass(self)
self.RenderClass = RenderClass(self)
self.RenderClass.color_correction_param = self.CameraClass.color_correction_param
self.RenderClass.contrast_lut = self.CameraClass.contrast_lut
self.RenderClass.gamma_lut = self.CameraClass.gamma_lut
self.CameraClass.moveToThread(self.CameraThread)
self.DetectClass.moveToThread(self.DetectThread)
self.RenderClass.moveToThread(self.RenderThread)
self.CameraThread.started.connect(self.CameraClass.start)
self.DetectThread.started.connect(self.DetectClass.start)
self.RenderThread.started.connect(self.RenderClass.start)
self.CameraThread.start()
self.RenderThread.start()
self.DetectThread.start()
self.keyPressed.connect(self.on_key)
self.new_frame_for_detect_signal.connect(self.DetectClass.detect)
self.new_frame_for_render_signal.connect(self.RenderClass.render)
self.wires_slider.valueChanged.connect(self.wires_threshold_set)
self.terminals_slider.valueChanged.connect(self.terminals_threshold_set)
self.slider_red.valueChanged.connect(self.change_balance_red)
self.slider_green.valueChanged.connect(self.change_balance_green)
self.slider_blue.valueChanged.connect(self.change_balance_blue)
self.slider_red.setValue(int(self.CameraClass.balance_red * 100))
self.slider_green.setValue(int(self.CameraClass.balance_green * 100))
self.slider_blue.setValue(int(self.CameraClass.balance_blue * 100))
self.checkBox_wires.stateChanged.connect(self.enable_wires)
self.checkBox_terminals.stateChanged.connect(self.enable_terminals)
self.checkBox_datamatrix.stateChanged.connect(self.enable_datamatrix)
self.btn_save_image.clicked.connect(self.save_frame_masked)
self.label_red.setText("{:.2f}".format(self.CameraClass.balance_red))
self.label_green.setText("{:.2f}".format(self.CameraClass.balance_green))
self.label_blue.setText("{:.2f}".format(self.CameraClass.balance_blue))
self.combo_vision_recipe.addItems(self.vision_test.recipes.keys())
self.change_recipe()
self.combo_vision_recipe.currentIndexChanged.connect(self.change_recipe)
self.slider_zoom.valueChanged.connect(self.change_zoom)
def change_zoom(self, zoom):
self.RenderClass.render_mutex.lock()
self.DetectClass.detect_mutex.lock()
self.vision_test.update_scale(zoom)
self.label_zoom.setText("{:2d}%".format(zoom))
self.image.setFixedSize(self.vision_test.image_height_scaled, self.vision_test.image_width_scaled)
self.RenderClass.render_mutex.unlock()
self.DetectClass.detect_mutex.unlock()
def update_global_test_result(self):
global_test_result = self.vision_test.global_test_result()
if global_test_result != self.current_global_test_result:
self.current_global_test_result = global_test_result
if global_test_result == "OK":
self.label_global_test_result.setPixmap(QtGui.QPixmap(self.ICON_OK))
else:
self.label_global_test_result.setPixmap(QtGui.QPixmap(self.ICON_KO))
def change_recipe(self):
self.vision_test.current_recipe = str(self.combo_vision_recipe.currentText())
i = 0
for zone in self.vision_test.wires_zones.keys():
self.table_detections.setItem(i, 3, QTableWidgetItem(
self.vision_test.recipes[self.vision_test.current_recipe]["wires"][zone]))
i += 1
for zone in self.vision_test.terminals_zones.keys():
self.table_detections.setItem(i, 3, QTableWidgetItem(
self.vision_test.recipes[self.vision_test.current_recipe]["terminals"][zone]))
i += 1
def enable_wires(self, val):
self.flag_detect_wires_on = val
def enable_terminals(self, val):
self.flag_detect_terminals_on = val
def enable_datamatrix(self, val):
self.flag_detect_datamatrix_on = val
def change_balance_red(self, val):
self.label_red.setText("{:.2f}".format(val / 100))
self.CameraClass.set_balance_red(val / 100)
def change_balance_green(self, val):
self.label_green.setText("{:.2f}".format(val / 100))
self.CameraClass.set_balance_green(val / 100)
def change_balance_blue(self, val):
self.label_blue.setText("{:.2f}".format(val / 100))
self.CameraClass.set_balance_blue(val / 100)
def wires_threshold_set(self, val):
self.vision_test.wires_threshold = val / 100
def terminals_threshold_set(self, val):
self.vision_test.terminals_threshold = val / 100
def keyPressEvent(self, event):
super(MainWindow, self).keyPressEvent(event)
self.keyPressed.emit(event)
def on_key(self, event):
if event.key() == QtCore.Qt.Key_Space:
self.save_frame()
if event.key() == QtCore.Qt.Key_V:
self.flag_save_video_on = not self.flag_save_video_on
if self.flag_save_video_on:
self.video_frames.clear()
else:
self.save_video()
elif event.key() == QtCore.Qt.Key_Q:
self.ui.close()
@QtCore.pyqtSlot(QImage)
def update_image(self, img_camera):
self.image.setPixmap(QPixmap.fromImage(img_camera))
self.update_global_test_result()
@QtCore.pyqtSlot(gx.RawImage)
@QtCore.pyqtSlot(np.ndarray)
def dispatch_new_frame(self, gxframe):
if simulate:
self.numpy_image_rot = gxframe
t1 = t2 = t3 = t4 = time.time()
else:
t1 = time.time()
rgb_image = gxframe.convert("RGB") # 0.021
t2 = time.time()
rgb_image.image_improvement(self.CameraClass.color_correction_param, self.CameraClass.contrast_lut,
self.CameraClass.gamma_lut)
t3 = time.time()
numpy_image = rgb_image.get_numpy_array() # 0.000
self.numpy_image_rot = numpy.rot90(numpy_image, k=-1) # 0.077
# self.numpy_image_rot = numpy_image
t4 = time.time()
if self.flag_save_video_on:
self.buffer_frame(self.numpy_image_rot)
if self.DetectClass.detect_mutex.tryLock():
self.new_frame_for_detect_signal.emit(self.numpy_image_rot)
self.DetectClass.detect_mutex.unlock()
else:
print("detect signal skip")
if self.RenderClass.render_mutex.tryLock():
self.new_frame_for_render_signal.emit(self.numpy_image_rot)
self.RenderClass.render_mutex.unlock()
else:
print("render signal skip")
self.watcher.watch("DISPATCH convert", "{:.3f}".format(t2 - t1))
self.watcher.watch("DISPATCH improve", "{:.3f}".format(t3 - t2))
self.watcher.watch("DISPATCH np_rot", "{:.3f}".format(t4 - t3))
# print("DISPATCH conv={:.3f} improv={:.3f} np_rot={:.3f} ".format(t2 - t1, t3 - t2,t4-t3))
def save_frame(self):
if not os.path.exists(TMP_DIR):
os.mkdir(TMP_DIR)
filename = TMP_DIR + "/{:.1f}.png".format(time.time())
print("saving " + filename)
image_np_bgr = cv2.cvtColor(self.numpy_image_rot, cv2.COLOR_RGB2BGR)
cv2.imwrite(filename, image_np_bgr)
def save_frame_masked(self):
if not os.path.exists(TMP_DIR):
os.mkdir(TMP_DIR)
filename = TMP_DIR + "/{:.1f}.png".format(time.time())
print("saving " + filename)
image_np_bgr = cv2.cvtColor(self.numpy_image_rot, cv2.COLOR_RGB2BGR)
image_np_bgr[self.vision_test.img_save_mask] = self.vision_test.img_solid_black_mask[
self.vision_test.img_save_mask]
cv2.imwrite(filename, image_np_bgr)
def save_video(self):
if not os.path.exists(TMP_DIR):
os.mkdir(TMP_DIR)
video_dir = TMP_DIR + "/video"
if not os.path.exists(video_dir):
os.mkdir(video_dir)
for filename, frame in self.video_frames.items():
filepath = video_dir + "/" + filename
print("saving " + filepath)
image_np_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
cv2.imwrite(filepath, image_np_bgr)
self.video_frames.clear()
def buffer_frame(self, frame):
filename = "{:.1f}.png".format(time.time())
self.video_frames[filename] = copy.deepcopy(frame)
class DetectClass(QObject):
new_detection_signal = pyqtSignal()
def __init__(self, parent):
super().__init__()
self.detect_mutex = QtCore.QMutex()
self.parent = parent
print("Init detect thread")
@QtCore.pyqtSlot(numpy.ndarray)
def detect(self, numpy_image_rot):
if self.detect_mutex.tryLock():
# WIRES
t4 = time.time()
if self.parent.flag_detect_wires_on:
wires_img = numpy_image_rot[
self.parent.vision_test.wires_crop["ymin"]:self.parent.vision_test.wires_crop["ymax"],
self.parent.vision_test.wires_crop["xmin"]:self.parent.vision_test.wires_crop["xmax"]]
self.parent.wires_output_dict = self.parent.vision_test.check_wires(wires_img)
# time.sleep(1) #simulate slowness
t5 = time.time()
# self.parent.wires_keypoints = self.parent.vision_test.find_wires_keypoints(self.parent.wires_output_dict)
t6 = time.time()
# TERMINALS
if self.parent.flag_detect_terminals_on:
terminals_img = numpy_image_rot[
self.parent.vision_test.terminals_crop["ymin"]:self.parent.vision_test.terminals_crop[
"ymax"],
self.parent.vision_test.terminals_crop["xmin"]:self.parent.vision_test.terminals_crop[
"xmax"]]
self.parent.terminals_output_dict = self.parent.vision_test.check_terminals(terminals_img)
t7 = time.time()
# DATAMATRIX
if self.parent.flag_detect_datamatrix_on:
datamatrix_img = numpy_image_rot[self.parent.vision_test.datamatrix_crop["ymin"]:
self.parent.vision_test.datamatrix_crop["ymax"],
self.parent.vision_test.datamatrix_crop["xmin"]:
self.parent.vision_test.datamatrix_crop["xmax"]]
self.parent.datamatrix = self.parent.vision_test.read_datamatrix(datamatrix_img)
t8 = time.time()
self.parent.watcher.watch("DETECT wires", "{:.3f}".format(t5 - t4))
self.parent.watcher.watch("DETECT w. keypoints", "{:.3f}".format(t6 - t5))
self.parent.watcher.watch("DETECT terminals", "{:.3f}".format(t7 - t6))
self.parent.watcher.watch("DETECT datamatrix", "{:.3f}".format(t8 - t7))
# print("DETECT wires={:.3f} keyp={:.3f} term={:.3f} dmx={:.3f}".format( t5 - t4,t6-t5,t7-t6,t8-t7))
self.new_detection_signal.emit()
self.detect_mutex.unlock()
else:
print("DETECT Mutex skip")
def start(self):
print("Init detect slot")
class RenderClass(QObject):
update_gui = pyqtSignal(QImage)
contrast_lut: Optional[Buffer]
color_correction_param: int
cam: Union[None, U3VDevice, U2Device, GEVDevice]
gamma_lut: Optional[Buffer]
def __init__(self, parent):
super().__init__()
self.img_out = numpy.array([])
self.render_mutex = QtCore.QMutex()
self.app = None
self.image_in = gx.RawImage
self.flag_new_cycle = False
self.parent = parent
print("Init render thread")
def start(self):
self.update_gui.connect(self.parent.update_image)
print("Init render slot")
@QtCore.pyqtSlot(numpy.ndarray)
def render(self, numpy_image_rot):
if self.render_mutex.tryLock():
wires_dict = self.parent.wires_output_dict
terminals_dict = self.parent.terminals_output_dict
t1 = time.time()
image_qt = qimage2ndarray.array2qimage(numpy_image_rot)
# image_qt = QImage(numpy_image_rot, numpy_image_rot.shape[1], numpy_image_rot.shape[0], QImage.Format_RGB888)
out_qt = image_qt.scaled(self.parent.vision_test.image_height_scaled,
self.parent.vision_test.image_width_scaled, Qt.KeepAspectRatio)
vis_util.visualize_zones(out_qt,
[self.parent.vision_test.wires_crop_qt, self.parent.vision_test.terminals_crop_qt,
self.parent.vision_test.datamatrix_crop_qt])
vis_util.visualize_subzones(out_qt, self.parent.vision_test.terminals_zones,
self.parent.vision_test.image_scale)
t2 = time.time()
if self.parent.flag_detect_wires_on:
if wires_dict is not None:
vis_util.visualize_wires_qt(out_qt, wires_dict, self.parent.vision_test.wires_category_index,
crop=self.parent.vision_test.wires_crop_qt,
min_score_thresh=self.parent.vision_test.wires_threshold)
# update GUI table
current_recipe = self.parent.vision_test.recipes[self.parent.combo_vision_recipe.currentText()]
i = 0
for name, zone in self.parent.vision_test.wires_zones.items():
if zone["detection"] is not None:
self.parent.table_detections.setItem(i, 1, QTableWidgetItem(zone["detection"]["class"]))
self.parent.table_detections.setItem(i, 2, QTableWidgetItem(
"{:.3f}".format(zone["detection"]["score"])))
else:
self.parent.table_detections.setItem(i, 1, QTableWidgetItem("no detection"))
self.parent.table_detections.setItem(i, 2, QTableWidgetItem("-"))
self.parent.table_detections.setItem(i, 4, QTableWidgetItem(zone["result"]))
if zone["result"] == "OK":
self.parent.table_detections.item(i, 4).setBackground(light_green)
else:
self.parent.table_detections.item(i, 4).setBackground(light_red)
i += 1
if self.parent.flag_detect_terminals_on:
if terminals_dict is not None:
vis_util.visualize_terminals_qt(out_qt, terminals_dict,
self.parent.vision_test.terminals_category_index,
crop=self.parent.vision_test.terminals_crop_qt,
min_score_thresh=self.parent.vision_test.terminals_threshold)
# update GUI table
i = len(self.parent.vision_test.wires_zones)
for name, zone in self.parent.vision_test.terminals_zones.items():
if zone["detection"] is not None:
self.parent.table_detections.setItem(i, 1, QTableWidgetItem(zone["detection"]["class"]))
self.parent.table_detections.setItem(i, 2, QTableWidgetItem(
"{:.3f}".format(zone["detection"]["score"])))
else:
self.parent.table_detections.setItem(i, 1, QTableWidgetItem("no detection"))
self.parent.table_detections.setItem(i, 2, QTableWidgetItem("-"))
self.parent.table_detections.setItem(i, 4, QTableWidgetItem(zone["result"]))
if zone["result"] == "OK":
self.parent.table_detections.item(i, 4).setBackground(light_green)
else:
self.parent.table_detections.item(i, 4).setBackground(light_red)
i += 1
if self.parent.flag_detect_datamatrix_on:
vis_util.visualize_datamatrix_qt(out_qt, self.parent.datamatrix,
crop=self.parent.vision_test.datamatrix_crop_qt,
scale=self.parent.vision_test.image_scale)
self.update_gui.emit(out_qt)
t3 = time.time()
self.parent.watcher.watch("RENDER scaling", "{:.3f}".format(t2 - t1))
self.parent.watcher.watch("RENDER visualize", "{:.3f}".format(t3 - t3))
# print("RENDER scale={:.3f} render={:.3f}".format(t2 - t1, t3 - t2))
if self.app is not None:
self.app.processEvents()
self.render_mutex.unlock()
else:
print("RENDER Mutex skip")
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
mainWindow.RenderClass.app = app
sys.exit(app.exec_())

563
src/test/camera_test.ui Executable file
View File

@ -0,0 +1,563 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1211</width>
<height>976</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QSlider" name="wires_slider">
<property name="geometry">
<rect>
<x>790</x>
<y>40</y>
<width>151</width>
<height>21</height>
</rect>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
<widget class="QSlider" name="terminals_slider">
<property name="geometry">
<rect>
<x>790</x>
<y>70</y>
<width>151</width>
<height>31</height>
</rect>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>710</x>
<y>80</y>
<width>59</width>
<height>15</height>
</rect>
</property>
<property name="text">
<string>Terminals</string>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>710</x>
<y>40</y>
<width>59</width>
<height>15</height>
</rect>
</property>
<property name="text">
<string>Wires</string>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>710</x>
<y>120</y>
<width>59</width>
<height>15</height>
</rect>
</property>
<property name="text">
<string>Red</string>
</property>
</widget>
<widget class="QSlider" name="slider_red">
<property name="geometry">
<rect>
<x>790</x>
<y>110</y>
<width>151</width>
<height>31</height>
</rect>
</property>
<property name="maximum">
<number>300</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>710</x>
<y>160</y>
<width>59</width>
<height>15</height>
</rect>
</property>
<property name="text">
<string>Green</string>
</property>
</widget>
<widget class="QSlider" name="slider_green">
<property name="geometry">
<rect>
<x>790</x>
<y>150</y>
<width>151</width>
<height>31</height>
</rect>
</property>
<property name="maximum">
<number>300</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
<widget class="QLabel" name="label_5">
<property name="geometry">
<rect>
<x>710</x>
<y>200</y>
<width>59</width>
<height>15</height>
</rect>
</property>
<property name="text">
<string>Blue</string>
</property>
</widget>
<widget class="QSlider" name="slider_blue">
<property name="geometry">
<rect>
<x>790</x>
<y>190</y>
<width>151</width>
<height>31</height>
</rect>
</property>
<property name="maximum">
<number>300</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
<widget class="QCheckBox" name="checkBox_wires">
<property name="geometry">
<rect>
<x>1060</x>
<y>40</y>
<width>85</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Wires</string>
</property>
</widget>
<widget class="QCheckBox" name="checkBox_terminals">
<property name="geometry">
<rect>
<x>1060</x>
<y>60</y>
<width>85</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Terminals</string>
</property>
</widget>
<widget class="QCheckBox" name="checkBox_datamatrix">
<property name="geometry">
<rect>
<x>1060</x>
<y>80</y>
<width>91</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Datamatrix</string>
</property>
</widget>
<widget class="QLabel" name="label_red">
<property name="geometry">
<rect>
<x>950</x>
<y>120</y>
<width>59</width>
<height>15</height>
</rect>
</property>
<property name="text">
<string>1</string>
</property>
</widget>
<widget class="QLabel" name="label_green">
<property name="geometry">
<rect>
<x>950</x>
<y>160</y>
<width>59</width>
<height>15</height>
</rect>
</property>
<property name="text">
<string>1</string>
</property>
</widget>
<widget class="QLabel" name="label_blue">
<property name="geometry">
<rect>
<x>950</x>
<y>200</y>
<width>59</width>
<height>15</height>
</rect>
</property>
<property name="text">
<string>1</string>
</property>
</widget>
<widget class="QTabWidget" name="tabWidget">
<property name="geometry">
<rect>
<x>700</x>
<y>270</y>
<width>441</width>
<height>661</height>
</rect>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_watches">
<attribute name="title">
<string>Watches</string>
</attribute>
<widget class="QTableWidget" name="watch_table">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>421</width>
<height>611</height>
</rect>
</property>
</widget>
</widget>
<widget class="QWidget" name="tab_detections">
<attribute name="title">
<string>Detections</string>
</attribute>
<widget class="QTableWidget" name="table_detections">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>421</width>
<height>611</height>
</rect>
</property>
</widget>
</widget>
</widget>
<widget class="QPushButton" name="btn_save_image">
<property name="geometry">
<rect>
<x>1060</x>
<y>120</y>
<width>80</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Save image</string>
</property>
</widget>
<widget class="QComboBox" name="combo_vision_recipe">
<property name="geometry">
<rect>
<x>800</x>
<y>230</y>
<width>131</width>
<height>23</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="label_6">
<property name="geometry">
<rect>
<x>710</x>
<y>230</y>
<width>81</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Vision recipe</string>
</property>
</widget>
<widget class="QSlider" name="slider_zoom">
<property name="geometry">
<rect>
<x>790</x>
<y>10</y>
<width>151</width>
<height>21</height>
</rect>
</property>
<property name="minimum">
<number>20</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>20</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
<widget class="QLabel" name="label_8">
<property name="geometry">
<rect>
<x>710</x>
<y>10</y>
<width>59</width>
<height>15</height>
</rect>
</property>
<property name="text">
<string>Zoom</string>
</property>
</widget>
<widget class="QLabel" name="label_zoom">
<property name="geometry">
<rect>
<x>950</x>
<y>10</y>
<width>59</width>
<height>15</height>
</rect>
</property>
<property name="text">
<string>20%</string>
</property>
</widget>
<widget class="QScrollArea" name="scrollArea">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>681</width>
<height>921</height>
</rect>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>679</width>
<height>919</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="image">
<property name="minimumSize">
<size>
<width>500</width>
<height>500</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>5000</width>
<height>5000</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">border: 1px solid black</string>
</property>
<property name="text">
<string>image</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QFrame" name="frame">
<property name="geometry">
<rect>
<x>980</x>
<y>180</y>
<width>161</width>
<height>91</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignHCenter">
<widget class="QLabel" name="label_7">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>120</width>
<height>20</height>
</size>
</property>
<property name="text">
<string>GLOBAL TEST RESULT</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QLabel" name="label_global_test_result">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1211</width>
<height>20</height>
</rect>
</property>
<widget class="QMenu" name="menuTools">
<property name="title">
<string>Menu</string>
</property>
<addaction name="actionExit"/>
</widget>
<addaction name="menuTools"/>
</widget>
<action name="actionExit">
<property name="text">
<string>Exit</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>