프로젝트

일반

사용자정보

통계
| 브랜치(Branch): | 개정판:

hytos / DTI_PID / DTI_PID / RecognitionDialog.py @ e2ae85c2

이력 | 보기 | 이력해설 | 다운로드 (97.7 KB)

1
# coding: utf-8
2
"""
3
    This is recognition dialog module
4
"""
5
import sys
6
import os
7
import threading
8
import cv2
9
import numpy as np
10
from PyQt5.QtCore import *
11
from PyQt5.QtGui import *
12
from PyQt5.QtWidgets import *
13
import Recognition_UI
14

    
15
import concurrent.futures as futures
16

    
17
from AppDocData import *
18
from SymbolSvgItem import SymbolSvgItem
19
from EngineeringTextItem import QEngineeringTextItem
20
from EngineeringUnknownItem import QEngineeringUnknownItem
21
from EngineeringErrorItem import QEngineeringErrorItem
22
from EngineeringEndBreakItem import QEngineeringEndBreakItem
23
from EngineeringLineItem import QEngineeringLineItem
24
from EngineeringTextItem import QEngineeringTextItem
25
from EngineeringLineNoTextItem import QEngineeringLineNoTextItem
26
from GraphicsBoundingBoxItem import QGraphicsBoundingBoxItem
27
from QEngineeringOPCItem import QEngineeringOPCItem
28
from LineDetector import LineDetector
29
from symbol import Symbol
30

    
31
from MainWindow import MainWindow
32

    
33
# region Symbol Image path List for test
34
targetSymbolList = []
35
# endregion
36

    
37
# region Global variables
38
searchedSymbolList = []
39
textInfoList = []
40

    
41
src = []
42

    
43
ocrCompletedSrc = []
44
afterDenoising = []
45
canvas = []
46

    
47
WHITE_LIST_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-"
48

    
49
MIN_TEXT_SIZE = 10
50

    
51
THREAD_MAX_WORKER = os.cpu_count()
52
threadLock = threading.Lock()
53

    
54
ACCEPT_OVERLAY_AREA = 20
55
# endregion
56

    
57
'''
58
    @history    2018.05.25  Jeongwoo    Add pyqtSignal(recognizeLine, loadRecognitionResult)
59
'''
60

    
61

    
62
class Worker(QObject):
63
    from PyQt5.QtCore import QThread
64
    from PyQt5.QtCore import QTranslator
65
    from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout, QListWidget
66
    from QtImageViewer import QtImageViewer
67
    import sys
68

    
69
    '''
70
        @history    2018.05.30  Jeongwoo    Remove parameter on recognizeLine signal / Change signal name (drawDetectedItems)
71
        @history    humkyung 2018.06.08 add signal for progressbar
72
        @history    euisung 2018.11.27 add signal for unknown items
73
    '''
74
    finished = pyqtSignal()
75
    intReady = pyqtSignal(int)
76
    displayTitle = pyqtSignal(str)
77
    displayMessage = pyqtSignal(QListWidgetItem)
78
    updateProgress = pyqtSignal(int, str)
79
    updateBatchProgress = pyqtSignal(int, int)
80
    displayLog = pyqtSignal(MessageType, str)
81

    
82
    def __init__(self):
83
        super(Worker, self).__init__()
84

    
85
    '''
86
        @history    2018.05.25  Jeongwoo    Add if-statements by isSymbolTextChecked and isLineChecked variable
87
                    2018.05.28  Jeongwoo    Add Parameter 'xmlPath[0]'
88
                    2018.05.30  Jeongwoo    Remove import recognizeLine and self.xmlPath and remove parameter on recognizeLine.emit() (xmlPath)
89
                                            Change signal name (drawDetectedItems)
90
    '''
91

    
92
    def procCounter(self):  # A slot takes no params
93
        try:
94
            if self.isSymbolChecked or self.isTrainingChecked:
95
                Worker.executeRecognition(self.createDetectedItems, self.path, self.listWidget, self.isLineChecked,
96
                                          self, self.batch, self.createUnknownItems)
97
        except Exception as ex:
98
            from App import App
99
            from AppDocData import MessageType
100

    
101
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
102
                                                           sys.exc_info()[-1].tb_lineno)
103
            App.mainWnd().addMessage.emit(MessageType.Error, message)
104
            self.displayLog.emit(MessageType.Error, message)
105
        except:
106
            (_type, value, traceback) = sys.exc_info()
107
            sys.excepthook(_type, value, traceback)
108
        finally:
109
            self.finished.emit()
110

    
111
    '''
112
        @brief  remove small objects from given image
113
        @author humkyung
114
        @date   2018.04.26
115
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
116
    '''
117

    
118
    @staticmethod
119
    def remove_small_objects(image):
120
        try:
121
            app_doc_data = AppDocData.instance()
122
            configs = app_doc_data.getConfigs('Small Object Size', 'Min Area')
123
            minArea = int(configs[0].value) if 1 == len(configs) else 20
124
            configs = app_doc_data.getConfigs('Small Object Size', 'Max Area')
125
            maxArea = int(configs[0].value) if 1 == len(configs) else 50
126

    
127
            # try to convert grayscale to binary
128
            image = cv2.threshold(image, 200, 255, cv2.THRESH_BINARY)[1]
129

    
130
            contours, _ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
131
            selectedContours = []
132
            for contour in contours:
133
                area = cv2.contourArea(contour)
134
                if minArea < area < maxArea:
135
                    selectedContours.append(contour)
136

    
137
            # draw contour with white color
138
            contourImage = cv2.drawContours(image, selectedContours, -1, (255, 255, 255), -1)
139
        except Exception as ex:
140
            from App import App
141

    
142
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
143
                                                           sys.exc_info()[-1].tb_lineno)
144
            App.mainWnd().addMessage.emit(MessageType.Error, message)
145

    
146
        return contourImage
147

    
148
    '''
149
        @brief  arrange line's position
150
        @author humkyung
151
        @date   2018.07.04
152
    '''
153

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

    
171
                # skip jointed symbols
172
                symbolPool = [item for item in symbols if item not in visited and item.is_connected(line)]
173
                if symbolPool:
174
                    selected = []
175
                    visited.extend(symbolPool)
176
                    while len(symbolPool) > 0:
177
                        symbol = symbolPool.pop()
178

    
179
                        rhs = [item for item in symbols if item not in visited and item.is_connected(symbol)]
180
                        if rhs:
181
                            symbolPool.extend(rhs)
182
                            visited.extend(rhs)
183
                            selected.extend(rhs)
184
                        else:
185
                            selected.append(symbol)
186

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

    
200
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
201
                                                           sys.exc_info()[-1].tb_lineno)
202
            App.mainWnd().addMessage.emit(MessageType.Error, message)
203

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

    
229
        global ocrCompletedSrc
230
        global threadLock
231
        global searchedSymbolList
232
        global textInfoList
233
        global maxProgressValue
234

    
235
        try:
236
            app_doc_data = AppDocData.instance()
237
            drawings = app_doc_data.getDrawings()
238
            project = app_doc_data.getCurrentProject()
239
            textDetector = TextDetector()
240

    
241
            # remove already existing symbol and text
242
            if not batch:
243
                listWidget.addItem('Deleting existing items...')
244
                items = [item for item in worker.graphicsView.scene.items() if
245
                         type(item) is QEngineeringUnknownItem or type(item) is QEngineeringEndBreakItem or
246
                         type(item) is QEngineeringErrorItem or type(item) is QGraphicsBoundingBoxItem]
247

    
248
                if worker.isSymbolChecked:
249
                    items.extend(
250
                        [item for item in worker.graphicsView.scene.items() if issubclass(type(item), SymbolSvgItem)])
251
                if worker.isTextChecked:
252
                    items.extend([item for item in worker.graphicsView.scene.items() if
253
                                  issubclass(type(item), QEngineeringTextItem)])
254
                for item in items:
255
                    item.transfer.onRemoved.emit(item)
256
            # up to here
257

    
258
            srcList = path
259

    
260
            Worker.initTargetSymbolDataList()
261

    
262
            for mainRes in srcList:
263
                ocrCompletedSrc = []
264
                searchedSymbolList = []
265
                textInfoList = []
266

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

    
273
                app_doc_data.clearItemList(True)
274

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

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

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

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

    
304
                area = app_doc_data.getArea('Drawing')
305
                if area is not None:
306
                    # copy image
307
                    area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height),
308
                               round(area.x):round(area.x + area.width)]
309

    
310
                    _, area.mask = cv2.threshold(area.img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
311

    
312
                    # area.contours, area.hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
313
                    area.not_img = cv2.bitwise_not(area.img)
314
                    area.contours, area.hierachy = cv2.findContours(area.not_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
315

    
316
                maxProgressValue = 0
317
                start_time = timeit.default_timer()
318
                listWidget.addItem(f"Start recognition{str(start_time)} : {mainRes}")
319
                threadLock.acquire()
320

    
321
                worker.updateBatchProgress.emit(len(srcList), 1)
322

    
323
                if worker.isSymbolChecked:
324
                    # calculate total count of symbol
325
                    for targetItem in targetSymbolList:
326
                        if type(targetItem) is list:
327
                            maxProgressValue += len(targetItem)
328
                        else:
329
                            maxProgressValue += 1
330
                    # up to here
331
                    maxProgressValue = maxProgressValue * 2
332
                threadLock.release()
333

    
334
                if worker.isSymbolChecked:
335
                    worker.displayTitle.emit(worker.tr('Detecting symbols...'))
336

    
337
                    # detect equipments
338
                    pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER)
339
                    for symbol in targetSymbolList[0]:
340
                        pool.submit(Worker.detectEquipmentOnPid, mainRes, symbol, listWidget, worker)
341
                    pool.shutdown(wait=True)
342
                    # up to here
343

    
344
                    with futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as pool:
345
                        future_symbol = {pool.submit(Worker.detectSymbolsOnPid, mainRes, symbol, listWidget, worker):
346
                                           symbol for symbol in targetSymbolList[2]}
347
                        futures.wait(future_symbol)
348

    
349
                    if worker.isTrainingChecked:
350
                        import uuid
351
                        worker.displayTitle.emit(worker.tr('Generating Data...'))
352
                        listWidget.addItem('Generating Data...')
353

    
354
                        for item in searchedSymbolList:
355
                            path = os.path.join(project.getTempPath(), 'Tile', item.getBaseSymbol())
356
                            if not os.path.exists(path): os.makedirs(path)
357

    
358
                            _img = app_doc_data.imgSrc[round(item.getSp()[1]):round(item.getSp()[1] + item.getHeight()),
359
                                   round(item.getSp()[0]):round(item.getSp()[0] + item.getWidth())]
360
                            cv2.imwrite(os.path.join(path, str(uuid.uuid4()) + '.png'), _img)
361
                        worker.updateBatchProgress.emit(len(srcList), 4)
362
                        continue
363

    
364
                    pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER)
365
                    searchedTextSymList = []
366
                    for sym in searchedSymbolList:
367
                        pool.submit(Worker.remove_detected_symbol_image, sym, app_doc_data.imgSrc)
368
                    pool.shutdown(wait=True)
369
                else:
370
                    '''
371
                    import math
372

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

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

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

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

    
428
                    # for sym in searchedSymbolList:
429
                    #    pool.submit(Worker.remove_detected_symbol_image, sym, appDocData.imgSrc)
430
                    # pool.shutdown(wait = True)
431

    
432
                worker.updateBatchProgress.emit(len(srcList), 1)
433

    
434
                area = app_doc_data.getArea('Drawing')
435
                if area is not None:
436
                    # copy area image
437
                    area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height),
438
                               round(area.x):round(area.x + area.width)]
439

    
440
                offset = (area.x, area.y) if area is not None else (0, 0)
441

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

    
455
                    worker.displayTitle.emit(worker.tr('Detecting texts...'))
456
                    textDetector.recognizeText(ocr_image, offset, textAreas, searchedSymbolList, worker,
457
                                               listWidget, maxProgressValue)
458
                    textInfoList = textDetector.textInfoList.copy() if textDetector.textInfoList is not None else None
459
                    otherTextInfoList = textDetector.otherTextInfoList.copy() if textDetector.otherTextInfoList is not None else None
460
                    titleBlockTextInfoList = textDetector.titleBlockTextInfoList.copy() if textDetector.titleBlockTextInfoList is not None else None
461

    
462
                    app_doc_data.activeDrawing.width, app_doc_data.activeDrawing.height = app_doc_data.imgSrc.shape[
463
                                                                                          ::-1]
464

    
465
                    app_doc_data.imgName = os.path.splitext(os.path.basename(mainRes))[0]
466
                else:
467
                    import math
468
                    from TextInfo import TextInfo
469

    
470
                    textItems = [item for item in worker.graphicsView.scene.items() if
471
                                 issubclass(type(item), QEngineeringTextItem)]
472
                    app_doc_data.texts.extend(textItems)
473
                    app_doc_data.allItems.extend(textItems)
474

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

    
481
                    for textItem in textItems:
482
                        textInfoList.append(
483
                            TextInfo(textItem.text(), textItem.loc[0], textItem.loc[1], textItem.size[0],
484
                                     textItem.size[1], \
485
                                     round(math.degrees(textItem.angle))))
486

    
487
                        textItem.owner = None
488
                        worker.graphicsView.scene.removeItem(textItem)
489

    
490
                worker.updateBatchProgress.emit(len(srcList), 2)
491

    
492
                # check symbol validate
493
                valid_sym = []
494
                for index in reversed(range(len(searchedTextSymList))):
495
                    sym_xmin = searchedTextSymList[index].getSp()[0]
496
                    sym_ymin = searchedTextSymList[index].getSp()[1]
497
                    sym_xmax = searchedTextSymList[index].getSp()[0] + searchedTextSymList[index].getWidth()
498
                    sym_ymax = searchedTextSymList[index].getSp()[1] + searchedTextSymList[index].getHeight()
499
                    valid_count = searchedTextSymList[index].getHasInstrumentLabel()
500
                    valid = 0
501
                    for text_info in textInfoList:
502
                        info_center = text_info.center
503
                        if info_center[0] > sym_xmin and info_center[0] < sym_xmax and info_center[1] > sym_ymin and \
504
                                info_center[1] < sym_ymax:
505
                            valid += 1
506
                            if valid >= valid_count:
507
                                valid_sym.append(searchedTextSymList[index])
508
                                break
509
                    if valid < valid_count:
510
                        searchedSymbolList.pop(searchedSymbolList.index(searchedTextSymList[index]))
511

    
512
                pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER)
513

    
514
                for sym in valid_sym:
515
                    pool.submit(Worker.remove_detected_symbol_image, sym, app_doc_data.imgSrc)
516
                pool.shutdown(wait=True)
517
                # up to here
518

    
519
                # remove text from image
520
                Worker.drawFoundSymbolsOnCanvas(mainRes, textInfoList, listWidget)
521
                textDetector.removeTextFromImage(app_doc_data.imgSrc, offset)
522
                if not worker.isTextChecked:
523
                    textInfoList.clear()
524
                # up to here
525

    
526
                removedSymbolImgPath = os.path.join(project.getTempPath(), os.path.basename(mainRes))
527
                cv2.imwrite(removedSymbolImgPath, app_doc_data.imgSrc)
528

    
529
                area = AppDocData.instance().getArea('Drawing')
530
                if area is not None:
531
                    area.img = app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height),
532
                               round(area.x + 1):round(area.x + area.width)]
533
                cv2.imwrite(os.path.join(project.getTempPath(), "RECT_" + os.path.basename(mainRes)),
534
                            app_doc_data.imgSrc)
535

    
536
                listWidget.addItem("Recognized symbol count : " + str(len(searchedSymbolList)))
537

    
538
                # get difference between original and recognized image
539
                foundFilePath = os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(mainRes))
540
                Worker.getDifference(mainRes, foundFilePath)
541
                # up to here
542

    
543
                listWidget.addItem(worker.tr('Creating detected infos...'))
544
                worker.displayTitle.emit(worker.tr('Creating detected infos...'))
545
                createDetectedItems(searchedSymbolList, textInfoList,
546
                                    otherTextInfoList if otherTextInfoList is not None else [],
547
                                    titleBlockTextInfoList if titleBlockTextInfoList is not None else [])
548

    
549
                if isLineChecked:
550
                    Worker.recognizeLine(mainRes, listWidget, worker.graphicsView, worker, batch)
551
                else:
552
                    lineItems = [item for item in worker.graphicsView.scene.items()
553
                                 if issubclass(type(item), QEngineeringLineItem)]
554
                    app_doc_data.lines.extend(lineItems)
555
                    app_doc_data.allItems.extend(lineItems)
556

    
557
                    for lineItem in lineItems:
558
                        lineItem.owner = None
559
                        for conn in lineItem.connectors:
560
                            conn.connectedItem = None
561
                        worker.graphicsView.scene.removeItem(lineItem)
562

    
563
                # connect symbol to symbol
564
                try:
565
                    configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line')
566
                    toler = int(configs[0].value) if configs else 20
567
                    for symbol in app_doc_data.symbols:
568
                        matches = [it for it in app_doc_data.symbols if
569
                                   it is not symbol and symbol.is_connectable(it, toler=toler)]
570
                        for match in matches:
571
                            symbol.connect_if_possible(match)
572
                except Exception as ex:
573
                    message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
574
                                                                   sys.exc_info()[-1].tb_lineno)
575
                    worker.displayLog.emit(MessageType.Error, message)
576
                # up to here
577

    
578
                listWidget.addItem('Connecting lines')
579
                area = app_doc_data.getArea('Drawing')
580
                detector = LineDetector(area.img)
581
                symbols = app_doc_data.symbols
582
                configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line')
583
                toler = int(configs[0].value) if configs else 20
584
                if app_doc_data.lines:
585
                    # connect line to symbol
586
                    try:
587
                        for line in app_doc_data.lines:
588
                            matches = [symbol for symbol in symbols if symbol.is_connectable(line, toler=toler)]
589
                            for symbol in matches:
590
                                detector.connectLineToSymbol(line, symbol, toler=toler)
591
                    except Exception as ex:
592
                        message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[
593
                            -1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
594
                        worker.displayLog.emit(MessageType.Error, message)
595
                    # up to here
596

    
597
                    # connect line to line
598
                    try:
599
                        for line in app_doc_data.lines:
600
                            matches = [it for it in app_doc_data.lines if
601
                                       (it is not line) and (not line.isParallel(it))]
602

    
603
                            for match in matches:
604
                                detector.connectLineToLine(match, line, toler)
605

    
606
                        # change line type using symbol connection type(info)
607
                        for sym in symbols:
608
                            if sym.conn_type:
609
                                for index in range(len(sym.conn_type)):
610
                                    item = sym.connectors[index].connectedItem
611
                                    if item and type(item) is QEngineeringLineItem:
612
                                        Worker.changeConnectedLineType(item, sym.conn_type[index])
613
                    except Exception as ex:
614
                        message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[
615
                            -1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
616
                        worker.displayLog.emit(MessageType.Error, message)
617
                    # up to here
618

    
619
                createUnknownItems(mainRes)
620

    
621
                worker.updateBatchProgress.emit(len(srcList), 1)
622
        except Exception as ex:
623
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
624
                                                           sys.exc_info()[-1].tb_lineno)
625
            worker.displayLog.emit(MessageType.Error, message)
626
        finally:
627
            pass
628

    
629
    @staticmethod
630
    def changeConnectedLineType(line, lineType):
631
        line.lineType = lineType
632
        if type(line.connectors[0].connectedItem) is QEngineeringLineItem and \
633
                (line.connectors[0].connectedItem.connectors[0].connectedItem is line or
634
                 line.connectors[0].connectedItem.connectors[1].connectedItem is line) and \
635
                line.connectors[0].connectedItem.lineType is not lineType:
636
            Worker.changeConnectedLineType(line.connectors[0].connectedItem, lineType)
637
        if type(line.connectors[1].connectedItem) is QEngineeringLineItem and \
638
                (line.connectors[1].connectedItem.connectors[0].connectedItem is line or
639
                 line.connectors[1].connectedItem.connectors[1].connectedItem is line) and \
640
                line.connectors[1].connectedItem.lineType is not lineType:
641
            Worker.changeConnectedLineType(line.connectors[1].connectedItem, lineType)
642

    
643
    '''
644
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
645
                    2018.05.28  Jeongwoo    Add xmlPath Parameter and append LineInfo into xml
646
                    2018.05.29  Jeongwoo    Change method to add item
647
                    2018.05.30  Jeongwoo    Remove parameter (xmlPath)
648
                    humkyung 2018.06.11 add drawing path to parameter and write recognized lines to image
649
                    humkyung 2018.07.04 call arrangeLinePosition after creating line
650
    '''
651

    
652
    @staticmethod
653
    def recognizeLine(path, listWidget, graphicsView, worker, batch):
654
        from shapely.geometry import Point, LineString
655
        from SymbolSvgItem import SymbolSvgItem
656
        from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
657
        from EngineeringLineNoTextItem import QEngineeringLineNoTextItem
658
        from EngineeringTextItem import QEngineeringTextItem
659
        from EngineeringLineItem import QEngineeringLineItem
660
        from LineDetector import LineDetector
661

    
662
        try:
663
            listWidget.addItem('Starting line recognition')
664
            worker.displayTitle.emit(worker.tr('Detecting lines...'))
665

    
666
            app_doc_data = AppDocData.instance()
667
            project = app_doc_data.getCurrentProject()
668
            # remove already existing line and flow arrow item
669
            if not batch:
670
                items = [item for item in worker.graphicsView.scene.items() if (type(item) is QEngineeringLineItem)]
671
                for item in items:
672
                    item.transfer.onRemoved.emit(item)
673
            # up to here
674

    
675
            # detect line
676
            connectedLines = []
677

    
678
            area = app_doc_data.getArea('Drawing')
679
            area.img = worker.remove_small_objects(area.img)
680
            detector = LineDetector(area.img)
681

    
682
            symbols = []
683
            for item in app_doc_data.symbols:
684
                if issubclass(type(item), SymbolSvgItem):
685
                    symbols.append(item)
686
                    res = detector.detectConnectedLine(item, round(area.x), round(area.y))
687
                    if res is not None:
688
                        connectedLines.extend(res)
689

    
690
            # line detection without symbol connection point info
691
            configs = app_doc_data.getConfigs('Line', 'Diagonal')
692
            if configs and int(configs[0].value) is 1:
693
                remainLines = detector.detectLineWithoutSymbol()
694
                windowSize = app_doc_data.getSlidingWindowSize()
695
                thickness = int(windowSize[1])
696
                for line in remainLines:
697
                    line.append(thickness)
698
                connectedLines.extend(remainLines)
699

    
700
            configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line')
701
            toler = int(configs[0].value) if configs else 20
702
            detector.mergeLines(connectedLines, toler=toler)
703

    
704
            for pts in connectedLines:
705
                line = QEngineeringLineItem(
706
                    vertices=[(area.x + param[0], area.y + param[1]) for param in pts[:-1]], thickness=pts[2])
707
                line.area = 'Drawing'
708

    
709
                app_doc_data.lines.append(line)
710
                app_doc_data.allItems.append(line)
711
        except Exception as ex:
712
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
713
                                                           sys.exc_info()[-1].tb_lineno)
714
            worker.displayLog.emit(MessageType.Error, message)
715
        finally:
716
            listWidget.addItem('Finishing line recognition')
717
            worker.finished.emit()
718

    
719
    '''
720
        @history    2018.04.24  Jeongwoo    Add isExceptDetect Field
721
                    2018.05.09  Jeongwoo    Add targetSymbolList clear
722
                    humkyung 2018.07.07 store symbols to list as like [equipments],[nozzles],[symbols]
723
    '''
724

    
725
    @staticmethod
726
    def initTargetSymbolDataList():
727
        global targetSymbolList
728

    
729
        targetSymbolList.clear()
730
        appDocData = AppDocData.instance()
731
        symbolList = appDocData.getTargetSymbolList()
732
        equipments = [item for item in symbolList if appDocData.getSymbolCategoryByType(item.getType()) == 'Equipment']
733
        nozzles = [item for item in symbolList if item.getType() == 'Nozzles']
734
        # [[equipments],[nozzles],[symbols]]
735
        targetSymbolList.append(equipments)
736
        targetSymbolList.append(nozzles)
737
        targetSymbolList.append([item for item in symbolList if item not in equipments and item not in nozzles])
738

    
739
        return targetSymbolList
740

    
741
    '''
742
        @brief  detect equipment
743
        @author humkyung
744
        @date   2018.07.07
745
    '''
746

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

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

    
773
    @staticmethod
774
    def detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker):
775
        import copy
776
        global ocrCompletedSrc
777
        global threadLock
778
        global maxProgressValue
779

    
780
        try:
781
            forTraining = worker.isTrainingChecked
782
            symbolName = targetSymbol.getName()
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
            hasInstrumentLabel = targetSymbol.getHasInstrumentLabel()
798

    
799
            # check if symbol file is target or not
800
            if isExceptDetect == 1:
801
                item = QListWidgetItem('{} file is not target'.format(symbolName))
802
                item.setBackground(QColor('green'))
803
                listWidget.addItem(item)
804
                return
805

    
806
            foundSymbolCount = 0
807

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

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

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

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

    
844
            # when text is located inside of symbol
845
            if area is not None and hasInstrumentLabel:
846
                # remove objects smaller than symbol
847
                selected_contours = []
848
                contours, hierarchy = cv2.findContours(area.mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
849
                index = 0
850
                for contour in contours:
851
                    child, parent = hierarchy[0][index][2], hierarchy[0][index][3]
852
                    [x, y, w, h] = cv2.boundingRect(contour)
853
                    if w * h < sow * soh * 0.5:
854
                        contour_area = cv2.contourArea(contour, True)
855
                        if contour_area > 0:
856
                            selected_contours.append(contour)
857
                    index += 1
858
                # up to here
859

    
860
                # draw contour with white color
861
                roiItem = cv2.drawContours(roiItem, selected_contours, -1, (255, 255, 255), -1)
862

    
863
            # try to recognize symbol twice(first one is normal, second on is flipped)
864
            steps = [False, True] if detectFlip else [False]
865
            for flipped in steps:
866
                if flipped:
867
                    symGray = symGrayOri
868
                    symGray = cv2.flip(symGray, 1)
869

    
870
                symbolRotatedAngle = 0
871
                for rc in range(symbolRotateCount + 1):  # Rotation count를 사용자 기준으로 받아서 1을 더한 후 사용
872
                    sw, sh = symGray.shape[::-1]
873
                    roiw = (roiItemEp[0] - roiItemSp[0])
874
                    roih = (roiItemEp[1] - roiItemSp[1])
875

    
876
                    # Case : Bigger Symbol than Split ROI
877
                    if roiw < sw or roih < sh:
878
                        symGray = cv2.rotate(symGray, cv2.ROTATE_90_COUNTERCLOCKWISE)
879
                        symbolRotatedAngle = symbolRotatedAngle + 90
880

    
881
                        if baseSymbol is not None and additionalSymbol is not None:
882
                            additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol)
883
                        continue
884

    
885
                    # get Rotated Original Point
886
                    originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint,
887
                                                                      symbolRotatedAngle, sw, sh, sow, soh, flipped)
888
                    connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw,
889
                                                                          sh, sow, soh, flipped)
890

    
891
                    # For OPC
892
                    drawing_area = app_doc_data.getArea('Drawing')
893
                    if drawing_area is not None and (symbolType == "Piping OPC\'s" or symbolType == "Instrument OPC\'s"):
894
                        worker.detectOPCOnPid(drawing_area, symGray)
895

    
896
                    # Template Matching
897
                    tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED)
898
                    loc = np.where(tmRes >= symbolThreshold)
899

    
900
                    for pt in zip(*loc[::-1]):
901
                        mpCount = 0  # Match Point Count
902

    
903
                        roi = roiItem[pt[1]:pt[1] + sh, pt[0]:pt[0] + sw]
904

    
905
                        if symbolMinMatchCount > 0:
906
                            mpCount = Worker.getMatchPointCount(roi, symGray)
907
                            if not (mpCount >= symbolMinMatchCount):
908
                                continue
909

    
910
                        searchedItemSp = (roiItemSp[0] + pt[0] + round(offsetDrawingArea[0]),
911
                                          roiItemSp[1] + pt[1] + round(offsetDrawingArea[1]))
912
                        # print(searchedItemSp)
913

    
914
                        overlapArea = 0
915
                        symbolIndex = -1
916
                        for i in range(len(searchedSymbolList) - 1, -1, -1):
917
                            area = Worker.contains(searchedSymbolList[i], searchedItemSp, sw, sh)
918
                            if area > ACCEPT_OVERLAY_AREA:
919
                                # if area > overlapArea:
920
                                #    overlapArea = area
921
                                #    symbolIndex = i
922
                                overlapArea = area
923
                                symbolIndex = i
924
                                break
925
                                """
926
                                categories = [appDocData.isEquipmentType(symbolType), appDocData.isEquipmentType(searchedSymbolList[i].getType())]
927
                                if categories[0] == categories[1]:
928
                                    symbolIndex = i
929
                                    break
930
                                """
931

    
932
                        hitRate = tmRes[pt[1], pt[0]]
933

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

    
1033
                    # Rotate Symbol
1034
                    symGray = cv2.rotate(symGray, cv2.ROTATE_90_COUNTERCLOCKWISE)
1035
                    symbolRotatedAngle = symbolRotatedAngle + 90
1036

    
1037
                    if additionalSymbol is not None:
1038
                        additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol)
1039

    
1040
            threadLock.acquire()
1041
            listWidget.addItem('Found Symbol   : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str(
1042
                foundSymbolCount) + ')')
1043
            threadLock.release()
1044

    
1045
            """
1046
            if area is not None and hasInstrumentLabel:
1047
                # restore objects smaller than symbol
1048
                roiItem = cv2.drawContours(roiItem, outside_contours, -1, (0, 0, 0), -1)
1049
                roiItem = cv2.drawContours(roiItem, hole_contours, -1, (255, 255, 255), -1)
1050
                # up to here
1051
                cv2.imwrite('c:\\Temp\\contour2.png', roiItem)
1052
            """
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
    @staticmethod
1065
    def detectOPCOnPid(area, symGray):
1066
        results = []
1067

    
1068
        # try:
1069
        #     symbol = cv2.copyMakeBorder(symGray, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=255)
1070
        #     not_symbol = cv2.bitwise_not(symbol)
1071
        #     symbol_contours, symbol_hierachy = cv2.findContours(not_symbol, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
1072
        #     if symbol_hierachy[0][0][0] != -1:
1073
        #         return results
1074
        #
1075
        #     contour_count = len(symbol_contours)
1076
        #     for i in range(1, contour_count):
1077
        #         symbol_area = cv2.contourArea(symbol_contours[i])
1078
        #
1079
        #         for contour in area.contours:
1080
        #             area_area = cv2.contourArea(contour)
1081
        #             if area_area * 1.2 >= symbol_area >= area_area * 0.8:
1082
        #                 I1 = cv2.matchShapes(symbol_contours[i], contour, 1, 0)
1083
        #                 I2 = cv2.matchShapes(symbol_contours[i], contour, 2, 0)
1084
        #                 I3 = cv2.matchShapes(symbol_contours[i], contour, 3, 0)
1085
        #                 if I1 < 1 and I2 < 1 and I3 < 0.1:
1086
        #                     rect = cv2.boundingRect(contour)
1087
        #                     results.append([0.8, [rect[0],rect[1]]])
1088
        #         break
1089
        # except Exception as ex:
1090
        #     message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1091
        #                                                    sys.exc_info()[-1].tb_lineno)
1092
        #     a = 5
1093

    
1094
        return results
1095

    
1096
    '''
1097
        @brief      detect nozzle
1098
        @author     humkyung
1099
        @date       2018.07.07
1100
        @history    humkyhung 2018.07.17 pass equpment as parameter instead of image
1101
    '''
1102
    @staticmethod
1103
    def detectNozzleOnPid(equipment, nozzle, listWidget, worker):
1104
        global src
1105
        global threadLock
1106
        global maxProgressValue
1107

    
1108
        try:
1109
            symbolName = nozzle.getName()
1110
            symbolType = nozzle.getType()
1111
            symbolPath = nozzle.getPath()
1112
            symbolThreshold = nozzle.getThreshold()
1113
            symbolMinMatchCount = nozzle.getMinMatchCount()
1114
            isDetectOnOrigin = nozzle.getIsDetectOnOrigin()
1115
            symbolRotateCount = nozzle.getRotationCount()
1116
            symbolOcrOption = nozzle.getOcrOption()
1117
            isContainChild = nozzle.getIsContainChild()
1118
            symbolOriginalPoint = nozzle.getOriginalPoint()
1119
            symbolConnectionPoint = nozzle.getConnectionPoint()
1120
            baseSymbol = nozzle.getBaseSymbol()
1121
            additionalSymbol = nozzle.getAdditionalSymbol()
1122
            isExceptDetect = nozzle.getIsExceptDetect()
1123

    
1124
            foundSymbolCount = 0
1125

    
1126
            # check if symbol file exists
1127
            if not os.path.isfile(symbolPath):
1128
                item = QListWidgetItem('{} file not found'.format(os.path.split(os.path.basename(symbolPath))[0]))
1129
                item.setBackground(QColor('red'))
1130
                listWidget.addItem(item)
1131
                return
1132
            # up to here
1133

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

    
1137
            # get image of equipment with offset of nozzle size
1138
            appDocData = AppDocData.instance()
1139
            pt = equipment.getSp()
1140
            nozzleSize = max(sow, soh)
1141
            sx = round(pt[0]) - nozzleSize
1142
            sy = round(pt[1]) - nozzleSize
1143
            ex = round(pt[0] + equipment.getWidth()) + nozzleSize
1144
            ey = round(pt[1] + equipment.getHeight()) + nozzleSize
1145
            offset = (sx, sy)
1146
            eqpSize = (pt[0], pt[1], equipment.getWidth(), equipment.getHeight())
1147
            img = appDocData.imgSrc[sy:ey, sx:ex]
1148
            srcWidth, srcHeight = img.shape[::-1]
1149
            # up to here
1150

    
1151
            roiItemSp = (0, 0)
1152
            roiItemEp = (srcWidth, srcHeight)
1153
            roiItem = img
1154

    
1155
            symbolAngle = 0
1156
            for rc in range(symbolRotateCount + 1):  # Rotation Count를 사용자 기준으로 받아서 1을 더한 후 사용
1157
                sw, sh = symGray.shape[::-1]
1158
                roiw = (roiItemEp[0] - roiItemSp[0])
1159
                roih = (roiItemEp[1] - roiItemSp[1])
1160

    
1161
                # get Rotated Original Point
1162
                originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolAngle,
1163
                                                                  sw, sh, sow, soh)
1164
                connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolAngle, sw, sh, sow,
1165
                                                                      soh)
1166
                dx = connectionPoint[0][0] - originalPoint[0]
1167
                dy = connectionPoint[0][1] - originalPoint[1]
1168

    
1169
                # Template Matching
1170
                tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED)
1171
                loc = np.where(tmRes >= symbolThreshold)
1172

    
1173
                for pt in zip(*loc[::-1]):
1174
                    mpCount = 0  # Match Point Count
1175
                    symbolIndex = -1
1176

    
1177
                    roi = roiItem[pt[1]:pt[1] + sh, pt[0]:pt[0] + sw]
1178

    
1179
                    if symbolMinMatchCount > 0:
1180
                        mpCount = Worker.getMatchPointCount(roi, symGray)
1181
                        if not (mpCount >= symbolMinMatchCount):
1182
                            continue
1183

    
1184
                    mid = (offset[0] + pt[0] + (originalPoint[0] + connectionPoint[0][0]) * 0.5,
1185
                           offset[1] + pt[1] + (originalPoint[1] + connectionPoint[0][1]) * 0.5)
1186
                    searchedItemSp = (roiItemSp[0] + pt[0] + offset[0], roiItemSp[1] + pt[1] + offset[1])
1187
                    # check searched nozzle location
1188
                    if abs(dx) > abs(dy):
1189
                        if dx > 0:
1190
                            if mid[0] < eqpSize[0] + eqpSize[2] * 0.5: continue
1191
                        else:
1192
                            if mid[0] > eqpSize[0] + eqpSize[2] * 0.5: continue
1193
                    else:
1194
                        if dy > 0:
1195
                            if mid[1] < eqpSize[1] + eqpSize[3] * 0.5: continue
1196
                        else:
1197
                            if mid[1] > eqpSize[1] + eqpSize[3] * 0.5: continue
1198
                    # up to here
1199

    
1200
                    overlapArea = 0
1201
                    nozzles = [symbol for symbol in searchedSymbolList if symbol.getType() == 'Nozzles']
1202
                    for i in range(len(nozzles)):
1203
                        _pt = nozzles[i].getSp()
1204
                        rect = QRectF(_pt[0], _pt[1], nozzles[i].getWidth(), nozzles[i].getHeight())
1205
                        _rect = QRectF(searchedItemSp[0], searchedItemSp[1], sw, sh)
1206
                        if rect.intersects(_rect):
1207
                            intersect = rect.intersected(_rect)
1208
                            overlapArea = intersect.width() * intersect.height()
1209
                            if overlapArea > ACCEPT_OVERLAY_AREA:
1210
                                symbolIndex = i
1211
                                break
1212

    
1213
                    hitRate = tmRes[pt[1], pt[0]]
1214

    
1215
                    ## 겹치는 영역이 기준값보다 작을 경우
1216
                    if overlapArea <= ACCEPT_OVERLAY_AREA:
1217
                        threadLock.acquire()
1218
                        foundSymbolCount = foundSymbolCount + 1
1219
                        searched = Worker.addSearchedSymbol(symbolName, symbolType
1220
                                                            , searchedItemSp, sw, sh, symbolThreshold,
1221
                                                            symbolMinMatchCount, hitRate, symbolAngle
1222
                                                            , isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
1223
                                                            isContainChild
1224
                                                            , originalPoint, connectionPoint, baseSymbol,
1225
                                                            additionalSymbol, isExceptDetect)
1226
                        searched.owner = equipment
1227
                        threadLock.release()
1228
                    ## 겹치는 영역이 기준값보다 클 경우
1229
                    else:
1230
                        if symbolIndex != -1 and symbolIndex < len(nozzles):
1231
                            searchedSymbol = nozzles[symbolIndex]
1232
                            ## 현재 심볼과 검출된 심볼이 같을 경우 Match Point가 더 높은 정보로 교체
1233
                            if symbolName == searchedSymbol.getName():
1234
                                symbolHitRate = searchedSymbol.getHitRate()
1235
                                if symbolHitRate < hitRate:
1236
                                    threadLock.acquire()
1237
                                    nozzles[symbolIndex] = symbol.Symbol(symbolName, symbolType,
1238
                                                                         searchedItemSp, sw, sh, symbolThreshold,
1239
                                                                         symbolMinMatchCount, hitRate, symbolAngle,
1240
                                                                         isDetectOnOrigin, symbolRotateCount,
1241
                                                                         symbolOcrOption, isContainChild,
1242
                                                                         ','.join(str(x) for x in originalPoint),
1243
                                                                         '/'.join(
1244
                                                                             '{},{},{},{}'.format(param[0], param[1],
1245
                                                                                                  param[2], param[3])
1246
                                                                             for param in connectionPoint),
1247
                                                                         baseSymbol, additionalSymbol, isExceptDetect)
1248
                                    threadLock.release()
1249
                            ## 현재 심볼과 검출된 심볼이 같지 않을 경우 (포함)
1250
                            elif appDocData.isEquipmentType(searchedSymbol.getType()):
1251
                                threadLock.acquire()
1252
                                foundSymbolCount = foundSymbolCount + 1
1253
                                searched = Worker.addSearchedSymbol(symbolName, symbolType
1254
                                                                    , searchedItemSp, sw, sh, symbolThreshold, hitRate,
1255
                                                                    hitRate, symbolAngle
1256
                                                                    , isDetectOnOrigin, symbolRotateCount,
1257
                                                                    symbolOcrOption, isContainChild
1258
                                                                    , originalPoint, connectionPoint, baseSymbol,
1259
                                                                    additionalSymbol, isExceptDetect)
1260
                                searched.owner = equipment
1261
                                threadLock.release()
1262

    
1263
                ## Rotate Symbol
1264
                symGray = cv2.rotate(symGray, cv2.ROTATE_90_COUNTERCLOCKWISE)
1265
                symbolAngle = symbolAngle + 90
1266

    
1267
                if additionalSymbol is not None:
1268
                    additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol)
1269

    
1270
            threadLock.acquire()
1271
            listWidget.addItem('Found Symbol   : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str(
1272
                foundSymbolCount) + ')')
1273
            threadLock.release()
1274

    
1275
            worker.updateProgress.emit(maxProgressValue, symbolPath)
1276

    
1277
            return [symbol for symbol in searchedSymbolList if symbol.getName() == symbolName]
1278
        except Exception as ex:
1279
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1280
                                                           sys.exc_info()[-1].tb_lineno)
1281
            worker.displayLog(MessageType.Error, message)
1282

    
1283
        return []
1284

    
1285
    # Convert into Grayscale image
1286
    @staticmethod
1287
    def cvtGrayImage(img):
1288
        return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
1289

    
1290
    '''
1291
        @history    2018.06.12  Jeongwoo    Type changed (int → float)
1292
                    humkyung 2018.07.07 change return type as like [x,y]
1293
    '''
1294

    
1295
    @staticmethod
1296
    def getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolRotatedAngle, rotateSymbolWidth,
1297
                                   rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight, flipped=False):
1298
        res = []
1299

    
1300
        if additionalSymbol is None and symbolOriginalPoint is None:
1301
            res.append(rotateSymbolWidth // 2)
1302
            res.append(rotateSymbolHeight // 2)
1303
        else:
1304
            if flipped:
1305
                opx = originalSymbolWidth - float(symbolOriginalPoint.split(',')[0])
1306
                opy = float(symbolOriginalPoint.split(',')[1])
1307
            else:
1308
                opx = float(symbolOriginalPoint.split(',')[0])
1309
                opy = float(symbolOriginalPoint.split(',')[1])
1310

    
1311
            rPt = Worker.getCoordOnRotatedImage(symbolRotatedAngle, ('AUTO', opx, opy, '0'), originalSymbolWidth,
1312
                                                originalSymbolHeight)
1313

    
1314
            res.append(rPt[1])
1315
            res.append(rPt[2])
1316

    
1317
        return res
1318

    
1319
    '''
1320
        @history    2018.06.12  Jeongwoo    Type changed (int → float)
1321
                    humkyung 2018.07.07 change return type as like [[x,y],...]
1322
                    humkyung 2019.01.04 get symbol index
1323
    '''
1324

    
1325
    @staticmethod
1326
    def getCalculatedConnectionPoint(symbolConnectionPointStr, symbolRotatedAngle, rotateSymbolWidth,
1327
                                     rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight, flipped=0):
1328
        res = []
1329

    
1330
        if symbolConnectionPointStr is not None and symbolConnectionPointStr != '':
1331
            splitConnectionPointStr = symbolConnectionPointStr.split("/")
1332
            for strConnPt in splitConnectionPointStr:
1333
                tokens = strConnPt.split(',')
1334

    
1335
                direction = 'AUTO'
1336
                symbol_idx = '0'
1337
                if flipped:
1338
                    converted = {'AUTO': 'AUTO', 'LEFT': 'RIGHT', 'RIGHT': 'LEFT', 'UP': 'UP', 'DOWN': 'DOWN'}
1339

    
1340
                    if len(tokens) == 2:
1341
                        cpx = originalSymbolWidth - float(tokens[0])
1342
                        cpy = float(tokens[1])
1343
                    elif len(tokens) == 3:
1344
                        direction = converted[tokens[0]]
1345
                        cpx = originalSymbolWidth - float(tokens[1])
1346
                        cpy = float(tokens[2])
1347
                    elif len(tokens) >= 4:
1348
                        direction = converted[tokens[0]]
1349
                        cpx = originalSymbolWidth - float(tokens[1])
1350
                        cpy = float(tokens[2])
1351
                        symbol_idx = tokens[3]
1352
                else:
1353
                    if len(tokens) == 2:
1354
                        cpx = float(tokens[0])
1355
                        cpy = float(tokens[1])
1356
                    elif len(tokens) == 3:
1357
                        direction = tokens[0]
1358
                        cpx = float(tokens[1])
1359
                        cpy = float(tokens[2])
1360
                    elif len(tokens) >= 4:
1361
                        direction = tokens[0]
1362
                        cpx = float(tokens[1])
1363
                        cpy = float(tokens[2])
1364
                        symbol_idx = tokens[3]
1365

    
1366
                res.append(Worker.getCoordOnRotatedImage(symbolRotatedAngle, (direction, cpx, cpy, symbol_idx),
1367
                                                         originalSymbolWidth, originalSymbolHeight))
1368

    
1369
        return res
1370

    
1371
    '''
1372
        @brief      rotate (x,y) by given angle
1373
        @author     Jeongwoo
1374
        @date       2018.??.??
1375
        @history    humkyung 2018.04.13 fixed code when angle is 90 or 270    
1376
                    Jeongwoo 2018.04.27 Change calculation method with QTransform
1377
                    humkyung 2018.09.01 calculate rotated direction
1378
    '''
1379

    
1380
    @staticmethod
1381
    def getCoordOnRotatedImage(angle, connPt, originImageWidth, originImageHeight):
1382
        rx = None
1383
        ry = None
1384

    
1385
        # calculate rotated direction
1386
        direction = connPt[0]
1387
        if direction == 'LEFT':
1388
            direction = 'DOWN' if angle == 90 else 'RIGHT' if angle == 180 else 'UP' if angle == 270 else direction
1389
        elif direction == 'RIGHT':
1390
            direction = 'UP' if angle == 90 else 'LEFT' if angle == 180 else 'DOWN' if angle == 270 else direction
1391
        elif direction == 'UP':
1392
            direction = 'LEFT' if angle == 90 else 'DOWN' if angle == 180 else 'RIGHT' if angle == 270 else direction
1393
        elif direction == 'DOWN':
1394
            direction = 'RIGHT' if angle == 90 else 'UP' if angle == 180 else 'LEFT' if angle == 270 else direction
1395
        # up to here
1396

    
1397
        transform = QTransform()
1398
        if angle == 90 or angle == 270:
1399
            transform.translate(originImageHeight * 0.5, originImageWidth * 0.5)
1400
        elif angle == 0 or angle == 180:
1401
            transform.translate(originImageWidth * 0.5, originImageHeight * 0.5)
1402
        transform.rotate(-abs(angle))
1403
        transform.translate(-originImageWidth * 0.5, -originImageHeight * 0.5)
1404
        point = QPoint(connPt[1], connPt[2])
1405
        point = transform.map(point)
1406
        rx = point.x()
1407
        ry = point.y()
1408

    
1409
        symbol_idx = connPt[3]
1410

    
1411
        return (direction, rx, ry, symbol_idx)
1412

    
1413
    '''
1414
        @brief      Add symbols
1415
        @author     jwkim
1416
        @date
1417
        @history    Change parameter (mpCount → hitRate)
1418
                    Yecheol 2018.07.04 Delete Symbol Id 
1419
    '''
1420

    
1421
    @staticmethod
1422
    def addSearchedSymbol(sName, sType
1423
                          , sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle
1424
                          , isDetectOnOrigin, rotateCount, ocrOption, isContainChild
1425
                          , originalPoint, connectionPoint, baseSymbol, additionalSymbol, isExceptDetect, detectFlip
1426
                          , hasInstrumentLabel):
1427
        global searchedSymbolList
1428

    
1429
        newSym = None
1430
        try:
1431
            newSym = symbol.Symbol(sName, sType
1432
                                   , sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle,
1433
                                   isDetectOnOrigin, rotateCount, ocrOption, isContainChild,
1434
                                   ','.join(str(x) for x in originalPoint),
1435
                                   '/'.join('{},{},{},{}'.format(param[0], param[1], param[2], param[3]) for param in
1436
                                            connectionPoint),
1437
                                   baseSymbol, additionalSymbol, isExceptDetect, detectFlip=1 if detectFlip else 0,
1438
                                   hasInstrumentLabel=hasInstrumentLabel)
1439

    
1440
            searchedSymbolList.append(newSym)
1441
        except Exception as ex:
1442
            from App import App
1443

    
1444
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1445
                                                           sys.exc_info()[-1].tb_lineno)
1446
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1447

    
1448
        return newSym
1449

    
1450
    '''
1451
        @brief      Check object contains pt
1452
        @param      obj is item in searchedSymbolList
1453
    '''
1454

    
1455
    @staticmethod
1456
    def contains(obj, pt, tw, th):
1457
        sp = obj.getSp()
1458
        width = obj.getWidth()
1459
        height = obj.getHeight()
1460

    
1461
        if sp[0] > pt[0] + tw:
1462
            return 0
1463
        if sp[0] + width < pt[0]:
1464
            return 0
1465
        if sp[1] > pt[1] + th:
1466
            return 0
1467
        if sp[1] + height < pt[1]:
1468
            return 0
1469

    
1470
        # shared area
1471
        x = max(sp[0], pt[0])
1472
        y = max(sp[1], pt[1])
1473
        w = min(sp[0] + width, pt[0] + tw) - x
1474
        h = min(sp[1] + height, pt[1] + th) - y
1475

    
1476
        return float((w * h)) / float((tw * th)) * 100
1477

    
1478
    # Calculate count of keypoint match result
1479
    @staticmethod
1480
    def getMatchPointCount(src, cmp):
1481
        matchCount = 0
1482

    
1483
        try:
1484
            orb = cv2.ORB_create(1000, 2.0, 2, 1)
1485

    
1486
            kp1, des1 = orb.detectAndCompute(src, None)
1487
            kp2, des2 = orb.detectAndCompute(cmp, None)
1488

    
1489
            FLANN_INDEX_LSH = 6
1490
            # table_number      : The number of hash tables use
1491
            # key_size          : The length of the key in the hash tables
1492
            # multi_probe_level : Number of levels to use in multi-probe (0 for standard LSH)
1493
            #                     It controls how neighboring buckets are searched
1494
            #                     Recommended value is 2
1495
            # checks            : specifies the maximum leafs to visit when searching for neighbours.
1496
            # LSH : Locality-Sensitive Hashing
1497
            # ref : https://www.cs.ubc.ca/research/flann/uploads/FLANN/flann_manual-1.8.4.pdf
1498
            index_params = dict(algorithm=FLANN_INDEX_LSH, table_number=20, key_size=10, multi_probe_level=4)
1499
            search_params = dict(checks=100)
1500

    
1501
            flann = cv2.FlannBasedMatcher(index_params, search_params)
1502

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

    
1506
            count = 0
1507
            # ratio test as per Lowe's paper
1508
            for i in range(len(matches)):
1509
                if len(matches[i]) == 2:
1510
                    m = matches[i][0]
1511
                    n = matches[i][1]
1512
                    if m.distance < 0.85 * n.distance:
1513
                        count = count + 1
1514

    
1515
            matchCount = count
1516
        except Exception as ex:
1517
            from App import App
1518

    
1519
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1520
                                                           sys.exc_info()[-1].tb_lineno)
1521
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1522

    
1523
        return matchCount
1524

    
1525
    '''
1526
        @brief      Remake rotated child symbol info
1527
    '''
1528

    
1529
    @staticmethod
1530
    def getRotatedChildInfo(additionalSymbol):
1531
        tempChildInfo = ""
1532
        if additionalSymbol:
1533
            childList = additionalSymbol.split("/")
1534
            for index in range(len(childList)):
1535
                child = childList[index]
1536
                direction = Worker.convertDirectionCodeToValue(child.split(",")[0])
1537
                childName = child.split(",")[1]
1538
                direction = (direction - 1) if direction > 0 else 3
1539
                if index != 0:
1540
                    tempChildInfo = tempChildInfo + "/"
1541
                tempChildInfo = tempChildInfo + Worker.convertValueToDirectionCode(direction) + "," + childName
1542
        return tempChildInfo
1543

    
1544
    '''
1545
        @brief   detect symbols on PID
1546
        @history humkyung 2018.06.08 add parameteres for signal
1547
    '''
1548

    
1549
    @staticmethod
1550
    def detectSymbolsOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal):
1551
        if type(targetSymbols) is list:
1552
            for detailTarget in targetSymbols:
1553
                Worker.detectSymbolOnPid(mainRes, detailTarget, listWidget, updateProgressSignal)
1554
        else:
1555
            Worker.detectSymbolOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal)
1556

    
1557
    @staticmethod
1558
    def convertDirectionCodeToValue(directionCode):
1559
        if directionCode == "UP":
1560
            return 0
1561
        elif directionCode == "RIGHT":
1562
            return 1
1563
        elif directionCode == "DOWN":
1564
            return 2
1565
        elif directionCode == "LEFT":
1566
            return 3
1567
        else:
1568
            return -1
1569

    
1570
    @staticmethod
1571
    def convertValueToDirectionCode(value):
1572
        if value == 0:
1573
            return "UP"
1574
        elif value == 1:
1575
            return "RIGHT"
1576
        elif value == 2:
1577
            return "DOWN"
1578
        elif value == 3:
1579
            return "LEFT"
1580
        else:
1581
            return "NONE"
1582

    
1583
    '''
1584
        @brief  draw found symbols and texts
1585
        @author Jeongwoo
1586
    '''
1587

    
1588
    @staticmethod
1589
    def drawFoundSymbolsOnCanvas(drawingPath, textInfos, listWidget):
1590
        global src
1591
        global ocrCompletedSrc
1592
        global canvas
1593

    
1594
        appDocData = AppDocData.instance()
1595
        canvas = np.zeros(appDocData.imgSrc.shape, np.uint8)
1596
        canvas[::] = 255
1597

    
1598
        try:
1599
            appDocData = AppDocData.instance()
1600
            project = appDocData.getCurrentProject()
1601

    
1602
            for symbol in searchedSymbolList:
1603
                Worker.drawFoundSymbols(symbol, listWidget)
1604

    
1605
            for text in textInfos:
1606
                left = text.getX()
1607
                top = text.getY()
1608
                right = text.getX() + text.getW()
1609
                bottom = text.getY() + text.getH()
1610

    
1611
                canvas[top:bottom, left:right] = appDocData.imgSrc[top:bottom, left:right]
1612

    
1613
            cv2.imwrite(os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(drawingPath)), canvas)
1614
        except Exception as ex:
1615
            from App import App
1616

    
1617
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1618
                                                           sys.exc_info()[-1].tb_lineno)
1619
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1620

    
1621
    '''
1622
        @history    2018.04.27  Jeongwoo    Remove Tesseract Log on listWidget
1623
                    2018.05.04  Jeongwoo    Change method to OCR with tesseract_ocr_module.py
1624
                    2018.05.09  Jeongwoo    Add global variable textInfoList, Remove text in symbol and Add tesseract result text
1625
                    2018.05.10  Jeongwoo    Remove not used if-statement
1626
                    2018.06.19  Jeongwoo    When detect text in symbol, use getTextAreaInfo() and Tesseract
1627
                    2018.06.21  Jeongwoo    Add if-statement for way to detect text by Type A
1628
    '''
1629

    
1630
    @staticmethod
1631
    def drawFoundSymbols(symbol, listWidget):
1632
        global src
1633
        global canvas
1634
        global WHITE_LIST_CHARS
1635
        global searchedSymbolList
1636
        global textInfoList
1637

    
1638
        # symbolId = symbol.getId()
1639
        symbolPath = symbol.getPath()
1640
        symbolSp = symbol.getSp()
1641
        symbolRotatedAngle = symbol.getRotatedAngle()
1642

    
1643
        symImg = cv2.cvtColor(cv2.imread(symbolPath, 1), cv2.COLOR_BGR2GRAY)
1644
        if symbol.getDetectFlip() is 1:
1645
            symImg = cv2.flip(symImg, 1)
1646
        for i in range(symbolRotatedAngle // 90):
1647
            symImg = cv2.rotate(symImg, cv2.ROTATE_90_COUNTERCLOCKWISE)
1648

    
1649
        w, h = symImg.shape[::-1]
1650
        canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w] = cv2.bitwise_and(
1651
            canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w], symImg)
1652

    
1653
    @staticmethod
1654
    def remove_detected_symbol_image(sym, imgSrc):
1655
        """remove detected symbol image from drawing image"""
1656
        global threadLock
1657

    
1658
        path = sym.getPath()
1659
        sp = sym.getSp()
1660
        sw = sym.getWidth()
1661
        sh = sym.getHeight()
1662
        angle = sym.getRotatedAngle()
1663
        # get symbol image
1664
        symImg = cv2.imread(path)
1665
        symImg = cv2.threshold(Worker.cvtGrayImage(symImg), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
1666
        if sym.getDetectFlip() is 1:
1667
            symImg = cv2.flip(symImg, 1)
1668

    
1669
        for i in range(angle // 90):
1670
            symImg = cv2.rotate(symImg, cv2.ROTATE_90_COUNTERCLOCKWISE)
1671
        # up to here
1672

    
1673
        threadLock.acquire()
1674
        temp = imgSrc[sp[1]:sp[1] + sh, sp[0]:sp[0] + sw]
1675
        symImg = cv2.erode(symImg, np.ones((3, 3), np.uint8))
1676
        mask = cv2.bitwise_or(temp, symImg)
1677
        imgXOR = cv2.bitwise_xor(temp, mask)
1678
        imgSrc[sp[1]:sp[1] + sh, sp[0]:sp[0] + sw] = cv2.bitwise_not(imgXOR)
1679
        threadLock.release()
1680

    
1681
    '''
1682
        @brief  get difference between given original and recognized image
1683
        @author humkyung
1684
        @date   2018.06.11
1685
    '''
1686

    
1687
    @staticmethod
1688
    def getDifference(orgImagePath, recImagePath):
1689
        import re
1690

    
1691
        global ocrCompletedSrc
1692
        global textInfoList
1693

    
1694
        try:
1695
            appDocData = AppDocData.instance()
1696
            if os.path.isfile(orgImagePath) and os.path.isfile(recImagePath):
1697
                imgOriginal = \
1698
                    cv2.threshold(Worker.cvtGrayImage(cv2.imread(orgImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1]
1699

    
1700
                configs = appDocData.getConfigs('Filter', 'DilateSize')
1701
                if 1 == len(configs) and int(configs[0].value) is not 0:
1702
                    size = int(configs[0].value)
1703
                    kernel = np.ones((size, size), np.uint8)
1704
                    imgOriginal = cv2.erode(imgOriginal, kernel, iterations=1)
1705

    
1706
                # remove not drawing area
1707
                configs = appDocData.getConfigs('{} Equipment Desc Area'.format(appDocData.imgName))
1708
                for config in configs:
1709
                    found = re.findall('\\d+', config.value)
1710
                    if len(found) == 4:
1711
                        cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
1712
                                      (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
1713

    
1714
                configs = appDocData.getConfigs('{} Typical Area'.format(appDocData.imgName))
1715
                for config in configs:
1716
                    found = re.findall('\\d+', config.value)
1717
                    if len(found) == 4:
1718
                        cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
1719
                                      (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
1720

    
1721
                noteArea = appDocData.getArea('Note')
1722
                if noteArea is not None:
1723
                    noteArea.img = appDocData.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height),
1724
                                   round(noteArea.x):round(noteArea.x + noteArea.width)].copy()
1725
                    cv2.rectangle(imgOriginal, (round(noteArea.x), round(noteArea.y)),
1726
                                  (round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1)
1727
                # up to here
1728

    
1729
                imgRecognized = \
1730
                    cv2.threshold(Worker.cvtGrayImage(cv2.imread(recImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1]
1731

    
1732
                imgDiff = np.ones(imgOriginal.shape, np.uint8) * 255
1733

    
1734
                area = AppDocData.instance().getArea('Drawing')
1735
                if area is not None:
1736
                    x = round(area.x)
1737
                    y = round(area.y)
1738
                    width = round(area.width)
1739
                    height = round(area.height)
1740
                    imgNotOper = cv2.bitwise_not(imgRecognized[y:y + height, x:x + width])
1741
                    imgDiff[y:y + height, x:x + width] = cv2.bitwise_xor(imgOriginal[y:y + height, x:x + width],
1742
                                                                         imgNotOper)
1743

    
1744
                # remove noise
1745
                imgDiff = cv2.dilate(imgDiff, np.ones((2, 2), np.uint8))
1746

    
1747
                appDocData = AppDocData.instance()
1748
                project = appDocData.getCurrentProject()
1749
                cv2.imwrite(os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(orgImagePath)), imgDiff)
1750
        except Exception as ex:
1751
            from App import App
1752

    
1753
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1754
                                                           sys.exc_info()[-1].tb_lineno)
1755
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1756

    
1757

    
1758
'''
1759
    @history    2018.05.25  Jeongwoo    Add pyqtSignal(svgItemClicked, itemRemoved)
1760
'''
1761

    
1762

    
1763
class QRecognitionDialog(QDialog):
1764
    svgItemClicked = pyqtSignal(SymbolSvgItem)
1765
    itemRemoved = pyqtSignal(QGraphicsItem)
1766
    unBlockEvent = pyqtSignal()
1767

    
1768
    '''
1769
        @history    2018.05.25  Jeongwoo    Add parameter and initialize / Connect recognizeButton signal and slot
1770
                    2018.05.29  Jeongwoo    Chnage parameter(graphicsView → parent) and Get graphicsView from parent
1771
    '''
1772

    
1773
    def __init__(self, parent, path, batch):  # Parent is MainWindow
1774
        from AppDocData import AppDocData
1775

    
1776
        QDialog.__init__(self, parent)
1777

    
1778
        self.parent = parent
1779
        self.graphicsView = parent.graphicsView
1780
        self.path = path
1781
        self.xmlPath = None
1782
        self.ui = Recognition_UI.Ui_Recognition()
1783
        self.ui.setupUi(self)
1784

    
1785
        self.ui.buttonBox.setEnabled(True)
1786
        self.ui.listWidget.model().rowsInserted.connect(self.rowInserted)
1787
        self.ui.recognizeButton.clicked.connect(self.recognizeButtonClicked)
1788
        self.ui.lineCheckBox.stateChanged.connect(self.checkBoxChanged)
1789
        self.ui.checkBoxSymbol.stateChanged.connect(self.checkBoxChanged)
1790
        self.ui.checkBoxText.stateChanged.connect(self.checkBoxChanged)
1791
        self.ui.checkBoxTraining.stateChanged.connect(self.checkBoxChanged)
1792
        self.isAccepted = False
1793
        self.batch = batch
1794

    
1795
        appDocData = AppDocData.instance()
1796
        configs = appDocData.getAppConfigs('app', 'mode')
1797
        if configs and 1 == len(configs) and 'advanced' == configs[0].value:
1798
            pass
1799
        else:
1800
            self.ui.checkBoxTraining.setVisible(False)
1801

    
1802
        '''
1803
        if self.batch:
1804
            self.ui.lineCheckBox.setCheckState(Qt.Checked)
1805
            self.ui.lineCheckBox.setEnabled(False)
1806
            self.ui.checkBoxSymbol.setEnabled(False)
1807
            self.ui.checkBoxText.setEnabled(False)
1808
        else:
1809
            self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
1810
            self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
1811
            self.ui.checkBoxText.setCheckState(Qt.Unchecked)
1812
        '''
1813
        self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
1814
        self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
1815
        self.ui.checkBoxText.setCheckState(Qt.Unchecked)
1816

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

    
1819
    def checkBoxChanged(self, checkState):
1820
        '''
1821
        @brief      line and text cannot be reocognized alone
1822
        @author     euisung
1823
        @date       2019.05.14
1824
        '''
1825
        if self.ui.checkBoxTraining.isChecked():
1826
            self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
1827
            self.ui.checkBoxText.setCheckState(Qt.Unchecked)
1828
            self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
1829
            self.ui.lineCheckBox.setEnabled(False)
1830
            self.ui.checkBoxText.setEnabled(False)
1831
            self.ui.checkBoxSymbol.setEnabled(False)
1832
            self.ui.recognizeButton.setText('Extract')
1833
            return
1834
        else:
1835
            self.ui.lineCheckBox.setEnabled(True)
1836
            self.ui.checkBoxText.setEnabled(True)
1837
            self.ui.checkBoxSymbol.setEnabled(True)
1838
            self.ui.recognizeButton.setText('Recognize')
1839

    
1840
        if checkState is int(Qt.Checked):
1841
            if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked():
1842
                self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
1843
            elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked():
1844
                self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
1845
        elif checkState is int(Qt.Unchecked):
1846
            if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked():
1847
                self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
1848
            elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked():
1849
                self.ui.checkBoxText.setCheckState(Qt.Unchecked)
1850

    
1851
    '''
1852
        @brief      QListWidget Row Inserted Listener
1853
                    Whenever row inserted, scroll to bottom
1854
        @author     Jeongwoo
1855
        @date       18.04.12
1856
    '''
1857

    
1858
    def rowInserted(self, item):
1859
        self.ui.listWidget.scrollToBottom()
1860

    
1861
    def accept(self):
1862
        self.isAccepted = True
1863
        QDialog.accept(self)
1864

    
1865
    def recognizeButtonClicked(self, event):
1866
        """
1867
        @brief      start recognization
1868
        @author     humkyung
1869
        @history    humkyung 2018.10.05 clear imgSrc before recognizing
1870
        """
1871
        if self.ui.checkBoxSymbol.isChecked():  # or self.ui.checkBoxText.isChecked() or self.ui.lineCheckBox.isChecked():
1872
            appDocData = AppDocData.instance()
1873
            appDocData.imgSrc = None
1874
            area = appDocData.getArea('Drawing')
1875
            if area is None:
1876
                QMessageBox.about(self, self.tr("Notice"), self.tr('Please select drawing area.'))
1877
                return
1878

    
1879
            self.ui.recognizeButton.setEnabled(False)
1880
            self.ui.buttonBox.setEnabled(False)
1881
            self.ui.progressBar.setValue(0)
1882
            self.ui.listWidget.addItem("Initializing...")
1883
            self.startThread()
1884

    
1885
    '''
1886
        @brief  add item to list widget
1887
        @author humkyung
1888
        @date   2018.06.19
1889
    '''
1890

    
1891
    def addListItem(self, item):
1892
        self.ui.listWidget.addItem(item)
1893

    
1894
    '''
1895
        @brief  update progressbar with given value
1896
        @author humkyung
1897
        @date   2018.06.08
1898
    '''
1899

    
1900
    def updateProgress(self, maxValue, image_path):
1901
        self.ui.progressBar.setMaximum(maxValue)
1902
        self.ui.progressBar.setValue(self.ui.progressBar.value() + 1)
1903

    
1904
        if image_path is not None and os.path.isfile(image_path):
1905
            self.ui.labelImage.setPixmap(QPixmap(image_path))
1906
        elif image_path is not None:
1907
            self.ui.labelImage.setText(image_path)
1908

    
1909
    def updateBatchProgress(self, maxValue, weight):
1910
        '''
1911
            @brief  update batch progressbar
1912
            @author euisung
1913
            @date   2018.11.28
1914
        '''
1915
        self.ui.progressBarBatch.setMaximum(maxValue * 5)
1916
        value = self.ui.progressBarBatch.value() + weight
1917
        self.ui.progressBarBatch.setValue(value)
1918
        self.ui.progressBarBatch.setFormat('{}/{}'.format(str(int(value / 5)), str(maxValue)))
1919

    
1920
    '''
1921
        @brief      display title
1922
        @author     humkyung
1923
        @date       2018.08.20
1924
    '''
1925

    
1926
    def displayTitle(self, title):
1927
        self.ui.labelTitle.setText(title)
1928

    
1929
    def startThread(self):
1930
        """
1931
        @brief  start thread
1932
        @author humkyung
1933
        @date   2018.04.??
1934
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
1935
                    2018.05.28  Jeongwoo    Add connects (self.loadRecognitionResult, recognizeLine)
1936
                    2018.05.30  Jeongwoo    Change signal name (drawDetectedItems)
1937
                    humkyung 2018.06.08 connect signal to self.updateProgress
1938
        """
1939
        import timeit
1940
        from PyQt5 import QtWidgets
1941
        from App import App
1942

    
1943
        self.ui.buttonBox.setDisabled(True)
1944

    
1945
        # 1 - create Worker and Thread inside the Form
1946
        self.obj = Worker()  # no parent!
1947
        self.obj.path = self.path
1948
        self.obj.listWidget = self.ui.listWidget
1949
        self.obj.graphicsView = self.graphicsView
1950
        self.obj.isSymbolChecked = self.ui.checkBoxSymbol.isChecked()
1951
        self.obj.isTextChecked = self.ui.checkBoxText.isChecked()
1952
        self.obj.isLineChecked = self.ui.lineCheckBox.isChecked()
1953
        self.obj.isTrainingChecked = self.ui.checkBoxTraining.isChecked()
1954
        self.obj.batch = self.batch
1955
        self.obj.createDetectedItems = self.parent.createDetectedItems
1956
        self.obj.createUnknownItems = self.parent.createUnknownItems
1957
        self.thread = QThread()  # no parent!
1958

    
1959
        # 2 - Move the Worker object to the Thread object
1960
        self.obj.moveToThread(self.thread)
1961

    
1962
        # 3 - Connect Worker Signals to the Thread slots
1963
        self.obj.finished.connect(self.thread.quit)
1964
        # self.obj.drawDetectedItems.connect(self.drawDetectedItems)
1965
        # self.obj.drawDetectedLines.connect(self.drawDetectedLines)
1966
        # self.obj.drawUnknownItems.connect(self.drawUnknownItems)
1967
        self.obj.displayMessage.connect(self.addListItem)
1968
        self.obj.updateProgress.connect(self.updateProgress)
1969
        self.obj.updateBatchProgress.connect(self.updateBatchProgress)
1970
        self.obj.displayLog.connect(App.mainWnd().addMessage)
1971
        self.obj.displayTitle.connect(self.displayTitle)
1972

    
1973
        # 4 - Connect Thread started signal to Worker operational slot method
1974
        self.thread.started.connect(self.obj.procCounter)
1975

    
1976
        # 5 - Thread finished signal will close the app if you want!
1977
        self.thread.finished.connect(self.dlgExit)
1978

    
1979
        # 6 - Start the thread
1980
        self.thread.start()
1981

    
1982
        self.tmStart = timeit.default_timer()
1983

    
1984
    '''
1985
        @brief set buttonbox's enabled flag
1986
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
1987
                    2018.06.14  Jeongwoo    Change sentence order
1988
                    2018.11.26  euisung     add drawing part
1989
                    2018.11.26  euising     move save and unknown part into executerecognition
1990
    '''
1991

    
1992
    def dlgExit(self):
1993
        import timeit
1994
        import XmlGenerator as xg
1995

    
1996
        try:
1997
            self.ui.buttonBox.setEnabled(True)
1998

    
1999
            self.ui.progressBar.setValue(self.ui.progressBar.maximum())
2000
            # if not self.batch:
2001
            #    self.parent.drawDetectedItemsToScene()
2002
        except Exception as ex:
2003
            from App import App
2004

    
2005
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
2006
                                                           sys.exc_info()[-1].tb_lineno)
2007
            App.mainWnd().addMessage.emit(MessageType.Error, message)
2008
        finally:
2009
            self.tmStop = timeit.default_timer()
2010
            seconds = self.tmStop - self.tmStart
2011
            self.ui.listWidget.addItem("\nRunning Time : {} min".format(str(round(seconds / 60, 1))) + "\n")
2012

    
2013
    '''
2014
        @history    2018.05.29  Jeongwoo    Call parent's method
2015
                    2018.05.30  Jeongwoo    Change method name
2016
                    2018.06.09  humkyung    set progressbar value to maximum
2017
                    2018.11.12  euisung     add title block properties
2018
                    2018.11.29  euisung     no more used
2019
    '''
2020

    
2021
    def drawDetectedItems(self, symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList, loop):
2022
        try:
2023
            self.ui.progressBar.setValue(self.ui.progressBar.maximum())
2024
            self.parent.drawDetectedItems(symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList)
2025
        finally:
2026
            loop.quit()
2027

    
2028
    '''
2029
        @brief      draw detected lines
2030
        @author     humkyung
2031
        @date       2018.08.23
2032
        @history    2018.11.27  euisung     no more used
2033
    '''
2034

    
2035
    def drawDetectedLines(self, lineList, loop):
2036
        try:
2037
            self.parent.drawDetectedLines(lineList, self.obj)
2038
        finally:
2039
            loop.quit()
2040

    
2041
    '''
2042
        @brief      draw detected lines
2043
        @author     euisung
2044
        @date       2018.11.27
2045
        @history    2018.11.27  euisung     no more used
2046
    '''
2047

    
2048
    def drawUnknownItems(self, path, loop):
2049
        try:
2050
            self.parent.drawUnknownItems(path)
2051
        finally:
2052
            loop.quit()
클립보드 이미지 추가 (최대 크기: 500 MB)