hytos / DTI_PID / DTI_PID / RecognitionDialog.py @ d1994d14
이력 | 보기 | 이력해설 | 다운로드 (195 KB)
1 |
# coding: utf-8
|
---|---|
2 |
"""
|
3 |
This is recognition dialog module
|
4 |
"""
|
5 |
import sys |
6 |
import os |
7 |
import threading |
8 |
import cv2 |
9 |
import numpy as np |
10 |
import timeit |
11 |
from PyQt5.QtCore import * |
12 |
from PyQt5.QtGui import * |
13 |
from PyQt5.QtWidgets import * |
14 |
import Recognition_UI |
15 |
|
16 |
import concurrent.futures as futures |
17 |
|
18 |
from AppDocData import * |
19 |
from SymbolSvgItem import SymbolSvgItem |
20 |
from EngineeringTextItem import QEngineeringTextItem |
21 |
from EngineeringUnknownItem import QEngineeringUnknownItem |
22 |
from EngineeringErrorItem import QEngineeringErrorItem |
23 |
from EngineeringEndBreakItem import QEngineeringEndBreakItem |
24 |
from EngineeringLineItem import QEngineeringLineItem |
25 |
from EngineeringTextItem import QEngineeringTextItem |
26 |
from EngineeringLineNoTextItem import QEngineeringLineNoTextItem |
27 |
from GraphicsBoundingBoxItem import QGraphicsBoundingBoxItem |
28 |
from QEngineeringOPCItem import QEngineeringOPCItem |
29 |
from EngineeringVendorItem import QEngineeringVendorItem |
30 |
from LineDetector import LineDetector |
31 |
from symbol import Symbol |
32 |
from Drawing import Drawing |
33 |
from QtImageViewer import QtImageViewer |
34 |
|
35 |
#from MainWindow import MainWindow
|
36 |
|
37 |
# region Symbol Image path List for test
|
38 |
targetSymbolList = [] |
39 |
# endregion
|
40 |
|
41 |
# region Global variables
|
42 |
searchedSymbolList = [] |
43 |
textInfoList = [] |
44 |
|
45 |
src = [] |
46 |
|
47 |
ocrCompletedSrc = [] |
48 |
afterDenoising = [] |
49 |
canvas = [] |
50 |
|
51 |
#WHITE_LIST_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-"
|
52 |
|
53 |
MIN_TEXT_SIZE = 10
|
54 |
|
55 |
THREAD_MAX_WORKER = os.cpu_count() |
56 |
threadLock = threading.Lock() |
57 |
|
58 |
ACCEPT_OVERLAY_AREA = 20
|
59 |
# endregion
|
60 |
|
61 |
'''
|
62 |
@history 2018.05.25 Jeongwoo Add pyqtSignal(recognizeLine, loadRecognitionResult)
|
63 |
'''
|
64 |
from enum import Enum |
65 |
class Arrow(Enum): |
66 |
NULL = 1
|
67 |
DOWN = 2
|
68 |
UP = 3
|
69 |
LEFT = 4
|
70 |
RIGHT = 5
|
71 |
|
72 |
class Worker(QObject): |
73 |
from PyQt5.QtCore import QThread |
74 |
from PyQt5.QtCore import QTranslator |
75 |
from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout, QListWidget |
76 |
import sys |
77 |
|
78 |
'''
|
79 |
@history 2018.05.30 Jeongwoo Remove parameter on recognizeLine signal / Change signal name (drawDetectedItems)
|
80 |
@history humkyung 2018.06.08 add signal for progressbar
|
81 |
@history euisung 2018.11.27 add signal for unknown items
|
82 |
'''
|
83 |
finished = pyqtSignal() |
84 |
intReady = pyqtSignal(int)
|
85 |
displayTitle = pyqtSignal(str)
|
86 |
displayMessage = pyqtSignal(QListWidgetItem) |
87 |
updateProgress = pyqtSignal(int, str) |
88 |
updateBatchProgress = pyqtSignal(int, int) |
89 |
displayLog = pyqtSignal(MessageType, str)
|
90 |
add_detected_items_to_scene = pyqtSignal(QGraphicsScene, list)
|
91 |
save_scene = pyqtSignal(QGraphicsScene) |
92 |
add_predata_to_scene = pyqtSignal(Drawing, QGraphicsScene, bool, bool, bool, bool, bool) |
93 |
clear_scene = pyqtSignal(QGraphicsScene, QGraphicsScene, QGraphicsScene) |
94 |
preset_execute = pyqtSignal(QGraphicsScene, str, str, str, str) |
95 |
item_remove = pyqtSignal(list)
|
96 |
|
97 |
def __init__(self, mutex, cond): |
98 |
super(Worker, self).__init__() |
99 |
self.mutex = mutex
|
100 |
self.cond = cond
|
101 |
|
102 |
'''
|
103 |
@history 2018.05.25 Jeongwoo Add if-statements by isSymbolTextChecked and isLineChecked variable
|
104 |
2018.05.28 Jeongwoo Add Parameter 'xmlPath[0]'
|
105 |
2018.05.30 Jeongwoo Remove import recognizeLine and self.xmlPath and remove parameter on recognizeLine.emit() (xmlPath)
|
106 |
Change signal name (drawDetectedItems)
|
107 |
'''
|
108 |
|
109 |
def procCounter(self): # A slot takes no params |
110 |
try:
|
111 |
self.mutex.lock()
|
112 |
if self.isSymbolChecked or self.isTrainingChecked: |
113 |
Worker.executeRecognition(self.drawings, self.listWidget, self.isLineChecked, self) |
114 |
except Exception as ex: |
115 |
from App import App |
116 |
from AppDocData import MessageType |
117 |
|
118 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
119 |
f"{sys.exc_info()[-1].tb_lineno}"
|
120 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
121 |
self.displayLog.emit(MessageType.Error, message)
|
122 |
except:
|
123 |
(_type, value, traceback) = sys.exc_info() |
124 |
sys.excepthook(_type, value, traceback) |
125 |
finally:
|
126 |
self.mutex.unlock()
|
127 |
self.finished.emit()
|
128 |
|
129 |
'''
|
130 |
@brief remove small objects from given image
|
131 |
@author humkyung
|
132 |
@date 2018.04.26
|
133 |
@history 2018.05.25 Jeongwoo Moved from MainWindow
|
134 |
'''
|
135 |
|
136 |
@staticmethod
|
137 |
def remove_small_objects(img, minArea=None, maxArea=None): |
138 |
try:
|
139 |
image = img.copy() |
140 |
|
141 |
if not minArea or not maxArea: |
142 |
app_doc_data = AppDocData.instance() |
143 |
configs = app_doc_data.getConfigs('Small Object Size', 'Min Area') |
144 |
minArea = int(configs[0].value) if 1 == len(configs) else 20 |
145 |
configs = app_doc_data.getConfigs('Small Object Size', 'Max Area') |
146 |
maxArea = int(configs[0].value) if 1 == len(configs) else 50 |
147 |
|
148 |
## try to convert grayscale to binary
|
149 |
#image = cv2.threshold(image, 200, 255, cv2.THRESH_BINARY)[1]
|
150 |
|
151 |
contours, _ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) |
152 |
selectedContours = [] |
153 |
for contour in contours: |
154 |
area = cv2.contourArea(contour) |
155 |
if minArea < area < maxArea:
|
156 |
selectedContours.append(contour) |
157 |
|
158 |
# draw contour with white color
|
159 |
cv2.drawContours(image, selectedContours, -1, (255, 255, 255), -1) |
160 |
except Exception as ex: |
161 |
from App import App |
162 |
|
163 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
164 |
f"{sys.exc_info()[-1].tb_lineno}"
|
165 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
166 |
|
167 |
return image
|
168 |
|
169 |
'''
|
170 |
@brief arrange line's position
|
171 |
@author humkyung
|
172 |
@date 2018.07.04
|
173 |
'''
|
174 |
|
175 |
@staticmethod
|
176 |
def arrangeLinePosition(lines, symbols, listWidget): |
177 |
try:
|
178 |
listWidget.addItem('Apply flow direction')
|
179 |
pool = [line for line in lines if line.flowMark is not None] |
180 |
visited = [] |
181 |
visited.extend(pool) |
182 |
while len(pool) > 0: |
183 |
line = pool.pop() |
184 |
print('{} - ({})'.format(line, len(pool))) |
185 |
rhs = [item for item in lines if item not in visited and item.is_connected(line)] |
186 |
if rhs:
|
187 |
pool.extend(rhs) |
188 |
visited.extend(rhs) |
189 |
for item in rhs: |
190 |
item.arrangeVertexOrder(line) |
191 |
|
192 |
# skip jointed symbols
|
193 |
symbolPool = [item for item in symbols if item not in visited and item.is_connected(line)] |
194 |
if symbolPool:
|
195 |
selected = [] |
196 |
visited.extend(symbolPool) |
197 |
while len(symbolPool) > 0: |
198 |
symbol = symbolPool.pop() |
199 |
|
200 |
rhs = [item for item in symbols if item not in visited and item.is_connected(symbol)] |
201 |
if rhs:
|
202 |
symbolPool.extend(rhs) |
203 |
visited.extend(rhs) |
204 |
selected.extend(rhs) |
205 |
else:
|
206 |
selected.append(symbol) |
207 |
|
208 |
# find lines which are connected last symbol
|
209 |
for symbol in selected: |
210 |
rhs = [item for item in lines if item not in visited and item.is_connected(symbol)] |
211 |
if rhs:
|
212 |
pool.extend(rhs) |
213 |
visited.extend(rhs) |
214 |
for item in rhs: |
215 |
item.arrangeVertexOrder(line) |
216 |
# up to here
|
217 |
# up to here
|
218 |
except Exception as ex: |
219 |
from App import App |
220 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
221 |
sys.exc_info()[-1].tb_lineno)
|
222 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
223 |
|
224 |
def create_detected_items(self, symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList): |
225 |
try:
|
226 |
QApplication.processEvents() |
227 |
self.create_detected_symbol_item(symbolList)
|
228 |
QApplication.processEvents() |
229 |
self.create_detected_text_item(textInfoList)
|
230 |
QApplication.processEvents() |
231 |
self.create_detected_other_text_item(otherTextInfoList)
|
232 |
QApplication.processEvents() |
233 |
self.create_detected_title_block_text_item(titleBlockTextInfoList)
|
234 |
|
235 |
# update scene
|
236 |
# self.graphicsView.scene().update(self.graphicsView.sceneRect())
|
237 |
except Exception as ex: |
238 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
239 |
sys.exc_info()[-1].tb_lineno)
|
240 |
self.displayLog.emit(MessageType.Error, message)
|
241 |
|
242 |
'''
|
243 |
history 2018.06.09 humkyung check length of original and connection point is 2 while parsing
|
244 |
2018.11.26 euisung remove scene dependency
|
245 |
2018.11.29 euisung change name drawDetectedSymbolItem() -> createDetectedSymbolItem
|
246 |
'''
|
247 |
|
248 |
def create_detected_symbol_item(self, symbolList): |
249 |
from GraphicsBoundingBoxItem import QGraphicsBoundingBoxItem |
250 |
from SymbolSvgItem import SymbolSvgItem |
251 |
import math |
252 |
|
253 |
try:
|
254 |
app_doc_data = AppDocData.instance() |
255 |
project = app_doc_data.getCurrentProject() |
256 |
|
257 |
searchedMap = [] |
258 |
for symbol in symbolList: |
259 |
pt = [float(x) for x in symbol.getSp()] |
260 |
size = [symbol.getWidth(), symbol.getHeight()] |
261 |
name = symbol.getName() |
262 |
angle = round(math.radians(symbol.getRotatedAngle()), 2) |
263 |
_type = symbol.getType() |
264 |
flip = symbol.getDetectFlip() |
265 |
origin = [0, 0] |
266 |
if 2 == len(symbol.getOriginalPoint().split(',')): |
267 |
tokens = symbol.getOriginalPoint().split(',')
|
268 |
origin = [pt[0] + float(tokens[0]), pt[1] + float(tokens[1])] |
269 |
connPts = [] |
270 |
if symbol.getConnectionPoint() is not None and symbol.getConnectionPoint() != '': |
271 |
for param in symbol.getConnectionPoint().split('/'): |
272 |
tokens = param.split(',')
|
273 |
connPts.append( |
274 |
('AUTO', pt[0] + float(tokens[0]), pt[1] + float(tokens[1]), '0') if len(tokens) == 2 else \ |
275 |
(tokens[0], pt[0] + float(tokens[1]), pt[1] + float(tokens[2]), '0') if len( |
276 |
tokens) == 3 else \ |
277 |
(tokens[0], pt[0] + float(tokens[1]), pt[1] + float(tokens[2]), tokens[3]) if len( |
278 |
tokens) == 4 else None) |
279 |
|
280 |
parentSymbol = symbol.getBaseSymbol() |
281 |
childSymbol = symbol.getAdditionalSymbol() |
282 |
hasInstrumentLabel = symbol.getHasInstrumentLabel() |
283 |
|
284 |
svgFilePath = os.path.join(project.getSvgFilePath(), _type, name + '.svg')
|
285 |
if os.path.isfile(svgFilePath):
|
286 |
svg = SymbolSvgItem.createItem(_type, None, svgFilePath, owner=None, flip=flip) |
287 |
svg.hit_ratio = symbol.hitRate |
288 |
svg.buildItem(name, _type, angle, pt, size, origin, connPts, parentSymbol, childSymbol, |
289 |
hasInstrumentLabel) |
290 |
svg.area = 'Drawing'
|
291 |
|
292 |
# set owner - 2018.07.20 added by humkyung
|
293 |
matches = [searched for searched in searchedMap if searched[0] == symbol.owner] |
294 |
if len(matches) == 1: |
295 |
svg.owner = matches[0][1] |
296 |
searchedMap.append((symbol, svg)) |
297 |
# up to here
|
298 |
|
299 |
# self.addSvgItemToScene(svg)
|
300 |
app_doc_data.symbols.append(svg) |
301 |
app_doc_data.allItems.append(svg) |
302 |
else:
|
303 |
item = QGraphicsBoundingBoxItem(pt[0], pt[1], size[0], size[1]) |
304 |
item.isSymbol = True
|
305 |
item.angle = angle |
306 |
item.setPen(QPen(Qt.red, 5, Qt.SolidLine))
|
307 |
# self.graphicsView.scene().addItem(item)
|
308 |
# appDocData.symbols.append(item)
|
309 |
app_doc_data.allItems.append(item) |
310 |
# up to here
|
311 |
except Exception as ex: |
312 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
313 |
sys.exc_info()[-1].tb_lineno)
|
314 |
self.displayLog.emit(MessageType.Error, message)
|
315 |
|
316 |
'''
|
317 |
@history 2018.06.08 Jeongwoo Add parameter on round method
|
318 |
@history 2018.11.02 euisung Add save note text item
|
319 |
@history 2018.11.05 euisung delete save note text item and move to drawDetectedItems()
|
320 |
2018.11.26 euisung remove scene dependency
|
321 |
2018.11.29 euisung change name drawDetectedTextItem() -> createDetectedTextItem
|
322 |
'''
|
323 |
|
324 |
def create_detected_text_item(self, textInfoList): |
325 |
from TextItemFactory import TextItemFactory |
326 |
import math |
327 |
|
328 |
try:
|
329 |
app_doc_data = AppDocData.instance() |
330 |
factory = TextItemFactory.instance() |
331 |
|
332 |
# parse texts
|
333 |
for textInfo in textInfoList: |
334 |
x = textInfo.getX() |
335 |
y = textInfo.getY() |
336 |
width = textInfo.getW() |
337 |
height = textInfo.getH() |
338 |
angle = round(math.radians(textInfo.getAngle()), 2) |
339 |
text = textInfo.getText() |
340 |
if not text: continue |
341 |
|
342 |
item = factory.createTextItem(textInfo) |
343 |
if item is not None: |
344 |
item.loc = [x, y] |
345 |
item.size = (width, height) |
346 |
item.angle = angle |
347 |
item.area = 'Drawing'
|
348 |
#item.transfer.onRemoved.connect(self.itemRemoved)
|
349 |
# self.addTextItemToScene(item)
|
350 |
app_doc_data.texts.append(item) |
351 |
app_doc_data.allItems.append(item) |
352 |
except Exception as ex: |
353 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
354 |
sys.exc_info()[-1].tb_lineno)
|
355 |
self.displayLog.emit(MessageType.Error, message)
|
356 |
|
357 |
'''
|
358 |
@brief draw detected texts except which in drawing area
|
359 |
@history 2018.11.29 euisung change name drawDetectedOtherTextItem() -> createDetectedOtherTextItem
|
360 |
'''
|
361 |
|
362 |
def create_detected_other_text_item(self, otherTextInfoList): |
363 |
from TextItemFactory import TextItemFactory |
364 |
import math |
365 |
|
366 |
try:
|
367 |
app_doc_data = AppDocData.instance() |
368 |
factory = TextItemFactory.instance() |
369 |
|
370 |
# parse notes
|
371 |
for textInfoMap in otherTextInfoList: |
372 |
if textInfoMap[0] == 'Note' or textInfoMap[1] is None: |
373 |
pass
|
374 |
|
375 |
for textInfo in textInfoMap[1]: |
376 |
x = textInfo.getX() |
377 |
y = textInfo.getY() |
378 |
width = textInfo.getW() |
379 |
height = textInfo.getH() |
380 |
angle = round(math.radians(textInfo.getAngle()))
|
381 |
text = textInfo.getText() |
382 |
|
383 |
item = factory.createTextItem(textInfo) |
384 |
|
385 |
item.loc = [x, y] |
386 |
item.size = (width, height) |
387 |
item.angle = angle |
388 |
item.area = textInfoMap[0]
|
389 |
#item.transfer.onRemoved.connect(self.itemRemoved)
|
390 |
app_doc_data.texts.append(item) |
391 |
app_doc_data.allItems.append(item) |
392 |
except Exception as ex: |
393 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
394 |
sys.exc_info()[-1].tb_lineno)
|
395 |
self.displayLog.emit(MessageType.Error, message)
|
396 |
|
397 |
def create_detected_title_block_text_item(self, textInfoList): |
398 |
"""draw title block"""
|
399 |
from TextItemFactory import TextItemFactory |
400 |
import math |
401 |
|
402 |
try:
|
403 |
app_doc_data = AppDocData.instance() |
404 |
factory = TextItemFactory.instance() |
405 |
|
406 |
# parse texts
|
407 |
for textInfos in textInfoList: |
408 |
if len(textInfos[1]) is 0: |
409 |
continue
|
410 |
|
411 |
for textInfo in textInfos[1]: |
412 |
x = textInfo.getX() |
413 |
y = textInfo.getY() |
414 |
width = textInfo.getW() |
415 |
height = textInfo.getH() |
416 |
angle = round(math.radians(textInfo.getAngle()), 2) |
417 |
text = textInfo.getText() |
418 |
if not text: continue |
419 |
item = factory.createTextItem(textInfo) |
420 |
|
421 |
if item is not None: |
422 |
item.loc = [x, y] |
423 |
item.size = (width, height) |
424 |
item.angle = angle |
425 |
item.area = textInfos[0]
|
426 |
# self.addTextItemToScene(item)
|
427 |
app_doc_data.texts.append(item) |
428 |
app_doc_data.allItems.append(item) |
429 |
except Exception as ex: |
430 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
431 |
sys.exc_info()[-1].tb_lineno)
|
432 |
self.displayLog.emit(MessageType.Error, message)
|
433 |
|
434 |
'''
|
435 |
@brief draw unknown items
|
436 |
@author humkyung
|
437 |
@date 2018.06.12
|
438 |
@history 2018.06.14 Jeongwoo Change method to add unknown item
|
439 |
2018.06.18 Jeongwoo Add connect on unknown item
|
440 |
Add [transfer] for using pyqtSignal
|
441 |
2018.11.26 euisung remove scene dependency
|
442 |
2018.11.26 euisung isolate scene adding part -> drawDetectedItemsToScene()
|
443 |
2018.11.27 euisung add save to xml
|
444 |
2018.11.29 euisung change name drawUnknownItems() -> createUnknownItems
|
445 |
'''
|
446 |
|
447 |
def create_unknown_items(self, path): |
448 |
from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem |
449 |
from EngineeringLineItem import QEngineeringLineItem |
450 |
from EngineeringUnknownItem import QEngineeringUnknownItem |
451 |
|
452 |
try:
|
453 |
app_doc_data = AppDocData.instance() |
454 |
project = app_doc_data.getCurrentProject() |
455 |
windowSize = app_doc_data.getSlidingWindowSize() |
456 |
|
457 |
thickness = int(windowSize[1] / 2) |
458 |
|
459 |
area = app_doc_data.getArea('Drawing')
|
460 |
diffFilePath = os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(path))
|
461 |
|
462 |
# remove line from image
|
463 |
imgDiff = np.ones(app_doc_data.imgSrc.shape, np.uint8) * 255
|
464 |
imgDiff[round(area.y + 1):round(area.y + area.height), |
465 |
round(area.x + 1):round(area.x + area.width)] = \ |
466 |
app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height), |
467 |
round(area.x + 1):round(area.x + area.width)] |
468 |
|
469 |
lines = app_doc_data.lines |
470 |
for line in lines: |
471 |
line.drawToImage(imgDiff, 255, thickness) if line.thickness is None else \ |
472 |
line.drawToImage(imgDiff, 255, line.thickness)
|
473 |
# up to here
|
474 |
# cv2.imwrite(diffFilePath, imgDiff) for debug
|
475 |
|
476 |
imgNot = np.ones(imgDiff.shape, np.uint8) |
477 |
cv2.bitwise_not(imgDiff, imgNot) |
478 |
configs = app_doc_data.getConfigs('Filter', 'ErodeSize') |
479 |
kernel = int(configs[0].value) if 1 == len(configs) else 3 |
480 |
imgNot = cv2.erode(imgNot, np.ones((kernel, kernel), np.uint8)) |
481 |
imgNot = cv2.dilate(imgNot, np.ones((8 + kernel, 8 + kernel), np.uint8)) |
482 |
|
483 |
contours, hierarchy = cv2.findContours(imgNot, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
484 |
|
485 |
##
|
486 |
idx = 0
|
487 |
##
|
488 |
smallContours = [] |
489 |
minimumSize = app_doc_data.getConfigs('Filter', 'MinimumSize') |
490 |
for contour in contours: |
491 |
[x, y, w, h] = cv2.boundingRect(contour) |
492 |
|
493 |
# remove too small one
|
494 |
if len(minimumSize) is 1: |
495 |
if w * h < int(minimumSize[0].value) * int(minimumSize[0].value): |
496 |
smallContours.append(contour) |
497 |
idx += 1
|
498 |
continue
|
499 |
|
500 |
# create unknown item
|
501 |
epsilon = cv2.arcLength(contour, True) * 0.001 |
502 |
approx = cv2.approxPolyDP(contour, epsilon, True)
|
503 |
approx = [pt[0] for pt in approx] |
504 |
resultStr, resultList = self.determine_remain_object(idx, contours, imgNot)
|
505 |
if resultStr == 'LineIndicator': |
506 |
item = QEngineeringUnknownItem(approx, 'True', resultList[0], resultList[1]) |
507 |
app_doc_data.lineIndicators.append(item) |
508 |
elif resultStr == 'MissingLine': |
509 |
idx += 1
|
510 |
continue
|
511 |
elif resultStr == 'Unknown': |
512 |
item = QEngineeringUnknownItem(approx, 'False')
|
513 |
app_doc_data.unknowns.append(item) |
514 |
item.area = 'Drawing'
|
515 |
app_doc_data.allItems.append(item) |
516 |
idx += 1
|
517 |
# up to here
|
518 |
|
519 |
"""
|
520 |
if app_doc_data.needReOpening is not None:
|
521 |
app_doc_data.needReOpening = True
|
522 |
"""
|
523 |
|
524 |
"""
|
525 |
diffFilePath = os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(path))
|
526 |
if os.path.isfile(diffFilePath):
|
527 |
imgDiff = cv2.threshold(cv2.cvtColor(cv2.imread(diffFilePath, 1), cv2.COLOR_BGR2GRAY), 0, 255,
|
528 |
cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
|
529 |
|
530 |
# remove line from image
|
531 |
lines = app_doc_data.lines
|
532 |
for line in lines:
|
533 |
line.drawToImage(imgDiff, 255, thickness) if line.thickness is None else \
|
534 |
line.drawToImage(imgDiff, 255, line.thickness)
|
535 |
cv2.imwrite(diffFilePath, imgDiff)
|
536 |
# up to here
|
537 |
|
538 |
imgNot = np.ones(imgDiff.shape, np.uint8)
|
539 |
cv2.bitwise_not(imgDiff, imgNot)
|
540 |
configs = app_doc_data.getConfigs('Filter', 'ErodeSize')
|
541 |
kernel = int(configs[0].value) if 1 == len(configs) else 3
|
542 |
imgNot = cv2.erode(imgNot, np.ones((kernel, kernel), np.uint8))
|
543 |
imgNot = cv2.dilate(imgNot, np.ones((8 + kernel, 8 + kernel), np.uint8))
|
544 |
|
545 |
contours, hierarchy = cv2.findContours(imgNot, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
546 |
|
547 |
##
|
548 |
idx = 0
|
549 |
##
|
550 |
smallContours = []
|
551 |
minimumSize = app_doc_data.getConfigs('Filter', 'MinimumSize')
|
552 |
for contour in contours:
|
553 |
[x, y, w, h] = cv2.boundingRect(contour)
|
554 |
|
555 |
# remove too small one
|
556 |
if len(minimumSize) is 1:
|
557 |
if w * h < int(minimumSize[0].value) * int(minimumSize[0].value):
|
558 |
smallContours.append(contour)
|
559 |
idx += 1
|
560 |
continue
|
561 |
|
562 |
'''
|
563 |
rect = QRectF(x, y, w, h)
|
564 |
items = [item for item in diffItems if item.boundingRect().contains(rect)]
|
565 |
if len(items) > 0: continue
|
566 |
|
567 |
items = [item for item in diffItems if rect.contains(item.boundingRect())]
|
568 |
for item in items:
|
569 |
diffItems.remove(item)
|
570 |
'''
|
571 |
|
572 |
# create unknown item
|
573 |
epsilon = cv2.arcLength(contour, True) * 0.001
|
574 |
approx = cv2.approxPolyDP(contour, epsilon, True)
|
575 |
approx = [pt[0] for pt in approx]
|
576 |
resultStr, resultList = self.determine_remain_object(idx, contours, imgNot)
|
577 |
if resultStr == 'LineIndicator':
|
578 |
item = QEngineeringUnknownItem(approx, 'True', resultList[0], resultList[1])
|
579 |
app_doc_data.lineIndicators.append(item)
|
580 |
elif resultStr == 'MissingLine':
|
581 |
pass
|
582 |
elif resultStr == 'Unknown':
|
583 |
item = QEngineeringUnknownItem(approx, 'False')
|
584 |
app_doc_data.unknowns.append(item)
|
585 |
item.area = 'Drawing'
|
586 |
app_doc_data.allItems.append(item)
|
587 |
#item.transfer.onRemoved.connect(self.itemRemoved)
|
588 |
idx += 1
|
589 |
# up to here
|
590 |
|
591 |
imgNotRemoveSmall = cv2.drawContours(imgNot, smallContours, -1, 0, -1)
|
592 |
notFilePath = os.path.join(project.getTempPath(), "NOT_" + os.path.basename(path))
|
593 |
cv2.imwrite(notFilePath, imgNotRemoveSmall)
|
594 |
else:
|
595 |
message = 'can\'t found {}'.format(diffFilePath)
|
596 |
self.displayLog.emit(MessageType.Normal, message)
|
597 |
"""
|
598 |
|
599 |
except Exception as ex: |
600 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
601 |
sys.exc_info()[-1].tb_lineno)
|
602 |
self.displayLog.emit(MessageType.Error, message)
|
603 |
|
604 |
def determine_remain_object(self, idx, contours, imgNot): |
605 |
"""determine remain objects -> line no indicator or unknown"""
|
606 |
import math |
607 |
[x, y, w, h] = cv2.boundingRect(contours[idx]) |
608 |
|
609 |
if w < 250 and h < 250: |
610 |
return ('Unknown', []) |
611 |
|
612 |
fLines = [] |
613 |
maxDifAngle = 3
|
614 |
mask = np.zeros_like(imgNot) |
615 |
cv2.drawContours(mask, contours, idx, 123, -1) # Draw filled contour in mask |
616 |
out = np.zeros_like(imgNot) # Extract out the object and place into output image
|
617 |
out[mask == 123] = imgNot[mask == 123] |
618 |
|
619 |
# Now crop
|
620 |
##print(out)
|
621 |
(x, y) = np.where(mask == 123)
|
622 |
(topx, topy) = (np.min(x), np.min(y)) |
623 |
(bottomx, bottomy) = (np.max(x), np.max(y)) |
624 |
out = out[topx:bottomx + 1, topy:bottomy + 1] |
625 |
h, w = out.shape[0], out.shape[1] |
626 |
maxDifH, maxDifW = math.ceil(math.tan(4 * math.pi / 180) / 2 * w), math.ceil( |
627 |
math.tan(4 * math.pi / 180) / 2 * h) |
628 |
|
629 |
# detection lines
|
630 |
edged2 = cv2.Canny(out, 100, 200) |
631 |
lines = cv2.HoughLinesP(image=edged2, rho=1, theta=np.pi / 180, threshold=25, minLineLength=30, maxLineGap=25) |
632 |
# lines = cv2.HoughLines(edged2, 1, np.pi/180, 60)
|
633 |
if lines is None: |
634 |
return ('Unknown', []) |
635 |
for line in lines: |
636 |
# r, theta = line[0]
|
637 |
# a, b = np.cos(theta), np.sin(theta)
|
638 |
# x0, y0 = a * r, b * r
|
639 |
# x1, y1 = int(x0 + 1000 * (-b)), int(y0 + 1000 * a)
|
640 |
# x2, y2 = int(x0 - 1000 * (-b)), int(y0 - 1000 * a)
|
641 |
# cv2.line(out, (x1, y1), (x2, y2), (0, 255, 0), 3)
|
642 |
x1, y1, x2, y2 = line[0]
|
643 |
degree = math.atan2(y2 - y1, x2 - x1) * 180 / math.pi
|
644 |
fLine = [x1, y1, x2, y2, degree] |
645 |
# print(fLine)
|
646 |
fLines.append(fLine) |
647 |
|
648 |
horLines = [] |
649 |
verLines = [] |
650 |
otherLines = [] |
651 |
isVH = None
|
652 |
for fLine in fLines: |
653 |
degree = math.fabs(fLine[4])
|
654 |
if degree >= 90 - maxDifAngle: |
655 |
verLines.append(fLine) |
656 |
elif degree <= maxDifAngle:
|
657 |
horLines.append(fLine) |
658 |
else:
|
659 |
otherLines.append(fLine) |
660 |
|
661 |
baseLines = [] |
662 |
baseDifV = 0
|
663 |
if len(horLines): |
664 |
x, y = w / 2, 0 |
665 |
baseDifV = maxDifH |
666 |
for horLine in horLines: |
667 |
x1, y1, x2, y2 = horLine[0], horLine[1], horLine[2], horLine[3] |
668 |
y = ((y2 - y1) / (x2 - x1)) * x + y1 - ((y2 - y1) / (x2 - x1)) * x1 |
669 |
horLine.append(y) |
670 |
baseLines = horLines |
671 |
isVH = 'H'
|
672 |
if len(verLines): |
673 |
x, y = 0, h / 2 |
674 |
baseDifV = maxDifW |
675 |
for verLine in verLines: |
676 |
x1, y1, x2, y2 = verLine[0], verLine[1], verLine[2], verLine[3] |
677 |
x = ((x2 - x1) / (y2 - y1)) * y + x1 - ((x2 - x1) / (y2 - y1)) * y1 |
678 |
verLine.append(x) |
679 |
baseLines = verLines |
680 |
isVH = 'V'
|
681 |
|
682 |
for otherLine in otherLines: |
683 |
x, y = w / 2, 0 |
684 |
x1, y1, x2, y2 = otherLine[0], otherLine[1], otherLine[2], otherLine[3] |
685 |
y = ((y2 - y1) / (x2 - x1)) * x + y1 - ((y2 - y1) / (x2 - x1)) * x1 |
686 |
otherLine.append(y) |
687 |
|
688 |
# determine line no indicator
|
689 |
if not ((len(horLines) > 0 and len(verLines) > 0) or len(otherLines) is 0 or ( |
690 |
len(horLines) == 0 and len(verLines) == 0)): |
691 |
result, mergedOtherLine = self.is_line_no_indicator(w, h, maxDifAngle, baseDifV, baseLines, otherLines)
|
692 |
if result:
|
693 |
# print(fLines)
|
694 |
return ('LineIndicator', [isVH, mergedOtherLine]) |
695 |
|
696 |
return ('Unknown', []) |
697 |
|
698 |
def is_line_no_indicator(self, w, h, maxDifAngle, baseDifV, baseLines, otherLines): |
699 |
"""determine line no indicator"""
|
700 |
import math |
701 |
|
702 |
if w < 250 and h < 250: |
703 |
return (False, None) |
704 |
|
705 |
isSameLine = True
|
706 |
i = 0
|
707 |
for baseLine in baseLines: |
708 |
if not isSameLine: break |
709 |
j = 0
|
710 |
for baseLinee in baseLines: |
711 |
if i == j:
|
712 |
j += 1
|
713 |
continue
|
714 |
difV = math.fabs(baseLine[5] - baseLinee[5]) |
715 |
if difV > baseDifV:
|
716 |
isSameLine = False
|
717 |
break
|
718 |
j += 1
|
719 |
i += 1
|
720 |
if not isSameLine: |
721 |
return (False, None) |
722 |
|
723 |
isSameLine = True
|
724 |
i = 0
|
725 |
maxY = 0
|
726 |
for otherLine in otherLines: |
727 |
y = otherLine[5]
|
728 |
if math.fabs(y) > maxY:
|
729 |
maxY = math.fabs(y) |
730 |
if not isSameLine: break |
731 |
j = 0
|
732 |
for otherLinee in otherLines: |
733 |
if i == j:
|
734 |
j += 1
|
735 |
continue
|
736 |
difV = math.fabs(otherLine[4] - otherLinee[4]) |
737 |
if difV > maxDifAngle:
|
738 |
isSameLine = False
|
739 |
break
|
740 |
j += 1
|
741 |
i += 1
|
742 |
if not isSameLine: |
743 |
return (False, None) |
744 |
|
745 |
isSameLine = True
|
746 |
mergedOtherLine = [0, 0, 0, 0] |
747 |
i = 0
|
748 |
maxDif = math.ceil(math.tan(4 * math.pi / 180) * maxY) |
749 |
for otherLine in otherLines: |
750 |
if not isSameLine: break |
751 |
j = 0
|
752 |
for otherLinee in otherLines: |
753 |
if i == j:
|
754 |
j += 1
|
755 |
continue
|
756 |
angle = math.fabs(otherLine[4] + otherLinee[4]) / 2 |
757 |
difV = math.fabs(otherLine[5] - otherLinee[5]) |
758 |
dist = math.sin((90 - angle) * math.pi / 180) * difV |
759 |
if dist > maxDif:
|
760 |
isSameLine = False
|
761 |
break
|
762 |
j += 1
|
763 |
i += 1
|
764 |
mergedOtherLine[0] += otherLine[0] |
765 |
mergedOtherLine[1] += otherLine[1] |
766 |
mergedOtherLine[2] += otherLine[2] |
767 |
mergedOtherLine[3] += otherLine[3] |
768 |
if not isSameLine: |
769 |
(False, None) |
770 |
|
771 |
# Show the output image
|
772 |
# print('line no indicator')
|
773 |
mergedOtherLine[0] = round(mergedOtherLine[0] / len(otherLines)) |
774 |
mergedOtherLine[1] = round(mergedOtherLine[1] / len(otherLines)) |
775 |
mergedOtherLine[2] = round(mergedOtherLine[2] / len(otherLines)) |
776 |
mergedOtherLine[3] = round(mergedOtherLine[3] / len(otherLines)) |
777 |
# cv2.line(out, (mergedOtherLine[0], mergedOtherLine[1]), (mergedOtherLine[2], mergedOtherLine[3]), (255, 255, 255), 3)
|
778 |
# cv2.imshow('Output', out)
|
779 |
# cv2.waitKey(0)
|
780 |
# cv2.destroyAllWindows()
|
781 |
return (True, mergedOtherLine) |
782 |
|
783 |
@staticmethod
|
784 |
def executeRecognition(drawings, listWidget, isLineChecked, worker): |
785 |
"""recognize symbol, text, line from image p&id"""
|
786 |
import re |
787 |
import timeit |
788 |
from TextDetector import TextDetector |
789 |
from CodeTables import CodeTable |
790 |
from datetime import datetime |
791 |
|
792 |
global ocrCompletedSrc
|
793 |
global threadLock
|
794 |
global searchedSymbolList
|
795 |
global textInfoList
|
796 |
global maxProgressValue
|
797 |
|
798 |
try:
|
799 |
app_doc_data = AppDocData.instance() |
800 |
#drawings = app_doc_data.getDrawings()
|
801 |
project = app_doc_data.getCurrentProject() |
802 |
textDetector = TextDetector() |
803 |
|
804 |
configs = app_doc_data.getConfigs('Engine', 'Symbol') |
805 |
if (configs and int(configs[0].value) is 1) or not configs or worker.isTrainingChecked: |
806 |
Worker.initTargetSymbolDataList() |
807 |
else:
|
808 |
Worker.initTargetSymbolDataList(all=True)
|
809 |
|
810 |
for drawing in drawings: |
811 |
ocrCompletedSrc = [] |
812 |
searchedSymbolList = [] |
813 |
textInfoList = [] |
814 |
|
815 |
mainRes = drawing.file_path |
816 |
if not os.path.isfile(mainRes): |
817 |
item = QListWidgetItem('{} file is not found'.format(os.path.basename(mainRes)))
|
818 |
item.setBackground(Qt.red) |
819 |
listWidget.addItem(item) |
820 |
continue
|
821 |
|
822 |
#worker.scene.clear()
|
823 |
#worker.text_scene.clear()
|
824 |
#worker.line_scene.clear()
|
825 |
worker.clear_scene.emit(worker.scene, worker.text_scene, worker.line_scene) |
826 |
worker.cond.wait(worker.mutex) |
827 |
|
828 |
app_doc_data.clearItemList(True)
|
829 |
|
830 |
app_doc_data.setImgFilePath(mainRes) |
831 |
app_doc_data.imgSrc = None
|
832 |
matches = [drawing for drawing in drawings if app_doc_data.imgName == os.path.splitext(drawing.name)[0]] |
833 |
app_doc_data.activeDrawing = drawing # matches[0] if matches else Drawing(None, app_doc_data.imgName, None)
|
834 |
#app_doc_data.activeDrawing.set_pid_source(Image.open(mainRes))
|
835 |
|
836 |
# Load equipment package
|
837 |
worker.add_predata_to_scene.emit(app_doc_data.activeDrawing, worker.scene, False, False, False, False, True) |
838 |
worker.cond.wait(worker.mutex) |
839 |
|
840 |
# remove not drawing area
|
841 |
configs = app_doc_data.getConfigs('{} Equipment Desc Area'.format(app_doc_data.imgName))
|
842 |
for config in configs: |
843 |
found = re.findall('\\d+', config.value)
|
844 |
if len(found) == 4: |
845 |
cv2.rectangle(app_doc_data.imgSrc, (int(found[0]), int(found[1])), |
846 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1) |
847 |
|
848 |
configs = app_doc_data.getConfigs('{} Typical Area'.format(app_doc_data.imgName))
|
849 |
for config in configs: |
850 |
found = re.findall('\\d+', config.value)
|
851 |
if len(found) == 4: |
852 |
cv2.rectangle(app_doc_data.imgSrc, (int(found[0]), int(found[1])), |
853 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1) |
854 |
|
855 |
noteArea = app_doc_data.getArea('Note')
|
856 |
if noteArea is not None: |
857 |
noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height), |
858 |
round(noteArea.x):round(noteArea.x + noteArea.width)].copy() |
859 |
cv2.rectangle(app_doc_data.imgSrc, (round(noteArea.x), round(noteArea.y)), |
860 |
(round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1) |
861 |
#cv2.imwrite('c:\\temp\\note.png', noteArea.img)
|
862 |
#cv2.imwrite('c:\\temp\\removed_note.png', app_doc_data.imgSrc)
|
863 |
# up to here
|
864 |
|
865 |
area = app_doc_data.getArea('Drawing')
|
866 |
if area is not None: |
867 |
# copy image
|
868 |
area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height), |
869 |
round(area.x):round(area.x + area.width)] |
870 |
|
871 |
_, area.mask = cv2.threshold(area.img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) |
872 |
|
873 |
# area.contours, area.hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
|
874 |
area.not_img = cv2.bitwise_not(area.img) |
875 |
area.contours, area.hierachy = cv2.findContours(area.not_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) |
876 |
|
877 |
# remove Equipment Package area
|
878 |
configs = app_doc_data.getConfigs('Symbol', 'Detect Inside Package') |
879 |
if (configs and int(configs[0].value) == -1): |
880 |
Worker.remove_equipment_package(worker.scene, area) |
881 |
|
882 |
maxProgressValue = 0
|
883 |
start_time = timeit.default_timer() |
884 |
listWidget.addItem(f"Start recognition {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} : {os.path.basename(mainRes).split('.')[0]}")
|
885 |
threadLock.acquire() |
886 |
|
887 |
worker.updateBatchProgress.emit(len(drawings), 1) |
888 |
|
889 |
if worker.isSymbolChecked:
|
890 |
if worker.symbol_time:
|
891 |
symbol_time_temp = worker.symbol_time |
892 |
else:
|
893 |
symbol_time_temp = None
|
894 |
worker.symbol_time = timeit.default_timer() |
895 |
|
896 |
# calculate total count of symbol
|
897 |
for targetItem in targetSymbolList: |
898 |
if type(targetItem) is list: |
899 |
maxProgressValue += len(targetItem)
|
900 |
else:
|
901 |
maxProgressValue += 1
|
902 |
# up to here
|
903 |
maxProgressValue = maxProgressValue * 2
|
904 |
threadLock.release() |
905 |
|
906 |
if worker.isSymbolChecked:
|
907 |
worker.displayTitle.emit(worker.tr('Detecting symbols...'))
|
908 |
|
909 |
configs = app_doc_data.getConfigs('Engine', 'Symbol') |
910 |
if (configs and int(configs[0].value) is 1) or not configs or worker.isTrainingChecked: |
911 |
# get symbol original way
|
912 |
|
913 |
# detect only equipments
|
914 |
with futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as pool: |
915 |
future_equipment = {pool.submit(Worker.detectSymbolsOnPid, mainRes, symbol, listWidget, worker): |
916 |
symbol for symbol in targetSymbolList[0]} |
917 |
futures.wait(future_equipment) |
918 |
# up to here
|
919 |
|
920 |
# detect normal symbols
|
921 |
with futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as pool: |
922 |
future_symbol = {pool.submit(Worker.detectSymbolsOnPid, mainRes, symbol, listWidget, worker): |
923 |
symbol for symbol in targetSymbolList[2]} |
924 |
futures.wait(future_symbol) |
925 |
# up to here
|
926 |
else:
|
927 |
# using AI
|
928 |
listWidget.addItem(worker.tr('Waiting for symbol information from the server...'))
|
929 |
Worker.detect_symbol_using_server(targetSymbolList, listWidget, worker) |
930 |
|
931 |
if worker.isTrainingChecked:
|
932 |
import uuid |
933 |
worker.displayTitle.emit(worker.tr('Generating Data...'))
|
934 |
listWidget.addItem(worker.tr('Generating Data...'))
|
935 |
|
936 |
for item in searchedSymbolList: |
937 |
path = os.path.join(project.getTempPath(), 'Tile', item.getName())
|
938 |
if not os.path.exists(path): os.makedirs(path) |
939 |
|
940 |
_img = app_doc_data.imgSrc[round(item.getSp()[1]):round(item.getSp()[1] + item.getHeight()), |
941 |
round(item.getSp()[0]):round(item.getSp()[0] + item.getWidth())] |
942 |
cv2.imwrite(os.path.join(path, str(uuid.uuid4()) + '.png'), _img) |
943 |
|
944 |
app_doc_data.activeDrawing.image = None
|
945 |
worker.updateBatchProgress.emit(len(drawings), 4) |
946 |
worker.symbol_time = timeit.default_timer() - worker.symbol_time if not symbol_time_temp else timeit.default_timer() - worker.symbol_time + symbol_time_temp |
947 |
continue
|
948 |
|
949 |
#cv2.imwrite('c:\\temp\\before-imgSrc.png', area.img)
|
950 |
## save original image before remove symbol
|
951 |
#worker.copy_imgSrc = app_doc_data.imgSrc.copy()
|
952 |
|
953 |
# roll back because equipment package was deleted from image
|
954 |
app_doc_data.imgSrc = app_doc_data.activeDrawing.image_origin |
955 |
|
956 |
searchedTextSymList = [] |
957 |
for sym in searchedSymbolList: |
958 |
Worker.remove_detected_symbol_image(sym, app_doc_data.imgSrc) |
959 |
|
960 |
worker.symbol_time = timeit.default_timer() - worker.symbol_time if not symbol_time_temp else timeit.default_timer() - worker.symbol_time + symbol_time_temp |
961 |
else:
|
962 |
'''
|
963 |
import math
|
964 |
|
965 |
symbolItems = [item for item in worker.scene.items() if issubclass(type(item), SymbolSvgItem)]
|
966 |
|
967 |
for symbolItem in symbolItems:
|
968 |
symbolName = symbolItem.name
|
969 |
symbolType = symbolItem.type
|
970 |
searchedItemSp = [int(symbolItem.loc[0]), int(symbolItem.loc[1])]
|
971 |
sw = symbolItem.size[0] # check later
|
972 |
sh = symbolItem.size[1] # check later
|
973 |
|
974 |
symbolThreshold = 50 # not necessary
|
975 |
symbolMinMatchCount = 0 # not necessary
|
976 |
hitRate = 80 # not necessary
|
977 |
|
978 |
allowed_error = 0.0001
|
979 |
#degree 0
|
980 |
if abs(symbolItem.angle - 0) <= allowed_error:
|
981 |
symbolRotatedAngle = 0
|
982 |
#degree 90
|
983 |
elif abs(symbolItem.angle - 1.57) <= allowed_error:
|
984 |
symbolRotatedAngle = 90
|
985 |
#degree 180
|
986 |
elif abs(symbolItem.angle - 3.14) <= allowed_error:
|
987 |
symbolRotatedAngle = 180
|
988 |
#degree 270
|
989 |
elif abs(symbolItem.angle - 4.71) <= allowed_error:
|
990 |
symbolRotatedAngle = 270
|
991 |
else:
|
992 |
symbolRotatedAngle = math.pi / 180 * symbolItem.angle
|
993 |
|
994 |
isDetectOnOrigin = 80 # not necessary
|
995 |
symbolRotateCount = 0 # not necessary
|
996 |
symbolOcrOption = 0 # not necessary
|
997 |
isContainChild = 0 # not necessary
|
998 |
originalPoint = [symbolItem.origin[0] - symbolItem.loc[0], symbolItem.origin[1] - symbolItem.loc[1]]
|
999 |
symbolConnectionPoint = []
|
1000 |
baseSymbol = str(symbolItem.parentSymbol)
|
1001 |
additionalSymbol = str(symbolItem.childSymbol)
|
1002 |
isExceptDetect = 0 # not necessary
|
1003 |
detectFlip = symbolItem.flip
|
1004 |
|
1005 |
searchedSymbolList.append(Symbol(symbolName, symbolType ,
|
1006 |
searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, hitRate, symbolRotatedAngle ,
|
1007 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild ,
|
1008 |
','.join(str(x) for x in originalPoint),
|
1009 |
[],
|
1010 |
baseSymbol, additionalSymbol, isExceptDetect, detectFlip))
|
1011 |
|
1012 |
worker.scene.removeItem(symbolItem)
|
1013 |
|
1014 |
pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER)
|
1015 |
'''
|
1016 |
# symbolItems = [item for item in worker.scene.items() if issubclass(type(item), SymbolSvgItem)]
|
1017 |
# appDocData.symbols.extend(symbolItems)
|
1018 |
# appDocData.allItems.extend(symbolItems)
|
1019 |
|
1020 |
# for sym in searchedSymbolList:
|
1021 |
# pool.submit(Worker.remove_detected_symbol_image, sym, appDocData.imgSrc)
|
1022 |
# pool.shutdown(wait = True)
|
1023 |
|
1024 |
worker.updateBatchProgress.emit(len(drawings), 1) |
1025 |
|
1026 |
area = app_doc_data.getArea('Drawing')
|
1027 |
if area is not None: |
1028 |
# copy area image
|
1029 |
area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height), |
1030 |
round(area.x):round(area.x + area.width)] |
1031 |
|
1032 |
offset = (area.x, area.y) if area is not None else (0, 0) |
1033 |
|
1034 |
otherTextInfoList = None
|
1035 |
titleBlockTextInfoList = None
|
1036 |
textInfoList = [] |
1037 |
if worker.isTextChecked:
|
1038 |
from TextInfo import TextInfo |
1039 |
|
1040 |
if worker.text_time:
|
1041 |
text_time_temp = worker.text_time |
1042 |
else:
|
1043 |
text_time_temp = None
|
1044 |
worker.text_time = timeit.default_timer() |
1045 |
|
1046 |
textAreas, ocr_image = textDetector.detectTextAreas(area.img if area is not None else app_doc_data.imgSrc, |
1047 |
offset, listWidget=listWidget, worker=worker) |
1048 |
|
1049 |
# get text area by using symbol setting
|
1050 |
text_area_symbols = [] |
1051 |
for symbol in [symbol for symbol in searchedSymbolList if symbol.text_area]: |
1052 |
symbol_angle = symbol.getRotatedAngle() |
1053 |
for text_area in symbol.text_area: |
1054 |
so = [symbol.getWidth(), symbol.getHeight()] if symbol_angle is 0 or symbol_angle is 180\ |
1055 |
else [symbol.getHeight(), symbol.getWidth()]
|
1056 |
text_area_origin = Worker.getCalculatedOriginalPoint(None, str(text_area.center()[0]) + ',' + str(text_area.center()[1]),\ |
1057 |
symbol_angle, None, None, so[0], so[1], symbol.getDetectFlip()) |
1058 |
x = text_area_origin[0] - round(text_area.width / 2) if symbol_angle is 0 or symbol_angle is 180\ |
1059 |
else text_area_origin[0] - round(text_area.height / 2) |
1060 |
x = x + symbol.sp[0]
|
1061 |
y = text_area_origin[1] - round(text_area.height / 2) if symbol_angle is 0 or symbol_angle is 180\ |
1062 |
else text_area_origin[1] - round(text_area.width / 2) |
1063 |
y = y + symbol.sp[1]
|
1064 |
w = text_area.width if symbol_angle is 0 or symbol_angle is 180 else text_area.height |
1065 |
h = text_area.height if symbol_angle is 0 or symbol_angle is 180 else text_area.width |
1066 |
|
1067 |
if symbol_angle is 180 or symbol_angle is 0: |
1068 |
text_angle = 0
|
1069 |
else:
|
1070 |
text_angle = 90
|
1071 |
text_area_symbols.append(TextInfo('', x, y, w, h, text_angle))
|
1072 |
|
1073 |
for textArea_index in reversed(range(len(textAreas))): |
1074 |
is_pop = False
|
1075 |
for text_area_index in reversed(range(len(text_area_symbols))): |
1076 |
if text_area_symbols[text_area_index].contains(textAreas[textArea_index].center) or \ |
1077 |
textAreas[textArea_index].contains(text_area_symbols[text_area_index].center): |
1078 |
if True: ## select text area from symbol |
1079 |
#if text_area_symbols[text_area_index].area > textAreas[textArea_index].area: ## select bigger one
|
1080 |
textAreas.pop(textArea_index) |
1081 |
is_pop = True
|
1082 |
break
|
1083 |
else:
|
1084 |
text_area_symbols.pop(text_area_index) |
1085 |
break
|
1086 |
if is_pop:
|
1087 |
break
|
1088 |
textAreas.extend(text_area_symbols) |
1089 |
# up to here
|
1090 |
|
1091 |
if maxProgressValue < 2 * len(textAreas): |
1092 |
for _ in range(len(textAreas) - int(maxProgressValue * 0.5)): |
1093 |
worker.updateProgress.emit(2 * len(textAreas), None) |
1094 |
maxProgressValue = 2 * len(textAreas) |
1095 |
else:
|
1096 |
maxProgressValue = int(maxProgressValue * 0.5) + len(textAreas) |
1097 |
|
1098 |
worker.displayTitle.emit(worker.tr('Detecting texts...'))
|
1099 |
textDetector.recognizeText(area.img, offset, textAreas, searchedSymbolList, worker, |
1100 |
listWidget, maxProgressValue) |
1101 |
textInfoList = textDetector.textInfoList.copy() if textDetector.textInfoList is not None else None |
1102 |
otherTextInfoList = textDetector.otherTextInfoList.copy() if textDetector.otherTextInfoList is not None else None |
1103 |
titleBlockTextInfoList = textDetector.titleBlockTextInfoList.copy() if textDetector.titleBlockTextInfoList is not None else None |
1104 |
|
1105 |
app_doc_data.activeDrawing.width, app_doc_data.activeDrawing.height = \ |
1106 |
app_doc_data.imgSrc.shape[::-1]
|
1107 |
|
1108 |
app_doc_data.imgName = os.path.splitext(os.path.basename(mainRes))[0]
|
1109 |
|
1110 |
worker.text_time = timeit.default_timer() - worker.text_time if not text_time_temp else timeit.default_timer() - worker.text_time + text_time_temp |
1111 |
else:
|
1112 |
import math |
1113 |
from TextInfo import TextInfo |
1114 |
|
1115 |
"""load texts"""
|
1116 |
worker.add_predata_to_scene.emit(app_doc_data.activeDrawing, worker.text_scene, False, True, False, False, False) |
1117 |
worker.cond.wait(worker.mutex) |
1118 |
"""up to here"""
|
1119 |
textItems = [item for item in worker.text_scene.items() if issubclass(type(item), QEngineeringTextItem)] |
1120 |
app_doc_data.texts.extend(textItems) |
1121 |
app_doc_data.allItems.extend(textItems) |
1122 |
|
1123 |
lineNoTextItems = [item for item in textItems if type(item) is QEngineeringLineNoTextItem] |
1124 |
for lineNoTextItem in lineNoTextItems: |
1125 |
lineNoTextItem.set_property('Freeze', False) |
1126 |
# lineNoTextItem.freeze_item.update_freeze(lineNoTextItem.prop('Freeze'))
|
1127 |
lineNoTextItem.explode() |
1128 |
|
1129 |
for textItem in textItems: |
1130 |
textInfoList.append( |
1131 |
TextInfo(textItem.text(), textItem.loc[0], textItem.loc[1], textItem.size[0], |
1132 |
textItem.size[1], round(math.degrees(textItem.angle)))) |
1133 |
|
1134 |
#textItem.owner = None
|
1135 |
#worker.scene.removeItem(textItem)
|
1136 |
|
1137 |
worker.updateBatchProgress.emit(len(drawings), 2) |
1138 |
|
1139 |
# check symbol validate
|
1140 |
#valid_sym = []
|
1141 |
for index in reversed(range(len(searchedTextSymList))): |
1142 |
sym_xmin = searchedTextSymList[index].getSp()[0]
|
1143 |
sym_ymin = searchedTextSymList[index].getSp()[1]
|
1144 |
sym_xmax = searchedTextSymList[index].getSp()[0] + searchedTextSymList[index].getWidth()
|
1145 |
sym_ymax = searchedTextSymList[index].getSp()[1] + searchedTextSymList[index].getHeight()
|
1146 |
valid_count = searchedTextSymList[index].getHasInstrumentLabel() |
1147 |
valid = 0
|
1148 |
for text_info in textInfoList: |
1149 |
info_center = text_info.center |
1150 |
if sym_xmin < info_center[0] < sym_xmax and sym_ymin < info_center[1] < sym_ymax: |
1151 |
valid += 1
|
1152 |
break
|
1153 |
#if valid >= valid_count:
|
1154 |
# valid_sym.append(searchedTextSymList[index])
|
1155 |
# break
|
1156 |
if valid < valid_count:
|
1157 |
searchedSymbolList.pop(searchedSymbolList.index(searchedTextSymList[index])) |
1158 |
|
1159 |
# roll back because invalidated symbol was deleted for text detection
|
1160 |
app_doc_data.imgSrc = app_doc_data.activeDrawing.image_origin |
1161 |
|
1162 |
for sym in searchedSymbolList: |
1163 |
Worker.remove_detected_symbol_image(sym, app_doc_data.imgSrc) |
1164 |
#pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER)
|
1165 |
#for sym in valid_sym:
|
1166 |
# pool.submit(Worker.remove_detected_symbol_image, sym, app_doc_data.imgSrc)
|
1167 |
#pool.shutdown(wait=True)
|
1168 |
# up to here
|
1169 |
|
1170 |
# remove text from image
|
1171 |
textDetector.remove_text_from_image(app_doc_data.imgSrc, [0, 0]) |
1172 |
#textDetector.remove_text_from_image(area.img, offset)
|
1173 |
if not worker.isTextChecked: |
1174 |
textInfoList.clear() |
1175 |
# up to here
|
1176 |
|
1177 |
#removedSymbolImgPath = os.path.join(project.getTempPath(), os.path.basename(mainRes))
|
1178 |
#cv2.imwrite(removedSymbolImgPath, app_doc_data.imgSrc) for debug
|
1179 |
|
1180 |
listWidget.addItem("Recognized symbol count : " + str(len(searchedSymbolList))) |
1181 |
|
1182 |
listWidget.addItem(worker.tr('Creating detected infos...'))
|
1183 |
worker.displayTitle.emit(worker.tr('Creating detected infos...'))
|
1184 |
worker.create_detected_items(searchedSymbolList, textInfoList, |
1185 |
otherTextInfoList if otherTextInfoList is not None else [], |
1186 |
titleBlockTextInfoList if titleBlockTextInfoList is not None else []) |
1187 |
|
1188 |
if isLineChecked:
|
1189 |
Worker.recognizeLine(mainRes, listWidget, worker.scene, worker) |
1190 |
else:
|
1191 |
"""load lines"""
|
1192 |
worker.add_predata_to_scene.emit(app_doc_data.activeDrawing, worker.line_scene, False, False, True, False, False) |
1193 |
worker.cond.wait(worker.mutex) |
1194 |
"""up to here"""
|
1195 |
lineItems = [item for item in worker.line_scene.items() |
1196 |
if issubclass(type(item), QEngineeringLineItem)] |
1197 |
app_doc_data.lines.extend(lineItems) |
1198 |
app_doc_data.allItems.extend(lineItems) |
1199 |
|
1200 |
for lineItem in lineItems: |
1201 |
lineItem.owner = None
|
1202 |
for conn in lineItem.connectors: |
1203 |
conn.connectedItem = None
|
1204 |
#worker.scene.removeItem(lineItem)
|
1205 |
|
1206 |
'''
|
1207 |
# try to detect nozzle from image drawing
|
1208 |
if worker.isSymbolChecked:
|
1209 |
worker.displayTitle.emit(worker.tr('Detecting nozzle and flange...'))
|
1210 |
|
1211 |
nozzles = []
|
1212 |
with futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as pool:
|
1213 |
future_nozzle = {pool.submit(Worker.detect_nozzles, mainRes, symbol, listWidget, worker):
|
1214 |
symbol for symbol in targetSymbolList[1]}
|
1215 |
for future in futures.as_completed(future_nozzle):
|
1216 |
data = future.result()
|
1217 |
if data:
|
1218 |
nozzles.extend(data)
|
1219 |
|
1220 |
worker.create_detected_items(nozzles, [], [], [])
|
1221 |
|
1222 |
for nozzle in nozzles:
|
1223 |
Worker.remove_detected_symbol_image(nozzle, app_doc_data.imgSrc)
|
1224 |
# up to here
|
1225 |
'''
|
1226 |
|
1227 |
area = app_doc_data.getArea('Drawing')
|
1228 |
if area is not None: |
1229 |
area.img = app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height), |
1230 |
round(area.x + 1):round(area.x + area.width)] |
1231 |
#cv2.imwrite(os.path.join(project.getTempPath(), "RECT_" + os.path.basename(mainRes)),
|
1232 |
# app_doc_data.imgSrc) for debug
|
1233 |
|
1234 |
Worker.drawFoundSymbolsOnCanvas(mainRes, searchedSymbolList, textInfoList, listWidget) |
1235 |
|
1236 |
# get difference between original and recognized image
|
1237 |
foundFilePath = os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(mainRes))
|
1238 |
Worker.getDifference(mainRes, foundFilePath) |
1239 |
# up to here
|
1240 |
|
1241 |
# connect symbol to symbol
|
1242 |
try:
|
1243 |
configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line') |
1244 |
toler = int(configs[0].value) if configs else 20 |
1245 |
for symbol in app_doc_data.symbols: |
1246 |
matches = [it for it in app_doc_data.symbols if |
1247 |
it is not symbol and symbol.is_connectable(it, toler=toler)] |
1248 |
for match in matches: |
1249 |
symbol.connect_if_possible(match, toler=int(toler / 5 * 3)) |
1250 |
except Exception as ex: |
1251 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1252 |
sys.exc_info()[-1].tb_lineno)
|
1253 |
worker.displayLog.emit(MessageType.Error, message) |
1254 |
# up to here
|
1255 |
|
1256 |
listWidget.addItem('Connecting lines')
|
1257 |
#area = app_doc_data.getArea('Drawing')
|
1258 |
detector = LineDetector(app_doc_data.imgSrc) |
1259 |
symbols = app_doc_data.symbols |
1260 |
configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line') |
1261 |
toler = int(configs[0].value) if configs else 20 |
1262 |
|
1263 |
# detect flange
|
1264 |
flange_list = [] |
1265 |
blind_list = [] |
1266 |
|
1267 |
print('flag3.9')
|
1268 |
configs = app_doc_data.getConfigs('Project', 'Operation') |
1269 |
instrument = int(configs[0].value) if configs else 1 |
1270 |
if app_doc_data.lines:
|
1271 |
# connect line to symbol
|
1272 |
try:
|
1273 |
for line in app_doc_data.lines: |
1274 |
matches = [_symbol for _symbol in symbols if _symbol.is_connectable(line, toler=toler)] |
1275 |
for _symbol in matches: |
1276 |
detector.connectLineToSymbol(line, _symbol, toler=toler) |
1277 |
except Exception as ex: |
1278 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[
|
1279 |
-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno) |
1280 |
worker.displayLog.emit(MessageType.Error, message) |
1281 |
# up to here
|
1282 |
print('flag3.98')
|
1283 |
# connect line to line
|
1284 |
try:
|
1285 |
for line in app_doc_data.lines: |
1286 |
matches = [it for it in app_doc_data.lines if |
1287 |
(it is not line) and (not line.isParallel(it))] |
1288 |
|
1289 |
for match in matches: |
1290 |
detector.connectLineToLine(match, line, toler) |
1291 |
print('flag3.989')
|
1292 |
# change line type using symbol connection type(info)
|
1293 |
for sym in symbols: |
1294 |
if sym.conn_type:
|
1295 |
for index in range(len(sym.conn_type)): |
1296 |
item = sym.connectors[index].connectedItem |
1297 |
if item and type(item) is QEngineeringLineItem and sym.conn_type[index] != 'Secondary' and sym.conn_type[index] != 'Primary': |
1298 |
Worker.changeConnectedLineType(item, sym.conn_type[index]) |
1299 |
|
1300 |
except Exception as ex: |
1301 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
1302 |
f"{sys.exc_info()[-1].tb_lineno}"
|
1303 |
worker.displayLog.emit(MessageType.Error, message) |
1304 |
# up to here
|
1305 |
print('flag3.99')
|
1306 |
|
1307 |
# make short line that can not detected, symbol to symbol
|
1308 |
try:
|
1309 |
conns = [] |
1310 |
for sym in symbols: |
1311 |
if sym.conn_type:
|
1312 |
for index in range(len(sym.conn_type)): |
1313 |
if sym.conn_type[index] == 'Secondary' or sym.conn_type[index] == 'Primary': |
1314 |
item = sym.connectors[index].connectedItem |
1315 |
if item is None: |
1316 |
conns.append(sym.connectors[index]) |
1317 |
|
1318 |
Worker.make_short_lines_sts(conns, worker) |
1319 |
|
1320 |
except Exception as ex: |
1321 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
1322 |
f"{sys.exc_info()[-1].tb_lineno}"
|
1323 |
worker.displayLog.emit(MessageType.Error, message) |
1324 |
|
1325 |
# detect flange
|
1326 |
try:
|
1327 |
not_conn = [] |
1328 |
if instrument == 1: |
1329 |
flange = None
|
1330 |
configs = app_doc_data.getConfigs('Default', 'Flange') |
1331 |
flange_name = configs[0].value if 1 == len(configs) else 'flange' |
1332 |
flange = QtImageViewer.createSymbolObject(flange_name) |
1333 |
|
1334 |
blind = None
|
1335 |
configs = app_doc_data.getConfigs('Default', 'Blind') |
1336 |
flange_name = configs[0].value if 1 == len(configs) else 'blind flange' |
1337 |
blind = QtImageViewer.createSymbolObject(flange_name) |
1338 |
|
1339 |
for sym in symbols: |
1340 |
if sym.conn_type:
|
1341 |
for index in range(len(sym.conn_type)): |
1342 |
item = sym.connectors[index].connectedItem |
1343 |
if flange and item and type(item) is QEngineeringLineItem and (sym.conn_type[index] == 'Secondary' or sym.conn_type[index] == 'Primary') \ |
1344 |
and item.is_piping(True): |
1345 |
point = worker.detectFlangeOnPid(sym, sym.connectors[index], item, app_doc_data.activeDrawing.image_origin) |
1346 |
if point is not None: |
1347 |
flange_list.append(point) |
1348 |
|
1349 |
elif blind and not item and (sym.conn_type[index] == 'Secondary' or sym.conn_type[index] == 'Primary'): |
1350 |
point = worker.detectFlangeOnPid(sym, sym.connectors[index], None, app_doc_data.activeDrawing.image_origin)
|
1351 |
if point is not None: |
1352 |
blind_list.append(point) |
1353 |
not_conn.append(sym.connectors[index]) |
1354 |
|
1355 |
except Exception as ex: |
1356 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
1357 |
f"{sys.exc_info()[-1].tb_lineno}"
|
1358 |
worker.displayLog.emit(MessageType.Error, message) |
1359 |
# up to here
|
1360 |
|
1361 |
# make short line that can not detected, symbol to line
|
1362 |
try:
|
1363 |
conns = [] |
1364 |
for sym in symbols: |
1365 |
if sym.conn_type:
|
1366 |
for index in range(len(sym.conn_type)): |
1367 |
if sym.conn_type[index] == 'Secondary' or sym.conn_type[index] == 'Primary': |
1368 |
item = sym.connectors[index].connectedItem |
1369 |
if item is None and sym.connectors[index] not in not_conn: |
1370 |
conns.append(sym.connectors[index]) |
1371 |
|
1372 |
Worker.make_short_lines_stl(app_doc_data.lines, conns, worker) |
1373 |
|
1374 |
except Exception as ex: |
1375 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
1376 |
f"{sys.exc_info()[-1].tb_lineno}"
|
1377 |
worker.displayLog.emit(MessageType.Error, message) |
1378 |
|
1379 |
# remove line has not connected item
|
1380 |
try:
|
1381 |
count = 1
|
1382 |
while count > 0: |
1383 |
count = 0
|
1384 |
for line in reversed(app_doc_data.lines): |
1385 |
if not line.has_connection: |
1386 |
app_doc_data.lines.remove(line) |
1387 |
app_doc_data.allItems.remove(line) |
1388 |
count += 1
|
1389 |
for item in app_doc_data.lines + symbols: |
1390 |
for conn in item.connectors: |
1391 |
if conn.connectedItem is line: |
1392 |
conn.connectedItem = None
|
1393 |
except Exception as ex: |
1394 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
1395 |
f"{sys.exc_info()[-1].tb_lineno}"
|
1396 |
worker.displayLog.emit(MessageType.Error, message) |
1397 |
# up to here
|
1398 |
|
1399 |
# change line type using visual pattern
|
1400 |
try:
|
1401 |
lines = [] |
1402 |
for sym in symbols: |
1403 |
if sym.conn_type:
|
1404 |
for index in range(len(sym.conn_type)): |
1405 |
if sym.conn_type[index] != 'Secondary' and sym.conn_type[index] != 'Primary': |
1406 |
item = sym.connectors[index].connectedItem |
1407 |
if item and type(item) is QEngineeringLineItem: |
1408 |
if item in lines: |
1409 |
continue
|
1410 |
else:
|
1411 |
run = [item] |
1412 |
Worker.find_connected_line(run, item) |
1413 |
lines.extend(run) |
1414 |
|
1415 |
Worker.changeVisualLineType(lines, worker) |
1416 |
|
1417 |
except Exception as ex: |
1418 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
1419 |
f"{sys.exc_info()[-1].tb_lineno}"
|
1420 |
worker.displayLog.emit(MessageType.Error, message) |
1421 |
# up to here
|
1422 |
print('flag4')
|
1423 |
worker.create_unknown_items(mainRes) |
1424 |
worker.add_detected_items_to_scene.emit(worker.scene, [flange_list, blind_list]) |
1425 |
worker.cond.wait(worker.mutex) |
1426 |
|
1427 |
# run preset
|
1428 |
presets = app_doc_data.getSymbolPreset() |
1429 |
for preset in presets: |
1430 |
worker.preset_execute.emit(worker.scene, preset[0], preset[1], preset[2], preset[3]) |
1431 |
worker.cond.wait(worker.mutex) |
1432 |
|
1433 |
# remove piping line and text for instrument mode
|
1434 |
try:
|
1435 |
if instrument == -1: |
1436 |
# remove piping line
|
1437 |
remove_items = [] |
1438 |
|
1439 |
for line in reversed(app_doc_data.lines): |
1440 |
if line.is_piping(True): |
1441 |
app_doc_data.lines.remove(line) |
1442 |
app_doc_data.allItems.remove(line) |
1443 |
remove_items.append(line) |
1444 |
'''
|
1445 |
for item in app_doc_data.lines + app_doc_data.symbols:
|
1446 |
for conn in item.connectors:
|
1447 |
if conn.connectedItem is line:
|
1448 |
conn.connectedItem = None
|
1449 |
'''
|
1450 |
|
1451 |
# remove text
|
1452 |
for text in reversed([text for text in app_doc_data.texts if type(text) is QEngineeringTextItem and text.area == 'Drawing']): |
1453 |
contained = False
|
1454 |
for symbol in app_doc_data.symbols: |
1455 |
if symbol.includes([text.sceneBoundingRect().center().x(), text.sceneBoundingRect().center().y()]):
|
1456 |
contained = True
|
1457 |
break
|
1458 |
if not contained: |
1459 |
remove_items.append(text) |
1460 |
|
1461 |
## load code tables
|
1462 |
app_doc_data.getReplaceTables() |
1463 |
app_doc_data.getCustomTables() |
1464 |
app_doc_data.loadSymbolAttributeCodeTables() |
1465 |
for index in reversed(range(len(remove_items))): |
1466 |
if issubclass(type(remove_items[index]), QEngineeringTextItem): |
1467 |
found = False
|
1468 |
for table in CodeTable.TABLES.values(): |
1469 |
if table.find_match_exactly(remove_items[index].text()):
|
1470 |
found = True
|
1471 |
break
|
1472 |
if found:
|
1473 |
remove_items.pop(index) |
1474 |
|
1475 |
worker.item_remove.emit(remove_items) |
1476 |
worker.cond.wait(worker.mutex) |
1477 |
# up to here
|
1478 |
|
1479 |
except Exception as ex: |
1480 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
1481 |
f"{sys.exc_info()[-1].tb_lineno}"
|
1482 |
worker.displayLog.emit(MessageType.Error, message) |
1483 |
# up to here
|
1484 |
|
1485 |
print('flag5')
|
1486 |
worker.save_scene.emit(worker.scene) |
1487 |
worker.cond.wait(worker.mutex) |
1488 |
|
1489 |
worker.scene._end = False
|
1490 |
# clear drawing
|
1491 |
app_doc_data.activeDrawing.image = None
|
1492 |
|
1493 |
worker.updateBatchProgress.emit(len(drawings), 1) |
1494 |
except Exception as ex: |
1495 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
1496 |
f"{sys.exc_info()[-1].tb_lineno}"
|
1497 |
worker.displayLog.emit(MessageType.Error, message) |
1498 |
finally:
|
1499 |
pass
|
1500 |
|
1501 |
@staticmethod
|
1502 |
def make_short_lines_sts(connectors, worker=None): |
1503 |
""" make short lines logically, symbol to symbol """
|
1504 |
|
1505 |
import math |
1506 |
from EngineeringAbstractItem import QEngineeringAbstractItem |
1507 |
|
1508 |
try:
|
1509 |
app_doc_data = AppDocData.instance() |
1510 |
lineLengthConfigs = app_doc_data.getConfigs('Small Line Minimum Length', 'Min Length') |
1511 |
lineMinLength = int(lineLengthConfigs[0].value) if 1 == len(lineLengthConfigs) else 25 |
1512 |
lineMaxLength = lineMinLength * 3
|
1513 |
windowSize = app_doc_data.getSlidingWindowSize() |
1514 |
thickness = int(windowSize[1] / 2) |
1515 |
|
1516 |
new_lines = [] |
1517 |
|
1518 |
# symbol to symbol
|
1519 |
connectors = [conn for conn in connectors if conn.connectedItem is None] |
1520 |
for connector1 in connectors: |
1521 |
if connector1.connectedItem:
|
1522 |
continue
|
1523 |
|
1524 |
selected = None
|
1525 |
min_dist = sys.maxsize |
1526 |
|
1527 |
for connector2 in connectors: |
1528 |
if connector2.connectedItem or connector1 is connector2 or connector1.parentItem() is connector2.parentItem(): |
1529 |
continue
|
1530 |
|
1531 |
dx = connector1.center()[0] - connector2.center()[0] |
1532 |
dy = connector1.center()[1] - connector2.center()[1] |
1533 |
dist = math.sqrt(dx * dx + dy * dy) |
1534 |
if dist < lineMaxLength and dist < min_dist and min(abs(dx), abs(dy)) < thickness: |
1535 |
selected = connector2 |
1536 |
min_dist = dist |
1537 |
|
1538 |
if selected:
|
1539 |
new_line = QEngineeringLineItem(vertices=[connector1.center(), selected.center()], thickness=thickness) |
1540 |
new_line.area = 'Drawing'
|
1541 |
|
1542 |
connector1.connect(new_line) |
1543 |
selected.connect(new_line) |
1544 |
new_line.connectors[0].connect(connector1.parentItem())
|
1545 |
new_line.connectors[1].connect(selected.parentItem())
|
1546 |
new_lines.append(new_line) |
1547 |
|
1548 |
app_doc_data.lines.extend(new_lines) |
1549 |
app_doc_data.allItems.extend(new_lines) |
1550 |
|
1551 |
return new_lines
|
1552 |
|
1553 |
except Exception as ex: |
1554 |
if worker:
|
1555 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1556 |
sys.exc_info()[-1].tb_lineno)
|
1557 |
worker.displayLog.emit(MessageType.Error, message) |
1558 |
else:
|
1559 |
from App import App |
1560 |
from AppDocData import MessageType |
1561 |
|
1562 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
1563 |
f"{sys.exc_info()[-1].tb_lineno}"
|
1564 |
QMessageBox.warning(self, self._tr('Warning'), message) |
1565 |
|
1566 |
@staticmethod
|
1567 |
def make_short_lines_stl(lines, connectors, worker=None): |
1568 |
""" make short lines logically, symbol to line """
|
1569 |
|
1570 |
import math |
1571 |
from EngineeringAbstractItem import QEngineeringAbstractItem |
1572 |
|
1573 |
try:
|
1574 |
app_doc_data = AppDocData.instance() |
1575 |
lineLengthConfigs = app_doc_data.getConfigs('Small Line Minimum Length', 'Min Length') |
1576 |
lineMinLength = int(lineLengthConfigs[0].value) if 1 == len(lineLengthConfigs) else 25 |
1577 |
lineMaxLength = lineMinLength * 3
|
1578 |
windowSize = app_doc_data.getSlidingWindowSize() |
1579 |
thickness = int(windowSize[1] / 2) |
1580 |
|
1581 |
new_lines = [] |
1582 |
|
1583 |
verticals = [] |
1584 |
horizontals = [] |
1585 |
|
1586 |
for line in lines: |
1587 |
if line.isVertical():
|
1588 |
verticals.append(line) |
1589 |
else:
|
1590 |
horizontals.append(line) |
1591 |
|
1592 |
# symbol to line
|
1593 |
for connector in connectors: |
1594 |
direction = connector.dir() |
1595 |
symbol = connector.parentItem() |
1596 |
if direction is None: |
1597 |
dx = connector.center()[0] - symbol.origin[0] |
1598 |
dy = connector.center()[1] - symbol.origin[1] |
1599 |
length = math.sqrt(dx * dx + dy * dy) |
1600 |
if length <= 0: |
1601 |
continue
|
1602 |
dx /= length |
1603 |
dy /= length |
1604 |
else:
|
1605 |
dx, dy = direction.x(), direction.y() |
1606 |
|
1607 |
_lines = None
|
1608 |
if abs(dx) < 0.1: |
1609 |
_lines = horizontals |
1610 |
elif abs(dy) < 0.1: |
1611 |
_lines = verticals |
1612 |
|
1613 |
if not _lines: |
1614 |
continue
|
1615 |
|
1616 |
selected = None
|
1617 |
min_dist = sys.maxsize |
1618 |
for line in _lines: |
1619 |
dist = line.distanceTo(connector.center()) |
1620 |
if dist < lineMaxLength and dist < min_dist: |
1621 |
selected = line |
1622 |
min_dist = dist |
1623 |
|
1624 |
if selected:
|
1625 |
if selected in verticals: |
1626 |
min_x_y = min(selected.start_point()[1], selected.end_point()[1]) |
1627 |
max_x_y = max(selected.start_point()[1], selected.end_point()[1]) |
1628 |
con_x_y = connector.center()[1]
|
1629 |
start_point = [selected.start_point()[0], connector.center()[1]] |
1630 |
else:
|
1631 |
min_x_y = min(selected.start_point()[0], selected.end_point()[0]) |
1632 |
max_x_y = max(selected.start_point()[0], selected.end_point()[0]) |
1633 |
con_x_y = connector.center()[0]
|
1634 |
start_point = [connector.center()[0], selected.start_point()[1]] |
1635 |
|
1636 |
end_point = connector.center() |
1637 |
|
1638 |
if min_x_y < con_x_y and con_x_y < max_x_y: |
1639 |
new_line = QEngineeringLineItem(vertices=[start_point, end_point], thickness=thickness) |
1640 |
new_line.area = 'Drawing'
|
1641 |
|
1642 |
connector.connect(new_line) |
1643 |
new_line.connectors[0].connect(selected, at=QEngineeringAbstractItem.CONNECTED_AT_BODY)
|
1644 |
new_line.connectors[1].connect(symbol)
|
1645 |
new_lines.append(new_line) |
1646 |
|
1647 |
app_doc_data.lines.extend(new_lines) |
1648 |
app_doc_data.allItems.extend(new_lines) |
1649 |
|
1650 |
return new_lines
|
1651 |
|
1652 |
except Exception as ex: |
1653 |
if worker:
|
1654 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1655 |
sys.exc_info()[-1].tb_lineno)
|
1656 |
worker.displayLog.emit(MessageType.Error, message) |
1657 |
else:
|
1658 |
from App import App |
1659 |
from AppDocData import MessageType |
1660 |
|
1661 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
1662 |
f"{sys.exc_info()[-1].tb_lineno}"
|
1663 |
QMessageBox.warning(self, self._tr('Warning'), message) |
1664 |
|
1665 |
@staticmethod
|
1666 |
def changeVisualLineType(all_lines, worker): |
1667 |
""" change line type by using visual data"""
|
1668 |
|
1669 |
from LineDetector import LineDetector |
1670 |
from EngineeringAbstractItem import QEngineeringAbstractItem |
1671 |
|
1672 |
try:
|
1673 |
app_doc_data = AppDocData.instance() |
1674 |
image = app_doc_data.activeDrawing.image_origin |
1675 |
imgNot = np.ones(image.shape, np.uint8) * 255
|
1676 |
image = cv2.bitwise_xor(image, imgNot) |
1677 |
|
1678 |
#electric = [137, [1,1,1,1,1,1,1,1,1], sys.maxsize]
|
1679 |
#software = [187, [0.948,1.081,0.932,1.081,0.932,1.068,0.932,1.081,0.929], sys.maxsize]
|
1680 |
#line_patterns = {'Electric':electric, 'Software':software }
|
1681 |
line_patterns = [] |
1682 |
line_shapes = [] |
1683 |
|
1684 |
# can not determine piping line cuz always matched
|
1685 |
lines = [line for line in all_lines if line.lineType != 'Secondary' and line.lineType != 'Primary'] |
1686 |
|
1687 |
line_names = app_doc_data.getSymbolListByType('type', 'Line') |
1688 |
if len(line_names) != 0: |
1689 |
max_avg_area = 0
|
1690 |
for line_name in line_names: |
1691 |
line = line_name |
1692 |
line_path = line.getPath() |
1693 |
if not os.path.isfile(line_path): |
1694 |
continue
|
1695 |
|
1696 |
line_img = cv2.cvtColor(cv2.imread(line_path), cv2.COLOR_BGR2GRAY) |
1697 |
imgNot = np.ones(line_img.shape, np.uint8) * 255
|
1698 |
line_img = cv2.bitwise_xor(line_img, imgNot) |
1699 |
|
1700 |
contours, _ = cv2.findContours(line_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
1701 |
|
1702 |
line_thickness = 0
|
1703 |
|
1704 |
if len(contours) < 3: |
1705 |
max_x, max_y, min_x, min_y = 0, 0, sys.maxsize, sys.maxsize |
1706 |
for rect in [cv2.boundingRect(contour) for contour in contours]: |
1707 |
if rect[0] < min_x: |
1708 |
min_x = rect[0]
|
1709 |
if rect[0] + rect[2] > max_x: |
1710 |
max_x = rect[0] + rect[2] |
1711 |
if rect[1] < min_y: |
1712 |
min_y = rect[1]
|
1713 |
if rect[1] + rect[3] > max_y: |
1714 |
max_y = rect[1] + rect[3] |
1715 |
|
1716 |
thickness = min(max_y - min_y, max_x - min_x)
|
1717 |
if line_thickness < thickness:
|
1718 |
line_thickness = thickness |
1719 |
|
1720 |
line_shapes.append([line_img[min_y:max_y, min_x:max_x], sys.maxsize, line.getThreshold(), line.getBaseSymbol()]) |
1721 |
continue
|
1722 |
|
1723 |
i = 1 if line_img.shape[0] > line_img.shape[1] else 0 |
1724 |
boundingBoxes = [cv2.boundingRect(contour) for contour in contours] |
1725 |
(contours, boundingBoxes) = zip(*sorted(zip(contours, boundingBoxes), key=lambda b:b[1][i], reverse=False)) |
1726 |
|
1727 |
avg_area = sum([cv2.contourArea(contour) for contour in contours]) / len(contours) |
1728 |
ratio_area = [cv2.contourArea(contour) / avg_area for contour in contours] |
1729 |
if avg_area > max_avg_area:
|
1730 |
max_avg_area = avg_area |
1731 |
|
1732 |
line_patterns.append([avg_area, ratio_area, sys.maxsize, line.getThreshold(), line.getBaseSymbol()]) |
1733 |
else:
|
1734 |
return
|
1735 |
|
1736 |
lines_found = [] |
1737 |
lines_shape = [] |
1738 |
|
1739 |
# detemine line type : broken
|
1740 |
for line in lines: |
1741 |
rect = line.boundingRect() |
1742 |
image_line = image[round(rect.y() - 1):round(rect.y() + rect.height() + 1), round(rect.x() - 1):round(rect.x() + rect.width() + 1)] |
1743 |
contours, _ = cv2.findContours(image_line, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
1744 |
|
1745 |
# skip piping line
|
1746 |
if len(contours) < 3 or (sum([cv2.contourArea(contour) for contour in contours]) / len(contours)) > max_avg_area * 2: |
1747 |
if rect.width() > rect.height():
|
1748 |
image_line = image[round(rect.y() - line_thickness / 2):round(rect.y() + rect.height() + line_thickness / 2), round(rect.x() - 1):round(rect.x() + rect.width() + 1)] |
1749 |
else:
|
1750 |
image_line = image[round(rect.y() - 1):round(rect.y() + rect.height() + 1), round(rect.x() - line_thickness / 2):round(rect.x() + rect.width() + line_thickness / 2)] |
1751 |
|
1752 |
contours, _ = cv2.findContours(image_line, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
1753 |
max_x, max_y, min_x, min_y = 0, 0, sys.maxsize, sys.maxsize |
1754 |
for rect in [cv2.boundingRect(contour) for contour in contours]: |
1755 |
if rect[0] < min_x: |
1756 |
min_x = rect[0]
|
1757 |
if rect[0] + rect[2] > max_x: |
1758 |
max_x = rect[0] + rect[2] |
1759 |
if rect[1] < min_y: |
1760 |
min_y = rect[1]
|
1761 |
if rect[1] + rect[3] > max_y: |
1762 |
max_y = rect[1] + rect[3] |
1763 |
if max_y == 0 or max_x == 0: |
1764 |
continue
|
1765 |
|
1766 |
lines_shape.append([line, image_line[min_y:max_y, min_x:max_x]]) |
1767 |
continue
|
1768 |
|
1769 |
vertical = LineDetector.is_vertical([line.start_point()[0], line.start_point()[1], line.end_point()[0], line.end_point()[1]]) |
1770 |
|
1771 |
i = 1 if vertical else 0 |
1772 |
boundingBoxes = [cv2.boundingRect(contour) for contour in contours] |
1773 |
(contours, boundingBoxes) = zip(*sorted(zip(contours, boundingBoxes), key=lambda b:b[1][i], reverse=False)) |
1774 |
|
1775 |
avg_area = sum([cv2.contourArea(contour) for contour in contours]) / len(contours) |
1776 |
ratio_area = [cv2.contourArea(contour) / avg_area for contour in contours] |
1777 |
|
1778 |
for line_pattern in line_patterns: |
1779 |
line_type = line_pattern[-1]
|
1780 |
|
1781 |
line_pattern[2] = sys.maxsize
|
1782 |
ratio_area_cal = [ratio * avg_area / line_pattern[0] for ratio in ratio_area] |
1783 |
long_line = ratio_area_cal if len(ratio_area_cal) > len(line_pattern[1]) else line_pattern[1] |
1784 |
short_line = line_pattern[1] if len(ratio_area_cal) > len(line_pattern[1]) else ratio_area_cal |
1785 |
|
1786 |
min_error = sys.maxsize |
1787 |
for offset in range(len(long_line) - len(short_line) + 1): |
1788 |
error = 0
|
1789 |
for index in range(len(short_line)): |
1790 |
error += abs(short_line[index] - long_line[index + offset])
|
1791 |
error = error / len(short_line)
|
1792 |
if error < min_error:
|
1793 |
min_error = error |
1794 |
|
1795 |
line_pattern[2] = min_error
|
1796 |
|
1797 |
line_type_founds = sorted([(line_pattern[-1], line_pattern[2]) for line_pattern in line_patterns], key=lambda error:error[1]) |
1798 |
if line_type_founds:
|
1799 |
lines_found.append([line, line_type_founds[0]])
|
1800 |
|
1801 |
'''
|
1802 |
# feature matching not work
|
1803 |
orb = cv2.ORB_create()
|
1804 |
kp1, des1 = orb.detectAndCompute(image_line, None)
|
1805 |
kp2, des2 = orb.detectAndCompute(image_line, None)
|
1806 |
|
1807 |
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
|
1808 |
matches = bf.match(des1, des2)
|
1809 |
matches = sorted(matches, key=lambda x: x.distance)
|
1810 |
|
1811 |
good = []
|
1812 |
for m, n in matches:
|
1813 |
if m.distance < 0.75 * n.distance:
|
1814 |
good.append([m])
|
1815 |
|
1816 |
sift = cv2.xfeatures2d.SIFT_create()
|
1817 |
kp1, des1 = sift.detectAndCompute(image_line, None)
|
1818 |
kp2, des2 = sift.detectAndCompute(image_line, None)
|
1819 |
|
1820 |
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)
|
1821 |
matches = bf.match(des1, des2)
|
1822 |
matches = sorted(matches, key=lambda x: x.distance)
|
1823 |
|
1824 |
good = []
|
1825 |
for m, n in matches:
|
1826 |
if m.distance < 0.75 * n.distance:
|
1827 |
good.append([m])
|
1828 |
'''
|
1829 |
# determine line type : solid
|
1830 |
for line, image_line in lines_shape: |
1831 |
for line_shape in line_shapes: |
1832 |
line_type = line_shape[-1]
|
1833 |
|
1834 |
shape = line_shape[0].copy()
|
1835 |
line_shape[1] = sys.maxsize
|
1836 |
|
1837 |
big = shape if max(shape.shape) > max(image_line.shape) else image_line |
1838 |
small = image_line if max(shape.shape) > max(image_line.shape) else shape |
1839 |
|
1840 |
if big.shape[0] == max(max(big.shape), max(small.shape)): |
1841 |
mask = np.zeros([big.shape[0] + 40, max(big.shape[1], max(small.shape)) + 40], np.uint8) |
1842 |
else:
|
1843 |
mask = np.zeros([max(big.shape[0], max(small.shape)) + 40, big.shape[1] + 40], np.uint8) |
1844 |
|
1845 |
mask[20:big.shape[0] + 20, 20:big.shape[1] + 20] = big |
1846 |
big = mask |
1847 |
|
1848 |
searchedInfos = [] |
1849 |
steps = [False, True] |
1850 |
for flipped in steps: |
1851 |
symGray = small.copy() |
1852 |
if flipped:
|
1853 |
symGray = cv2.flip(symGray, 1)
|
1854 |
|
1855 |
symbolRotatedAngle = 0
|
1856 |
for rc in range(4): |
1857 |
sw, sh = symGray.shape[::-1]
|
1858 |
|
1859 |
r_w, r_h = big.shape[::-1]
|
1860 |
if r_w < sw or r_h < sh: |
1861 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE) |
1862 |
symbolRotatedAngle = (symbolRotatedAngle + 90) % 360 |
1863 |
continue
|
1864 |
|
1865 |
tmRes = cv2.matchTemplate(big, symGray, cv2.TM_CCOEFF_NORMED) |
1866 |
_, max_val, __, max_loc = cv2.minMaxLoc(tmRes) |
1867 |
|
1868 |
if max_val > line_shape[-2]: |
1869 |
searchedInfos.append(1 - max_val)
|
1870 |
|
1871 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE) |
1872 |
symbolRotatedAngle = (symbolRotatedAngle + 90) % 360 |
1873 |
|
1874 |
if searchedInfos:
|
1875 |
line_shape[1] = sorted(searchedInfos)[0] |
1876 |
|
1877 |
line_type_founds = sorted([(line_shape[-1], (1 - line_shape[-2] - line_shape[1]) * 2) for line_shape in line_shapes \ |
1878 |
if line_shape[1] < (1 - line_shape[-2])], key=lambda error: error[1], reverse=True) |
1879 |
if line_type_founds:
|
1880 |
lines_found.append([line, line_type_founds[0]])
|
1881 |
'''
|
1882 |
if line_type_founds and line_type_founds[0][1] < 0.4:
|
1883 |
if line_type_founds[0][0] == 'Connect To Process' and len(line_type_founds) > 1:
|
1884 |
if line_type_founds[1][1] < 0.4:
|
1885 |
lines_found.append([line, line_type_founds[1]])
|
1886 |
else:
|
1887 |
lines_found.append([line, line_type_founds[0]])
|
1888 |
else:
|
1889 |
lines_found.append([line, line_type_founds[0]])
|
1890 |
'''
|
1891 |
|
1892 |
line_runs = [] |
1893 |
for line_found in lines_found: |
1894 |
inserted = False
|
1895 |
for line_run in line_runs: |
1896 |
if line_found[0] in line_run: |
1897 |
inserted = True
|
1898 |
break
|
1899 |
|
1900 |
if inserted:
|
1901 |
continue
|
1902 |
else:
|
1903 |
run = [line_found[0]]
|
1904 |
Worker.find_connected_line(run, line_found[0])
|
1905 |
line_runs.append(run) |
1906 |
|
1907 |
for line_run in line_runs: |
1908 |
_lines_found = [] |
1909 |
for _line in line_run: |
1910 |
_lines = [line_found[0] for line_found in lines_found] |
1911 |
if _line in _lines: |
1912 |
index = _lines.index(_line) |
1913 |
_lines_found.append(lines_found[index]) |
1914 |
_line_found = sorted(_lines_found, key=lambda param:param[1][1])[0] |
1915 |
Worker.changeConnectedLineType(_line_found[0], _line_found[1][0], visual_marker=True) |
1916 |
|
1917 |
# change line type: connected at body
|
1918 |
remains = [line for line in app_doc_data.lines if not hasattr(line, 'visual_marker')] |
1919 |
while True: |
1920 |
remains_count = len(remains)
|
1921 |
|
1922 |
for _line in remains: |
1923 |
matches = [conn for conn in _line.connectors if conn.connectedItem and \ |
1924 |
conn._connected_at == QEngineeringAbstractItem.CONNECTED_AT_BODY and hasattr(conn.connectedItem, 'visual_marker')] |
1925 |
if matches:
|
1926 |
Worker.changeConnectedLineType(_line, matches[0].connectedItem.lineType, visual_marker=True) |
1927 |
break
|
1928 |
|
1929 |
remains = [line for line in app_doc_data.lines if not hasattr(line, 'visual_marker')] |
1930 |
if remains_count == len(remains): |
1931 |
break
|
1932 |
|
1933 |
except Exception as ex: |
1934 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1935 |
sys.exc_info()[-1].tb_lineno)
|
1936 |
worker.displayLog.emit(MessageType.Error, message) |
1937 |
|
1938 |
@staticmethod
|
1939 |
def find_connected_line(lines, line): |
1940 |
if type(line.connectors[0].connectedItem) is QEngineeringLineItem and line.connectors[0].connectedItem not in lines and\ |
1941 |
(line.connectors[0].connectedItem.connectors[0].connectedItem is line or |
1942 |
line.connectors[0].connectedItem.connectors[1].connectedItem is line): |
1943 |
current_line = line.connectors[0].connectedItem
|
1944 |
lines.append(current_line) |
1945 |
Worker.find_connected_line(lines, current_line) |
1946 |
|
1947 |
if type(line.connectors[1].connectedItem) is QEngineeringLineItem and line.connectors[1].connectedItem not in lines and\ |
1948 |
(line.connectors[1].connectedItem.connectors[0].connectedItem is line or |
1949 |
line.connectors[1].connectedItem.connectors[1].connectedItem is line): |
1950 |
current_line = line.connectors[1].connectedItem
|
1951 |
lines.append(current_line) |
1952 |
Worker.find_connected_line(lines, current_line) |
1953 |
|
1954 |
@staticmethod
|
1955 |
def changeConnectedLineType(line, lineType, visual_marker=False): |
1956 |
# for visual line type detection
|
1957 |
if visual_marker:
|
1958 |
line.visual_marker = visual_marker |
1959 |
|
1960 |
line.lineType = lineType |
1961 |
if type(line.connectors[0].connectedItem) is QEngineeringLineItem and \ |
1962 |
(line.connectors[0].connectedItem.connectors[0].connectedItem is line or |
1963 |
line.connectors[0].connectedItem.connectors[1].connectedItem is line) and \ |
1964 |
line.connectors[0].connectedItem.lineType is not lineType: |
1965 |
Worker.changeConnectedLineType(line.connectors[0].connectedItem, lineType, visual_marker=visual_marker)
|
1966 |
|
1967 |
if type(line.connectors[1].connectedItem) is QEngineeringLineItem and \ |
1968 |
(line.connectors[1].connectedItem.connectors[0].connectedItem is line or |
1969 |
line.connectors[1].connectedItem.connectors[1].connectedItem is line) and \ |
1970 |
line.connectors[1].connectedItem.lineType is not lineType: |
1971 |
Worker.changeConnectedLineType(line.connectors[1].connectedItem, lineType, visual_marker=visual_marker)
|
1972 |
|
1973 |
'''
|
1974 |
@history 2018.05.25 Jeongwoo Moved from MainWindow
|
1975 |
2018.05.28 Jeongwoo Add xmlPath Parameter and append LineInfo into xml
|
1976 |
2018.05.29 Jeongwoo Change method to add item
|
1977 |
2018.05.30 Jeongwoo Remove parameter (xmlPath)
|
1978 |
humkyung 2018.06.11 add drawing path to parameter and write recognized lines to image
|
1979 |
humkyung 2018.07.04 call arrangeLinePosition after creating line
|
1980 |
'''
|
1981 |
|
1982 |
@staticmethod
|
1983 |
def recognizeLine(path, listWidget, graphicsView, worker): |
1984 |
from shapely.geometry import Point, LineString |
1985 |
from SymbolSvgItem import SymbolSvgItem |
1986 |
from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem |
1987 |
from EngineeringLineNoTextItem import QEngineeringLineNoTextItem |
1988 |
from EngineeringTextItem import QEngineeringTextItem |
1989 |
from EngineeringLineItem import QEngineeringLineItem |
1990 |
from LineDetector import LineDetector |
1991 |
|
1992 |
try:
|
1993 |
listWidget.addItem('Starting line recognition')
|
1994 |
worker.displayTitle.emit(worker.tr('Detecting lines...'))
|
1995 |
|
1996 |
app_doc_data = AppDocData.instance() |
1997 |
project = app_doc_data.getCurrentProject() |
1998 |
|
1999 |
# detect line
|
2000 |
connectedLines = [] |
2001 |
|
2002 |
area = app_doc_data.getArea('Drawing')
|
2003 |
if area is not None: |
2004 |
area.img = app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height), |
2005 |
round(area.x + 1):round(area.x + area.width)] |
2006 |
|
2007 |
Worker.remove_equipment_package(worker.scene, area) |
2008 |
|
2009 |
area.img = worker.remove_small_objects(area.img) |
2010 |
detector = LineDetector(area.img) |
2011 |
|
2012 |
symbols = [] |
2013 |
for item in app_doc_data.symbols: |
2014 |
if issubclass(type(item), SymbolSvgItem): |
2015 |
symbols.append(item) |
2016 |
res = detector.detectConnectedLine(item, round(area.x), round(area.y)) |
2017 |
if res is not None: |
2018 |
connectedLines.extend(res) |
2019 |
|
2020 |
# line detection without symbol connection point info
|
2021 |
configs = app_doc_data.getConfigs('Line', 'Gap') |
2022 |
if configs and int(configs[0].value) is 1: |
2023 |
remainLines = detector.detect_line_without_symbol() |
2024 |
windowSize = app_doc_data.getSlidingWindowSize() |
2025 |
thickness = int(windowSize[1] / 2) |
2026 |
|
2027 |
for line in remainLines: |
2028 |
line.append(thickness) |
2029 |
connectedLines.extend(remainLines) |
2030 |
|
2031 |
configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line') |
2032 |
toler = int(configs[0].value) if configs else 20 |
2033 |
|
2034 |
symbol_areas = [] |
2035 |
for symbol in symbols: |
2036 |
symbol_areas.append(QRect(symbol.loc[0] - area.x, symbol.loc[1] - area.y, symbol.size[0], symbol.size[1])) |
2037 |
|
2038 |
detector.mergeLines(connectedLines, toler=toler, symbol_areas=symbol_areas) |
2039 |
|
2040 |
for pts in connectedLines: |
2041 |
line = QEngineeringLineItem( |
2042 |
vertices=[(area.x + param[0], area.y + param[1]) for param in pts[:-1]], thickness=pts[2]) |
2043 |
line.area = 'Drawing'
|
2044 |
|
2045 |
app_doc_data.lines.append(line) |
2046 |
app_doc_data.allItems.append(line) |
2047 |
except Exception as ex: |
2048 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
2049 |
sys.exc_info()[-1].tb_lineno)
|
2050 |
worker.displayLog.emit(MessageType.Error, message) |
2051 |
finally:
|
2052 |
listWidget.addItem('Finishing line recognition')
|
2053 |
worker.finished.emit() |
2054 |
|
2055 |
'''
|
2056 |
@history 2018.04.24 Jeongwoo Add isExceptDetect Field
|
2057 |
2018.05.09 Jeongwoo Add targetSymbolList clear
|
2058 |
humkyung 2018.07.07 store symbols to list as like [equipments],[nozzles],[symbols]
|
2059 |
'''
|
2060 |
|
2061 |
@staticmethod
|
2062 |
def initTargetSymbolDataList(all=False): |
2063 |
global targetSymbolList
|
2064 |
|
2065 |
targetSymbolList.clear() |
2066 |
app_doc_data = AppDocData.instance() |
2067 |
symbolList = app_doc_data.getTargetSymbolList(all=all)
|
2068 |
equipments = [item for item in symbolList if app_doc_data.getSymbolCategoryByType(item.getType()) == 'Equipment'] |
2069 |
nozzles = [item for item in symbolList if item.getType() == 'Nozzles'] |
2070 |
# [[equipments],[nozzles],[symbols]]
|
2071 |
targetSymbolList.append(equipments) |
2072 |
targetSymbolList.append(nozzles) |
2073 |
targetSymbolList.append([item for item in symbolList if item not in equipments and item not in nozzles]) |
2074 |
|
2075 |
return targetSymbolList
|
2076 |
|
2077 |
'''
|
2078 |
@brief detect equipment
|
2079 |
@author humkyung
|
2080 |
@date 2018.07.07
|
2081 |
'''
|
2082 |
|
2083 |
@staticmethod
|
2084 |
def detect_nozzles(mainRes, targetSymbol, listWidget, worker): |
2085 |
res = [] |
2086 |
try:
|
2087 |
app_doc_data = AppDocData.instance() |
2088 |
|
2089 |
nozzles = Worker.detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker) |
2090 |
equipments = [symbol for symbol in searchedSymbolList if app_doc_data.isEquipmentType(symbol.getType())] |
2091 |
for equipment in equipments: |
2092 |
rect = QRectF(equipment.sp[0], equipment.sp[1], equipment.width, equipment.height) |
2093 |
rect.adjust(-equipment.width*0.1, -equipment.height*0.1, equipment.width*0.1, equipment.height*0.1) |
2094 |
matches = [nozzle for nozzle in nozzles if rect.contains(nozzle.rect)] |
2095 |
res.extend(matches) |
2096 |
for match in matches: |
2097 |
nozzles.remove(match) |
2098 |
except Exception as ex: |
2099 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
2100 |
sys.exc_info()[-1].tb_lineno)
|
2101 |
worker.displayLog(MessageType.Error, message) |
2102 |
|
2103 |
return res
|
2104 |
|
2105 |
'''
|
2106 |
@brief detect symbol on PID
|
2107 |
@author jwkim
|
2108 |
@date
|
2109 |
@history humkyung 2018.04.06 check if symbol file exists
|
2110 |
Jeongwoo 2018.05.29 Change method to adjust detail symbol location with hit-rate. Not feature point count
|
2111 |
Change parameter on add symbol part (mpCount → hitRate)
|
2112 |
Remove unusing calculation (avg)
|
2113 |
Jeongwoo 2018.06.27 Remove part to split P&ID image and for loop
|
2114 |
humkyung 2018.07.07 return searched symbols
|
2115 |
'''
|
2116 |
|
2117 |
@staticmethod
|
2118 |
def detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker): |
2119 |
import copy |
2120 |
global ocrCompletedSrc
|
2121 |
global threadLock
|
2122 |
global maxProgressValue
|
2123 |
|
2124 |
try:
|
2125 |
forTraining = worker.isTrainingChecked |
2126 |
symbolName = targetSymbol.getName() |
2127 |
symbolType = targetSymbol.getType() |
2128 |
symbolPath = targetSymbol.getPath() |
2129 |
symbolThreshold = targetSymbol.getThreshold() if not forTraining else targetSymbol.getThreshold() * 0.90 |
2130 |
symbolMinMatchCount = targetSymbol.getMinMatchCount() |
2131 |
isDetectOnOrigin = targetSymbol.getIsDetectOnOrigin() |
2132 |
symbolRotateCount = targetSymbol.getRotationCount() |
2133 |
symbolOcrOption = targetSymbol.getOcrOption() |
2134 |
isContainChild = targetSymbol.getIsContainChild() |
2135 |
symbolOriginalPoint = targetSymbol.getOriginalPoint() |
2136 |
symbolConnectionPoint = targetSymbol.getConnectionPoint() |
2137 |
baseSymbol = targetSymbol.getBaseSymbol() |
2138 |
additionalSymbol = targetSymbol.getAdditionalSymbol() |
2139 |
isExceptDetect = targetSymbol.getIsExceptDetect() |
2140 |
detectFlip = targetSymbol.getDetectFlip() |
2141 |
hasInstrumentLabel = targetSymbol.getHasInstrumentLabel() |
2142 |
text_area = targetSymbol.getText_area() |
2143 |
|
2144 |
# check if symbol file is target or not
|
2145 |
if isExceptDetect == 1: |
2146 |
item = QListWidgetItem('{} file is not target'.format(symbolName))
|
2147 |
item.setBackground(QColor('green'))
|
2148 |
listWidget.addItem(item) |
2149 |
return
|
2150 |
|
2151 |
foundSymbolCount = 0
|
2152 |
|
2153 |
# check if symbol file exists
|
2154 |
if not os.path.isfile(symbolPath): |
2155 |
item = QListWidgetItem('{} file not found'.format(symbolName))
|
2156 |
item.setBackground(QColor('red'))
|
2157 |
listWidget.addItem(item) |
2158 |
return
|
2159 |
# up to here
|
2160 |
|
2161 |
sym = cv2.imread(symbolPath, 1)
|
2162 |
symGray = Worker.cvtGrayImage(sym) |
2163 |
symGrayOri = copy.copy(symGray) |
2164 |
## TODO: 이진화 시켰을때 심볼이 검출되지 않음
|
2165 |
## symGray = cv2.threshold(cvtGrayImage(sym), 127, 255, cv2.THRESH_BINARY)[1]
|
2166 |
## cv2.imshow('symbol', symGray)
|
2167 |
## cv2.waitKey(0)
|
2168 |
sow, soh = symGray.shape[::-1] # symbol original w, h |
2169 |
|
2170 |
offsetDrawingArea = [] |
2171 |
app_doc_data = AppDocData.instance() |
2172 |
area = app_doc_data.getArea('Drawing')
|
2173 |
if area is not None: |
2174 |
roiItem = Worker.remove_small_objects(area.img, 0, sow * soh * 0.5) if hasInstrumentLabel else area.img |
2175 |
offsetDrawingArea.append(area.x) |
2176 |
offsetDrawingArea.append(area.y) |
2177 |
else:
|
2178 |
offsetDrawingArea.append(0)
|
2179 |
offsetDrawingArea.append(0)
|
2180 |
if isDetectOnOrigin == 1: |
2181 |
roiItem = app_doc_data.imgSrc |
2182 |
else:
|
2183 |
roiItem = ocrCompletedSrc |
2184 |
srcWidth, srcHeight = roiItem.shape[::-1]
|
2185 |
|
2186 |
roiItemSp = (0, 0) |
2187 |
roiItemEp = (srcWidth, srcHeight) |
2188 |
|
2189 |
# try to recognize symbol twice(first one is normal, second one is flipped)
|
2190 |
steps = [False, True] if detectFlip else [False] |
2191 |
for flipped in steps: |
2192 |
if flipped:
|
2193 |
symGray = symGrayOri |
2194 |
symGray = cv2.flip(symGray, 1)
|
2195 |
|
2196 |
symbolRotatedAngle = 0
|
2197 |
#for rc in range((symbolRotateCount + 1) * 2): # Rotation count를 사용자 기준으로 받아서 1을 더한 후 사용
|
2198 |
for rc in range(symbolRotateCount + 1): # Rotation count를 사용자 기준으로 받아서 1을 더한 후 사용 |
2199 |
sw, sh = symGray.shape[::-1]
|
2200 |
roiw = (roiItemEp[0] - roiItemSp[0]) |
2201 |
roih = (roiItemEp[1] - roiItemSp[1]) |
2202 |
|
2203 |
# get Rotated Original Point
|
2204 |
originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, |
2205 |
symbolRotatedAngle, sw, sh, sow, soh, flipped) |
2206 |
connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw, |
2207 |
sh, sow, soh, flipped) |
2208 |
|
2209 |
# Case : symbol is bigger than roi
|
2210 |
if roiw < sw or roih < sh: |
2211 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE) |
2212 |
symbolRotatedAngle = (symbolRotatedAngle + 90) % 360 |
2213 |
#cX, cY = originalPoint[0], originalPoint[0]
|
2214 |
#M = cv2.getRotationMatrix2D((cX, cY), 45, 1.0)
|
2215 |
#symGray = cv2.warpAffine(symGray, M, (sw, sh))
|
2216 |
#symbolRotatedAngle = (symbolRotatedAngle + 45) % 360
|
2217 |
|
2218 |
if baseSymbol is not None and additionalSymbol is not None: |
2219 |
additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol) |
2220 |
continue
|
2221 |
|
2222 |
# For OPC
|
2223 |
drawing_area = app_doc_data.getArea('Drawing')
|
2224 |
configs = app_doc_data.getConfigs('Symbol', 'OPC') |
2225 |
if drawing_area is not None and (symbolType == "Piping OPC\'s" or symbolType == "Instrument OPC\'s") and configs and int(configs[0].value) == 1: |
2226 |
customMatch = worker.detectOPCOnPid(drawing_area, symGray) |
2227 |
customMatch = [match for match in customMatch if match[0] > symbolThreshold] |
2228 |
if customMatch and len(customMatch) > 0: |
2229 |
for custom in customMatch: |
2230 |
hitRate = custom[0]
|
2231 |
searchedItemSp = (custom[1][0] + round(offsetDrawingArea[0]), custom[1][1] + round(offsetDrawingArea[1])) |
2232 |
|
2233 |
is_add = True
|
2234 |
for searched in searchedSymbolList: |
2235 |
if Worker.IsOverlap((searchedItemSp[0], searchedItemSp[1], sw, sh), ( |
2236 |
searched.getSp()[0], searched.getSp()[1], searched.getWidth(), |
2237 |
searched.getHeight())): |
2238 |
if searched.getHitRate() > hitRate:
|
2239 |
is_add = False
|
2240 |
else:
|
2241 |
searchedSymbolList.remove(searched) |
2242 |
break
|
2243 |
|
2244 |
if is_add:
|
2245 |
threadLock.acquire() |
2246 |
foundSymbolCount = foundSymbolCount + 1
|
2247 |
Worker.addSearchedSymbol(symbolName, symbolType, |
2248 |
searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, |
2249 |
hitRate, symbolRotatedAngle, |
2250 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, |
2251 |
isContainChild, |
2252 |
originalPoint, connectionPoint, baseSymbol, additionalSymbol, |
2253 |
isExceptDetect, detectFlip=1 if flipped else 0, |
2254 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
2255 |
threadLock.release() |
2256 |
|
2257 |
if forTraining:
|
2258 |
sw, sh = sw + 20, sh + 20 |
2259 |
|
2260 |
# Template Matching
|
2261 |
tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED) |
2262 |
loc = np.where(tmRes >= symbolThreshold) |
2263 |
|
2264 |
for pt in zip(*loc[::-1]): |
2265 |
'''
|
2266 |
# no more used
|
2267 |
mpCount = 0 # Match Point Count
|
2268 |
roi = roiItem[pt[1]:pt[1] + sh, pt[0]:pt[0] + sw]
|
2269 |
|
2270 |
if symbolMinMatchCount > 0:
|
2271 |
mpCount = Worker.getMatchPointCount(roi, symGray)
|
2272 |
if not (mpCount >= symbolMinMatchCount):
|
2273 |
continue
|
2274 |
'''
|
2275 |
|
2276 |
searchedItemSp = (roiItemSp[0] + pt[0] + round(offsetDrawingArea[0]), |
2277 |
roiItemSp[1] + pt[1] + round(offsetDrawingArea[1])) |
2278 |
|
2279 |
if forTraining:
|
2280 |
searchedItemSp = [searchedItemSp[0] - 10, searchedItemSp[1] - 10] |
2281 |
|
2282 |
overlapArea = 0
|
2283 |
symbolIndex = -1
|
2284 |
for i in range(len(searchedSymbolList) - 1, -1, -1): |
2285 |
area = Worker.contains(searchedSymbolList[i], searchedItemSp, sw, sh) |
2286 |
if area > ACCEPT_OVERLAY_AREA:
|
2287 |
# if area > overlapArea:
|
2288 |
# overlapArea = area
|
2289 |
# symbolIndex = i
|
2290 |
overlapArea = area |
2291 |
symbolIndex = i |
2292 |
break
|
2293 |
"""
|
2294 |
categories = [appDocData.isEquipmentType(symbolType), appDocData.isEquipmentType(searchedSymbolList[i].getType())]
|
2295 |
if categories[0] == categories[1]:
|
2296 |
symbolIndex = i
|
2297 |
break
|
2298 |
"""
|
2299 |
|
2300 |
hitRate = tmRes[pt[1], pt[0]] |
2301 |
|
2302 |
# 겹치는 영역이 기준값보다 작을 경우
|
2303 |
if overlapArea <= ACCEPT_OVERLAY_AREA:
|
2304 |
threadLock.acquire() |
2305 |
foundSymbolCount = foundSymbolCount + 1
|
2306 |
Worker.addSearchedSymbol(symbolName, symbolType, |
2307 |
searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, |
2308 |
hitRate, symbolRotatedAngle, |
2309 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, |
2310 |
isContainChild, |
2311 |
originalPoint, connectionPoint, baseSymbol, additionalSymbol, |
2312 |
isExceptDetect, |
2313 |
detectFlip=1 if flipped else 0, |
2314 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
2315 |
threadLock.release() |
2316 |
else: # 겹치는 영역이 기준값보다 클 경우 |
2317 |
if symbolIndex != -1 and symbolIndex < len(searchedSymbolList): |
2318 |
searchedSymbol = searchedSymbolList[symbolIndex] |
2319 |
# 현재 심볼과 검출된 심볼이 같을 경우 Match Point가 더 높은 정보로 교체
|
2320 |
if symbolName == searchedSymbol.getName():
|
2321 |
symbolHitRate = searchedSymbol.getHitRate() |
2322 |
if symbolHitRate - searchedSymbol.getThreshold() < hitRate - symbolThreshold:
|
2323 |
threadLock.acquire() |
2324 |
# replace existing symbol with new symbol has high accuracy
|
2325 |
searchedSymbolList[symbolIndex] = symbol.Symbol(symbolName, symbolType, |
2326 |
searchedItemSp, sw, sh, |
2327 |
symbolThreshold, |
2328 |
symbolMinMatchCount, hitRate, |
2329 |
symbolRotatedAngle, |
2330 |
isDetectOnOrigin, |
2331 |
symbolRotateCount, |
2332 |
symbolOcrOption, isContainChild, |
2333 |
','.join(str(x) for x in |
2334 |
originalPoint), |
2335 |
'/'.join('{},{},{},{}'.format( |
2336 |
param[0], param[1], |
2337 |
param[2], param[3]) for |
2338 |
param in
|
2339 |
connectionPoint), |
2340 |
baseSymbol, additionalSymbol, |
2341 |
isExceptDetect, |
2342 |
detectFlip=1 if flipped else 0, |
2343 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
2344 |
threadLock.release() |
2345 |
# 현재 심볼과 검출된 심볼이 같지 않을 경우 (포함)
|
2346 |
elif app_doc_data.isEquipmentType(searchedSymbol.getType()) and not app_doc_data.isEquipmentType(symbolType): |
2347 |
if searchedSymbol.area > sw * sh * 10: # searched equipment area is greather than 10 times of symbol's area |
2348 |
threadLock.acquire() |
2349 |
foundSymbolCount = foundSymbolCount + 1
|
2350 |
Worker.addSearchedSymbol(symbolName, symbolType, |
2351 |
searchedItemSp, sw, sh, symbolThreshold, hitRate, |
2352 |
hitRate, symbolRotatedAngle, |
2353 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, |
2354 |
isContainChild, |
2355 |
originalPoint, connectionPoint, baseSymbol, |
2356 |
additionalSymbol, isExceptDetect, |
2357 |
detectFlip=1 if flipped else 0, |
2358 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
2359 |
threadLock.release() |
2360 |
# 현재 심볼과 검출된 심볼이 같지 않을 경우 (교체)
|
2361 |
elif not forTraining: |
2362 |
searchedSymbol = searchedSymbolList[symbolIndex] |
2363 |
symbolHitRate = searchedSymbol.getHitRate() |
2364 |
if symbolHitRate - searchedSymbol.getThreshold() < hitRate - symbolThreshold:
|
2365 |
threadLock.acquire() |
2366 |
searchedSymbolList[symbolIndex] = symbol.Symbol(symbolName, symbolType, |
2367 |
searchedItemSp, sw, sh, |
2368 |
symbolThreshold, |
2369 |
symbolMinMatchCount, hitRate, |
2370 |
symbolRotatedAngle, |
2371 |
isDetectOnOrigin, |
2372 |
symbolRotateCount, |
2373 |
symbolOcrOption, isContainChild, |
2374 |
','.join(str(x) for x in |
2375 |
originalPoint), |
2376 |
'/'.join('{},{},{},{}'.format( |
2377 |
param[0], param[1], |
2378 |
param[2], param[3]) for |
2379 |
param in
|
2380 |
connectionPoint), |
2381 |
baseSymbol, additionalSymbol, |
2382 |
isExceptDetect, |
2383 |
detectFlip=1 if flipped else 0, |
2384 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
2385 |
threadLock.release() |
2386 |
# 학습용 데이터 생성을 위해 교체하지 않고 추가함
|
2387 |
elif forTraining:
|
2388 |
threadLock.acquire() |
2389 |
foundSymbolCount = foundSymbolCount + 1
|
2390 |
Worker.addSearchedSymbol(symbolName, symbolType, |
2391 |
searchedItemSp, sw, sh, symbolThreshold, |
2392 |
symbolMinMatchCount, hitRate, symbolRotatedAngle, |
2393 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, |
2394 |
isContainChild, |
2395 |
originalPoint, connectionPoint, baseSymbol, |
2396 |
additionalSymbol, isExceptDetect, |
2397 |
detectFlip=1 if flipped else 0, |
2398 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
2399 |
threadLock.release() |
2400 |
|
2401 |
# rotate symbol
|
2402 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE) |
2403 |
symbolRotatedAngle = (symbolRotatedAngle + 90) % 360 |
2404 |
#cX, cY = originalPoint[0], originalPoint[0]
|
2405 |
#M = cv2.getRotationMatrix2D((cX, cY), 45, 1.0)
|
2406 |
#symGray = cv2.warpAffine(symGray, M, (sw, sh))
|
2407 |
#symbolRotatedAngle = (symbolRotatedAngle + 45) % 360
|
2408 |
|
2409 |
if additionalSymbol is not None: |
2410 |
additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol) |
2411 |
|
2412 |
threadLock.acquire() |
2413 |
listWidget.addItem('Found Symbol : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str( |
2414 |
foundSymbolCount) + ')')
|
2415 |
threadLock.release() |
2416 |
|
2417 |
"""
|
2418 |
if area is not None and hasInstrumentLabel:
|
2419 |
# restore objects smaller than symbol
|
2420 |
roiItem = cv2.drawContours(roiItem, outside_contours, -1, (0, 0, 0), -1)
|
2421 |
roiItem = cv2.drawContours(roiItem, hole_contours, -1, (255, 255, 255), -1)
|
2422 |
# up to here
|
2423 |
cv2.imwrite('c:\\Temp\\contour2.png', roiItem)
|
2424 |
"""
|
2425 |
|
2426 |
worker.updateProgress.emit(maxProgressValue, symbolPath) |
2427 |
|
2428 |
return [symbol for symbol in searchedSymbolList if symbol.getName() == symbolName] |
2429 |
except Exception as ex: |
2430 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
2431 |
sys.exc_info()[-1].tb_lineno)
|
2432 |
worker.displayLog.emit(MessageType.Error, message) |
2433 |
|
2434 |
return []
|
2435 |
|
2436 |
@staticmethod
|
2437 |
def IsOverlap(range1, range2): |
2438 |
if range1[0] <= range2[0] + range2[2] and range1[0] + range1[2] >= range2[0] and range1[1] <= range2[1] + \ |
2439 |
range2[3] and range1[1] + range1[3] >= range2[1]: |
2440 |
range = (min(range1[0] + range1[2], range2[0] + range2[2]) - max(range1[0], range2[0])) * ( |
2441 |
min(range1[1] + range1[3], range2[1] + range2[3]) - max(range1[1], range2[1])) |
2442 |
if range >= range1[2] * range1[3] * 0.4 and range >= range2[2] * range2[3] * 0.4: |
2443 |
return True |
2444 |
else:
|
2445 |
return False |
2446 |
else:
|
2447 |
return False |
2448 |
|
2449 |
@staticmethod
|
2450 |
def detectOPCOnPid(area, symGray): |
2451 |
results = [] |
2452 |
|
2453 |
try:
|
2454 |
symbol = cv2.copyMakeBorder(symGray, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=255) |
2455 |
not_symbol = cv2.bitwise_not(symbol) |
2456 |
symbol_contours, symbol_hierachy = cv2.findContours(not_symbol, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) |
2457 |
if symbol_hierachy[0][0][0] != -1: |
2458 |
return results
|
2459 |
|
2460 |
contour_count = len(symbol_contours)
|
2461 |
if contour_count != 2: |
2462 |
return results
|
2463 |
|
2464 |
for i in range(1, contour_count): |
2465 |
# contour size
|
2466 |
symbol_area = cv2.contourArea(symbol_contours[i]) |
2467 |
# moments mass center
|
2468 |
symbol_moments = cv2.moments(symbol_contours[i]) |
2469 |
symbol_x = int(symbol_moments['m10'] / (symbol_moments['m00'] + 1e-5)) |
2470 |
symbol_y = int(symbol_moments['m01'] / (symbol_moments['m00'] + 1e-5)) |
2471 |
rect_x, rect_y, rect_w, rect_h = cv2.boundingRect(symbol_contours[i]) |
2472 |
symbol_x = symbol_x - rect_x |
2473 |
symbol_y = symbol_y - rect_y |
2474 |
# percent x, y
|
2475 |
percent_x = symbol_x / rect_w |
2476 |
percent_y = symbol_y / rect_h |
2477 |
|
2478 |
for contour in area.contours: |
2479 |
area_area = cv2.contourArea(contour) |
2480 |
if area_area * 1.2 >= symbol_area >= area_area * 0.8: |
2481 |
I1 = cv2.matchShapes(symbol_contours[i], contour, 1, 0) |
2482 |
I2 = cv2.matchShapes(symbol_contours[i], contour, 2, 0) |
2483 |
I3 = cv2.matchShapes(symbol_contours[i], contour, 3, 0) |
2484 |
if I1 < 1 and I2 < 1 and I3 < 0.1: |
2485 |
rect_x2, rect_y2, rect_w2, rect_h2 = cv2.boundingRect(contour) |
2486 |
if rect_w * 1.2 >= rect_w2 >= rect_w * 0.8 and rect_h * 1.2 >= rect_h2 >= rect_h * 0.8: |
2487 |
# moments mass center
|
2488 |
moments = cv2.moments(contour) |
2489 |
x = int(moments['m10'] / (moments['m00'] + 1e-5)) |
2490 |
y = int(moments['m01'] / (moments['m00'] + 1e-5)) |
2491 |
|
2492 |
x = x - rect_x2 |
2493 |
y = y - rect_y2 |
2494 |
percent_x2 = x / rect_w2 |
2495 |
percent_y2 = y / rect_h2 |
2496 |
|
2497 |
value_x = abs(percent_x - percent_x2)
|
2498 |
value_y = abs(percent_y - percent_y2)
|
2499 |
|
2500 |
results.append([1 - (value_x + value_y), [rect_x2, rect_y2]])
|
2501 |
break
|
2502 |
except Exception as ex: |
2503 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
2504 |
sys.exc_info()[-1].tb_lineno)
|
2505 |
return results
|
2506 |
|
2507 |
# TODO: detect flange
|
2508 |
@staticmethod
|
2509 |
def detectFlangeOnPid(symbol, connector, line, image): |
2510 |
pt = connector.center() |
2511 |
x = int(pt[0]) |
2512 |
y = int(pt[1]) |
2513 |
center_x = int(symbol.loc[0] + symbol.size[0] / 2) |
2514 |
center_y = int(symbol.loc[1] + symbol.size[1] / 2) |
2515 |
|
2516 |
arrow = Arrow.NULL |
2517 |
if line:
|
2518 |
line_center_x = int((line.start_point()[0] + line.end_point()[0]) / 2) |
2519 |
line_center_y = int((line.start_point()[1] + line.end_point()[1]) / 2) |
2520 |
|
2521 |
if line.isHorizontal():
|
2522 |
if center_x < line_center_x:
|
2523 |
arrow = Arrow.RIGHT |
2524 |
else:
|
2525 |
arrow = Arrow.LEFT |
2526 |
elif line.isVertical():
|
2527 |
if center_y < line_center_y:
|
2528 |
arrow = Arrow.DOWN |
2529 |
else:
|
2530 |
arrow = Arrow.UP |
2531 |
|
2532 |
if arrow is not Arrow.NULL: |
2533 |
result = Worker.detectFlangeOnPidArrow(symbol, x, y, arrow, image) |
2534 |
if result:
|
2535 |
return [x, y]
|
2536 |
else:
|
2537 |
_dir = [round(connector.dir().x()), round(connector.dir().y())] |
2538 |
if abs(_dir[0]) == 1: |
2539 |
if _dir[0] > 0: |
2540 |
arrow = Arrow.RIGHT |
2541 |
else:
|
2542 |
arrow = Arrow.LEFT |
2543 |
elif abs(_dir[1]) == 1: |
2544 |
if _dir[1] > 0: |
2545 |
arrow = Arrow.DOWN |
2546 |
else:
|
2547 |
arrow = Arrow.UP |
2548 |
|
2549 |
result = Worker.detectFlangeBlindOnPid(x, y, arrow, image) |
2550 |
if result:
|
2551 |
return [x, y]
|
2552 |
|
2553 |
return None |
2554 |
|
2555 |
@staticmethod
|
2556 |
def detectFlangeBlindOnPid(start_x, start_y, arrow, image): |
2557 |
loopRange = [] |
2558 |
if arrow is Arrow.DOWN: |
2559 |
loopRange = range(start_y - 10, start_y + 25) |
2560 |
elif arrow is Arrow.UP: |
2561 |
loopRange = range(start_y + 10, start_y - 25, -1) |
2562 |
elif arrow is Arrow.LEFT: |
2563 |
loopRange = range(start_x + 10, start_x - 25, -1) |
2564 |
elif arrow is Arrow.RIGHT: |
2565 |
loopRange = range(start_x - 10, start_x + 25) |
2566 |
else:
|
2567 |
return None |
2568 |
|
2569 |
find_forward = False
|
2570 |
find_backward = False
|
2571 |
find = None
|
2572 |
# 검은색 점을 찾는 범위
|
2573 |
search_length = 30
|
2574 |
# Line 최소 Length
|
2575 |
checkLineLength = 20
|
2576 |
# Line 최대 Width
|
2577 |
line_width = 18
|
2578 |
# flange min length
|
2579 |
flange_min = 25
|
2580 |
# flange max length
|
2581 |
flange_max = 50
|
2582 |
# flange max width
|
2583 |
flange_count_max = 10
|
2584 |
# noise max count
|
2585 |
noise_count_max = 5
|
2586 |
|
2587 |
# 임시
|
2588 |
temp_count = 0
|
2589 |
noise_count = 0
|
2590 |
find_list_x = [] |
2591 |
find_list_y = [] |
2592 |
for i in loopRange: |
2593 |
loop_find = False
|
2594 |
for j in range(search_length): |
2595 |
width = 0
|
2596 |
color_1 = 0
|
2597 |
color_2 = 0
|
2598 |
find_x = 0
|
2599 |
find_y = 0
|
2600 |
|
2601 |
if arrow is Arrow.DOWN or arrow is Arrow.UP: |
2602 |
color_1 = image[i, start_x - j] |
2603 |
color_2 = image[i, start_x + j] |
2604 |
if int(color_1) is 0: |
2605 |
width = Worker.getWidth(start_x - j, i, arrow, image) |
2606 |
find_x = start_x - j |
2607 |
find_y = i |
2608 |
elif int(color_2) is 0: |
2609 |
width = Worker.getWidth(start_x + j, i, arrow, image) |
2610 |
find_x = start_x + j |
2611 |
find_y = i |
2612 |
|
2613 |
elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT: |
2614 |
color_1 = image[start_y - j, i] |
2615 |
color_2 = image[start_y + j, i] |
2616 |
if int(color_1) is 0: |
2617 |
width = Worker.getWidth(i, start_y - j, arrow, image) |
2618 |
find_x = i |
2619 |
find_y = start_y - j |
2620 |
elif int(color_2) is 0: |
2621 |
width = Worker.getWidth(i, start_y + j, arrow, image) |
2622 |
find_x = i |
2623 |
find_y = start_y + j |
2624 |
|
2625 |
if flange_min < width < flange_max:
|
2626 |
loop_find = True
|
2627 |
find_list_x.append(find_x) |
2628 |
find_list_y.append(find_y) |
2629 |
break
|
2630 |
elif width > 0: |
2631 |
if temp_count > 0: |
2632 |
noise_count += 1
|
2633 |
break
|
2634 |
|
2635 |
if loop_find:
|
2636 |
if temp_count > flange_count_max:
|
2637 |
break
|
2638 |
temp_count = temp_count + 1
|
2639 |
elif 0 < temp_count < flange_count_max and width > 0 and noise_count <= noise_count_max: |
2640 |
continue
|
2641 |
elif noise_count > noise_count_max:
|
2642 |
break
|
2643 |
elif 0 < temp_count < flange_count_max and width == 0 and noise_count <= noise_count_max: |
2644 |
find_forward = True
|
2645 |
break
|
2646 |
else:
|
2647 |
find_list_x.clear() |
2648 |
find_list_y.clear() |
2649 |
temp_count = 0
|
2650 |
|
2651 |
if not find_forward: |
2652 |
return False |
2653 |
|
2654 |
# 임시
|
2655 |
temp_count = 0
|
2656 |
noise_count = 0
|
2657 |
find_list_x = [] |
2658 |
find_list_y = [] |
2659 |
for i in reversed(loopRange): |
2660 |
loop_find = False
|
2661 |
for j in range(search_length): |
2662 |
width = 0
|
2663 |
color_1 = 0
|
2664 |
color_2 = 0
|
2665 |
find_x = 0
|
2666 |
find_y = 0
|
2667 |
|
2668 |
if arrow is Arrow.DOWN or arrow is Arrow.UP: |
2669 |
color_1 = image[i, start_x - j] |
2670 |
color_2 = image[i, start_x + j] |
2671 |
if int(color_1) is 0: |
2672 |
width = Worker.getWidth(start_x - j, i, arrow, image) |
2673 |
find_x = start_x - j |
2674 |
find_y = i |
2675 |
elif int(color_2) is 0: |
2676 |
width = Worker.getWidth(start_x + j, i, arrow, image) |
2677 |
find_x = start_x + j |
2678 |
find_y = i |
2679 |
|
2680 |
elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT: |
2681 |
color_1 = image[start_y - j, i] |
2682 |
color_2 = image[start_y + j, i] |
2683 |
if int(color_1) is 0: |
2684 |
width = Worker.getWidth(i, start_y - j, arrow, image) |
2685 |
find_x = i |
2686 |
find_y = start_y - j |
2687 |
elif int(color_2) is 0: |
2688 |
width = Worker.getWidth(i, start_y + j, arrow, image) |
2689 |
find_x = i |
2690 |
find_y = start_y + j |
2691 |
|
2692 |
if flange_min < width < flange_max:
|
2693 |
loop_find = True
|
2694 |
find_list_x.append(find_x) |
2695 |
find_list_y.append(find_y) |
2696 |
break
|
2697 |
elif width > 0: |
2698 |
if temp_count > 0: |
2699 |
noise_count += 1
|
2700 |
break
|
2701 |
|
2702 |
if loop_find:
|
2703 |
if temp_count > flange_count_max:
|
2704 |
break
|
2705 |
temp_count = temp_count + 1
|
2706 |
elif 0 < temp_count < flange_count_max and width > 0 and noise_count <= noise_count_max: |
2707 |
continue
|
2708 |
elif noise_count > noise_count_max:
|
2709 |
break
|
2710 |
elif 0 < temp_count < flange_count_max and width == 0 and noise_count <= noise_count_max: |
2711 |
find_backward = True
|
2712 |
break
|
2713 |
else:
|
2714 |
find_list_x.clear() |
2715 |
find_list_y.clear() |
2716 |
temp_count = 0
|
2717 |
|
2718 |
if find_forward and find_backward: |
2719 |
return True |
2720 |
|
2721 |
@staticmethod
|
2722 |
def detectFlangeOnPidArrow(symbol, start_x, start_y, arrow, image): |
2723 |
loopRange = [] |
2724 |
if arrow is Arrow.DOWN: |
2725 |
loopRange = range(start_y - 20, start_y + 40) |
2726 |
elif arrow is Arrow.UP: |
2727 |
loopRange = range(start_y + 20, start_y - 40, -1) |
2728 |
elif arrow is Arrow.LEFT: |
2729 |
loopRange = range(start_x + 20, start_x - 40, -1) |
2730 |
elif arrow is Arrow.RIGHT: |
2731 |
loopRange = range(start_x - 20, start_x + 40) |
2732 |
else:
|
2733 |
return None |
2734 |
|
2735 |
find = False
|
2736 |
# 검은색 점을 찾는 범위
|
2737 |
search_length = 10
|
2738 |
# Line 최소 Length
|
2739 |
checkLineLength = 20
|
2740 |
# Line 최대 Width
|
2741 |
line_width = 18
|
2742 |
# flange min length
|
2743 |
flange_min = 25
|
2744 |
# flange max length
|
2745 |
flange_max = 50
|
2746 |
# flange max width
|
2747 |
flange_count_max = 10
|
2748 |
# 임시
|
2749 |
temp_count = 0
|
2750 |
find_list_x = [] |
2751 |
find_list_y = [] |
2752 |
for i in loopRange: |
2753 |
loop_find = False
|
2754 |
for j in range(search_length): |
2755 |
width = 0
|
2756 |
color_1 = 0
|
2757 |
color_2 = 0
|
2758 |
find_x = 0
|
2759 |
find_y = 0
|
2760 |
|
2761 |
if arrow is Arrow.DOWN or arrow is Arrow.UP: |
2762 |
color_1 = image[i, start_x - j] |
2763 |
color_2 = image[i, start_x + j] |
2764 |
if int(color_1) is 0: |
2765 |
width = Worker.getWidth(start_x - j, i, arrow, image) |
2766 |
find_x = start_x - j |
2767 |
find_y = i |
2768 |
elif int(color_2) is 0: |
2769 |
width = Worker.getWidth(start_x + j, i, arrow, image) |
2770 |
find_x = start_x + j |
2771 |
find_y = i |
2772 |
|
2773 |
elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT: |
2774 |
color_1 = image[start_y - j, i] |
2775 |
color_2 = image[start_y + j, i] |
2776 |
if int(color_1) is 0: |
2777 |
width = Worker.getWidth(i, start_y - j, arrow, image) |
2778 |
find_x = i |
2779 |
find_y = start_y - j |
2780 |
elif int(color_2) is 0: |
2781 |
width = Worker.getWidth(i, start_y + j, arrow, image) |
2782 |
find_x = i |
2783 |
find_y = start_y + j |
2784 |
|
2785 |
if 0 < width <= line_width: |
2786 |
loop_find = True
|
2787 |
find_list_x.append(find_x) |
2788 |
find_list_y.append(find_y) |
2789 |
break
|
2790 |
|
2791 |
if loop_find:
|
2792 |
if temp_count > checkLineLength:
|
2793 |
find = True
|
2794 |
break
|
2795 |
temp_count = temp_count + 1
|
2796 |
else:
|
2797 |
find_list_x.clear() |
2798 |
find_list_y.clear() |
2799 |
temp_count = 0
|
2800 |
|
2801 |
if find:
|
2802 |
count = 0
|
2803 |
temp_list = [] |
2804 |
find_white = False
|
2805 |
average = 0
|
2806 |
if arrow is Arrow.DOWN or arrow is Arrow.UP: |
2807 |
average = Worker.getAverage(find_list_x) |
2808 |
elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT: |
2809 |
average = Worker.getAverage(find_list_y) |
2810 |
else:
|
2811 |
pass
|
2812 |
|
2813 |
flange_count = 0
|
2814 |
for i in range(20): |
2815 |
width = 0
|
2816 |
if arrow is Arrow.DOWN: |
2817 |
width = Worker.getWidth(average, find_list_y[0] - i, arrow, image)
|
2818 |
elif arrow is Arrow.UP: |
2819 |
width = Worker.getWidth(average, find_list_y[0] + i, arrow, image)
|
2820 |
elif arrow is Arrow.LEFT: |
2821 |
width = Worker.getWidth(find_list_x[0] + i, average, arrow, image)
|
2822 |
elif arrow is Arrow.RIGHT: |
2823 |
width = Worker.getWidth(find_list_x[0] - i, average, arrow, image)
|
2824 |
else:
|
2825 |
pass
|
2826 |
|
2827 |
if flange_min < width < flange_max:
|
2828 |
flange_count = flange_count + 1
|
2829 |
elif width <= 0: |
2830 |
find_white = True
|
2831 |
break
|
2832 |
else:
|
2833 |
pass
|
2834 |
|
2835 |
if 0 < flange_count < flange_count_max and find_white: |
2836 |
crop_image = None
|
2837 |
x = find_list_x[0] - (int(symbol.loc[0]) - int(symbol.size[0]) - 1) |
2838 |
y = find_list_y[0] - (int(symbol.loc[1]) - int(symbol.size[1]) - 1) |
2839 |
crop_image = Worker.removeLine(symbol, find_list_x[0], find_list_y[0], arrow, image) |
2840 |
if arrow is Arrow.DOWN: |
2841 |
y = y - 1
|
2842 |
elif arrow is Arrow.UP: |
2843 |
y = y + 1
|
2844 |
elif arrow is Arrow.LEFT: |
2845 |
x = x + 1
|
2846 |
elif arrow is Arrow.RIGHT: |
2847 |
x = x - 1
|
2848 |
else:
|
2849 |
return None |
2850 |
crop_image = cv2.copyMakeBorder(crop_image, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=255) |
2851 |
not_image = cv2.bitwise_not(crop_image) |
2852 |
image_contours, image_hierachy = cv2.findContours(not_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) |
2853 |
for contour in image_contours: |
2854 |
c_x, c_y, c_w, c_h = cv2.boundingRect(contour) |
2855 |
area = cv2.contourArea(contour) |
2856 |
if c_x <= x and c_x + c_w >= x and c_y <= y and c_y + c_h >= y: |
2857 |
if flange_count * flange_max * 1.8 >= area: |
2858 |
#not_image[y, x] = 0
|
2859 |
#from PIL import Image
|
2860 |
#Image.fromarray(not_image).show()
|
2861 |
return True |
2862 |
|
2863 |
else:
|
2864 |
return False |
2865 |
|
2866 |
return False |
2867 |
|
2868 |
@staticmethod
|
2869 |
def getWidth(x, y, arrow, image): |
2870 |
width = 0
|
2871 |
move_x1 = 0
|
2872 |
move_y1 = 0
|
2873 |
move_x2 = 0
|
2874 |
move_y2 = 0
|
2875 |
|
2876 |
if arrow is Arrow.DOWN or arrow is Arrow.UP: |
2877 |
while True: |
2878 |
color = image[y, x - move_x1] |
2879 |
if int(color) is 0: |
2880 |
move_x1 = move_x1 + 1
|
2881 |
else:
|
2882 |
break
|
2883 |
while True: |
2884 |
color = image[y, x + move_x2] |
2885 |
if int(color) is 0: |
2886 |
move_x2 = move_x2 + 1
|
2887 |
else:
|
2888 |
break
|
2889 |
width = move_x1 + move_x2 - 1
|
2890 |
elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT: |
2891 |
while True: |
2892 |
color = image[y - move_y1, x] |
2893 |
if int(color) is 0: |
2894 |
move_y1 = move_y1 + 1
|
2895 |
else:
|
2896 |
break
|
2897 |
while True: |
2898 |
color = image[y + move_y2, x] |
2899 |
if int(color) is 0: |
2900 |
move_y2 = move_y2 + 1
|
2901 |
else:
|
2902 |
break
|
2903 |
width = move_y1 + move_y2 - 1
|
2904 |
|
2905 |
return width
|
2906 |
|
2907 |
@staticmethod
|
2908 |
def removeLine(symbol, x, y, arrow, image): |
2909 |
symbol_width = int(symbol.size[0]) |
2910 |
symbol_height = int(symbol.size[1]) |
2911 |
|
2912 |
if arrow is Arrow.DOWN or arrow is Arrow.UP: |
2913 |
for i in range(symbol_width): |
2914 |
image[y, x - i] = 255
|
2915 |
image[y, x + i] = 255
|
2916 |
elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT: |
2917 |
for i in range(symbol_height): |
2918 |
image[y - i, x] = 255
|
2919 |
image[y + i, x] = 255
|
2920 |
|
2921 |
crop_x1 = int(symbol.loc[0]) - symbol_width |
2922 |
crop_y1 = int(symbol.loc[1]) - symbol_height |
2923 |
crop_x2 = int(symbol.loc[0]) + symbol_width * 2 |
2924 |
crop_y2 = int(symbol.loc[1]) + symbol_height * 2 |
2925 |
image = image[crop_y1:crop_y2, crop_x1:crop_x2] |
2926 |
|
2927 |
return image
|
2928 |
|
2929 |
@staticmethod
|
2930 |
def getAverage(datas): |
2931 |
result = 0
|
2932 |
|
2933 |
for x in datas: |
2934 |
result = result + x |
2935 |
|
2936 |
result = result / len(datas)
|
2937 |
|
2938 |
return int(result) |
2939 |
|
2940 |
|
2941 |
# Convert into Grayscale image
|
2942 |
@staticmethod
|
2943 |
def cvtGrayImage(img): |
2944 |
return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
2945 |
|
2946 |
'''
|
2947 |
@history 2018.06.12 Jeongwoo Type changed (int → float)
|
2948 |
humkyung 2018.07.07 change return type as like [x,y]
|
2949 |
'''
|
2950 |
|
2951 |
@staticmethod
|
2952 |
def getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolRotatedAngle, rotateSymbolWidth, |
2953 |
rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight, flipped=False):
|
2954 |
res = [] |
2955 |
|
2956 |
if additionalSymbol is None and symbolOriginalPoint is None: |
2957 |
res.append(rotateSymbolWidth // 2)
|
2958 |
res.append(rotateSymbolHeight // 2)
|
2959 |
else:
|
2960 |
if flipped:
|
2961 |
opx = originalSymbolWidth - float(symbolOriginalPoint.split(',')[0]) |
2962 |
opy = float(symbolOriginalPoint.split(',')[1]) |
2963 |
else:
|
2964 |
opx = float(symbolOriginalPoint.split(',')[0]) |
2965 |
opy = float(symbolOriginalPoint.split(',')[1]) |
2966 |
|
2967 |
rPt = Worker.getCoordOnRotatedImage(symbolRotatedAngle, ('AUTO', opx, opy, '0'), originalSymbolWidth, |
2968 |
originalSymbolHeight) |
2969 |
|
2970 |
res.append(rPt[1])
|
2971 |
res.append(rPt[2])
|
2972 |
|
2973 |
return res
|
2974 |
|
2975 |
'''
|
2976 |
@history 2018.06.12 Jeongwoo Type changed (int → float)
|
2977 |
humkyung 2018.07.07 change return type as like [[x,y],...]
|
2978 |
humkyung 2019.01.04 get symbol index
|
2979 |
'''
|
2980 |
|
2981 |
@staticmethod
|
2982 |
def getCalculatedConnectionPoint(symbolConnectionPointStr, symbolRotatedAngle, rotateSymbolWidth, |
2983 |
rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight, flipped=0):
|
2984 |
res = [] |
2985 |
|
2986 |
if symbolConnectionPointStr is not None and symbolConnectionPointStr != '': |
2987 |
splitConnectionPointStr = symbolConnectionPointStr.split("/")
|
2988 |
for strConnPt in splitConnectionPointStr: |
2989 |
tokens = strConnPt.split(',')
|
2990 |
|
2991 |
direction = 'AUTO'
|
2992 |
symbol_idx = '0'
|
2993 |
if flipped:
|
2994 |
converted = {'AUTO': 'AUTO', 'LEFT': 'RIGHT', 'RIGHT': 'LEFT', 'UP': 'UP', 'DOWN': 'DOWN'} |
2995 |
|
2996 |
if len(tokens) == 2: |
2997 |
cpx = originalSymbolWidth - float(tokens[0]) |
2998 |
cpy = float(tokens[1]) |
2999 |
elif len(tokens) == 3: |
3000 |
#direction = converted[tokens[0]]
|
3001 |
direction = tokens[0]
|
3002 |
cpx = originalSymbolWidth - float(tokens[1]) |
3003 |
cpy = float(tokens[2]) |
3004 |
elif len(tokens) >= 4: |
3005 |
#direction = converted[tokens[0]]
|
3006 |
direction = tokens[0]
|
3007 |
cpx = originalSymbolWidth - float(tokens[1]) |
3008 |
cpy = float(tokens[2]) |
3009 |
symbol_idx = tokens[3]
|
3010 |
else:
|
3011 |
if len(tokens) == 2: |
3012 |
cpx = float(tokens[0]) |
3013 |
cpy = float(tokens[1]) |
3014 |
elif len(tokens) == 3: |
3015 |
direction = tokens[0]
|
3016 |
cpx = float(tokens[1]) |
3017 |
cpy = float(tokens[2]) |
3018 |
elif len(tokens) >= 4: |
3019 |
direction = tokens[0]
|
3020 |
cpx = float(tokens[1]) |
3021 |
cpy = float(tokens[2]) |
3022 |
symbol_idx = tokens[3]
|
3023 |
|
3024 |
res.append(Worker.getCoordOnRotatedImage(symbolRotatedAngle, (direction, cpx, cpy, symbol_idx), |
3025 |
originalSymbolWidth, originalSymbolHeight)) |
3026 |
|
3027 |
return res
|
3028 |
|
3029 |
'''
|
3030 |
@brief rotate (x,y) by given angle
|
3031 |
@author Jeongwoo
|
3032 |
@date 2018.??.??
|
3033 |
@history humkyung 2018.04.13 fixed code when angle is 90 or 270
|
3034 |
Jeongwoo 2018.04.27 Change calculation method with QTransform
|
3035 |
humkyung 2018.09.01 calculate rotated direction
|
3036 |
'''
|
3037 |
|
3038 |
@staticmethod
|
3039 |
def getCoordOnRotatedImage(angle, connPt, originImageWidth, originImageHeight): |
3040 |
import math |
3041 |
|
3042 |
rx = None
|
3043 |
ry = None
|
3044 |
|
3045 |
# calculate rotated direction
|
3046 |
direction = connPt[0]
|
3047 |
'''
|
3048 |
if direction == 'LEFT':
|
3049 |
direction = 'DOWN' if angle == 90 else 'RIGHT' if angle == 180 else 'UP' if angle == 270 else direction
|
3050 |
elif direction == 'RIGHT':
|
3051 |
direction = 'UP' if angle == 90 else 'LEFT' if angle == 180 else 'DOWN' if angle == 270 else direction
|
3052 |
elif direction == 'UP':
|
3053 |
direction = 'LEFT' if angle == 90 else 'DOWN' if angle == 180 else 'RIGHT' if angle == 270 else direction
|
3054 |
elif direction == 'DOWN':
|
3055 |
direction = 'RIGHT' if angle == 90 else 'UP' if angle == 180 else 'LEFT' if angle == 270 else direction
|
3056 |
'''
|
3057 |
# up to here
|
3058 |
|
3059 |
'''
|
3060 |
transform = QTransform()
|
3061 |
if angle == 90 or angle == 270:
|
3062 |
transform.translate(originImageHeight * 0.5, originImageWidth * 0.5)
|
3063 |
elif angle == 0 or angle == 180:
|
3064 |
transform.translate(originImageWidth * 0.5, originImageHeight * 0.5)
|
3065 |
transform.rotate(abs(angle))
|
3066 |
transform.translate(-originImageWidth * 0.5, -originImageHeight * 0.5)
|
3067 |
point = QPoint(connPt[1], connPt[2])
|
3068 |
point = transform.map(point)
|
3069 |
rx = point.x()
|
3070 |
ry = point.y()
|
3071 |
'''
|
3072 |
|
3073 |
rect = QRectF(0, 0, originImageWidth, originImageHeight) |
3074 |
points = [rect.bottomLeft(), rect.topLeft(), rect.topRight(), rect.bottomRight()] |
3075 |
transform2 = QTransform() |
3076 |
transform2.rotate(abs(angle))
|
3077 |
points = [transform2.map(point) for point in points] |
3078 |
offset_x = abs(min([point.x() for point in points])) |
3079 |
offset_y = abs(min([point.y() for point in points])) |
3080 |
point = QPoint(connPt[1], connPt[2]) |
3081 |
point = transform2.map(point) |
3082 |
rx, ry = point.x() + offset_x, point.y() + offset_y |
3083 |
|
3084 |
'''
|
3085 |
pX, pY = connPt[1], originImageHeight - connPt[2]
|
3086 |
rad = math.radians(-angle)
|
3087 |
rot_ce_X = originImageWidth * 0.5
|
3088 |
rot_ce_Y = originImageHeight * 0.5
|
3089 |
rX = (pX - rot_ce_X) * math.cos(rad) - (pY - rot_ce_Y) * math.sin(rad) + rot_ce_X
|
3090 |
rY = (pX - rot_ce_X) * math.sin(rad) + (pY - rot_ce_Y) * math.cos(rad) + rot_ce_Y
|
3091 |
rx = rX
|
3092 |
ry = originImageHeight - rY
|
3093 |
|
3094 |
if abs(point.x() - rx) > 0.1 or abs(point.y() - ry) > 0.1:
|
3095 |
print('a')
|
3096 |
'''
|
3097 |
|
3098 |
symbol_idx = connPt[3]
|
3099 |
|
3100 |
return (direction, rx, ry, symbol_idx)
|
3101 |
|
3102 |
'''
|
3103 |
@brief Add symbols
|
3104 |
@author jwkim
|
3105 |
@date
|
3106 |
@history Change parameter (mpCount → hitRate)
|
3107 |
Yecheol 2018.07.04 Delete Symbol Id
|
3108 |
'''
|
3109 |
|
3110 |
@staticmethod
|
3111 |
def addSearchedSymbol(sName, sType |
3112 |
, sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle |
3113 |
, isDetectOnOrigin, rotateCount, ocrOption, isContainChild |
3114 |
, originalPoint, connectionPoint, baseSymbol, additionalSymbol, isExceptDetect, detectFlip |
3115 |
, hasInstrumentLabel, text_area): |
3116 |
global searchedSymbolList
|
3117 |
|
3118 |
newSym = None
|
3119 |
try:
|
3120 |
newSym = symbol.Symbol(sName, sType |
3121 |
, sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle, |
3122 |
isDetectOnOrigin, rotateCount, ocrOption, isContainChild, |
3123 |
','.join(str(x) for x in originalPoint), |
3124 |
'/'.join('{},{},{},{}'.format(param[0], param[1], param[2], param[3]) for param in |
3125 |
connectionPoint), |
3126 |
baseSymbol, additionalSymbol, isExceptDetect, detectFlip=1 if detectFlip else 0, |
3127 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
3128 |
|
3129 |
searchedSymbolList.append(newSym) |
3130 |
except Exception as ex: |
3131 |
from App import App |
3132 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
3133 |
sys.exc_info()[-1].tb_lineno)
|
3134 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
3135 |
|
3136 |
return newSym
|
3137 |
|
3138 |
'''
|
3139 |
@brief Check object contains pt
|
3140 |
@param obj is item in searchedSymbolList
|
3141 |
'''
|
3142 |
|
3143 |
@staticmethod
|
3144 |
def contains(obj, pt, tw, th): |
3145 |
sp = obj.getSp() |
3146 |
width = obj.getWidth() |
3147 |
height = obj.getHeight() |
3148 |
|
3149 |
if sp[0] > pt[0] + tw: |
3150 |
return 0 |
3151 |
if sp[0] + width < pt[0]: |
3152 |
return 0 |
3153 |
if sp[1] > pt[1] + th: |
3154 |
return 0 |
3155 |
if sp[1] + height < pt[1]: |
3156 |
return 0 |
3157 |
|
3158 |
# shared area
|
3159 |
x = max(sp[0], pt[0]) |
3160 |
y = max(sp[1], pt[1]) |
3161 |
w = min(sp[0] + width, pt[0] + tw) - x |
3162 |
h = min(sp[1] + height, pt[1] + th) - y |
3163 |
|
3164 |
return float((w * h)) / float((tw * th)) * 100 |
3165 |
|
3166 |
# Calculate count of keypoint match result
|
3167 |
@staticmethod
|
3168 |
def getMatchPointCount(src, cmp): |
3169 |
matchCount = 0
|
3170 |
|
3171 |
try:
|
3172 |
orb = cv2.ORB_create(1000, 2.0, 2, 1) |
3173 |
|
3174 |
kp1, des1 = orb.detectAndCompute(src, None)
|
3175 |
kp2, des2 = orb.detectAndCompute(cmp, None) |
3176 |
|
3177 |
FLANN_INDEX_LSH = 6
|
3178 |
# table_number : The number of hash tables use
|
3179 |
# key_size : The length of the key in the hash tables
|
3180 |
# multi_probe_level : Number of levels to use in multi-probe (0 for standard LSH)
|
3181 |
# It controls how neighboring buckets are searched
|
3182 |
# Recommended value is 2
|
3183 |
# checks : specifies the maximum leafs to visit when searching for neighbours.
|
3184 |
# LSH : Locality-Sensitive Hashing
|
3185 |
# ref : https://www.cs.ubc.ca/research/flann/uploads/FLANN/flann_manual-1.8.4.pdf
|
3186 |
index_params = dict(algorithm=FLANN_INDEX_LSH, table_number=20, key_size=10, multi_probe_level=4) |
3187 |
search_params = dict(checks=100) |
3188 |
|
3189 |
flann = cv2.FlannBasedMatcher(index_params, search_params) |
3190 |
|
3191 |
matches = flann.knnMatch(des1, des2, k=2)
|
3192 |
matchesMask = [[0, 0] for i in range(len(matches))] # Python 3.x |
3193 |
|
3194 |
count = 0
|
3195 |
# ratio test as per Lowe's paper
|
3196 |
for i in range(len(matches)): |
3197 |
if len(matches[i]) == 2: |
3198 |
m = matches[i][0]
|
3199 |
n = matches[i][1]
|
3200 |
if m.distance < 0.85 * n.distance: |
3201 |
count = count + 1
|
3202 |
|
3203 |
matchCount = count |
3204 |
except Exception as ex: |
3205 |
from App import App |
3206 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
3207 |
sys.exc_info()[-1].tb_lineno)
|
3208 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
3209 |
|
3210 |
return matchCount
|
3211 |
|
3212 |
'''
|
3213 |
@brief Remake rotated child symbol info
|
3214 |
'''
|
3215 |
|
3216 |
@staticmethod
|
3217 |
def getRotatedChildInfo(additionalSymbol): |
3218 |
tempChildInfo = ""
|
3219 |
if additionalSymbol:
|
3220 |
childList = additionalSymbol.split("/")
|
3221 |
for index in range(len(childList)): |
3222 |
child = childList[index] |
3223 |
direction = Worker.convertDirectionCodeToValue(child.split(",")[0]) |
3224 |
childName = child.split(",")[1] |
3225 |
direction = (direction - 1) if direction > 0 else 3 |
3226 |
if index != 0: |
3227 |
tempChildInfo = tempChildInfo + "/"
|
3228 |
tempChildInfo = tempChildInfo + Worker.convertValueToDirectionCode(direction) + "," + childName
|
3229 |
return tempChildInfo
|
3230 |
|
3231 |
@staticmethod
|
3232 |
def calculate_exact_position(area, symGray, symbol, rect, worker, threshold): |
3233 |
import copy |
3234 |
|
3235 |
try:
|
3236 |
tilt = False
|
3237 |
|
3238 |
symbolThreshold = symbol.getThreshold() |
3239 |
symbolRotateCount = symbol.getRotationCount() |
3240 |
detectFlip = symbol.getDetectFlip() |
3241 |
hasInstrumentLabel = symbol.getHasInstrumentLabel() |
3242 |
symbolOriginalPoint = symbol.getOriginalPoint() |
3243 |
symbolConnectionPoint = symbol.getConnectionPoint() |
3244 |
additionalSymbol = symbol.getAdditionalSymbol() |
3245 |
|
3246 |
sow, soh = symGray.shape[::-1]
|
3247 |
|
3248 |
# symbol is bigger than roi -> detected symbol area is too big
|
3249 |
if rect[3] * rect[4] > sow * soh * 2.5: |
3250 |
# for single class test
|
3251 |
#pass
|
3252 |
return (None, None, None, None, None, None, None, None) |
3253 |
# detected symbol area is too small
|
3254 |
elif rect[3] * rect[4] * 1.8 < sow * soh: |
3255 |
# for single class test
|
3256 |
#pass
|
3257 |
return (None, None, None, None, None, None, None, None) |
3258 |
|
3259 |
# get Rotated Original Point
|
3260 |
sow, soh = symGray.shape[::-1]
|
3261 |
offset_x, offset_y = round(rect[3] * 0.5), round(rect[4] * 0.5)#int(max(sow, soh) / 2) |
3262 |
|
3263 |
roiItem = Worker.remove_small_objects(area.img, 0, sow * soh * 0.5) if hasInstrumentLabel else area.img.copy() |
3264 |
x_start = round(rect[1]) - round(offset_x) if round(rect[1]) - round(offset_x) > 0 else 0 |
3265 |
y_start = round(rect[2]) - round(offset_y) if round(rect[2]) - round(offset_y) > 0 else 0 |
3266 |
x_max = round(rect[1]) + round(rect[3]) + round(offset_x) if round(rect[1]) + round(rect[3]) + round(offset_x) < len(roiItem[0]) else len(roiItem[0]) - 1 |
3267 |
y_max = round(rect[2]) + round(rect[4]) + round(offset_y) if round(rect[2]) + round(rect[4]) + round(offset_y) < len(roiItem) else len(roiItem) - 1 |
3268 |
roiItem = roiItem[y_start:y_max, x_start:x_max] |
3269 |
|
3270 |
symGrayOri = copy.copy(symGray) |
3271 |
|
3272 |
searchedInfos = [] # score, x, y, angle, flip, originalPoint, connectionPoint, sw, sh
|
3273 |
|
3274 |
|
3275 |
steps = [False, True] if detectFlip else [False] |
3276 |
for flipped in steps: |
3277 |
if flipped:
|
3278 |
symGray = symGrayOri |
3279 |
symGray = cv2.flip(symGray, 1)
|
3280 |
|
3281 |
# 45 test
|
3282 |
symGray_0 = copy.copy(symGray) |
3283 |
if tilt:
|
3284 |
rect = QRectF(0, 0, symGray.shape[::-1][0], symGray.shape[::-1][1]) |
3285 |
points = [rect.bottomLeft(), rect.topLeft(), rect.topRight(), rect.bottomRight()] |
3286 |
transform2 = QTransform() |
3287 |
transform2.rotate(abs(45)) |
3288 |
points = [transform2.map(point) for point in points] |
3289 |
offset_x = int(max([point.x() for point in points]) - min([point.x() for point in points])) |
3290 |
offset_y = int(max([point.y() for point in points]) - min([point.y() for point in points])) |
3291 |
s_x, s_y = round((offset_x - symGray.shape[::-1][0]) / 2), round((offset_y - symGray.shape[::-1][1]) / 2) |
3292 |
mask = np.ones((offset_x, offset_y), dtype=np.uint8) * 255
|
3293 |
mask[s_y:s_y + symGray.shape[::-1][1], s_x:s_x + symGray.shape[::-1][0]] = symGray |
3294 |
|
3295 |
symGray_45 = mask |
3296 |
cX, cY = int(symGray_45.shape[::-1][0] / 2), int(symGray_45.shape[::-1][1] / 2) |
3297 |
M = cv2.getRotationMatrix2D((cX, cY), -45, 1.0) |
3298 |
symGray_45 = cv2.warpAffine(symGray_45, M, (symGray_45.shape[::-1][0], symGray_45.shape[::-1][1]), borderValue=(255)) |
3299 |
|
3300 |
symbolRotatedAngle = 0
|
3301 |
for rc in range((symbolRotateCount + 1) * 2): |
3302 |
#for rc in range(symbolRotateCount + 1):
|
3303 |
if rc % 2 == 0: |
3304 |
symGray = symGray_0 |
3305 |
else:
|
3306 |
if tilt:
|
3307 |
symGray = symGray_45 |
3308 |
else:
|
3309 |
symbolRotatedAngle = (symbolRotatedAngle + 45) % 360 |
3310 |
continue
|
3311 |
|
3312 |
sw, sh = symGray.shape[::-1]
|
3313 |
|
3314 |
originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, |
3315 |
symbolRotatedAngle, sw, sh, sow, soh, flipped) |
3316 |
connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw, |
3317 |
sh, sow, soh, flipped) |
3318 |
|
3319 |
r_w, r_h = roiItem.shape[::-1]
|
3320 |
if r_w < sw or r_h < sh: |
3321 |
if rc % 2 == 0: |
3322 |
symGray_0 = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE) |
3323 |
else:
|
3324 |
if tilt:
|
3325 |
symGray_45 = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE) |
3326 |
#symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
|
3327 |
#symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
|
3328 |
#cX, cY = originalPoint[0], originalPoint[0]
|
3329 |
#M = cv2.getRotationMatrix2D((cX, cY), 45, 1.0)
|
3330 |
#symGray = cv2.warpAffine(symGray, M, (sw, sh))
|
3331 |
symbolRotatedAngle = (symbolRotatedAngle + 45) % 360 |
3332 |
continue
|
3333 |
|
3334 |
tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED) |
3335 |
_, max_val, __, max_loc = cv2.minMaxLoc(tmRes) |
3336 |
#maxIndex = tmRes.argmax()
|
3337 |
#colCount = len(tmRes[0])
|
3338 |
#col, row = divmod(maxIndex, colCount)
|
3339 |
|
3340 |
# for single class test
|
3341 |
#max_val = 80
|
3342 |
|
3343 |
if max_val > threshold:
|
3344 |
searchedInfos.append([max_val, max_loc[0] + x_start, max_loc[1] + y_start, symbolRotatedAngle, flipped, originalPoint, connectionPoint, sw, sh]) |
3345 |
|
3346 |
if rc % 2 == 0: |
3347 |
symGray_0 = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE) |
3348 |
else:
|
3349 |
if tilt:
|
3350 |
symGray_45 = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE) |
3351 |
#symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
|
3352 |
#symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
|
3353 |
#cX, cY = originalPoint[0], originalPoint[0]
|
3354 |
#M = cv2.getRotationMatrix2D((cX, cY), 45, 1.0)
|
3355 |
#symGray = cv2.warpAffine(symGray, M, (sw, sh))
|
3356 |
symbolRotatedAngle = (symbolRotatedAngle + 45) % 360 |
3357 |
|
3358 |
if searchedInfos:
|
3359 |
searchedInfos = sorted(searchedInfos, key=lambda param: param[0], reverse=True) |
3360 |
searchedInfo = searchedInfos[0]
|
3361 |
return ((searchedInfo[1] + area.x, searchedInfo[2] + area.y), searchedInfo[3], searchedInfo[4], \ |
3362 |
searchedInfo[5], searchedInfo[6], searchedInfo[7], searchedInfo[8], searchedInfo[0]) |
3363 |
else:
|
3364 |
return (None, None, None, None, None, None, None, None) |
3365 |
|
3366 |
except Exception as ex: |
3367 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
3368 |
sys.exc_info()[-1].tb_lineno)
|
3369 |
worker.displayLog.emit(MessageType.Error, message) |
3370 |
|
3371 |
return (None, None, None, None, None, None, None, None) |
3372 |
|
3373 |
@staticmethod
|
3374 |
def detect_symbol_using_server(targetSymbols, listWidget, worker): |
3375 |
from AppWebService import AppWebService |
3376 |
|
3377 |
res = [] |
3378 |
|
3379 |
app_doc_data = AppDocData.instance() |
3380 |
project = app_doc_data.getCurrentProject() |
3381 |
area = app_doc_data.getArea('Drawing')
|
3382 |
|
3383 |
app_web_service = AppWebService() |
3384 |
symbols = app_web_service.request_symbol_box(project.name, area.img) |
3385 |
|
3386 |
configs = app_doc_data.getConfigs('Engine', 'AI') |
3387 |
mode = 1
|
3388 |
if configs:
|
3389 |
mode = int(configs[0].value) |
3390 |
if mode != 1: |
3391 |
count = 50 if mode == 2 else 100 |
3392 |
for index in reversed(range(len(symbols))): |
3393 |
if index % count == 0: |
3394 |
symbols.pop(index) |
3395 |
|
3396 |
time_delay = 10 if mode == 2 else 20 |
3397 |
time_stamp = timeit.default_timer() |
3398 |
while True: |
3399 |
if time_stamp - timeit.default_timer() > time_delay:
|
3400 |
break
|
3401 |
|
3402 |
# merge symbol box
|
3403 |
overlap_merges = [] |
3404 |
for rect1 in symbols: |
3405 |
for rect2 in symbols: |
3406 |
if rect1 is rect2 or rect1[0] != rect2[0]: |
3407 |
continue
|
3408 |
rect1_x_gap = int(rect1[3] / 10) |
3409 |
rect1_y_gap = int(rect1[4] / 10) |
3410 |
rect2_x_gap = int(rect1[3] / 10) |
3411 |
rect2_y_gap = int(rect1[4] / 10) |
3412 |
|
3413 |
l1, l2 = rect1[1] + rect1_x_gap, rect2[1] + rect2_x_gap |
3414 |
r1, r2 = rect1[1] + rect1[3] - rect1_x_gap, rect2[1] + rect2[3] - rect2_x_gap |
3415 |
l_x, s_x = [l1, r1], [l2, r2] |
3416 |
t1, t2 = rect1[2] + rect1_y_gap, rect2[2] + rect2_y_gap |
3417 |
b1, b2 = rect1[2] + rect1[4] - rect1_y_gap, rect2[2] + rect2[4] - rect2_y_gap |
3418 |
l_y, s_y = [t1, b1], [t2, b2] |
3419 |
if not (max(l_x) < min(s_x) or max(s_x) < min(l_x)) and \ |
3420 |
not (max(l_y) < min(s_y) or max(s_y) < min(l_y)): |
3421 |
inserted = False
|
3422 |
for merge in overlap_merges: |
3423 |
if (rect1 in merge) and (rect2 in merge): |
3424 |
inserted = True
|
3425 |
break
|
3426 |
elif (rect1 in merge) and (rect2 not in merge): |
3427 |
merge.append(rect2) |
3428 |
inserted = True
|
3429 |
break
|
3430 |
elif (rect2 in merge) and (rect1 not in merge): |
3431 |
merge.append(rect1) |
3432 |
inserted = True
|
3433 |
break
|
3434 |
if not inserted: |
3435 |
overlap_merges.append([rect1, rect2]) |
3436 |
|
3437 |
for merge in overlap_merges: |
3438 |
for rect in merge: |
3439 |
if rect in symbols: |
3440 |
symbols.remove(rect) |
3441 |
else:
|
3442 |
pass
|
3443 |
#print(str(rect))
|
3444 |
|
3445 |
for merge in overlap_merges: |
3446 |
max_x, max_y, min_x, min_y = 0, 0, sys.maxsize, sys.maxsize |
3447 |
ratio = 0
|
3448 |
for rect in merge: |
3449 |
if rect[5] > ratio: |
3450 |
ratio = rect[5]
|
3451 |
|
3452 |
if rect[1] < min_x: |
3453 |
min_x = rect[1]
|
3454 |
if rect[1] + rect[3] > max_x: |
3455 |
max_x = rect[1] + rect[3] |
3456 |
if rect[2] < min_y: |
3457 |
min_y = rect[2]
|
3458 |
if rect[2] + rect[4] > max_y: |
3459 |
max_y = rect[2] + rect[4] |
3460 |
|
3461 |
rect = [rect[0], min_x, min_y, max_x - min_x, max_y - min_y, ratio]
|
3462 |
symbols.append(rect) |
3463 |
# up to here
|
3464 |
|
3465 |
# for single class test
|
3466 |
#for symbol in symbols:
|
3467 |
# symbol[0] = 'globe'
|
3468 |
|
3469 |
for targetSymbol in targetSymbols[2]: |
3470 |
symbolName = targetSymbol.getName() |
3471 |
symbolType = targetSymbol.getType() |
3472 |
symbolPath = targetSymbol.getPath() |
3473 |
symbolThreshold = targetSymbol.getThreshold() |
3474 |
symbolMinMatchCount = targetSymbol.getMinMatchCount() |
3475 |
isDetectOnOrigin = targetSymbol.getIsDetectOnOrigin() |
3476 |
symbolRotateCount = targetSymbol.getRotationCount() |
3477 |
symbolOcrOption = targetSymbol.getOcrOption() |
3478 |
isContainChild = targetSymbol.getIsContainChild() |
3479 |
baseSymbol = targetSymbol.getBaseSymbol() |
3480 |
additionalSymbol = targetSymbol.getAdditionalSymbol() |
3481 |
isExceptDetect = targetSymbol.getIsExceptDetect() |
3482 |
hasInstrumentLabel = targetSymbol.getHasInstrumentLabel() |
3483 |
text_area = targetSymbol.getText_area() |
3484 |
|
3485 |
'''
|
3486 |
# check if symbol file is target or not
|
3487 |
if isExceptDetect == 1:
|
3488 |
item = QListWidgetItem('{} file is not target'.format(symbolName))
|
3489 |
item.setBackground(QColor('green'))
|
3490 |
listWidget.addItem(item)
|
3491 |
continue
|
3492 |
'''
|
3493 |
|
3494 |
foundSymbolCount = 0
|
3495 |
|
3496 |
# check if symbol file exists
|
3497 |
if not os.path.isfile(symbolPath): |
3498 |
item = QListWidgetItem('{} file not found'.format(symbolName))
|
3499 |
item.setBackground(QColor('red'))
|
3500 |
listWidget.addItem(item) |
3501 |
continue
|
3502 |
# up to here
|
3503 |
|
3504 |
sym = cv2.imread(symbolPath, 1)
|
3505 |
symGray = Worker.cvtGrayImage(sym) |
3506 |
|
3507 |
configs = app_doc_data.getConfigs('Engine', 'Threshold') |
3508 |
threshold = int(configs[0].value) / 100 if configs else 0.25 |
3509 |
|
3510 |
for symbol in symbols: |
3511 |
if symbol[0] == symbolName: |
3512 |
searchedItemSp, symbolRotatedAngle, flipped, originalPoint, connectionPoint, sw, sh, score = \ |
3513 |
Worker.calculate_exact_position(area, symGray, targetSymbol, symbol, worker, threshold) |
3514 |
if not searchedItemSp: |
3515 |
continue
|
3516 |
|
3517 |
#hitRate = symbol[5]
|
3518 |
hitRate = score |
3519 |
|
3520 |
Worker.addSearchedSymbol(symbolName, symbolType, |
3521 |
searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, |
3522 |
hitRate, symbolRotatedAngle, |
3523 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, |
3524 |
isContainChild, |
3525 |
originalPoint, connectionPoint, baseSymbol, additionalSymbol, |
3526 |
isExceptDetect, |
3527 |
detectFlip=1 if flipped else 0, |
3528 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
3529 |
|
3530 |
return symbols
|
3531 |
'''
|
3532 |
@brief detect symbols on PID
|
3533 |
@history humkyung 2018.06.08 add parameteres for signal
|
3534 |
'''
|
3535 |
@staticmethod
|
3536 |
def detectSymbolsOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal): |
3537 |
res = [] |
3538 |
|
3539 |
if type(targetSymbols) is list: |
3540 |
for detailTarget in targetSymbols: |
3541 |
res.extend(Worker.detectSymbolOnPid(mainRes, detailTarget, listWidget, updateProgressSignal)) |
3542 |
else:
|
3543 |
res = Worker.detectSymbolOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal) |
3544 |
|
3545 |
return res
|
3546 |
|
3547 |
@staticmethod
|
3548 |
def convertDirectionCodeToValue(directionCode): |
3549 |
if directionCode == "UP": |
3550 |
return 0 |
3551 |
elif directionCode == "RIGHT": |
3552 |
return 1 |
3553 |
elif directionCode == "DOWN": |
3554 |
return 2 |
3555 |
elif directionCode == "LEFT": |
3556 |
return 3 |
3557 |
else:
|
3558 |
return -1 |
3559 |
|
3560 |
@staticmethod
|
3561 |
def convertValueToDirectionCode(value): |
3562 |
if value == 0: |
3563 |
return "UP" |
3564 |
elif value == 1: |
3565 |
return "RIGHT" |
3566 |
elif value == 2: |
3567 |
return "DOWN" |
3568 |
elif value == 3: |
3569 |
return "LEFT" |
3570 |
else:
|
3571 |
return "NONE" |
3572 |
|
3573 |
@staticmethod
|
3574 |
def drawFoundSymbolsOnCanvas(drawingPath, symbols, textInfos, listWidget): |
3575 |
"""draw found symbols and texts to image"""
|
3576 |
|
3577 |
global src
|
3578 |
global ocrCompletedSrc
|
3579 |
global canvas
|
3580 |
|
3581 |
app_doc_data = AppDocData.instance() |
3582 |
canvas = np.zeros(app_doc_data.imgSrc.shape, np.uint8) |
3583 |
canvas[::] = 255
|
3584 |
|
3585 |
try:
|
3586 |
project = app_doc_data.getCurrentProject() |
3587 |
|
3588 |
for symbol in symbols: |
3589 |
Worker.drawFoundSymbols(symbol, listWidget) |
3590 |
|
3591 |
for text in textInfos: |
3592 |
left = text.getX() |
3593 |
top = text.getY() |
3594 |
right = text.getX() + text.getW() |
3595 |
bottom = text.getY() + text.getH() |
3596 |
|
3597 |
canvas[top:bottom, left:right] = app_doc_data.imgSrc[top:bottom, left:right] |
3598 |
|
3599 |
#cv2.imwrite(os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(drawingPath)), canvas) for debug
|
3600 |
except Exception as ex: |
3601 |
from App import App |
3602 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
3603 |
sys.exc_info()[-1].tb_lineno)
|
3604 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
3605 |
|
3606 |
'''
|
3607 |
@history 2018.04.27 Jeongwoo Remove Tesseract Log on listWidget
|
3608 |
2018.05.04 Jeongwoo Change method to OCR with tesseract_ocr_module.py
|
3609 |
2018.05.09 Jeongwoo Add global variable textInfoList, Remove text in symbol and Add tesseract result text
|
3610 |
2018.05.10 Jeongwoo Remove not used if-statement
|
3611 |
2018.06.19 Jeongwoo When detect text in symbol, use getTextAreaInfo() and Tesseract
|
3612 |
2018.06.21 Jeongwoo Add if-statement for way to detect text by Type A
|
3613 |
'''
|
3614 |
|
3615 |
@staticmethod
|
3616 |
def drawFoundSymbols(symbol, listWidget): |
3617 |
global src
|
3618 |
global canvas
|
3619 |
#global WHITE_LIST_CHARS
|
3620 |
global searchedSymbolList
|
3621 |
global textInfoList
|
3622 |
|
3623 |
# symbolId = symbol.getId()
|
3624 |
symbolPath = symbol.getPath() |
3625 |
symbolSp = symbol.getSp() |
3626 |
symbolRotatedAngle = symbol.getRotatedAngle() |
3627 |
|
3628 |
symImg = cv2.cvtColor(cv2.imread(symbolPath, 1), cv2.COLOR_BGR2GRAY)
|
3629 |
if symbol.getDetectFlip() is 1: |
3630 |
symImg = cv2.flip(symImg, 1)
|
3631 |
for i in range(symbolRotatedAngle // 90): |
3632 |
symImg = cv2.rotate(symImg, cv2.ROTATE_90_COUNTERCLOCKWISE) |
3633 |
|
3634 |
w, h = symImg.shape[::-1]
|
3635 |
canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w] = cv2.bitwise_and( |
3636 |
canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w], symImg) |
3637 |
|
3638 |
@staticmethod
|
3639 |
def remove_equipment_package(scene, area): |
3640 |
""" remove equipment package area from drawing image """
|
3641 |
for item in scene.items(): |
3642 |
if issubclass(type(item), QEngineeringVendorItem) and item.pack_type =='Equipment Package': |
3643 |
points = [] |
3644 |
for conn in item.connectors: |
3645 |
points.append([round(conn.center()[0] - area.x), round(conn.center()[1] - area.y)]) |
3646 |
|
3647 |
points = np.array(points, np.int32) |
3648 |
cv2.fillConvexPoly(area.img, points, 255)
|
3649 |
|
3650 |
@staticmethod
|
3651 |
def remove_detected_symbol_image(sym, imgSrc, lock=True): |
3652 |
"""remove detected symbol image from drawing image"""
|
3653 |
if lock:
|
3654 |
global threadLock
|
3655 |
|
3656 |
try:
|
3657 |
path = sym.getPath() |
3658 |
sp = (int(sym.getSp()[0]), int(sym.getSp()[1])) |
3659 |
sw = int(sym.getWidth())
|
3660 |
sh = int(sym.getHeight())
|
3661 |
angle = int(sym.getRotatedAngle())
|
3662 |
# get symbol image
|
3663 |
sym_img = cv2.imread(path) |
3664 |
sym_img = cv2.cvtColor(sym_img, cv2.COLOR_BGR2GRAY) |
3665 |
# symImg = cv2.threshold(Worker.cvtGrayImage(symImg), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
|
3666 |
if sym.getDetectFlip() is 1: |
3667 |
sym_img = cv2.flip(sym_img, 1)
|
3668 |
|
3669 |
for i in range(angle // 90): |
3670 |
sym_img = cv2.rotate(sym_img, cv2.ROTATE_90_COUNTERCLOCKWISE) |
3671 |
# up to here
|
3672 |
|
3673 |
if lock:
|
3674 |
threadLock.acquire() |
3675 |
temp = imgSrc[sp[1]:sp[1] + sh, sp[0]:sp[0] + sw] |
3676 |
sym_img = cv2.erode(sym_img, np.ones((5, 5), np.uint8)) |
3677 |
mask = cv2.bitwise_or(temp, sym_img) |
3678 |
imgXOR = cv2.bitwise_xor(temp, mask) |
3679 |
imgSrc[sp[1]:sp[1] + sh, sp[0]:sp[0] + sw] = cv2.bitwise_not(imgXOR) |
3680 |
except Exception as ex: |
3681 |
from App import App |
3682 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
3683 |
sys.exc_info()[-1].tb_lineno)
|
3684 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
3685 |
finally:
|
3686 |
if lock:
|
3687 |
threadLock.release() |
3688 |
|
3689 |
'''
|
3690 |
@brief get difference between given original and recognized image
|
3691 |
@author humkyung
|
3692 |
@date 2018.06.11
|
3693 |
'''
|
3694 |
|
3695 |
@staticmethod
|
3696 |
def getDifference(orgImagePath, recImagePath): |
3697 |
import re |
3698 |
|
3699 |
global ocrCompletedSrc
|
3700 |
global textInfoList
|
3701 |
|
3702 |
try:
|
3703 |
app_doc_data = AppDocData.instance() |
3704 |
imgOriginal = app_doc_data.imgSrc |
3705 |
|
3706 |
# remove not drawing area
|
3707 |
configs = app_doc_data.getConfigs('{} Equipment Desc Area'.format(app_doc_data.imgName))
|
3708 |
for config in configs: |
3709 |
found = re.findall('\\d+', config.value)
|
3710 |
if len(found) == 4: |
3711 |
cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])), |
3712 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1) |
3713 |
|
3714 |
configs = app_doc_data.getConfigs('{} Typical Area'.format(app_doc_data.imgName))
|
3715 |
for config in configs: |
3716 |
found = re.findall('\\d+', config.value)
|
3717 |
if len(found) == 4: |
3718 |
cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])), |
3719 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1) |
3720 |
|
3721 |
noteArea = app_doc_data.getArea('Note')
|
3722 |
if noteArea is not None: |
3723 |
noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height), |
3724 |
round(noteArea.x):round(noteArea.x + noteArea.width)].copy() |
3725 |
cv2.rectangle(imgOriginal, (round(noteArea.x), round(noteArea.y)), |
3726 |
(round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1) |
3727 |
# up to here
|
3728 |
|
3729 |
"""
|
3730 |
if os.path.isfile(orgImagePath) and os.path.isfile(recImagePath):
|
3731 |
imgOriginal = \
|
3732 |
cv2.threshold(Worker.cvtGrayImage(cv2.imread(orgImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1]
|
3733 |
|
3734 |
configs = app_doc_data.getConfigs('Filter', 'DilateSize')
|
3735 |
if 1 == len(configs) and int(configs[0].value) is not 0:
|
3736 |
size = int(configs[0].value)
|
3737 |
kernel = np.ones((size, size), np.uint8)
|
3738 |
imgOriginal = cv2.erode(imgOriginal, kernel, iterations=1)
|
3739 |
|
3740 |
# remove not drawing area
|
3741 |
configs = app_doc_data.getConfigs('{} Equipment Desc Area'.format(app_doc_data.imgName))
|
3742 |
for config in configs:
|
3743 |
found = re.findall('\\d+', config.value)
|
3744 |
if len(found) == 4:
|
3745 |
cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
|
3746 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
|
3747 |
|
3748 |
configs = app_doc_data.getConfigs('{} Typical Area'.format(app_doc_data.imgName))
|
3749 |
for config in configs:
|
3750 |
found = re.findall('\\d+', config.value)
|
3751 |
if len(found) == 4:
|
3752 |
cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
|
3753 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
|
3754 |
|
3755 |
noteArea = app_doc_data.getArea('Note')
|
3756 |
if noteArea is not None:
|
3757 |
noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height),
|
3758 |
round(noteArea.x):round(noteArea.x + noteArea.width)].copy()
|
3759 |
cv2.rectangle(imgOriginal, (round(noteArea.x), round(noteArea.y)),
|
3760 |
(round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1)
|
3761 |
# up to here
|
3762 |
|
3763 |
imgRecognized = \
|
3764 |
cv2.threshold(Worker.cvtGrayImage(cv2.imread(recImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1]
|
3765 |
|
3766 |
imgDiff = np.ones(imgOriginal.shape, np.uint8) * 255
|
3767 |
|
3768 |
area = app_doc_data.getArea('Drawing')
|
3769 |
if area is not None:
|
3770 |
x = round(area.x)
|
3771 |
y = round(area.y)
|
3772 |
width = round(area.width)
|
3773 |
height = round(area.height)
|
3774 |
imgNotOper = cv2.bitwise_not(imgRecognized[y:y + height, x:x + width])
|
3775 |
imgDiff[y:y + height, x:x + width] = cv2.bitwise_xor(imgOriginal[y:y + height, x:x + width],
|
3776 |
imgNotOper)
|
3777 |
|
3778 |
# remove noise
|
3779 |
imgDiff = cv2.dilate(imgDiff, np.ones((2, 2), np.uint8))
|
3780 |
|
3781 |
project = app_doc_data.getCurrentProject()
|
3782 |
cv2.imwrite(os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(orgImagePath)), imgDiff)
|
3783 |
"""
|
3784 |
except Exception as ex: |
3785 |
from App import App |
3786 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
3787 |
sys.exc_info()[-1].tb_lineno)
|
3788 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
3789 |
|
3790 |
|
3791 |
'''
|
3792 |
@history 2018.05.25 Jeongwoo Add pyqtSignal(svgItemClicked, itemRemoved)
|
3793 |
'''
|
3794 |
|
3795 |
|
3796 |
class QRecognitionDialog(QDialog): |
3797 |
svgItemClicked = pyqtSignal(SymbolSvgItem) |
3798 |
itemRemoved = pyqtSignal(QGraphicsItem) |
3799 |
unBlockEvent = pyqtSignal() |
3800 |
|
3801 |
'''
|
3802 |
@history 2018.05.25 Jeongwoo Add parameter and initialize / Connect recognizeButton signal and slot
|
3803 |
2018.05.29 Jeongwoo Chnage parameter(graphicsView → parent) and Get graphicsView from parent
|
3804 |
'''
|
3805 |
|
3806 |
def __init__(self, parent, drawings): # Parent is MainWindow |
3807 |
from AppDocData import AppDocData |
3808 |
|
3809 |
QDialog.__init__(self, parent)
|
3810 |
|
3811 |
self.parent = parent
|
3812 |
self._scene = QGraphicsScene()
|
3813 |
self._text_scene = QGraphicsScene()
|
3814 |
self._line_scene = QGraphicsScene()
|
3815 |
self.drawings = drawings
|
3816 |
self.xmlPath = None |
3817 |
self.ui = Recognition_UI.Ui_Recognition()
|
3818 |
self.ui.setupUi(self) |
3819 |
self.isTreated = False |
3820 |
|
3821 |
self.ui.buttonBox.setEnabled(True) |
3822 |
self.ui.listWidget.model().rowsInserted.connect(self.rowInserted) |
3823 |
self.ui.recognizeButton.clicked.connect(self.recognizeButtonClicked) |
3824 |
self.ui.lineCheckBox.stateChanged.connect(self.checkBoxChanged) |
3825 |
self.ui.checkBoxSymbol.stateChanged.connect(self.checkBoxChanged) |
3826 |
self.ui.checkBoxText.stateChanged.connect(self.checkBoxChanged) |
3827 |
self.ui.checkBoxTraining.stateChanged.connect(self.checkBoxChanged) |
3828 |
self.isAccepted = False |
3829 |
|
3830 |
appDocData = AppDocData.instance() |
3831 |
configs = appDocData.getAppConfigs('app', 'mode') |
3832 |
if configs and 1 == len(configs) and 'advanced' == configs[0].value: |
3833 |
pass
|
3834 |
else:
|
3835 |
self.ui.checkBoxTraining.setVisible(False) |
3836 |
|
3837 |
if False: |
3838 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
3839 |
self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
|
3840 |
self.ui.checkBoxText.setCheckState(Qt.Unchecked)
|
3841 |
else:
|
3842 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
3843 |
self.ui.lineCheckBox.setCheckState(Qt.Checked)
|
3844 |
self.ui.checkBoxText.setCheckState(Qt.Checked)
|
3845 |
|
3846 |
if len(self.drawings) != 1: |
3847 |
self.ui.checkBoxOCRUnknown.hide()
|
3848 |
|
3849 |
self.ui.progressBarBatch.setFormat('{}/{}'.format('0', str(len(self.drawings)))) |
3850 |
|
3851 |
def checkBoxChanged(self, checkState): |
3852 |
'''
|
3853 |
@brief line and text cannot be reocognized alone
|
3854 |
@author euisung
|
3855 |
@date 2019.05.14
|
3856 |
'''
|
3857 |
if self.ui.checkBoxTraining.isChecked(): |
3858 |
self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
|
3859 |
self.ui.checkBoxText.setCheckState(Qt.Unchecked)
|
3860 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
3861 |
self.ui.lineCheckBox.setEnabled(False) |
3862 |
self.ui.checkBoxText.setEnabled(False) |
3863 |
self.ui.checkBoxSymbol.setEnabled(False) |
3864 |
self.ui.recognizeButton.setText('Extract') |
3865 |
return
|
3866 |
else:
|
3867 |
self.ui.lineCheckBox.setEnabled(True) |
3868 |
self.ui.checkBoxText.setEnabled(True) |
3869 |
self.ui.checkBoxSymbol.setEnabled(True) |
3870 |
self.ui.recognizeButton.setText('Recognize') |
3871 |
|
3872 |
if checkState is int(Qt.Checked): |
3873 |
if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked(): |
3874 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
3875 |
elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked(): |
3876 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
3877 |
elif checkState is int(Qt.Unchecked): |
3878 |
if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked(): |
3879 |
self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
|
3880 |
elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked(): |
3881 |
self.ui.checkBoxText.setCheckState(Qt.Unchecked)
|
3882 |
|
3883 |
if self.ui.checkBoxSymbol.isChecked() and self.ui.checkBoxText.isChecked() and self.ui.lineCheckBox.isChecked() and len(self.drawings) == 1: |
3884 |
self.ui.checkBoxOCRUnknown.setHidden(False) |
3885 |
else:
|
3886 |
self.ui.checkBoxOCRUnknown.setHidden(True) |
3887 |
self.ui.checkBoxOCRUnknown.setCheckState(Qt.Unchecked)
|
3888 |
|
3889 |
'''
|
3890 |
@brief QListWidget Row Inserted Listener
|
3891 |
Whenever row inserted, scroll to bottom
|
3892 |
@author Jeongwoo
|
3893 |
@date 18.04.12
|
3894 |
'''
|
3895 |
|
3896 |
def rowInserted(self, item): |
3897 |
self.ui.listWidget.scrollToBottom()
|
3898 |
|
3899 |
def accept(self): |
3900 |
self.isAccepted = True |
3901 |
QDialog.accept(self)
|
3902 |
|
3903 |
def recognizeButtonClicked(self, event): |
3904 |
"""
|
3905 |
@brief start recognization
|
3906 |
@author humkyung
|
3907 |
@history humkyung 2018.10.05 clear imgSrc before recognizing
|
3908 |
"""
|
3909 |
if self.ui.checkBoxSymbol.isChecked(): # or self.ui.checkBoxText.isChecked() or self.ui.lineCheckBox.isChecked(): |
3910 |
appDocData = AppDocData.instance() |
3911 |
appDocData.imgSrc = None
|
3912 |
area = appDocData.getArea('Drawing')
|
3913 |
if area is None: |
3914 |
QMessageBox.about(self, self.tr("Notice"), self.tr('Please select drawing area.')) |
3915 |
return
|
3916 |
|
3917 |
self.ui.recognizeButton.setEnabled(False) |
3918 |
self.ui.buttonBox.setEnabled(False) |
3919 |
self.ui.progressBar.setValue(0) |
3920 |
self.ui.listWidget.addItem("Initializing...") |
3921 |
self.startThread()
|
3922 |
|
3923 |
self.isTreated = True |
3924 |
|
3925 |
'''
|
3926 |
@brief add item to list widget
|
3927 |
@author humkyung
|
3928 |
@date 2018.06.19
|
3929 |
'''
|
3930 |
|
3931 |
def addListItem(self, item): |
3932 |
self.ui.listWidget.addItem(item)
|
3933 |
|
3934 |
'''
|
3935 |
@brief update progressbar with given value
|
3936 |
@author humkyung
|
3937 |
@date 2018.06.08
|
3938 |
'''
|
3939 |
|
3940 |
def updateProgress(self, maxValue, image_path): |
3941 |
self.ui.progressBar.setMaximum(maxValue)
|
3942 |
self.ui.progressBar.setValue(self.ui.progressBar.value() + 1) |
3943 |
|
3944 |
if image_path is not None and os.path.isfile(image_path): |
3945 |
self.ui.labelImage.setPixmap(QPixmap(image_path))
|
3946 |
elif image_path is not None: |
3947 |
self.ui.labelImage.setText(image_path)
|
3948 |
|
3949 |
def updateBatchProgress(self, maxValue, weight): |
3950 |
"""update batch progressbar"""
|
3951 |
self.ui.progressBarBatch.setMaximum(maxValue * 5) |
3952 |
value = self.ui.progressBarBatch.value() + weight
|
3953 |
self.ui.progressBarBatch.setValue(value)
|
3954 |
self.ui.progressBarBatch.setFormat('{}/{}'.format(str(int(value / 5)), str(maxValue))) |
3955 |
|
3956 |
'''
|
3957 |
@brief display title
|
3958 |
@author humkyung
|
3959 |
@date 2018.08.20
|
3960 |
'''
|
3961 |
|
3962 |
def displayTitle(self, title): |
3963 |
self.ui.labelTitle.setText(title)
|
3964 |
|
3965 |
def startThread(self): |
3966 |
"""start thread"""
|
3967 |
from PyQt5 import QtWidgets |
3968 |
from App import App |
3969 |
|
3970 |
self.ui.buttonBox.setDisabled(True) |
3971 |
|
3972 |
self.mutex = QMutex()
|
3973 |
self.cond = QWaitCondition()
|
3974 |
|
3975 |
# 1 - create Worker and Thread inside the Form
|
3976 |
self.obj = Worker(self.mutex, self.cond) # no parent! |
3977 |
self.obj.symbol_time = None |
3978 |
self.obj.text_time = None |
3979 |
self.obj.drawings = self.drawings |
3980 |
self.obj.listWidget = self.ui.listWidget |
3981 |
self.obj.scene = self._scene |
3982 |
self.obj.text_scene = self._text_scene |
3983 |
self.obj.line_scene = self._line_scene |
3984 |
self.obj.scene._end = False # for waiting each drawing finished |
3985 |
self.obj.isSymbolChecked = self.ui.checkBoxSymbol.isChecked() |
3986 |
self.obj.isTextChecked = self.ui.checkBoxText.isChecked() |
3987 |
self.obj.isLineChecked = self.ui.lineCheckBox.isChecked() |
3988 |
self.obj.isTrainingChecked = self.ui.checkBoxTraining.isChecked() |
3989 |
self.thread = QThread() # no parent! |
3990 |
|
3991 |
# 2 - Move the Worker object to the Thread object
|
3992 |
self.obj.moveToThread(self.thread) |
3993 |
|
3994 |
# 3 - Connect Worker Signals to the Thread slots
|
3995 |
self.obj.finished.connect(self.thread.quit) |
3996 |
self.obj.displayMessage.connect(self.addListItem) |
3997 |
self.obj.updateProgress.connect(self.updateProgress) |
3998 |
self.obj.updateBatchProgress.connect(self.updateBatchProgress) |
3999 |
self.obj.displayLog.connect(App.mainWnd().addMessage)
|
4000 |
self.obj.displayTitle.connect(self.displayTitle) |
4001 |
self.obj.add_detected_items_to_scene.connect(self.add_detected_items_to_scene) |
4002 |
self.obj.save_scene.connect(self.save_scene) |
4003 |
self.obj.preset_execute.connect(self.preset_execute) |
4004 |
self.obj.item_remove.connect(self.item_remove) |
4005 |
self.obj.add_predata_to_scene.connect(self.add_predata_to_scene) |
4006 |
self.obj.clear_scene.connect(self.clear_scene) |
4007 |
|
4008 |
# 4 - Connect Thread started signal to Worker operational slot method
|
4009 |
self.thread.started.connect(self.obj.procCounter) |
4010 |
|
4011 |
# 5 - Thread finished signal will close the app if you want!
|
4012 |
self.thread.finished.connect(self.dlgExit) |
4013 |
|
4014 |
# 6 - Start the thread
|
4015 |
self.thread.start()
|
4016 |
|
4017 |
self.tmStart = timeit.default_timer()
|
4018 |
|
4019 |
'''
|
4020 |
@brief set buttonbox's enabled flag
|
4021 |
@history 2018.05.25 Jeongwoo Moved from MainWindow
|
4022 |
2018.06.14 Jeongwoo Change sentence order
|
4023 |
2018.11.26 euisung add drawing part
|
4024 |
2018.11.26 euising move save and unknown part into executerecognition
|
4025 |
'''
|
4026 |
|
4027 |
def dlgExit(self): |
4028 |
import timeit |
4029 |
|
4030 |
try:
|
4031 |
self.ui.buttonBox.setEnabled(True) |
4032 |
|
4033 |
self.ui.progressBar.setValue(self.ui.progressBar.maximum()) |
4034 |
except Exception as ex: |
4035 |
from App import App |
4036 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
4037 |
sys.exc_info()[-1].tb_lineno)
|
4038 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
4039 |
finally:
|
4040 |
self.tmStop = timeit.default_timer()
|
4041 |
seconds = self.tmStop - self.tmStart |
4042 |
self.ui.listWidget.addItem("\nRunning Time(total) : {} min".format(str(round(seconds / 60, 1)))) |
4043 |
if self.obj.symbol_time: |
4044 |
self.ui.listWidget.addItem("Running Time(symbol) : {} min".format(str(round(self.obj.symbol_time / 60, 1)))) |
4045 |
if self.obj.text_time: |
4046 |
self.ui.listWidget.addItem("Running Time(text) : {} min".format(str(round(self.obj.text_time / 60, 1)))) |
4047 |
self.ui.listWidget.addItem("\n") |
4048 |
|
4049 |
'''
|
4050 |
@history 2018.05.29 Jeongwoo Call parent's method
|
4051 |
2018.05.30 Jeongwoo Change method name
|
4052 |
2018.06.09 humkyung set progressbar value to maximum
|
4053 |
2018.11.12 euisung add title block properties
|
4054 |
2018.11.29 euisung no more used
|
4055 |
'''
|
4056 |
|
4057 |
def drawDetectedItems(self, symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList, loop): |
4058 |
try:
|
4059 |
self.ui.progressBar.setValue(self.ui.progressBar.maximum()) |
4060 |
self.parent.drawDetectedItems(symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList)
|
4061 |
finally:
|
4062 |
loop.quit() |
4063 |
|
4064 |
'''
|
4065 |
@brief draw detected lines
|
4066 |
@author humkyung
|
4067 |
@date 2018.08.23
|
4068 |
@history 2018.11.27 euisung no more used
|
4069 |
'''
|
4070 |
|
4071 |
def drawDetectedLines(self, lineList, loop): |
4072 |
try:
|
4073 |
self.parent.drawDetectedLines(lineList, self.obj) |
4074 |
finally:
|
4075 |
loop.quit() |
4076 |
|
4077 |
'''
|
4078 |
@brief draw detected lines
|
4079 |
@author euisung
|
4080 |
@date 2018.11.27
|
4081 |
@history 2018.11.27 euisung no more used
|
4082 |
'''
|
4083 |
|
4084 |
def drawUnknownItems(self, path, loop): |
4085 |
try:
|
4086 |
self.parent.drawUnknownItems(path)
|
4087 |
finally:
|
4088 |
loop.quit() |
4089 |
|
4090 |
def add_predata_to_scene(self, drawing, scene, symbol: bool, text: bool, line: bool, unknown: bool, package: bool) \ |
4091 |
-> None:
|
4092 |
"""add predata to scene"""
|
4093 |
from LoadCommand import LoadCommand |
4094 |
from App import App |
4095 |
|
4096 |
try:
|
4097 |
cmd = LoadCommand() |
4098 |
cmd.display_message.connect(App.mainWnd().onAddMessage) |
4099 |
cmd.execute((drawing, scene), symbol=symbol, text=text, line=line, unknown=unknown, |
4100 |
package=package, update=False)
|
4101 |
except Exception as ex: |
4102 |
from App import App |
4103 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
4104 |
f"{sys.exc_info()[-1].tb_lineno}"
|
4105 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
4106 |
finally:
|
4107 |
self.cond.wakeAll()
|
4108 |
|
4109 |
def clear_scene(self, scene1, scene2, scene3) -> None: |
4110 |
"""clear scenes"""
|
4111 |
|
4112 |
try:
|
4113 |
scene1.clear() |
4114 |
scene2.clear() |
4115 |
scene3.clear() |
4116 |
|
4117 |
except Exception as ex: |
4118 |
from App import App |
4119 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
4120 |
f"{sys.exc_info()[-1].tb_lineno}"
|
4121 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
4122 |
finally:
|
4123 |
self.cond.wakeAll()
|
4124 |
|
4125 |
def item_remove(self, items): |
4126 |
""" remove useless items """
|
4127 |
from App import App |
4128 |
|
4129 |
try:
|
4130 |
for item in items: |
4131 |
App.mainWnd().itemRemoved(item) |
4132 |
|
4133 |
except Exception as ex: |
4134 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
4135 |
sys.exc_info()[-1].tb_lineno)
|
4136 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
4137 |
finally:
|
4138 |
self.cond.wakeAll()
|
4139 |
|
4140 |
def preset_execute(self, scene, find_symbol, replace_symbol, replace_action, condition): |
4141 |
""" run preset """
|
4142 |
from App import App |
4143 |
|
4144 |
try:
|
4145 |
from ReplaceInsertCommand import ReplaceInsertCommand |
4146 |
|
4147 |
cmd = ReplaceInsertCommand() |
4148 |
cmd.display_message.connect(App.mainWnd().onAddMessage) |
4149 |
cmd.execute(scene, find_symbol, replace_symbol, replace_action, condition) |
4150 |
except Exception as ex: |
4151 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
4152 |
f"{sys.exc_info()[-1].tb_lineno}"
|
4153 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
4154 |
finally:
|
4155 |
self.cond.wakeAll()
|
4156 |
|
4157 |
def save_scene(self, scene): |
4158 |
""" save scene """
|
4159 |
from App import App |
4160 |
|
4161 |
try:
|
4162 |
from SaveWorkCommand import SaveWorkCommand |
4163 |
|
4164 |
save = SaveWorkCommand(scene) |
4165 |
save.run() |
4166 |
#SaveWorkCommand.save_to_database()
|
4167 |
#SaveWorkCommand.save_to_xml()
|
4168 |
except Exception as ex: |
4169 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
4170 |
sys.exc_info()[-1].tb_lineno)
|
4171 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
4172 |
finally:
|
4173 |
self.cond.wakeAll()
|
4174 |
|
4175 |
def add_detected_items_to_scene(self, scene, flanges) -> None: |
4176 |
"""add detected items to scene"""
|
4177 |
from App import App |
4178 |
|
4179 |
app_doc_data = AppDocData.instance() |
4180 |
|
4181 |
try:
|
4182 |
for item in scene.items(): |
4183 |
if issubclass(type(item), QEngineeringVendorItem): |
4184 |
app_doc_data.allItems.append(item) |
4185 |
|
4186 |
# symbol
|
4187 |
for symbol in app_doc_data.symbols: |
4188 |
if issubclass(type(symbol), SymbolSvgItem): |
4189 |
symbol.transfer.onRemoved.connect(App.mainWnd().itemRemoved) |
4190 |
symbol.addSvgItemToScene(scene) |
4191 |
else:
|
4192 |
scene.addItem(symbol) |
4193 |
|
4194 |
# text
|
4195 |
for text in app_doc_data.texts: |
4196 |
text.addTextItemToScene(scene) |
4197 |
|
4198 |
#for lineNo in app_doc_data.tracerLineNos:
|
4199 |
# lineNo.addTextItemToScene(scene)
|
4200 |
|
4201 |
# remove lines which is located inside symbol
|
4202 |
for symbol in app_doc_data.symbols: |
4203 |
rect = symbol.sceneBoundingRect() |
4204 |
rect.adjust(int(rect.width() / -10), int(rect.height() / -10), int(rect.width() / 10), int(rect.height() / 10)) |
4205 |
matches = [line for line in app_doc_data.lines if rect.contains(line.line().p1()) and |
4206 |
rect.contains(line.line().p2())]# and not line.has_connection]
|
4207 |
for line in matches: |
4208 |
app_doc_data.allItems.remove(line) |
4209 |
app_doc_data.lines.remove(line) |
4210 |
# up to here
|
4211 |
|
4212 |
for line in app_doc_data.lines: |
4213 |
scene.addItem(line) |
4214 |
# line.transfer.onRemoved.connect(self.itemRemoved)
|
4215 |
for conn in line.connectors: |
4216 |
conn.transfer.onPosChanged.connect(line.onConnectorPosChaned) |
4217 |
|
4218 |
# insert flange
|
4219 |
configs = app_doc_data.getConfigs('Project', 'Operation') |
4220 |
instrument = int(configs[0].value) if configs else 1 |
4221 |
if instrument == 1: |
4222 |
configs = app_doc_data.getConfigs('Default', 'Flange') |
4223 |
flange_name = configs[0].value if 1 == len(configs) else 'flange' |
4224 |
for flange in flanges[0]: |
4225 |
svg = QtImageViewer.createSymbolObject(flange_name) |
4226 |
if svg:
|
4227 |
QtImageViewer.matchSymbolToLine(scene, svg, QPointF(flange[0], flange[1]), strict=True, auto=True) |
4228 |
|
4229 |
configs = app_doc_data.getConfigs('Default', 'Blind') |
4230 |
flange_name = configs[0].value if 1 == len(configs) else 'blind flange' |
4231 |
for flange in flanges[1]: |
4232 |
svg = QtImageViewer.createSymbolObject(flange_name) |
4233 |
if svg:
|
4234 |
QtImageViewer.matchSymbolToLine(scene, svg, QPointF(flange[0], flange[1]), strict=False, auto=True) |
4235 |
|
4236 |
for unknown in app_doc_data.unknowns + app_doc_data.lineIndicators: |
4237 |
scene.addItem(unknown) |
4238 |
|
4239 |
except Exception as ex: |
4240 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
4241 |
sys.exc_info()[-1].tb_lineno)
|
4242 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
4243 |
finally:
|
4244 |
self.cond.wakeAll()
|
4245 |
scene._end = True
|