프로젝트

일반

사용자정보

통계
| 개정판:

hytos / DTI_PID / DTI_PID / RecognitionDialog.py @ a6f10552

이력 | 보기 | 이력해설 | 다운로드 (94.6 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 removeSmallObjects(image):
120
        try:
121
            appDocData = AppDocData.instance()
122
            configs = appDocData.getConfigs('Small Object Size', 'Min Area')
123
            minArea = int(configs[0].value) if 1 == len(configs) else 20
124
            configs = appDocData.getConfigs('Small Object Size', 'Max Area')
125
            maxArea = int(configs[0].value) if 1 == len(configs) else 50
126

    
127
            contours, _ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
128
            selectedContours = []
129
            for contour in contours:
130
                area = cv2.contourArea(contour)
131
                if area > minArea and area < maxArea: selectedContours.append(contour)
132
            contourImage = cv2.drawContours(image, selectedContours, -1, (255, 255, 255),
133
                                            -1);  # draw contour with white color
134
        except Exception as ex:
135
            from App import App
136

    
137
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
138
                                                          sys.exc_info()[-1].tb_lineno)
139
            App.mainWnd().addMessage.emit(MessageType.Error, message)
140

    
141
        return contourImage
142

    
143
    '''
144
        @brief  arrange line's position
145
        @author humkyung
146
        @date   2018.07.04
147
    '''
148

    
149
    @staticmethod
150
    def arrangeLinePosition(lines, symbols, listWidget):
151
        try:
152
            listWidget.addItem('Apply flow direction')
153
            pool = [line for line in lines if line.flowMark is not None]
154
            visited = []
155
            visited.extend(pool)
156
            while len(pool) > 0:
157
                line = pool.pop()
158
                print('{} - ({})'.format(line, len(pool)))
159
                rhs = [item for item in lines if item not in visited and item.is_connected(line)]
160
                if rhs:
161
                    pool.extend(rhs)
162
                    visited.extend(rhs)
163
                    for item in rhs:
164
                        item.arrangeVertexOrder(line)
165

    
166
                # skip jointed symbols
167
                symbolPool = [item for item in symbols if item not in visited and item.is_connected(line)]
168
                if symbolPool:
169
                    selected = []
170
                    visited.extend(symbolPool)
171
                    while len(symbolPool) > 0:
172
                        symbol = symbolPool.pop()
173

    
174
                        rhs = [item for item in symbols if item not in visited and item.is_connected(symbol)]
175
                        if rhs:
176
                            symbolPool.extend(rhs)
177
                            visited.extend(rhs)
178
                            selected.extend(rhs)
179
                        else:
180
                            selected.append(symbol)
181

    
182
                    # find lines which are connected last symbol
183
                    for symbol in selected:
184
                        rhs = [item for item in lines if item not in visited and item.is_connected(symbol)]
185
                        if rhs:
186
                            pool.extend(rhs)
187
                            visited.extend(rhs)
188
                            for item in rhs:
189
                                item.arrangeVertexOrder(line)
190
                # up to here
191
            # up to here
192
        except Exception as ex:
193
            from App import App
194

    
195
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
196
                                                          sys.exc_info()[-1].tb_lineno)
197
            App.mainWnd().addMessage.emit(MessageType.Error, message)
198

    
199
    @staticmethod
200
    def executeRecognition(createDetectedItems, path, listWidget, isLineChecked, worker, batch, createUnknownItems):
201
        """
202
        @brief      Main function
203
        @author     Jeongwoo
204
        @date
205
        @history    humkyung 2018.04.06 change error display from message box to print
206
                    Jeongwoo 2018.04.25 Remove 'Current Symbol : ' QListItem
207
                    Jeongwoo 2018.05.09 Make Comments OCR.removeTextFromNpArray block
208
                    Jeongwoo 2018.05.25 Remove imgLineList variable and parameter on writeXml()
209
                    humkyung 2018.05.26 add parameters(graphicsView, isSymbolTextChecked, isLineChecked)
210
                    Jeongwoo 2018.05.28 Add/Remove Parameters(Add : signal / Remove : graphicsView, isLineChecked)
211
                    Jeongwoo 2018.05.30 Remove return value
212
                    humkyung 2018.06.08 add signal for progressbar to parameter
213
                    humkyung 2018.06.11 get difference between original and recognized image
214
                    Jeongwoo 2018.06.21 If noteTextInfoList is None, change from None to empty list
215
                    humkyung 2018.08.20 remove alreay existing symbol and text item before recognizing
216
                                        block until drawing reconized objects
217
                    euisung  2018.11.12 add title block properties
218
        """
219
        import re
220
        from TextDetector import TextDetector
221
        from Drawing import Drawing
222

    
223
        global ocrCompletedSrc
224
        global threadLock
225
        global searchedSymbolList
226
        global textInfoList
227
        global maxProgressValue
228

    
229
        try:
230
            app_doc_data = AppDocData.instance()
231
            drawings = app_doc_data.getDrawings()
232
            project = app_doc_data.getCurrentProject()
233
            textDetector = TextDetector()
234

    
235
            # remove already existing symbol and text
236
            if not batch:
237
                items = [item for item in worker.graphicsView.scene.items() if
238
                         type(item) is QEngineeringUnknownItem or type(item) is QEngineeringEndBreakItem or type(
239
                             item) is QEngineeringErrorItem]
240
                if worker.isSymbolChecked:
241
                    items.extend(
242
                        [item for item in worker.graphicsView.scene.items() if issubclass(type(item), SymbolSvgItem)])
243
                if worker.isTextChecked:
244
                    items.extend([item for item in worker.graphicsView.scene.items() if
245
                                  issubclass(type(item), QEngineeringTextItem)])
246
                for item in items:
247
                    item.transfer.onRemoved.emit(item)
248
                    # worker.graphicsView.scene.removeItem(item)
249
                for item in [item for item in worker.graphicsView.scene.items() if
250
                             type(item) is QGraphicsBoundingBoxItem]:
251
                    item.transfer.onRemoved.emit(item)
252

    
253
            # up to here
254

    
255
            srcList = path
256

    
257
            Worker.initTargetSymbolDataList()
258

    
259
            for mainRes in srcList:
260
                ocrCompletedSrc = []
261
                searchedSymbolList = []
262
                textInfoList = []
263

    
264
                if not os.path.isfile(mainRes):
265
                    item = QListWidgetItem('{} file is not found'.format(os.path.basename(mainRes)))
266
                    item.setBackground(Qt.red)
267
                    listWidget.addItem(item)
268
                    continue
269

    
270
                app_doc_data.clearItemList(True)
271

    
272
                app_doc_data.setImgFilePath(mainRes)
273
                app_doc_data.imgSrc = None
274
                matches = [drawing for drawing in drawings if app_doc_data.imgName == os.path.splitext(drawing.name)[0]]
275
                app_doc_data.activeDrawing = matches[0] if matches else Drawing(None, app_doc_data.imgName, None)
276
                app_doc_data.activeDrawing.set_pid_source(Image.open(mainRes))
277

    
278
                # remove not drawing area
279
                configs = app_doc_data.getConfigs('{} Equipment Desc Area'.format(app_doc_data.imgName))
280
                for config in configs:
281
                    found = re.findall('\\d+', config.value)
282
                    if len(found) == 4:
283
                        cv2.rectangle(app_doc_data.imgSrc, (int(found[0]), int(found[1])),
284
                                      (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
285

    
286
                configs = app_doc_data.getConfigs('{} Typical Area'.format(app_doc_data.imgName))
287
                for config in configs:
288
                    found = re.findall('\\d+', config.value)
289
                    if len(found) == 4:
290
                        cv2.rectangle(app_doc_data.imgSrc, (int(found[0]), int(found[1])),
291
                                      (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
292

    
293
                noteArea = app_doc_data.getArea('Note')
294
                if noteArea is not None:
295
                    noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height),
296
                                   round(noteArea.x):round(noteArea.x + noteArea.width)].copy()
297
                    cv2.rectangle(app_doc_data.imgSrc, (round(noteArea.x), round(noteArea.y)),
298
                                  (round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1)
299
                # up to here
300

    
301
                area = app_doc_data.getArea('Drawing')
302
                if area is not None:
303
                    area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height),
304
                               round(area.x):round(area.x + area.width)]
305

    
306
                maxProgressValue = 0
307
                listWidget.addItem("Start recognition : " + mainRes)
308
                threadLock.acquire()
309

    
310
                worker.updateBatchProgress.emit(len(srcList), 1)
311

    
312
                if worker.isSymbolChecked:
313
                    # calculate total count of symbol
314
                    for targetItem in targetSymbolList:
315
                        if type(targetItem) is list:
316
                            maxProgressValue += len(targetItem)
317
                        else:
318
                            maxProgressValue += 1
319
                    # up to here
320
                    maxProgressValue = maxProgressValue * 2
321
                threadLock.release()
322

    
323
                if worker.isSymbolChecked:
324
                    worker.displayTitle.emit(worker.tr('Detecting symbols...'))
325

    
326
                    # detect equipments
327
                    pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER)
328
                    for symbol in targetSymbolList[0]:
329
                        pool.submit(Worker.detectEquipmentOnPid, mainRes, symbol, listWidget, worker)
330
                    pool.shutdown(wait=True)
331
                    # up to here
332

    
333
                    pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER)
334
                    for symbol in targetSymbolList[2]:
335
                        if type(symbol) is list:
336
                            pool.submit(Worker.detectSymbolsOnPid, mainRes, symbol, listWidget, worker)
337
                        else:
338
                            pool.submit(Worker.detectSymbolOnPid, mainRes, symbol, listWidget, worker)
339
                    pool.shutdown(wait=True)
340

    
341
                    if worker.isTrainingChecked:
342
                        import uuid
343
                        worker.displayTitle.emit(worker.tr('Generating Data...'))
344
                        listWidget.addItem('Generating Data...')
345

    
346
                        for item in searchedSymbolList:
347
                            path = os.path.join(project.getTempPath(), 'Tile', item.getBaseSymbol())
348
                            if not os.path.exists(path): os.makedirs(path)
349

    
350
                            _img = app_doc_data.imgSrc[round(item.getSp()[1]):round(item.getSp()[1] + item.getHeight()),
351
                                   round(item.getSp()[0]):round(item.getSp()[0] + item.getWidth())]
352
                            cv2.imwrite(os.path.join(path, str(uuid.uuid4()) + '.png'), _img)
353
                        worker.updateBatchProgress.emit(len(srcList), 4)
354
                        continue
355

    
356
                    pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER)
357

    
358
                    searchedOPCList = []
359
                    for sym in searchedSymbolList:
360
                        if sym.getType() == 'Piping OPC\'s':
361
                            searchedOPCList.append(sym)
362
                            continue
363
                        pool.submit(Worker.removeDetectedSymbol, sym, app_doc_data.imgSrc)
364
                    pool.shutdown(wait=True)
365
                    # print(searchedSymbolList[0].getOcrOption())
366
                else:
367
                    # if symbol is excluded from recognition, keep it
368
                    '''
369
                    import math
370

371
                    symbolItems = [item for item in worker.graphicsView.scene.items() if issubclass(type(item), SymbolSvgItem)]
372
                    
373
                    for symbolItem in symbolItems:
374
                        symbolName = symbolItem.name
375
                        symbolType = symbolItem.type
376
                        searchedItemSp = [int(symbolItem.loc[0]), int(symbolItem.loc[1])]
377
                        sw = symbolItem.size[0] # check later
378
                        sh = symbolItem.size[1] # check later
379

380
                        symbolThreshold = 50 # not necessary
381
                        symbolMinMatchCount = 0 # not necessary
382
                        hitRate = 80 # not necessary
383
                        
384
                        allowed_error = 0.0001
385
                        #degree 0
386
                        if abs(symbolItem.angle - 0) <= allowed_error:
387
                            symbolRotatedAngle = 0
388
                        #degree 90
389
                        elif abs(symbolItem.angle - 1.57) <= allowed_error:
390
                            symbolRotatedAngle = 90
391
                        #degree 180
392
                        elif abs(symbolItem.angle - 3.14) <= allowed_error:
393
                            symbolRotatedAngle = 180
394
                        #degree 270
395
                        elif abs(symbolItem.angle - 4.71) <= allowed_error:
396
                            symbolRotatedAngle = 270
397
                        else:
398
                            symbolRotatedAngle = math.pi / 180 * symbolItem.angle
399

400
                        isDetectOnOrigin = 80 # not necessary
401
                        symbolRotateCount = 0 # not necessary
402
                        symbolOcrOption = 0 # not necessary
403
                        isContainChild = 0 # not necessary
404
                        originalPoint = [symbolItem.origin[0] - symbolItem.loc[0], symbolItem.origin[1] - symbolItem.loc[1]]
405
                        symbolConnectionPoint = []
406
                        baseSymbol = str(symbolItem.parentSymbol)
407
                        additionalSymbol = str(symbolItem.childSymbol)
408
                        isExceptDetect = 0 # not necessary
409
                        detectFlip = symbolItem.flip
410

411
                        searchedSymbolList.append(Symbol(symbolName, symbolType , 
412
                            searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, hitRate, symbolRotatedAngle , 
413
                            isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild , 
414
                            ','.join(str(x) for x in originalPoint), 
415
                            [], 
416
                            baseSymbol, additionalSymbol, isExceptDetect, detectFlip))
417
                        
418
                        worker.graphicsView.scene.removeItem(symbolItem)
419
                    
420
                    pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER)
421
                    '''
422
                    # symbolItems = [item for item in worker.graphicsView.scene.items() if issubclass(type(item), SymbolSvgItem)]
423
                    # appDocData.symbols.extend(symbolItems)
424
                    # appDocData.allItems.extend(symbolItems)
425

    
426
                    # for sym in searchedSymbolList:
427
                    #    pool.submit(Worker.removeDetectedSymbol, sym, appDocData.imgSrc)
428
                    # pool.shutdown(wait = True)
429

    
430
                worker.updateBatchProgress.emit(len(srcList), 1)
431

    
432
                area = app_doc_data.getArea('Drawing')
433
                if area is not None:
434
                    area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height),
435
                               round(area.x):round(area.x + area.width)]
436

    
437
                offset = (area.x, area.y) if area is not None else (0, 0)
438

    
439
                otherTextInfoList = None
440
                titleBlockTextInfoList = None
441
                if worker.isTextChecked:
442
                    textAreas = textDetector.detectTextAreas(area.img if area is not None else app_doc_data.imgSrc,
443
                                                             offset)
444
                    if maxProgressValue < 2 * len(textAreas):
445
                        for _ in range(len(textAreas) - int(maxProgressValue * 0.5)):
446
                            worker.updateProgress.emit(2 * len(textAreas), None)
447
                        maxProgressValue = 2 * len(textAreas)
448
                    else:
449
                        maxProgressValue = int(maxProgressValue * 0.5) + len(textAreas)
450

    
451
                    worker.displayTitle.emit(worker.tr('Detecting texts...'))
452
                    textDetector.recognizeText(app_doc_data.imgSrc, offset, textAreas, searchedSymbolList, worker,
453
                                               listWidget, maxProgressValue)
454
                    textInfoList = textDetector.textInfoList.copy() if textDetector.textInfoList is not None else None
455
                    otherTextInfoList = textDetector.otherTextInfoList.copy() if textDetector.otherTextInfoList is not None else None
456
                    titleBlockTextInfoList = textDetector.titleBlockTextInfoList.copy() if textDetector.titleBlockTextInfoList is not None else None
457

    
458
                    app_doc_data.activeDrawing.width, app_doc_data.activeDrawing.height = app_doc_data.imgSrc.shape[::-1]
459
                    Worker.drawFoundSymbolsOnCanvas(mainRes, textInfoList, listWidget)
460

    
461
                    # remove text from image
462
                    textDetector.removeTextFromImage(app_doc_data.imgSrc, offset)
463
                    # up to here
464

    
465
                    app_doc_data.imgName = os.path.splitext(os.path.basename(mainRes))[0]
466
                else:
467
                    textItems = [item for item in worker.graphicsView.scene.items() if
468
                                 issubclass(type(item), QEngineeringTextItem)]
469
                    app_doc_data.texts.extend(textItems)
470
                    app_doc_data.allItems.extend(textItems)
471

    
472
                    lineNoTextItems = [item for item in textItems if type(item) is QEngineeringLineNoTextItem]
473
                    for lineNoTextItem in lineNoTextItems:
474
                        lineNoTextItem.set_property('Freeze', False)
475
                        # lineNoTextItem.freeze_item.update_freeze(lineNoTextItem.prop('Freeze'))
476
                        lineNoTextItem.explode()
477

    
478
                    for textItem in textItems:
479
                        textItem.owner = None
480
                        worker.graphicsView.scene.removeItem(textItem)
481

    
482
                worker.updateBatchProgress.emit(len(srcList), 2)
483

    
484
                # check opc validate
485
                valid_opc = []
486
                for index in reversed(range(len(searchedOPCList))):
487
                    opc_xmin = searchedOPCList[index].getSp()[0]
488
                    opc_ymin = searchedOPCList[index].getSp()[1]
489
                    opc_xmax = searchedOPCList[index].getSp()[0] + searchedOPCList[index].getWidth()
490
                    opc_ymax = searchedOPCList[index].getSp()[1] + searchedOPCList[index].getHeight()
491
                    valid = False
492
                    for text_info in textInfoList:
493
                        info_center = text_info.center
494
                        if info_center[0] > opc_xmin and info_center[0] < opc_xmax and info_center[1] > opc_ymin and info_center[1] < opc_ymax:
495
                            valid_opc.append(searchedOPCList[index])
496
                            valid = True
497
                            break
498
                    if not valid:
499
                        searchedSymbolList.pop(searchedSymbolList.index(searchedOPCList[index]))
500

    
501
                pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER)
502

    
503
                for sym in valid_opc:
504
                    pool.submit(Worker.removeDetectedSymbol, sym, app_doc_data.imgSrc)
505
                pool.shutdown(wait=True)
506
                # up to here
507

    
508
                removedSymbolImgPath = os.path.join(project.getTempPath(), os.path.basename(mainRes))
509
                cv2.imwrite(removedSymbolImgPath, app_doc_data.imgSrc)
510

    
511
                area = AppDocData.instance().getArea('Drawing')
512
                if area is not None:
513
                    area.img = app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height),
514
                               round(area.x + 1):round(area.x + area.width)]
515
                cv2.imwrite(os.path.join(project.getTempPath(), "RECT_" + os.path.basename(mainRes)), app_doc_data.imgSrc)
516

    
517
                listWidget.addItem("Recognized symbol count : " + str(len(searchedSymbolList)))
518

    
519
                # get difference between original and recognized image
520
                foundFilePath = os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(mainRes))
521
                Worker.getDifference(mainRes, foundFilePath)
522
                # up to here
523

    
524
                listWidget.addItem(worker.tr('Creating detected infos...'))
525
                worker.displayTitle.emit(worker.tr('Creating detected infos...'))
526
                createDetectedItems(searchedSymbolList, textInfoList,
527
                                    otherTextInfoList if otherTextInfoList is not None else [],
528
                                    titleBlockTextInfoList if titleBlockTextInfoList is not None else [])
529

    
530
                if isLineChecked:
531
                    Worker.recognizeLine(mainRes, listWidget, worker.graphicsView, worker, batch)
532
                else:
533
                    lineItems = [item for item in worker.graphicsView.scene.items() if
534
                                 issubclass(type(item), QEngineeringLineItem)]
535
                    app_doc_data.lines.extend(lineItems)
536
                    app_doc_data.allItems.extend(lineItems)
537

    
538
                    for lineItem in lineItems:
539
                        lineItem.owner = None
540
                        for conn in lineItem.connectors:
541
                            conn.connectedItem = None
542
                        worker.graphicsView.scene.removeItem(lineItem)
543

    
544
                # connect symbol to symbol
545
                try:
546
                    configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line')
547
                    toler = int(configs[0].value) if configs else 20
548
                    for symbol in app_doc_data.symbols:
549
                        matches = [it for it in app_doc_data.symbols if it is not symbol and symbol.is_connectable(it, toler=toler)]
550
                        # print(str(symbol))
551
                        # print(matches)
552
                        for match in matches:
553
                            symbol.connect_if_possible(match)
554
                except Exception as ex:
555
                    message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
556
                                                                  sys.exc_info()[-1].tb_lineno)
557
                    worker.displayLog.emit(MessageType.Error, message)
558
                # up to here
559

    
560
                listWidget.addItem('Connecting lines')
561
                area = app_doc_data.getArea('Drawing')
562
                detector = LineDetector(area.img)
563
                symbols = app_doc_data.symbols
564
                configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line')
565
                toler = int(configs[0].value) if configs else 20
566
                if app_doc_data.lines:
567
                    # connect line to symbol
568
                    try:
569
                        for line in app_doc_data.lines:
570
                            matches = [symbol for symbol in symbols if symbol.is_connectable(line, toler=toler)]
571
                            for symbol in matches:
572
                                detector.connectLineToSymbol(line, symbol, toler=toler)
573
                    except Exception as ex:
574
                        message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[
575
                            -1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
576
                        worker.displayLog.emit(MessageType.Error, message)
577
                    # up to here
578

    
579
                    # connect line to line
580
                    try:
581
                        for line in app_doc_data.lines:
582
                            matches = [it for it in app_doc_data.lines if
583
                                       (it is not line) and (not line.isParallel(it))]
584

    
585
                            for match in matches:
586
                                detector.connectLineToLine(match, line, toler)
587

    
588
                        # change line type using symbol connection type(info)
589
                        for sym in symbols:
590
                            if sym.conn_type:
591
                                for index in range(len(sym.conn_type)):
592
                                    item = sym.connectors[index].connectedItem
593
                                    if item and type(item) is QEngineeringLineItem:
594
                                        Worker.changeConnectedLineType(item, sym.conn_type[index])
595
                    except Exception as ex:
596
                        message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[
597
                            -1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
598
                        worker.displayLog.emit(MessageType.Error, message)
599
                    # up to here
600

    
601
                createUnknownItems(mainRes)
602

    
603
                worker.updateBatchProgress.emit(len(srcList), 1)
604
        except Exception as ex:
605
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
606
                                                          sys.exc_info()[-1].tb_lineno)
607
            worker.displayLog.emit(MessageType.Error, message)
608
        finally:
609
            pass
610

    
611
    @staticmethod
612
    def changeConnectedLineType(line, lineType):
613
        line.lineType = lineType
614
        if type(line.connectors[0].connectedItem) is QEngineeringLineItem and \
615
                (line.connectors[0].connectedItem.connectors[0].connectedItem is line or line.connectors[0].connectedItem.connectors[1].connectedItem is line) and \
616
                line.connectors[0].connectedItem.lineType is not lineType:
617
            Worker.changeConnectedLineType(line.connectors[0].connectedItem, lineType)
618
        if type(line.connectors[1].connectedItem) is QEngineeringLineItem and \
619
                (line.connectors[1].connectedItem.connectors[0].connectedItem is line or line.connectors[1].connectedItem.connectors[1].connectedItem is line) and \
620
                line.connectors[1].connectedItem.lineType is not lineType:
621
            Worker.changeConnectedLineType(line.connectors[1].connectedItem, lineType)
622

    
623
    '''
624
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
625
                    2018.05.28  Jeongwoo    Add xmlPath Parameter and append LineInfo into xml
626
                    2018.05.29  Jeongwoo    Change method to add item
627
                    2018.05.30  Jeongwoo    Remove parameter (xmlPath)
628
                    humkyung 2018.06.11 add drawing path to parameter and write recognized lines to image
629
                    humkyung 2018.07.04 call arrangeLinePosition after creating line
630
    '''
631

    
632
    @staticmethod
633
    def recognizeLine(path, listWidget, graphicsView, worker, batch):
634
        from shapely.geometry import Point, LineString
635
        from SymbolSvgItem import SymbolSvgItem
636
        from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
637
        from EngineeringLineNoTextItem import QEngineeringLineNoTextItem
638
        from EngineeringTextItem import QEngineeringTextItem
639
        from EngineeringLineItem import QEngineeringLineItem
640
        from LineDetector import LineDetector
641

    
642
        try:
643
            listWidget.addItem('Starting line recognization')
644
            worker.displayTitle.emit(worker.tr('Detecting lines...'))
645

    
646
            appDocData = AppDocData.instance()
647
            # remove already existing line and flow arrow item
648
            if not batch:
649
                items = [item for item in worker.graphicsView.scene.items() if (type(item) is QEngineeringLineItem)]
650
                for item in items:
651
                    item.transfer.onRemoved.emit(item)
652
                    # worker.graphicsView.scene.removeItem(item)
653
            # up to here
654

    
655
            # detect line
656
            connectedLines = []
657

    
658
            area = appDocData.getArea('Drawing')
659
            area.img = worker.removeSmallObjects(area.img)
660
            detector = LineDetector(area.img)
661

    
662
            symbols = []
663
            for item in appDocData.symbols:
664
                if issubclass(type(item), SymbolSvgItem):
665
                    symbols.append(item)
666
                    res = detector.detectConnectedLine(item, round(area.x), round(area.y))
667
                    if res is not None:
668
                        connectedLines.extend(res)
669

    
670
            # line detection without symbol connection point info
671
            remainLines = detector.detectLineWithoutSymbol(area)
672
            windowSize = appDocData.getSlidingWindowSize()
673
            thickness = int(windowSize[1])
674
            for line in remainLines:
675
                line.append(thickness)
676
            connectedLines.extend(remainLines)
677

    
678
            configs = appDocData.getConfigs('Line Detector', 'Length to connect line')
679
            toler = int(configs[0].value) if configs else 20
680
            detector.mergeLines(connectedLines, toler=toler)
681

    
682
            for pts in connectedLines:
683
                processLine = QEngineeringLineItem(
684
                    vertices=[(area.x + param[0], area.y + param[1]) for param in pts[:-1]], thickness=pts[2])
685
                processLine.area = 'Drawing'
686

    
687
                appDocData.lines.append(processLine)
688
                appDocData.allItems.append(processLine)
689

    
690
            """
691
            for pts in connectedLines:
692
                processLine = QEngineeringLineItem(vertices=[(area.x + param[0], area.y + param[1]) for param in pts[:-1]], thickness=pts[2])
693
                processLine.area = 'Drawing'
694

695
                appDocData.lines.append(processLine)
696
                appDocData.allItems.append(processLine)
697

698
                if processLine.length() > 100: # TODO: check critical length
699
                    processLine.addFlowArrow()
700
            """
701
        except Exception as ex:
702
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
703
                                                          sys.exc_info()[-1].tb_lineno)
704
            worker.displayLog.emit(MessageType.Error, message)
705
        finally:
706
            listWidget.addItem('Finishing line recognization')
707
            worker.finished.emit()
708

    
709
    '''
710
        @history    2018.04.24  Jeongwoo    Add isExceptDetect Field
711
                    2018.05.09  Jeongwoo    Add targetSymbolList clear
712
                    humkyung 2018.07.07 store symbols to list as like [equipments],[nozzles],[symbols]
713
    '''
714

    
715
    @staticmethod
716
    def initTargetSymbolDataList():
717
        global targetSymbolList
718

    
719
        targetSymbolList.clear()
720
        appDocData = AppDocData.instance()
721
        symbolList = appDocData.getTargetSymbolList()
722
        equipments = [item for item in symbolList if appDocData.getSymbolCategoryByType(item.getType()) == 'Equipment']
723
        nozzles = [item for item in symbolList if item.getType() == 'Nozzles']
724
        # [[equipments],[nozzles],[symbols]]
725
        targetSymbolList.append(equipments)
726
        targetSymbolList.append(nozzles)
727
        targetSymbolList.append([item for item in symbolList if item not in equipments and item not in nozzles])
728

    
729
        # for i in range(len(targetSymbolList[0])):
730
        #    print(targetSymbolList[0][i].getName())
731
        # for i in range(len(targetSymbolList[1])):
732
        #    print(targetSymbolList[1][i].getName())
733
        # for i in range(len(targetSymbolList[2])):
734
        #    print(targetSymbolList[2][i].getName())
735

    
736
        return targetSymbolList
737

    
738
    '''
739
        @brief  detect equipment
740
        @author humkyung
741
        @date   2018.07.07
742
    '''
743

    
744
    @staticmethod
745
    def detectEquipmentOnPid(mainRes, targetSymbol, listWidget, worker):
746
        try:
747
            equipments = Worker.detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker)
748
            for equipment in equipments:
749
                # detect nozzles around equimpent
750
                for nozzle in targetSymbolList[1]:
751
                    Worker.detectNozzleOnPid(equipment, nozzle, listWidget, worker)
752
                # up to here
753
        except Exception as ex:
754
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
755
                                                          sys.exc_info()[-1].tb_lineno)
756
            worker.displayLog(MessageType.Error, message)
757

    
758
    '''
759
        @brief      detect symbol on PID
760
        @author     jwkim
761
        @date   
762
        @history    humkyung 2018.04.06 check if symbol file exists
763
                    Jeongwoo 2018.05.29 Change method to adjust detail symbol location with hit-rate. Not feature point count
764
                                        Change parameter on add symbol part (mpCount → hitRate)
765
                                        Remove unusing calculation (avg)
766
                    Jeongwoo 2018.06.27 Remove part to split P&ID image and for loop
767
                    humkyung 2018.07.07 return searched symbols
768
    '''
769

    
770
    @staticmethod
771
    def detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker):
772
        import copy
773
        global ocrCompletedSrc
774
        global threadLock
775
        global maxProgressValue
776

    
777
        try:
778
            forTraining = worker.isTrainingChecked
779
            symbolName = targetSymbol.getName()
780
            # threadLock.acquire()
781
            # print(symbolName)
782
            # threadLock.release()
783
            symbolType = targetSymbol.getType()
784
            symbolPath = targetSymbol.getPath()
785
            symbolThreshold = targetSymbol.getThreshold()  # if not forTraining else targetSymbol.getThreshold() / 3 * 2
786
            symbolMinMatchCount = targetSymbol.getMinMatchCount()
787
            isDetectOnOrigin = targetSymbol.getIsDetectOnOrigin()
788
            symbolRotateCount = targetSymbol.getRotationCount()
789
            symbolOcrOption = targetSymbol.getOcrOption()
790
            isContainChild = targetSymbol.getIsContainChild()
791
            symbolOriginalPoint = targetSymbol.getOriginalPoint()
792
            symbolConnectionPoint = targetSymbol.getConnectionPoint()
793
            baseSymbol = targetSymbol.getBaseSymbol()
794
            additionalSymbol = targetSymbol.getAdditionalSymbol()
795
            isExceptDetect = targetSymbol.getIsExceptDetect()
796
            detectFlip = targetSymbol.getDetectFlip()
797

    
798
            # check if symbol file is target or not
799
            if isExceptDetect == 1:
800
                item = QListWidgetItem('{} file is not target'.format(os.path.split(os.path.basename(symbolPath))[0]))
801
                item.setBackground(QColor('green'))
802
                listWidget.addItem(item)
803
                return
804

    
805
            foundSymbolCount = 0
806

    
807
            # check if symbol file exists
808
            if not os.path.isfile(symbolPath):
809
                item = QListWidgetItem('{} file not found'.format(os.path.split(os.path.basename(symbolPath))[0]))
810
                item.setBackground(QColor('red'))
811
                listWidget.addItem(item)
812
                return
813
            # up to here
814

    
815
            sym = cv2.imread(symbolPath, 1)
816
            symGray = Worker.cvtGrayImage(sym)
817
            symGrayOri = copy.copy(symGray)
818
            ## TODO: 이진화 시켰을때 심볼이 검출되지 않음
819
            ## symGray = cv2.threshold(cvtGrayImage(sym), 127, 255, cv2.THRESH_BINARY)[1]
820
            ## cv2.imshow('symbol', symGray)
821
            ## cv2.waitKey(0)
822
            sow, soh = symGray.shape[::-1]  # symbol original w, h
823

    
824
            offsetDrawingArea = []
825
            appDocData = AppDocData.instance()
826
            area = appDocData.getArea('Drawing')
827
            if area is not None:
828
                copiedBasePid = area.img.copy()
829
                offsetDrawingArea.append(area.x)
830
                offsetDrawingArea.append(area.y)
831
            else:
832
                offsetDrawingArea.append(0)
833
                offsetDrawingArea.append(0)
834
                if isDetectOnOrigin == 1:
835
                    copiedBasePid = appDocData.imgSrc.copy()
836
                else:
837
                    copiedBasePid = ocrCompletedSrc.copy()
838
            srcWidth, srcHeight = copiedBasePid.shape[::-1]
839

    
840
            roiItemSp = (0, 0)
841
            roiItemEp = (srcWidth, srcHeight)
842
            roiItem = copiedBasePid
843

    
844
            for index in range(2):
845
                if index is 0:
846
                    # continue
847
                    pass
848
                elif detectFlip is not 1 and index is 1:
849
                    continue
850
                else:
851
                    symGray = symGrayOri
852
                    symGray = cv2.flip(symGray, 1)
853
                    # cv2.imwrite('out.png', symGray)
854
                    opx = sow - float(symbolOriginalPoint.split(',')[0])
855
                    opy = float(symbolOriginalPoint.split(',')[1])
856
                    symbolOriginalPoint = str(opx) + ',' + str(opy)
857

    
858
                    symbolConnectionPoint = symbolConnectionPoint.split("/")
859
                    symbolConnectionPointStr = ''
860
                    for strConnPt in symbolConnectionPoint:
861
                        if strConnPt == '': continue
862
                        tokens = strConnPt.split(',')
863

    
864
                        direction = 'AUTO'
865
                        symbol_idx = '0'
866
                        if len(tokens) == 2:
867
                            cpx = sow - float(tokens[0])
868
                            cpy = float(tokens[1])
869
                            cflip = direction + ',' + str(cpx) + ',' + str(cpy)
870
                        elif len(tokens) == 3:
871
                            direction = tokens[0]
872
                            cpx = sow - float(tokens[1])
873
                            cpy = float(tokens[2])
874
                            cflip = direction + ',' + str(cpx) + ',' + str(cpy)
875
                        elif len(tokens) >= 4:
876
                            direction = tokens[0]
877
                            cpx = sow - float(tokens[1])
878
                            cpy = float(tokens[2])
879
                            symbol_idx = tokens[3]
880
                            cflip = direction + ',' + str(cpx) + ',' + str(cpy) + ',' + str(symbol_idx)
881

    
882
                        if symbolConnectionPointStr == '':
883
                            symbolConnectionPointStr = cflip
884
                        else:
885
                            symbolConnectionPointStr = symbolConnectionPointStr + '/' + cflip
886
                    symbolConnectionPoint = symbolConnectionPointStr
887

    
888
                # print(symbolOriginalPoint)
889
                # print(symbolConnectionPoint)
890
                symbolRotatedAngle = 0
891
                for rc in range(symbolRotateCount + 1):  ## Rotation Count를 사용자 기준으로 받아서 1을 더한 후 사용
892
                    sw, sh = symGray.shape[::-1]
893
                    roiw = (roiItemEp[0] - roiItemSp[0])
894
                    roih = (roiItemEp[1] - roiItemSp[1])
895

    
896
                    ## Case : Bigger Symbol than Split ROI
897
                    if roiw < sw or roih < sh:
898
                        symGray = cv2.rotate(symGray, cv2.ROTATE_90_COUNTERCLOCKWISE)
899
                        symbolRotatedAngle = symbolRotatedAngle + 90
900

    
901
                        if baseSymbol is not None and additionalSymbol is not None:
902
                            additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol)
903
                        continue
904

    
905
                    ## get Rotated Original Point
906
                    originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint,
907
                                                                      symbolRotatedAngle, sw, sh, sow, soh)
908
                    connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw,
909
                                                                          sh, sow, soh)
910

    
911
                    ## Template Matching
912
                    tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED)
913
                    loc = np.where(tmRes >= symbolThreshold)
914

    
915
                    for pt in zip(*loc[::-1]):
916
                        mpCount = 0  # Match Point Count
917

    
918
                        roi = roiItem[pt[1]:pt[1] + sh, pt[0]:pt[0] + sw]
919

    
920
                        if symbolMinMatchCount > 0:
921
                            mpCount = Worker.getMatchPointCount(roi, symGray)
922
                            if not (mpCount >= symbolMinMatchCount):
923
                                continue
924

    
925
                        searchedItemSp = (roiItemSp[0] + pt[0] + round(offsetDrawingArea[0]),
926
                                          roiItemSp[1] + pt[1] + round(offsetDrawingArea[1]))
927
                        # print(searchedItemSp)
928

    
929
                        overlapArea = 0
930
                        symbolIndex = -1
931
                        for i in range(len(searchedSymbolList) - 1, -1, -1):
932
                            area = Worker.contains(searchedSymbolList[i], searchedItemSp, sw, sh)
933
                            if area > ACCEPT_OVERLAY_AREA:
934
                                # if area > overlapArea:
935
                                #    overlapArea = area
936
                                #    symbolIndex = i
937
                                overlapArea = area
938
                                symbolIndex = i
939
                                break
940
                                """
941
                                categories = [appDocData.isEquipmentType(symbolType), appDocData.isEquipmentType(searchedSymbolList[i].getType())]
942
                                if categories[0] == categories[1]:
943
                                    symbolIndex = i
944
                                    break
945
                                """
946

    
947
                        hitRate = tmRes[pt[1], pt[0]]
948

    
949
                        ## 겹치는 영역이 기준값보다 작을 경우
950
                        if overlapArea <= ACCEPT_OVERLAY_AREA:
951
                            threadLock.acquire()
952
                            foundSymbolCount = foundSymbolCount + 1
953
                            Worker.addSearchedSymbol(symbolName, symbolType,
954
                                                     searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount,
955
                                                     hitRate, symbolRotatedAngle,
956
                                                     isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
957
                                                     isContainChild,
958
                                                     originalPoint, connectionPoint, baseSymbol, additionalSymbol,
959
                                                     isExceptDetect, detectFlip=1 if index is 1 else 0)
960
                            threadLock.release()
961
                        else:  ## 겹치는 영역이 기준값보다 클 경우
962
                            if symbolIndex != -1 and symbolIndex < len(searchedSymbolList):
963
                                searchedSymbol = searchedSymbolList[symbolIndex]
964
                                ## 현재 심볼과 검출된 심볼이 같을 경우 Match Point가 더 높은 정보로 교체
965
                                if symbolName == searchedSymbol.getName():
966
                                    symbolHitRate = searchedSymbol.getHitRate()
967
                                    if symbolHitRate - searchedSymbol.getThreshold() < hitRate - symbolThreshold:
968
                                        threadLock.acquire()
969
                                        # replace existing symbol with new symbol has high accuracy
970
                                        searchedSymbolList[symbolIndex] = symbol.Symbol(symbolName, symbolType,
971
                                                                                        searchedItemSp, sw, sh,
972
                                                                                        symbolThreshold,
973
                                                                                        symbolMinMatchCount, hitRate,
974
                                                                                        symbolRotatedAngle,
975
                                                                                        isDetectOnOrigin,
976
                                                                                        symbolRotateCount,
977
                                                                                        symbolOcrOption, isContainChild,
978
                                                                                        ','.join(str(x) for x in
979
                                                                                                 originalPoint),
980
                                                                                        '/'.join('{},{},{},{}'.format(
981
                                                                                            param[0], param[1],
982
                                                                                            param[2], param[3]) for
983
                                                                                                 param in
984
                                                                                                 connectionPoint),
985
                                                                                        baseSymbol, additionalSymbol,
986
                                                                                        isExceptDetect,
987
                                                                                        detectFlip=1 if index is 1 else 0)
988
                                        threadLock.release()
989
                                ## 현재 심볼과 검출된 심볼이 같지 않을 경우 (포함)
990
                                elif appDocData.isEquipmentType(searchedSymbol.getType()):
991
                                    if searchedSymbol.area > sw * sh * 10:  # searched equipment area is greather than 10 times of symbol's area
992
                                        threadLock.acquire()
993
                                        foundSymbolCount = foundSymbolCount + 1
994
                                        Worker.addSearchedSymbol(symbolName, symbolType,
995
                                                                 searchedItemSp, sw, sh, symbolThreshold, hitRate,
996
                                                                 hitRate, symbolRotatedAngle,
997
                                                                 isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
998
                                                                 isContainChild,
999
                                                                 originalPoint, connectionPoint, baseSymbol,
1000
                                                                 additionalSymbol, isExceptDetect,
1001
                                                                 detectFlip=1 if index is 1 else 0)
1002
                                        threadLock.release()
1003
                                ## 현재 심볼과 검출된 심볼이 같지 않을 경우 (교체)
1004
                                elif not forTraining:
1005
                                    searchedSymbol = searchedSymbolList[symbolIndex]
1006
                                    symbolHitRate = searchedSymbol.getHitRate()
1007
                                    if symbolHitRate - searchedSymbol.getThreshold() < hitRate - symbolThreshold:
1008
                                        threadLock.acquire()
1009
                                        searchedSymbolList[symbolIndex] = symbol.Symbol(symbolName, symbolType,
1010
                                                                                        searchedItemSp, sw, sh,
1011
                                                                                        symbolThreshold,
1012
                                                                                        symbolMinMatchCount, hitRate,
1013
                                                                                        symbolRotatedAngle,
1014
                                                                                        isDetectOnOrigin,
1015
                                                                                        symbolRotateCount,
1016
                                                                                        symbolOcrOption, isContainChild,
1017
                                                                                        ','.join(str(x) for x in
1018
                                                                                                 originalPoint),
1019
                                                                                        '/'.join('{},{},{},{}'.format(
1020
                                                                                            param[0], param[1],
1021
                                                                                            param[2], param[3]) for
1022
                                                                                                 param in
1023
                                                                                                 connectionPoint),
1024
                                                                                        baseSymbol, additionalSymbol,
1025
                                                                                        isExceptDetect,
1026
                                                                                        detectFlip=1 if index is 1 else 0)
1027
                                        threadLock.release()
1028
                                # 학습용 데이터 생성을 위해 교체하지 않고 추가함
1029
                                elif forTraining:
1030
                                    threadLock.acquire()
1031
                                    foundSymbolCount = foundSymbolCount + 1
1032
                                    Worker.addSearchedSymbol(symbolName, symbolType,
1033
                                                             searchedItemSp, sw, sh, symbolThreshold,
1034
                                                             symbolMinMatchCount, hitRate, symbolRotatedAngle,
1035
                                                             isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
1036
                                                             isContainChild,
1037
                                                             originalPoint, connectionPoint, baseSymbol,
1038
                                                             additionalSymbol, isExceptDetect,
1039
                                                             detectFlip=1 if index is 1 else 0)
1040
                                    threadLock.release()
1041

    
1042
                    ## Rotate Symbol
1043
                    symGray = cv2.rotate(symGray, cv2.ROTATE_90_COUNTERCLOCKWISE)
1044
                    symbolRotatedAngle = symbolRotatedAngle + 90
1045

    
1046
                    if additionalSymbol is not None:
1047
                        additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol)
1048

    
1049
            threadLock.acquire()
1050
            listWidget.addItem('Found Symbol   : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str(
1051
                foundSymbolCount) + ')')
1052
            threadLock.release()
1053

    
1054
            worker.updateProgress.emit(maxProgressValue, symbolPath)
1055

    
1056
            return [symbol for symbol in searchedSymbolList if symbol.getName() == symbolName]
1057
        except Exception as ex:
1058
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1059
                                                          sys.exc_info()[-1].tb_lineno)
1060
            worker.displayLog.emit(MessageType.Error, message)
1061

    
1062
        return []
1063

    
1064
    '''
1065
        @brief      detect nozzle
1066
        @author     humkyung
1067
        @date       2018.07.07
1068
        @history    humkyhung 2018.07.17 pass equpment as parameter instead of image
1069
    '''
1070

    
1071
    @staticmethod
1072
    def detectNozzleOnPid(equipment, nozzle, listWidget, worker):
1073
        global src
1074
        global threadLock
1075
        global maxProgressValue
1076

    
1077
        try:
1078
            symbolName = nozzle.getName()
1079
            symbolType = nozzle.getType()
1080
            symbolPath = nozzle.getPath()
1081
            symbolThreshold = nozzle.getThreshold()
1082
            symbolMinMatchCount = nozzle.getMinMatchCount()
1083
            isDetectOnOrigin = nozzle.getIsDetectOnOrigin()
1084
            symbolRotateCount = nozzle.getRotationCount()
1085
            symbolOcrOption = nozzle.getOcrOption()
1086
            isContainChild = nozzle.getIsContainChild()
1087
            symbolOriginalPoint = nozzle.getOriginalPoint()
1088
            symbolConnectionPoint = nozzle.getConnectionPoint()
1089
            baseSymbol = nozzle.getBaseSymbol()
1090
            additionalSymbol = nozzle.getAdditionalSymbol()
1091
            isExceptDetect = nozzle.getIsExceptDetect()
1092

    
1093
            foundSymbolCount = 0
1094

    
1095
            # check if symbol file exists
1096
            if not os.path.isfile(symbolPath):
1097
                item = QListWidgetItem('{} file not found'.format(os.path.split(os.path.basename(symbolPath))[0]))
1098
                item.setBackground(QColor('red'))
1099
                listWidget.addItem(item)
1100
                return
1101
            # up to here
1102

    
1103
            symGray = Worker.cvtGrayImage(cv2.imread(symbolPath, 1))
1104
            sow, soh = symGray.shape[::-1]  # symbol original w, h
1105

    
1106
            # get image of equipment with offset of nozzle size
1107
            appDocData = AppDocData.instance()
1108
            pt = equipment.getSp()
1109
            nozzleSize = max(sow, soh)
1110
            sx = round(pt[0]) - nozzleSize
1111
            sy = round(pt[1]) - nozzleSize
1112
            ex = round(pt[0] + equipment.getWidth()) + nozzleSize
1113
            ey = round(pt[1] + equipment.getHeight()) + nozzleSize
1114
            offset = (sx, sy)
1115
            eqpSize = (pt[0], pt[1], equipment.getWidth(), equipment.getHeight())
1116
            img = appDocData.imgSrc[sy:ey, sx:ex]
1117
            srcWidth, srcHeight = img.shape[::-1]
1118
            # up to here
1119

    
1120
            roiItemSp = (0, 0)
1121
            roiItemEp = (srcWidth, srcHeight)
1122
            roiItem = img
1123

    
1124
            symbolAngle = 0
1125
            for rc in range(symbolRotateCount + 1):  ## Rotation Count를 사용자 기준으로 받아서 1을 더한 후 사용
1126
                sw, sh = symGray.shape[::-1]
1127
                roiw = (roiItemEp[0] - roiItemSp[0])
1128
                roih = (roiItemEp[1] - roiItemSp[1])
1129

    
1130
                ## get Rotated Original Point
1131
                originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolAngle,
1132
                                                                  sw, sh, sow, soh)
1133
                connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolAngle, sw, sh, sow,
1134
                                                                      soh)
1135
                dx = connectionPoint[0][0] - originalPoint[0]
1136
                dy = connectionPoint[0][1] - originalPoint[1]
1137

    
1138
                ## Template Matching
1139
                tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED)
1140
                loc = np.where(tmRes >= symbolThreshold)
1141

    
1142
                for pt in zip(*loc[::-1]):
1143
                    mpCount = 0  # Match Point Count
1144
                    symbolIndex = -1
1145

    
1146
                    roi = roiItem[pt[1]:pt[1] + sh, pt[0]:pt[0] + sw]
1147

    
1148
                    if symbolMinMatchCount > 0:
1149
                        mpCount = Worker.getMatchPointCount(roi, symGray)
1150
                        if not (mpCount >= symbolMinMatchCount):
1151
                            continue
1152

    
1153
                    mid = (offset[0] + pt[0] + (originalPoint[0] + connectionPoint[0][0]) * 0.5,
1154
                           offset[1] + pt[1] + (originalPoint[1] + connectionPoint[0][1]) * 0.5)
1155
                    searchedItemSp = (roiItemSp[0] + pt[0] + offset[0], roiItemSp[1] + pt[1] + offset[1])
1156
                    # check searched nozzle location
1157
                    if abs(dx) > abs(dy):
1158
                        if dx > 0:
1159
                            if mid[0] < eqpSize[0] + eqpSize[2] * 0.5: continue
1160
                        else:
1161
                            if mid[0] > eqpSize[0] + eqpSize[2] * 0.5: continue
1162
                    else:
1163
                        if dy > 0:
1164
                            if mid[1] < eqpSize[1] + eqpSize[3] * 0.5: continue
1165
                        else:
1166
                            if mid[1] > eqpSize[1] + eqpSize[3] * 0.5: continue
1167
                    # up to here
1168

    
1169
                    overlapArea = 0
1170
                    nozzles = [symbol for symbol in searchedSymbolList if symbol.getType() == 'Nozzles']
1171
                    for i in range(len(nozzles)):
1172
                        _pt = nozzles[i].getSp()
1173
                        rect = QRectF(_pt[0], _pt[1], nozzles[i].getWidth(), nozzles[i].getHeight())
1174
                        _rect = QRectF(searchedItemSp[0], searchedItemSp[1], sw, sh)
1175
                        if rect.intersects(_rect):
1176
                            intersect = rect.intersected(_rect)
1177
                            overlapArea = intersect.width() * intersect.height()
1178
                            if overlapArea > ACCEPT_OVERLAY_AREA:
1179
                                symbolIndex = i
1180
                                break
1181

    
1182
                    hitRate = tmRes[pt[1], pt[0]]
1183

    
1184
                    ## 겹치는 영역이 기준값보다 작을 경우
1185
                    if overlapArea <= ACCEPT_OVERLAY_AREA:
1186
                        threadLock.acquire()
1187
                        foundSymbolCount = foundSymbolCount + 1
1188
                        searched = Worker.addSearchedSymbol(symbolName, symbolType
1189
                                                            , searchedItemSp, sw, sh, symbolThreshold,
1190
                                                            symbolMinMatchCount, hitRate, symbolAngle
1191
                                                            , isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
1192
                                                            isContainChild
1193
                                                            , originalPoint, connectionPoint, baseSymbol,
1194
                                                            additionalSymbol, isExceptDetect)
1195
                        searched.owner = equipment
1196
                        threadLock.release()
1197
                    ## 겹치는 영역이 기준값보다 클 경우
1198
                    else:
1199
                        if symbolIndex != -1 and symbolIndex < len(nozzles):
1200
                            searchedSymbol = nozzles[symbolIndex]
1201
                            ## 현재 심볼과 검출된 심볼이 같을 경우 Match Point가 더 높은 정보로 교체
1202
                            if symbolName == searchedSymbol.getName():
1203
                                symbolHitRate = searchedSymbol.getHitRate()
1204
                                if symbolHitRate < hitRate:
1205
                                    threadLock.acquire()
1206
                                    nozzles[symbolIndex] = symbol.Symbol(symbolName, symbolType,
1207
                                                                         searchedItemSp, sw, sh, symbolThreshold,
1208
                                                                         symbolMinMatchCount, hitRate, symbolAngle,
1209
                                                                         isDetectOnOrigin, symbolRotateCount,
1210
                                                                         symbolOcrOption, isContainChild,
1211
                                                                         ','.join(str(x) for x in originalPoint),
1212
                                                                         '/'.join(
1213
                                                                             '{},{},{},{}'.format(param[0], param[1],
1214
                                                                                                  param[2], param[3])
1215
                                                                             for param in connectionPoint),
1216
                                                                         baseSymbol, additionalSymbol, isExceptDetect)
1217
                                    threadLock.release()
1218
                            ## 현재 심볼과 검출된 심볼이 같지 않을 경우 (포함)
1219
                            elif appDocData.isEquipmentType(searchedSymbol.getType()):
1220
                                threadLock.acquire()
1221
                                foundSymbolCount = foundSymbolCount + 1
1222
                                searched = Worker.addSearchedSymbol(symbolName, symbolType
1223
                                                                    , searchedItemSp, sw, sh, symbolThreshold, hitRate,
1224
                                                                    hitRate, symbolAngle
1225
                                                                    , isDetectOnOrigin, symbolRotateCount,
1226
                                                                    symbolOcrOption, isContainChild
1227
                                                                    , originalPoint, connectionPoint, baseSymbol,
1228
                                                                    additionalSymbol, isExceptDetect)
1229
                                searched.owner = equipment
1230
                                threadLock.release()
1231

    
1232
                ## Rotate Symbol
1233
                symGray = cv2.rotate(symGray, cv2.ROTATE_90_COUNTERCLOCKWISE)
1234
                symbolAngle = symbolAngle + 90
1235

    
1236
                if additionalSymbol is not None:
1237
                    additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol)
1238

    
1239
            threadLock.acquire()
1240
            listWidget.addItem('Found Symbol   : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str(
1241
                foundSymbolCount) + ')')
1242
            threadLock.release()
1243

    
1244
            worker.updateProgress.emit(maxProgressValue, symbolPath)
1245

    
1246
            return [symbol for symbol in searchedSymbolList if symbol.getName() == symbolName]
1247
        except Exception as ex:
1248
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1249
                                                          sys.exc_info()[-1].tb_lineno)
1250
            worker.displayLog(MessageType.Error, message)
1251

    
1252
        return []
1253

    
1254
    # Convert into Grayscale image
1255
    @staticmethod
1256
    def cvtGrayImage(img):
1257
        return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
1258

    
1259
    '''
1260
        @history    2018.06.12  Jeongwoo    Type changed (int → float)
1261
                    humkyung 2018.07.07 change return type as like [x,y]
1262
    '''
1263

    
1264
    @staticmethod
1265
    def getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolRotatedAngle, rotateSymbolWidth,
1266
                                   rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight):
1267
        res = []
1268

    
1269
        if additionalSymbol is None and symbolOriginalPoint is None:
1270
            res.append(rotateSymbolWidth // 2)
1271
            res.append(rotateSymbolHeight // 2)
1272
        else:
1273
            opx = float(symbolOriginalPoint.split(',')[0])
1274
            opy = float(symbolOriginalPoint.split(',')[1])
1275
            rPt = Worker.getCoordOnRotatedImage(symbolRotatedAngle, ('AUTO', opx, opy, '0'), originalSymbolWidth,
1276
                                                originalSymbolHeight)
1277

    
1278
            res.append(rPt[1])
1279
            res.append(rPt[2])
1280

    
1281
        return res
1282

    
1283
    '''
1284
        @history    2018.06.12  Jeongwoo    Type changed (int → float)
1285
                    humkyung 2018.07.07 change return type as like [[x,y],...]
1286
                    humkyung 2019.01.04 get symbol index
1287
    '''
1288

    
1289
    @staticmethod
1290
    def getCalculatedConnectionPoint(symbolConnectionPointStr, symbolRotatedAngle, rotateSymbolWidth,
1291
                                     rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight):
1292
        res = []
1293

    
1294
        if symbolConnectionPointStr is not None and symbolConnectionPointStr != '':
1295
            splitConnectionPointStr = symbolConnectionPointStr.split("/")
1296
            for strConnPt in splitConnectionPointStr:
1297
                tokens = strConnPt.split(',')
1298

    
1299
                direction = 'AUTO'
1300
                symbol_idx = '0'
1301
                if len(tokens) == 2:
1302
                    cpx = float(tokens[0])
1303
                    cpy = float(tokens[1])
1304
                elif len(tokens) == 3:
1305
                    direction = tokens[0]
1306
                    cpx = float(tokens[1])
1307
                    cpy = float(tokens[2])
1308
                elif len(tokens) >= 4:
1309
                    direction = tokens[0]
1310
                    cpx = float(tokens[1])
1311
                    cpy = float(tokens[2])
1312
                    symbol_idx = tokens[3]
1313

    
1314
                res.append(Worker.getCoordOnRotatedImage(symbolRotatedAngle, (direction, cpx, cpy, symbol_idx),
1315
                                                         originalSymbolWidth, originalSymbolHeight))
1316

    
1317
        return res
1318

    
1319
    '''
1320
        @brief      rotate (x,y) by given angle
1321
        @author     Jeongwoo
1322
        @date       2018.??.??
1323
        @history    humkyung 2018.04.13 fixed code when angle is 90 or 270    
1324
                    Jeongwoo 2018.04.27 Change calculation method with QTransform
1325
                    humkyung 2018.09.01 calculate rotated direction
1326
    '''
1327

    
1328
    @staticmethod
1329
    def getCoordOnRotatedImage(rAngle, connPt, originImageWidth, originImageHeight):
1330
        rx = None
1331
        ry = None
1332

    
1333
        ## calculate rotated direction
1334
        direction = connPt[0]
1335
        if direction == 'LEFT':
1336
            direction = 'DOWN' if rAngle == 90 else 'RIGHT' if rAngle == 180 else 'UP' if rAngle == 270 else direction
1337
        elif direction == 'RIGHT':
1338
            direction = 'UP' if rAngle == 90 else 'LEFT' if rAngle == 180 else 'DOWN' if rAngle == 270 else direction
1339
        elif direction == 'UP':
1340
            direction = 'LEFT' if rAngle == 90 else 'DOWN' if rAngle == 180 else 'RIGHT' if rAngle == 270 else direction
1341
        elif direction == 'DOWN':
1342
            direction = 'RIGHT' if rAngle == 90 else 'UP' if rAngle == 180 else 'LEFT' if rAngle == 270 else direction
1343
        ## up to here
1344

    
1345
        transform = QTransform()
1346
        if rAngle == 90 or rAngle == 270:
1347
            transform.translate(originImageHeight * 0.5, originImageWidth * 0.5)
1348
        elif rAngle == 0 or rAngle == 180:
1349
            transform.translate(originImageWidth * 0.5, originImageHeight * 0.5)
1350
        transform.rotate(-abs(rAngle))
1351
        transform.translate(-originImageWidth * 0.5, -originImageHeight * 0.5)
1352
        point = QPoint(connPt[1], connPt[2])
1353
        point = transform.map(point)
1354
        rx = point.x()
1355
        ry = point.y()
1356

    
1357
        symbol_idx = connPt[3]
1358

    
1359
        return (direction, rx, ry, symbol_idx)
1360

    
1361
    '''
1362
        @brief      Add symbols
1363
        @author     jwkim
1364
        @date
1365
        @history    Change parameter (mpCount → hitRate)
1366
                    Yecheol 2018.07.04 Delete Symbol Id 
1367
    '''
1368

    
1369
    @staticmethod
1370
    def addSearchedSymbol(sName, sType
1371
                          , sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle
1372
                          , isDetectOnOrigin, rotateCount, ocrOption, isContainChild
1373
                          , originalPoint, connectionPoint, baseSymbol, additionalSymbol, isExceptDetect, detectFlip):
1374
        global searchedSymbolList
1375

    
1376
        newSym = None
1377
        try:
1378
            newSym = symbol.Symbol(sName, sType
1379
                                   , sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle,
1380
                                   isDetectOnOrigin, rotateCount, ocrOption, isContainChild,
1381
                                   ','.join(str(x) for x in originalPoint),
1382
                                   '/'.join('{},{},{},{}'.format(param[0], param[1], param[2], param[3]) for param in
1383
                                            connectionPoint),
1384
                                   baseSymbol, additionalSymbol, isExceptDetect, detectFlip=detectFlip)
1385

    
1386
            searchedSymbolList.append(newSym)
1387
        except Exception as ex:
1388
            from App import App
1389

    
1390
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1391
                                                          sys.exc_info()[-1].tb_lineno)
1392
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1393

    
1394
        return newSym
1395

    
1396
    '''
1397
        @brief      Check object contains pt
1398
        @param      obj is item in searchedSymbolList
1399
    '''
1400

    
1401
    @staticmethod
1402
    def contains(obj, pt, tw, th):
1403
        sp = obj.getSp()
1404
        width = obj.getWidth()
1405
        height = obj.getHeight()
1406

    
1407
        if sp[0] > pt[0] + tw:
1408
            return 0
1409
        if sp[0] + width < pt[0]:
1410
            return 0
1411
        if sp[1] > pt[1] + th:
1412
            return 0
1413
        if sp[1] + height < pt[1]:
1414
            return 0
1415

    
1416
        # shared area
1417
        x = max(sp[0], pt[0])
1418
        y = max(sp[1], pt[1])
1419
        w = min(sp[0] + width, pt[0] + tw) - x
1420
        h = min(sp[1] + height, pt[1] + th) - y
1421

    
1422
        return float((w * h)) / float((tw * th)) * 100
1423

    
1424
    # Calculate count of keypoint match result
1425
    @staticmethod
1426
    def getMatchPointCount(src, cmp):
1427
        matchCount = 0
1428

    
1429
        try:
1430
            orb = cv2.ORB_create(1000, 2.0, 2, 1)
1431

    
1432
            kp1, des1 = orb.detectAndCompute(src, None)
1433
            kp2, des2 = orb.detectAndCompute(cmp, None)
1434

    
1435
            FLANN_INDEX_LSH = 6
1436
            # table_number      : The number of hash tables use
1437
            # key_size          : The length of the key in the hash tables
1438
            # multi_probe_level : Number of levels to use in multi-probe (0 for standard LSH)
1439
            #                     It controls how neighboring buckets are searched
1440
            #                     Recommended value is 2
1441
            # checks            : specifies the maximum leafs to visit when searching for neighbours.
1442
            # LSH : Locality-Sensitive Hashing
1443
            # ref : https://www.cs.ubc.ca/research/flann/uploads/FLANN/flann_manual-1.8.4.pdf
1444
            index_params = dict(algorithm=FLANN_INDEX_LSH, table_number=20, key_size=10, multi_probe_level=4)
1445
            search_params = dict(checks=100)
1446

    
1447
            flann = cv2.FlannBasedMatcher(index_params, search_params)
1448

    
1449
            matches = flann.knnMatch(des1, des2, k=2)
1450
            matchesMask = [[0, 0] for i in range(len(matches))]  # Python 3.x
1451

    
1452
            count = 0
1453
            # ratio test as per Lowe's paper
1454
            for i in range(len(matches)):
1455
                if len(matches[i]) == 2:
1456
                    m = matches[i][0]
1457
                    n = matches[i][1]
1458
                    if m.distance < 0.85 * n.distance:
1459
                        count = count + 1
1460

    
1461
            matchCount = count
1462
        except Exception as ex:
1463
            from App import App
1464

    
1465
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1466
                                                          sys.exc_info()[-1].tb_lineno)
1467
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1468

    
1469
        return matchCount
1470

    
1471
    '''
1472
        @brief      Remake rotated child symbol info
1473
    '''
1474

    
1475
    @staticmethod
1476
    def getRotatedChildInfo(additionalSymbol):
1477
        tempChildInfo = ""
1478
        if additionalSymbol:
1479
            childList = additionalSymbol.split("/")
1480
            for index in range(len(childList)):
1481
                child = childList[index]
1482
                direction = Worker.convertDirectionCodeToValue(child.split(",")[0])
1483
                childName = child.split(",")[1]
1484
                direction = (direction - 1) if direction > 0 else 3
1485
                if index != 0:
1486
                    tempChildInfo = tempChildInfo + "/"
1487
                tempChildInfo = tempChildInfo + Worker.convertValueToDirectionCode(direction) + "," + childName
1488
        return tempChildInfo
1489

    
1490
    '''
1491
        @brief   detect symbols on PID
1492
        @history humkyung 2018.06.08 add parameteres for signal
1493
    '''
1494

    
1495
    @staticmethod
1496
    def detectSymbolsOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal):
1497
        for detailTarget in targetSymbols:
1498
            Worker.detectSymbolOnPid(mainRes, detailTarget, listWidget, updateProgressSignal)
1499

    
1500
    @staticmethod
1501
    def convertDirectionCodeToValue(directionCode):
1502
        if directionCode == "UP":
1503
            return 0
1504
        elif directionCode == "RIGHT":
1505
            return 1
1506
        elif directionCode == "DOWN":
1507
            return 2
1508
        elif directionCode == "LEFT":
1509
            return 3
1510
        else:
1511
            return -1
1512

    
1513
    @staticmethod
1514
    def convertValueToDirectionCode(value):
1515
        if value == 0:
1516
            return "UP"
1517
        elif value == 1:
1518
            return "RIGHT"
1519
        elif value == 2:
1520
            return "DOWN"
1521
        elif value == 3:
1522
            return "LEFT"
1523
        else:
1524
            return "NONE"
1525

    
1526
    '''
1527
        @brief  draw found symbols and texts
1528
        @author Jeongwoo
1529
    '''
1530

    
1531
    @staticmethod
1532
    def drawFoundSymbolsOnCanvas(drawingPath, textInfos, listWidget):
1533
        global src
1534
        global ocrCompletedSrc
1535
        global canvas
1536

    
1537
        appDocData = AppDocData.instance()
1538
        canvas = np.zeros(appDocData.imgSrc.shape, np.uint8)
1539
        canvas[::] = 255
1540

    
1541
        try:
1542
            appDocData = AppDocData.instance()
1543
            project = appDocData.getCurrentProject()
1544

    
1545
            for symbol in searchedSymbolList:
1546
                Worker.drawFoundSymbols(symbol, listWidget)
1547

    
1548
            for text in textInfos:
1549
                left = text.getX()
1550
                top = text.getY()
1551
                right = text.getX() + text.getW()
1552
                bottom = text.getY() + text.getH()
1553

    
1554
                canvas[top:bottom, left:right] = appDocData.imgSrc[top:bottom, left:right]
1555

    
1556
            cv2.imwrite(os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(drawingPath)), canvas)
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
    '''
1565
        @history    2018.04.27  Jeongwoo    Remove Tesseract Log on listWidget
1566
                    2018.05.04  Jeongwoo    Change method to OCR with tesseract_ocr_module.py
1567
                    2018.05.09  Jeongwoo    Add global variable textInfoList, Remove text in symbol and Add tesseract result text
1568
                    2018.05.10  Jeongwoo    Remove not used if-statement
1569
                    2018.06.19  Jeongwoo    When detect text in symbol, use getTextAreaInfo() and Tesseract
1570
                    2018.06.21  Jeongwoo    Add if-statement for way to detect text by Type A
1571
    '''
1572

    
1573
    @staticmethod
1574
    def drawFoundSymbols(symbol, listWidget):
1575
        global src
1576
        global canvas
1577
        global WHITE_LIST_CHARS
1578
        global searchedSymbolList
1579
        global textInfoList
1580

    
1581
        # symbolId = symbol.getId()
1582
        symbolPath = symbol.getPath()
1583
        symbolSp = symbol.getSp()
1584
        symbolRotatedAngle = symbol.getRotatedAngle()
1585

    
1586
        symImg = cv2.cvtColor(cv2.imread(symbolPath, 1), cv2.COLOR_BGR2GRAY)
1587
        if symbol.getDetectFlip() is 1:
1588
            symImg = cv2.flip(symImg, 1)
1589
        for i in range(symbolRotatedAngle // 90):
1590
            symImg = cv2.rotate(symImg, cv2.ROTATE_90_COUNTERCLOCKWISE)
1591

    
1592
        w, h = symImg.shape[::-1]
1593
        canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w] = cv2.bitwise_and(
1594
            canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w], symImg)
1595

    
1596
    '''
1597
        @history    2018.05.17  Jeongwoo    Bitwise_not target changed (Original Image → Symbol Image)
1598
                    humkyung 2018.07.11 add parameter for image
1599
    '''
1600

    
1601
    @staticmethod
1602
    def removeDetectedSymbol(sym, imgSrc):
1603
        global ocrCompletedSrc
1604
        global threadLock
1605

    
1606
        path = sym.getPath()
1607
        sp = sym.getSp()
1608
        sw = sym.getWidth()
1609
        sh = sym.getHeight()
1610
        angle = sym.getRotatedAngle()
1611
        symImg = cv2.imread(path)
1612
        symImg = cv2.threshold(Worker.cvtGrayImage(symImg), 127, 255, cv2.THRESH_BINARY)[1]
1613
        if sym.getDetectFlip() is 1:
1614
            symImg = cv2.flip(symImg, 1)
1615

    
1616
        for i in range(angle // 90):
1617
            symImg = cv2.rotate(symImg, cv2.ROTATE_90_COUNTERCLOCKWISE)
1618

    
1619
        threadLock.acquire()
1620
        temp = imgSrc[sp[1]:sp[1] + sh, sp[0]:sp[0] + sw]
1621
        symImg = cv2.erode(symImg, np.ones((3, 3), np.uint8))
1622
        mask = cv2.bitwise_or(temp, symImg)
1623
        imgXOR = cv2.bitwise_xor(temp, mask)
1624
        # imgXOR = cv2.dilate(imgXOR, np.ones((5, 5), np.uint8))
1625
        imgSrc[sp[1]:sp[1] + sh, sp[0]:sp[0] + sw] = cv2.bitwise_not(imgXOR)
1626
        threadLock.release()
1627

    
1628
    '''
1629
        @brief  get difference between given original and recognized image
1630
        @author humkyung
1631
        @date   2018.06.11
1632
    '''
1633

    
1634
    @staticmethod
1635
    def getDifference(orgImagePath, recImagePath):
1636
        import re
1637

    
1638
        global ocrCompletedSrc
1639
        global textInfoList
1640

    
1641
        try:
1642
            appDocData = AppDocData.instance()
1643
            if os.path.isfile(orgImagePath) and os.path.isfile(recImagePath):
1644
                imgOriginal = \
1645
                    cv2.threshold(Worker.cvtGrayImage(cv2.imread(orgImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1]
1646

    
1647
                configs = appDocData.getConfigs('Filter', 'DilateSize')
1648
                if 1 == len(configs) and int(configs[0].value) is not 0:
1649
                    size = int(configs[0].value)
1650
                    kernel = np.ones((size, size), np.uint8)
1651
                    imgOriginal = cv2.erode(imgOriginal, kernel, iterations=1)
1652

    
1653
                # remove not drawing area
1654
                configs = appDocData.getConfigs('{} Equipment Desc Area'.format(appDocData.imgName))
1655
                for config in configs:
1656
                    found = re.findall('\\d+', config.value)
1657
                    if len(found) == 4:
1658
                        cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
1659
                                      (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
1660

    
1661
                configs = appDocData.getConfigs('{} Typical Area'.format(appDocData.imgName))
1662
                for config in configs:
1663
                    found = re.findall('\\d+', config.value)
1664
                    if len(found) == 4:
1665
                        cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
1666
                                      (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
1667

    
1668
                noteArea = appDocData.getArea('Note')
1669
                if noteArea is not None:
1670
                    noteArea.img = appDocData.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height),
1671
                                   round(noteArea.x):round(noteArea.x + noteArea.width)].copy()
1672
                    cv2.rectangle(imgOriginal, (round(noteArea.x), round(noteArea.y)),
1673
                                  (round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1)
1674
                # up to here
1675

    
1676
                imgRecognized = \
1677
                    cv2.threshold(Worker.cvtGrayImage(cv2.imread(recImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1]
1678

    
1679
                imgDiff = np.ones(imgOriginal.shape, np.uint8) * 255
1680

    
1681
                area = AppDocData.instance().getArea('Drawing')
1682
                if area is not None:
1683
                    x = round(area.x)
1684
                    y = round(area.y)
1685
                    width = round(area.width)
1686
                    height = round(area.height)
1687
                    imgNotOper = cv2.bitwise_not(imgRecognized[y:y + height, x:x + width])
1688
                    imgDiff[y:y + height, x:x + width] = cv2.bitwise_xor(imgOriginal[y:y + height, x:x + width],
1689
                                                                         imgNotOper)
1690

    
1691
                # remove noise
1692
                imgDiff = cv2.dilate(imgDiff, np.ones((2, 2), np.uint8))
1693

    
1694
                appDocData = AppDocData.instance()
1695
                project = appDocData.getCurrentProject()
1696
                cv2.imwrite(os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(orgImagePath)), imgDiff)
1697
        except Exception as ex:
1698
            from App import App
1699

    
1700
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1701
                                                          sys.exc_info()[-1].tb_lineno)
1702
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1703

    
1704

    
1705
'''
1706
    @history    2018.05.25  Jeongwoo    Add pyqtSignal(svgItemClicked, itemRemoved)
1707
'''
1708

    
1709

    
1710
class QRecognitionDialog(QDialog):
1711
    svgItemClicked = pyqtSignal(SymbolSvgItem)
1712
    itemRemoved = pyqtSignal(QGraphicsItem)
1713
    unBlockEvent = pyqtSignal()
1714

    
1715
    '''
1716
        @history    2018.05.25  Jeongwoo    Add parameter and initialize / Connect recognizeButton signal and slot
1717
                    2018.05.29  Jeongwoo    Chnage parameter(graphicsView → parent) and Get graphicsView from parent
1718
    '''
1719

    
1720
    def __init__(self, parent, path, batch):  # Parent is MainWindow
1721
        from AppDocData import AppDocData
1722

    
1723
        QDialog.__init__(self, parent)
1724

    
1725
        self.parent = parent
1726
        self.graphicsView = parent.graphicsView
1727
        self.path = path
1728
        self.xmlPath = None
1729
        self.ui = Recognition_UI.Ui_Recognition()
1730
        self.ui.setupUi(self)
1731

    
1732
        self.ui.buttonBox.setEnabled(True)
1733
        self.ui.listWidget.model().rowsInserted.connect(self.rowInserted)
1734
        self.ui.recognizeButton.clicked.connect(self.recognizeButtonClicked)
1735
        self.ui.lineCheckBox.stateChanged.connect(self.checkBoxChanged)
1736
        self.ui.checkBoxSymbol.stateChanged.connect(self.checkBoxChanged)
1737
        self.ui.checkBoxText.stateChanged.connect(self.checkBoxChanged)
1738
        self.ui.checkBoxTraining.stateChanged.connect(self.checkBoxChanged)
1739
        self.isAccepted = False
1740
        self.batch = batch
1741

    
1742
        appDocData = AppDocData.instance()
1743
        configs = appDocData.getAppConfigs('app', 'mode')
1744
        if configs and 1 == len(configs) and 'advanced' == configs[0].value:
1745
            pass
1746
        else:
1747
            self.ui.checkBoxTraining.setVisible(False)
1748

    
1749
        '''
1750
        if self.batch:
1751
            self.ui.lineCheckBox.setCheckState(Qt.Checked)
1752
            self.ui.lineCheckBox.setEnabled(False)
1753
            self.ui.checkBoxSymbol.setEnabled(False)
1754
            self.ui.checkBoxText.setEnabled(False)
1755
        else:
1756
            self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
1757
            self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
1758
            self.ui.checkBoxText.setCheckState(Qt.Unchecked)
1759
        '''
1760
        self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
1761
        self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
1762
        self.ui.checkBoxText.setCheckState(Qt.Unchecked)
1763

    
1764
        self.ui.progressBarBatch.setFormat('{}/{}'.format('0', str(len(self.path))))
1765

    
1766
    def checkBoxChanged(self, checkState):
1767
        '''
1768
        @brief      line and text cannot be reocognized alone
1769
        @author     euisung
1770
        @date       2019.05.14
1771
        '''
1772
        if self.ui.checkBoxTraining.isChecked():
1773
            self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
1774
            self.ui.checkBoxText.setCheckState(Qt.Unchecked)
1775
            self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
1776
            self.ui.lineCheckBox.setEnabled(False)
1777
            self.ui.checkBoxText.setEnabled(False)
1778
            self.ui.checkBoxSymbol.setEnabled(False)
1779
            self.ui.recognizeButton.setText('Extract')
1780
            return
1781
        else:
1782
            self.ui.lineCheckBox.setEnabled(True)
1783
            self.ui.checkBoxText.setEnabled(True)
1784
            self.ui.checkBoxSymbol.setEnabled(True)
1785
            self.ui.recognizeButton.setText('Recognize')
1786

    
1787
        if checkState is int(Qt.Checked):
1788
            if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked():
1789
                self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
1790
            elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked():
1791
                self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
1792
        elif checkState is int(Qt.Unchecked):
1793
            if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked():
1794
                self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
1795
            elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked():
1796
                self.ui.checkBoxText.setCheckState(Qt.Unchecked)
1797

    
1798
    '''
1799
        @brief      QListWidget Row Inserted Listener
1800
                    Whenever row inserted, scroll to bottom
1801
        @author     Jeongwoo
1802
        @date       18.04.12
1803
    '''
1804

    
1805
    def rowInserted(self, item):
1806
        self.ui.listWidget.scrollToBottom()
1807

    
1808
    def accept(self):
1809
        self.isAccepted = True
1810
        QDialog.accept(self)
1811

    
1812
    def recognizeButtonClicked(self, event):
1813
        """
1814
        @brief      start recognization
1815
        @author     humkyung
1816
        @history    humkyung 2018.10.05 clear imgSrc before recognizing
1817
        """
1818
        if self.ui.checkBoxSymbol.isChecked():  # or self.ui.checkBoxText.isChecked() or self.ui.lineCheckBox.isChecked():
1819
            appDocData = AppDocData.instance()
1820
            appDocData.imgSrc = None
1821
            area = appDocData.getArea('Drawing')
1822
            if area is None:
1823
                QMessageBox.about(self, self.tr("Notice"), self.tr('Please select drawing area.'))
1824
                return
1825

    
1826
            self.ui.recognizeButton.setEnabled(False)
1827
            self.ui.buttonBox.setEnabled(False)
1828
            self.ui.progressBar.setValue(0)
1829
            self.ui.listWidget.addItem("Initializing...")
1830
            self.startThread()
1831

    
1832
    '''
1833
        @brief  add item to list widget
1834
        @author humkyung
1835
        @date   2018.06.19
1836
    '''
1837

    
1838
    def addListItem(self, item):
1839
        self.ui.listWidget.addItem(item)
1840

    
1841
    '''
1842
        @brief  update progressbar with given value
1843
        @author humkyung
1844
        @date   2018.06.08
1845
    '''
1846

    
1847
    def updateProgress(self, maxValue, image_path):
1848
        self.ui.progressBar.setMaximum(maxValue)
1849
        self.ui.progressBar.setValue(self.ui.progressBar.value() + 1)
1850

    
1851
        if image_path is not None and os.path.isfile(image_path):
1852
            self.ui.labelImage.setPixmap(QPixmap(image_path))
1853
        elif image_path is not None:
1854
            self.ui.labelImage.setText(image_path)
1855

    
1856
    def updateBatchProgress(self, maxValue, weight):
1857
        '''
1858
            @brief  update batch progressbar
1859
            @author euisung
1860
            @date   2018.11.28
1861
        '''
1862
        self.ui.progressBarBatch.setMaximum(maxValue * 5)
1863
        value = self.ui.progressBarBatch.value() + weight
1864
        self.ui.progressBarBatch.setValue(value)        
1865
        self.ui.progressBarBatch.setFormat('{}/{}'.format(str(int(value / 5)), str(maxValue)))
1866

    
1867
    '''
1868
        @brief      display title
1869
        @author     humkyung
1870
        @date       2018.08.20
1871
    '''
1872

    
1873
    def displayTitle(self, title):
1874
        self.ui.labelTitle.setText(title)
1875

    
1876
    def startThread(self):
1877
        """
1878
        @brief  start thread
1879
        @author humkyung
1880
        @date   2018.04.??
1881
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
1882
                    2018.05.28  Jeongwoo    Add connects (self.loadRecognitionResult, recognizeLine)
1883
                    2018.05.30  Jeongwoo    Change signal name (drawDetectedItems)
1884
                    humkyung 2018.06.08 connect signal to self.updateProgress
1885
        """
1886
        import timeit
1887
        from PyQt5 import QtWidgets
1888
        from App import App
1889

    
1890
        self.ui.buttonBox.setDisabled(True)
1891

    
1892
        # 1 - create Worker and Thread inside the Form
1893
        self.obj = Worker()  # no parent!
1894
        self.obj.path = self.path
1895
        self.obj.listWidget = self.ui.listWidget
1896
        self.obj.graphicsView = self.graphicsView
1897
        self.obj.isSymbolChecked = self.ui.checkBoxSymbol.isChecked()
1898
        self.obj.isTextChecked = self.ui.checkBoxText.isChecked()
1899
        self.obj.isLineChecked = self.ui.lineCheckBox.isChecked()
1900
        self.obj.isTrainingChecked = self.ui.checkBoxTraining.isChecked()
1901
        self.obj.batch = self.batch
1902
        self.obj.createDetectedItems = self.parent.createDetectedItems
1903
        self.obj.createUnknownItems = self.parent.createUnknownItems
1904
        self.thread = QThread()  # no parent!
1905

    
1906
        # 2 - Move the Worker object to the Thread object
1907
        self.obj.moveToThread(self.thread)
1908

    
1909
        # 3 - Connect Worker Signals to the Thread slots
1910
        self.obj.finished.connect(self.thread.quit)
1911
        # self.obj.drawDetectedItems.connect(self.drawDetectedItems)
1912
        # self.obj.drawDetectedLines.connect(self.drawDetectedLines)
1913
        # self.obj.drawUnknownItems.connect(self.drawUnknownItems)
1914
        self.obj.displayMessage.connect(self.addListItem)
1915
        self.obj.updateProgress.connect(self.updateProgress)
1916
        self.obj.updateBatchProgress.connect(self.updateBatchProgress)
1917
        self.obj.displayLog.connect(App.mainWnd().addMessage)
1918
        self.obj.displayTitle.connect(self.displayTitle)
1919

    
1920
        # 4 - Connect Thread started signal to Worker operational slot method
1921
        self.thread.started.connect(self.obj.procCounter)
1922

    
1923
        # 5 - Thread finished signal will close the app if you want!
1924
        self.thread.finished.connect(self.dlgExit)
1925

    
1926
        # 6 - Start the thread
1927
        self.thread.start()
1928

    
1929
        self.tmStart = timeit.default_timer()
1930

    
1931
    '''
1932
        @brief set buttonbox's enabled flag
1933
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
1934
                    2018.06.14  Jeongwoo    Change sentence order
1935
                    2018.11.26  euisung     add drawing part
1936
                    2018.11.26  euising     move save and unknown part into executerecognition
1937
    '''
1938

    
1939
    def dlgExit(self):
1940
        import timeit
1941
        import XmlGenerator as xg
1942

    
1943
        try:
1944
            self.ui.buttonBox.setEnabled(True)
1945

    
1946
            self.ui.progressBar.setValue(self.ui.progressBar.maximum())
1947
            # if not self.batch:
1948
            #    self.parent.drawDetectedItemsToScene()
1949
        except Exception as ex:
1950
            from App import App
1951

    
1952
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1953
                                                          sys.exc_info()[-1].tb_lineno)
1954
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1955
        finally:
1956
            self.tmStop = timeit.default_timer()
1957
            seconds = self.tmStop - self.tmStart
1958
            self.ui.listWidget.addItem("\nRunning Time : {} min".format(str(round(seconds / 60, 1))) + "\n")
1959

    
1960
    '''
1961
        @history    2018.05.29  Jeongwoo    Call parent's method
1962
                    2018.05.30  Jeongwoo    Change method name
1963
                    2018.06.09  humkyung    set progressbar value to maximum
1964
                    2018.11.12  euisung     add title block properties
1965
                    2018.11.29  euisung     no more used
1966
    '''
1967

    
1968
    def drawDetectedItems(self, symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList, loop):
1969
        try:
1970
            self.ui.progressBar.setValue(self.ui.progressBar.maximum())
1971
            self.parent.drawDetectedItems(symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList)
1972
        finally:
1973
            loop.quit()
1974

    
1975
    '''
1976
        @brief      draw detected lines
1977
        @author     humkyung
1978
        @date       2018.08.23
1979
        @history    2018.11.27  euisung     no more used
1980
    '''
1981

    
1982
    def drawDetectedLines(self, lineList, loop):
1983
        try:
1984
            self.parent.drawDetectedLines(lineList, self.obj)
1985
        finally:
1986
            loop.quit()
1987

    
1988
    '''
1989
        @brief      draw detected lines
1990
        @author     euisung
1991
        @date       2018.11.27
1992
        @history    2018.11.27  euisung     no more used
1993
    '''
1994

    
1995
    def drawUnknownItems(self, path, loop):
1996
        try:
1997
            self.parent.drawUnknownItems(path)
1998
        finally:
1999
            loop.quit()
클립보드 이미지 추가 (최대 크기: 500 MB)