Merge remote-tracking branch 'origin/master'

# Conflicts:
#	config/machine_settings/hostnames.ini
This commit is contained in:
stres1 2022-08-02 17:33:02 +02:00
commit fca98bdd0f
17 changed files with 134 additions and 138 deletions

View File

@ -17,6 +17,7 @@ balance_blue: 1.5
time_format: %Y-%m-%d_%H-%M-%S time_format: %Y-%m-%d_%H-%M-%S
path: ./data/images path: ./data/images
minimum_disk_free_space_gb: 20 minimum_disk_free_space_gb: 20
resize_resolution: 612x512
[archive_synchronizer] [archive_synchronizer]
archive_endpoint: https://r5portal.it/api/echo/ archive_endpoint: https://r5portal.it/api/echo/

View File

@ -1,7 +1,8 @@
import sys import sys
import time
import traceback import traceback
from PyQt5.QtCore import QThread
if "--sim-serial" in sys.argv: if "--sim-serial" in sys.argv:
from components.dummies.serial import serial from components.dummies.serial import serial
else: else:
@ -16,16 +17,8 @@ C_UINT32_MAX = c_uint32(-1).value
class NeoPixels(Component): class NeoPixels(Component):
def __init__(self, config=None, name=None): def __init__(self, config=None, name=None):
self.conn = None
super().__init__(config=config, name=name, threaded=False) 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): def config_changed(self):
self.port = self.config[self.name]["port"] 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.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.parity = getattr(serial, self.config[self.name].get("parity", "parity_none").upper())
self.bytesize = getattr(serial, self.config[self.name].get("bytesize", "eightbits").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): def set(self, pixel, color):
if type(pixel) is not int or pixel < 0 or pixel > C_UINT32_MAX: 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() response = self.conn.readline()
if response != b"ok\n": if response != b"ok\n":
self.log.error(response.strip()) self.log.error(response.strip())
# conn.close() # self.conn.close()
except serial.serialutil.SerialException: except serial.serialutil.SerialException:
self.log.exception(traceback.format_exc()) self.log.exception(traceback.format_exc())

View File

@ -180,7 +180,7 @@ class TecnaMarpossProvasetT3P(ModbusComponent):
"Running test: test type", "Running test: test type",
"Running test: sequence index", "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()) info.update(self.get_test_results())
self.log.debug(str(info)) self.log.debug(str(info))
super()._get([info]) super()._get([info])

View File

@ -7,7 +7,7 @@ registers = {
"Life counter: TOTAL TESTS": [8 - 1, {"dt": "32bit_uint", }], "Life counter: TOTAL TESTS": [8 - 1, {"dt": "32bit_uint", }],
"Real time test pressure output": [11 - 1, {"dt": "32bit_int", "f": 21, }], "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 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", }], "Active alarm flags": [16 - 1, {"dt": "16bit_uint", }],
# | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | # | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
# | | | | A5 | A4 | A3 | A2 | A1 | | | | | | E3 | E2 | E1 | # | | | | A5 | A4 | A3 | A2 | A1 | | | | | | E3 | E2 | E1 |
@ -28,9 +28,9 @@ registers = {
"Running test: flow rate format": [27 - 1, {"dt": "16bit_uint", }], # white "Running test: flow rate format": [27 - 1, {"dt": "16bit_uint", }], # white
"Active test program number": [31 - 1, {"dt": "16bit_uint", }], "Active test program number": [31 - 1, {"dt": "16bit_uint", }],
"Running test: active phase": [32 - 1, {"dt": "16bit_uint", "decoding": { "Running test: active phase": [32 - 1, {"dt": "16bit_uint", "decoding": {
0: "ATTESA START", 0: "ATTESA START", # "WAITING START",
10: "CONTROLLO BARCODE", 10: "CONTROLLO BARCODE", # "CHECK BARCODE",
20: "ATTESA BARCODE", 20: "ATTESA BARCODE", # "WAITING BARCODE",
30: "START TEST", 30: "START TEST",
40: "START CAGE", 40: "START CAGE",
50: "START PLUG", 50: "START PLUG",
@ -38,10 +38,10 @@ registers = {
70: "WAIT DELAY ACKNOWLEDGE COMMAND", 70: "WAIT DELAY ACKNOWLEDGE COMMAND",
80: "WAIT DELAY", 80: "WAIT DELAY",
90: "WAIT ACKNOWLEDGE", 90: "WAIT ACKNOWLEDGE",
100: "T0 PRE-RIEMPIMENTO", 100: "T0 PRE-RIEMPIMENTO", # "T0 PRE-FILL",
110: "T1 RIEMPIMENTO", 110: "T1 RIEMPIMENTO", # "T1 FILLING",
120: "T2 ASSESTAMENTO", 120: "T2 ASSESTAMENTO", # "T2 SETTLING",
130: "T3 MISURA", 130: "T3 MISURA", # "T3 MEASURE",
140: "WAITING OPERATOR RESULT ACKNOWLEDGE", 140: "WAITING OPERATOR RESULT ACKNOWLEDGE",
150: "RESULT PRESENT", 150: "RESULT PRESENT",
160: "MARKING", 160: "MARKING",
@ -49,7 +49,7 @@ registers = {
180: "END SEQUENCE PROGRAM", 180: "END SEQUENCE PROGRAM",
190: "END PLUG", 190: "END PLUG",
200: "END CAGE", 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: phase backwards time": [33 - 1, {"dt": "16bit_uint", "f": 26, }],
"Running test: filling pressure": [34 - 1, {"dt": "32bit_int", "f": 21, }], "Running test: filling pressure": [34 - 1, {"dt": "32bit_int", "f": 21, }],

View File

@ -1,29 +1,26 @@
import logging import logging
import os import os
import sys import sys
import time
import traceback import traceback
from configparser import ConfigParser from configparser import ConfigParser
from pathlib import Path from pathlib import Path
import cv2 import cv2
import numpy as np 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 PyQt5.QtWidgets import QApplication
from lib.helpers import time_this from .component import Component
from .consumer import Consumer
if "--no-gpu" in sys.argv: if "--no-gpu" in sys.argv:
os.environ["CUDA_VISIBLE_DEVICES"] = "-1" os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
import tensorflow as tf import tensorflow as tf
from lib.helpers.object_detection.utils import label_map_util 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: if "--no-edgetpu" not in sys.argv:
try: try:
@ -317,8 +314,7 @@ class Vision(Component):
if tf_mode is None and "normal" in self.allowed_modes: if tf_mode is None and "normal" in self.allowed_modes:
try: try:
# create tensorflow model # create tensorflow model
model = tf.saved_model.load(str(self.models_dir / model_name)) model = tf.saved_model.load(str(self.models_dir / model_name)).signatures["serving_default"]
model = model.signatures['serving_default']
tf_mode = "normal" tf_mode = "normal"
except Exception: except Exception:
self.log.exception(traceback.format_exc()) self.log.exception(traceback.format_exc())
@ -339,7 +335,6 @@ class Vision(Component):
self.log.info(f"initialized model {self.model!r} with mode {self.tf_mode!r}") self.log.info(f"initialized model {self.model!r} with mode {self.tf_mode!r}")
self.loading_model_signal.emit({"status": "done"}) self.loading_model_signal.emit({"status": "done"})
@time_this
def check_features(self, frame, lock=True): 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]: 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) tensor = np.expand_dims(cv2.resize(frame, self.interpreter.get_input_details()[0]["shape"][1:3], interpolation=cv2.INTER_LINEAR), axis=0)
@ -347,6 +342,7 @@ class Vision(Component):
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 = tf.convert_to_tensor(np.asarray(frame_resized))
tensor = tensor[tf.newaxis, ...] tensor = tensor[tf.newaxis, ...]
# tensor = np.expand_dims(frame, axis=0)
# Run inference # Run inference
if lock: if lock:
self.lock.lock() self.lock.lock()
@ -388,33 +384,36 @@ class Vision(Component):
if lock: if lock:
self.lock.unlock() self.lock.unlock()
detections = { detections = {
"detection_scores": [scores][0:1], "detection_scores": [scores],
"detection_boxes": [boxes][0:1], "detection_boxes": [boxes],
"detection_classes": [map(lambda class_id: class_id + 1, class_ids)][0:1], "detection_classes": [map(lambda class_id: class_id + 1, class_ids)],
} }
else: else:
detections = self.model(tensor) detections = self.model(tensor)
if lock: if lock:
self.lock.unlock() 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 # WARNING: results other than the ones related to tensor[-1] will be discarded
parsed_detections = [] 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_boxes"][-1],
detections["detection_classes"][-1], detections["detection_classes"][-1],
detections["detection_scores"][-1],
# detections["detection_masks"][-1], # detections["detection_masks"][-1],
): ):
if d_score < self.detection_threshold: if d_score < self.detection_threshold:
continue continue
box = list(d_box) 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 box = [i * s for i, s in zip(box, frame.shape[:2] * 2)] # rescale detection to frame size
detection = { detection = {
"class": self.category_index[int(d_class)], "score": d_score,
"score": float(d_score),
# "score": d_score.numpy().tolist(),
# "mask": d_mask.numpy().tolist(),
"box": box, "box": box,
"class": self.category_index[int(d_class)],
# "mask": d_mask,
"center": self.get_center(box), "center": self.get_center(box),
"size": self.get_size(box), "size": self.get_size(box),
} }
@ -477,7 +476,6 @@ class Vision(Component):
else: else:
raise NotImplementedError(f"invalid zone_detection_filter_mode: {self.zone_detection_filter_mode!r}") raise NotImplementedError(f"invalid zone_detection_filter_mode: {self.zone_detection_filter_mode!r}")
elif zone["shape"] == "ellipse": # it's a circle elif zone["shape"] == "ellipse": # it's a circle
distance = self.get_distance(detection["center"], zone["center"])
if self.zone_detection_filter_mode == "center_inside": if self.zone_detection_filter_mode == "center_inside":
outside_zone = distance > zone["size"][0] / 2 outside_zone = distance > zone["size"][0] / 2
elif self.zone_detection_filter_mode == "box_inside": elif self.zone_detection_filter_mode == "box_inside":

View File

@ -17,19 +17,30 @@ class VisionSaver(Component):
def config_changed(self): def config_changed(self):
self.location = Path(self.config[self.name]["path"]) self.location = Path(self.config[self.name]["path"])
os.makedirs(self.location, exist_ok=True) 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.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) 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: if self.minimum_disk_free_space_gb is not None:
self.minimum_disk_free_space_gb = float(self.minimum_disk_free_space_gb) self.minimum_disk_free_space_gb = float(self.minimum_disk_free_space_gb)
self.time_format = self.config[self.name]["time_format"] self.time_format = self.config[self.name]["time_format"]
def save(self, save_time, img, mask=True): def save(self, save_time, img, mask=True, resize=True):
timestamp = datetime.fromtimestamp(save_time).strftime(self.time_format) 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") save_dir = self.location / save_time.strftime("%Y") / save_time.strftime("%m")
os.makedirs(save_dir, exist_ok=True) os.makedirs(save_dir, exist_ok=True)
out_path = save_dir / f"{timestamp}.png" out_path = save_dir / f"{timestamp}.png"
self.log.info(f"saving {out_path}") self.log.info(f"saving {out_path}")
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) 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: if mask and self.mask_zones is not None:
height, width, channels = img.shape height, width, channels = img.shape
out = np.full( 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]] out[zone[1]:zone[3], zone[0]:zone[2]] = img[zone[1]:zone[3], zone[0]:zone[2]]
else: else:
out = img out = img
cv2.imwrite(out_path, out) cv2.imwrite(str(out_path), out)
return out_path return out_path
def remove_older_images_if_needed(self): def remove_older_images_if_needed(self):

View File

@ -15,7 +15,7 @@ conn = serial.Serial(
) )
pixel = C_UINT32_MAX pixel = C_UINT32_MAX
time.sleep(5) time.sleep(5)
color = "#000000" color = "#ff00ff"
conn.write(int.to_bytes(pixel, length=4, byteorder="big") + bytes.fromhex(color[1:7]) + b"\n") conn.write(int.to_bytes(pixel, length=4, byteorder="big") + bytes.fromhex(color[1:7]) + b"\n")
response = conn.readline() response = conn.readline()
print(response) print(response)

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import sys import sys
import weakref
from PyQt5.QtCore import QEvent, Qt from PyQt5.QtCore import QEvent, Qt
from PyQt5.QtWidgets import QApplication, QTableWidget from PyQt5.QtWidgets import QApplication, QTableWidget
@ -28,7 +29,7 @@ class CopyPastableCrudQTableWidget(QTableWidget):
copy_sparse[row] = {} copy_sparse[row] = {}
cell = self.cellWidget(row, col) cell = self.cellWidget(row, col)
if cell is not None: 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)] 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 row, row_cells in copy_sparse.items():
for col, cell_text in row_cells.items(): for col, cell_text in row_cells.items():
@ -54,7 +55,7 @@ class CopyPastableCrudQTableWidget(QTableWidget):
c = min_col + col c = min_col + col
cell = self.cellWidget(r, c) cell = self.cellWidget(r, c)
if cell is not None: 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) super().keyPressEvent(event)
QApplication.sendEvent(self, event) QApplication.sendEvent(self, event)
return True return True

View File

@ -6,7 +6,7 @@ from datetime import datetime
from lib.db import Crud_DB from lib.db import Crud_DB
from peewee import TextField from peewee import TextField
from PyQt5.QtCore import QObject, Qt, pyqtSignal from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtWidgets import (QAbstractItemView, QComboBox, QDialog, from PyQt5.QtWidgets import (QAbstractItemView, QComboBox, QDialog,
QGridLayout, QHeaderView, QLineEdit, QMessageBox, QGridLayout, QHeaderView, QLineEdit, QMessageBox,
QPlainTextEdit, QPushButton) QPlainTextEdit, QPushButton)
@ -46,7 +46,12 @@ class Cell:
self.field_alias = field_alias self.field_alias = field_alias
self.field = field self.field = field
self.row_number = row_number self.row_number = row_number
if crud is None:
self.crud = None
elif type(crud) is weakref.ReferenceType:
self.crud = crud self.crud = crud
else:
self.crud = weakref.ref(crud)
self.value = None self.value = None
self.is_modified = False self.is_modified = False
self.connected_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): 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): if not issubclass(editor_widget_class, Editor):
raise AssertionError(f"editor_widget_class {editor_widget_class!r} must be a subclass of {Editor!r}") 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") 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) 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() self.dialog = Dialog()
@ -335,7 +340,7 @@ class Crud(Widget):
for fn, cn in self.select_index.items(): for fn, cn in self.select_index.items():
w = self.db_tw.cellWidget(rn, cn) w = self.db_tw.cellWidget(rn, cn)
try: 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: except Exception as e:
self.log.exception(traceback.format_exc()) self.log.exception(traceback.format_exc())
self.set_row_color(rn, "red") self.set_row_color(rn, "red")
@ -427,12 +432,12 @@ class Crud(Widget):
r = self.row_upgrader(r, rn, self) r = self.row_upgrader(r, rn, self)
for fn, cn in self.select_index.items(): 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) 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.set_readonly(readonly=w.readonly)
w.do_autocomplete(w.autocomplete) w.do_autocomplete(w.autocomplete)
w.modified.connect(self.set_modified) w.modified.connect(self.set_modified)
if fn in r: 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) self.db_tw.setCellWidget(rn, cn, w)
# REENABLE EVENTS AFTER UPDATE # REENABLE EVENTS AFTER UPDATE
self.db_tw.blockSignals(False) self.db_tw.blockSignals(False)
@ -452,7 +457,7 @@ class Crud(Widget):
self.db_tw.setRowCount(rn + 1) self.db_tw.setRowCount(rn + 1)
for fn, cn in self.select_index.items(): 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) 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.set_readonly(readonly=w.readonly)
w.do_autocomplete(w.autocomplete) w.do_autocomplete(w.autocomplete)
w.modified.connect(self.set_modified) w.modified.connect(self.set_modified)

View File

@ -1,3 +1,5 @@
import weakref
from PyQt5.QtCore import QObject from PyQt5.QtCore import QObject
from PyQt5.QtWidgets import (QCheckBox, QComboBox, QDoubleSpinBox, QLineEdit, from PyQt5.QtWidgets import (QCheckBox, QComboBox, QDoubleSpinBox, QLineEdit,
QListWidget, QPlainTextEdit, QRadioButton, QListWidget, QPlainTextEdit, QRadioButton,
@ -8,7 +10,12 @@ from ui.widget import Widget
class Editor(Widget): class Editor(Widget):
def __init__(self, cell_widget=None): def __init__(self, cell_widget=None):
super().__init__() super().__init__()
if cell_widget is None:
self.cell_widget = None
elif type(cell_widget) is weakref.ReferenceType:
self.cell_widget = cell_widget self.cell_widget = cell_widget
else:
self.cell_widget = weakref.ref(cell_widget)
self.spec = {} self.spec = {}
def set_readonly(self, readonly=True): def set_readonly(self, readonly=True):

View File

@ -22,12 +22,12 @@ class Step_Spec_JEDECW(QPushButton, Cell):
super().__init__(u"\u238B modifica") 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) 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.update_editor()
self.crud.modified.connect(self.update_editor) self.crud().modified.connect(self.update_editor)
self.dialog.setWindowTitle(self.field_alias) self.dialog.setWindowTitle(self.field_alias)
self.clicked.connect(self.dialog.show) self.clicked.connect(self.dialog.show)
def update_editor(self): 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] self.editor = self.editors[self.editor_type]
if self.dialog.centralWidget is not self.editor: if self.dialog.centralWidget is not self.editor:
self.dialog.setCentralWidget(self.editor) self.dialog.setCentralWidget(self.editor)

View File

@ -252,26 +252,32 @@ class Test(Widget):
def set_leak(self, leak=None): def set_leak(self, leak=None):
if "leak" not in self.data: if "leak" not in self.data:
self.data["leak"] = [] self.data["leak"] = {}
leak["step"] = model_to_dict(self.step) 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["overridden"] = self.data["overridden"] or leak.get("overridden", False)
self.data["ok"] = self.data["ok"] and leak.get("ok", False) self.data["ok"] = self.data["ok"] and leak.get("ok", False)
self.next() self.next()
def set_vision(self, vision=None): def set_vision(self, vision=None):
if "vision" not in self.data: if "vision" not in self.data:
self.data["vision"] = [] self.data["vision"] = {}
vision["step"] = model_to_dict(self.step) 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["overridden"] = self.data["overridden"] or vision.get("overridden", False)
self.data["ok"] = self.data["ok"] and vision.get("ok", False) self.data["ok"] = self.data["ok"] and vision.get("ok", False)
self.next() self.next()
def done(self, ok=True): def done(self, ok=True):
self.log.info("cycle done") self.log.info("cycle done")
self.data["vision"][0].pop("detections") t = self.data.get("vision", {}).get(0, {}).get("time", None)
self.data["vision"][0].pop("results") 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"]) 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}") self.log.info(f"cycle archived locally: {archived!r}")
# COUNT OK # COUNT OK
@ -281,29 +287,28 @@ class Test(Widget):
def print(self, archived=None): def print(self, archived=None):
self.log.info("cycle print") self.log.info("cycle print")
# LABEL PRINT # LABEL PRINT
datamatrix= archived.recipe.part_number+ archived.time.strftime("%m%y")+f"{archived.id:0>5}" datamatrix = str(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", {}).get(0, {}).get("step", {}).get("spec", {}).get("test_pressure", "-")
pmax = self.data.get("leak", [{}, {}])[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", "-")
pmin=self.data.get("leak", [{}, {}])[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", "-")
leak=self.data.get("leak", [{}, {}])[0].get("results", {}).get("data", {}).get("Running test: measured leak", "-")
context = { context = {
"DATAMATRIX": datamatrix, "DATAMATRIX": datamatrix,
"DATAMATRIX_TEXT": datamatrix, "DATAMATRIX_TEXT": datamatrix,
"PIECE_NUM": str(archived.id), "PIECE_NUM": str(archived.id),
"DATETIME": dt_label, "DATETIME": archived.time.strftime("%d/%m/%Y %H:%M:%S"),
"SHIFT": str(get_shift(archived.time)), "SHIFT": str(get_shift(archived.time)),
"STATION": str(self.machine_id), "STATION": str(self.machine_id),
"OPERATOR": str(Users.get_session().user.username), "OPERATOR": str(Users.get_session().user.username),
"PMAX": f"{pmax:.3f}", "PMAX": f"{pmax:.3f}" if type(pmax) is not str else pmax,
"PMIN": f"{pmin:.3f}", "PMIN": f"{pmin:.3f}" if type(pmin) is not str else pmin,
"LEAK": f"{leak:.3f}", "LEAK": f"{leak:.3f}" if type(leak) is not str else leak,
"TFILL": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("filling_time", "-")), "TFILL": str(self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("filling_time", "-")),
"TSTAB": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("settling_time", "-")), "TSTAB": str(self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("settling_time", "-")),
"TTEST": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("test_time", "-")), "TTEST": str(self.data.get("leak", {}).get(0, {}).get("step", {}).get("spec", {}).get("test_time", "-")),
"MAXLEAK": str(self.data.get("leak", [{}, {}])[0].get("step", {}).get("spec", {}).get("test_pressure_max_delta", "-")), "MAXLEAK": str(self.data.get("leak", {}).get(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", "-")), "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_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.components["label_printer"].print_label("EtichettaR5", context=context)
self.log.info(f"cycle printed: {context!r}") self.log.info(f"cycle printed: {context!r}")

View File

@ -1,18 +1,9 @@
import os
import sys import sys
from datetime import datetime
import cv2
from PyQt5.QtCore import pyqtSignal from PyQt5.QtCore import pyqtSignal
from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtGui import QImage, QPixmap
from ui.test_test import Test_Test 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): class Test_Vision(Test_Test):
request_frame = pyqtSignal() request_frame = pyqtSignal()
@ -36,7 +27,6 @@ class Test_Vision(Test_Test):
self.components["vision"].resume() self.components["vision"].resume()
self.components["galaxy_camera"].resume() self.components["galaxy_camera"].resume()
self.request_frame.emit() # request first frame self.request_frame.emit() # request first frame
self.save_b.clicked.connect(lambda: self.save_frame_all(position="tmp"))
def stop(self): def stop(self):
# disable camera-vision loop # disable camera-vision loop
@ -96,38 +86,12 @@ class Test_Vision(Test_Test):
def save_last(self): def save_last(self):
if self.last is None: if self.last is None:
return return
self.components["vision_saver"].save(self.last["time"], self.last["frame"])
def emit_ok(self): def emit_ok(self):
self.last.pop("frame", None)
self.last.pop("render", None)
super().emit_ok() super().emit_ok()
self.components["neo_pixels"].set_all_pixel_color("#000000") self.components["neo_pixels"].set_all_pixel_color("#000000")
def emit_ko(self): def emit_ko(self):
self.last.pop("frame", None)
self.last.pop("render", None)
super().emit_ko() super().emit_ko()
self.components["neo_pixels"].set_all_pixel_color("#000000") 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

View File

@ -69,7 +69,7 @@ class Users_Management(Widget):
user = Users.generate(username=row["username"], password=row["password"], roles=row["roles"]) user = Users.generate(username=row["username"], password=row["password"], roles=row["roles"])
except AssertionError as e: except AssertionError as e:
self.log.exception(traceback.format_exc()) 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)}") QMessageBox.critical(None, "Errore Salvataggio DB", f"Errore alla riga {row_number}:\n{str(e)}")
return False, None, True return False, None, True
row.update(user) row.update(user)