This commit is contained in:
matteo porta 2022-08-02 18:15:30 +02:00
parent df3be4c784
commit 304fd9d664
28 changed files with 590 additions and 179 deletions

View File

@ -44,7 +44,4 @@ port: COM1
baudrate: 9600
[vision]
detection_threshold: 0.5
neural_network: hs5-20000
; recipes_path: ./config/vision_test_recipes

View File

@ -22,7 +22,7 @@ instruction: CONTROLLARE PRESENZA TERMORESTRINGENTE
[markers]
[zones]
p1: 1000,500 800,800 hs-ok # HEATSINK PRESENT
p1: 1200,600 1800,1200 hs-ok # HEATSINK PRESENT
[labels]
p1: 660,1200 120 0xffffffff 0xff000000 4 TERMORESTRINGENTE

View File

@ -31,6 +31,7 @@ $* 2> >(sed $'s/.*/\e[31m&\e[m/' >&2) # &
# --auto-login-user \
# --autotests-archive \
# --camera-edits \
# --fail-vision \
# --full-screen \
# --interact \
# --maximized \

View File

@ -49,7 +49,7 @@ registers = {
180: "END SEQUENCE PROGRAM",
190: "END PLUG",
200: "END CAGE",
210: "FINE TEST", # WAITING THE START OF A NEW TEST",
210: "FINE TEST", # "WAITING THE START OF A NEW TEST",
}, }],
"Running test: phase backwards time": [33 - 1, {"dt": "16bit_uint", "f": 26, }],
"Running test: filling pressure": [34 - 1, {"dt": "32bit_int", "f": 21, }],

View File

@ -47,6 +47,11 @@ else:
def Interpreter(*args, **kwargs):
raise ValueError("\"--no-tflite\" in sys.argv")
if "--fail-vision" not in sys.argv:
vision_override = None
else:
vision_override = False
# # Patch the location of gfile
# tf.gfile = tf.io.gfile
@ -95,7 +100,7 @@ class Vision(Component):
def config_changed(self):
# OBJECT DETECTION
self.detection_threshold = float(self.config[self.name]["detection_threshold"])
self.detection_threshold = float(self.config[self.name].get("detection_threshold", 0.5))
# recipe
self.zones = None
self.labels = None
@ -122,20 +127,26 @@ class Vision(Component):
self.num_classes = len(label_map.item)
categories = label_map_util.convert_label_map_to_categories(label_map, max_num_classes=self.num_classes, use_display_name=True)
self.category_index = label_map_util.create_category_index(categories)
for k in self.category_index:
self.category_index[k]["color"] = self.category_index[k]["color"].replace("0x", "#")
self.classes_map = {c["name"]: k for k, c in self.category_index.items()}
self.zone_detection_filter_mode = self.config[self.name].get("zone_detection_filter_mode", "box_touches")
self.zone_detection_preference_mode = self.config[self.name].get("zone_detection_preference_mode", "distance")
self.zone_detection_filter_mode = self.config[self.name].get("zone_detection_filter_mode", "box_inside")
self.zone_detection_preference_mode = self.config[self.name].get("zone_detection_preference_mode", "score")
def get_center(self, rect):
@staticmethod
def get_center(rect):
return [(rect[0] + rect[2]) / 2, (rect[1] + rect[3]) / 2]
def get_size(self, rect):
@staticmethod
def get_size(rect):
return [rect[2] - rect[0], rect[3] - rect[1]]
def get_box(self, center, size):
@staticmethod
def get_box(center, size):
return [center[0] - size[0] / 2, center[1] - size[1] / 2, center[0] + size[0] / 2, center[1] + size[1] / 2]
def get_distance(self, p1, p2):
@staticmethod
def get_distance(p1, p2):
return pow((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2, 1 / 2)
def clear_recipe(self):
@ -180,6 +191,7 @@ class Vision(Component):
if recipe is None:
self.clear_recipe()
else:
self.recipe = recipe
self._set_recipe(self.recipes_dir / str(recipe))
def parse_markers(self, config=None):
@ -229,9 +241,10 @@ class Vision(Component):
shape = "ellipse"
d_class = self.category_index[self.classes_map[d_class]]
zones[zone_name] = {
"border_color": QColor(d_class["color"].replace("0x", "#")),
"border_color": QColor(d_class["color"]),
"border_thickness": 25,
"box": self.get_box(center, size),
"convert_negative_placement": False,
"center": center,
"class": d_class,
"fill_color": QColor("#00000000"),
@ -426,22 +439,23 @@ class Vision(Component):
style = {
"border_thickness": 25,
"fill_color": QColor("#00000000"),
"shape": "ellipse",
"shape": "rect",
"convert_negative_placement": False,
}
items = {}
for item_name, item in enumerate(detections):
items[str(item_name)] = {
"box": item["box"],
**item,
**style,
"border_color": QColor(item["class"]["color"].replace("0x", "#")),
"border_color": QColor(item["class"]["color"]),
}
return items
else:
return {}
def process_detections(self, detections):
if self.zones is None or not len(self.zones) or detections is None or not len(detections):
return {}
if self.zones is None or not len(self.zones):
return None
# MATCH DETECTIONS WITH RECIPE
results = dict.fromkeys(self.zones)
for detection in detections:
@ -533,8 +547,13 @@ class Vision(Component):
"expected": expected_class,
"detection": detection,
}
global vision_override
if vision_override is None:
ok = all(map(lambda detection: detection["ok"] is True, checked.values()))
else:
ok = vision_override
return {
"ok": all(map(lambda detection: detection["ok"] is True, checked.values())),
"ok": ok,
"results": checked,
}
@ -542,6 +561,7 @@ class Vision(Component):
# DRAW ZONES RESULTS
if self.zones is not None and len(self.zones) and results is not None and len(results):
style = {
"pen_line": "SolidLine",
"border_thickness": 50,
"fill_color": QColor("#00000000"),
}
@ -561,9 +581,7 @@ class Vision(Component):
for item_name, item in results["results"].items():
zone = self.zones[item_name]
items[str(item_name)] = {
"center": zone["center"],
"size": zone["size"],
"shape": zone["shape"],
**zone,
**style,
"border_color": Qt.green if item["ok"] else Qt.red,
}
@ -571,9 +589,30 @@ class Vision(Component):
else:
return {}
@staticmethod
def convert_negative_placement(p, painter):
for k in p:
if p[k] < 0:
if k.startswith("x") or k.startswith("w"):
p[k] += painter.device().width()
elif k.startswith("y") or k.startswith("h"):
p[k] += painter.device().height()
else:
raise AssertionError("could not detect variable direction")
@staticmethod
def apply_placement_offset(p, offset):
for k in p:
if k.startswith("x"):
p[k] += offset["x"]
elif k.startswith("y"):
p[k] += offset["y"]
else:
raise AssertionError("could not detect variable direction")
def render_items(self, items, offset=None, qimage=None, painter=None):
if offset is None:
offset = [0, 0]
offset = {"x": 0, "y": 0}
if painter is None:
if qimage is None:
raise AssertionError("one of 'qimage' or 'painter' parameter must not be None")
@ -581,35 +620,36 @@ class Vision(Component):
painter.begin(qimage)
for item_name, item in items.items():
try:
v = {}
convert_negative_placement = item.get("convert_negative_placement", True)
if "box" in item:
v["x1"], v["y1"], v["x2"], v["y2"] = item["box"][1] + offset[1], item["box"][0] + offset[0], item["box"][3] + offset[1], item["box"][2] + offset[0]
v["w"], v["h"] = v["x2"] - v["x1"], v["y2"] - v["y1"]
v["xc"], v["yc"] = self.get_center([v["x1"], v["y1"], v["x2"], v["y2"]])
p = {"x1": item["box"][1], "y1": item["box"][0], "x2": item["box"][3], "y2": item["box"][2], }
if convert_negative_placement:
Vision.convert_negative_placement(p, painter)
Vision.apply_placement_offset(p, offset)
p["w"], p["h"] = p["x2"] - p["x1"], p["y2"] - p["y1"]
p["xc"], p["yc"] = Vision.get_center([p["x1"], p["y1"], p["x2"], p["y2"]])
elif "location" in item:
v["x1"], v["y1"] = item["location"][1] + offset[1], item["location"][0] + offset[0]
p = {"x1": item["location"][1], "y1": item["location"][0], }
if convert_negative_placement:
Vision.convert_negative_placement(p, painter)
Vision.apply_placement_offset(p, offset)
if "size" in item:
v["w"], v["h"] = item["size"][1], item["size"][0]
v["x2"], v["y2"] = v["x1"] + v["w"], v["y1"] + v["h"]
v["xc"], v["yc"] = self.get_center([v["x1"], v["y1"], v["x2"], v["y2"]])
p["w"], p["h"] = item["size"][1], item["size"][0]
p["x2"], p["y2"] = p["x1"] + p["w"], p["y1"] + p["h"]
p["xc"], p["yc"] = Vision.get_center([p["x1"], p["y1"], p["x2"], p["y2"]])
else:
v["w"], v["h"] = 0, 0
v["x2"], v["y2"] = v["x1"], v["y1"]
v["xc"], v["yc"] = v["x1"], v["y1"]
p["w"], p["h"] = 0, 0
p["x2"], p["y2"] = p["x1"], p["y1"]
p["xc"], p["yc"] = p["x1"], p["y1"]
elif "center" in item and "size" in item:
v["xc"], v["yc"] = item["center"][1] + offset[1], item["center"][0] + offset[0]
v["w"], v["h"] = item["size"][1], item["size"][0]
v["x1"], v["y1"], v["x2"], v["y2"] = self.get_box([v["xc"], v["yc"]], [v["w"], v["h"]])
p = {"xc": item["center"][1], "yc": item["center"][0], }
if convert_negative_placement:
Vision.convert_negative_placement(p, painter)
Vision.apply_placement_offset(p, offset)
p["w"], p["h"] = item["size"][1], item["size"][0]
p["x1"], p["y1"], p["x2"], p["y2"] = Vision.get_box([p["xc"], p["yc"]], [p["w"], p["h"]])
else:
raise AssertionError("item has no valid positioning information")
for k in list(v):
if v[k] < 0:
if k.startswith("x") or k.startswith("w"):
v[k] = painter.device().width() + v[k]
elif k.startswith("y") or k.startswith("h"):
v[k] = painter.device().height() + v[k]
else:
raise AssertionError("could not detect variable direction")
painter.setOpacity(item.get("opacity", 0.5))
painter.setBrush(QBrush(item.get("fill_color", QColor("#ffffff")), getattr(Qt, item.get("brush_pattern", "SolidPattern"))))
painter.setPen(QPen(
@ -620,14 +660,14 @@ class Vision(Component):
getattr(Qt, item.get("pen_join", "MiterJoin")),
))
if item["shape"] == "ellipse":
painter.drawEllipse(QPointF(v["xc"], v["yc"]), v["w"] / 2, v["h"] / 2)
painter.drawEllipse(QPointF(p["xc"], p["yc"]), p["w"] / 2, p["h"] / 2)
elif item["shape"] == "cross":
painter.drawLine(QLineF(v["xc"], v["y1"], v["xc"], v["y2"]))
painter.drawLine(QLineF(v["x1"], v["yc"], v["x2"], v["yc"]))
painter.drawLine(QLineF(p["xc"], p["y1"], p["xc"], p["y2"]))
painter.drawLine(QLineF(p["x1"], p["yc"], p["x2"], p["yc"]))
elif item["shape"] == "line":
painter.drawLine(QLineF(v["xc"], v["yc"], v["x2"], v["y2"]))
painter.drawLine(QLineF(p["xc"], p["yc"], p["x2"], p["y2"]))
elif item["shape"] == "rect":
painter.drawRect(QRectF(v["x1"], v["y1"], v["x2"], v["y2"]))
painter.drawRect(QRectF(QPointF(p["x1"], p["y1"]), QPointF(p["x2"], p["y2"])))
elif item["shape"] == "text":
old_render_hints = painter.renderHints()
painter.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing, True)
@ -640,7 +680,7 @@ class Vision(Component):
font.setWordSpacing(item.get("font_word_spacing", 0))
font.setPixelSize(round(item.get("font_size", 25)))
path = QPainterPath()
path.addText(v["x1"], v["y1"], font, item["text"])
path.addText(p["x1"], p["y1"], font, item["text"])
painter.drawPath(path)
painter.setRenderHints(old_render_hints, True)
else:

View File

@ -1,4 +1,5 @@
import glob
import json
import os
import shutil
from datetime import datetime
@ -26,34 +27,60 @@ class VisionSaver(Component):
self.minimum_disk_free_space_gb = float(self.minimum_disk_free_space_gb)
self.time_format = self.config[self.name]["time_format"]
def save(self, save_time, img, mask=True, resize=True):
if type(save_time) is float:
save_time = int(save_time)
if type(save_time) is int:
save_time = datetime.fromtimestamp(save_time)
def save(self, save_time=None, suffix=None, frame=None, vision=None, resize=None, mask=None):
self.remove_older_images_if_needed()
if type(save_time) is None:
save_time = datetime.now()
else:
if type(save_time) is float:
save_time = int(save_time)
if type(save_time) is int:
save_time = datetime.fromtimestamp(save_time)
if type(save_time) is not datetime:
raise ValueError(f"save_time must be float int or datetime, not {type(save_time)}")
timestamp = save_time.strftime(self.time_format)
save_dir = self.location / save_time.strftime("%Y") / save_time.strftime("%m")
os.makedirs(save_dir, exist_ok=True)
out_path = save_dir / f"{timestamp}.png"
self.log.info(f"saving {out_path}")
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
if resize and self.resize_resolution is not None:
img = cv2.resize(img, self.resize_resolution, interpolation=cv2.INTER_LINEAR)
if mask and self.mask_zones is not None:
height, width, channels = img.shape
out = np.full(
[height, width, channels],
[0] * channels
)
for zone_name in self.mask_zones:
zone = self.bench.zones[zone_name]["box"]
out[zone[1]:zone[3], zone[0]:zone[2]] = img[zone[1]:zone[3], zone[0]:zone[2]]
else:
out = img
cv2.imwrite(str(out_path), out)
return out_path
out_paths = []
if frame is not None:
if suffix is not None:
out_paths.append(save_dir / f"{timestamp}.{suffix}.png")
else:
out_paths.append(save_dir / f"{timestamp}.png")
self.log.info(f"saving {out_paths[-1]}")
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
# resize
if resize is None or resize is True:
resize = self.resize_resolution
elif resize is False:
resize = None
if resize is not None:
frame = cv2.resize(frame, resize, interpolation=cv2.INTER_LINEAR)
# mask
if mask is None or mask is True:
mask = self.mask_zones
elif mask is False:
mask = None
if mask is not None:
height, width, channels = frame.shape
out = np.full([height, width, channels], [0] * channels)
for zone_name in mask:
zone = self.bench.zones[zone_name]["box"]
out[zone[1]:zone[3], zone[0]:zone[2]] = frame[zone[1]:zone[3], zone[0]:zone[2]]
else:
out = frame
# save frame
cv2.imwrite(str(out_paths[-1]), out)
if vision is not None:
if suffix is not None:
out_paths.append(save_dir / f"{timestamp}.{suffix}.json")
else:
out_paths.append(save_dir / f"{timestamp}.json")
self.log.info(f"saving {out_paths[-1]}")
# save vision
with open(out_paths[-1], "w") as f:
json.dump(vision, f)
return out_paths
def remove_older_images_if_needed(self):
if self.minimum_disk_free_space_gb is None:

View File

@ -46,19 +46,19 @@ class Archive(Widget):
replace_widget(self, "crud_w", self.crud)
self.selected = None
self.print_b.setEnabled(False)
self.crud().db_tw.setSelectionBehavior(QAbstractItemView.SelectRows)
self.crud().db_tw.setSelectionMode(QAbstractItemView.SingleSelection)
self.crud().db_tw.itemSelectionChanged.connect(self.check)
self.crud.db_tw.setSelectionBehavior(QAbstractItemView.SelectRows)
self.crud.db_tw.setSelectionMode(QAbstractItemView.SingleSelection)
self.crud.db_tw.itemSelectionChanged.connect(self.check)
self.print_b.clicked.connect(self.print_label)
def check(self):
if not self.crud().modified:
selected = self.crud().get_selected_rows()
if not self.crud.modified:
selected = self.crud.get_selected_rows()
if len(selected) == 1:
selected = selected[0] - 1 # - 1 because rn starts from 1 (filters line)
if selected >= 0 and selected < len(self.crud().data_index):
selected = self.crud().data_index[selected]
self.selected = self.crud().db.table_model.get_by_id(selected)
if selected >= 0 and selected < len(self.crud.data_index):
selected = self.crud.data_index[selected]
self.selected = self.crud.db.table_model.get_by_id(selected)
self.print_b.setEnabled(True)
return
self.selected = None

View File

@ -46,19 +46,19 @@ class Autotests_Archive(Widget):
replace_widget(self, "crud_w", self.crud)
self.selected = None
self.print_b.setEnabled(False)
self.crud().db_tw.setSelectionBehavior(QAbstractItemView.SelectRows)
self.crud().db_tw.setSelectionMode(QAbstractItemView.SingleSelection)
self.crud().db_tw.itemSelectionChanged.connect(self.check)
self.crud.db_tw.setSelectionBehavior(QAbstractItemView.SelectRows)
self.crud.db_tw.setSelectionMode(QAbstractItemView.SingleSelection)
self.crud.db_tw.itemSelectionChanged.connect(self.check)
self.print_b.clicked.connect(self.print_label)
def check(self):
if not self.crud().modified:
selected = self.crud().get_selected_rows()
if not self.crud.modified:
selected = self.crud.get_selected_rows()
if len(selected) == 1:
selected = selected[0] - 1 # - 1 because rn starts from 1 (filters line)
if selected >= 0 and selected < len(self.crud().data_index):
selected = self.crud().data_index[selected]
self.selected = self.crud().db.table_model.get_by_id(selected)
if selected >= 0 and selected < len(self.crud.data_index):
selected = self.crud.data_index[selected]
self.selected = self.crud.db.table_model.get_by_id(selected)
self.print_b.setEnabled(True)
return
self.selected = None

View File

@ -26,6 +26,18 @@
<string>Crud</string>
</property>
<layout class="QGridLayout" name="gridLayout_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 row="0" column="0">
<widget class="QGroupBox" name="db_gb">
<property name="sizePolicy">

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>28</width>
<height>28</height>
<width>94</width>
<height>16</height>
</rect>
</property>
<property name="sizePolicy">
@ -20,6 +20,18 @@
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<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 row="0" column="0">
<widget class="QWidget" name="centralWidget" native="true">
<property name="sizePolicy">

View File

@ -1,4 +1,10 @@
from PyQt5.QtGui import QColor
def calc_foreground_color(background_color):
if type(background_color) is QColor:
background_color = [background_color.red(), background_color.green(), background_color.blue(), ]
to_qcolor = True
colors = []
for c in background_color[:3]:
c /= 255 # 8bit to sRGB
@ -8,6 +14,8 @@ def calc_foreground_color(background_color):
c = ((c + 0.055) / 1.055) ** 2.4
colors.append(c)
# Luminance
L = 0.2126 * colors[0] + 0.7152 * colors[1] + 0.0722 * colors[1]
L = 0.2126 * colors[0] + 0.7152 * colors[1] + 0.0722 * colors[2]
foreground_color = [(1 - round(L)) * 255] * 3
if to_qcolor:
foreground_color = QColor(*foreground_color)
return foreground_color

View File

@ -6,14 +6,26 @@
<rect>
<x>0</x>
<y>0</y>
<width>987</width>
<height>210</height>
<width>955</width>
<height>161</height>
</rect>
</property>
<property name="windowTitle">
<string>Leak Step Editor</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<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 row="1" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="font">

View File

@ -6,14 +6,26 @@
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
<width>347</width>
<height>50</height>
</rect>
</property>
<property name="windowTitle">
<string>Recipe Selection</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<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 row="1" column="0">
<widget class="QPushButton" name="select_b">
<property name="sizePolicy">

View File

@ -38,18 +38,18 @@ class Test(Widget):
self.step = None
# INIT CYCLE STATES
self.cycle_available_steps = {
# "assembly_1": Test_Assembly(self.select_step_img("assembly_1"), u"INSERIRE SENSORE"),
"autotest": Test_Assembly(None, u"ESEGUIRE PROCEDURA DI AUTOTEST", Test_Autotest()),
"barcodes": Test_Assembly(self.select_step_img("scan"), u"LEGGERE IL BARCODE DEL PEZZO DA COLLAUDARE", Test_Barcodes()),
"done": Test_Assembly(self.select_step_img("success"), u"COLLAUDO COMPLETATO"),
"emergency": Test_Assembly(self.select_step_img("reset_emergency"), u"EMERGENZA INTERVENUTA - RIPRISTINARE PULSANTE E SELEZIONARE \"RESET EMERGENZA\" DAL MEN\u00d9 \"STRUMENTI\""),
"fail": Test_Assembly(self.select_step_img("fail"), u"CICLO INTERROTTO"),
"leak": Test_Assembly(None, u"PREMERE START PER INIZIARE LA PROVA TENUTA", Test_Leak(components=self.components, recipe=self.recipe, step=self.step)),
"print": Test_Assembly(self.select_step_img("print"), u"STAMPA ETICHETTA IN CORSO"),
"select_recipe": Test_Assembly(None, u"SELEZIONARE IL CODICE DA COLLAUDARE", Recipe_Selection()),
"vision": Test_Assembly(None, u"VERIFICARE CONTROLLO CON TELECAMERA", Test_Vision(components=self.components, recipe=self.recipe, step=self.step)),
"wait": Test_Assembly(self.select_step_img("wait"), u"ATTENDERE - PAUSA INTER CICLO"),
None: Test_Assembly(self.select_step_img("warning"), u"ATTENZIONE - LA RICETTA SELEZIONATA NON CONTIENE FASI DI TEST"),
# "assembly_1": Test_Assembly(img_path=self.select_step_img("assembly_1"), text=u"INSERIRE SENSORE", widget=None),
"autotest": Test_Assembly(img_path=None, text=u"ESEGUIRE PROCEDURA DI AUTOTEST", widget=Test_Autotest()),
"barcodes": Test_Assembly(img_path=self.select_step_img("scan"), text=u"LEGGERE IL BARCODE DEL PEZZO DA COLLAUDARE", widget=Test_Barcodes()),
"done": Test_Assembly(img_path=self.select_step_img("success"), text=u"COLLAUDO COMPLETATO", widget=None),
"emergency": Test_Assembly(img_path=self.select_step_img("reset_emergency"), text=u"EMERGENZA INTERVENUTA - RIPRISTINARE PULSANTE E SELEZIONARE \"RESET EMERGENZA\" DAL MEN\u00d9 \"STRUMENTI\"", widget=None),
"fail": Test_Assembly(img_path=self.select_step_img("fail"), text=u"CICLO INTERROTTO", widget=None),
"leak": Test_Assembly(img_path=None, text=None, widget=Test_Leak(components=self.components, recipe=self.recipe, step=self.step)),
"print": Test_Assembly(img_path=self.select_step_img("print"), text=u"STAMPA ETICHETTA IN CORSO", widget=None),
"select_recipe": Test_Assembly(img_path=None, text=u"SELEZIONARE IL CODICE DA COLLAUDARE", widget=Recipe_Selection()),
"vision": Test_Assembly(img_path=None, text=u"VERIFICARE CONTROLLO CON TELECAMERA", widget=Test_Vision(components=self.components, recipe=self.recipe, step=self.step)),
"wait": Test_Assembly(img_path=self.select_step_img("wait"), text=u"ATTENDERE - PAUSA INTER CICLO", widget=None),
None: Test_Assembly(img_path=self.select_step_img("warning"), text=u"ATTENZIONE - LA RICETTA SELEZIONATA NON CONTIENE FASI DI TEST", widget=None),
}
self.cycle_steps = None
self.cycle_index = -1
@ -155,16 +155,19 @@ class Test(Widget):
# RESET TEST DATA
self.data = {"ok": True, "overridden": False}
self.archived = None
# COUNT RESET
self.pieces[0] = 0
self.pieces[1] = 0
elif action == "fail":
self.log.info(f"cycle next: action: {action!r}")
# COUNT FAIL
self.pieces[1] += 1
# FAIL AND RESTART TEST
self.step = Steps(type="fail")
self.cycle_index = -1
# RESET TEST DATA
self.data = {"ok": True, "overridden": False}
self.archived = None
# COUNT FAIL
self.pieces[1] += 1
elif action is not None:
raise NotImplementedError(f"cycle next: action {action!r} is not a valid action")
# if action did not set the next cycle step
@ -270,11 +273,13 @@ class Test(Widget):
def done(self, ok=True):
self.log.info("cycle done")
t = self.data.get("vision", {}).get(0, {}).get("time", None)
f = self.data.get("vision", {}).get(0, {}).get("frame", None)
if t is not None and f is not None:
file_path = self.components["vision_saver"].save(t, f)
self.log.info(f"cycle vision image saved locally: {file_path!r}")
vision_test_1 = self.data.get("vision", {}).get(0, {})
out_paths = self.components["vision_saver"].save(
save_time=vision_test_1.get("time", None),
frame=vision_test_1.get("frame", None),
# vision=vision_test_1.get("detections", None),
)
self.log.info(f"cycle vision saved locally: {out_paths!r}")
for vision in self.data["vision"].values():
vision.pop("frame", None)
vision.pop("render", None)
@ -287,10 +292,14 @@ class Test(Widget):
def print(self, archived=None):
self.log.info("cycle print")
# LABEL PRINT
leak_test_1 = self.data.get("leak", {}).get(0, {})
leak_test_1_step_spec = leak_test_1.get("step", {}).get("spec", {})
leak_test_1_results = leak_test_1.get("results", {})
leak_test_1_results_data = leak_test_1_results.get("data", {})
datamatrix = str(archived.recipe.part_number) + archived.time.strftime("%m%y") + f"{archived.id:0>5}"
pmax = self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("test_pressure", "-")
pmin = self.data.get("leak", {}).get(0, {}).get("results", {}).get("data", {}).get("Running test: pressure at the end of settling", "-")
leak = self.data.get("leak", {}).get(0, {}).get("results", {}).get("data", {}).get("Running test: measured leak", "-")
pmax = leak_test_1_step_spec.get("test_pressure", "-")
pmin = leak_test_1_results_data.get("Running test: pressure at the end of settling", "-")
leak = leak_test_1_results_data.get("Running test: measured leak", "-")
context = {
"DATAMATRIX": datamatrix,
"DATAMATRIX_TEXT": datamatrix,
@ -302,13 +311,13 @@ class Test(Widget):
"PMAX": f"{pmax:.3f}" if type(pmax) is not str else pmax,
"PMIN": f"{pmin:.3f}" if type(pmin) is not str else pmin,
"LEAK": f"{leak:.3f}" if type(leak) is not str else leak,
"TFILL": str(self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("filling_time", "-")),
"TSTAB": str(self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("settling_time", "-")),
"TTEST": str(self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("test_time", "-")),
"MAXLEAK": str(self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("test_pressure_max_delta", "-")),
"PTESTMIN": str(self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("test_pressure_min_delta", "-")),
"TFILL": str(leak_test_1_step_spec.get("filling_time", "-")),
"TSTAB": str(leak_test_1_step_spec.get("settling_time", "-")),
"TTEST": str(leak_test_1_step_spec.get("test_time", "-")),
"MAXLEAK": str(leak_test_1_step_spec.get("test_pressure_max_delta", "-")),
"PTESTMIN": str(leak_test_1_step_spec.get("test_pressure_min_delta", "-")),
"RESULT_L1": "ESITO" + str(" FORZATO" if self.data.get("overridden", False) else ""),
"RESULT_L2": str("CONFORME" if self.data.get("leak", {}).get(0, {}).get("results", {}).get("ok", False) else "SCARTO"),
"RESULT_L2": str("CONFORME" if leak_test_1_results.get("ok", False) else "SCARTO"),
}
self.components["label_printer"].print_label("EtichettaR5", context=context)
self.log.info(f"cycle printed: {context!r}")

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1003</width>
<height>255</height>
<width>804</width>
<height>86</height>
</rect>
</property>
<property name="windowTitle">

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>422</width>
<height>139</height>
<width>184</width>
<height>103</height>
</rect>
</property>
<property name="windowTitle">

View File

@ -1,3 +1,5 @@
import weakref
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
from ui.helpers import replace_widget
@ -39,6 +41,8 @@ class Test_Assembly(Widget):
else:
if hasattr(self, attr):
delattr(self, attr)
if hasattr(widget, "set_parent_assembly_widget"):
widget.set_parent_assembly_widget(weakref.ref(self))
self.widget.setVisible(True)
else:
self.widget.setVisible(False)

View File

@ -7,13 +7,25 @@
<x>0</x>
<y>0</y>
<width>94</width>
<height>108</height>
<height>89</height>
</rect>
</property>
<property name="windowTitle">
<string>Test Assembly</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<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 row="0" column="0">
<widget class="QLabel" name="img_l">
<property name="sizePolicy">

View File

@ -6,14 +6,27 @@
<rect>
<x>0</x>
<y>0</y>
<width>18</width>
<height>18</height>
<width>94</width>
<height>16</height>
</rect>
</property>
<property name="windowTitle">
<string>Test Autotest</string>
</property>
<layout class="QGridLayout" name="gridLayout"/>
<layout class="QGridLayout" name="gridLayout">
<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>
</layout>
</widget>
<resources/>
<connections/>

View File

@ -6,14 +6,26 @@
<rect>
<x>0</x>
<y>0</y>
<width>187</width>
<height>162</height>
<width>169</width>
<height>144</height>
</rect>
</property>
<property name="windowTitle">
<string>Test Barcodes</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<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 row="13" column="0">
<widget class="QLineEdit" name="barcodes_le"/>
</item>

View File

@ -15,6 +15,10 @@ class Test_Leak(Test_Test):
self.components["tecna_t3"].write_recipe(self.recipe, self.step)
self.get_connection = self.components["tecna_t3"].out.connect(self.get)
self.components["tecna_t3"].resume()
if self.parent_assembly_widget is not None:
self.parent_assembly_widget().set_text(text=None)
self.start_b.setEnabled(False)
self.stop_b.setEnabled(False)
def stop(self):
# disable tes loop
@ -22,6 +26,10 @@ class Test_Leak(Test_Test):
self.components["tecna_t3"].pause()
self.disconnect(self.get_connection)
super().stop()
if self.parent_assembly_widget is not None:
self.parent_assembly_widget().set_text(text=None)
self.start_b.setEnabled(False)
self.stop_b.setEnabled(False)
def get(self, data=None, override=False):
if self.done: # avoid proccessing if completed
@ -62,6 +70,21 @@ class Test_Leak(Test_Test):
if type(v) is float:
v = round(v, 2)
l.setText(str(v))
if d.get("Running test: active phase", None) in {
"WAITING START",
"ATTESA START",
"WAITING THE START OF A NEW TEST"
"FINE TEST",
}:
if self.parent_assembly_widget is not None:
self.parent_assembly_widget().set_text(text=u"PREMERE START PER INIZIARE LA PROVA TENUTA")
self.start_b.setEnabled(True)
self.stop_b.setEnabled(False)
else:
if self.parent_assembly_widget is not None:
self.parent_assembly_widget().set_text(text=u"PROVA TENUTA IN CORSO")
self.start_b.setEnabled(False)
self.stop_b.setEnabled(True)
super().visualize(data)
def save_last(self):

View File

@ -6,14 +6,26 @@
<rect>
<x>0</x>
<y>0</y>
<width>980</width>
<height>704</height>
<width>370</width>
<height>491</height>
</rect>
</property>
<property name="windowTitle">
<string>Test Leak</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<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 row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_2">
<property name="font">

View File

@ -1,4 +1,5 @@
import sys
import weakref
from lib.helpers import timing
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
@ -40,6 +41,7 @@ class Test_Test(Widget):
self.override_b.clicked.connect(self.override)
self.override_b.setEnabled(True)
# setup vision staus gui
self.parent_assembly_widget = None
self.status_imgs_full = {
True: QPixmap("src/ui/imgs/success.png"),
"": QPixmap("src/ui/imgs/neo.ico"),
@ -61,6 +63,14 @@ class Test_Test(Widget):
self.status_palettes[""].setColor(QPalette.Base, QColor(255, 255, 0))
self.visualize()
def set_parent_assembly_widget(self, parent_assembly_widget=None):
if parent_assembly_widget is None:
self.parent_assembly_widget = None
elif type(parent_assembly_widget) is weakref.ReferenceType:
self.parent_assembly_widget = parent_assembly_widget
else:
self.parent_assembly_widget = weakref.ref(parent_assembly_widget)
def start(self, recipe=None, step=None):
self.admin_challenged = False
self.recipe = recipe

View File

@ -32,7 +32,7 @@
<property name="title">
<string>Risultato</string>
</property>
<layout class="QFormLayout" name="formLayout">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QProgressBar" name="ok_counter_pb">
<property name="sizePolicy">
@ -99,7 +99,7 @@
</sizepolicy>
</property>
<property name="text">
<string>SALVA IMMAGINE</string>
<string>SALVA</string>
</property>
</widget>
</item>

View File

@ -1,7 +1,9 @@
import sys
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtGui import QColor, QImage, QPalette, QPixmap
from PyQt5.QtWidgets import QHeaderView, QProgressBar, QTableWidgetItem
from ui.helpers import calc_foreground_color
from ui.test_test import Test_Test
@ -13,7 +15,17 @@ class Test_Vision(Test_Test):
self.ok_counter_limit = 2
else:
self.ok_counter_limit = 1
# DETECTIONS TABLE
self.results_table_headers = {
"class": "RILEVATO",
"expected": "ATTESO",
"score": "PUNTEGGIO",
}
super().__init__(components=components, recipe=recipe, step=step)
self.results_tw.setColumnCount(len(self.results_table_headers))
self.results_tw.setHorizontalHeaderLabels(self.results_table_headers.values())
self.results_tw.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.results_tw.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
def start(self, recipe=None, step=None):
super().start(recipe=recipe, step=step)
@ -27,6 +39,9 @@ class Test_Vision(Test_Test):
self.components["vision"].resume()
self.components["galaxy_camera"].resume()
self.request_frame.emit() # request first frame
# DETECTIONS TABLE
# hide previous results
self.results_tw.setRowCount(0)
def stop(self):
# disable camera-vision loop
@ -52,12 +67,36 @@ class Test_Vision(Test_Test):
"render": data["vision"].get("render", None),
}], override=override)
@staticmethod
def tr(text):
translations = {
"no_detection": "non_rilevato",
"_": " ",
"big": "grande",
"black": "nero",
"blue": "blu",
"brown": "marrone",
"empty": "vuoto",
"green": "verde",
"ko": "ko",
"ok": "ok",
"orange": "arancione",
"red": "rosso",
"small": "piccolo",
"white": "bianco",
"yellow": "giallo",
}
for s, t in translations.items():
text = text.replace(s, t)
return text
def visualize(self, data=None):
if data is None:
data = {}
overridden = data.get("overridden", False)
frame = data.get("frame", None)
render = data.get("render", None)
results = data.get("results", {}).get("results", None)
if overridden:
img = self.status_imgs_full["warning"]
elif render is not None:
@ -81,12 +120,84 @@ class Test_Vision(Test_Test):
self.components["neo_pixels"].set_all_pixel_color("#ff0000")
else:
self.components["neo_pixels"].set_all_pixel_color("#ffffff")
# DETECTIONS TABLE
if results is not None:
self.results_tw.setRowCount(len(results))
self.results_tw.setVerticalHeaderLabels(list(results))
for r, [zone_name, result] in enumerate(results.items()):
for c, header in enumerate(self.results_table_headers):
if header == "class":
i = self.results_tw.item(r, c)
if i is None:
i = QTableWidgetItem()
self.results_tw.setItem(r, c, i)
detection = result.get("detection", None)
if detection is not None:
text = self.tr(detection["class"]["name"])
color = QColor(detection["class"]["color"])
else:
text = ""
color = QColor("#ffffff")
i.setText(text)
i.setBackground(color)
i.setForeground(calc_foreground_color(color))
elif header == "expected":
i = self.results_tw.item(r, c)
if i is None:
i = QTableWidgetItem()
self.results_tw.setItem(r, c, i)
expected = result.get("expected", None)
if expected is not None:
text = self.tr(expected["name"])
color = QColor(expected["color"])
else:
text = ""
color = QColor("#ffffff")
i.setText(text)
i.setBackground(color)
i.setForeground(calc_foreground_color(color))
elif header == "score":
w = self.results_tw.cellWidget(r, c)
if w is None:
w = QProgressBar()
w.setRange(0, 100)
self.results_tw.setCellWidget(r, c, w)
score = result.get("detection", {}).get("score", None)
ok = result.get("ok", None)
if score is not None:
value = int(score * 100)
else:
value = 0
w.setValue(value)
if ok is not None:
if ok:
if score is not None:
color = QColor(int(255 - max(score - 0.5, 0) / 0.5 * 255), 255, 0)
else:
color = QColor("#00ff00")
else:
color = QColor("#ff0000")
else:
color = QColor("#0000ff")
p = QPalette()
p.setColor(QPalette.Highlight, color)
w.setPalette(p)
else:
raise NotImplementedError()
else:
self.results_tw.setRowCount(0)
super().visualize(data, img=img)
def save_last(self):
if self.last is None:
return
self.components["vision_saver"].save(self.last["time"], self.last["frame"])
self.components["vision_saver"].save(
save_time=self.last.get("time", None),
frame=self.last.get("frame", None),
vision=self.last.get("detections", None),
suffix="manual_save",
resize=[256, 256],
)
def emit_ok(self):
super().emit_ok()

View File

@ -6,15 +6,27 @@
<rect>
<x>0</x>
<y>0</y>
<width>618</width>
<height>835</height>
<width>880</width>
<height>629</height>
</rect>
</property>
<property name="windowTitle">
<string>Test Vision</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<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 row="3" column="0">
<widget class="QPushButton" name="override_b">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
@ -27,13 +39,32 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<item row="3" column="1">
<widget class="QPushButton" name="save_b">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>SALVA IMMAGINE</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Risultato</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<item row="1" column="0">
<widget class="QProgressBar" name="ok_counter_pb">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
@ -49,7 +80,7 @@
</property>
</widget>
</item>
<item row="0" column="1">
<item row="1" column="1">
<widget class="QLabel" name="state_l">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
@ -72,35 +103,77 @@
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="img_l">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>600</width>
<height>700</height>
</size>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="save_b">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>SALVA IMMAGINE</string>
</property>
<widget class="QWidget" name="widget" native="true">
<layout class="QGridLayout" name="gridLayout_4">
<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 row="0" column="2">
<widget class="QGroupBox" name="groupBox_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Visione</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<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 row="0" column="0">
<widget class="QTableWidget" name="results_tw">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="img_l">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>612</width>
<height>512</height>
</size>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>

View File

@ -6,14 +6,26 @@
<rect>
<x>0</x>
<y>0</y>
<width>174</width>
<height>91</height>
<width>143</width>
<height>67</height>
</rect>
</property>
<property name="windowTitle">
<string>Vision Step Edito</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<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 row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_6">
<property name="font">

View File

@ -13,7 +13,6 @@
<property name="windowTitle">
<string>Window</string>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>