hytos / DTI_PID / DTI_PID / RecognitionDialog.py @ f3f9d47e
이력 | 보기 | 이력해설 | 다운로드 (133 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 |
from PyQt5.QtCore import * |
11 |
from PyQt5.QtGui import * |
12 |
from PyQt5.QtWidgets import * |
13 |
import Recognition_UI |
14 | |
15 |
import concurrent.futures as futures |
16 | |
17 |
from AppDocData import * |
18 |
from SymbolSvgItem import SymbolSvgItem |
19 |
from EngineeringTextItem import QEngineeringTextItem |
20 |
from EngineeringUnknownItem import QEngineeringUnknownItem |
21 |
from EngineeringErrorItem import QEngineeringErrorItem |
22 |
from EngineeringEndBreakItem import QEngineeringEndBreakItem |
23 |
from EngineeringLineItem import QEngineeringLineItem |
24 |
from EngineeringTextItem import QEngineeringTextItem |
25 |
from EngineeringLineNoTextItem import QEngineeringLineNoTextItem |
26 |
from GraphicsBoundingBoxItem import QGraphicsBoundingBoxItem |
27 |
from QEngineeringOPCItem import QEngineeringOPCItem |
28 |
from LineDetector import LineDetector |
29 |
from symbol import Symbol |
30 |
from Drawing import Drawing |
31 | |
32 |
from MainWindow import MainWindow |
33 | |
34 |
# region Symbol Image path List for test
|
35 |
targetSymbolList = [] |
36 |
# endregion
|
37 | |
38 |
# region Global variables
|
39 |
searchedSymbolList = [] |
40 |
textInfoList = [] |
41 | |
42 |
src = [] |
43 | |
44 |
ocrCompletedSrc = [] |
45 |
afterDenoising = [] |
46 |
canvas = [] |
47 | |
48 |
#WHITE_LIST_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-"
|
49 | |
50 |
MIN_TEXT_SIZE = 10
|
51 | |
52 |
THREAD_MAX_WORKER = os.cpu_count() |
53 |
threadLock = threading.Lock() |
54 | |
55 |
ACCEPT_OVERLAY_AREA = 20
|
56 |
# endregion
|
57 | |
58 |
'''
|
59 |
@history 2018.05.25 Jeongwoo Add pyqtSignal(recognizeLine, loadRecognitionResult)
|
60 |
'''
|
61 | |
62 | |
63 |
class Worker(QObject): |
64 |
from PyQt5.QtCore import QThread |
65 |
from PyQt5.QtCore import QTranslator |
66 |
from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout, QListWidget |
67 |
from QtImageViewer import QtImageViewer |
68 |
import sys |
69 | |
70 |
'''
|
71 |
@history 2018.05.30 Jeongwoo Remove parameter on recognizeLine signal / Change signal name (drawDetectedItems)
|
72 |
@history humkyung 2018.06.08 add signal for progressbar
|
73 |
@history euisung 2018.11.27 add signal for unknown items
|
74 |
'''
|
75 |
finished = pyqtSignal() |
76 |
intReady = pyqtSignal(int)
|
77 |
displayTitle = pyqtSignal(str)
|
78 |
displayMessage = pyqtSignal(QListWidgetItem) |
79 |
updateProgress = pyqtSignal(int, str) |
80 |
updateBatchProgress = pyqtSignal(int, int) |
81 |
displayLog = pyqtSignal(MessageType, str)
|
82 |
add_detected_items_to_scene = pyqtSignal(QGraphicsScene) |
83 |
add_predata_to_scene = pyqtSignal(Drawing, QGraphicsScene, bool, bool, bool, bool, bool) |
84 |
clear_scene = pyqtSignal(list)
|
85 | |
86 |
def __init__(self, mutex, cond): |
87 |
super(Worker, self).__init__() |
88 |
self.mutex = mutex
|
89 |
self.cond = cond
|
90 | |
91 |
'''
|
92 |
@history 2018.05.25 Jeongwoo Add if-statements by isSymbolTextChecked and isLineChecked variable
|
93 |
2018.05.28 Jeongwoo Add Parameter 'xmlPath[0]'
|
94 |
2018.05.30 Jeongwoo Remove import recognizeLine and self.xmlPath and remove parameter on recognizeLine.emit() (xmlPath)
|
95 |
Change signal name (drawDetectedItems)
|
96 |
'''
|
97 | |
98 |
def procCounter(self): # A slot takes no params |
99 |
try:
|
100 |
self.mutex.lock()
|
101 |
if self.isSymbolChecked or self.isTrainingChecked: |
102 |
Worker.executeRecognition(self.drawings, self.listWidget, self.isLineChecked, self) |
103 |
except Exception as ex: |
104 |
from App import App |
105 |
from AppDocData import MessageType |
106 | |
107 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
108 |
f"{sys.exc_info()[-1].tb_lineno}"
|
109 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
110 |
self.displayLog.emit(MessageType.Error, message)
|
111 |
except:
|
112 |
(_type, value, traceback) = sys.exc_info() |
113 |
sys.excepthook(_type, value, traceback) |
114 |
finally:
|
115 |
self.mutex.unlock()
|
116 |
self.finished.emit()
|
117 | |
118 |
'''
|
119 |
@brief remove small objects from given image
|
120 |
@author humkyung
|
121 |
@date 2018.04.26
|
122 |
@history 2018.05.25 Jeongwoo Moved from MainWindow
|
123 |
'''
|
124 | |
125 |
@staticmethod
|
126 |
def remove_small_objects(img, minArea=None, maxArea=None): |
127 |
try:
|
128 |
image = img.copy() |
129 | |
130 |
if not minArea or not maxArea: |
131 |
app_doc_data = AppDocData.instance() |
132 |
configs = app_doc_data.getConfigs('Small Object Size', 'Min Area') |
133 |
minArea = int(configs[0].value) if 1 == len(configs) else 20 |
134 |
configs = app_doc_data.getConfigs('Small Object Size', 'Max Area') |
135 |
maxArea = int(configs[0].value) if 1 == len(configs) else 50 |
136 | |
137 |
## try to convert grayscale to binary
|
138 |
#image = cv2.threshold(image, 200, 255, cv2.THRESH_BINARY)[1]
|
139 | |
140 |
contours, _ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) |
141 |
selectedContours = [] |
142 |
for contour in contours: |
143 |
area = cv2.contourArea(contour) |
144 |
if minArea < area < maxArea:
|
145 |
selectedContours.append(contour) |
146 | |
147 |
# draw contour with white color
|
148 |
cv2.drawContours(image, selectedContours, -1, (255, 255, 255), -1) |
149 |
except Exception as ex: |
150 |
from App import App |
151 | |
152 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
153 |
f"{sys.exc_info()[-1].tb_lineno}"
|
154 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
155 | |
156 |
return image
|
157 | |
158 |
'''
|
159 |
@brief arrange line's position
|
160 |
@author humkyung
|
161 |
@date 2018.07.04
|
162 |
'''
|
163 | |
164 |
@staticmethod
|
165 |
def arrangeLinePosition(lines, symbols, listWidget): |
166 |
try:
|
167 |
listWidget.addItem('Apply flow direction')
|
168 |
pool = [line for line in lines if line.flowMark is not None] |
169 |
visited = [] |
170 |
visited.extend(pool) |
171 |
while len(pool) > 0: |
172 |
line = pool.pop() |
173 |
print('{} - ({})'.format(line, len(pool))) |
174 |
rhs = [item for item in lines if item not in visited and item.is_connected(line)] |
175 |
if rhs:
|
176 |
pool.extend(rhs) |
177 |
visited.extend(rhs) |
178 |
for item in rhs: |
179 |
item.arrangeVertexOrder(line) |
180 | |
181 |
# skip jointed symbols
|
182 |
symbolPool = [item for item in symbols if item not in visited and item.is_connected(line)] |
183 |
if symbolPool:
|
184 |
selected = [] |
185 |
visited.extend(symbolPool) |
186 |
while len(symbolPool) > 0: |
187 |
symbol = symbolPool.pop() |
188 | |
189 |
rhs = [item for item in symbols if item not in visited and item.is_connected(symbol)] |
190 |
if rhs:
|
191 |
symbolPool.extend(rhs) |
192 |
visited.extend(rhs) |
193 |
selected.extend(rhs) |
194 |
else:
|
195 |
selected.append(symbol) |
196 | |
197 |
# find lines which are connected last symbol
|
198 |
for symbol in selected: |
199 |
rhs = [item for item in lines if item not in visited and item.is_connected(symbol)] |
200 |
if rhs:
|
201 |
pool.extend(rhs) |
202 |
visited.extend(rhs) |
203 |
for item in rhs: |
204 |
item.arrangeVertexOrder(line) |
205 |
# up to here
|
206 |
# up to here
|
207 |
except Exception as ex: |
208 |
from App import App |
209 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
210 |
sys.exc_info()[-1].tb_lineno)
|
211 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
212 | |
213 |
def create_detected_items(self, symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList): |
214 |
try:
|
215 |
QApplication.processEvents() |
216 |
self.create_detected_symbol_item(symbolList)
|
217 |
QApplication.processEvents() |
218 |
self.create_detected_text_item(textInfoList)
|
219 |
QApplication.processEvents() |
220 |
self.create_detected_other_text_item(otherTextInfoList)
|
221 |
QApplication.processEvents() |
222 |
self.create_detected_title_block_text_item(titleBlockTextInfoList)
|
223 | |
224 |
# update scene
|
225 |
# self.graphicsView.scene().update(self.graphicsView.sceneRect())
|
226 |
except Exception as ex: |
227 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
228 |
sys.exc_info()[-1].tb_lineno)
|
229 |
self.displayLog.emit(MessageType.Error, message)
|
230 | |
231 |
'''
|
232 |
history 2018.06.09 humkyung check length of original and connection point is 2 while parsing
|
233 |
2018.11.26 euisung remove scene dependency
|
234 |
2018.11.29 euisung change name drawDetectedSymbolItem() -> createDetectedSymbolItem
|
235 |
'''
|
236 | |
237 |
def create_detected_symbol_item(self, symbolList): |
238 |
from GraphicsBoundingBoxItem import QGraphicsBoundingBoxItem |
239 |
from SymbolSvgItem import SymbolSvgItem |
240 |
import math |
241 | |
242 |
try:
|
243 |
app_doc_data = AppDocData.instance() |
244 |
project = app_doc_data.getCurrentProject() |
245 | |
246 |
searchedMap = [] |
247 |
for symbol in symbolList: |
248 |
pt = [float(x) for x in symbol.getSp()] |
249 |
size = [symbol.getWidth(), symbol.getHeight()] |
250 |
name = symbol.getName() |
251 |
angle = round(math.radians(symbol.getRotatedAngle()), 2) |
252 |
_type = symbol.getType() |
253 |
flip = symbol.getDetectFlip() |
254 |
origin = [0, 0] |
255 |
if 2 == len(symbol.getOriginalPoint().split(',')): |
256 |
tokens = symbol.getOriginalPoint().split(',')
|
257 |
origin = [pt[0] + float(tokens[0]), pt[1] + float(tokens[1])] |
258 |
connPts = [] |
259 |
if symbol.getConnectionPoint() is not None and symbol.getConnectionPoint() != '': |
260 |
for param in symbol.getConnectionPoint().split('/'): |
261 |
tokens = param.split(',')
|
262 |
connPts.append( |
263 |
('AUTO', pt[0] + float(tokens[0]), pt[1] + float(tokens[1]), '0') if len(tokens) == 2 else \ |
264 |
(tokens[0], pt[0] + float(tokens[1]), pt[1] + float(tokens[2]), '0') if len( |
265 |
tokens) == 3 else \ |
266 |
(tokens[0], pt[0] + float(tokens[1]), pt[1] + float(tokens[2]), tokens[3]) if len( |
267 |
tokens) == 4 else None) |
268 | |
269 |
parentSymbol = symbol.getBaseSymbol() |
270 |
childSymbol = symbol.getAdditionalSymbol() |
271 |
hasInstrumentLabel = symbol.getHasInstrumentLabel() |
272 | |
273 |
svgFilePath = os.path.join(project.getSvgFilePath(), _type, name + '.svg')
|
274 |
if os.path.isfile(svgFilePath):
|
275 |
svg = SymbolSvgItem.createItem(_type, None, svgFilePath, owner=None, flip=flip) |
276 |
svg.hit_ratio = symbol.hitRate |
277 |
svg.buildItem(name, _type, angle, pt, size, origin, connPts, parentSymbol, childSymbol, |
278 |
hasInstrumentLabel) |
279 |
svg.area = 'Drawing'
|
280 | |
281 |
# set owner - 2018.07.20 added by humkyung
|
282 |
matches = [searched for searched in searchedMap if searched[0] == symbol.owner] |
283 |
if len(matches) == 1: |
284 |
svg.owner = matches[0][1] |
285 |
searchedMap.append((symbol, svg)) |
286 |
# up to here
|
287 | |
288 |
# self.addSvgItemToScene(svg)
|
289 |
app_doc_data.symbols.append(svg) |
290 |
app_doc_data.allItems.append(svg) |
291 |
else:
|
292 |
item = QGraphicsBoundingBoxItem(pt[0], pt[1], size[0], size[1]) |
293 |
item.isSymbol = True
|
294 |
item.angle = angle |
295 |
item.setPen(QPen(Qt.red, 5, Qt.SolidLine))
|
296 |
# self.graphicsView.scene().addItem(item)
|
297 |
# appDocData.symbols.append(item)
|
298 |
app_doc_data.allItems.append(item) |
299 |
# up to here
|
300 |
except Exception as ex: |
301 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
302 |
sys.exc_info()[-1].tb_lineno)
|
303 |
self.displayLog.emit(MessageType.Error, message)
|
304 | |
305 |
'''
|
306 |
@history 2018.06.08 Jeongwoo Add parameter on round method
|
307 |
@history 2018.11.02 euisung Add save note text item
|
308 |
@history 2018.11.05 euisung delete save note text item and move to drawDetectedItems()
|
309 |
2018.11.26 euisung remove scene dependency
|
310 |
2018.11.29 euisung change name drawDetectedTextItem() -> createDetectedTextItem
|
311 |
'''
|
312 | |
313 |
def create_detected_text_item(self, textInfoList): |
314 |
from TextItemFactory import TextItemFactory |
315 |
import math |
316 | |
317 |
try:
|
318 |
app_doc_data = AppDocData.instance() |
319 | |
320 |
# parse texts
|
321 |
for textInfo in textInfoList: |
322 |
x = textInfo.getX() |
323 |
y = textInfo.getY() |
324 |
width = textInfo.getW() |
325 |
height = textInfo.getH() |
326 |
angle = round(math.radians(textInfo.getAngle()), 2) |
327 |
text = textInfo.getText() |
328 |
if not text: continue |
329 | |
330 |
item = TextItemFactory.instance().createTextItem(textInfo) |
331 |
if item is not None: |
332 |
item.loc = [x, y] |
333 |
item.size = (width, height) |
334 |
item.angle = angle |
335 |
item.area = 'Drawing'
|
336 |
#item.transfer.onRemoved.connect(self.itemRemoved)
|
337 |
# self.addTextItemToScene(item)
|
338 |
app_doc_data.texts.append(item) |
339 |
app_doc_data.allItems.append(item) |
340 |
except Exception as ex: |
341 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
342 |
sys.exc_info()[-1].tb_lineno)
|
343 |
self.displayLog.emit(MessageType.Error, message)
|
344 | |
345 |
'''
|
346 |
@brief draw detected texts except which in drawing area
|
347 |
@history 2018.11.29 euisung change name drawDetectedOtherTextItem() -> createDetectedOtherTextItem
|
348 |
'''
|
349 | |
350 |
def create_detected_other_text_item(self, otherTextInfoList): |
351 |
from TextItemFactory import TextItemFactory |
352 |
import math |
353 | |
354 |
try:
|
355 |
app_doc_data = AppDocData.instance() |
356 | |
357 |
# parse notes
|
358 |
for textInfoMap in otherTextInfoList: |
359 |
if textInfoMap[0] == 'Note' or textInfoMap[1] is None: |
360 |
pass
|
361 | |
362 |
for textInfo in textInfoMap[1]: |
363 |
x = textInfo.getX() |
364 |
y = textInfo.getY() |
365 |
width = textInfo.getW() |
366 |
height = textInfo.getH() |
367 |
angle = round(math.radians(textInfo.getAngle()))
|
368 |
text = textInfo.getText() |
369 | |
370 |
item = TextItemFactory.instance().createTextItem(textInfo) |
371 | |
372 |
item.loc = [x, y] |
373 |
item.size = (width, height) |
374 |
item.angle = angle |
375 |
item.area = textInfoMap[0]
|
376 |
#item.transfer.onRemoved.connect(self.itemRemoved)
|
377 |
app_doc_data.texts.append(item) |
378 |
app_doc_data.allItems.append(item) |
379 |
except Exception as ex: |
380 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
381 |
sys.exc_info()[-1].tb_lineno)
|
382 |
self.displayLog.emit(MessageType.Error, message)
|
383 | |
384 |
def create_detected_title_block_text_item(self, textInfoList): |
385 |
"""draw title block"""
|
386 |
from TextItemFactory import TextItemFactory |
387 |
import math |
388 | |
389 |
try:
|
390 |
app_doc_data = AppDocData.instance() |
391 | |
392 |
# parse texts
|
393 |
for textInfos in textInfoList: |
394 |
if len(textInfos[1]) is 0: |
395 |
continue
|
396 | |
397 |
for textInfo in textInfos[1]: |
398 |
x = textInfo.getX() |
399 |
y = textInfo.getY() |
400 |
width = textInfo.getW() |
401 |
height = textInfo.getH() |
402 |
angle = round(math.radians(textInfo.getAngle()), 2) |
403 |
text = textInfo.getText() |
404 |
if not text: continue |
405 |
item = TextItemFactory.instance().createTextItem(textInfo) |
406 | |
407 |
if item is not None: |
408 |
item.loc = [x, y] |
409 |
item.size = (width, height) |
410 |
item.angle = angle |
411 |
item.area = textInfos[0]
|
412 |
# self.addTextItemToScene(item)
|
413 |
app_doc_data.texts.append(item) |
414 |
app_doc_data.allItems.append(item) |
415 |
except Exception as ex: |
416 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
417 |
sys.exc_info()[-1].tb_lineno)
|
418 |
self.displayLog.emit(MessageType.Error, message)
|
419 | |
420 |
'''
|
421 |
@brief draw unknown items
|
422 |
@author humkyung
|
423 |
@date 2018.06.12
|
424 |
@history 2018.06.14 Jeongwoo Change method to add unknown item
|
425 |
2018.06.18 Jeongwoo Add connect on unknown item
|
426 |
Add [transfer] for using pyqtSignal
|
427 |
2018.11.26 euisung remove scene dependency
|
428 |
2018.11.26 euisung isolate scene adding part -> drawDetectedItemsToScene()
|
429 |
2018.11.27 euisung add save to xml
|
430 |
2018.11.29 euisung change name drawUnknownItems() -> createUnknownItems
|
431 |
'''
|
432 | |
433 |
def create_unknown_items(self, path): |
434 |
from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem |
435 |
from EngineeringLineItem import QEngineeringLineItem |
436 |
from EngineeringUnknownItem import QEngineeringUnknownItem |
437 | |
438 |
try:
|
439 |
app_doc_data = AppDocData.instance() |
440 |
project = app_doc_data.getCurrentProject() |
441 |
windowSize = app_doc_data.getSlidingWindowSize() |
442 | |
443 |
thickness = int(windowSize[1] / 2) |
444 | |
445 |
area = app_doc_data.getArea('Drawing')
|
446 |
diffFilePath = os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(path))
|
447 | |
448 |
# remove line from image
|
449 |
imgDiff = np.ones(app_doc_data.imgSrc.shape, np.uint8) * 255
|
450 |
imgDiff[round(area.y + 1):round(area.y + area.height), |
451 |
round(area.x + 1):round(area.x + area.width)] = \ |
452 |
app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height), |
453 |
round(area.x + 1):round(area.x + area.width)] |
454 | |
455 |
lines = app_doc_data.lines |
456 |
for line in lines: |
457 |
line.drawToImage(imgDiff, 255, thickness) if line.thickness is None else \ |
458 |
line.drawToImage(imgDiff, 255, line.thickness)
|
459 |
# up to here
|
460 |
cv2.imwrite(diffFilePath, imgDiff) |
461 | |
462 |
imgNot = np.ones(imgDiff.shape, np.uint8) |
463 |
cv2.bitwise_not(imgDiff, imgNot) |
464 |
configs = app_doc_data.getConfigs('Filter', 'ErodeSize') |
465 |
kernel = int(configs[0].value) if 1 == len(configs) else 3 |
466 |
imgNot = cv2.erode(imgNot, np.ones((kernel, kernel), np.uint8)) |
467 |
imgNot = cv2.dilate(imgNot, np.ones((8 + kernel, 8 + kernel), np.uint8)) |
468 | |
469 |
contours, hierarchy = cv2.findContours(imgNot, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
470 | |
471 |
##
|
472 |
idx = 0
|
473 |
##
|
474 |
smallContours = [] |
475 |
minimumSize = app_doc_data.getConfigs('Filter', 'MinimumSize') |
476 |
for contour in contours: |
477 |
[x, y, w, h] = cv2.boundingRect(contour) |
478 | |
479 |
# remove too small one
|
480 |
if len(minimumSize) is 1: |
481 |
if w * h < int(minimumSize[0].value) * int(minimumSize[0].value): |
482 |
smallContours.append(contour) |
483 |
idx += 1
|
484 |
continue
|
485 | |
486 |
# create unknown item
|
487 |
epsilon = cv2.arcLength(contour, True) * 0.001 |
488 |
approx = cv2.approxPolyDP(contour, epsilon, True)
|
489 |
approx = [pt[0] for pt in approx] |
490 |
resultStr, resultList = self.determine_remain_object(idx, contours, imgNot)
|
491 |
if resultStr == 'LineIndicator': |
492 |
item = QEngineeringUnknownItem(approx, 'True', resultList[0], resultList[1]) |
493 |
app_doc_data.lineIndicators.append(item) |
494 |
elif resultStr == 'MissingLine': |
495 |
idx += 1
|
496 |
continue
|
497 |
elif resultStr == 'Unknown': |
498 |
item = QEngineeringUnknownItem(approx, 'False')
|
499 |
app_doc_data.unknowns.append(item) |
500 |
item.area = 'Drawing'
|
501 |
app_doc_data.allItems.append(item) |
502 |
idx += 1
|
503 |
# up to here
|
504 | |
505 |
"""
|
506 |
if app_doc_data.needReOpening is not None:
|
507 |
app_doc_data.needReOpening = True
|
508 |
"""
|
509 | |
510 |
"""
|
511 |
diffFilePath = os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(path))
|
512 |
if os.path.isfile(diffFilePath):
|
513 |
imgDiff = cv2.threshold(cv2.cvtColor(cv2.imread(diffFilePath, 1), cv2.COLOR_BGR2GRAY), 0, 255,
|
514 |
cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
|
515 |
|
516 |
# remove line from image
|
517 |
lines = app_doc_data.lines
|
518 |
for line in lines:
|
519 |
line.drawToImage(imgDiff, 255, thickness) if line.thickness is None else \
|
520 |
line.drawToImage(imgDiff, 255, line.thickness)
|
521 |
cv2.imwrite(diffFilePath, imgDiff)
|
522 |
# up to here
|
523 |
|
524 |
imgNot = np.ones(imgDiff.shape, np.uint8)
|
525 |
cv2.bitwise_not(imgDiff, imgNot)
|
526 |
configs = app_doc_data.getConfigs('Filter', 'ErodeSize')
|
527 |
kernel = int(configs[0].value) if 1 == len(configs) else 3
|
528 |
imgNot = cv2.erode(imgNot, np.ones((kernel, kernel), np.uint8))
|
529 |
imgNot = cv2.dilate(imgNot, np.ones((8 + kernel, 8 + kernel), np.uint8))
|
530 |
|
531 |
contours, hierarchy = cv2.findContours(imgNot, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
532 |
|
533 |
##
|
534 |
idx = 0
|
535 |
##
|
536 |
smallContours = []
|
537 |
minimumSize = app_doc_data.getConfigs('Filter', 'MinimumSize')
|
538 |
for contour in contours:
|
539 |
[x, y, w, h] = cv2.boundingRect(contour)
|
540 |
|
541 |
# remove too small one
|
542 |
if len(minimumSize) is 1:
|
543 |
if w * h < int(minimumSize[0].value) * int(minimumSize[0].value):
|
544 |
smallContours.append(contour)
|
545 |
idx += 1
|
546 |
continue
|
547 |
|
548 |
'''
|
549 |
rect = QRectF(x, y, w, h)
|
550 |
items = [item for item in diffItems if item.boundingRect().contains(rect)]
|
551 |
if len(items) > 0: continue
|
552 |
|
553 |
items = [item for item in diffItems if rect.contains(item.boundingRect())]
|
554 |
for item in items:
|
555 |
diffItems.remove(item)
|
556 |
'''
|
557 |
|
558 |
# create unknown item
|
559 |
epsilon = cv2.arcLength(contour, True) * 0.001
|
560 |
approx = cv2.approxPolyDP(contour, epsilon, True)
|
561 |
approx = [pt[0] for pt in approx]
|
562 |
resultStr, resultList = self.determine_remain_object(idx, contours, imgNot)
|
563 |
if resultStr == 'LineIndicator':
|
564 |
item = QEngineeringUnknownItem(approx, 'True', resultList[0], resultList[1])
|
565 |
app_doc_data.lineIndicators.append(item)
|
566 |
elif resultStr == 'MissingLine':
|
567 |
pass
|
568 |
elif resultStr == 'Unknown':
|
569 |
item = QEngineeringUnknownItem(approx, 'False')
|
570 |
app_doc_data.unknowns.append(item)
|
571 |
item.area = 'Drawing'
|
572 |
app_doc_data.allItems.append(item)
|
573 |
#item.transfer.onRemoved.connect(self.itemRemoved)
|
574 |
idx += 1
|
575 |
# up to here
|
576 |
|
577 |
imgNotRemoveSmall = cv2.drawContours(imgNot, smallContours, -1, 0, -1)
|
578 |
notFilePath = os.path.join(project.getTempPath(), "NOT_" + os.path.basename(path))
|
579 |
cv2.imwrite(notFilePath, imgNotRemoveSmall)
|
580 |
else:
|
581 |
message = 'can\'t found {}'.format(diffFilePath)
|
582 |
self.displayLog.emit(MessageType.Normal, message)
|
583 |
"""
|
584 | |
585 |
except Exception as ex: |
586 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
587 |
sys.exc_info()[-1].tb_lineno)
|
588 |
self.displayLog.emit(MessageType.Error, message)
|
589 | |
590 |
def determine_remain_object(self, idx, contours, imgNot): |
591 |
"""determine remain objects -> line no indicator or unknown"""
|
592 |
import math |
593 |
[x, y, w, h] = cv2.boundingRect(contours[idx]) |
594 | |
595 |
if w < 250 and h < 250: |
596 |
return ('Unknown', []) |
597 | |
598 |
fLines = [] |
599 |
maxDifAngle = 3
|
600 |
mask = np.zeros_like(imgNot) |
601 |
cv2.drawContours(mask, contours, idx, 123, -1) # Draw filled contour in mask |
602 |
out = np.zeros_like(imgNot) # Extract out the object and place into output image
|
603 |
out[mask == 123] = imgNot[mask == 123] |
604 | |
605 |
# Now crop
|
606 |
##print(out)
|
607 |
(x, y) = np.where(mask == 123)
|
608 |
(topx, topy) = (np.min(x), np.min(y)) |
609 |
(bottomx, bottomy) = (np.max(x), np.max(y)) |
610 |
out = out[topx:bottomx + 1, topy:bottomy + 1] |
611 |
h, w = out.shape[0], out.shape[1] |
612 |
maxDifH, maxDifW = math.ceil(math.tan(4 * math.pi / 180) / 2 * w), math.ceil( |
613 |
math.tan(4 * math.pi / 180) / 2 * h) |
614 | |
615 |
# detection lines
|
616 |
edged2 = cv2.Canny(out, 100, 200) |
617 |
lines = cv2.HoughLinesP(image=edged2, rho=1, theta=np.pi / 180, threshold=25, minLineLength=30, maxLineGap=25) |
618 |
# lines = cv2.HoughLines(edged2, 1, np.pi/180, 60)
|
619 |
if lines is None: |
620 |
return ('Unknown', []) |
621 |
for line in lines: |
622 |
# r, theta = line[0]
|
623 |
# a, b = np.cos(theta), np.sin(theta)
|
624 |
# x0, y0 = a * r, b * r
|
625 |
# x1, y1 = int(x0 + 1000 * (-b)), int(y0 + 1000 * a)
|
626 |
# x2, y2 = int(x0 - 1000 * (-b)), int(y0 - 1000 * a)
|
627 |
# cv2.line(out, (x1, y1), (x2, y2), (0, 255, 0), 3)
|
628 |
x1, y1, x2, y2 = line[0]
|
629 |
degree = math.atan2(y2 - y1, x2 - x1) * 180 / math.pi
|
630 |
fLine = [x1, y1, x2, y2, degree] |
631 |
# print(fLine)
|
632 |
fLines.append(fLine) |
633 | |
634 |
horLines = [] |
635 |
verLines = [] |
636 |
otherLines = [] |
637 |
isVH = None
|
638 |
for fLine in fLines: |
639 |
degree = math.fabs(fLine[4])
|
640 |
if degree >= 90 - maxDifAngle: |
641 |
verLines.append(fLine) |
642 |
elif degree <= maxDifAngle:
|
643 |
horLines.append(fLine) |
644 |
else:
|
645 |
otherLines.append(fLine) |
646 | |
647 |
baseLines = [] |
648 |
baseDifV = 0
|
649 |
if len(horLines): |
650 |
x, y = w / 2, 0 |
651 |
baseDifV = maxDifH |
652 |
for horLine in horLines: |
653 |
x1, y1, x2, y2 = horLine[0], horLine[1], horLine[2], horLine[3] |
654 |
y = ((y2 - y1) / (x2 - x1)) * x + y1 - ((y2 - y1) / (x2 - x1)) * x1 |
655 |
horLine.append(y) |
656 |
baseLines = horLines |
657 |
isVH = 'H'
|
658 |
if len(verLines): |
659 |
x, y = 0, h / 2 |
660 |
baseDifV = maxDifW |
661 |
for verLine in verLines: |
662 |
x1, y1, x2, y2 = verLine[0], verLine[1], verLine[2], verLine[3] |
663 |
x = ((x2 - x1) / (y2 - y1)) * y + x1 - ((x2 - x1) / (y2 - y1)) * y1 |
664 |
verLine.append(x) |
665 |
baseLines = verLines |
666 |
isVH = 'V'
|
667 | |
668 |
for otherLine in otherLines: |
669 |
x, y = w / 2, 0 |
670 |
x1, y1, x2, y2 = otherLine[0], otherLine[1], otherLine[2], otherLine[3] |
671 |
y = ((y2 - y1) / (x2 - x1)) * x + y1 - ((y2 - y1) / (x2 - x1)) * x1 |
672 |
otherLine.append(y) |
673 | |
674 |
# determine line no indicator
|
675 |
if not ((len(horLines) > 0 and len(verLines) > 0) or len(otherLines) is 0 or ( |
676 |
len(horLines) == 0 and len(verLines) == 0)): |
677 |
result, mergedOtherLine = self.is_line_no_indicator(w, h, maxDifAngle, baseDifV, baseLines, otherLines)
|
678 |
if result:
|
679 |
# print(fLines)
|
680 |
return ('LineIndicator', [isVH, mergedOtherLine]) |
681 | |
682 |
return ('Unknown', []) |
683 | |
684 |
def is_line_no_indicator(self, w, h, maxDifAngle, baseDifV, baseLines, otherLines): |
685 |
"""determine line no indicator"""
|
686 |
import math |
687 | |
688 |
if w < 250 and h < 250: |
689 |
return (False, None) |
690 | |
691 |
isSameLine = True
|
692 |
i = 0
|
693 |
for baseLine in baseLines: |
694 |
if not isSameLine: break |
695 |
j = 0
|
696 |
for baseLinee in baseLines: |
697 |
if i == j:
|
698 |
j += 1
|
699 |
continue
|
700 |
difV = math.fabs(baseLine[5] - baseLinee[5]) |
701 |
if difV > baseDifV:
|
702 |
isSameLine = False
|
703 |
break
|
704 |
j += 1
|
705 |
i += 1
|
706 |
if not isSameLine: |
707 |
return (False, None) |
708 | |
709 |
isSameLine = True
|
710 |
i = 0
|
711 |
maxY = 0
|
712 |
for otherLine in otherLines: |
713 |
y = otherLine[5]
|
714 |
if math.fabs(y) > maxY:
|
715 |
maxY = math.fabs(y) |
716 |
if not isSameLine: break |
717 |
j = 0
|
718 |
for otherLinee in otherLines: |
719 |
if i == j:
|
720 |
j += 1
|
721 |
continue
|
722 |
difV = math.fabs(otherLine[4] - otherLinee[4]) |
723 |
if difV > maxDifAngle:
|
724 |
isSameLine = False
|
725 |
break
|
726 |
j += 1
|
727 |
i += 1
|
728 |
if not isSameLine: |
729 |
return (False, None) |
730 | |
731 |
isSameLine = True
|
732 |
mergedOtherLine = [0, 0, 0, 0] |
733 |
i = 0
|
734 |
maxDif = math.ceil(math.tan(4 * math.pi / 180) * maxY) |
735 |
for otherLine in otherLines: |
736 |
if not isSameLine: break |
737 |
j = 0
|
738 |
for otherLinee in otherLines: |
739 |
if i == j:
|
740 |
j += 1
|
741 |
continue
|
742 |
angle = math.fabs(otherLine[4] + otherLinee[4]) / 2 |
743 |
difV = math.fabs(otherLine[5] - otherLinee[5]) |
744 |
dist = math.sin((90 - angle) * math.pi / 180) * difV |
745 |
if dist > maxDif:
|
746 |
isSameLine = False
|
747 |
break
|
748 |
j += 1
|
749 |
i += 1
|
750 |
mergedOtherLine[0] += otherLine[0] |
751 |
mergedOtherLine[1] += otherLine[1] |
752 |
mergedOtherLine[2] += otherLine[2] |
753 |
mergedOtherLine[3] += otherLine[3] |
754 |
if not isSameLine: |
755 |
(False, None) |
756 | |
757 |
# Show the output image
|
758 |
# print('line no indicator')
|
759 |
mergedOtherLine[0] = round(mergedOtherLine[0] / len(otherLines)) |
760 |
mergedOtherLine[1] = round(mergedOtherLine[1] / len(otherLines)) |
761 |
mergedOtherLine[2] = round(mergedOtherLine[2] / len(otherLines)) |
762 |
mergedOtherLine[3] = round(mergedOtherLine[3] / len(otherLines)) |
763 |
# cv2.line(out, (mergedOtherLine[0], mergedOtherLine[1]), (mergedOtherLine[2], mergedOtherLine[3]), (255, 255, 255), 3)
|
764 |
# cv2.imshow('Output', out)
|
765 |
# cv2.waitKey(0)
|
766 |
# cv2.destroyAllWindows()
|
767 |
return (True, mergedOtherLine) |
768 | |
769 |
@staticmethod
|
770 |
def executeRecognition(drawings, listWidget, isLineChecked, worker): |
771 |
"""recognize symbol, text, line from image p&id"""
|
772 |
import re |
773 |
import timeit |
774 |
from TextDetector import TextDetector |
775 |
from datetime import datetime |
776 | |
777 |
global ocrCompletedSrc
|
778 |
global threadLock
|
779 |
global searchedSymbolList
|
780 |
global textInfoList
|
781 |
global maxProgressValue
|
782 | |
783 |
try:
|
784 |
app_doc_data = AppDocData.instance() |
785 |
#drawings = app_doc_data.getDrawings()
|
786 |
project = app_doc_data.getCurrentProject() |
787 |
textDetector = TextDetector() |
788 | |
789 |
Worker.initTargetSymbolDataList() |
790 | |
791 |
for drawing in drawings: |
792 |
ocrCompletedSrc = [] |
793 |
searchedSymbolList = [] |
794 |
textInfoList = [] |
795 | |
796 |
mainRes = drawing.file_path |
797 |
if not os.path.isfile(mainRes): |
798 |
item = QListWidgetItem('{} file is not found'.format(os.path.basename(mainRes)))
|
799 |
item.setBackground(Qt.red) |
800 |
listWidget.addItem(item) |
801 |
continue
|
802 | |
803 |
#worker.scene.clear()
|
804 |
#worker.text_scene.clear()
|
805 |
#worker.line_scene.clear()
|
806 |
worker.clear_scene.emit([worker.scene, worker.text_scene, worker.line_scene]) |
807 |
worker.cond.wait(worker.mutex) |
808 | |
809 |
app_doc_data.clearItemList(True)
|
810 | |
811 |
app_doc_data.setImgFilePath(mainRes) |
812 |
app_doc_data.imgSrc = None
|
813 |
matches = [drawing for drawing in drawings if app_doc_data.imgName == os.path.splitext(drawing.name)[0]] |
814 |
app_doc_data.activeDrawing = drawing # matches[0] if matches else Drawing(None, app_doc_data.imgName, None)
|
815 |
app_doc_data.activeDrawing.set_pid_source(Image.open(mainRes)) |
816 | |
817 |
# Load equipment package
|
818 |
worker.add_predata_to_scene.emit(app_doc_data.activeDrawing, worker.scene, False, False, False, False, True) |
819 |
worker.cond.wait(worker.mutex) |
820 | |
821 |
# remove not drawing area
|
822 |
configs = app_doc_data.getConfigs('{} Equipment Desc Area'.format(app_doc_data.imgName))
|
823 |
for config in configs: |
824 |
found = re.findall('\\d+', config.value)
|
825 |
if len(found) == 4: |
826 |
cv2.rectangle(app_doc_data.imgSrc, (int(found[0]), int(found[1])), |
827 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1) |
828 | |
829 |
configs = app_doc_data.getConfigs('{} Typical Area'.format(app_doc_data.imgName))
|
830 |
for config in configs: |
831 |
found = re.findall('\\d+', config.value)
|
832 |
if len(found) == 4: |
833 |
cv2.rectangle(app_doc_data.imgSrc, (int(found[0]), int(found[1])), |
834 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1) |
835 | |
836 |
noteArea = app_doc_data.getArea('Note')
|
837 |
if noteArea is not None: |
838 |
noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height), |
839 |
round(noteArea.x):round(noteArea.x + noteArea.width)].copy() |
840 |
cv2.rectangle(app_doc_data.imgSrc, (round(noteArea.x), round(noteArea.y)), |
841 |
(round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1) |
842 |
#cv2.imwrite('c:\\temp\\note.png', noteArea.img)
|
843 |
#cv2.imwrite('c:\\temp\\removed_note.png', app_doc_data.imgSrc)
|
844 |
# up to here
|
845 | |
846 |
area = app_doc_data.getArea('Drawing')
|
847 |
if area is not None: |
848 |
# copy image
|
849 |
area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height), |
850 |
round(area.x):round(area.x + area.width)] |
851 | |
852 |
_, area.mask = cv2.threshold(area.img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) |
853 | |
854 |
# area.contours, area.hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
|
855 |
area.not_img = cv2.bitwise_not(area.img) |
856 |
area.contours, area.hierachy = cv2.findContours(area.not_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) |
857 | |
858 |
maxProgressValue = 0
|
859 |
start_time = timeit.default_timer() |
860 |
listWidget.addItem(f"Start recognition {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} : {mainRes}")
|
861 |
threadLock.acquire() |
862 | |
863 |
worker.updateBatchProgress.emit(len(drawings), 1) |
864 | |
865 |
if worker.isSymbolChecked:
|
866 |
# calculate total count of symbol
|
867 |
for targetItem in targetSymbolList: |
868 |
if type(targetItem) is list: |
869 |
maxProgressValue += len(targetItem)
|
870 |
else:
|
871 |
maxProgressValue += 1
|
872 |
# up to here
|
873 |
maxProgressValue = maxProgressValue * 2
|
874 |
threadLock.release() |
875 | |
876 |
if worker.isSymbolChecked:
|
877 |
worker.displayTitle.emit(worker.tr('Detecting symbols...'))
|
878 |
|
879 |
configs = app_doc_data.getConfigs('Engine', 'Symbol') |
880 |
if (configs and int(configs[0].value) is 1) or not configs: |
881 |
# get symbol original way
|
882 |
|
883 |
# detect only equipments
|
884 |
with futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as pool: |
885 |
future_equipment = {pool.submit(Worker.detectSymbolsOnPid, mainRes, symbol, listWidget, worker): |
886 |
symbol for symbol in targetSymbolList[0]} |
887 |
futures.wait(future_equipment) |
888 |
# up to here
|
889 | |
890 |
# detect normal symbols
|
891 |
with futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as pool: |
892 |
future_symbol = {pool.submit(Worker.detectSymbolsOnPid, mainRes, symbol, listWidget, worker): |
893 |
symbol for symbol in targetSymbolList[2]} |
894 |
futures.wait(future_symbol) |
895 |
# up to here
|
896 |
else:
|
897 |
# using yolo
|
898 |
Worker.detect_symbol_using_server(targetSymbolList, listWidget, worker) |
899 | |
900 |
if worker.isTrainingChecked:
|
901 |
import uuid |
902 |
worker.displayTitle.emit(worker.tr('Generating Data...'))
|
903 |
listWidget.addItem('Generating Data...')
|
904 | |
905 |
for item in searchedSymbolList: |
906 |
path = os.path.join(project.getTempPath(), 'Tile', item.getBaseSymbol())
|
907 |
if not os.path.exists(path): os.makedirs(path) |
908 | |
909 |
_img = app_doc_data.imgSrc[round(item.getSp()[1]):round(item.getSp()[1] + item.getHeight()), |
910 |
round(item.getSp()[0]):round(item.getSp()[0] + item.getWidth())] |
911 |
cv2.imwrite(os.path.join(path, str(uuid.uuid4()) + '.png'), _img) |
912 |
worker.updateBatchProgress.emit(len(srcList), 4) |
913 |
continue
|
914 | |
915 |
#cv2.imwrite('c:\\temp\\before-imgSrc.png', area.img)
|
916 |
# save original image before remove symbol
|
917 |
worker.copy_imgSrc = app_doc_data.imgSrc.copy() |
918 |
searchedTextSymList = [] |
919 |
for sym in searchedSymbolList: |
920 |
Worker.remove_detected_symbol_image(sym, app_doc_data.imgSrc) |
921 |
else:
|
922 |
'''
|
923 |
import math
|
924 |
|
925 |
symbolItems = [item for item in worker.scene.items() if issubclass(type(item), SymbolSvgItem)]
|
926 |
|
927 |
for symbolItem in symbolItems:
|
928 |
symbolName = symbolItem.name
|
929 |
symbolType = symbolItem.type
|
930 |
searchedItemSp = [int(symbolItem.loc[0]), int(symbolItem.loc[1])]
|
931 |
sw = symbolItem.size[0] # check later
|
932 |
sh = symbolItem.size[1] # check later
|
933 |
|
934 |
symbolThreshold = 50 # not necessary
|
935 |
symbolMinMatchCount = 0 # not necessary
|
936 |
hitRate = 80 # not necessary
|
937 |
|
938 |
allowed_error = 0.0001
|
939 |
#degree 0
|
940 |
if abs(symbolItem.angle - 0) <= allowed_error:
|
941 |
symbolRotatedAngle = 0
|
942 |
#degree 90
|
943 |
elif abs(symbolItem.angle - 1.57) <= allowed_error:
|
944 |
symbolRotatedAngle = 90
|
945 |
#degree 180
|
946 |
elif abs(symbolItem.angle - 3.14) <= allowed_error:
|
947 |
symbolRotatedAngle = 180
|
948 |
#degree 270
|
949 |
elif abs(symbolItem.angle - 4.71) <= allowed_error:
|
950 |
symbolRotatedAngle = 270
|
951 |
else:
|
952 |
symbolRotatedAngle = math.pi / 180 * symbolItem.angle
|
953 |
|
954 |
isDetectOnOrigin = 80 # not necessary
|
955 |
symbolRotateCount = 0 # not necessary
|
956 |
symbolOcrOption = 0 # not necessary
|
957 |
isContainChild = 0 # not necessary
|
958 |
originalPoint = [symbolItem.origin[0] - symbolItem.loc[0], symbolItem.origin[1] - symbolItem.loc[1]]
|
959 |
symbolConnectionPoint = []
|
960 |
baseSymbol = str(symbolItem.parentSymbol)
|
961 |
additionalSymbol = str(symbolItem.childSymbol)
|
962 |
isExceptDetect = 0 # not necessary
|
963 |
detectFlip = symbolItem.flip
|
964 |
|
965 |
searchedSymbolList.append(Symbol(symbolName, symbolType ,
|
966 |
searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, hitRate, symbolRotatedAngle ,
|
967 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild ,
|
968 |
','.join(str(x) for x in originalPoint),
|
969 |
[],
|
970 |
baseSymbol, additionalSymbol, isExceptDetect, detectFlip))
|
971 |
|
972 |
worker.scene.removeItem(symbolItem)
|
973 |
|
974 |
pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER)
|
975 |
'''
|
976 |
# symbolItems = [item for item in worker.scene.items() if issubclass(type(item), SymbolSvgItem)]
|
977 |
# appDocData.symbols.extend(symbolItems)
|
978 |
# appDocData.allItems.extend(symbolItems)
|
979 | |
980 |
# for sym in searchedSymbolList:
|
981 |
# pool.submit(Worker.remove_detected_symbol_image, sym, appDocData.imgSrc)
|
982 |
# pool.shutdown(wait = True)
|
983 | |
984 |
worker.updateBatchProgress.emit(len(drawings), 1) |
985 | |
986 |
area = app_doc_data.getArea('Drawing')
|
987 |
if area is not None: |
988 |
# copy area image
|
989 |
area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height), |
990 |
round(area.x):round(area.x + area.width)] |
991 | |
992 |
offset = (area.x, area.y) if area is not None else (0, 0) |
993 | |
994 |
otherTextInfoList = None
|
995 |
titleBlockTextInfoList = None
|
996 |
textInfoList = [] |
997 |
if worker.isTextChecked:
|
998 |
from TextInfo import TextInfo |
999 | |
1000 |
textAreas, ocr_image = textDetector.detectTextAreas(area.img if area is not None else app_doc_data.imgSrc, |
1001 |
offset) |
1002 | |
1003 |
# get text area by using symbol setting
|
1004 |
text_area_symbols = [] |
1005 |
for symbol in [symbol for symbol in searchedSymbolList if symbol.text_area]: |
1006 |
symbol_angle = symbol.getRotatedAngle() |
1007 |
for text_area in symbol.text_area: |
1008 |
so = [symbol.getWidth(), symbol.getHeight()] if symbol_angle is 0 or symbol_angle is 180\ |
1009 |
else [symbol.getHeight(), symbol.getWidth()]
|
1010 |
text_area_origin = Worker.getCalculatedOriginalPoint(None, str(text_area.center()[0]) + ',' + str(text_area.center()[1]),\ |
1011 |
symbol_angle, None, None, so[0], so[1], symbol.getDetectFlip()) |
1012 |
x = text_area_origin[0] - round(text_area.width / 2) if symbol_angle is 0 or symbol_angle is 180\ |
1013 |
else text_area_origin[0] - round(text_area.height / 2) |
1014 |
x = x + symbol.sp[0]
|
1015 |
y = text_area_origin[1] - round(text_area.height / 2) if symbol_angle is 0 or symbol_angle is 180\ |
1016 |
else text_area_origin[1] - round(text_area.width / 2) |
1017 |
y = y + symbol.sp[1]
|
1018 |
w = text_area.width if symbol_angle is 0 or symbol_angle is 180 else text_area.height |
1019 |
h = text_area.height if symbol_angle is 0 or symbol_angle is 180 else text_area.width |
1020 | |
1021 |
if symbol_angle is 180 or symbol_angle is 0: |
1022 |
text_angle = 0
|
1023 |
else:
|
1024 |
text_angle = 90
|
1025 |
text_area_symbols.append(TextInfo('', x, y, w, h, text_angle))
|
1026 | |
1027 |
for textArea_index in reversed(range(len(textAreas))): |
1028 |
is_pop = False
|
1029 |
for text_area_index in reversed(range(len(text_area_symbols))): |
1030 |
if text_area_symbols[text_area_index].contains(textAreas[textArea_index].center) or \ |
1031 |
textAreas[textArea_index].contains(text_area_symbols[text_area_index].center): |
1032 |
if text_area_symbols[text_area_index].area > textAreas[textArea_index].area:
|
1033 |
textAreas.pop(textArea_index) |
1034 |
is_pop = True
|
1035 |
break
|
1036 |
else:
|
1037 |
text_area_symbols.pop(text_area_index) |
1038 |
break
|
1039 |
if is_pop:
|
1040 |
break
|
1041 |
textAreas.extend(text_area_symbols) |
1042 |
# up to here
|
1043 | |
1044 |
if maxProgressValue < 2 * len(textAreas): |
1045 |
for _ in range(len(textAreas) - int(maxProgressValue * 0.5)): |
1046 |
worker.updateProgress.emit(2 * len(textAreas), None) |
1047 |
maxProgressValue = 2 * len(textAreas) |
1048 |
else:
|
1049 |
maxProgressValue = int(maxProgressValue * 0.5) + len(textAreas) |
1050 | |
1051 |
worker.displayTitle.emit(worker.tr('Detecting texts...'))
|
1052 |
textDetector.recognizeText(area.img, offset, textAreas, searchedSymbolList, worker, |
1053 |
listWidget, maxProgressValue) |
1054 |
textInfoList = textDetector.textInfoList.copy() if textDetector.textInfoList is not None else None |
1055 |
otherTextInfoList = textDetector.otherTextInfoList.copy() if textDetector.otherTextInfoList is not None else None |
1056 |
titleBlockTextInfoList = textDetector.titleBlockTextInfoList.copy() if textDetector.titleBlockTextInfoList is not None else None |
1057 | |
1058 |
app_doc_data.activeDrawing.width, app_doc_data.activeDrawing.height = \ |
1059 |
app_doc_data.imgSrc.shape[::-1]
|
1060 | |
1061 |
app_doc_data.imgName = os.path.splitext(os.path.basename(mainRes))[0]
|
1062 |
else:
|
1063 |
import math |
1064 |
from TextInfo import TextInfo |
1065 | |
1066 |
"""load texts"""
|
1067 |
worker.add_predata_to_scene.emit(app_doc_data.activeDrawing, worker.text_scene, False, True, False, False, False) |
1068 |
worker.cond.wait(worker.mutex) |
1069 |
"""up to here"""
|
1070 |
textItems = [item for item in worker.text_scene.items() if issubclass(type(item), QEngineeringTextItem)] |
1071 |
app_doc_data.texts.extend(textItems) |
1072 |
app_doc_data.allItems.extend(textItems) |
1073 | |
1074 |
lineNoTextItems = [item for item in textItems if type(item) is QEngineeringLineNoTextItem] |
1075 |
for lineNoTextItem in lineNoTextItems: |
1076 |
lineNoTextItem.set_property('Freeze', False) |
1077 |
# lineNoTextItem.freeze_item.update_freeze(lineNoTextItem.prop('Freeze'))
|
1078 |
lineNoTextItem.explode() |
1079 | |
1080 |
for textItem in textItems: |
1081 |
textInfoList.append( |
1082 |
TextInfo(textItem.text(), textItem.loc[0], textItem.loc[1], textItem.size[0], |
1083 |
textItem.size[1], round(math.degrees(textItem.angle)))) |
1084 | |
1085 |
#textItem.owner = None
|
1086 |
#worker.scene.removeItem(textItem)
|
1087 | |
1088 |
worker.updateBatchProgress.emit(len(drawings), 2) |
1089 | |
1090 |
# check symbol validate
|
1091 |
#valid_sym = []
|
1092 |
for index in reversed(range(len(searchedTextSymList))): |
1093 |
sym_xmin = searchedTextSymList[index].getSp()[0]
|
1094 |
sym_ymin = searchedTextSymList[index].getSp()[1]
|
1095 |
sym_xmax = searchedTextSymList[index].getSp()[0] + searchedTextSymList[index].getWidth()
|
1096 |
sym_ymax = searchedTextSymList[index].getSp()[1] + searchedTextSymList[index].getHeight()
|
1097 |
valid_count = searchedTextSymList[index].getHasInstrumentLabel() |
1098 |
valid = 0
|
1099 |
for text_info in textInfoList: |
1100 |
info_center = text_info.center |
1101 |
if sym_xmin < info_center[0] < sym_xmax and sym_ymin < info_center[1] < sym_ymax: |
1102 |
valid += 1
|
1103 |
break
|
1104 |
#if valid >= valid_count:
|
1105 |
# valid_sym.append(searchedTextSymList[index])
|
1106 |
# break
|
1107 |
if valid < valid_count:
|
1108 |
searchedSymbolList.pop(searchedSymbolList.index(searchedTextSymList[index])) |
1109 | |
1110 |
# roll back because invalidated symbol was deleted for text detection
|
1111 |
app_doc_data.imgSrc = worker.copy_imgSrc |
1112 | |
1113 |
for sym in searchedSymbolList: |
1114 |
Worker.remove_detected_symbol_image(sym, app_doc_data.imgSrc) |
1115 |
#pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER)
|
1116 |
#for sym in valid_sym:
|
1117 |
# pool.submit(Worker.remove_detected_symbol_image, sym, app_doc_data.imgSrc)
|
1118 |
#pool.shutdown(wait=True)
|
1119 |
# up to here
|
1120 | |
1121 |
# remove text from image
|
1122 |
textDetector.remove_text_from_image(app_doc_data.imgSrc, [0, 0]) |
1123 |
#textDetector.remove_text_from_image(area.img, offset)
|
1124 |
if not worker.isTextChecked: |
1125 |
textInfoList.clear() |
1126 |
# up to here
|
1127 | |
1128 |
removedSymbolImgPath = os.path.join(project.getTempPath(), os.path.basename(mainRes)) |
1129 |
cv2.imwrite(removedSymbolImgPath, app_doc_data.imgSrc) |
1130 | |
1131 |
listWidget.addItem("Recognized symbol count : " + str(len(searchedSymbolList))) |
1132 | |
1133 |
listWidget.addItem(worker.tr('Creating detected infos...'))
|
1134 |
worker.displayTitle.emit(worker.tr('Creating detected infos...'))
|
1135 |
worker.create_detected_items(searchedSymbolList, textInfoList, |
1136 |
otherTextInfoList if otherTextInfoList is not None else [], |
1137 |
titleBlockTextInfoList if titleBlockTextInfoList is not None else []) |
1138 | |
1139 |
if isLineChecked:
|
1140 |
Worker.recognizeLine(mainRes, listWidget, worker.scene, worker) |
1141 |
else:
|
1142 |
"""load lines"""
|
1143 |
worker.add_predata_to_scene.emit(app_doc_data.activeDrawing, worker.line_scene, False, False, True, False, False) |
1144 |
worker.cond.wait(worker.mutex) |
1145 |
"""up to here"""
|
1146 |
lineItems = [item for item in worker.line_scene.items() |
1147 |
if issubclass(type(item), QEngineeringLineItem)] |
1148 |
app_doc_data.lines.extend(lineItems) |
1149 |
app_doc_data.allItems.extend(lineItems) |
1150 | |
1151 |
for lineItem in lineItems: |
1152 |
lineItem.owner = None
|
1153 |
for conn in lineItem.connectors: |
1154 |
conn.connectedItem = None
|
1155 |
#worker.scene.removeItem(lineItem)
|
1156 | |
1157 |
# try to detect nozzle from image drawing
|
1158 |
if worker.isSymbolChecked:
|
1159 |
worker.displayTitle.emit(worker.tr('Detecting nozzle and flange...'))
|
1160 | |
1161 |
nozzles = [] |
1162 |
with futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as pool: |
1163 |
future_nozzle = {pool.submit(Worker.detect_nozzles, mainRes, symbol, listWidget, worker): |
1164 |
symbol for symbol in targetSymbolList[1]} |
1165 |
for future in futures.as_completed(future_nozzle): |
1166 |
data = future.result() |
1167 |
if data:
|
1168 |
nozzles.extend(data) |
1169 | |
1170 |
worker.create_detected_items(nozzles, [], [], []) |
1171 | |
1172 |
for nozzle in nozzles: |
1173 |
Worker.remove_detected_symbol_image(nozzle, app_doc_data.imgSrc) |
1174 |
# up to here
|
1175 | |
1176 |
area = app_doc_data.getArea('Drawing')
|
1177 |
if area is not None: |
1178 |
area.img = app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height), |
1179 |
round(area.x + 1):round(area.x + area.width)] |
1180 |
cv2.imwrite(os.path.join(project.getTempPath(), "RECT_" + os.path.basename(mainRes)),
|
1181 |
app_doc_data.imgSrc) |
1182 | |
1183 |
Worker.drawFoundSymbolsOnCanvas(mainRes, searchedSymbolList, textInfoList, listWidget) |
1184 | |
1185 |
# get difference between original and recognized image
|
1186 |
foundFilePath = os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(mainRes))
|
1187 |
Worker.getDifference(mainRes, foundFilePath) |
1188 |
# up to here
|
1189 | |
1190 |
# connect symbol to symbol
|
1191 |
try:
|
1192 |
configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line') |
1193 |
toler = int(configs[0].value) if configs else 20 |
1194 |
for symbol in app_doc_data.symbols: |
1195 |
matches = [it for it in app_doc_data.symbols if |
1196 |
it is not symbol and symbol.is_connectable(it, toler=toler)] |
1197 |
for match in matches: |
1198 |
symbol.connect_if_possible(match) |
1199 |
except Exception as ex: |
1200 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1201 |
sys.exc_info()[-1].tb_lineno)
|
1202 |
worker.displayLog.emit(MessageType.Error, message) |
1203 |
# up to here
|
1204 | |
1205 |
listWidget.addItem('Connecting lines')
|
1206 |
#area = app_doc_data.getArea('Drawing')
|
1207 |
detector = LineDetector(app_doc_data.imgSrc) |
1208 |
symbols = app_doc_data.symbols |
1209 |
configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line') |
1210 |
toler = int(configs[0].value) if configs else 20 |
1211 |
if app_doc_data.lines:
|
1212 |
# connect line to symbol
|
1213 |
try:
|
1214 |
for line in app_doc_data.lines: |
1215 |
matches = [_symbol for _symbol in symbols if _symbol.is_connectable(line, toler=toler)] |
1216 |
for _symbol in matches: |
1217 |
detector.connectLineToSymbol(line, _symbol, toler=toler) |
1218 |
except Exception as ex: |
1219 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[
|
1220 |
-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno) |
1221 |
worker.displayLog.emit(MessageType.Error, message) |
1222 |
# up to here
|
1223 | |
1224 |
# connect line to line
|
1225 |
try:
|
1226 |
for line in app_doc_data.lines: |
1227 |
matches = [it for it in app_doc_data.lines if |
1228 |
(it is not line) and (not line.isParallel(it))] |
1229 | |
1230 |
for match in matches: |
1231 |
detector.connectLineToLine(match, line, toler) |
1232 | |
1233 |
# change line type using symbol connection type(info)
|
1234 |
for sym in symbols: |
1235 |
if sym.conn_type:
|
1236 |
for index in range(len(sym.conn_type)): |
1237 |
item = sym.connectors[index].connectedItem |
1238 |
if item and type(item) is QEngineeringLineItem: |
1239 |
Worker.changeConnectedLineType(item, sym.conn_type[index]) |
1240 |
except Exception as ex: |
1241 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
1242 |
f"{sys.exc_info()[-1].tb_lineno}"
|
1243 |
worker.displayLog.emit(MessageType.Error, message) |
1244 |
# up to here
|
1245 | |
1246 |
# remove line has not connected item
|
1247 |
try:
|
1248 |
count = 1
|
1249 |
while count > 0: |
1250 |
count = 0
|
1251 |
for line in reversed(app_doc_data.lines): |
1252 |
if not line.has_connection: |
1253 |
app_doc_data.lines.remove(line) |
1254 |
app_doc_data.allItems.remove(line) |
1255 |
count += 1
|
1256 |
for item in app_doc_data.lines + symbols: |
1257 |
for conn in item.connectors: |
1258 |
if conn.connectedItem is line: |
1259 |
conn.connectedItem = None
|
1260 |
except Exception as ex: |
1261 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
1262 |
f"{sys.exc_info()[-1].tb_lineno}"
|
1263 |
worker.displayLog.emit(MessageType.Error, message) |
1264 |
# up to here
|
1265 | |
1266 |
worker.create_unknown_items(mainRes) |
1267 |
worker.add_detected_items_to_scene.emit(worker.scene) |
1268 |
worker.cond.wait(worker.mutex) |
1269 | |
1270 |
worker.scene._end = False
|
1271 |
# clear drawing
|
1272 |
app_doc_data.activeDrawing.image = None
|
1273 | |
1274 |
worker.updateBatchProgress.emit(len(drawings), 1) |
1275 |
except Exception as ex: |
1276 |
message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
|
1277 |
f"{sys.exc_info()[-1].tb_lineno}"
|
1278 |
worker.displayLog.emit(MessageType.Error, message) |
1279 |
finally:
|
1280 |
pass
|
1281 | |
1282 |
@staticmethod
|
1283 |
def changeConnectedLineType(line, lineType): |
1284 |
line.lineType = lineType |
1285 |
if type(line.connectors[0].connectedItem) is QEngineeringLineItem and \ |
1286 |
(line.connectors[0].connectedItem.connectors[0].connectedItem is line or |
1287 |
line.connectors[0].connectedItem.connectors[1].connectedItem is line) and \ |
1288 |
line.connectors[0].connectedItem.lineType is not lineType: |
1289 |
Worker.changeConnectedLineType(line.connectors[0].connectedItem, lineType)
|
1290 |
if type(line.connectors[1].connectedItem) is QEngineeringLineItem and \ |
1291 |
(line.connectors[1].connectedItem.connectors[0].connectedItem is line or |
1292 |
line.connectors[1].connectedItem.connectors[1].connectedItem is line) and \ |
1293 |
line.connectors[1].connectedItem.lineType is not lineType: |
1294 |
Worker.changeConnectedLineType(line.connectors[1].connectedItem, lineType)
|
1295 | |
1296 |
'''
|
1297 |
@history 2018.05.25 Jeongwoo Moved from MainWindow
|
1298 |
2018.05.28 Jeongwoo Add xmlPath Parameter and append LineInfo into xml
|
1299 |
2018.05.29 Jeongwoo Change method to add item
|
1300 |
2018.05.30 Jeongwoo Remove parameter (xmlPath)
|
1301 |
humkyung 2018.06.11 add drawing path to parameter and write recognized lines to image
|
1302 |
humkyung 2018.07.04 call arrangeLinePosition after creating line
|
1303 |
'''
|
1304 | |
1305 |
@staticmethod
|
1306 |
def recognizeLine(path, listWidget, graphicsView, worker): |
1307 |
from shapely.geometry import Point, LineString |
1308 |
from SymbolSvgItem import SymbolSvgItem |
1309 |
from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem |
1310 |
from EngineeringLineNoTextItem import QEngineeringLineNoTextItem |
1311 |
from EngineeringTextItem import QEngineeringTextItem |
1312 |
from EngineeringLineItem import QEngineeringLineItem |
1313 |
from LineDetector import LineDetector |
1314 |
from EngineeringVendorItem import QEngineeringVendorItem |
1315 | |
1316 |
try:
|
1317 |
listWidget.addItem('Starting line recognition')
|
1318 |
worker.displayTitle.emit(worker.tr('Detecting lines...'))
|
1319 | |
1320 |
app_doc_data = AppDocData.instance() |
1321 |
project = app_doc_data.getCurrentProject() |
1322 | |
1323 |
# detect line
|
1324 |
connectedLines = [] |
1325 | |
1326 |
area = app_doc_data.getArea('Drawing')
|
1327 |
if area is not None: |
1328 |
area.img = app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height), |
1329 |
round(area.x + 1):round(area.x + area.width)] |
1330 | |
1331 |
for item in worker.scene.items(): |
1332 |
if issubclass(type(item), QEngineeringVendorItem) and item.pack_type =='Equipment Package': |
1333 |
points = [] |
1334 |
for conn in item.connectors: |
1335 |
points.append([conn.center()[0] - area.x, conn.center()[1] - area.y]) |
1336 |
|
1337 |
points = np.array(points, np.int32) |
1338 |
cv2.fillConvexPoly(area.img, points, 255)
|
1339 | |
1340 |
area.img = worker.remove_small_objects(area.img) |
1341 |
detector = LineDetector(area.img) |
1342 | |
1343 |
symbols = [] |
1344 |
for item in app_doc_data.symbols: |
1345 |
if issubclass(type(item), SymbolSvgItem): |
1346 |
symbols.append(item) |
1347 |
res = detector.detectConnectedLine(item, round(area.x), round(area.y)) |
1348 |
if res is not None: |
1349 |
connectedLines.extend(res) |
1350 | |
1351 |
# line detection without symbol connection point info
|
1352 |
configs = app_doc_data.getConfigs('Line', 'Gap') |
1353 |
if configs and int(configs[0].value) is 1: |
1354 |
remainLines = detector.detect_line_without_symbol() |
1355 |
windowSize = app_doc_data.getSlidingWindowSize() |
1356 |
thickness = int(windowSize[1] / 2) |
1357 | |
1358 |
for line in remainLines: |
1359 |
line.append(thickness) |
1360 |
connectedLines.extend(remainLines) |
1361 | |
1362 |
configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line') |
1363 |
toler = int(configs[0].value) if configs else 20 |
1364 | |
1365 |
symbol_areas = [] |
1366 |
for symbol in symbols: |
1367 |
symbol_areas.append(QRect(symbol.loc[0] - area.x, symbol.loc[1] - area.y, symbol.size[0], symbol.size[1])) |
1368 | |
1369 |
detector.mergeLines(connectedLines, toler=toler, symbol_areas=symbol_areas) |
1370 | |
1371 |
for pts in connectedLines: |
1372 |
line = QEngineeringLineItem( |
1373 |
vertices=[(area.x + param[0], area.y + param[1]) for param in pts[:-1]], thickness=pts[2]) |
1374 |
line.area = 'Drawing'
|
1375 | |
1376 |
app_doc_data.lines.append(line) |
1377 |
app_doc_data.allItems.append(line) |
1378 |
except Exception as ex: |
1379 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1380 |
sys.exc_info()[-1].tb_lineno)
|
1381 |
worker.displayLog.emit(MessageType.Error, message) |
1382 |
finally:
|
1383 |
listWidget.addItem('Finishing line recognition')
|
1384 |
worker.finished.emit() |
1385 | |
1386 |
'''
|
1387 |
@history 2018.04.24 Jeongwoo Add isExceptDetect Field
|
1388 |
2018.05.09 Jeongwoo Add targetSymbolList clear
|
1389 |
humkyung 2018.07.07 store symbols to list as like [equipments],[nozzles],[symbols]
|
1390 |
'''
|
1391 | |
1392 |
@staticmethod
|
1393 |
def initTargetSymbolDataList(): |
1394 |
global targetSymbolList
|
1395 | |
1396 |
targetSymbolList.clear() |
1397 |
app_doc_data = AppDocData.instance() |
1398 |
symbolList = app_doc_data.getTargetSymbolList() |
1399 |
equipments = [item for item in symbolList if app_doc_data.getSymbolCategoryByType(item.getType()) == 'Equipment'] |
1400 |
nozzles = [item for item in symbolList if item.getType() == 'Nozzles'] |
1401 |
# [[equipments],[nozzles],[symbols]]
|
1402 |
targetSymbolList.append(equipments) |
1403 |
targetSymbolList.append(nozzles) |
1404 |
targetSymbolList.append([item for item in symbolList if item not in equipments and item not in nozzles]) |
1405 | |
1406 |
return targetSymbolList
|
1407 | |
1408 |
'''
|
1409 |
@brief detect equipment
|
1410 |
@author humkyung
|
1411 |
@date 2018.07.07
|
1412 |
'''
|
1413 | |
1414 |
@staticmethod
|
1415 |
def detect_nozzles(mainRes, targetSymbol, listWidget, worker): |
1416 |
res = [] |
1417 |
try:
|
1418 |
app_doc_data = AppDocData.instance() |
1419 | |
1420 |
nozzles = Worker.detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker) |
1421 |
equipments = [symbol for symbol in searchedSymbolList if app_doc_data.isEquipmentType(symbol.getType())] |
1422 |
for equipment in equipments: |
1423 |
rect = QRectF(equipment.sp[0], equipment.sp[1], equipment.width, equipment.height) |
1424 |
rect.adjust(-equipment.width*0.1, -equipment.height*0.1, equipment.width*0.1, equipment.height*0.1) |
1425 |
matches = [nozzle for nozzle in nozzles if rect.contains(nozzle.rect)] |
1426 |
res.extend(matches) |
1427 |
for match in matches: |
1428 |
nozzles.remove(match) |
1429 |
except Exception as ex: |
1430 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1431 |
sys.exc_info()[-1].tb_lineno)
|
1432 |
worker.displayLog(MessageType.Error, message) |
1433 | |
1434 |
return res
|
1435 | |
1436 |
'''
|
1437 |
@brief detect symbol on PID
|
1438 |
@author jwkim
|
1439 |
@date
|
1440 |
@history humkyung 2018.04.06 check if symbol file exists
|
1441 |
Jeongwoo 2018.05.29 Change method to adjust detail symbol location with hit-rate. Not feature point count
|
1442 |
Change parameter on add symbol part (mpCount → hitRate)
|
1443 |
Remove unusing calculation (avg)
|
1444 |
Jeongwoo 2018.06.27 Remove part to split P&ID image and for loop
|
1445 |
humkyung 2018.07.07 return searched symbols
|
1446 |
'''
|
1447 | |
1448 |
@staticmethod
|
1449 |
def detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker): |
1450 |
import copy |
1451 |
global ocrCompletedSrc
|
1452 |
global threadLock
|
1453 |
global maxProgressValue
|
1454 | |
1455 |
try:
|
1456 |
forTraining = worker.isTrainingChecked |
1457 |
symbolName = targetSymbol.getName() |
1458 |
symbolType = targetSymbol.getType() |
1459 |
symbolPath = targetSymbol.getPath() |
1460 |
symbolThreshold = targetSymbol.getThreshold() # if not forTraining else targetSymbol.getThreshold() / 3 * 2
|
1461 |
symbolMinMatchCount = targetSymbol.getMinMatchCount() |
1462 |
isDetectOnOrigin = targetSymbol.getIsDetectOnOrigin() |
1463 |
symbolRotateCount = targetSymbol.getRotationCount() |
1464 |
symbolOcrOption = targetSymbol.getOcrOption() |
1465 |
isContainChild = targetSymbol.getIsContainChild() |
1466 |
symbolOriginalPoint = targetSymbol.getOriginalPoint() |
1467 |
symbolConnectionPoint = targetSymbol.getConnectionPoint() |
1468 |
baseSymbol = targetSymbol.getBaseSymbol() |
1469 |
additionalSymbol = targetSymbol.getAdditionalSymbol() |
1470 |
isExceptDetect = targetSymbol.getIsExceptDetect() |
1471 |
detectFlip = targetSymbol.getDetectFlip() |
1472 |
hasInstrumentLabel = targetSymbol.getHasInstrumentLabel() |
1473 |
text_area = targetSymbol.getText_area() |
1474 | |
1475 |
# check if symbol file is target or not
|
1476 |
if isExceptDetect == 1: |
1477 |
item = QListWidgetItem('{} file is not target'.format(symbolName))
|
1478 |
item.setBackground(QColor('green'))
|
1479 |
listWidget.addItem(item) |
1480 |
return
|
1481 | |
1482 |
foundSymbolCount = 0
|
1483 | |
1484 |
# check if symbol file exists
|
1485 |
if not os.path.isfile(symbolPath): |
1486 |
item = QListWidgetItem('{} file not found'.format(symbolName))
|
1487 |
item.setBackground(QColor('red'))
|
1488 |
listWidget.addItem(item) |
1489 |
return
|
1490 |
# up to here
|
1491 | |
1492 |
sym = cv2.imread(symbolPath, 1)
|
1493 |
symGray = Worker.cvtGrayImage(sym) |
1494 |
symGrayOri = copy.copy(symGray) |
1495 |
## TODO: 이진화 시켰을때 심볼이 검출되지 않음
|
1496 |
## symGray = cv2.threshold(cvtGrayImage(sym), 127, 255, cv2.THRESH_BINARY)[1]
|
1497 |
## cv2.imshow('symbol', symGray)
|
1498 |
## cv2.waitKey(0)
|
1499 |
sow, soh = symGray.shape[::-1] # symbol original w, h |
1500 | |
1501 |
offsetDrawingArea = [] |
1502 |
app_doc_data = AppDocData.instance() |
1503 |
area = app_doc_data.getArea('Drawing')
|
1504 |
if area is not None: |
1505 |
roiItem = Worker.remove_small_objects(area.img, 0, sow * soh * 0.5) if hasInstrumentLabel else area.img |
1506 |
offsetDrawingArea.append(area.x) |
1507 |
offsetDrawingArea.append(area.y) |
1508 |
else:
|
1509 |
offsetDrawingArea.append(0)
|
1510 |
offsetDrawingArea.append(0)
|
1511 |
if isDetectOnOrigin == 1: |
1512 |
roiItem = app_doc_data.imgSrc |
1513 |
else:
|
1514 |
roiItem = ocrCompletedSrc |
1515 |
srcWidth, srcHeight = roiItem.shape[::-1]
|
1516 | |
1517 |
roiItemSp = (0, 0) |
1518 |
roiItemEp = (srcWidth, srcHeight) |
1519 | |
1520 |
# try to recognize symbol twice(first one is normal, second one is flipped)
|
1521 |
steps = [False, True] if detectFlip else [False] |
1522 |
for flipped in steps: |
1523 |
if flipped:
|
1524 |
symGray = symGrayOri |
1525 |
symGray = cv2.flip(symGray, 1)
|
1526 | |
1527 |
symbolRotatedAngle = 0
|
1528 |
for rc in range(symbolRotateCount + 1): # Rotation count를 사용자 기준으로 받아서 1을 더한 후 사용 |
1529 |
sw, sh = symGray.shape[::-1]
|
1530 |
roiw = (roiItemEp[0] - roiItemSp[0]) |
1531 |
roih = (roiItemEp[1] - roiItemSp[1]) |
1532 | |
1533 |
# Case : symbol is bigger than roi
|
1534 |
if roiw < sw or roih < sh: |
1535 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE) |
1536 |
symbolRotatedAngle = (symbolRotatedAngle + 90) % 360 |
1537 | |
1538 |
if baseSymbol is not None and additionalSymbol is not None: |
1539 |
additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol) |
1540 |
continue
|
1541 | |
1542 |
# get Rotated Original Point
|
1543 |
originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, |
1544 |
symbolRotatedAngle, sw, sh, sow, soh, flipped) |
1545 |
connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw, |
1546 |
sh, sow, soh, flipped) |
1547 | |
1548 |
# For OPC
|
1549 |
drawing_area = app_doc_data.getArea('Drawing')
|
1550 |
if drawing_area is not None and (symbolType == "Piping OPC\'s" or symbolType == "Instrument OPC\'s"): |
1551 |
customMatch = worker.detectOPCOnPid(drawing_area, symGray) |
1552 |
if customMatch and len(customMatch) > 0: |
1553 |
for custom in customMatch: |
1554 |
hitRate = custom[0]
|
1555 |
searchedItemSp = (custom[1][0] + round(offsetDrawingArea[0]), custom[1][1] + round(offsetDrawingArea[1])) |
1556 | |
1557 |
is_add = True
|
1558 |
for searched in searchedSymbolList: |
1559 |
if Worker.IsOverlap((searchedItemSp[0], searchedItemSp[1], sw, sh), ( |
1560 |
searched.getSp()[0], searched.getSp()[1], searched.getWidth(), |
1561 |
searched.getHeight())): |
1562 |
if searched.getHitRate() > hitRate:
|
1563 |
is_add = False
|
1564 |
else:
|
1565 |
searchedSymbolList.remove(searched) |
1566 |
break
|
1567 | |
1568 |
if is_add:
|
1569 |
threadLock.acquire() |
1570 |
foundSymbolCount = foundSymbolCount + 1
|
1571 |
Worker.addSearchedSymbol(symbolName, symbolType, |
1572 |
searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, |
1573 |
hitRate, symbolRotatedAngle, |
1574 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, |
1575 |
isContainChild, |
1576 |
originalPoint, connectionPoint, baseSymbol, additionalSymbol, |
1577 |
isExceptDetect, detectFlip=1 if flipped else 0, |
1578 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
1579 |
threadLock.release() |
1580 | |
1581 |
# Template Matching
|
1582 |
tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED) |
1583 |
loc = np.where(tmRes >= symbolThreshold) |
1584 | |
1585 |
for pt in zip(*loc[::-1]): |
1586 |
'''
|
1587 |
# no more used
|
1588 |
mpCount = 0 # Match Point Count
|
1589 |
roi = roiItem[pt[1]:pt[1] + sh, pt[0]:pt[0] + sw]
|
1590 |
|
1591 |
if symbolMinMatchCount > 0:
|
1592 |
mpCount = Worker.getMatchPointCount(roi, symGray)
|
1593 |
if not (mpCount >= symbolMinMatchCount):
|
1594 |
continue
|
1595 |
'''
|
1596 | |
1597 |
searchedItemSp = (roiItemSp[0] + pt[0] + round(offsetDrawingArea[0]), |
1598 |
roiItemSp[1] + pt[1] + round(offsetDrawingArea[1])) |
1599 |
# print(searchedItemSp)
|
1600 | |
1601 |
overlapArea = 0
|
1602 |
symbolIndex = -1
|
1603 |
for i in range(len(searchedSymbolList) - 1, -1, -1): |
1604 |
area = Worker.contains(searchedSymbolList[i], searchedItemSp, sw, sh) |
1605 |
if area > ACCEPT_OVERLAY_AREA:
|
1606 |
# if area > overlapArea:
|
1607 |
# overlapArea = area
|
1608 |
# symbolIndex = i
|
1609 |
overlapArea = area |
1610 |
symbolIndex = i |
1611 |
break
|
1612 |
"""
|
1613 |
categories = [appDocData.isEquipmentType(symbolType), appDocData.isEquipmentType(searchedSymbolList[i].getType())]
|
1614 |
if categories[0] == categories[1]:
|
1615 |
symbolIndex = i
|
1616 |
break
|
1617 |
"""
|
1618 | |
1619 |
hitRate = tmRes[pt[1], pt[0]] |
1620 | |
1621 |
# 겹치는 영역이 기준값보다 작을 경우
|
1622 |
if overlapArea <= ACCEPT_OVERLAY_AREA:
|
1623 |
threadLock.acquire() |
1624 |
foundSymbolCount = foundSymbolCount + 1
|
1625 |
Worker.addSearchedSymbol(symbolName, symbolType, |
1626 |
searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, |
1627 |
hitRate, symbolRotatedAngle, |
1628 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, |
1629 |
isContainChild, |
1630 |
originalPoint, connectionPoint, baseSymbol, additionalSymbol, |
1631 |
isExceptDetect, |
1632 |
detectFlip=1 if flipped else 0, |
1633 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
1634 |
threadLock.release() |
1635 |
else: # 겹치는 영역이 기준값보다 클 경우 |
1636 |
if symbolIndex != -1 and symbolIndex < len(searchedSymbolList): |
1637 |
searchedSymbol = searchedSymbolList[symbolIndex] |
1638 |
# 현재 심볼과 검출된 심볼이 같을 경우 Match Point가 더 높은 정보로 교체
|
1639 |
if symbolName == searchedSymbol.getName():
|
1640 |
symbolHitRate = searchedSymbol.getHitRate() |
1641 |
if symbolHitRate - searchedSymbol.getThreshold() < hitRate - symbolThreshold:
|
1642 |
threadLock.acquire() |
1643 |
# replace existing symbol with new symbol has high accuracy
|
1644 |
searchedSymbolList[symbolIndex] = symbol.Symbol(symbolName, symbolType, |
1645 |
searchedItemSp, sw, sh, |
1646 |
symbolThreshold, |
1647 |
symbolMinMatchCount, hitRate, |
1648 |
symbolRotatedAngle, |
1649 |
isDetectOnOrigin, |
1650 |
symbolRotateCount, |
1651 |
symbolOcrOption, isContainChild, |
1652 |
','.join(str(x) for x in |
1653 |
originalPoint), |
1654 |
'/'.join('{},{},{},{}'.format( |
1655 |
param[0], param[1], |
1656 |
param[2], param[3]) for |
1657 |
param in
|
1658 |
connectionPoint), |
1659 |
baseSymbol, additionalSymbol, |
1660 |
isExceptDetect, |
1661 |
detectFlip=1 if flipped else 0, |
1662 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
1663 |
threadLock.release() |
1664 |
# 현재 심볼과 검출된 심볼이 같지 않을 경우 (포함)
|
1665 |
elif app_doc_data.isEquipmentType(searchedSymbol.getType()) and not app_doc_data.isEquipmentType(symbolType): |
1666 |
if searchedSymbol.area > sw * sh * 10: # searched equipment area is greather than 10 times of symbol's area |
1667 |
threadLock.acquire() |
1668 |
foundSymbolCount = foundSymbolCount + 1
|
1669 |
Worker.addSearchedSymbol(symbolName, symbolType, |
1670 |
searchedItemSp, sw, sh, symbolThreshold, hitRate, |
1671 |
hitRate, symbolRotatedAngle, |
1672 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, |
1673 |
isContainChild, |
1674 |
originalPoint, connectionPoint, baseSymbol, |
1675 |
additionalSymbol, isExceptDetect, |
1676 |
detectFlip=1 if flipped else 0, |
1677 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
1678 |
threadLock.release() |
1679 |
# 현재 심볼과 검출된 심볼이 같지 않을 경우 (교체)
|
1680 |
elif not forTraining: |
1681 |
searchedSymbol = searchedSymbolList[symbolIndex] |
1682 |
symbolHitRate = searchedSymbol.getHitRate() |
1683 |
if symbolHitRate - searchedSymbol.getThreshold() < hitRate - symbolThreshold:
|
1684 |
threadLock.acquire() |
1685 |
searchedSymbolList[symbolIndex] = symbol.Symbol(symbolName, symbolType, |
1686 |
searchedItemSp, sw, sh, |
1687 |
symbolThreshold, |
1688 |
symbolMinMatchCount, hitRate, |
1689 |
symbolRotatedAngle, |
1690 |
isDetectOnOrigin, |
1691 |
symbolRotateCount, |
1692 |
symbolOcrOption, isContainChild, |
1693 |
','.join(str(x) for x in |
1694 |
originalPoint), |
1695 |
'/'.join('{},{},{},{}'.format( |
1696 |
param[0], param[1], |
1697 |
param[2], param[3]) for |
1698 |
param in
|
1699 |
connectionPoint), |
1700 |
baseSymbol, additionalSymbol, |
1701 |
isExceptDetect, |
1702 |
detectFlip=1 if flipped else 0, |
1703 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
1704 |
threadLock.release() |
1705 |
# 학습용 데이터 생성을 위해 교체하지 않고 추가함
|
1706 |
elif forTraining:
|
1707 |
threadLock.acquire() |
1708 |
foundSymbolCount = foundSymbolCount + 1
|
1709 |
Worker.addSearchedSymbol(symbolName, symbolType, |
1710 |
searchedItemSp, sw, sh, symbolThreshold, |
1711 |
symbolMinMatchCount, hitRate, symbolRotatedAngle, |
1712 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, |
1713 |
isContainChild, |
1714 |
originalPoint, connectionPoint, baseSymbol, |
1715 |
additionalSymbol, isExceptDetect, |
1716 |
detectFlip=1 if flipped else 0, |
1717 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
1718 |
threadLock.release() |
1719 | |
1720 |
# rotate symbol
|
1721 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE) |
1722 |
symbolRotatedAngle = (symbolRotatedAngle + 90) % 360 |
1723 | |
1724 |
if additionalSymbol is not None: |
1725 |
additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol) |
1726 | |
1727 |
threadLock.acquire() |
1728 |
listWidget.addItem('Found Symbol : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str( |
1729 |
foundSymbolCount) + ')')
|
1730 |
threadLock.release() |
1731 | |
1732 |
"""
|
1733 |
if area is not None and hasInstrumentLabel:
|
1734 |
# restore objects smaller than symbol
|
1735 |
roiItem = cv2.drawContours(roiItem, outside_contours, -1, (0, 0, 0), -1)
|
1736 |
roiItem = cv2.drawContours(roiItem, hole_contours, -1, (255, 255, 255), -1)
|
1737 |
# up to here
|
1738 |
cv2.imwrite('c:\\Temp\\contour2.png', roiItem)
|
1739 |
"""
|
1740 | |
1741 |
worker.updateProgress.emit(maxProgressValue, symbolPath) |
1742 | |
1743 |
return [symbol for symbol in searchedSymbolList if symbol.getName() == symbolName] |
1744 |
except Exception as ex: |
1745 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1746 |
sys.exc_info()[-1].tb_lineno)
|
1747 |
worker.displayLog.emit(MessageType.Error, message) |
1748 | |
1749 |
return []
|
1750 | |
1751 |
@staticmethod
|
1752 |
def IsOverlap(range1, range2): |
1753 |
if range1[0] <= range2[0] + range2[2] and range1[0] + range1[2] >= range2[0] and range1[1] <= range2[1] + \ |
1754 |
range2[3] and range1[1] + range1[3] >= range2[1]: |
1755 |
range = (min(range1[0] + range1[2], range2[0] + range2[2]) - max(range1[0], range2[0])) * ( |
1756 |
min(range1[1] + range1[3], range2[1] + range2[3]) - max(range1[1], range2[1])) |
1757 |
if range >= range1[2] * range1[3] * 0.4 and range >= range2[2] * range2[3] * 0.4: |
1758 |
return True |
1759 |
else:
|
1760 |
return False |
1761 |
else:
|
1762 |
return False |
1763 | |
1764 |
@staticmethod
|
1765 |
def detectOPCOnPid(area, symGray): |
1766 |
results = [] |
1767 | |
1768 |
try:
|
1769 |
symbol = cv2.copyMakeBorder(symGray, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=255) |
1770 |
not_symbol = cv2.bitwise_not(symbol) |
1771 |
symbol_contours, symbol_hierachy = cv2.findContours(not_symbol, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) |
1772 |
if symbol_hierachy[0][0][0] != -1: |
1773 |
return results
|
1774 | |
1775 |
contour_count = len(symbol_contours)
|
1776 |
if contour_count != 2: |
1777 |
return results
|
1778 | |
1779 |
for i in range(1, contour_count): |
1780 |
# contour size
|
1781 |
symbol_area = cv2.contourArea(symbol_contours[i]) |
1782 |
# moments mass center
|
1783 |
symbol_moments = cv2.moments(symbol_contours[i]) |
1784 |
symbol_x = int(symbol_moments['m10'] / (symbol_moments['m00'] + 1e-5)) |
1785 |
symbol_y = int(symbol_moments['m01'] / (symbol_moments['m00'] + 1e-5)) |
1786 |
rect_x, rect_y, rect_w, rect_h = cv2.boundingRect(symbol_contours[i]) |
1787 |
symbol_x = symbol_x - rect_x |
1788 |
symbol_y = symbol_y - rect_y |
1789 |
# percent x, y
|
1790 |
percent_x = symbol_x / rect_w |
1791 |
percent_y = symbol_y / rect_h |
1792 | |
1793 |
for contour in area.contours: |
1794 |
area_area = cv2.contourArea(contour) |
1795 |
if area_area * 1.2 >= symbol_area >= area_area * 0.8: |
1796 |
I1 = cv2.matchShapes(symbol_contours[i], contour, 1, 0) |
1797 |
I2 = cv2.matchShapes(symbol_contours[i], contour, 2, 0) |
1798 |
I3 = cv2.matchShapes(symbol_contours[i], contour, 3, 0) |
1799 |
if I1 < 1 and I2 < 1 and I3 < 0.1: |
1800 |
rect_x2, rect_y2, rect_w2, rect_h2 = cv2.boundingRect(contour) |
1801 |
if rect_w * 1.2 >= rect_w2 >= rect_w * 0.8 and rect_h * 1.2 >= rect_h2 >= rect_h * 0.8: |
1802 |
# moments mass center
|
1803 |
moments = cv2.moments(contour) |
1804 |
x = int(moments['m10'] / (moments['m00'] + 1e-5)) |
1805 |
y = int(moments['m01'] / (moments['m00'] + 1e-5)) |
1806 | |
1807 |
x = x - rect_x2 |
1808 |
y = y - rect_y2 |
1809 |
percent_x2 = x / rect_w2 |
1810 |
percent_y2 = y / rect_h2 |
1811 | |
1812 |
value_x = abs(percent_x - percent_x2)
|
1813 |
value_y = abs(percent_y - percent_y2)
|
1814 | |
1815 |
results.append([1 - (value_x + value_y), [rect_x2, rect_y2]])
|
1816 |
break
|
1817 |
except Exception as ex: |
1818 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1819 |
sys.exc_info()[-1].tb_lineno)
|
1820 |
return results
|
1821 | |
1822 |
# Convert into Grayscale image
|
1823 |
@staticmethod
|
1824 |
def cvtGrayImage(img): |
1825 |
return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
1826 | |
1827 |
'''
|
1828 |
@history 2018.06.12 Jeongwoo Type changed (int → float)
|
1829 |
humkyung 2018.07.07 change return type as like [x,y]
|
1830 |
'''
|
1831 | |
1832 |
@staticmethod
|
1833 |
def getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolRotatedAngle, rotateSymbolWidth, |
1834 |
rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight, flipped=False):
|
1835 |
res = [] |
1836 | |
1837 |
if additionalSymbol is None and symbolOriginalPoint is None: |
1838 |
res.append(rotateSymbolWidth // 2)
|
1839 |
res.append(rotateSymbolHeight // 2)
|
1840 |
else:
|
1841 |
if flipped:
|
1842 |
opx = originalSymbolWidth - float(symbolOriginalPoint.split(',')[0]) |
1843 |
opy = float(symbolOriginalPoint.split(',')[1]) |
1844 |
else:
|
1845 |
opx = float(symbolOriginalPoint.split(',')[0]) |
1846 |
opy = float(symbolOriginalPoint.split(',')[1]) |
1847 | |
1848 |
rPt = Worker.getCoordOnRotatedImage(symbolRotatedAngle, ('AUTO', opx, opy, '0'), originalSymbolWidth, |
1849 |
originalSymbolHeight) |
1850 | |
1851 |
res.append(rPt[1])
|
1852 |
res.append(rPt[2])
|
1853 | |
1854 |
return res
|
1855 | |
1856 |
'''
|
1857 |
@history 2018.06.12 Jeongwoo Type changed (int → float)
|
1858 |
humkyung 2018.07.07 change return type as like [[x,y],...]
|
1859 |
humkyung 2019.01.04 get symbol index
|
1860 |
'''
|
1861 | |
1862 |
@staticmethod
|
1863 |
def getCalculatedConnectionPoint(symbolConnectionPointStr, symbolRotatedAngle, rotateSymbolWidth, |
1864 |
rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight, flipped=0):
|
1865 |
res = [] |
1866 | |
1867 |
if symbolConnectionPointStr is not None and symbolConnectionPointStr != '': |
1868 |
splitConnectionPointStr = symbolConnectionPointStr.split("/")
|
1869 |
for strConnPt in splitConnectionPointStr: |
1870 |
tokens = strConnPt.split(',')
|
1871 | |
1872 |
direction = 'AUTO'
|
1873 |
symbol_idx = '0'
|
1874 |
if flipped:
|
1875 |
converted = {'AUTO': 'AUTO', 'LEFT': 'RIGHT', 'RIGHT': 'LEFT', 'UP': 'UP', 'DOWN': 'DOWN'} |
1876 | |
1877 |
if len(tokens) == 2: |
1878 |
cpx = originalSymbolWidth - float(tokens[0]) |
1879 |
cpy = float(tokens[1]) |
1880 |
elif len(tokens) == 3: |
1881 |
direction = converted[tokens[0]]
|
1882 |
cpx = originalSymbolWidth - float(tokens[1]) |
1883 |
cpy = float(tokens[2]) |
1884 |
elif len(tokens) >= 4: |
1885 |
direction = converted[tokens[0]]
|
1886 |
cpx = originalSymbolWidth - float(tokens[1]) |
1887 |
cpy = float(tokens[2]) |
1888 |
symbol_idx = tokens[3]
|
1889 |
else:
|
1890 |
if len(tokens) == 2: |
1891 |
cpx = float(tokens[0]) |
1892 |
cpy = float(tokens[1]) |
1893 |
elif len(tokens) == 3: |
1894 |
direction = tokens[0]
|
1895 |
cpx = float(tokens[1]) |
1896 |
cpy = float(tokens[2]) |
1897 |
elif len(tokens) >= 4: |
1898 |
direction = tokens[0]
|
1899 |
cpx = float(tokens[1]) |
1900 |
cpy = float(tokens[2]) |
1901 |
symbol_idx = tokens[3]
|
1902 | |
1903 |
res.append(Worker.getCoordOnRotatedImage(symbolRotatedAngle, (direction, cpx, cpy, symbol_idx), |
1904 |
originalSymbolWidth, originalSymbolHeight)) |
1905 | |
1906 |
return res
|
1907 | |
1908 |
'''
|
1909 |
@brief rotate (x,y) by given angle
|
1910 |
@author Jeongwoo
|
1911 |
@date 2018.??.??
|
1912 |
@history humkyung 2018.04.13 fixed code when angle is 90 or 270
|
1913 |
Jeongwoo 2018.04.27 Change calculation method with QTransform
|
1914 |
humkyung 2018.09.01 calculate rotated direction
|
1915 |
'''
|
1916 | |
1917 |
@staticmethod
|
1918 |
def getCoordOnRotatedImage(angle, connPt, originImageWidth, originImageHeight): |
1919 |
rx = None
|
1920 |
ry = None
|
1921 | |
1922 |
# calculate rotated direction
|
1923 |
direction = connPt[0]
|
1924 |
if direction == 'LEFT': |
1925 |
direction = 'DOWN' if angle == 90 else 'RIGHT' if angle == 180 else 'UP' if angle == 270 else direction |
1926 |
elif direction == 'RIGHT': |
1927 |
direction = 'UP' if angle == 90 else 'LEFT' if angle == 180 else 'DOWN' if angle == 270 else direction |
1928 |
elif direction == 'UP': |
1929 |
direction = 'LEFT' if angle == 90 else 'DOWN' if angle == 180 else 'RIGHT' if angle == 270 else direction |
1930 |
elif direction == 'DOWN': |
1931 |
direction = 'RIGHT' if angle == 90 else 'UP' if angle == 180 else 'LEFT' if angle == 270 else direction |
1932 |
# up to here
|
1933 | |
1934 |
transform = QTransform() |
1935 |
if angle == 90 or angle == 270: |
1936 |
transform.translate(originImageHeight * 0.5, originImageWidth * 0.5) |
1937 |
elif angle == 0 or angle == 180: |
1938 |
transform.translate(originImageWidth * 0.5, originImageHeight * 0.5) |
1939 |
transform.rotate(abs(angle))
|
1940 |
transform.translate(-originImageWidth * 0.5, -originImageHeight * 0.5) |
1941 |
point = QPoint(connPt[1], connPt[2]) |
1942 |
point = transform.map(point) |
1943 |
rx = point.x() |
1944 |
ry = point.y() |
1945 | |
1946 |
symbol_idx = connPt[3]
|
1947 | |
1948 |
return (direction, rx, ry, symbol_idx)
|
1949 | |
1950 |
'''
|
1951 |
@brief Add symbols
|
1952 |
@author jwkim
|
1953 |
@date
|
1954 |
@history Change parameter (mpCount → hitRate)
|
1955 |
Yecheol 2018.07.04 Delete Symbol Id
|
1956 |
'''
|
1957 | |
1958 |
@staticmethod
|
1959 |
def addSearchedSymbol(sName, sType |
1960 |
, sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle |
1961 |
, isDetectOnOrigin, rotateCount, ocrOption, isContainChild |
1962 |
, originalPoint, connectionPoint, baseSymbol, additionalSymbol, isExceptDetect, detectFlip |
1963 |
, hasInstrumentLabel, text_area): |
1964 |
global searchedSymbolList
|
1965 | |
1966 |
newSym = None
|
1967 |
try:
|
1968 |
newSym = symbol.Symbol(sName, sType |
1969 |
, sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle, |
1970 |
isDetectOnOrigin, rotateCount, ocrOption, isContainChild, |
1971 |
','.join(str(x) for x in originalPoint), |
1972 |
'/'.join('{},{},{},{}'.format(param[0], param[1], param[2], param[3]) for param in |
1973 |
connectionPoint), |
1974 |
baseSymbol, additionalSymbol, isExceptDetect, detectFlip=1 if detectFlip else 0, |
1975 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
1976 | |
1977 |
searchedSymbolList.append(newSym) |
1978 |
except Exception as ex: |
1979 |
from App import App |
1980 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1981 |
sys.exc_info()[-1].tb_lineno)
|
1982 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
1983 | |
1984 |
return newSym
|
1985 | |
1986 |
'''
|
1987 |
@brief Check object contains pt
|
1988 |
@param obj is item in searchedSymbolList
|
1989 |
'''
|
1990 | |
1991 |
@staticmethod
|
1992 |
def contains(obj, pt, tw, th): |
1993 |
sp = obj.getSp() |
1994 |
width = obj.getWidth() |
1995 |
height = obj.getHeight() |
1996 | |
1997 |
if sp[0] > pt[0] + tw: |
1998 |
return 0 |
1999 |
if sp[0] + width < pt[0]: |
2000 |
return 0 |
2001 |
if sp[1] > pt[1] + th: |
2002 |
return 0 |
2003 |
if sp[1] + height < pt[1]: |
2004 |
return 0 |
2005 | |
2006 |
# shared area
|
2007 |
x = max(sp[0], pt[0]) |
2008 |
y = max(sp[1], pt[1]) |
2009 |
w = min(sp[0] + width, pt[0] + tw) - x |
2010 |
h = min(sp[1] + height, pt[1] + th) - y |
2011 | |
2012 |
return float((w * h)) / float((tw * th)) * 100 |
2013 | |
2014 |
# Calculate count of keypoint match result
|
2015 |
@staticmethod
|
2016 |
def getMatchPointCount(src, cmp): |
2017 |
matchCount = 0
|
2018 | |
2019 |
try:
|
2020 |
orb = cv2.ORB_create(1000, 2.0, 2, 1) |
2021 | |
2022 |
kp1, des1 = orb.detectAndCompute(src, None)
|
2023 |
kp2, des2 = orb.detectAndCompute(cmp, None) |
2024 | |
2025 |
FLANN_INDEX_LSH = 6
|
2026 |
# table_number : The number of hash tables use
|
2027 |
# key_size : The length of the key in the hash tables
|
2028 |
# multi_probe_level : Number of levels to use in multi-probe (0 for standard LSH)
|
2029 |
# It controls how neighboring buckets are searched
|
2030 |
# Recommended value is 2
|
2031 |
# checks : specifies the maximum leafs to visit when searching for neighbours.
|
2032 |
# LSH : Locality-Sensitive Hashing
|
2033 |
# ref : https://www.cs.ubc.ca/research/flann/uploads/FLANN/flann_manual-1.8.4.pdf
|
2034 |
index_params = dict(algorithm=FLANN_INDEX_LSH, table_number=20, key_size=10, multi_probe_level=4) |
2035 |
search_params = dict(checks=100) |
2036 | |
2037 |
flann = cv2.FlannBasedMatcher(index_params, search_params) |
2038 | |
2039 |
matches = flann.knnMatch(des1, des2, k=2)
|
2040 |
matchesMask = [[0, 0] for i in range(len(matches))] # Python 3.x |
2041 | |
2042 |
count = 0
|
2043 |
# ratio test as per Lowe's paper
|
2044 |
for i in range(len(matches)): |
2045 |
if len(matches[i]) == 2: |
2046 |
m = matches[i][0]
|
2047 |
n = matches[i][1]
|
2048 |
if m.distance < 0.85 * n.distance: |
2049 |
count = count + 1
|
2050 | |
2051 |
matchCount = count |
2052 |
except Exception as ex: |
2053 |
from App import App |
2054 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
2055 |
sys.exc_info()[-1].tb_lineno)
|
2056 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
2057 | |
2058 |
return matchCount
|
2059 | |
2060 |
'''
|
2061 |
@brief Remake rotated child symbol info
|
2062 |
'''
|
2063 | |
2064 |
@staticmethod
|
2065 |
def getRotatedChildInfo(additionalSymbol): |
2066 |
tempChildInfo = ""
|
2067 |
if additionalSymbol:
|
2068 |
childList = additionalSymbol.split("/")
|
2069 |
for index in range(len(childList)): |
2070 |
child = childList[index] |
2071 |
direction = Worker.convertDirectionCodeToValue(child.split(",")[0]) |
2072 |
childName = child.split(",")[1] |
2073 |
direction = (direction - 1) if direction > 0 else 3 |
2074 |
if index != 0: |
2075 |
tempChildInfo = tempChildInfo + "/"
|
2076 |
tempChildInfo = tempChildInfo + Worker.convertValueToDirectionCode(direction) + "," + childName
|
2077 |
return tempChildInfo
|
2078 | |
2079 |
@staticmethod
|
2080 |
def calculate_exact_position(area, symGray, symbol, rect, worker): |
2081 |
import copy |
2082 | |
2083 |
try:
|
2084 |
symbolThreshold = symbol.getThreshold() |
2085 |
symbolRotateCount = symbol.getRotationCount() |
2086 |
detectFlip = symbol.getDetectFlip() |
2087 |
hasInstrumentLabel = symbol.getHasInstrumentLabel() |
2088 |
symbolOriginalPoint = symbol.getOriginalPoint() |
2089 |
symbolConnectionPoint = symbol.getConnectionPoint() |
2090 |
additionalSymbol = symbol.getAdditionalSymbol() |
2091 | |
2092 |
sow, soh = symGray.shape[::-1]
|
2093 | |
2094 |
# symbol is bigger than roi -> detected symbol area is too big
|
2095 |
if rect[3] * rect[4] > sow * soh * 1.5: |
2096 |
return (None, None, None, None, None, None, None) |
2097 |
# detected symbol area is too small
|
2098 |
elif rect[3] * rect[4] * 1.5 < sow * soh: |
2099 |
return (None, None, None, None, None, None, None) |
2100 | |
2101 |
# get Rotated Original Point
|
2102 |
sow, soh = symGray.shape[::-1]
|
2103 |
offset = max(sow, soh)
|
2104 | |
2105 |
roiItem = Worker.remove_small_objects(area.img, 0, sow * soh * 0.5) if hasInstrumentLabel else area.img.copy() |
2106 |
x_start = round(rect[1]) - round(offset) if round(rect[1]) - round(offset) > 0 else 0 |
2107 |
y_start = round(rect[2]) - round(offset) if round(rect[2]) - round(offset) > 0 else 0 |
2108 |
x_max = round(rect[1]) + round(rect[3]) + round(offset) if round(rect[1]) + round(rect[3]) + round(offset) < len(roiItem[0]) else len(roiItem[0]) - 1 |
2109 |
y_max = round(rect[2]) + round(rect[4]) + round(offset) if round(rect[2]) + round(rect[4]) + round(offset) < len(roiItem) else len(roiItem) - 1 |
2110 |
roiItem = roiItem[y_start:y_max, x_start:x_max] |
2111 | |
2112 |
symGrayOri = copy.copy(symGray) |
2113 | |
2114 |
searchedInfos = [] # score, x, y, angle, flip, originalPoint, connectionPoint, sw, sh
|
2115 | |
2116 |
steps = [False, True] if detectFlip else [False] |
2117 |
for flipped in steps: |
2118 |
if flipped:
|
2119 |
symGray = symGrayOri |
2120 |
symGray = cv2.flip(symGray, 1)
|
2121 | |
2122 |
symbolRotatedAngle = 0
|
2123 |
for rc in range(symbolRotateCount + 1): |
2124 |
sw, sh = symGray.shape[::-1]
|
2125 | |
2126 |
originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, |
2127 |
symbolRotatedAngle, sw, sh, sow, soh, flipped) |
2128 |
connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw, |
2129 |
sh, sow, soh, 0)
|
2130 | |
2131 |
tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED) |
2132 |
_, max_val, __, max_loc = cv2.minMaxLoc(tmRes) |
2133 |
#maxIndex = tmRes.argmax()
|
2134 |
#colCount = len(tmRes[0])
|
2135 |
#col, row = divmod(maxIndex, colCount)
|
2136 | |
2137 |
if max_val > symbolThreshold * 0.8: |
2138 |
searchedInfos.append([max_val, max_loc[0] + x_start, max_loc[1] + y_start, symbolRotatedAngle, flipped, originalPoint, connectionPoint, sw, sh]) |
2139 | |
2140 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE) |
2141 |
symbolRotatedAngle = (symbolRotatedAngle + 90) % 360 |
2142 | |
2143 |
if searchedInfos:
|
2144 |
searchedInfos = sorted(searchedInfos, key=lambda param: param[0], reverse=True) |
2145 |
searchedInfo = searchedInfos[0]
|
2146 |
return ((searchedInfo[1] + area.x, searchedInfo[2] + area.y), searchedInfo[3], searchedInfo[4], \ |
2147 |
searchedInfo[5], searchedInfo[6], searchedInfo[7], searchedInfo[8]) |
2148 |
else:
|
2149 |
return (None, None, None, None, None, None, None) |
2150 | |
2151 |
except Exception as ex: |
2152 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
2153 |
sys.exc_info()[-1].tb_lineno)
|
2154 |
worker.displayLog.emit(MessageType.Error, message) |
2155 | |
2156 |
return (None, None, None, None, None, None, None) |
2157 | |
2158 |
@staticmethod
|
2159 |
def detect_symbol_using_server(targetSymbols, listWidget, worker): |
2160 |
from AppWebService import AppWebService |
2161 | |
2162 |
res = [] |
2163 | |
2164 |
app_doc_data = AppDocData.instance() |
2165 |
area = app_doc_data.getArea('Drawing')
|
2166 |
|
2167 |
app_web_service = AppWebService() |
2168 |
symbols = app_web_service.request_symbol_box(area.img) |
2169 | |
2170 |
for targetSymbol in targetSymbols[2]: |
2171 |
symbolName = targetSymbol.getName() |
2172 |
symbolType = targetSymbol.getType() |
2173 |
symbolPath = targetSymbol.getPath() |
2174 |
symbolThreshold = targetSymbol.getThreshold() |
2175 |
symbolMinMatchCount = targetSymbol.getMinMatchCount() |
2176 |
isDetectOnOrigin = targetSymbol.getIsDetectOnOrigin() |
2177 |
symbolRotateCount = targetSymbol.getRotationCount() |
2178 |
symbolOcrOption = targetSymbol.getOcrOption() |
2179 |
isContainChild = targetSymbol.getIsContainChild() |
2180 |
baseSymbol = targetSymbol.getBaseSymbol() |
2181 |
additionalSymbol = targetSymbol.getAdditionalSymbol() |
2182 |
isExceptDetect = targetSymbol.getIsExceptDetect() |
2183 |
hasInstrumentLabel = targetSymbol.getHasInstrumentLabel() |
2184 |
text_area = targetSymbol.getText_area() |
2185 | |
2186 |
# check if symbol file is target or not
|
2187 |
if isExceptDetect == 1: |
2188 |
item = QListWidgetItem('{} file is not target'.format(symbolName))
|
2189 |
item.setBackground(QColor('green'))
|
2190 |
listWidget.addItem(item) |
2191 |
continue
|
2192 | |
2193 |
foundSymbolCount = 0
|
2194 | |
2195 |
# check if symbol file exists
|
2196 |
if not os.path.isfile(symbolPath): |
2197 |
item = QListWidgetItem('{} file not found'.format(symbolName))
|
2198 |
item.setBackground(QColor('red'))
|
2199 |
listWidget.addItem(item) |
2200 |
continue
|
2201 |
# up to here
|
2202 | |
2203 |
sym = cv2.imread(symbolPath, 1)
|
2204 |
symGray = Worker.cvtGrayImage(sym) |
2205 | |
2206 |
for symbol in symbols: |
2207 |
if symbol[0] == symbolName: |
2208 |
searchedItemSp, symbolRotatedAngle, flipped, originalPoint, connectionPoint, sw, sh = \ |
2209 |
Worker.calculate_exact_position(area, symGray, targetSymbol, symbol, worker) |
2210 |
if not searchedItemSp: |
2211 |
continue
|
2212 | |
2213 |
hitRate = symbol[5]
|
2214 | |
2215 |
Worker.addSearchedSymbol(symbolName, symbolType, |
2216 |
searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, |
2217 |
hitRate, symbolRotatedAngle, |
2218 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, |
2219 |
isContainChild, |
2220 |
originalPoint, connectionPoint, baseSymbol, additionalSymbol, |
2221 |
isExceptDetect, |
2222 |
detectFlip=1 if flipped else 0, |
2223 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
2224 | |
2225 |
return symbols
|
2226 |
'''
|
2227 |
@brief detect symbols on PID
|
2228 |
@history humkyung 2018.06.08 add parameteres for signal
|
2229 |
'''
|
2230 |
@staticmethod
|
2231 |
def detectSymbolsOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal): |
2232 |
res = [] |
2233 | |
2234 |
if type(targetSymbols) is list: |
2235 |
for detailTarget in targetSymbols: |
2236 |
res.extend(Worker.detectSymbolOnPid(mainRes, detailTarget, listWidget, updateProgressSignal)) |
2237 |
else:
|
2238 |
res = Worker.detectSymbolOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal) |
2239 | |
2240 |
return res
|
2241 | |
2242 |
@staticmethod
|
2243 |
def convertDirectionCodeToValue(directionCode): |
2244 |
if directionCode == "UP": |
2245 |
return 0 |
2246 |
elif directionCode == "RIGHT": |
2247 |
return 1 |
2248 |
elif directionCode == "DOWN": |
2249 |
return 2 |
2250 |
elif directionCode == "LEFT": |
2251 |
return 3 |
2252 |
else:
|
2253 |
return -1 |
2254 | |
2255 |
@staticmethod
|
2256 |
def convertValueToDirectionCode(value): |
2257 |
if value == 0: |
2258 |
return "UP" |
2259 |
elif value == 1: |
2260 |
return "RIGHT" |
2261 |
elif value == 2: |
2262 |
return "DOWN" |
2263 |
elif value == 3: |
2264 |
return "LEFT" |
2265 |
else:
|
2266 |
return "NONE" |
2267 | |
2268 |
@staticmethod
|
2269 |
def drawFoundSymbolsOnCanvas(drawingPath, symbols, textInfos, listWidget): |
2270 |
"""draw found symbols and texts to image"""
|
2271 | |
2272 |
global src
|
2273 |
global ocrCompletedSrc
|
2274 |
global canvas
|
2275 | |
2276 |
app_doc_data = AppDocData.instance() |
2277 |
canvas = np.zeros(app_doc_data.imgSrc.shape, np.uint8) |
2278 |
canvas[::] = 255
|
2279 | |
2280 |
try:
|
2281 |
project = app_doc_data.getCurrentProject() |
2282 | |
2283 |
for symbol in symbols: |
2284 |
Worker.drawFoundSymbols(symbol, listWidget) |
2285 | |
2286 |
for text in textInfos: |
2287 |
left = text.getX() |
2288 |
top = text.getY() |
2289 |
right = text.getX() + text.getW() |
2290 |
bottom = text.getY() + text.getH() |
2291 | |
2292 |
canvas[top:bottom, left:right] = app_doc_data.imgSrc[top:bottom, left:right] |
2293 | |
2294 |
cv2.imwrite(os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(drawingPath)), canvas)
|
2295 |
except Exception as ex: |
2296 |
from App import App |
2297 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
2298 |
sys.exc_info()[-1].tb_lineno)
|
2299 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
2300 | |
2301 |
'''
|
2302 |
@history 2018.04.27 Jeongwoo Remove Tesseract Log on listWidget
|
2303 |
2018.05.04 Jeongwoo Change method to OCR with tesseract_ocr_module.py
|
2304 |
2018.05.09 Jeongwoo Add global variable textInfoList, Remove text in symbol and Add tesseract result text
|
2305 |
2018.05.10 Jeongwoo Remove not used if-statement
|
2306 |
2018.06.19 Jeongwoo When detect text in symbol, use getTextAreaInfo() and Tesseract
|
2307 |
2018.06.21 Jeongwoo Add if-statement for way to detect text by Type A
|
2308 |
'''
|
2309 | |
2310 |
@staticmethod
|
2311 |
def drawFoundSymbols(symbol, listWidget): |
2312 |
global src
|
2313 |
global canvas
|
2314 |
#global WHITE_LIST_CHARS
|
2315 |
global searchedSymbolList
|
2316 |
global textInfoList
|
2317 | |
2318 |
# symbolId = symbol.getId()
|
2319 |
symbolPath = symbol.getPath() |
2320 |
symbolSp = symbol.getSp() |
2321 |
symbolRotatedAngle = symbol.getRotatedAngle() |
2322 | |
2323 |
symImg = cv2.cvtColor(cv2.imread(symbolPath, 1), cv2.COLOR_BGR2GRAY)
|
2324 |
if symbol.getDetectFlip() is 1: |
2325 |
symImg = cv2.flip(symImg, 1)
|
2326 |
for i in range(symbolRotatedAngle // 90): |
2327 |
symImg = cv2.rotate(symImg, cv2.ROTATE_90_COUNTERCLOCKWISE) |
2328 | |
2329 |
w, h = symImg.shape[::-1]
|
2330 |
canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w] = cv2.bitwise_and( |
2331 |
canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w], symImg) |
2332 | |
2333 |
@staticmethod
|
2334 |
def remove_detected_symbol_image(sym, imgSrc, lock=True): |
2335 |
"""remove detected symbol image from drawing image"""
|
2336 |
if lock:
|
2337 |
global threadLock
|
2338 | |
2339 |
try:
|
2340 |
path = sym.getPath() |
2341 |
sp = (int(sym.getSp()[0]), int(sym.getSp()[1])) |
2342 |
sw = int(sym.getWidth())
|
2343 |
sh = int(sym.getHeight())
|
2344 |
angle = int(sym.getRotatedAngle())
|
2345 |
# get symbol image
|
2346 |
sym_img = cv2.imread(path) |
2347 |
sym_img = cv2.cvtColor(sym_img, cv2.COLOR_BGR2GRAY) |
2348 |
# symImg = cv2.threshold(Worker.cvtGrayImage(symImg), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
|
2349 |
if sym.getDetectFlip() is 1: |
2350 |
sym_img = cv2.flip(sym_img, 1)
|
2351 | |
2352 |
for i in range(angle // 90): |
2353 |
sym_img = cv2.rotate(sym_img, cv2.ROTATE_90_COUNTERCLOCKWISE) |
2354 |
# up to here
|
2355 | |
2356 |
if lock:
|
2357 |
threadLock.acquire() |
2358 |
temp = imgSrc[sp[1]:sp[1] + sh, sp[0]:sp[0] + sw] |
2359 |
sym_img = cv2.erode(sym_img, np.ones((5, 5), np.uint8)) |
2360 |
mask = cv2.bitwise_or(temp, sym_img) |
2361 |
imgXOR = cv2.bitwise_xor(temp, mask) |
2362 |
imgSrc[sp[1]:sp[1] + sh, sp[0]:sp[0] + sw] = cv2.bitwise_not(imgXOR) |
2363 |
except Exception as ex: |
2364 |
from App import App |
2365 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
2366 |
sys.exc_info()[-1].tb_lineno)
|
2367 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
2368 |
finally:
|
2369 |
if lock:
|
2370 |
threadLock.release() |
2371 | |
2372 |
'''
|
2373 |
@brief get difference between given original and recognized image
|
2374 |
@author humkyung
|
2375 |
@date 2018.06.11
|
2376 |
'''
|
2377 | |
2378 |
@staticmethod
|
2379 |
def getDifference(orgImagePath, recImagePath): |
2380 |
import re |
2381 | |
2382 |
global ocrCompletedSrc
|
2383 |
global textInfoList
|
2384 | |
2385 |
try:
|
2386 |
app_doc_data = AppDocData.instance() |
2387 |
imgOriginal = app_doc_data.imgSrc |
2388 | |
2389 |
# remove not drawing area
|
2390 |
configs = app_doc_data.getConfigs('{} Equipment Desc Area'.format(app_doc_data.imgName))
|
2391 |
for config in configs: |
2392 |
found = re.findall('\\d+', config.value)
|
2393 |
if len(found) == 4: |
2394 |
cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])), |
2395 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1) |
2396 | |
2397 |
configs = app_doc_data.getConfigs('{} Typical Area'.format(app_doc_data.imgName))
|
2398 |
for config in configs: |
2399 |
found = re.findall('\\d+', config.value)
|
2400 |
if len(found) == 4: |
2401 |
cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])), |
2402 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1) |
2403 | |
2404 |
noteArea = app_doc_data.getArea('Note')
|
2405 |
if noteArea is not None: |
2406 |
noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height), |
2407 |
round(noteArea.x):round(noteArea.x + noteArea.width)].copy() |
2408 |
cv2.rectangle(imgOriginal, (round(noteArea.x), round(noteArea.y)), |
2409 |
(round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1) |
2410 |
# up to here
|
2411 | |
2412 |
"""
|
2413 |
if os.path.isfile(orgImagePath) and os.path.isfile(recImagePath):
|
2414 |
imgOriginal = \
|
2415 |
cv2.threshold(Worker.cvtGrayImage(cv2.imread(orgImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1]
|
2416 |
|
2417 |
configs = app_doc_data.getConfigs('Filter', 'DilateSize')
|
2418 |
if 1 == len(configs) and int(configs[0].value) is not 0:
|
2419 |
size = int(configs[0].value)
|
2420 |
kernel = np.ones((size, size), np.uint8)
|
2421 |
imgOriginal = cv2.erode(imgOriginal, kernel, iterations=1)
|
2422 |
|
2423 |
# remove not drawing area
|
2424 |
configs = app_doc_data.getConfigs('{} Equipment Desc Area'.format(app_doc_data.imgName))
|
2425 |
for config in configs:
|
2426 |
found = re.findall('\\d+', config.value)
|
2427 |
if len(found) == 4:
|
2428 |
cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
|
2429 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
|
2430 |
|
2431 |
configs = app_doc_data.getConfigs('{} Typical Area'.format(app_doc_data.imgName))
|
2432 |
for config in configs:
|
2433 |
found = re.findall('\\d+', config.value)
|
2434 |
if len(found) == 4:
|
2435 |
cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
|
2436 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
|
2437 |
|
2438 |
noteArea = app_doc_data.getArea('Note')
|
2439 |
if noteArea is not None:
|
2440 |
noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height),
|
2441 |
round(noteArea.x):round(noteArea.x + noteArea.width)].copy()
|
2442 |
cv2.rectangle(imgOriginal, (round(noteArea.x), round(noteArea.y)),
|
2443 |
(round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1)
|
2444 |
# up to here
|
2445 |
|
2446 |
imgRecognized = \
|
2447 |
cv2.threshold(Worker.cvtGrayImage(cv2.imread(recImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1]
|
2448 |
|
2449 |
imgDiff = np.ones(imgOriginal.shape, np.uint8) * 255
|
2450 |
|
2451 |
area = app_doc_data.getArea('Drawing')
|
2452 |
if area is not None:
|
2453 |
x = round(area.x)
|
2454 |
y = round(area.y)
|
2455 |
width = round(area.width)
|
2456 |
height = round(area.height)
|
2457 |
imgNotOper = cv2.bitwise_not(imgRecognized[y:y + height, x:x + width])
|
2458 |
imgDiff[y:y + height, x:x + width] = cv2.bitwise_xor(imgOriginal[y:y + height, x:x + width],
|
2459 |
imgNotOper)
|
2460 |
|
2461 |
# remove noise
|
2462 |
imgDiff = cv2.dilate(imgDiff, np.ones((2, 2), np.uint8))
|
2463 |
|
2464 |
project = app_doc_data.getCurrentProject()
|
2465 |
cv2.imwrite(os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(orgImagePath)), imgDiff)
|
2466 |
"""
|
2467 |
except Exception as ex: |
2468 |
from App import App |
2469 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
2470 |
sys.exc_info()[-1].tb_lineno)
|
2471 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
2472 | |
2473 | |
2474 |
'''
|
2475 |
@history 2018.05.25 Jeongwoo Add pyqtSignal(svgItemClicked, itemRemoved)
|
2476 |
'''
|
2477 | |
2478 | |
2479 |
class QRecognitionDialog(QDialog): |
2480 |
svgItemClicked = pyqtSignal(SymbolSvgItem) |
2481 |
itemRemoved = pyqtSignal(QGraphicsItem) |
2482 |
unBlockEvent = pyqtSignal() |
2483 | |
2484 |
'''
|
2485 |
@history 2018.05.25 Jeongwoo Add parameter and initialize / Connect recognizeButton signal and slot
|
2486 |
2018.05.29 Jeongwoo Chnage parameter(graphicsView → parent) and Get graphicsView from parent
|
2487 |
'''
|
2488 | |
2489 |
def __init__(self, parent, drawings): # Parent is MainWindow |
2490 |
from AppDocData import AppDocData |
2491 | |
2492 |
QDialog.__init__(self, parent)
|
2493 | |
2494 |
self.parent = parent
|
2495 |
self._scene = QGraphicsScene()
|
2496 |
self._text_scene = QGraphicsScene()
|
2497 |
self._line_scene = QGraphicsScene()
|
2498 |
self.drawings = drawings
|
2499 |
self.xmlPath = None |
2500 |
self.ui = Recognition_UI.Ui_Recognition()
|
2501 |
self.ui.setupUi(self) |
2502 |
self.isTreated = False |
2503 | |
2504 |
self.ui.buttonBox.setEnabled(True) |
2505 |
self.ui.listWidget.model().rowsInserted.connect(self.rowInserted) |
2506 |
self.ui.recognizeButton.clicked.connect(self.recognizeButtonClicked) |
2507 |
self.ui.lineCheckBox.stateChanged.connect(self.checkBoxChanged) |
2508 |
self.ui.checkBoxSymbol.stateChanged.connect(self.checkBoxChanged) |
2509 |
self.ui.checkBoxText.stateChanged.connect(self.checkBoxChanged) |
2510 |
self.ui.checkBoxTraining.stateChanged.connect(self.checkBoxChanged) |
2511 |
self.isAccepted = False |
2512 | |
2513 |
appDocData = AppDocData.instance() |
2514 |
configs = appDocData.getAppConfigs('app', 'mode') |
2515 |
if configs and 1 == len(configs) and 'advanced' == configs[0].value: |
2516 |
pass
|
2517 |
else:
|
2518 |
self.ui.checkBoxTraining.setVisible(False) |
2519 | |
2520 |
if False:#len(self.drawings) == 1 and appDocData.activeDrawing and appDocData.activeDrawing == self.drawings[0]: |
2521 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
2522 |
self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
|
2523 |
self.ui.checkBoxText.setCheckState(Qt.Unchecked)
|
2524 |
else:
|
2525 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
2526 |
self.ui.lineCheckBox.setCheckState(Qt.Checked)
|
2527 |
self.ui.checkBoxText.setCheckState(Qt.Checked)
|
2528 |
#self.ui.checkBoxSymbol.setEnabled(False)
|
2529 |
#self.ui.lineCheckBox.setEnabled(False)
|
2530 |
#self.ui.checkBoxText.setEnabled(False)
|
2531 | |
2532 |
self.ui.progressBarBatch.setFormat('{}/{}'.format('0', str(len(self.drawings)))) |
2533 | |
2534 |
def checkBoxChanged(self, checkState): |
2535 |
'''
|
2536 |
@brief line and text cannot be reocognized alone
|
2537 |
@author euisung
|
2538 |
@date 2019.05.14
|
2539 |
'''
|
2540 |
if self.ui.checkBoxTraining.isChecked(): |
2541 |
self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
|
2542 |
self.ui.checkBoxText.setCheckState(Qt.Unchecked)
|
2543 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
2544 |
self.ui.lineCheckBox.setEnabled(False) |
2545 |
self.ui.checkBoxText.setEnabled(False) |
2546 |
self.ui.checkBoxSymbol.setEnabled(False) |
2547 |
self.ui.recognizeButton.setText('Extract') |
2548 |
return
|
2549 |
else:
|
2550 |
self.ui.lineCheckBox.setEnabled(True) |
2551 |
self.ui.checkBoxText.setEnabled(True) |
2552 |
self.ui.checkBoxSymbol.setEnabled(True) |
2553 |
self.ui.recognizeButton.setText('Recognize') |
2554 | |
2555 |
if checkState is int(Qt.Checked): |
2556 |
if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked(): |
2557 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
2558 |
elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked(): |
2559 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
2560 |
elif checkState is int(Qt.Unchecked): |
2561 |
if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked(): |
2562 |
self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
|
2563 |
elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked(): |
2564 |
self.ui.checkBoxText.setCheckState(Qt.Unchecked)
|
2565 | |
2566 |
'''
|
2567 |
@brief QListWidget Row Inserted Listener
|
2568 |
Whenever row inserted, scroll to bottom
|
2569 |
@author Jeongwoo
|
2570 |
@date 18.04.12
|
2571 |
'''
|
2572 | |
2573 |
def rowInserted(self, item): |
2574 |
self.ui.listWidget.scrollToBottom()
|
2575 | |
2576 |
def accept(self): |
2577 |
self.isAccepted = True |
2578 |
QDialog.accept(self)
|
2579 | |
2580 |
def recognizeButtonClicked(self, event): |
2581 |
"""
|
2582 |
@brief start recognization
|
2583 |
@author humkyung
|
2584 |
@history humkyung 2018.10.05 clear imgSrc before recognizing
|
2585 |
"""
|
2586 |
if self.ui.checkBoxSymbol.isChecked(): # or self.ui.checkBoxText.isChecked() or self.ui.lineCheckBox.isChecked(): |
2587 |
appDocData = AppDocData.instance() |
2588 |
appDocData.imgSrc = None
|
2589 |
area = appDocData.getArea('Drawing')
|
2590 |
if area is None: |
2591 |
QMessageBox.about(self, self.tr("Notice"), self.tr('Please select drawing area.')) |
2592 |
return
|
2593 | |
2594 |
self.ui.recognizeButton.setEnabled(False) |
2595 |
self.ui.buttonBox.setEnabled(False) |
2596 |
self.ui.progressBar.setValue(0) |
2597 |
self.ui.listWidget.addItem("Initializing...") |
2598 |
self.startThread()
|
2599 | |
2600 |
self.isTreated = True |
2601 | |
2602 |
'''
|
2603 |
@brief add item to list widget
|
2604 |
@author humkyung
|
2605 |
@date 2018.06.19
|
2606 |
'''
|
2607 | |
2608 |
def addListItem(self, item): |
2609 |
self.ui.listWidget.addItem(item)
|
2610 | |
2611 |
'''
|
2612 |
@brief update progressbar with given value
|
2613 |
@author humkyung
|
2614 |
@date 2018.06.08
|
2615 |
'''
|
2616 | |
2617 |
def updateProgress(self, maxValue, image_path): |
2618 |
self.ui.progressBar.setMaximum(maxValue)
|
2619 |
self.ui.progressBar.setValue(self.ui.progressBar.value() + 1) |
2620 | |
2621 |
if image_path is not None and os.path.isfile(image_path): |
2622 |
self.ui.labelImage.setPixmap(QPixmap(image_path))
|
2623 |
elif image_path is not None: |
2624 |
self.ui.labelImage.setText(image_path)
|
2625 | |
2626 |
def updateBatchProgress(self, maxValue, weight): |
2627 |
"""update batch progressbar"""
|
2628 |
self.ui.progressBarBatch.setMaximum(maxValue * 5) |
2629 |
value = self.ui.progressBarBatch.value() + weight
|
2630 |
self.ui.progressBarBatch.setValue(value)
|
2631 |
self.ui.progressBarBatch.setFormat('{}/{}'.format(str(int(value / 5)), str(maxValue))) |
2632 | |
2633 |
'''
|
2634 |
@brief display title
|
2635 |
@author humkyung
|
2636 |
@date 2018.08.20
|
2637 |
'''
|
2638 | |
2639 |
def displayTitle(self, title): |
2640 |
self.ui.labelTitle.setText(title)
|
2641 | |
2642 |
def startThread(self): |
2643 |
"""start thread"""
|
2644 |
import timeit |
2645 |
from PyQt5 import QtWidgets |
2646 |
from App import App |
2647 | |
2648 |
self.ui.buttonBox.setDisabled(True) |
2649 | |
2650 |
self.mutex = QMutex()
|
2651 |
self.cond = QWaitCondition()
|
2652 | |
2653 |
# 1 - create Worker and Thread inside the Form
|
2654 |
self.obj = Worker(self.mutex, self.cond) # no parent! |
2655 |
self.obj.drawings = self.drawings |
2656 |
self.obj.listWidget = self.ui.listWidget |
2657 |
self.obj.scene = self._scene |
2658 |
self.obj.text_scene = self._text_scene |
2659 |
self.obj.line_scene = self._line_scene |
2660 |
self.obj.scene._end = False # for waiting each drawing finished |
2661 |
self.obj.isSymbolChecked = self.ui.checkBoxSymbol.isChecked() |
2662 |
self.obj.isTextChecked = self.ui.checkBoxText.isChecked() |
2663 |
self.obj.isLineChecked = self.ui.lineCheckBox.isChecked() |
2664 |
self.obj.isTrainingChecked = self.ui.checkBoxTraining.isChecked() |
2665 |
self.thread = QThread() # no parent! |
2666 | |
2667 |
# 2 - Move the Worker object to the Thread object
|
2668 |
self.obj.moveToThread(self.thread) |
2669 | |
2670 |
# 3 - Connect Worker Signals to the Thread slots
|
2671 |
self.obj.finished.connect(self.thread.quit) |
2672 |
self.obj.displayMessage.connect(self.addListItem) |
2673 |
self.obj.updateProgress.connect(self.updateProgress) |
2674 |
self.obj.updateBatchProgress.connect(self.updateBatchProgress) |
2675 |
self.obj.displayLog.connect(App.mainWnd().addMessage)
|
2676 |
self.obj.displayTitle.connect(self.displayTitle) |
2677 |
self.obj.add_detected_items_to_scene.connect(self.add_detected_items_to_scene) |
2678 |
self.obj.add_predata_to_scene.connect(self.add_predata_to_scene) |
2679 |
self.obj.clear_scene.connect(self.clear_scene) |
2680 | |
2681 |
# 4 - Connect Thread started signal to Worker operational slot method
|
2682 |
self.thread.started.connect(self.obj.procCounter) |
2683 | |
2684 |
# 5 - Thread finished signal will close the app if you want!
|
2685 |
self.thread.finished.connect(self.dlgExit) |
2686 | |
2687 |
# 6 - Start the thread
|
2688 |
self.thread.start()
|
2689 | |
2690 |
self.tmStart = timeit.default_timer()
|
2691 | |
2692 |
'''
|
2693 |
@brief set buttonbox's enabled flag
|
2694 |
@history 2018.05.25 Jeongwoo Moved from MainWindow
|
2695 |
2018.06.14 Jeongwoo Change sentence order
|
2696 |
2018.11.26 euisung add drawing part
|
2697 |
2018.11.26 euising move save and unknown part into executerecognition
|
2698 |
'''
|
2699 | |
2700 |
def dlgExit(self): |
2701 |
import timeit |
2702 | |
2703 |
try:
|
2704 |
self.ui.buttonBox.setEnabled(True) |
2705 | |
2706 |
self.ui.progressBar.setValue(self.ui.progressBar.maximum()) |
2707 |
except Exception as ex: |
2708 |
from App import App |
2709 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
2710 |
sys.exc_info()[-1].tb_lineno)
|
2711 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
2712 |
finally:
|
2713 |
self.tmStop = timeit.default_timer()
|
2714 |
seconds = self.tmStop - self.tmStart |
2715 |
self.ui.listWidget.addItem("\nRunning Time : {} min".format(str(round(seconds / 60, 1))) + "\n") |
2716 | |
2717 |
'''
|
2718 |
@history 2018.05.29 Jeongwoo Call parent's method
|
2719 |
2018.05.30 Jeongwoo Change method name
|
2720 |
2018.06.09 humkyung set progressbar value to maximum
|
2721 |
2018.11.12 euisung add title block properties
|
2722 |
2018.11.29 euisung no more used
|
2723 |
'''
|
2724 | |
2725 |
def drawDetectedItems(self, symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList, loop): |
2726 |
try:
|
2727 |
self.ui.progressBar.setValue(self.ui.progressBar.maximum()) |
2728 |
self.parent.drawDetectedItems(symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList)
|
2729 |
finally:
|
2730 |
loop.quit() |
2731 | |
2732 |
'''
|
2733 |
@brief draw detected lines
|
2734 |
@author humkyung
|
2735 |
@date 2018.08.23
|
2736 |
@history 2018.11.27 euisung no more used
|
2737 |
'''
|
2738 | |
2739 |
def drawDetectedLines(self, lineList, loop): |
2740 |
try:
|
2741 |
self.parent.drawDetectedLines(lineList, self.obj) |
2742 |
finally:
|
2743 |
loop.quit() |
2744 | |
2745 |
'''
|
2746 |
@brief draw detected lines
|
2747 |
@author euisung
|
2748 |
@date 2018.11.27
|
2749 |
@history 2018.11.27 euisung no more used
|
2750 |
'''
|
2751 | |
2752 |
def drawUnknownItems(self, path, loop): |
2753 |
try:
|
2754 |
self.parent.drawUnknownItems(path)
|
2755 |
finally:
|
2756 |
loop.quit() |
2757 | |
2758 |
def add_predata_to_scene(self, drawing, scene, symbol, text, line, unknown, package) -> None: |
2759 |
""" add predata to scene """
|
2760 |
from LoadCommand import LoadCommand |
2761 | |
2762 |
try:
|
2763 |
cmd = LoadCommand() |
2764 |
cmd.execute((drawing, scene), symbol=symbol, text=text, line=line, unknown=unknown, |
2765 |
package=package, update=False)
|
2766 |
except Exception as ex: |
2767 |
from App import App |
2768 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
2769 |
sys.exc_info()[-1].tb_lineno)
|
2770 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
2771 |
finally:
|
2772 |
self.cond.wakeAll()
|
2773 | |
2774 |
def clear_scene(self, scenes) -> None: |
2775 |
""" clear scenes """
|
2776 | |
2777 |
try:
|
2778 |
for scene in scenes: |
2779 |
scene.clear() |
2780 | |
2781 |
except Exception as ex: |
2782 |
from App import App |
2783 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
2784 |
sys.exc_info()[-1].tb_lineno)
|
2785 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
2786 |
finally:
|
2787 |
self.cond.wakeAll()
|
2788 | |
2789 |
def add_detected_items_to_scene(self, scene) -> None: |
2790 |
"""add detected items to scene"""
|
2791 |
from SaveWorkCommand import SaveWorkCommand |
2792 |
from EngineeringVendorItem import QEngineeringVendorItem |
2793 | |
2794 |
app_doc_data = AppDocData.instance() |
2795 | |
2796 |
try:
|
2797 |
for item in scene.items(): |
2798 |
if issubclass(type(item), QEngineeringVendorItem): |
2799 |
app_doc_data.allItems.append(item) |
2800 | |
2801 |
# symbol
|
2802 |
for symbol in app_doc_data.symbols: |
2803 |
if issubclass(type(symbol), SymbolSvgItem): |
2804 |
symbol.addSvgItemToScene(scene) |
2805 |
else:
|
2806 |
scene.addItem(symbol) |
2807 | |
2808 |
# text
|
2809 |
for text in app_doc_data.texts: |
2810 |
text.addTextItemToScene(scene) |
2811 | |
2812 |
#for lineNo in app_doc_data.tracerLineNos:
|
2813 |
# lineNo.addTextItemToScene(scene)
|
2814 | |
2815 |
# remove lines which is located inside symbol
|
2816 |
for symbol in app_doc_data.symbols: |
2817 |
rect = symbol.sceneBoundingRect() |
2818 |
rect.adjust(-10, -10, 10, 10) |
2819 |
matches = [line for line in app_doc_data.lines if rect.contains(line.line().p1()) and |
2820 |
rect.contains(line.line().p2())]# and not line.has_connection]
|
2821 |
for line in matches: |
2822 |
app_doc_data.allItems.remove(line) |
2823 |
app_doc_data.lines.remove(line) |
2824 |
# up to here
|
2825 | |
2826 |
for line in app_doc_data.lines: |
2827 |
scene.addItem(line) |
2828 |
# line.transfer.onRemoved.connect(self.itemRemoved)
|
2829 |
for conn in line.connectors: |
2830 |
conn.transfer.onPosChanged.connect(line.onConnectorPosChaned) |
2831 | |
2832 |
for unknown in app_doc_data.unknowns + app_doc_data.lineIndicators: |
2833 |
scene.addItem(unknown) |
2834 | |
2835 |
# save scene
|
2836 |
save = SaveWorkCommand(scene) |
2837 |
save.run() |
2838 |
#SaveWorkCommand.save_to_database()
|
2839 |
#SaveWorkCommand.save_to_xml()
|
2840 |
except Exception as ex: |
2841 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
2842 |
sys.exc_info()[-1].tb_lineno)
|
2843 |
self.displayLog.emit(MessageType.Error, message)
|
2844 |
finally:
|
2845 |
self.cond.wakeAll()
|
2846 |
scene._end = True
|