diff --git a/config/machine_settings/defaults.ini b/config/machine_settings/defaults.ini
index 6e689c4..8be6fe5 100644
--- a/config/machine_settings/defaults.ini
+++ b/config/machine_settings/defaults.ini
@@ -44,7 +44,4 @@ port: COM1
baudrate: 9600
[vision]
-detection_threshold: 0.5
neural_network: hs5-20000
-
-; recipes_path: ./config/vision_test_recipes
diff --git a/config/vision/recipes/termorestringente_923578.ini b/config/vision/recipes/termorestringente_923578.ini
index 9f9a83d..861193a 100644
--- a/config/vision/recipes/termorestringente_923578.ini
+++ b/config/vision/recipes/termorestringente_923578.ini
@@ -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
diff --git a/simulate.sh b/simulate.sh
index 3406f97..cad15a1 100755
--- a/simulate.sh
+++ b/simulate.sh
@@ -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 \
diff --git a/src/components/tecna_marposs_provaset_t3p_registers.py b/src/components/tecna_marposs_provaset_t3p_registers.py
index 9ab50ce..9c9195c 100644
--- a/src/components/tecna_marposs_provaset_t3p_registers.py
+++ b/src/components/tecna_marposs_provaset_t3p_registers.py
@@ -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, }],
diff --git a/src/components/vision.py b/src/components/vision.py
index 378c0a7..5bdff57 100644
--- a/src/components/vision.py
+++ b/src/components/vision.py
@@ -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:
diff --git a/src/components/vision_saver.py b/src/components/vision_saver.py
index e5e25ac..1428572 100755
--- a/src/components/vision_saver.py
+++ b/src/components/vision_saver.py
@@ -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:
diff --git a/src/ui/archive/archive.py b/src/ui/archive/archive.py
index 13ba81b..d706234 100755
--- a/src/ui/archive/archive.py
+++ b/src/ui/archive/archive.py
@@ -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
diff --git a/src/ui/autotests_archive/autotests_archive.py b/src/ui/autotests_archive/autotests_archive.py
index 3d24536..8768564 100755
--- a/src/ui/autotests_archive/autotests_archive.py
+++ b/src/ui/autotests_archive/autotests_archive.py
@@ -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
diff --git a/src/ui/crud/crud.ui b/src/ui/crud/crud.ui
index a2b62b4..0c12501 100755
--- a/src/ui/crud/crud.ui
+++ b/src/ui/crud/crud.ui
@@ -26,6 +26,18 @@
Crud
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
-
diff --git a/src/ui/dialog/dialog.ui b/src/ui/dialog/dialog.ui
index ccca71d..a854bcc 100644
--- a/src/ui/dialog/dialog.ui
+++ b/src/ui/dialog/dialog.ui
@@ -6,8 +6,8 @@
0
0
- 28
- 28
+ 94
+ 16
@@ -20,6 +20,18 @@
Dialog
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
-
diff --git a/src/ui/helpers/calc_foreground_color.py b/src/ui/helpers/calc_foreground_color.py
index f60ae30..3c7b6b4 100644
--- a/src/ui/helpers/calc_foreground_color.py
+++ b/src/ui/helpers/calc_foreground_color.py
@@ -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
diff --git a/src/ui/leak_step_editor/leak_step_editor.ui b/src/ui/leak_step_editor/leak_step_editor.ui
index c6bb528..ebceec0 100644
--- a/src/ui/leak_step_editor/leak_step_editor.ui
+++ b/src/ui/leak_step_editor/leak_step_editor.ui
@@ -6,14 +6,26 @@
0
0
- 987
- 210
+ 955
+ 161
Leak Step Editor
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
-
diff --git a/src/ui/recipe_selection/recipe_selection.ui b/src/ui/recipe_selection/recipe_selection.ui
index cb59aef..cfe82d6 100644
--- a/src/ui/recipe_selection/recipe_selection.ui
+++ b/src/ui/recipe_selection/recipe_selection.ui
@@ -6,14 +6,26 @@
0
0
- 800
- 600
+ 347
+ 50
Recipe Selection
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
-
diff --git a/src/ui/test/test.py b/src/ui/test/test.py
index dbf5031..de5a7d8 100755
--- a/src/ui/test/test.py
+++ b/src/ui/test/test.py
@@ -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}")
diff --git a/src/ui/test/test.ui b/src/ui/test/test.ui
index 18f8721..c4d3b5d 100755
--- a/src/ui/test/test.ui
+++ b/src/ui/test/test.ui
@@ -6,8 +6,8 @@
0
0
- 1003
- 255
+ 804
+ 86
diff --git a/src/ui/test_admin_permission/test_admin_permission.ui b/src/ui/test_admin_permission/test_admin_permission.ui
index 5a75bec..d117bec 100644
--- a/src/ui/test_admin_permission/test_admin_permission.ui
+++ b/src/ui/test_admin_permission/test_admin_permission.ui
@@ -6,8 +6,8 @@
0
0
- 422
- 139
+ 184
+ 103
diff --git a/src/ui/test_assembly/test_assembly.py b/src/ui/test_assembly/test_assembly.py
index d003c79..b4b131b 100755
--- a/src/ui/test_assembly/test_assembly.py
+++ b/src/ui/test_assembly/test_assembly.py
@@ -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)
diff --git a/src/ui/test_assembly/test_assembly.ui b/src/ui/test_assembly/test_assembly.ui
index 2bf66ac..90a6d07 100755
--- a/src/ui/test_assembly/test_assembly.ui
+++ b/src/ui/test_assembly/test_assembly.ui
@@ -7,13 +7,25 @@
0
0
94
- 108
+ 89
Test Assembly
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
-
diff --git a/src/ui/test_autotest/test_autotest.ui b/src/ui/test_autotest/test_autotest.ui
index ab44c93..42bf393 100755
--- a/src/ui/test_autotest/test_autotest.ui
+++ b/src/ui/test_autotest/test_autotest.ui
@@ -6,14 +6,27 @@
0
0
- 18
- 18
+ 94
+ 16
Test Autotest
-
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
diff --git a/src/ui/test_barcodes/test_barcodes.ui b/src/ui/test_barcodes/test_barcodes.ui
index 994686b..b002d36 100644
--- a/src/ui/test_barcodes/test_barcodes.ui
+++ b/src/ui/test_barcodes/test_barcodes.ui
@@ -6,14 +6,26 @@
0
0
- 187
- 162
+ 169
+ 144
Test Barcodes
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
-
diff --git a/src/ui/test_leak/test_leak.py b/src/ui/test_leak/test_leak.py
index 649e9c4..98d1167 100644
--- a/src/ui/test_leak/test_leak.py
+++ b/src/ui/test_leak/test_leak.py
@@ -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):
diff --git a/src/ui/test_leak/test_leak.ui b/src/ui/test_leak/test_leak.ui
index af9e28f..90ef48e 100644
--- a/src/ui/test_leak/test_leak.ui
+++ b/src/ui/test_leak/test_leak.ui
@@ -6,14 +6,26 @@
0
0
- 980
- 704
+ 370
+ 491
Test Leak
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
-
diff --git a/src/ui/test_test/test_test.py b/src/ui/test_test/test_test.py
index 2acd5a2..77a61c0 100644
--- a/src/ui/test_test/test_test.py
+++ b/src/ui/test_test/test_test.py
@@ -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
diff --git a/src/ui/test_test/test_test.ui b/src/ui/test_test/test_test.ui
index 79f63c4..be761cb 100644
--- a/src/ui/test_test/test_test.ui
+++ b/src/ui/test_test/test_test.ui
@@ -32,7 +32,7 @@
Risultato
-
+
-
@@ -99,7 +99,7 @@
- SALVA IMMAGINE
+ SALVA
diff --git a/src/ui/test_vision/test_vision.py b/src/ui/test_vision/test_vision.py
index 6d3c7db..e90dcb5 100644
--- a/src/ui/test_vision/test_vision.py
+++ b/src/ui/test_vision/test_vision.py
@@ -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()
diff --git a/src/ui/test_vision/test_vision.ui b/src/ui/test_vision/test_vision.ui
index 2d09b26..bb9707b 100644
--- a/src/ui/test_vision/test_vision.ui
+++ b/src/ui/test_vision/test_vision.ui
@@ -6,15 +6,27 @@
0
0
- 618
- 835
+ 880
+ 629
Test Vision
- -
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
@@ -27,13 +39,32 @@
- -
+
-
+
+
+
+ 0
+ 0
+
+
+
+ SALVA IMMAGINE
+
+
+
+ -
+
+
+ 0
+ 0
+
+
Risultato
-
-
+
-
@@ -49,7 +80,7 @@
- -
+
-
@@ -72,35 +103,77 @@
-
-
-
-
- 0
- 0
-
-
-
-
- 600
- 700
-
-
-
- -
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- SALVA IMMAGINE
-
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Visione
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 612
+ 512
+
+
+
+ -
+
+
+
+
diff --git a/src/ui/vision_step_editor/vision_step_editor.ui b/src/ui/vision_step_editor/vision_step_editor.ui
index 5842a40..a146aec 100644
--- a/src/ui/vision_step_editor/vision_step_editor.ui
+++ b/src/ui/vision_step_editor/vision_step_editor.ui
@@ -6,14 +6,26 @@
0
0
- 174
- 91
+ 143
+ 67
Vision Step Edito
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
-
diff --git a/src/ui/window/window.ui b/src/ui/window/window.ui
index 7a20ddc..8c7fc91 100644
--- a/src/ui/window/window.ui
+++ b/src/ui/window/window.ui
@@ -13,7 +13,6 @@
Window
-