hytos / DTI_PID / DTI_PID / RecognitionDialog.py @ f9068b2a
이력 | 보기 | 이력해설 | 다운로드 (106 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 |
|
82 |
def __init__(self): |
83 |
super(Worker, self).__init__() |
84 |
|
85 |
'''
|
86 |
@history 2018.05.25 Jeongwoo Add if-statements by isSymbolTextChecked and isLineChecked variable
|
87 |
2018.05.28 Jeongwoo Add Parameter 'xmlPath[0]'
|
88 |
2018.05.30 Jeongwoo Remove import recognizeLine and self.xmlPath and remove parameter on recognizeLine.emit() (xmlPath)
|
89 |
Change signal name (drawDetectedItems)
|
90 |
'''
|
91 |
|
92 |
def procCounter(self): # A slot takes no params |
93 |
try:
|
94 |
if self.isSymbolChecked or self.isTrainingChecked: |
95 |
Worker.executeRecognition(self.createDetectedItems, self.path, self.listWidget, self.isLineChecked, |
96 |
self, self.batch, self.createUnknownItems) |
97 |
except Exception as ex: |
98 |
from App import App |
99 |
from AppDocData import MessageType |
100 |
|
101 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
102 |
sys.exc_info()[-1].tb_lineno)
|
103 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
104 |
self.displayLog.emit(MessageType.Error, message)
|
105 |
except:
|
106 |
(_type, value, traceback) = sys.exc_info() |
107 |
sys.excepthook(_type, value, traceback) |
108 |
finally:
|
109 |
self.finished.emit()
|
110 |
|
111 |
'''
|
112 |
@brief remove small objects from given image
|
113 |
@author humkyung
|
114 |
@date 2018.04.26
|
115 |
@history 2018.05.25 Jeongwoo Moved from MainWindow
|
116 |
'''
|
117 |
|
118 |
@staticmethod
|
119 |
def remove_small_objects(image): |
120 |
try:
|
121 |
app_doc_data = AppDocData.instance() |
122 |
configs = app_doc_data.getConfigs('Small Object Size', 'Min Area') |
123 |
minArea = int(configs[0].value) if 1 == len(configs) else 20 |
124 |
configs = app_doc_data.getConfigs('Small Object Size', 'Max Area') |
125 |
maxArea = int(configs[0].value) if 1 == len(configs) else 50 |
126 |
|
127 |
# try to convert grayscale to binary
|
128 |
image = cv2.threshold(image, 200, 255, cv2.THRESH_BINARY)[1] |
129 |
|
130 |
contours, _ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) |
131 |
selectedContours = [] |
132 |
for contour in contours: |
133 |
area = cv2.contourArea(contour) |
134 |
if minArea < area < maxArea:
|
135 |
selectedContours.append(contour) |
136 |
|
137 |
# draw contour with white color
|
138 |
cv2.drawContours(image, selectedContours, -1, (255, 255, 255), -1) |
139 |
except Exception as ex: |
140 |
from App import App |
141 |
|
142 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
143 |
sys.exc_info()[-1].tb_lineno)
|
144 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
145 |
|
146 |
return image
|
147 |
|
148 |
'''
|
149 |
@brief arrange line's position
|
150 |
@author humkyung
|
151 |
@date 2018.07.04
|
152 |
'''
|
153 |
|
154 |
@staticmethod
|
155 |
def arrangeLinePosition(lines, symbols, listWidget): |
156 |
try:
|
157 |
listWidget.addItem('Apply flow direction')
|
158 |
pool = [line for line in lines if line.flowMark is not None] |
159 |
visited = [] |
160 |
visited.extend(pool) |
161 |
while len(pool) > 0: |
162 |
line = pool.pop() |
163 |
print('{} - ({})'.format(line, len(pool))) |
164 |
rhs = [item for item in lines if item not in visited and item.is_connected(line)] |
165 |
if rhs:
|
166 |
pool.extend(rhs) |
167 |
visited.extend(rhs) |
168 |
for item in rhs: |
169 |
item.arrangeVertexOrder(line) |
170 |
|
171 |
# skip jointed symbols
|
172 |
symbolPool = [item for item in symbols if item not in visited and item.is_connected(line)] |
173 |
if symbolPool:
|
174 |
selected = [] |
175 |
visited.extend(symbolPool) |
176 |
while len(symbolPool) > 0: |
177 |
symbol = symbolPool.pop() |
178 |
|
179 |
rhs = [item for item in symbols if item not in visited and item.is_connected(symbol)] |
180 |
if rhs:
|
181 |
symbolPool.extend(rhs) |
182 |
visited.extend(rhs) |
183 |
selected.extend(rhs) |
184 |
else:
|
185 |
selected.append(symbol) |
186 |
|
187 |
# find lines which are connected last symbol
|
188 |
for symbol in selected: |
189 |
rhs = [item for item in lines if item not in visited and item.is_connected(symbol)] |
190 |
if rhs:
|
191 |
pool.extend(rhs) |
192 |
visited.extend(rhs) |
193 |
for item in rhs: |
194 |
item.arrangeVertexOrder(line) |
195 |
# up to here
|
196 |
# up to here
|
197 |
except Exception as ex: |
198 |
from App import App |
199 |
|
200 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
201 |
sys.exc_info()[-1].tb_lineno)
|
202 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
203 |
|
204 |
@staticmethod
|
205 |
def executeRecognition(createDetectedItems, path, listWidget, isLineChecked, worker, batch, createUnknownItems): |
206 |
"""
|
207 |
@brief recognize symbol, text, line from image p&id
|
208 |
@author Jeongwoo
|
209 |
@date
|
210 |
@history humkyung 2018.04.06 change error display from message box to print
|
211 |
Jeongwoo 2018.04.25 Remove 'Current Symbol : ' QListItem
|
212 |
Jeongwoo 2018.05.09 Make Comments OCR.removeTextFromNpArray block
|
213 |
Jeongwoo 2018.05.25 Remove imgLineList variable and parameter on writeXml()
|
214 |
humkyung 2018.05.26 add parameters(graphicsView, isSymbolTextChecked, isLineChecked)
|
215 |
Jeongwoo 2018.05.28 Add/Remove Parameters(Add : signal / Remove : graphicsView, isLineChecked)
|
216 |
Jeongwoo 2018.05.30 Remove return value
|
217 |
humkyung 2018.06.08 add signal for progressbar to parameter
|
218 |
humkyung 2018.06.11 get difference between original and recognized image
|
219 |
Jeongwoo 2018.06.21 If noteTextInfoList is None, change from None to empty list
|
220 |
humkyung 2018.08.20 remove alreay existing symbol and text item before recognizing
|
221 |
block until drawing reconized objects
|
222 |
euisung 2018.11.12 add title block properties
|
223 |
"""
|
224 |
import re |
225 |
import timeit |
226 |
from TextDetector import TextDetector |
227 |
from Drawing import Drawing |
228 |
from datetime import datetime |
229 |
|
230 |
global ocrCompletedSrc
|
231 |
global threadLock
|
232 |
global searchedSymbolList
|
233 |
global textInfoList
|
234 |
global maxProgressValue
|
235 |
|
236 |
try:
|
237 |
app_doc_data = AppDocData.instance() |
238 |
drawings = app_doc_data.getDrawings() |
239 |
project = app_doc_data.getCurrentProject() |
240 |
textDetector = TextDetector() |
241 |
|
242 |
# remove already existing symbol and text
|
243 |
if not batch: |
244 |
listWidget.addItem('Deleting existing items...')
|
245 |
items = [item for item in worker.graphicsView.scene().items() if |
246 |
type(item) is QEngineeringUnknownItem or type(item) is QEngineeringEndBreakItem or |
247 |
type(item) is QEngineeringErrorItem or type(item) is QGraphicsBoundingBoxItem] |
248 |
|
249 |
if worker.isSymbolChecked:
|
250 |
items.extend( |
251 |
[item for item in worker.graphicsView.scene().items() if issubclass(type(item), SymbolSvgItem)]) |
252 |
if worker.isTextChecked:
|
253 |
items.extend([item for item in worker.graphicsView.scene().items() if |
254 |
issubclass(type(item), QEngineeringTextItem)]) |
255 |
for item in items: |
256 |
item.transfer.onRemoved.emit(item) |
257 |
# up to here
|
258 |
|
259 |
srcList = path |
260 |
|
261 |
Worker.initTargetSymbolDataList() |
262 |
|
263 |
for mainRes in srcList: |
264 |
ocrCompletedSrc = [] |
265 |
searchedSymbolList = [] |
266 |
textInfoList = [] |
267 |
|
268 |
if not os.path.isfile(mainRes): |
269 |
item = QListWidgetItem('{} file is not found'.format(os.path.basename(mainRes)))
|
270 |
item.setBackground(Qt.red) |
271 |
listWidget.addItem(item) |
272 |
continue
|
273 |
|
274 |
app_doc_data.clearItemList(True)
|
275 |
|
276 |
app_doc_data.setImgFilePath(mainRes) |
277 |
app_doc_data.imgSrc = None
|
278 |
matches = [drawing for drawing in drawings if app_doc_data.imgName == os.path.splitext(drawing.name)[0]] |
279 |
app_doc_data.activeDrawing = matches[0] if matches else Drawing(None, app_doc_data.imgName, None) |
280 |
app_doc_data.activeDrawing.set_pid_source(Image.open(mainRes)) |
281 |
|
282 |
# remove not drawing area
|
283 |
configs = app_doc_data.getConfigs('{} Equipment Desc Area'.format(app_doc_data.imgName))
|
284 |
for config in configs: |
285 |
found = re.findall('\\d+', config.value)
|
286 |
if len(found) == 4: |
287 |
cv2.rectangle(app_doc_data.imgSrc, (int(found[0]), int(found[1])), |
288 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1) |
289 |
|
290 |
configs = app_doc_data.getConfigs('{} Typical Area'.format(app_doc_data.imgName))
|
291 |
for config in configs: |
292 |
found = re.findall('\\d+', config.value)
|
293 |
if len(found) == 4: |
294 |
cv2.rectangle(app_doc_data.imgSrc, (int(found[0]), int(found[1])), |
295 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1) |
296 |
|
297 |
noteArea = app_doc_data.getArea('Note')
|
298 |
if noteArea is not None: |
299 |
noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height), |
300 |
round(noteArea.x):round(noteArea.x + noteArea.width)].copy() |
301 |
cv2.rectangle(app_doc_data.imgSrc, (round(noteArea.x), round(noteArea.y)), |
302 |
(round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1) |
303 |
#cv2.imwrite('c:\\temp\\note.png', noteArea.img)
|
304 |
#cv2.imwrite('c:\\temp\\removed_note.png', app_doc_data.imgSrc)
|
305 |
# up to here
|
306 |
|
307 |
area = app_doc_data.getArea('Drawing')
|
308 |
if area is not None: |
309 |
# copy image
|
310 |
area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height), |
311 |
round(area.x):round(area.x + area.width)] |
312 |
|
313 |
_, area.mask = cv2.threshold(area.img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) |
314 |
|
315 |
# area.contours, area.hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
|
316 |
area.not_img = cv2.bitwise_not(area.img) |
317 |
area.contours, area.hierachy = cv2.findContours(area.not_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) |
318 |
|
319 |
maxProgressValue = 0
|
320 |
start_time = timeit.default_timer() |
321 |
listWidget.addItem(f"Start recognition {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} : {mainRes}")
|
322 |
threadLock.acquire() |
323 |
|
324 |
worker.updateBatchProgress.emit(len(srcList), 1) |
325 |
|
326 |
if worker.isSymbolChecked:
|
327 |
# calculate total count of symbol
|
328 |
for targetItem in targetSymbolList: |
329 |
if type(targetItem) is list: |
330 |
maxProgressValue += len(targetItem)
|
331 |
else:
|
332 |
maxProgressValue += 1
|
333 |
# up to here
|
334 |
maxProgressValue = maxProgressValue * 2
|
335 |
threadLock.release() |
336 |
|
337 |
if worker.isSymbolChecked:
|
338 |
worker.displayTitle.emit(worker.tr('Detecting symbols...'))
|
339 |
|
340 |
# detect equipments
|
341 |
pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) |
342 |
for symbol in targetSymbolList[0]: |
343 |
pool.submit(Worker.detectEquipmentOnPid, mainRes, symbol, listWidget, worker) |
344 |
pool.shutdown(wait=True)
|
345 |
# up to here
|
346 |
|
347 |
with futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as pool: |
348 |
future_symbol = {pool.submit(Worker.detectSymbolsOnPid, mainRes, symbol, listWidget, worker): |
349 |
symbol for symbol in targetSymbolList[2]} |
350 |
futures.wait(future_symbol) |
351 |
|
352 |
if worker.isTrainingChecked:
|
353 |
import uuid |
354 |
worker.displayTitle.emit(worker.tr('Generating Data...'))
|
355 |
listWidget.addItem('Generating Data...')
|
356 |
|
357 |
for item in searchedSymbolList: |
358 |
path = os.path.join(project.getTempPath(), 'Tile', item.getBaseSymbol())
|
359 |
if not os.path.exists(path): os.makedirs(path) |
360 |
|
361 |
_img = app_doc_data.imgSrc[round(item.getSp()[1]):round(item.getSp()[1] + item.getHeight()), |
362 |
round(item.getSp()[0]):round(item.getSp()[0] + item.getWidth())] |
363 |
cv2.imwrite(os.path.join(path, str(uuid.uuid4()) + '.png'), _img) |
364 |
worker.updateBatchProgress.emit(len(srcList), 4) |
365 |
continue
|
366 |
|
367 |
#cv2.imwrite('c:\\temp\\before-imgSrc.png', area.img)
|
368 |
#pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER)
|
369 |
searchedTextSymList = [] |
370 |
for sym in searchedSymbolList: |
371 |
Worker.remove_detected_symbol_image(sym, app_doc_data.imgSrc) |
372 |
#pool.submit(Worker.remove_detected_symbol_image, sym, app_doc_data.imgSrc)
|
373 |
#pool.shutdown(wait=True)
|
374 |
#cv2.imwrite('c:\\temp\\imgSrc.png', area.img)
|
375 |
else:
|
376 |
'''
|
377 |
import math
|
378 |
|
379 |
symbolItems = [item for item in worker.graphicsView.scene().items() if issubclass(type(item), SymbolSvgItem)]
|
380 |
|
381 |
for symbolItem in symbolItems:
|
382 |
symbolName = symbolItem.name
|
383 |
symbolType = symbolItem.type
|
384 |
searchedItemSp = [int(symbolItem.loc[0]), int(symbolItem.loc[1])]
|
385 |
sw = symbolItem.size[0] # check later
|
386 |
sh = symbolItem.size[1] # check later
|
387 |
|
388 |
symbolThreshold = 50 # not necessary
|
389 |
symbolMinMatchCount = 0 # not necessary
|
390 |
hitRate = 80 # not necessary
|
391 |
|
392 |
allowed_error = 0.0001
|
393 |
#degree 0
|
394 |
if abs(symbolItem.angle - 0) <= allowed_error:
|
395 |
symbolRotatedAngle = 0
|
396 |
#degree 90
|
397 |
elif abs(symbolItem.angle - 1.57) <= allowed_error:
|
398 |
symbolRotatedAngle = 90
|
399 |
#degree 180
|
400 |
elif abs(symbolItem.angle - 3.14) <= allowed_error:
|
401 |
symbolRotatedAngle = 180
|
402 |
#degree 270
|
403 |
elif abs(symbolItem.angle - 4.71) <= allowed_error:
|
404 |
symbolRotatedAngle = 270
|
405 |
else:
|
406 |
symbolRotatedAngle = math.pi / 180 * symbolItem.angle
|
407 |
|
408 |
isDetectOnOrigin = 80 # not necessary
|
409 |
symbolRotateCount = 0 # not necessary
|
410 |
symbolOcrOption = 0 # not necessary
|
411 |
isContainChild = 0 # not necessary
|
412 |
originalPoint = [symbolItem.origin[0] - symbolItem.loc[0], symbolItem.origin[1] - symbolItem.loc[1]]
|
413 |
symbolConnectionPoint = []
|
414 |
baseSymbol = str(symbolItem.parentSymbol)
|
415 |
additionalSymbol = str(symbolItem.childSymbol)
|
416 |
isExceptDetect = 0 # not necessary
|
417 |
detectFlip = symbolItem.flip
|
418 |
|
419 |
searchedSymbolList.append(Symbol(symbolName, symbolType ,
|
420 |
searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, hitRate, symbolRotatedAngle ,
|
421 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild ,
|
422 |
','.join(str(x) for x in originalPoint),
|
423 |
[],
|
424 |
baseSymbol, additionalSymbol, isExceptDetect, detectFlip))
|
425 |
|
426 |
worker.graphicsView.scene().removeItem(symbolItem)
|
427 |
|
428 |
pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER)
|
429 |
'''
|
430 |
# symbolItems = [item for item in worker.graphicsView.scene().items() if issubclass(type(item), SymbolSvgItem)]
|
431 |
# appDocData.symbols.extend(symbolItems)
|
432 |
# appDocData.allItems.extend(symbolItems)
|
433 |
|
434 |
# for sym in searchedSymbolList:
|
435 |
# pool.submit(Worker.remove_detected_symbol_image, sym, appDocData.imgSrc)
|
436 |
# pool.shutdown(wait = True)
|
437 |
|
438 |
worker.updateBatchProgress.emit(len(srcList), 1) |
439 |
|
440 |
area = app_doc_data.getArea('Drawing')
|
441 |
if area is not None: |
442 |
# copy area image
|
443 |
area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height), |
444 |
round(area.x):round(area.x + area.width)] |
445 |
|
446 |
offset = (area.x, area.y) if area is not None else (0, 0) |
447 |
|
448 |
otherTextInfoList = None
|
449 |
titleBlockTextInfoList = None
|
450 |
textInfoList = [] |
451 |
if worker.isTextChecked:
|
452 |
from TextInfo import TextInfo |
453 |
|
454 |
textAreas, ocr_image = textDetector.detectTextAreas(area.img if area is not None else app_doc_data.imgSrc, |
455 |
offset) |
456 |
|
457 |
# get text area by using symbol setting
|
458 |
text_area_symbols = [] |
459 |
for symbol in [symbol for symbol in searchedSymbolList if symbol.text_area]: |
460 |
for text_area in symbol.text_area: |
461 |
so = [symbol.getWidth(), symbol.getHeight()] if symbol.getRotatedAngle() is 0 or symbol.getRotatedAngle() is 180\ |
462 |
else [symbol.getHeight(), symbol.getWidth()]
|
463 |
text_area_origin = Worker.getCalculatedOriginalPoint(None, str(text_area.center()[0]) + ',' + str(text_area.center()[1]),\ |
464 |
symbol.getRotatedAngle(), None, None, so[0], so[1], symbol.getDetectFlip()) |
465 |
x = text_area_origin[0] - round(text_area.width / 2) if symbol.getRotatedAngle() is 0 or symbol.getRotatedAngle() is 180\ |
466 |
else text_area_origin[0] - round(text_area.height / 2) |
467 |
x = x + symbol.sp[0]
|
468 |
y = text_area_origin[1] - round(text_area.height / 2) if symbol.getRotatedAngle() is 0 or symbol.getRotatedAngle() is 180\ |
469 |
else text_area_origin[1] - round(text_area.width / 2) |
470 |
y = y + symbol.sp[1]
|
471 |
w = text_area.width if symbol.getRotatedAngle() is 0 or symbol.getRotatedAngle() is 180 else text_area.height |
472 |
h = text_area.height if symbol.getRotatedAngle() is 0 or symbol.getRotatedAngle() is 180 else text_area.width |
473 |
text_area_symbols.append(TextInfo('', x, y, w, h, symbol.getRotatedAngle()))
|
474 |
|
475 |
for textArea_index in reversed(range(len(textAreas))): |
476 |
for text_area_index in reversed(range(len(text_area_symbols))): |
477 |
if text_area_symbols[text_area_index].contains(textAreas[textArea_index].center) or \ |
478 |
textAreas[textArea_index].contains(text_area_symbols[text_area_index].center): |
479 |
if text_area_symbols[text_area_index].area > textAreas[textArea_index].area:
|
480 |
textAreas.pop(textArea_index) |
481 |
else:
|
482 |
text_area_symbols.pop(text_area_index) |
483 |
textAreas.extend(text_area_symbols) |
484 |
# up to here
|
485 |
|
486 |
if maxProgressValue < 2 * len(textAreas): |
487 |
for _ in range(len(textAreas) - int(maxProgressValue * 0.5)): |
488 |
worker.updateProgress.emit(2 * len(textAreas), None) |
489 |
maxProgressValue = 2 * len(textAreas) |
490 |
else:
|
491 |
maxProgressValue = int(maxProgressValue * 0.5) + len(textAreas) |
492 |
|
493 |
worker.displayTitle.emit(worker.tr('Detecting texts...'))
|
494 |
textDetector.recognizeText(area.img, offset, textAreas, searchedSymbolList, worker, |
495 |
listWidget, maxProgressValue) |
496 |
textInfoList = textDetector.textInfoList.copy() if textDetector.textInfoList is not None else None |
497 |
otherTextInfoList = textDetector.otherTextInfoList.copy() if textDetector.otherTextInfoList is not None else None |
498 |
titleBlockTextInfoList = textDetector.titleBlockTextInfoList.copy() if textDetector.titleBlockTextInfoList is not None else None |
499 |
|
500 |
app_doc_data.activeDrawing.width, app_doc_data.activeDrawing.height = app_doc_data.imgSrc.shape[ |
501 |
::-1]
|
502 |
|
503 |
app_doc_data.imgName = os.path.splitext(os.path.basename(mainRes))[0]
|
504 |
else:
|
505 |
import math |
506 |
from TextInfo import TextInfo |
507 |
|
508 |
textItems = [item for item in worker.graphicsView.scene().items() if |
509 |
issubclass(type(item), QEngineeringTextItem)] |
510 |
app_doc_data.texts.extend(textItems) |
511 |
app_doc_data.allItems.extend(textItems) |
512 |
|
513 |
lineNoTextItems = [item for item in textItems if type(item) is QEngineeringLineNoTextItem] |
514 |
for lineNoTextItem in lineNoTextItems: |
515 |
lineNoTextItem.set_property('Freeze', False) |
516 |
# lineNoTextItem.freeze_item.update_freeze(lineNoTextItem.prop('Freeze'))
|
517 |
lineNoTextItem.explode() |
518 |
|
519 |
for textItem in textItems: |
520 |
textInfoList.append( |
521 |
TextInfo(textItem.text(), textItem.loc[0], textItem.loc[1], textItem.size[0], |
522 |
textItem.size[1], \
|
523 |
round(math.degrees(textItem.angle))))
|
524 |
|
525 |
textItem.owner = None
|
526 |
worker.graphicsView.scene().removeItem(textItem) |
527 |
|
528 |
worker.updateBatchProgress.emit(len(srcList), 2) |
529 |
|
530 |
# check symbol validate
|
531 |
valid_sym = [] |
532 |
for index in reversed(range(len(searchedTextSymList))): |
533 |
sym_xmin = searchedTextSymList[index].getSp()[0]
|
534 |
sym_ymin = searchedTextSymList[index].getSp()[1]
|
535 |
sym_xmax = searchedTextSymList[index].getSp()[0] + searchedTextSymList[index].getWidth()
|
536 |
sym_ymax = searchedTextSymList[index].getSp()[1] + searchedTextSymList[index].getHeight()
|
537 |
valid_count = searchedTextSymList[index].getHasInstrumentLabel() |
538 |
valid = 0
|
539 |
for text_info in textInfoList: |
540 |
info_center = text_info.center |
541 |
if info_center[0] > sym_xmin and info_center[0] < sym_xmax and info_center[1] > sym_ymin and \ |
542 |
info_center[1] < sym_ymax:
|
543 |
valid += 1
|
544 |
if valid >= valid_count:
|
545 |
valid_sym.append(searchedTextSymList[index]) |
546 |
break
|
547 |
if valid < valid_count:
|
548 |
searchedSymbolList.pop(searchedSymbolList.index(searchedTextSymList[index])) |
549 |
|
550 |
pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) |
551 |
|
552 |
for sym in valid_sym: |
553 |
pool.submit(Worker.remove_detected_symbol_image, sym, app_doc_data.imgSrc) |
554 |
pool.shutdown(wait=True)
|
555 |
# up to here
|
556 |
|
557 |
# remove text from image
|
558 |
Worker.drawFoundSymbolsOnCanvas(mainRes, textInfoList, listWidget) |
559 |
textDetector.remove_text_from_image(area.img, offset) |
560 |
if not worker.isTextChecked: |
561 |
textInfoList.clear() |
562 |
# up to here
|
563 |
|
564 |
removedSymbolImgPath = os.path.join(project.getTempPath(), os.path.basename(mainRes)) |
565 |
cv2.imwrite(removedSymbolImgPath, app_doc_data.imgSrc) |
566 |
|
567 |
area = AppDocData.instance().getArea('Drawing')
|
568 |
if area is not None: |
569 |
area.img = app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height), |
570 |
round(area.x + 1):round(area.x + area.width)] |
571 |
cv2.imwrite(os.path.join(project.getTempPath(), "RECT_" + os.path.basename(mainRes)),
|
572 |
app_doc_data.imgSrc) |
573 |
|
574 |
listWidget.addItem("Recognized symbol count : " + str(len(searchedSymbolList))) |
575 |
|
576 |
# get difference between original and recognized image
|
577 |
foundFilePath = os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(mainRes))
|
578 |
Worker.getDifference(mainRes, foundFilePath) |
579 |
# up to here
|
580 |
|
581 |
listWidget.addItem(worker.tr('Creating detected infos...'))
|
582 |
worker.displayTitle.emit(worker.tr('Creating detected infos...'))
|
583 |
createDetectedItems(searchedSymbolList, textInfoList, |
584 |
otherTextInfoList if otherTextInfoList is not None else [], |
585 |
titleBlockTextInfoList if titleBlockTextInfoList is not None else []) |
586 |
|
587 |
if isLineChecked:
|
588 |
Worker.recognizeLine(mainRes, listWidget, worker.graphicsView, worker, batch) |
589 |
else:
|
590 |
lineItems = [item for item in worker.graphicsView.scene().items() |
591 |
if issubclass(type(item), QEngineeringLineItem)] |
592 |
app_doc_data.lines.extend(lineItems) |
593 |
app_doc_data.allItems.extend(lineItems) |
594 |
|
595 |
for lineItem in lineItems: |
596 |
lineItem.owner = None
|
597 |
for conn in lineItem.connectors: |
598 |
conn.connectedItem = None
|
599 |
worker.graphicsView.scene().removeItem(lineItem) |
600 |
|
601 |
# connect symbol to symbol
|
602 |
try:
|
603 |
configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line') |
604 |
toler = int(configs[0].value) if configs else 20 |
605 |
for symbol in app_doc_data.symbols: |
606 |
matches = [it for it in app_doc_data.symbols if |
607 |
it is not symbol and symbol.is_connectable(it, toler=toler)] |
608 |
for match in matches: |
609 |
symbol.connect_if_possible(match) |
610 |
except Exception as ex: |
611 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
612 |
sys.exc_info()[-1].tb_lineno)
|
613 |
worker.displayLog.emit(MessageType.Error, message) |
614 |
# up to here
|
615 |
|
616 |
listWidget.addItem('Connecting lines')
|
617 |
area = app_doc_data.getArea('Drawing')
|
618 |
detector = LineDetector(area.img) |
619 |
symbols = app_doc_data.symbols |
620 |
configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line') |
621 |
toler = int(configs[0].value) if configs else 20 |
622 |
if app_doc_data.lines:
|
623 |
# connect line to symbol
|
624 |
try:
|
625 |
for line in app_doc_data.lines: |
626 |
matches = [symbol for symbol in symbols if symbol.is_connectable(line, toler=toler)] |
627 |
for symbol in matches: |
628 |
detector.connectLineToSymbol(line, symbol, toler=toler) |
629 |
except Exception as ex: |
630 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[
|
631 |
-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno) |
632 |
worker.displayLog.emit(MessageType.Error, message) |
633 |
# up to here
|
634 |
|
635 |
# connect line to line
|
636 |
try:
|
637 |
for line in app_doc_data.lines: |
638 |
matches = [it for it in app_doc_data.lines if |
639 |
(it is not line) and (not line.isParallel(it))] |
640 |
|
641 |
for match in matches: |
642 |
detector.connectLineToLine(match, line, toler) |
643 |
|
644 |
# change line type using symbol connection type(info)
|
645 |
for sym in symbols: |
646 |
if sym.conn_type:
|
647 |
for index in range(len(sym.conn_type)): |
648 |
item = sym.connectors[index].connectedItem |
649 |
if item and type(item) is QEngineeringLineItem: |
650 |
Worker.changeConnectedLineType(item, sym.conn_type[index]) |
651 |
except Exception as ex: |
652 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[
|
653 |
-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno) |
654 |
worker.displayLog.emit(MessageType.Error, message) |
655 |
# up to here
|
656 |
|
657 |
# remove line has not connected item
|
658 |
try:
|
659 |
count = 1
|
660 |
while count > 0: |
661 |
count = 0
|
662 |
for line in reversed(app_doc_data.lines): |
663 |
if not line.has_connection: |
664 |
app_doc_data.lines.remove(line) |
665 |
app_doc_data.allItems.remove(line) |
666 |
count += 1
|
667 |
for item in app_doc_data.lines + symbols: |
668 |
for conn in item.connectors: |
669 |
if conn.connectedItem is line: |
670 |
conn.connectedItem = None
|
671 |
|
672 |
except Exception as ex: |
673 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[
|
674 |
-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno) |
675 |
worker.displayLog.emit(MessageType.Error, message) |
676 |
# up to here
|
677 |
|
678 |
createUnknownItems(mainRes) |
679 |
|
680 |
worker.updateBatchProgress.emit(len(srcList), 1) |
681 |
except Exception as ex: |
682 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
683 |
sys.exc_info()[-1].tb_lineno)
|
684 |
worker.displayLog.emit(MessageType.Error, message) |
685 |
finally:
|
686 |
pass
|
687 |
|
688 |
@staticmethod
|
689 |
def changeConnectedLineType(line, lineType): |
690 |
line.lineType = lineType |
691 |
if type(line.connectors[0].connectedItem) is QEngineeringLineItem and \ |
692 |
(line.connectors[0].connectedItem.connectors[0].connectedItem is line or |
693 |
line.connectors[0].connectedItem.connectors[1].connectedItem is line) and \ |
694 |
line.connectors[0].connectedItem.lineType is not lineType: |
695 |
Worker.changeConnectedLineType(line.connectors[0].connectedItem, lineType)
|
696 |
if type(line.connectors[1].connectedItem) is QEngineeringLineItem and \ |
697 |
(line.connectors[1].connectedItem.connectors[0].connectedItem is line or |
698 |
line.connectors[1].connectedItem.connectors[1].connectedItem is line) and \ |
699 |
line.connectors[1].connectedItem.lineType is not lineType: |
700 |
Worker.changeConnectedLineType(line.connectors[1].connectedItem, lineType)
|
701 |
|
702 |
'''
|
703 |
@history 2018.05.25 Jeongwoo Moved from MainWindow
|
704 |
2018.05.28 Jeongwoo Add xmlPath Parameter and append LineInfo into xml
|
705 |
2018.05.29 Jeongwoo Change method to add item
|
706 |
2018.05.30 Jeongwoo Remove parameter (xmlPath)
|
707 |
humkyung 2018.06.11 add drawing path to parameter and write recognized lines to image
|
708 |
humkyung 2018.07.04 call arrangeLinePosition after creating line
|
709 |
'''
|
710 |
|
711 |
@staticmethod
|
712 |
def recognizeLine(path, listWidget, graphicsView, worker, batch): |
713 |
from shapely.geometry import Point, LineString |
714 |
from SymbolSvgItem import SymbolSvgItem |
715 |
from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem |
716 |
from EngineeringLineNoTextItem import QEngineeringLineNoTextItem |
717 |
from EngineeringTextItem import QEngineeringTextItem |
718 |
from EngineeringLineItem import QEngineeringLineItem |
719 |
from LineDetector import LineDetector |
720 |
from HoughBundler import HoughBundler |
721 |
|
722 |
try:
|
723 |
listWidget.addItem('Starting line recognition')
|
724 |
worker.displayTitle.emit(worker.tr('Detecting lines...'))
|
725 |
|
726 |
app_doc_data = AppDocData.instance() |
727 |
project = app_doc_data.getCurrentProject() |
728 |
# remove already existing line and flow arrow item
|
729 |
if not batch: |
730 |
items = [item for item in worker.graphicsView.scene().items() if (type(item) is QEngineeringLineItem)] |
731 |
for item in items: |
732 |
item.transfer.onRemoved.emit(item) |
733 |
# up to here
|
734 |
|
735 |
# detect line
|
736 |
connectedLines = [] |
737 |
|
738 |
area = app_doc_data.getArea('Drawing')
|
739 |
area.img = worker.remove_small_objects(area.img) |
740 |
detector = LineDetector(area.img) |
741 |
|
742 |
symbols = [] |
743 |
for item in app_doc_data.symbols: |
744 |
if issubclass(type(item), SymbolSvgItem): |
745 |
symbols.append(item) |
746 |
res = detector.detectConnectedLine(item, round(area.x), round(area.y)) |
747 |
if res is not None: |
748 |
connectedLines.extend(res) |
749 |
|
750 |
# line detection without symbol connection point info
|
751 |
remainLines = detector.detect_line_without_symbol() |
752 |
windowSize = app_doc_data.getSlidingWindowSize() |
753 |
thickness = int(windowSize[1]) |
754 |
|
755 |
configs = app_doc_data.getConfigs('Line', 'Diagonal') |
756 |
if configs and int(configs[0].value) is not 1: |
757 |
diagonal = HoughBundler() |
758 |
for index in reversed(range(len(remainLines))): |
759 |
angle = diagonal.get_orientation([remainLines[index][0][0], remainLines[index][0][1], remainLines[index][1][0], remainLines[index][1][1]]) |
760 |
if not (angle < 3 or angle > 87): |
761 |
remainLines.pop(index) |
762 |
|
763 |
for line in remainLines: |
764 |
line.append(thickness) |
765 |
connectedLines.extend(remainLines) |
766 |
|
767 |
configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line') |
768 |
toler = int(configs[0].value) if configs else 20 |
769 |
|
770 |
symbol_areas = [] |
771 |
for symbol in symbols: |
772 |
symbol_areas.append(QRect(symbol.loc[0] - area.x, symbol.loc[1] - area.y, symbol.size[0], symbol.size[1])) |
773 |
|
774 |
detector.mergeLines(connectedLines, toler=toler, symbol_areas=symbol_areas) |
775 |
|
776 |
for pts in connectedLines: |
777 |
line = QEngineeringLineItem( |
778 |
vertices=[(area.x + param[0], area.y + param[1]) for param in pts[:-1]], thickness=pts[2]) |
779 |
line.area = 'Drawing'
|
780 |
|
781 |
app_doc_data.lines.append(line) |
782 |
app_doc_data.allItems.append(line) |
783 |
except Exception as ex: |
784 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
785 |
sys.exc_info()[-1].tb_lineno)
|
786 |
worker.displayLog.emit(MessageType.Error, message) |
787 |
finally:
|
788 |
listWidget.addItem('Finishing line recognition')
|
789 |
worker.finished.emit() |
790 |
|
791 |
'''
|
792 |
@history 2018.04.24 Jeongwoo Add isExceptDetect Field
|
793 |
2018.05.09 Jeongwoo Add targetSymbolList clear
|
794 |
humkyung 2018.07.07 store symbols to list as like [equipments],[nozzles],[symbols]
|
795 |
'''
|
796 |
|
797 |
@staticmethod
|
798 |
def initTargetSymbolDataList(): |
799 |
global targetSymbolList
|
800 |
|
801 |
targetSymbolList.clear() |
802 |
appDocData = AppDocData.instance() |
803 |
symbolList = appDocData.getTargetSymbolList() |
804 |
equipments = [item for item in symbolList if appDocData.getSymbolCategoryByType(item.getType()) == 'Equipment'] |
805 |
nozzles = [item for item in symbolList if item.getType() == 'Nozzles'] |
806 |
# [[equipments],[nozzles],[symbols]]
|
807 |
targetSymbolList.append(equipments) |
808 |
targetSymbolList.append(nozzles) |
809 |
targetSymbolList.append([item for item in symbolList if item not in equipments and item not in nozzles]) |
810 |
|
811 |
return targetSymbolList
|
812 |
|
813 |
'''
|
814 |
@brief detect equipment
|
815 |
@author humkyung
|
816 |
@date 2018.07.07
|
817 |
'''
|
818 |
|
819 |
@staticmethod
|
820 |
def detectEquipmentOnPid(mainRes, targetSymbol, listWidget, worker): |
821 |
try:
|
822 |
equipments = Worker.detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker) |
823 |
for equipment in equipments: |
824 |
# detect nozzles around equimpent
|
825 |
for nozzle in targetSymbolList[1]: |
826 |
Worker.detectNozzleOnPid(equipment, nozzle, listWidget, worker) |
827 |
# up to here
|
828 |
except Exception as ex: |
829 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
830 |
sys.exc_info()[-1].tb_lineno)
|
831 |
worker.displayLog(MessageType.Error, message) |
832 |
|
833 |
'''
|
834 |
@brief detect symbol on PID
|
835 |
@author jwkim
|
836 |
@date
|
837 |
@history humkyung 2018.04.06 check if symbol file exists
|
838 |
Jeongwoo 2018.05.29 Change method to adjust detail symbol location with hit-rate. Not feature point count
|
839 |
Change parameter on add symbol part (mpCount → hitRate)
|
840 |
Remove unusing calculation (avg)
|
841 |
Jeongwoo 2018.06.27 Remove part to split P&ID image and for loop
|
842 |
humkyung 2018.07.07 return searched symbols
|
843 |
'''
|
844 |
|
845 |
@staticmethod
|
846 |
def detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker): |
847 |
import copy |
848 |
global ocrCompletedSrc
|
849 |
global threadLock
|
850 |
global maxProgressValue
|
851 |
|
852 |
try:
|
853 |
forTraining = worker.isTrainingChecked |
854 |
symbolName = targetSymbol.getName() |
855 |
symbolType = targetSymbol.getType() |
856 |
symbolPath = targetSymbol.getPath() |
857 |
symbolThreshold = targetSymbol.getThreshold() # if not forTraining else targetSymbol.getThreshold() / 3 * 2
|
858 |
symbolMinMatchCount = targetSymbol.getMinMatchCount() |
859 |
isDetectOnOrigin = targetSymbol.getIsDetectOnOrigin() |
860 |
symbolRotateCount = targetSymbol.getRotationCount() |
861 |
symbolOcrOption = targetSymbol.getOcrOption() |
862 |
isContainChild = targetSymbol.getIsContainChild() |
863 |
symbolOriginalPoint = targetSymbol.getOriginalPoint() |
864 |
symbolConnectionPoint = targetSymbol.getConnectionPoint() |
865 |
baseSymbol = targetSymbol.getBaseSymbol() |
866 |
additionalSymbol = targetSymbol.getAdditionalSymbol() |
867 |
isExceptDetect = targetSymbol.getIsExceptDetect() |
868 |
detectFlip = targetSymbol.getDetectFlip() |
869 |
hasInstrumentLabel = targetSymbol.getHasInstrumentLabel() |
870 |
text_area = targetSymbol.getText_area() |
871 |
|
872 |
# check if symbol file is target or not
|
873 |
if isExceptDetect == 1: |
874 |
item = QListWidgetItem('{} file is not target'.format(symbolName))
|
875 |
item.setBackground(QColor('green'))
|
876 |
listWidget.addItem(item) |
877 |
return
|
878 |
|
879 |
foundSymbolCount = 0
|
880 |
|
881 |
# check if symbol file exists
|
882 |
if not os.path.isfile(symbolPath): |
883 |
item = QListWidgetItem('{} file not found'.format(symbolName))
|
884 |
item.setBackground(QColor('red'))
|
885 |
listWidget.addItem(item) |
886 |
return
|
887 |
# up to here
|
888 |
|
889 |
sym = cv2.imread(symbolPath, 1)
|
890 |
symGray = Worker.cvtGrayImage(sym) |
891 |
symGrayOri = copy.copy(symGray) |
892 |
## TODO: 이진화 시켰을때 심볼이 검출되지 않음
|
893 |
## symGray = cv2.threshold(cvtGrayImage(sym), 127, 255, cv2.THRESH_BINARY)[1]
|
894 |
## cv2.imshow('symbol', symGray)
|
895 |
## cv2.waitKey(0)
|
896 |
sow, soh = symGray.shape[::-1] # symbol original w, h |
897 |
|
898 |
offsetDrawingArea = [] |
899 |
app_doc_data = AppDocData.instance() |
900 |
area = app_doc_data.getArea('Drawing')
|
901 |
if area is not None: |
902 |
roiItem = area.img.copy() if hasInstrumentLabel else area.img |
903 |
offsetDrawingArea.append(area.x) |
904 |
offsetDrawingArea.append(area.y) |
905 |
else:
|
906 |
offsetDrawingArea.append(0)
|
907 |
offsetDrawingArea.append(0)
|
908 |
if isDetectOnOrigin == 1: |
909 |
roiItem = app_doc_data.imgSrc |
910 |
else:
|
911 |
roiItem = ocrCompletedSrc |
912 |
srcWidth, srcHeight = roiItem.shape[::-1]
|
913 |
|
914 |
roiItemSp = (0, 0) |
915 |
roiItemEp = (srcWidth, srcHeight) |
916 |
|
917 |
# when text is located inside of symbol
|
918 |
if area is not None and hasInstrumentLabel: |
919 |
# remove objects smaller than symbol
|
920 |
selected_contours = [] |
921 |
contours, hierarchy = cv2.findContours(area.mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) |
922 |
index = 0
|
923 |
for contour in contours: |
924 |
child, parent = hierarchy[0][index][2], hierarchy[0][index][3] |
925 |
[x, y, w, h] = cv2.boundingRect(contour) |
926 |
if w * h < sow * soh * 0.5: |
927 |
contour_area = cv2.contourArea(contour, True)
|
928 |
if contour_area > 0: |
929 |
selected_contours.append(contour) |
930 |
index += 1
|
931 |
# up to here
|
932 |
|
933 |
# draw contour with white color
|
934 |
roiItem = cv2.drawContours(roiItem, selected_contours, -1, (255, 255, 255), -1) |
935 |
# cv2.imwrite("c:\\temp\\roiItem.png", roiItem)
|
936 |
|
937 |
# try to recognize symbol twice(first one is normal, second on is flipped)
|
938 |
steps = [False, True] if detectFlip else [False] |
939 |
for flipped in steps: |
940 |
if flipped:
|
941 |
symGray = symGrayOri |
942 |
symGray = cv2.flip(symGray, 1)
|
943 |
|
944 |
symbolRotatedAngle = 0
|
945 |
for rc in range(symbolRotateCount + 1): # Rotation count를 사용자 기준으로 받아서 1을 더한 후 사용 |
946 |
sw, sh = symGray.shape[::-1]
|
947 |
roiw = (roiItemEp[0] - roiItemSp[0]) |
948 |
roih = (roiItemEp[1] - roiItemSp[1]) |
949 |
|
950 |
# Case : Bigger Symbol than Split ROI
|
951 |
if roiw < sw or roih < sh: |
952 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_COUNTERCLOCKWISE) |
953 |
symbolRotatedAngle = symbolRotatedAngle + 90
|
954 |
|
955 |
if baseSymbol is not None and additionalSymbol is not None: |
956 |
additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol) |
957 |
continue
|
958 |
|
959 |
# get Rotated Original Point
|
960 |
originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, |
961 |
symbolRotatedAngle, sw, sh, sow, soh, flipped) |
962 |
connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw, |
963 |
sh, sow, soh, flipped) |
964 |
|
965 |
# # For OPC
|
966 |
# drawing_area = app_doc_data.getArea('Drawing')
|
967 |
# if drawing_area is not None and (symbolType == "Piping OPC\'s" or symbolType == "Instrument OPC\'s"):
|
968 |
# customMatch = worker.detectOPCOnPid(drawing_area, symGray)
|
969 |
# if customMatch and len(customMatch) > 0:
|
970 |
# for custom in customMatch:
|
971 |
# hitRate = custom[0]
|
972 |
# searchedItemSp = (custom[1][0] + round(offsetDrawingArea[0]), custom[1][1] + round(offsetDrawingArea[1]))
|
973 |
#
|
974 |
# is_add = True
|
975 |
# for searched in searchedSymbolList:
|
976 |
# if Worker.IsOverlap((searchedItemSp[0], searchedItemSp[1], sw, sh), (
|
977 |
# searched.getSp()[0], searched.getSp()[1], searched.getWidth(),
|
978 |
# searched.getHeight())):
|
979 |
# if searched.getHitRate() > hitRate:
|
980 |
# is_add = False
|
981 |
# else:
|
982 |
# searchedSymbolList.remove(searched)
|
983 |
# break
|
984 |
#
|
985 |
# if is_add:
|
986 |
# threadLock.acquire()
|
987 |
# foundSymbolCount = foundSymbolCount + 1
|
988 |
# Worker.addSearchedSymbol(symbolName, symbolType,
|
989 |
# searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount,
|
990 |
# hitRate, symbolRotatedAngle,
|
991 |
# isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
|
992 |
# isContainChild,
|
993 |
# originalPoint, connectionPoint, baseSymbol, additionalSymbol,
|
994 |
# isExceptDetect, detectFlip=1 if flipped else 0,
|
995 |
# hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
|
996 |
# threadLock.release()
|
997 |
|
998 |
# Template Matching
|
999 |
tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED) |
1000 |
loc = np.where(tmRes >= symbolThreshold) |
1001 |
|
1002 |
for pt in zip(*loc[::-1]): |
1003 |
mpCount = 0 # Match Point Count |
1004 |
|
1005 |
roi = roiItem[pt[1]:pt[1] + sh, pt[0]:pt[0] + sw] |
1006 |
|
1007 |
if symbolMinMatchCount > 0: |
1008 |
mpCount = Worker.getMatchPointCount(roi, symGray) |
1009 |
if not (mpCount >= symbolMinMatchCount): |
1010 |
continue
|
1011 |
|
1012 |
searchedItemSp = (roiItemSp[0] + pt[0] + round(offsetDrawingArea[0]), |
1013 |
roiItemSp[1] + pt[1] + round(offsetDrawingArea[1])) |
1014 |
# print(searchedItemSp)
|
1015 |
|
1016 |
overlapArea = 0
|
1017 |
symbolIndex = -1
|
1018 |
for i in range(len(searchedSymbolList) - 1, -1, -1): |
1019 |
area = Worker.contains(searchedSymbolList[i], searchedItemSp, sw, sh) |
1020 |
if area > ACCEPT_OVERLAY_AREA:
|
1021 |
# if area > overlapArea:
|
1022 |
# overlapArea = area
|
1023 |
# symbolIndex = i
|
1024 |
overlapArea = area |
1025 |
symbolIndex = i |
1026 |
break
|
1027 |
"""
|
1028 |
categories = [appDocData.isEquipmentType(symbolType), appDocData.isEquipmentType(searchedSymbolList[i].getType())]
|
1029 |
if categories[0] == categories[1]:
|
1030 |
symbolIndex = i
|
1031 |
break
|
1032 |
"""
|
1033 |
|
1034 |
hitRate = tmRes[pt[1], pt[0]] |
1035 |
|
1036 |
# 겹치는 영역이 기준값보다 작을 경우
|
1037 |
if overlapArea <= ACCEPT_OVERLAY_AREA:
|
1038 |
threadLock.acquire() |
1039 |
foundSymbolCount = foundSymbolCount + 1
|
1040 |
Worker.addSearchedSymbol(symbolName, symbolType, |
1041 |
searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, |
1042 |
hitRate, symbolRotatedAngle, |
1043 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, |
1044 |
isContainChild, |
1045 |
originalPoint, connectionPoint, baseSymbol, additionalSymbol, |
1046 |
isExceptDetect, |
1047 |
detectFlip=1 if flipped else 0, |
1048 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
1049 |
threadLock.release() |
1050 |
else: # 겹치는 영역이 기준값보다 클 경우 |
1051 |
if symbolIndex != -1 and symbolIndex < len(searchedSymbolList): |
1052 |
searchedSymbol = searchedSymbolList[symbolIndex] |
1053 |
# 현재 심볼과 검출된 심볼이 같을 경우 Match Point가 더 높은 정보로 교체
|
1054 |
if symbolName == searchedSymbol.getName():
|
1055 |
symbolHitRate = searchedSymbol.getHitRate() |
1056 |
if symbolHitRate - searchedSymbol.getThreshold() < hitRate - symbolThreshold:
|
1057 |
threadLock.acquire() |
1058 |
# replace existing symbol with new symbol has high accuracy
|
1059 |
searchedSymbolList[symbolIndex] = symbol.Symbol(symbolName, symbolType, |
1060 |
searchedItemSp, sw, sh, |
1061 |
symbolThreshold, |
1062 |
symbolMinMatchCount, hitRate, |
1063 |
symbolRotatedAngle, |
1064 |
isDetectOnOrigin, |
1065 |
symbolRotateCount, |
1066 |
symbolOcrOption, isContainChild, |
1067 |
','.join(str(x) for x in |
1068 |
originalPoint), |
1069 |
'/'.join('{},{},{},{}'.format( |
1070 |
param[0], param[1], |
1071 |
param[2], param[3]) for |
1072 |
param in
|
1073 |
connectionPoint), |
1074 |
baseSymbol, additionalSymbol, |
1075 |
isExceptDetect, |
1076 |
detectFlip=1 if flipped else 0, |
1077 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
1078 |
threadLock.release() |
1079 |
# 현재 심볼과 검출된 심볼이 같지 않을 경우 (포함)
|
1080 |
elif app_doc_data.isEquipmentType(searchedSymbol.getType()):
|
1081 |
if searchedSymbol.area > sw * sh * 10: # searched equipment area is greather than 10 times of symbol's area |
1082 |
threadLock.acquire() |
1083 |
foundSymbolCount = foundSymbolCount + 1
|
1084 |
Worker.addSearchedSymbol(symbolName, symbolType, |
1085 |
searchedItemSp, sw, sh, symbolThreshold, hitRate, |
1086 |
hitRate, symbolRotatedAngle, |
1087 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, |
1088 |
isContainChild, |
1089 |
originalPoint, connectionPoint, baseSymbol, |
1090 |
additionalSymbol, isExceptDetect, |
1091 |
detectFlip=1 if flipped else 0, |
1092 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
1093 |
threadLock.release() |
1094 |
# 현재 심볼과 검출된 심볼이 같지 않을 경우 (교체)
|
1095 |
elif not forTraining: |
1096 |
searchedSymbol = searchedSymbolList[symbolIndex] |
1097 |
symbolHitRate = searchedSymbol.getHitRate() |
1098 |
if symbolHitRate - searchedSymbol.getThreshold() < hitRate - symbolThreshold:
|
1099 |
threadLock.acquire() |
1100 |
searchedSymbolList[symbolIndex] = symbol.Symbol(symbolName, symbolType, |
1101 |
searchedItemSp, sw, sh, |
1102 |
symbolThreshold, |
1103 |
symbolMinMatchCount, hitRate, |
1104 |
symbolRotatedAngle, |
1105 |
isDetectOnOrigin, |
1106 |
symbolRotateCount, |
1107 |
symbolOcrOption, isContainChild, |
1108 |
','.join(str(x) for x in |
1109 |
originalPoint), |
1110 |
'/'.join('{},{},{},{}'.format( |
1111 |
param[0], param[1], |
1112 |
param[2], param[3]) for |
1113 |
param in
|
1114 |
connectionPoint), |
1115 |
baseSymbol, additionalSymbol, |
1116 |
isExceptDetect, |
1117 |
detectFlip=1 if flipped else 0, |
1118 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
1119 |
threadLock.release() |
1120 |
# 학습용 데이터 생성을 위해 교체하지 않고 추가함
|
1121 |
elif forTraining:
|
1122 |
threadLock.acquire() |
1123 |
foundSymbolCount = foundSymbolCount + 1
|
1124 |
Worker.addSearchedSymbol(symbolName, symbolType, |
1125 |
searchedItemSp, sw, sh, symbolThreshold, |
1126 |
symbolMinMatchCount, hitRate, symbolRotatedAngle, |
1127 |
isDetectOnOrigin, symbolRotateCount, symbolOcrOption, |
1128 |
isContainChild, |
1129 |
originalPoint, connectionPoint, baseSymbol, |
1130 |
additionalSymbol, isExceptDetect, |
1131 |
detectFlip=1 if flipped else 0, |
1132 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
1133 |
threadLock.release() |
1134 |
|
1135 |
# Rotate Symbol
|
1136 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_COUNTERCLOCKWISE) |
1137 |
symbolRotatedAngle = symbolRotatedAngle + 90
|
1138 |
|
1139 |
if additionalSymbol is not None: |
1140 |
additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol) |
1141 |
|
1142 |
threadLock.acquire() |
1143 |
listWidget.addItem('Found Symbol : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str( |
1144 |
foundSymbolCount) + ')')
|
1145 |
threadLock.release() |
1146 |
|
1147 |
"""
|
1148 |
if area is not None and hasInstrumentLabel:
|
1149 |
# restore objects smaller than symbol
|
1150 |
roiItem = cv2.drawContours(roiItem, outside_contours, -1, (0, 0, 0), -1)
|
1151 |
roiItem = cv2.drawContours(roiItem, hole_contours, -1, (255, 255, 255), -1)
|
1152 |
# up to here
|
1153 |
cv2.imwrite('c:\\Temp\\contour2.png', roiItem)
|
1154 |
"""
|
1155 |
|
1156 |
worker.updateProgress.emit(maxProgressValue, symbolPath) |
1157 |
|
1158 |
return [symbol for symbol in searchedSymbolList if symbol.getName() == symbolName] |
1159 |
except Exception as ex: |
1160 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1161 |
sys.exc_info()[-1].tb_lineno)
|
1162 |
worker.displayLog.emit(MessageType.Error, message) |
1163 |
|
1164 |
return []
|
1165 |
|
1166 |
@staticmethod
|
1167 |
def IsOverlap(range1, range2): |
1168 |
if range1[0] <= range2[0] + range2[2] and range1[0] + range1[2] >= range2[0] and range1[1] <= range2[1] + \ |
1169 |
range2[3] and range1[1] + range1[3] >= range2[1]: |
1170 |
range = (min(range1[0] + range1[2], range2[0] + range2[2]) - max(range1[0], range2[0])) * ( |
1171 |
min(range1[1] + range1[3], range2[1] + range2[3]) - max(range1[1], range2[1])) |
1172 |
if range >= range1[2] * range1[3] * 0.4 and range >= range2[2] * range2[3] * 0.4: |
1173 |
return True |
1174 |
else:
|
1175 |
return False |
1176 |
else:
|
1177 |
return False |
1178 |
|
1179 |
@staticmethod
|
1180 |
def detectOPCOnPid(area, symGray): |
1181 |
results = [] |
1182 |
|
1183 |
try:
|
1184 |
symbol = cv2.copyMakeBorder(symGray, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=255) |
1185 |
not_symbol = cv2.bitwise_not(symbol) |
1186 |
symbol_contours, symbol_hierachy = cv2.findContours(not_symbol, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) |
1187 |
if symbol_hierachy[0][0][0] != -1: |
1188 |
return results
|
1189 |
|
1190 |
contour_count = len(symbol_contours)
|
1191 |
for i in range(1, contour_count): |
1192 |
symbol_area = cv2.contourArea(symbol_contours[i]) |
1193 |
|
1194 |
for contour in area.contours: |
1195 |
area_area = cv2.contourArea(contour) |
1196 |
if area_area * 1.2 >= symbol_area >= area_area * 0.8: |
1197 |
I1 = cv2.matchShapes(symbol_contours[i], contour, 1, 0) |
1198 |
I2 = cv2.matchShapes(symbol_contours[i], contour, 2, 0) |
1199 |
I3 = cv2.matchShapes(symbol_contours[i], contour, 3, 0) |
1200 |
if I1 < 1 and I2 < 1 and I3 < 0.1: |
1201 |
# need shape check
|
1202 |
|
1203 |
rect = cv2.boundingRect(contour) |
1204 |
results.append([1 - I3, [rect[0], rect[1]]]) |
1205 |
break
|
1206 |
except Exception as ex: |
1207 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1208 |
sys.exc_info()[-1].tb_lineno)
|
1209 |
return results
|
1210 |
|
1211 |
'''
|
1212 |
@brief detect nozzlez
|
1213 |
@author humkyung
|
1214 |
@date 2018.07.07
|
1215 |
@history humkyhung 2018.07.17 pass equpment as parameter instead of image
|
1216 |
'''
|
1217 |
@staticmethod
|
1218 |
def detectNozzleOnPid(equipment, nozzle, listWidget, worker): |
1219 |
global src
|
1220 |
global threadLock
|
1221 |
global maxProgressValue
|
1222 |
|
1223 |
try:
|
1224 |
symbolName = nozzle.getName() |
1225 |
symbolType = nozzle.getType() |
1226 |
symbolPath = nozzle.getPath() |
1227 |
symbolThreshold = nozzle.getThreshold() |
1228 |
symbolMinMatchCount = nozzle.getMinMatchCount() |
1229 |
isDetectOnOrigin = nozzle.getIsDetectOnOrigin() |
1230 |
symbolRotateCount = nozzle.getRotationCount() |
1231 |
symbolOcrOption = nozzle.getOcrOption() |
1232 |
isContainChild = nozzle.getIsContainChild() |
1233 |
symbolOriginalPoint = nozzle.getOriginalPoint() |
1234 |
symbolConnectionPoint = nozzle.getConnectionPoint() |
1235 |
baseSymbol = nozzle.getBaseSymbol() |
1236 |
additionalSymbol = nozzle.getAdditionalSymbol() |
1237 |
isExceptDetect = nozzle.getIsExceptDetect() |
1238 |
text_area = text_area |
1239 |
|
1240 |
foundSymbolCount = 0
|
1241 |
|
1242 |
# check if symbol file exists
|
1243 |
if not os.path.isfile(symbolPath): |
1244 |
item = QListWidgetItem('{} file not found'.format(os.path.split(os.path.basename(symbolPath))[0])) |
1245 |
item.setBackground(QColor('red'))
|
1246 |
listWidget.addItem(item) |
1247 |
return
|
1248 |
# up to here
|
1249 |
|
1250 |
symGray = Worker.cvtGrayImage(cv2.imread(symbolPath, 1))
|
1251 |
sow, soh = symGray.shape[::-1] # symbol original w, h |
1252 |
|
1253 |
# get image of equipment with offset of nozzle size
|
1254 |
appDocData = AppDocData.instance() |
1255 |
pt = equipment.getSp() |
1256 |
nozzleSize = max(sow, soh)
|
1257 |
sx = round(pt[0]) - nozzleSize |
1258 |
sy = round(pt[1]) - nozzleSize |
1259 |
ex = round(pt[0] + equipment.getWidth()) + nozzleSize |
1260 |
ey = round(pt[1] + equipment.getHeight()) + nozzleSize |
1261 |
offset = (sx, sy) |
1262 |
eqpSize = (pt[0], pt[1], equipment.getWidth(), equipment.getHeight()) |
1263 |
img = appDocData.imgSrc[sy:ey, sx:ex] |
1264 |
srcWidth, srcHeight = img.shape[::-1]
|
1265 |
# up to here
|
1266 |
|
1267 |
roiItemSp = (0, 0) |
1268 |
roiItemEp = (srcWidth, srcHeight) |
1269 |
roiItem = img |
1270 |
|
1271 |
symbolAngle = 0
|
1272 |
for rc in range(symbolRotateCount + 1): # Rotation Count를 사용자 기준으로 받아서 1을 더한 후 사용 |
1273 |
sw, sh = symGray.shape[::-1]
|
1274 |
roiw = (roiItemEp[0] - roiItemSp[0]) |
1275 |
roih = (roiItemEp[1] - roiItemSp[1]) |
1276 |
|
1277 |
# get Rotated Original Point
|
1278 |
originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolAngle, |
1279 |
sw, sh, sow, soh) |
1280 |
connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolAngle, sw, sh, sow, |
1281 |
soh) |
1282 |
dx = connectionPoint[0][0] - originalPoint[0] |
1283 |
dy = connectionPoint[0][1] - originalPoint[1] |
1284 |
|
1285 |
# Template Matching
|
1286 |
tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED) |
1287 |
loc = np.where(tmRes >= symbolThreshold) |
1288 |
|
1289 |
for pt in zip(*loc[::-1]): |
1290 |
mpCount = 0 # Match Point Count |
1291 |
symbolIndex = -1
|
1292 |
|
1293 |
roi = roiItem[pt[1]:pt[1] + sh, pt[0]:pt[0] + sw] |
1294 |
|
1295 |
if symbolMinMatchCount > 0: |
1296 |
mpCount = Worker.getMatchPointCount(roi, symGray) |
1297 |
if not (mpCount >= symbolMinMatchCount): |
1298 |
continue
|
1299 |
|
1300 |
mid = (offset[0] + pt[0] + (originalPoint[0] + connectionPoint[0][0]) * 0.5, |
1301 |
offset[1] + pt[1] + (originalPoint[1] + connectionPoint[0][1]) * 0.5) |
1302 |
searchedItemSp = (roiItemSp[0] + pt[0] + offset[0], roiItemSp[1] + pt[1] + offset[1]) |
1303 |
# check searched nozzle location
|
1304 |
if abs(dx) > abs(dy): |
1305 |
if dx > 0: |
1306 |
if mid[0] < eqpSize[0] + eqpSize[2] * 0.5: continue |
1307 |
else:
|
1308 |
if mid[0] > eqpSize[0] + eqpSize[2] * 0.5: continue |
1309 |
else:
|
1310 |
if dy > 0: |
1311 |
if mid[1] < eqpSize[1] + eqpSize[3] * 0.5: continue |
1312 |
else:
|
1313 |
if mid[1] > eqpSize[1] + eqpSize[3] * 0.5: continue |
1314 |
# up to here
|
1315 |
|
1316 |
overlapArea = 0
|
1317 |
nozzles = [symbol for symbol in searchedSymbolList if symbol.getType() == 'Nozzles'] |
1318 |
for i in range(len(nozzles)): |
1319 |
_pt = nozzles[i].getSp() |
1320 |
rect = QRectF(_pt[0], _pt[1], nozzles[i].getWidth(), nozzles[i].getHeight()) |
1321 |
_rect = QRectF(searchedItemSp[0], searchedItemSp[1], sw, sh) |
1322 |
if rect.intersects(_rect):
|
1323 |
intersect = rect.intersected(_rect) |
1324 |
overlapArea = intersect.width() * intersect.height() |
1325 |
if overlapArea > ACCEPT_OVERLAY_AREA:
|
1326 |
symbolIndex = i |
1327 |
break
|
1328 |
|
1329 |
hitRate = tmRes[pt[1], pt[0]] |
1330 |
|
1331 |
## 겹치는 영역이 기준값보다 작을 경우
|
1332 |
if overlapArea <= ACCEPT_OVERLAY_AREA:
|
1333 |
threadLock.acquire() |
1334 |
foundSymbolCount = foundSymbolCount + 1
|
1335 |
searched = Worker.addSearchedSymbol(symbolName, symbolType |
1336 |
, searchedItemSp, sw, sh, symbolThreshold, |
1337 |
symbolMinMatchCount, hitRate, symbolAngle |
1338 |
, isDetectOnOrigin, symbolRotateCount, symbolOcrOption, |
1339 |
isContainChild |
1340 |
, originalPoint, connectionPoint, baseSymbol, |
1341 |
additionalSymbol, isExceptDetect, text_area) |
1342 |
searched.owner = equipment |
1343 |
threadLock.release() |
1344 |
## 겹치는 영역이 기준값보다 클 경우
|
1345 |
else:
|
1346 |
if symbolIndex != -1 and symbolIndex < len(nozzles): |
1347 |
searchedSymbol = nozzles[symbolIndex] |
1348 |
## 현재 심볼과 검출된 심볼이 같을 경우 Match Point가 더 높은 정보로 교체
|
1349 |
if symbolName == searchedSymbol.getName():
|
1350 |
symbolHitRate = searchedSymbol.getHitRate() |
1351 |
if symbolHitRate < hitRate:
|
1352 |
threadLock.acquire() |
1353 |
nozzles[symbolIndex] = symbol.Symbol(symbolName, symbolType, |
1354 |
searchedItemSp, sw, sh, symbolThreshold, |
1355 |
symbolMinMatchCount, hitRate, symbolAngle, |
1356 |
isDetectOnOrigin, symbolRotateCount, |
1357 |
symbolOcrOption, isContainChild, |
1358 |
','.join(str(x) for x in originalPoint), |
1359 |
'/'.join(
|
1360 |
'{},{},{},{}'.format(param[0], param[1], |
1361 |
param[2], param[3]) |
1362 |
for param in connectionPoint), |
1363 |
baseSymbol, additionalSymbol, isExceptDetect, text_area) |
1364 |
threadLock.release() |
1365 |
## 현재 심볼과 검출된 심볼이 같지 않을 경우 (포함)
|
1366 |
elif appDocData.isEquipmentType(searchedSymbol.getType()):
|
1367 |
threadLock.acquire() |
1368 |
foundSymbolCount = foundSymbolCount + 1
|
1369 |
searched = Worker.addSearchedSymbol(symbolName, symbolType |
1370 |
, searchedItemSp, sw, sh, symbolThreshold, hitRate, |
1371 |
hitRate, symbolAngle |
1372 |
, isDetectOnOrigin, symbolRotateCount, |
1373 |
symbolOcrOption, isContainChild |
1374 |
, originalPoint, connectionPoint, baseSymbol, |
1375 |
additionalSymbol, isExceptDetect, text_area) |
1376 |
searched.owner = equipment |
1377 |
threadLock.release() |
1378 |
|
1379 |
## Rotate Symbol
|
1380 |
symGray = cv2.rotate(symGray, cv2.ROTATE_90_COUNTERCLOCKWISE) |
1381 |
symbolAngle = symbolAngle + 90
|
1382 |
|
1383 |
if additionalSymbol is not None: |
1384 |
additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol) |
1385 |
|
1386 |
threadLock.acquire() |
1387 |
listWidget.addItem('Found Symbol : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str( |
1388 |
foundSymbolCount) + ')')
|
1389 |
threadLock.release() |
1390 |
|
1391 |
worker.updateProgress.emit(maxProgressValue, symbolPath) |
1392 |
|
1393 |
return [symbol for symbol in searchedSymbolList if symbol.getName() == symbolName] |
1394 |
except Exception as ex: |
1395 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1396 |
sys.exc_info()[-1].tb_lineno)
|
1397 |
worker.displayLog(MessageType.Error, message) |
1398 |
|
1399 |
return []
|
1400 |
|
1401 |
# Convert into Grayscale image
|
1402 |
@staticmethod
|
1403 |
def cvtGrayImage(img): |
1404 |
return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
1405 |
|
1406 |
'''
|
1407 |
@history 2018.06.12 Jeongwoo Type changed (int → float)
|
1408 |
humkyung 2018.07.07 change return type as like [x,y]
|
1409 |
'''
|
1410 |
|
1411 |
@staticmethod
|
1412 |
def getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolRotatedAngle, rotateSymbolWidth, |
1413 |
rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight, flipped=False):
|
1414 |
res = [] |
1415 |
|
1416 |
if additionalSymbol is None and symbolOriginalPoint is None: |
1417 |
res.append(rotateSymbolWidth // 2)
|
1418 |
res.append(rotateSymbolHeight // 2)
|
1419 |
else:
|
1420 |
if flipped:
|
1421 |
opx = originalSymbolWidth - float(symbolOriginalPoint.split(',')[0]) |
1422 |
opy = float(symbolOriginalPoint.split(',')[1]) |
1423 |
else:
|
1424 |
opx = float(symbolOriginalPoint.split(',')[0]) |
1425 |
opy = float(symbolOriginalPoint.split(',')[1]) |
1426 |
|
1427 |
rPt = Worker.getCoordOnRotatedImage(symbolRotatedAngle, ('AUTO', opx, opy, '0'), originalSymbolWidth, |
1428 |
originalSymbolHeight) |
1429 |
|
1430 |
res.append(rPt[1])
|
1431 |
res.append(rPt[2])
|
1432 |
|
1433 |
return res
|
1434 |
|
1435 |
'''
|
1436 |
@history 2018.06.12 Jeongwoo Type changed (int → float)
|
1437 |
humkyung 2018.07.07 change return type as like [[x,y],...]
|
1438 |
humkyung 2019.01.04 get symbol index
|
1439 |
'''
|
1440 |
|
1441 |
@staticmethod
|
1442 |
def getCalculatedConnectionPoint(symbolConnectionPointStr, symbolRotatedAngle, rotateSymbolWidth, |
1443 |
rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight, flipped=0):
|
1444 |
res = [] |
1445 |
|
1446 |
if symbolConnectionPointStr is not None and symbolConnectionPointStr != '': |
1447 |
splitConnectionPointStr = symbolConnectionPointStr.split("/")
|
1448 |
for strConnPt in splitConnectionPointStr: |
1449 |
tokens = strConnPt.split(',')
|
1450 |
|
1451 |
direction = 'AUTO'
|
1452 |
symbol_idx = '0'
|
1453 |
if flipped:
|
1454 |
converted = {'AUTO': 'AUTO', 'LEFT': 'RIGHT', 'RIGHT': 'LEFT', 'UP': 'UP', 'DOWN': 'DOWN'} |
1455 |
|
1456 |
if len(tokens) == 2: |
1457 |
cpx = originalSymbolWidth - float(tokens[0]) |
1458 |
cpy = float(tokens[1]) |
1459 |
elif len(tokens) == 3: |
1460 |
direction = converted[tokens[0]]
|
1461 |
cpx = originalSymbolWidth - float(tokens[1]) |
1462 |
cpy = float(tokens[2]) |
1463 |
elif len(tokens) >= 4: |
1464 |
direction = converted[tokens[0]]
|
1465 |
cpx = originalSymbolWidth - float(tokens[1]) |
1466 |
cpy = float(tokens[2]) |
1467 |
symbol_idx = tokens[3]
|
1468 |
else:
|
1469 |
if len(tokens) == 2: |
1470 |
cpx = float(tokens[0]) |
1471 |
cpy = float(tokens[1]) |
1472 |
elif len(tokens) == 3: |
1473 |
direction = tokens[0]
|
1474 |
cpx = float(tokens[1]) |
1475 |
cpy = float(tokens[2]) |
1476 |
elif len(tokens) >= 4: |
1477 |
direction = tokens[0]
|
1478 |
cpx = float(tokens[1]) |
1479 |
cpy = float(tokens[2]) |
1480 |
symbol_idx = tokens[3]
|
1481 |
|
1482 |
res.append(Worker.getCoordOnRotatedImage(symbolRotatedAngle, (direction, cpx, cpy, symbol_idx), |
1483 |
originalSymbolWidth, originalSymbolHeight)) |
1484 |
|
1485 |
return res
|
1486 |
|
1487 |
'''
|
1488 |
@brief rotate (x,y) by given angle
|
1489 |
@author Jeongwoo
|
1490 |
@date 2018.??.??
|
1491 |
@history humkyung 2018.04.13 fixed code when angle is 90 or 270
|
1492 |
Jeongwoo 2018.04.27 Change calculation method with QTransform
|
1493 |
humkyung 2018.09.01 calculate rotated direction
|
1494 |
'''
|
1495 |
|
1496 |
@staticmethod
|
1497 |
def getCoordOnRotatedImage(angle, connPt, originImageWidth, originImageHeight): |
1498 |
rx = None
|
1499 |
ry = None
|
1500 |
|
1501 |
# calculate rotated direction
|
1502 |
direction = connPt[0]
|
1503 |
if direction == 'LEFT': |
1504 |
direction = 'DOWN' if angle == 90 else 'RIGHT' if angle == 180 else 'UP' if angle == 270 else direction |
1505 |
elif direction == 'RIGHT': |
1506 |
direction = 'UP' if angle == 90 else 'LEFT' if angle == 180 else 'DOWN' if angle == 270 else direction |
1507 |
elif direction == 'UP': |
1508 |
direction = 'LEFT' if angle == 90 else 'DOWN' if angle == 180 else 'RIGHT' if angle == 270 else direction |
1509 |
elif direction == 'DOWN': |
1510 |
direction = 'RIGHT' if angle == 90 else 'UP' if angle == 180 else 'LEFT' if angle == 270 else direction |
1511 |
# up to here
|
1512 |
|
1513 |
transform = QTransform() |
1514 |
if angle == 90 or angle == 270: |
1515 |
transform.translate(originImageHeight * 0.5, originImageWidth * 0.5) |
1516 |
elif angle == 0 or angle == 180: |
1517 |
transform.translate(originImageWidth * 0.5, originImageHeight * 0.5) |
1518 |
transform.rotate(-abs(angle))
|
1519 |
transform.translate(-originImageWidth * 0.5, -originImageHeight * 0.5) |
1520 |
point = QPoint(connPt[1], connPt[2]) |
1521 |
point = transform.map(point) |
1522 |
rx = point.x() |
1523 |
ry = point.y() |
1524 |
|
1525 |
symbol_idx = connPt[3]
|
1526 |
|
1527 |
return (direction, rx, ry, symbol_idx)
|
1528 |
|
1529 |
'''
|
1530 |
@brief Add symbols
|
1531 |
@author jwkim
|
1532 |
@date
|
1533 |
@history Change parameter (mpCount → hitRate)
|
1534 |
Yecheol 2018.07.04 Delete Symbol Id
|
1535 |
'''
|
1536 |
|
1537 |
@staticmethod
|
1538 |
def addSearchedSymbol(sName, sType |
1539 |
, sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle |
1540 |
, isDetectOnOrigin, rotateCount, ocrOption, isContainChild |
1541 |
, originalPoint, connectionPoint, baseSymbol, additionalSymbol, isExceptDetect, detectFlip |
1542 |
, hasInstrumentLabel, text_area): |
1543 |
global searchedSymbolList
|
1544 |
|
1545 |
newSym = None
|
1546 |
try:
|
1547 |
newSym = symbol.Symbol(sName, sType |
1548 |
, sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle, |
1549 |
isDetectOnOrigin, rotateCount, ocrOption, isContainChild, |
1550 |
','.join(str(x) for x in originalPoint), |
1551 |
'/'.join('{},{},{},{}'.format(param[0], param[1], param[2], param[3]) for param in |
1552 |
connectionPoint), |
1553 |
baseSymbol, additionalSymbol, isExceptDetect, detectFlip=1 if detectFlip else 0, |
1554 |
hasInstrumentLabel=hasInstrumentLabel, text_area=text_area) |
1555 |
|
1556 |
searchedSymbolList.append(newSym) |
1557 |
except Exception as ex: |
1558 |
from App import App |
1559 |
|
1560 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1561 |
sys.exc_info()[-1].tb_lineno)
|
1562 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
1563 |
|
1564 |
return newSym
|
1565 |
|
1566 |
'''
|
1567 |
@brief Check object contains pt
|
1568 |
@param obj is item in searchedSymbolList
|
1569 |
'''
|
1570 |
|
1571 |
@staticmethod
|
1572 |
def contains(obj, pt, tw, th): |
1573 |
sp = obj.getSp() |
1574 |
width = obj.getWidth() |
1575 |
height = obj.getHeight() |
1576 |
|
1577 |
if sp[0] > pt[0] + tw: |
1578 |
return 0 |
1579 |
if sp[0] + width < pt[0]: |
1580 |
return 0 |
1581 |
if sp[1] > pt[1] + th: |
1582 |
return 0 |
1583 |
if sp[1] + height < pt[1]: |
1584 |
return 0 |
1585 |
|
1586 |
# shared area
|
1587 |
x = max(sp[0], pt[0]) |
1588 |
y = max(sp[1], pt[1]) |
1589 |
w = min(sp[0] + width, pt[0] + tw) - x |
1590 |
h = min(sp[1] + height, pt[1] + th) - y |
1591 |
|
1592 |
return float((w * h)) / float((tw * th)) * 100 |
1593 |
|
1594 |
# Calculate count of keypoint match result
|
1595 |
@staticmethod
|
1596 |
def getMatchPointCount(src, cmp): |
1597 |
matchCount = 0
|
1598 |
|
1599 |
try:
|
1600 |
orb = cv2.ORB_create(1000, 2.0, 2, 1) |
1601 |
|
1602 |
kp1, des1 = orb.detectAndCompute(src, None)
|
1603 |
kp2, des2 = orb.detectAndCompute(cmp, None) |
1604 |
|
1605 |
FLANN_INDEX_LSH = 6
|
1606 |
# table_number : The number of hash tables use
|
1607 |
# key_size : The length of the key in the hash tables
|
1608 |
# multi_probe_level : Number of levels to use in multi-probe (0 for standard LSH)
|
1609 |
# It controls how neighboring buckets are searched
|
1610 |
# Recommended value is 2
|
1611 |
# checks : specifies the maximum leafs to visit when searching for neighbours.
|
1612 |
# LSH : Locality-Sensitive Hashing
|
1613 |
# ref : https://www.cs.ubc.ca/research/flann/uploads/FLANN/flann_manual-1.8.4.pdf
|
1614 |
index_params = dict(algorithm=FLANN_INDEX_LSH, table_number=20, key_size=10, multi_probe_level=4) |
1615 |
search_params = dict(checks=100) |
1616 |
|
1617 |
flann = cv2.FlannBasedMatcher(index_params, search_params) |
1618 |
|
1619 |
matches = flann.knnMatch(des1, des2, k=2)
|
1620 |
matchesMask = [[0, 0] for i in range(len(matches))] # Python 3.x |
1621 |
|
1622 |
count = 0
|
1623 |
# ratio test as per Lowe's paper
|
1624 |
for i in range(len(matches)): |
1625 |
if len(matches[i]) == 2: |
1626 |
m = matches[i][0]
|
1627 |
n = matches[i][1]
|
1628 |
if m.distance < 0.85 * n.distance: |
1629 |
count = count + 1
|
1630 |
|
1631 |
matchCount = count |
1632 |
except Exception as ex: |
1633 |
from App import App |
1634 |
|
1635 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1636 |
sys.exc_info()[-1].tb_lineno)
|
1637 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
1638 |
|
1639 |
return matchCount
|
1640 |
|
1641 |
'''
|
1642 |
@brief Remake rotated child symbol info
|
1643 |
'''
|
1644 |
|
1645 |
@staticmethod
|
1646 |
def getRotatedChildInfo(additionalSymbol): |
1647 |
tempChildInfo = ""
|
1648 |
if additionalSymbol:
|
1649 |
childList = additionalSymbol.split("/")
|
1650 |
for index in range(len(childList)): |
1651 |
child = childList[index] |
1652 |
direction = Worker.convertDirectionCodeToValue(child.split(",")[0]) |
1653 |
childName = child.split(",")[1] |
1654 |
direction = (direction - 1) if direction > 0 else 3 |
1655 |
if index != 0: |
1656 |
tempChildInfo = tempChildInfo + "/"
|
1657 |
tempChildInfo = tempChildInfo + Worker.convertValueToDirectionCode(direction) + "," + childName
|
1658 |
return tempChildInfo
|
1659 |
|
1660 |
'''
|
1661 |
@brief detect symbols on PID
|
1662 |
@history humkyung 2018.06.08 add parameteres for signal
|
1663 |
'''
|
1664 |
|
1665 |
@staticmethod
|
1666 |
def detectSymbolsOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal): |
1667 |
if type(targetSymbols) is list: |
1668 |
for detailTarget in targetSymbols: |
1669 |
Worker.detectSymbolOnPid(mainRes, detailTarget, listWidget, updateProgressSignal) |
1670 |
else:
|
1671 |
Worker.detectSymbolOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal) |
1672 |
|
1673 |
@staticmethod
|
1674 |
def convertDirectionCodeToValue(directionCode): |
1675 |
if directionCode == "UP": |
1676 |
return 0 |
1677 |
elif directionCode == "RIGHT": |
1678 |
return 1 |
1679 |
elif directionCode == "DOWN": |
1680 |
return 2 |
1681 |
elif directionCode == "LEFT": |
1682 |
return 3 |
1683 |
else:
|
1684 |
return -1 |
1685 |
|
1686 |
@staticmethod
|
1687 |
def convertValueToDirectionCode(value): |
1688 |
if value == 0: |
1689 |
return "UP" |
1690 |
elif value == 1: |
1691 |
return "RIGHT" |
1692 |
elif value == 2: |
1693 |
return "DOWN" |
1694 |
elif value == 3: |
1695 |
return "LEFT" |
1696 |
else:
|
1697 |
return "NONE" |
1698 |
|
1699 |
'''
|
1700 |
@brief draw found symbols and texts
|
1701 |
@author Jeongwoo
|
1702 |
'''
|
1703 |
|
1704 |
@staticmethod
|
1705 |
def drawFoundSymbolsOnCanvas(drawingPath, textInfos, listWidget): |
1706 |
global src
|
1707 |
global ocrCompletedSrc
|
1708 |
global canvas
|
1709 |
|
1710 |
appDocData = AppDocData.instance() |
1711 |
canvas = np.zeros(appDocData.imgSrc.shape, np.uint8) |
1712 |
canvas[::] = 255
|
1713 |
|
1714 |
try:
|
1715 |
appDocData = AppDocData.instance() |
1716 |
project = appDocData.getCurrentProject() |
1717 |
|
1718 |
for symbol in searchedSymbolList: |
1719 |
Worker.drawFoundSymbols(symbol, listWidget) |
1720 |
|
1721 |
for text in textInfos: |
1722 |
left = text.getX() |
1723 |
top = text.getY() |
1724 |
right = text.getX() + text.getW() |
1725 |
bottom = text.getY() + text.getH() |
1726 |
|
1727 |
canvas[top:bottom, left:right] = appDocData.imgSrc[top:bottom, left:right] |
1728 |
|
1729 |
cv2.imwrite(os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(drawingPath)), canvas)
|
1730 |
except Exception as ex: |
1731 |
from App import App |
1732 |
|
1733 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1734 |
sys.exc_info()[-1].tb_lineno)
|
1735 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
1736 |
|
1737 |
'''
|
1738 |
@history 2018.04.27 Jeongwoo Remove Tesseract Log on listWidget
|
1739 |
2018.05.04 Jeongwoo Change method to OCR with tesseract_ocr_module.py
|
1740 |
2018.05.09 Jeongwoo Add global variable textInfoList, Remove text in symbol and Add tesseract result text
|
1741 |
2018.05.10 Jeongwoo Remove not used if-statement
|
1742 |
2018.06.19 Jeongwoo When detect text in symbol, use getTextAreaInfo() and Tesseract
|
1743 |
2018.06.21 Jeongwoo Add if-statement for way to detect text by Type A
|
1744 |
'''
|
1745 |
|
1746 |
@staticmethod
|
1747 |
def drawFoundSymbols(symbol, listWidget): |
1748 |
global src
|
1749 |
global canvas
|
1750 |
global WHITE_LIST_CHARS
|
1751 |
global searchedSymbolList
|
1752 |
global textInfoList
|
1753 |
|
1754 |
# symbolId = symbol.getId()
|
1755 |
symbolPath = symbol.getPath() |
1756 |
symbolSp = symbol.getSp() |
1757 |
symbolRotatedAngle = symbol.getRotatedAngle() |
1758 |
|
1759 |
symImg = cv2.cvtColor(cv2.imread(symbolPath, 1), cv2.COLOR_BGR2GRAY)
|
1760 |
if symbol.getDetectFlip() is 1: |
1761 |
symImg = cv2.flip(symImg, 1)
|
1762 |
for i in range(symbolRotatedAngle // 90): |
1763 |
symImg = cv2.rotate(symImg, cv2.ROTATE_90_COUNTERCLOCKWISE) |
1764 |
|
1765 |
w, h = symImg.shape[::-1]
|
1766 |
canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w] = cv2.bitwise_and( |
1767 |
canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w], symImg) |
1768 |
|
1769 |
@staticmethod
|
1770 |
def remove_detected_symbol_image(sym, imgSrc): |
1771 |
"""remove detected symbol image from drawing image"""
|
1772 |
global threadLock
|
1773 |
|
1774 |
path = sym.getPath() |
1775 |
sp = sym.getSp() |
1776 |
sw = sym.getWidth() |
1777 |
sh = sym.getHeight() |
1778 |
angle = sym.getRotatedAngle() |
1779 |
# get symbol image
|
1780 |
sym_img = cv2.imread(path) |
1781 |
sym_img = cv2.cvtColor(sym_img, cv2.COLOR_BGR2GRAY) |
1782 |
# symImg = cv2.threshold(Worker.cvtGrayImage(symImg), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
|
1783 |
if sym.getDetectFlip() is 1: |
1784 |
sym_img = cv2.flip(sym_img, 1)
|
1785 |
|
1786 |
for i in range(angle // 90): |
1787 |
sym_img = cv2.rotate(sym_img, cv2.ROTATE_90_COUNTERCLOCKWISE) |
1788 |
# up to here
|
1789 |
|
1790 |
threadLock.acquire() |
1791 |
temp = imgSrc[sp[1]:sp[1] + sh, sp[0]:sp[0] + sw] |
1792 |
sym_img = cv2.erode(sym_img, np.ones((5, 5), np.uint8)) |
1793 |
mask = cv2.bitwise_or(temp, sym_img) |
1794 |
imgXOR = cv2.bitwise_xor(temp, mask) |
1795 |
imgSrc[sp[1]:sp[1] + sh, sp[0]:sp[0] + sw] = cv2.bitwise_not(imgXOR) |
1796 |
threadLock.release() |
1797 |
|
1798 |
'''
|
1799 |
@brief get difference between given original and recognized image
|
1800 |
@author humkyung
|
1801 |
@date 2018.06.11
|
1802 |
'''
|
1803 |
|
1804 |
@staticmethod
|
1805 |
def getDifference(orgImagePath, recImagePath): |
1806 |
import re |
1807 |
|
1808 |
global ocrCompletedSrc
|
1809 |
global textInfoList
|
1810 |
|
1811 |
try:
|
1812 |
appDocData = AppDocData.instance() |
1813 |
if os.path.isfile(orgImagePath) and os.path.isfile(recImagePath): |
1814 |
imgOriginal = \ |
1815 |
cv2.threshold(Worker.cvtGrayImage(cv2.imread(orgImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1] |
1816 |
|
1817 |
configs = appDocData.getConfigs('Filter', 'DilateSize') |
1818 |
if 1 == len(configs) and int(configs[0].value) is not 0: |
1819 |
size = int(configs[0].value) |
1820 |
kernel = np.ones((size, size), np.uint8) |
1821 |
imgOriginal = cv2.erode(imgOriginal, kernel, iterations=1)
|
1822 |
|
1823 |
# remove not drawing area
|
1824 |
configs = appDocData.getConfigs('{} Equipment Desc Area'.format(appDocData.imgName))
|
1825 |
for config in configs: |
1826 |
found = re.findall('\\d+', config.value)
|
1827 |
if len(found) == 4: |
1828 |
cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])), |
1829 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1) |
1830 |
|
1831 |
configs = appDocData.getConfigs('{} Typical Area'.format(appDocData.imgName))
|
1832 |
for config in configs: |
1833 |
found = re.findall('\\d+', config.value)
|
1834 |
if len(found) == 4: |
1835 |
cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])), |
1836 |
(int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1) |
1837 |
|
1838 |
noteArea = appDocData.getArea('Note')
|
1839 |
if noteArea is not None: |
1840 |
noteArea.img = appDocData.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height), |
1841 |
round(noteArea.x):round(noteArea.x + noteArea.width)].copy() |
1842 |
cv2.rectangle(imgOriginal, (round(noteArea.x), round(noteArea.y)), |
1843 |
(round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1) |
1844 |
# up to here
|
1845 |
|
1846 |
imgRecognized = \ |
1847 |
cv2.threshold(Worker.cvtGrayImage(cv2.imread(recImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1] |
1848 |
|
1849 |
imgDiff = np.ones(imgOriginal.shape, np.uint8) * 255
|
1850 |
|
1851 |
area = AppDocData.instance().getArea('Drawing')
|
1852 |
if area is not None: |
1853 |
x = round(area.x)
|
1854 |
y = round(area.y)
|
1855 |
width = round(area.width)
|
1856 |
height = round(area.height)
|
1857 |
imgNotOper = cv2.bitwise_not(imgRecognized[y:y + height, x:x + width]) |
1858 |
imgDiff[y:y + height, x:x + width] = cv2.bitwise_xor(imgOriginal[y:y + height, x:x + width], |
1859 |
imgNotOper) |
1860 |
|
1861 |
# remove noise
|
1862 |
imgDiff = cv2.dilate(imgDiff, np.ones((2, 2), np.uint8)) |
1863 |
|
1864 |
appDocData = AppDocData.instance() |
1865 |
project = appDocData.getCurrentProject() |
1866 |
cv2.imwrite(os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(orgImagePath)), imgDiff)
|
1867 |
except Exception as ex: |
1868 |
from App import App |
1869 |
|
1870 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
1871 |
sys.exc_info()[-1].tb_lineno)
|
1872 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
1873 |
|
1874 |
|
1875 |
'''
|
1876 |
@history 2018.05.25 Jeongwoo Add pyqtSignal(svgItemClicked, itemRemoved)
|
1877 |
'''
|
1878 |
|
1879 |
|
1880 |
class QRecognitionDialog(QDialog): |
1881 |
svgItemClicked = pyqtSignal(SymbolSvgItem) |
1882 |
itemRemoved = pyqtSignal(QGraphicsItem) |
1883 |
unBlockEvent = pyqtSignal() |
1884 |
|
1885 |
'''
|
1886 |
@history 2018.05.25 Jeongwoo Add parameter and initialize / Connect recognizeButton signal and slot
|
1887 |
2018.05.29 Jeongwoo Chnage parameter(graphicsView → parent) and Get graphicsView from parent
|
1888 |
'''
|
1889 |
|
1890 |
def __init__(self, parent, path, batch): # Parent is MainWindow |
1891 |
from AppDocData import AppDocData |
1892 |
|
1893 |
QDialog.__init__(self, parent)
|
1894 |
|
1895 |
self.parent = parent
|
1896 |
self.graphicsView = parent.graphicsView
|
1897 |
self.path = path
|
1898 |
self.xmlPath = None |
1899 |
self.ui = Recognition_UI.Ui_Recognition()
|
1900 |
self.ui.setupUi(self) |
1901 |
|
1902 |
self.ui.buttonBox.setEnabled(True) |
1903 |
self.ui.listWidget.model().rowsInserted.connect(self.rowInserted) |
1904 |
self.ui.recognizeButton.clicked.connect(self.recognizeButtonClicked) |
1905 |
self.ui.lineCheckBox.stateChanged.connect(self.checkBoxChanged) |
1906 |
self.ui.checkBoxSymbol.stateChanged.connect(self.checkBoxChanged) |
1907 |
self.ui.checkBoxText.stateChanged.connect(self.checkBoxChanged) |
1908 |
self.ui.checkBoxTraining.stateChanged.connect(self.checkBoxChanged) |
1909 |
self.isAccepted = False |
1910 |
self.batch = batch
|
1911 |
|
1912 |
appDocData = AppDocData.instance() |
1913 |
configs = appDocData.getAppConfigs('app', 'mode') |
1914 |
if configs and 1 == len(configs) and 'advanced' == configs[0].value: |
1915 |
pass
|
1916 |
else:
|
1917 |
self.ui.checkBoxTraining.setVisible(False) |
1918 |
|
1919 |
'''
|
1920 |
if self.batch:
|
1921 |
self.ui.lineCheckBox.setCheckState(Qt.Checked)
|
1922 |
self.ui.lineCheckBox.setEnabled(False)
|
1923 |
self.ui.checkBoxSymbol.setEnabled(False)
|
1924 |
self.ui.checkBoxText.setEnabled(False)
|
1925 |
else:
|
1926 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
1927 |
self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
|
1928 |
self.ui.checkBoxText.setCheckState(Qt.Unchecked)
|
1929 |
'''
|
1930 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
1931 |
self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
|
1932 |
self.ui.checkBoxText.setCheckState(Qt.Unchecked)
|
1933 |
|
1934 |
self.ui.progressBarBatch.setFormat('{}/{}'.format('0', str(len(self.path)))) |
1935 |
|
1936 |
def checkBoxChanged(self, checkState): |
1937 |
'''
|
1938 |
@brief line and text cannot be reocognized alone
|
1939 |
@author euisung
|
1940 |
@date 2019.05.14
|
1941 |
'''
|
1942 |
if self.ui.checkBoxTraining.isChecked(): |
1943 |
self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
|
1944 |
self.ui.checkBoxText.setCheckState(Qt.Unchecked)
|
1945 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
1946 |
self.ui.lineCheckBox.setEnabled(False) |
1947 |
self.ui.checkBoxText.setEnabled(False) |
1948 |
self.ui.checkBoxSymbol.setEnabled(False) |
1949 |
self.ui.recognizeButton.setText('Extract') |
1950 |
return
|
1951 |
else:
|
1952 |
self.ui.lineCheckBox.setEnabled(True) |
1953 |
self.ui.checkBoxText.setEnabled(True) |
1954 |
self.ui.checkBoxSymbol.setEnabled(True) |
1955 |
self.ui.recognizeButton.setText('Recognize') |
1956 |
|
1957 |
if checkState is int(Qt.Checked): |
1958 |
if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked(): |
1959 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
1960 |
elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked(): |
1961 |
self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
|
1962 |
elif checkState is int(Qt.Unchecked): |
1963 |
if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked(): |
1964 |
self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
|
1965 |
elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked(): |
1966 |
self.ui.checkBoxText.setCheckState(Qt.Unchecked)
|
1967 |
|
1968 |
'''
|
1969 |
@brief QListWidget Row Inserted Listener
|
1970 |
Whenever row inserted, scroll to bottom
|
1971 |
@author Jeongwoo
|
1972 |
@date 18.04.12
|
1973 |
'''
|
1974 |
|
1975 |
def rowInserted(self, item): |
1976 |
self.ui.listWidget.scrollToBottom()
|
1977 |
|
1978 |
def accept(self): |
1979 |
self.isAccepted = True |
1980 |
QDialog.accept(self)
|
1981 |
|
1982 |
def recognizeButtonClicked(self, event): |
1983 |
"""
|
1984 |
@brief start recognization
|
1985 |
@author humkyung
|
1986 |
@history humkyung 2018.10.05 clear imgSrc before recognizing
|
1987 |
"""
|
1988 |
if self.ui.checkBoxSymbol.isChecked(): # or self.ui.checkBoxText.isChecked() or self.ui.lineCheckBox.isChecked(): |
1989 |
appDocData = AppDocData.instance() |
1990 |
appDocData.imgSrc = None
|
1991 |
area = appDocData.getArea('Drawing')
|
1992 |
if area is None: |
1993 |
QMessageBox.about(self, self.tr("Notice"), self.tr('Please select drawing area.')) |
1994 |
return
|
1995 |
|
1996 |
self.ui.recognizeButton.setEnabled(False) |
1997 |
self.ui.buttonBox.setEnabled(False) |
1998 |
self.ui.progressBar.setValue(0) |
1999 |
self.ui.listWidget.addItem("Initializing...") |
2000 |
self.startThread()
|
2001 |
|
2002 |
'''
|
2003 |
@brief add item to list widget
|
2004 |
@author humkyung
|
2005 |
@date 2018.06.19
|
2006 |
'''
|
2007 |
|
2008 |
def addListItem(self, item): |
2009 |
self.ui.listWidget.addItem(item)
|
2010 |
|
2011 |
'''
|
2012 |
@brief update progressbar with given value
|
2013 |
@author humkyung
|
2014 |
@date 2018.06.08
|
2015 |
'''
|
2016 |
|
2017 |
def updateProgress(self, maxValue, image_path): |
2018 |
self.ui.progressBar.setMaximum(maxValue)
|
2019 |
self.ui.progressBar.setValue(self.ui.progressBar.value() + 1) |
2020 |
|
2021 |
if image_path is not None and os.path.isfile(image_path): |
2022 |
self.ui.labelImage.setPixmap(QPixmap(image_path))
|
2023 |
elif image_path is not None: |
2024 |
self.ui.labelImage.setText(image_path)
|
2025 |
|
2026 |
def updateBatchProgress(self, maxValue, weight): |
2027 |
'''
|
2028 |
@brief update batch progressbar
|
2029 |
@author euisung
|
2030 |
@date 2018.11.28
|
2031 |
'''
|
2032 |
self.ui.progressBarBatch.setMaximum(maxValue * 5) |
2033 |
value = self.ui.progressBarBatch.value() + weight
|
2034 |
self.ui.progressBarBatch.setValue(value)
|
2035 |
self.ui.progressBarBatch.setFormat('{}/{}'.format(str(int(value / 5)), str(maxValue))) |
2036 |
|
2037 |
'''
|
2038 |
@brief display title
|
2039 |
@author humkyung
|
2040 |
@date 2018.08.20
|
2041 |
'''
|
2042 |
|
2043 |
def displayTitle(self, title): |
2044 |
self.ui.labelTitle.setText(title)
|
2045 |
|
2046 |
def startThread(self): |
2047 |
"""
|
2048 |
@brief start thread
|
2049 |
@author humkyung
|
2050 |
@date 2018.04.??
|
2051 |
@history 2018.05.25 Jeongwoo Moved from MainWindow
|
2052 |
2018.05.28 Jeongwoo Add connects (self.loadRecognitionResult, recognizeLine)
|
2053 |
2018.05.30 Jeongwoo Change signal name (drawDetectedItems)
|
2054 |
humkyung 2018.06.08 connect signal to self.updateProgress
|
2055 |
"""
|
2056 |
import timeit |
2057 |
from PyQt5 import QtWidgets |
2058 |
from App import App |
2059 |
|
2060 |
self.ui.buttonBox.setDisabled(True) |
2061 |
|
2062 |
# 1 - create Worker and Thread inside the Form
|
2063 |
self.obj = Worker() # no parent! |
2064 |
self.obj.path = self.path |
2065 |
self.obj.listWidget = self.ui.listWidget |
2066 |
self.obj.graphicsView = self.graphicsView |
2067 |
self.obj.isSymbolChecked = self.ui.checkBoxSymbol.isChecked() |
2068 |
self.obj.isTextChecked = self.ui.checkBoxText.isChecked() |
2069 |
self.obj.isLineChecked = self.ui.lineCheckBox.isChecked() |
2070 |
self.obj.isTrainingChecked = self.ui.checkBoxTraining.isChecked() |
2071 |
self.obj.batch = self.batch |
2072 |
self.obj.createDetectedItems = self.parent.createDetectedItems |
2073 |
self.obj.createUnknownItems = self.parent.createUnknownItems |
2074 |
self.thread = QThread() # no parent! |
2075 |
|
2076 |
# 2 - Move the Worker object to the Thread object
|
2077 |
self.obj.moveToThread(self.thread) |
2078 |
|
2079 |
# 3 - Connect Worker Signals to the Thread slots
|
2080 |
self.obj.finished.connect(self.thread.quit) |
2081 |
# self.obj.drawDetectedItems.connect(self.drawDetectedItems)
|
2082 |
# self.obj.drawDetectedLines.connect(self.drawDetectedLines)
|
2083 |
# self.obj.drawUnknownItems.connect(self.drawUnknownItems)
|
2084 |
self.obj.displayMessage.connect(self.addListItem) |
2085 |
self.obj.updateProgress.connect(self.updateProgress) |
2086 |
self.obj.updateBatchProgress.connect(self.updateBatchProgress) |
2087 |
self.obj.displayLog.connect(App.mainWnd().addMessage)
|
2088 |
self.obj.displayTitle.connect(self.displayTitle) |
2089 |
|
2090 |
# 4 - Connect Thread started signal to Worker operational slot method
|
2091 |
self.thread.started.connect(self.obj.procCounter) |
2092 |
|
2093 |
# 5 - Thread finished signal will close the app if you want!
|
2094 |
self.thread.finished.connect(self.dlgExit) |
2095 |
|
2096 |
# 6 - Start the thread
|
2097 |
self.thread.start()
|
2098 |
|
2099 |
self.tmStart = timeit.default_timer()
|
2100 |
|
2101 |
'''
|
2102 |
@brief set buttonbox's enabled flag
|
2103 |
@history 2018.05.25 Jeongwoo Moved from MainWindow
|
2104 |
2018.06.14 Jeongwoo Change sentence order
|
2105 |
2018.11.26 euisung add drawing part
|
2106 |
2018.11.26 euising move save and unknown part into executerecognition
|
2107 |
'''
|
2108 |
|
2109 |
def dlgExit(self): |
2110 |
import timeit |
2111 |
import XmlGenerator as xg |
2112 |
|
2113 |
try:
|
2114 |
self.ui.buttonBox.setEnabled(True) |
2115 |
|
2116 |
self.ui.progressBar.setValue(self.ui.progressBar.maximum()) |
2117 |
# if not self.batch:
|
2118 |
# self.parent.drawDetectedItemsToScene()
|
2119 |
except Exception as ex: |
2120 |
from App import App |
2121 |
|
2122 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
2123 |
sys.exc_info()[-1].tb_lineno)
|
2124 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
2125 |
finally:
|
2126 |
self.tmStop = timeit.default_timer()
|
2127 |
seconds = self.tmStop - self.tmStart |
2128 |
self.ui.listWidget.addItem("\nRunning Time : {} min".format(str(round(seconds / 60, 1))) + "\n") |
2129 |
|
2130 |
'''
|
2131 |
@history 2018.05.29 Jeongwoo Call parent's method
|
2132 |
2018.05.30 Jeongwoo Change method name
|
2133 |
2018.06.09 humkyung set progressbar value to maximum
|
2134 |
2018.11.12 euisung add title block properties
|
2135 |
2018.11.29 euisung no more used
|
2136 |
'''
|
2137 |
|
2138 |
def drawDetectedItems(self, symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList, loop): |
2139 |
try:
|
2140 |
self.ui.progressBar.setValue(self.ui.progressBar.maximum()) |
2141 |
self.parent.drawDetectedItems(symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList)
|
2142 |
finally:
|
2143 |
loop.quit() |
2144 |
|
2145 |
'''
|
2146 |
@brief draw detected lines
|
2147 |
@author humkyung
|
2148 |
@date 2018.08.23
|
2149 |
@history 2018.11.27 euisung no more used
|
2150 |
'''
|
2151 |
|
2152 |
def drawDetectedLines(self, lineList, loop): |
2153 |
try:
|
2154 |
self.parent.drawDetectedLines(lineList, self.obj) |
2155 |
finally:
|
2156 |
loop.quit() |
2157 |
|
2158 |
'''
|
2159 |
@brief draw detected lines
|
2160 |
@author euisung
|
2161 |
@date 2018.11.27
|
2162 |
@history 2018.11.27 euisung no more used
|
2163 |
'''
|
2164 |
|
2165 |
def drawUnknownItems(self, path, loop): |
2166 |
try:
|
2167 |
self.parent.drawUnknownItems(path)
|
2168 |
finally:
|
2169 |
loop.quit() |