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