diff --git a/config/machine_settings/defaults.ini b/config/machine_settings/defaults.ini index 2d03bab..6e689c4 100644 --- a/config/machine_settings/defaults.ini +++ b/config/machine_settings/defaults.ini @@ -17,6 +17,7 @@ balance_blue: 1.5 time_format: %Y-%m-%d_%H-%M-%S path: ./data/images minimum_disk_free_space_gb: 20 +resize_resolution: 612x512 [archive_synchronizer] archive_endpoint: https://r5portal.it/api/echo/ diff --git a/src/components/neo_pixels.py b/src/components/neo_pixels.py index cf11052..40e9c1a 100644 --- a/src/components/neo_pixels.py +++ b/src/components/neo_pixels.py @@ -1,7 +1,8 @@ import sys -import time import traceback +from PyQt5.QtCore import QThread + if "--sim-serial" in sys.argv: from components.dummies.serial import serial else: @@ -16,16 +17,8 @@ C_UINT32_MAX = c_uint32(-1).value class NeoPixels(Component): def __init__(self, config=None, name=None): + self.conn = None super().__init__(config=config, name=name, threaded=False) - self.conn = serial.Serial( - self.port, - baudrate=self.baudrate, - stopbits=self.stopbits, - parity=self.parity, - bytesize=self.bytesize, - ) - time.sleep(5) - self.set_all_pixel_color("#000000") def config_changed(self): self.port = self.config[self.name]["port"] @@ -33,6 +26,17 @@ class NeoPixels(Component): self.stopbits = getattr(serial, self.config[self.name].get("stopbits", "stopbits_one").upper()) self.parity = getattr(serial, self.config[self.name].get("parity", "parity_none").upper()) self.bytesize = getattr(serial, self.config[self.name].get("bytesize", "eightbits").upper()) + if self.conn is not None: + self.conn.close() + self.conn = serial.Serial( + self.port, + baudrate=self.baudrate, + stopbits=self.stopbits, + parity=self.parity, + bytesize=self.bytesize, + ) + QThread.msleep(5000) + self.set_all_pixel_color("#000000") def set(self, pixel, color): if type(pixel) is not int or pixel < 0 or pixel > C_UINT32_MAX: @@ -44,7 +48,7 @@ class NeoPixels(Component): response = self.conn.readline() if response != b"ok\n": self.log.error(response.strip()) - # conn.close() + # self.conn.close() except serial.serialutil.SerialException: self.log.exception(traceback.format_exc()) diff --git a/src/components/tecna_marposs_provaset_t3p.py b/src/components/tecna_marposs_provaset_t3p.py index 6db4684..c1c5376 100644 --- a/src/components/tecna_marposs_provaset_t3p.py +++ b/src/components/tecna_marposs_provaset_t3p.py @@ -180,7 +180,7 @@ class TecnaMarpossProvasetT3P(ModbusComponent): "Running test: test type", "Running test: sequence index", ]} - if info["Running test: active phase"] == "FINE TEST": + if info["Running test: active phase"] == "FINE TEST": # "END TEST, WAITING THE START OF A NEW TEST": info.update(self.get_test_results()) self.log.debug(str(info)) super()._get([info]) diff --git a/src/components/tecna_marposs_provaset_t3p_registers.py b/src/components/tecna_marposs_provaset_t3p_registers.py index cdfeedf..9ab50ce 100644 --- a/src/components/tecna_marposs_provaset_t3p_registers.py +++ b/src/components/tecna_marposs_provaset_t3p_registers.py @@ -7,7 +7,7 @@ registers = { "Life counter: TOTAL TESTS": [8 - 1, {"dt": "32bit_uint", }], "Real time test pressure output": [11 - 1, {"dt": "32bit_int", "f": 21, }], "Real time differential pressure output": [13 - 1, {"dt": "32bit_int", "f": 22, }], - "Real time pressure line regulator": [15 - 1, {"dt": "16bit_int", "f": 23,"g":0.1 }], + "Real time pressure line regulator": [15 - 1, {"dt": "16bit_int", "f": 23, "g": 0.1}], # TODO: REMOVE "g" "Active alarm flags": [16 - 1, {"dt": "16bit_uint", }], # | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | # | | | | A5 | A4 | A3 | A2 | A1 | | | | | | E3 | E2 | E1 | @@ -28,9 +28,9 @@ registers = { "Running test: flow rate format": [27 - 1, {"dt": "16bit_uint", }], # white "Active test program number": [31 - 1, {"dt": "16bit_uint", }], "Running test: active phase": [32 - 1, {"dt": "16bit_uint", "decoding": { - 0: "ATTESA START", - 10: "CONTROLLO BARCODE", - 20: "ATTESA BARCODE", + 0: "ATTESA START", # "WAITING START", + 10: "CONTROLLO BARCODE", # "CHECK BARCODE", + 20: "ATTESA BARCODE", # "WAITING BARCODE", 30: "START TEST", 40: "START CAGE", 50: "START PLUG", @@ -38,10 +38,10 @@ registers = { 70: "WAIT DELAY ACKNOWLEDGE COMMAND", 80: "WAIT DELAY", 90: "WAIT ACKNOWLEDGE", - 100: "T0 PRE-RIEMPIMENTO", - 110: "T1 RIEMPIMENTO", - 120: "T2 ASSESTAMENTO", - 130: "T3 MISURA", + 100: "T0 PRE-RIEMPIMENTO", # "T0 PRE-FILL", + 110: "T1 RIEMPIMENTO", # "T1 FILLING", + 120: "T2 ASSESTAMENTO", # "T2 SETTLING", + 130: "T3 MISURA", # "T3 MEASURE", 140: "WAITING OPERATOR RESULT ACKNOWLEDGE", 150: "RESULT PRESENT", 160: "MARKING", @@ -49,7 +49,7 @@ registers = { 180: "END SEQUENCE PROGRAM", 190: "END PLUG", 200: "END CAGE", - 210: "FINE 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 0d6f169..378c0a7 100644 --- a/src/components/vision.py +++ b/src/components/vision.py @@ -1,29 +1,26 @@ import logging import os import sys -import time import traceback from configparser import ConfigParser from pathlib import Path import cv2 import numpy as np +from PyQt5.QtCore import (QFileSystemWatcher, QLineF, QMutex, QPointF, QRectF, + Qt, QThread, pyqtSignal) +from PyQt5.QtGui import (QBrush, QColor, QFont, QImage, QPainter, QPainterPath, + QPen, QPixmap) from PyQt5.QtWidgets import QApplication -from lib.helpers import time_this +from .component import Component +from .consumer import Consumer if "--no-gpu" in sys.argv: os.environ["CUDA_VISIBLE_DEVICES"] = "-1" import tensorflow as tf from lib.helpers.object_detection.utils import label_map_util -from PyQt5.QtCore import (QFileSystemWatcher, QLineF, QMutex, QPointF, QRectF, - Qt, QThread, pyqtSignal) -from PyQt5.QtGui import (QBrush, QColor, QFont, QImage, QPainter, QPainterPath, - QPen, QPixmap) - -from .component import Component -from .consumer import Consumer if "--no-edgetpu" not in sys.argv: try: @@ -317,8 +314,7 @@ class Vision(Component): if tf_mode is None and "normal" in self.allowed_modes: try: # create tensorflow model - model = tf.saved_model.load(str(self.models_dir / model_name)) - model = model.signatures['serving_default'] + model = tf.saved_model.load(str(self.models_dir / model_name)).signatures["serving_default"] tf_mode = "normal" except Exception: self.log.exception(traceback.format_exc()) @@ -339,14 +335,14 @@ class Vision(Component): self.log.info(f"initialized model {self.model!r} with mode {self.tf_mode!r}") self.loading_model_signal.emit({"status": "done"}) - @time_this def check_features(self, frame, lock=True): if self.interpreter is not None and frame.shape != self.interpreter.get_input_details()[0]["shape"][1:3]: tensor = np.expand_dims(cv2.resize(frame, self.interpreter.get_input_details()[0]["shape"][1:3], interpolation=cv2.INTER_LINEAR), axis=0) else: - frame_resized=cv2.resize(frame, (256,256), interpolation=cv2.INTER_LINEAR) + frame_resized = cv2.resize(frame, (256, 256), interpolation=cv2.INTER_LINEAR) tensor = tf.convert_to_tensor(np.asarray(frame_resized)) tensor = tensor[tf.newaxis, ...] + # tensor = np.expand_dims(frame, axis=0) # Run inference if lock: self.lock.lock() @@ -388,33 +384,36 @@ class Vision(Component): if lock: self.lock.unlock() detections = { - "detection_scores": [scores][0:1], - "detection_boxes": [boxes][0:1], - "detection_classes": [map(lambda class_id: class_id + 1, class_ids)][0:1], + "detection_scores": [scores], + "detection_boxes": [boxes], + "detection_classes": [map(lambda class_id: class_id + 1, class_ids)], } else: detections = self.model(tensor) if lock: self.lock.unlock() + detections = { + "detection_scores": detections["detection_scores"].numpy().tolist(), + "detection_boxes": detections["detection_boxes"].numpy().tolist(), + "detection_classes": detections["detection_classes"], + } # WARNING: results other than the ones related to tensor[-1] will be discarded parsed_detections = [] - for d_box, d_class, d_score in zip( # , d_mask in zip( + for d_score, d_box, d_class in zip( # , d_mask in zip( + detections["detection_scores"][-1], detections["detection_boxes"][-1], detections["detection_classes"][-1], - detections["detection_scores"][-1], # detections["detection_masks"][-1], ): if d_score < self.detection_threshold: continue box = list(d_box) - # box = d_box.numpy().tolist() box = [i * s for i, s in zip(box, frame.shape[:2] * 2)] # rescale detection to frame size detection = { - "class": self.category_index[int(d_class)], - "score": float(d_score), - # "score": d_score.numpy().tolist(), - # "mask": d_mask.numpy().tolist(), + "score": d_score, "box": box, + "class": self.category_index[int(d_class)], + # "mask": d_mask, "center": self.get_center(box), "size": self.get_size(box), } @@ -477,7 +476,6 @@ class Vision(Component): else: raise NotImplementedError(f"invalid zone_detection_filter_mode: {self.zone_detection_filter_mode!r}") elif zone["shape"] == "ellipse": # it's a circle - distance = self.get_distance(detection["center"], zone["center"]) if self.zone_detection_filter_mode == "center_inside": outside_zone = distance > zone["size"][0] / 2 elif self.zone_detection_filter_mode == "box_inside": diff --git a/src/components/vision_saver.py b/src/components/vision_saver.py index ea610c6..e5e25ac 100755 --- a/src/components/vision_saver.py +++ b/src/components/vision_saver.py @@ -17,19 +17,30 @@ class VisionSaver(Component): def config_changed(self): self.location = Path(self.config[self.name]["path"]) os.makedirs(self.location, exist_ok=True) + self.resize_resolution = self.config[self.name].get("resize_resolution", None) + if self.resize_resolution is not None: + self.resize_resolution = list(map(int, self.resize_resolution.split("x"))) self.mask_zones = self.config[self.name].get("mask_zones", None) self.minimum_disk_free_space_gb = self.config[self.name].get("minimum_disk_free_space_gb", None) if self.minimum_disk_free_space_gb is not None: 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): - timestamp = datetime.fromtimestamp(save_time).strftime(self.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) + 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( @@ -41,7 +52,7 @@ class VisionSaver(Component): out[zone[1]:zone[3], zone[0]:zone[2]] = img[zone[1]:zone[3], zone[0]:zone[2]] else: out = img - cv2.imwrite(out_path, out) + cv2.imwrite(str(out_path), out) return out_path def remove_older_images_if_needed(self): diff --git a/src/test/multicomp.py b/src/test/multicomp.py index f23cacd..9ecf60a 100644 --- a/src/test/multicomp.py +++ b/src/test/multicomp.py @@ -2,8 +2,8 @@ import time import serial -#tester_port="/dev/ttyUSB0" -tester_port="COM1" +# tester_port="/dev/ttyUSB0" +tester_port = "COM1" conn = serial.Serial( tester_port, diff --git a/src/test/neo_lights.py b/src/test/neo_lights.py index e64e933..411b994 100644 --- a/src/test/neo_lights.py +++ b/src/test/neo_lights.py @@ -15,7 +15,7 @@ conn = serial.Serial( ) pixel = C_UINT32_MAX time.sleep(5) -color = "#000000" +color = "#ff00ff" conn.write(int.to_bytes(pixel, length=4, byteorder="big") + bytes.fromhex(color[1:7]) + b"\n") response = conn.readline() print(response) diff --git a/src/ui/archive/archive.py b/src/ui/archive/archive.py index d706234..13ba81b 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 8768564..3d24536 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/CopyPastableCrudQTableWidget.py b/src/ui/crud/CopyPastableCrudQTableWidget.py index 298e00b..7709c73 100644 --- a/src/ui/crud/CopyPastableCrudQTableWidget.py +++ b/src/ui/crud/CopyPastableCrudQTableWidget.py @@ -1,4 +1,5 @@ import sys +import weakref from PyQt5.QtCore import QEvent, Qt from PyQt5.QtWidgets import QApplication, QTableWidget @@ -28,7 +29,7 @@ class CopyPastableCrudQTableWidget(QTableWidget): copy_sparse[row] = {} cell = self.cellWidget(row, col) if cell is not None: - copy_sparse[row][col] = cell.parse(row_number=row, crud=self.crud) + copy_sparse[row][col] = cell.parse(row_number=row, crud=weakref.ref(self.crud)) copy_dense = [["" for col in range(max_col - min_col + 1)] for row in range(max_row - min_row + 1)] for row, row_cells in copy_sparse.items(): for col, cell_text in row_cells.items(): @@ -54,7 +55,7 @@ class CopyPastableCrudQTableWidget(QTableWidget): c = min_col + col cell = self.cellWidget(r, c) if cell is not None: - cell.render(cell_text, field_name=self.crud.select[c] if self.crud is not None else None, row_number=r, crud=self.crud) + cell.render(cell_text, field_name=self.crud.select[c] if self.crud is not None else None, row_number=r, crud=weakref.ref(self.crud)) super().keyPressEvent(event) QApplication.sendEvent(self, event) return True diff --git a/src/ui/crud/crud.py b/src/ui/crud/crud.py index 750b0f9..2f0d7f3 100755 --- a/src/ui/crud/crud.py +++ b/src/ui/crud/crud.py @@ -6,7 +6,7 @@ from datetime import datetime from lib.db import Crud_DB from peewee import TextField -from PyQt5.QtCore import QObject, Qt, pyqtSignal +from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtWidgets import (QAbstractItemView, QComboBox, QDialog, QGridLayout, QHeaderView, QLineEdit, QMessageBox, QPlainTextEdit, QPushButton) @@ -46,7 +46,12 @@ class Cell: self.field_alias = field_alias self.field = field self.row_number = row_number - self.crud = crud + if crud is None: + self.crud = None + elif type(crud) is weakref.ReferenceType: + self.crud = crud + else: + self.crud = weakref.ref(crud) self.value = None self.is_modified = False self.connected_modified = False @@ -181,7 +186,7 @@ class Json_External_Dialog_Editor_Cell_Widget(QPushButton, Cell): def __init__(self, editor_widget_class, *args, readonly=True, autocomplete=None, field_name=None, field_alias=None, field=None, row_number=None, crud=None, **kwargs): if not issubclass(editor_widget_class, Editor): raise AssertionError(f"editor_widget_class {editor_widget_class!r} must be a subclass of {Editor!r}") - self.editor = editor_widget_class(*args, cell_widget=self, **kwargs) + self.editor = editor_widget_class(*args, cell_widget=weakref.ref(self), **kwargs) super().__init__(u"\u238B modifica") Cell.__init__(self, readonly=readonly, autocomplete=autocomplete, field_name=field_name, field_alias=field_alias, field=field, row_number=row_number, crud=crud) self.dialog = Dialog() @@ -335,7 +340,7 @@ class Crud(Widget): for fn, cn in self.select_index.items(): w = self.db_tw.cellWidget(rn, cn) try: - r[fn] = w._parse(row_number=rn, crud=self) + r[fn] = w._parse(row_number=rn, crud=weakref.ref(self)) except Exception as e: self.log.exception(traceback.format_exc()) self.set_row_color(rn, "red") @@ -427,12 +432,12 @@ class Crud(Widget): r = self.row_upgrader(r, rn, self) for fn, cn in self.select_index.items(): readonly = self.readonly is None or self.readonly is True or (self.readonly is not False and fn in self.readonly) - w = self.widget_classes.get(fn, self.default_widget_class)(readonly=readonly, autocomplete=self.autocomplete.get(fn, None), field_name=fn, field_alias=self.fields_aliases[fn], field=self.db.table_model._meta.fields.get(fn, None), row_number=rn, crud=self) + w = self.widget_classes.get(fn, self.default_widget_class)(readonly=readonly, autocomplete=self.autocomplete.get(fn, None), field_name=fn, field_alias=self.fields_aliases[fn], field=self.db.table_model._meta.fields.get(fn, None), row_number=rn, crud=weakref.ref(self)) w.set_readonly(readonly=w.readonly) w.do_autocomplete(w.autocomplete) w.modified.connect(self.set_modified) if fn in r: - w._render(data=r[fn], row_number=rn, crud=self) + w._render(data=r[fn], row_number=rn, crud=weakref.ref(self)) self.db_tw.setCellWidget(rn, cn, w) # REENABLE EVENTS AFTER UPDATE self.db_tw.blockSignals(False) @@ -452,7 +457,7 @@ class Crud(Widget): self.db_tw.setRowCount(rn + 1) for fn, cn in self.select_index.items(): readonly = self.readonly is None or self.readonly is True or (self.readonly is not False and fn in self.readonly) - w = self.widget_classes.get(fn, self.default_widget_class)(readonly=readonly, autocomplete=self.autocomplete.get(fn, None), field_name=fn, field_alias=self.fields_aliases[fn], field=self.db.table_model._meta.fields.get(fn, None), row_number=rn, crud=self) + w = self.widget_classes.get(fn, self.default_widget_class)(readonly=readonly, autocomplete=self.autocomplete.get(fn, None), field_name=fn, field_alias=self.fields_aliases[fn], field=self.db.table_model._meta.fields.get(fn, None), row_number=rn, crud=weakref.ref(self)) w.set_readonly(readonly=w.readonly) w.do_autocomplete(w.autocomplete) w.modified.connect(self.set_modified) diff --git a/src/ui/editor/editor.py b/src/ui/editor/editor.py index defa791..b9809ca 100644 --- a/src/ui/editor/editor.py +++ b/src/ui/editor/editor.py @@ -1,3 +1,5 @@ +import weakref + from PyQt5.QtCore import QObject from PyQt5.QtWidgets import (QCheckBox, QComboBox, QDoubleSpinBox, QLineEdit, QListWidget, QPlainTextEdit, QRadioButton, @@ -8,7 +10,12 @@ from ui.widget import Widget class Editor(Widget): def __init__(self, cell_widget=None): super().__init__() - self.cell_widget = cell_widget + if cell_widget is None: + self.cell_widget = None + elif type(cell_widget) is weakref.ReferenceType: + self.cell_widget = cell_widget + else: + self.cell_widget = weakref.ref(cell_widget) self.spec = {} def set_readonly(self, readonly=True): diff --git a/src/ui/steps_management/steps_management.py b/src/ui/steps_management/steps_management.py index c17e86c..fde6b0d 100644 --- a/src/ui/steps_management/steps_management.py +++ b/src/ui/steps_management/steps_management.py @@ -22,12 +22,12 @@ class Step_Spec_JEDECW(QPushButton, Cell): super().__init__(u"\u238B modifica") Cell.__init__(self, readonly=readonly, autocomplete=autocomplete, field_name=field_name, field_alias=field_alias, field=field, row_number=row_number, crud=crud) self.update_editor() - self.crud.modified.connect(self.update_editor) + self.crud().modified.connect(self.update_editor) self.dialog.setWindowTitle(self.field_alias) self.clicked.connect(self.dialog.show) def update_editor(self): - self.editor_type = self.crud.db_tw.cellWidget(self.row_number, self.crud.select_index["type"])._parse() + self.editor_type = self.crud().db_tw.cellWidget(self.row_number, self.crud().select_index["type"])._parse() self.editor = self.editors[self.editor_type] if self.dialog.centralWidget is not self.editor: self.dialog.setCentralWidget(self.editor) diff --git a/src/ui/test/test.py b/src/ui/test/test.py index 0a3f986..dbf5031 100755 --- a/src/ui/test/test.py +++ b/src/ui/test/test.py @@ -252,26 +252,32 @@ class Test(Widget): def set_leak(self, leak=None): if "leak" not in self.data: - self.data["leak"] = [] + self.data["leak"] = {} leak["step"] = model_to_dict(self.step) - self.data["leak"].append(leak) + self.data["leak"][len(self.data["leak"])] = leak self.data["overridden"] = self.data["overridden"] or leak.get("overridden", False) self.data["ok"] = self.data["ok"] and leak.get("ok", False) self.next() def set_vision(self, vision=None): if "vision" not in self.data: - self.data["vision"] = [] + self.data["vision"] = {} vision["step"] = model_to_dict(self.step) - self.data["vision"].append(vision) + self.data["vision"][len(self.data["vision"])] = vision self.data["overridden"] = self.data["overridden"] or vision.get("overridden", False) self.data["ok"] = self.data["ok"] and vision.get("ok", False) self.next() def done(self, ok=True): self.log.info("cycle done") - self.data["vision"][0].pop("detections") - self.data["vision"][0].pop("results") + 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}") + for vision in self.data["vision"].values(): + vision.pop("frame", None) + vision.pop("render", None) archived = Archive.archive(self.recipe, self.data, ok and self.data["ok"], overridden=self.data["overridden"]) self.log.info(f"cycle archived locally: {archived!r}") # COUNT OK @@ -281,29 +287,28 @@ class Test(Widget): def print(self, archived=None): self.log.info("cycle print") # LABEL PRINT - datamatrix= archived.recipe.part_number+ archived.time.strftime("%m%y")+f"{archived.id:0>5}" - dt_label=archived.time.strftime("%d/%m/%Y %H:%M:%S") - pmax = self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("test_pressure", "-") - pmin=self.data.get("leak", [{}, {}])[0].get("results", {}).get("data", {}).get("Running test: pressure at the end of settling", "-") - leak=self.data.get("leak", [{}, {}])[0].get("results", {}).get("data", {}).get("Running test: measured leak", "-") + 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", "-") context = { "DATAMATRIX": datamatrix, "DATAMATRIX_TEXT": datamatrix, "PIECE_NUM": str(archived.id), - "DATETIME": dt_label, + "DATETIME": archived.time.strftime("%d/%m/%Y %H:%M:%S"), "SHIFT": str(get_shift(archived.time)), "STATION": str(self.machine_id), "OPERATOR": str(Users.get_session().user.username), - "PMAX": f"{pmax:.3f}", - "PMIN": f"{pmin:.3f}", - "LEAK": f"{leak:.3f}", - "TFILL": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("filling_time", "-")), - "TSTAB": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("settling_time", "-")), - "TTEST": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("test_time", "-")), - "MAXLEAK": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("test_pressure_max_delta", "-")), - "PTESTMIN": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("test_pressure_min_delta", "-")), + "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", "-")), "RESULT_L1": "ESITO" + str(" FORZATO" if self.data.get("overridden", False) else ""), - "RESULT_L2": str("CONFORME" if self.data.get("leak", [{}, {}])[0].get("results", {}).get("ok", False) else "SCARTO"), + "RESULT_L2": str("CONFORME" if self.data.get("leak", {}).get(0, {}).get("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_vision/test_vision.py b/src/ui/test_vision/test_vision.py index 27093f9..6d3c7db 100644 --- a/src/ui/test_vision/test_vision.py +++ b/src/ui/test_vision/test_vision.py @@ -1,18 +1,9 @@ -import os import sys -from datetime import datetime -import cv2 from PyQt5.QtCore import pyqtSignal from PyQt5.QtGui import QImage, QPixmap from ui.test_test import Test_Test -TMP_DIR = "tmp" -ARCHIVE_DIR = "data/vision_saves" -INFERENCE_INPUT_W = 256 -INFERENCE_INPUT_H = 256 - - class Test_Vision(Test_Test): request_frame = pyqtSignal() @@ -36,7 +27,6 @@ class Test_Vision(Test_Test): self.components["vision"].resume() self.components["galaxy_camera"].resume() self.request_frame.emit() # request first frame - self.save_b.clicked.connect(lambda: self.save_frame_all(position="tmp")) def stop(self): # disable camera-vision loop @@ -96,38 +86,12 @@ class Test_Vision(Test_Test): def save_last(self): if self.last is None: return + self.components["vision_saver"].save(self.last["time"], self.last["frame"]) def emit_ok(self): - self.last.pop("frame", None) - self.last.pop("render", None) super().emit_ok() self.components["neo_pixels"].set_all_pixel_color("#000000") def emit_ko(self): - self.last.pop("frame", None) - self.last.pop("render", None) super().emit_ko() self.components["neo_pixels"].set_all_pixel_color("#000000") - - def save_frame_detection(self, position=None, date=None): - if date is None: - date = datetime.now() - if position == "archive": - save_dir = ARCHIVE_DIR - elif position == "tmp": - save_dir = TMP_DIR - elif position is None: - save_dir = self.config["image saver"]["location"] - else: - save_dir = position - save_dir += "/img_archive" - save_dir += f"/{date.strftime('%Y')}/{date.strftime('%m')}" - self.remove_older_images_if_needed() - if not os.path.exists(save_dir): - os.makedirs(save_dir, exist_ok=True) - filename = f"{save_dir}/{date.strftime('%Y-%m-%d_%H_%M_%S')}_{self.config['image saver']['suffix']}.png" - print(f"saving {filename}") - image_np_bgr = cv2.cvtColor(self.detection_image, cv2.COLOR_RGB2BGR) - cv2.imwrite(filename, image_np_bgr) - return filename - diff --git a/src/ui/users_management/users_management.py b/src/ui/users_management/users_management.py index d4ca05d..ab54004 100644 --- a/src/ui/users_management/users_management.py +++ b/src/ui/users_management/users_management.py @@ -69,7 +69,7 @@ class Users_Management(Widget): user = Users.generate(username=row["username"], password=row["password"], roles=row["roles"]) except AssertionError as e: self.log.exception(traceback.format_exc()) - crud.set_row_color(row_number, "red") + crud().set_row_color(row_number, "red") QMessageBox.critical(None, "Errore Salvataggio DB", f"Errore alla riga {row_number}:\n{str(e)}") return False, None, True row.update(user)