프로젝트

일반

사용자정보

통계
| 개정판:

hytos / DTI_PID / DTI_PID / RecognitionDialog.py @ eda9685a

이력 | 보기 | 이력해설 | 다운로드 (93.5 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 LineDetector import LineDetector
28
from symbol import Symbol
29

    
30
from MainWindow import MainWindow
31

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

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

    
40
src = []
41

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

    
46
WHITE_LIST_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-"
47

    
48
MIN_TEXT_SIZE = 10
49

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

    
53
ACCEPT_OVERLAY_AREA = 20
54
# endregion
55

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

    
60

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

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

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

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

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

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

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

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

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

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

    
140
        return contourImage
141

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

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

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

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

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

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

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

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

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

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

    
252
            # up to here
253

    
254
            srcList = path
255

    
256
            Worker.initTargetSymbolDataList()
257

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

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

    
269
                app_doc_data.clearItemList(True)
270

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

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

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

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

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

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

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

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

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

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

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

    
340
                    if worker.isTrainingChecked:
341
                        import uuid
342
                        worker.displayTitle.emit(worker.tr('Generating Data...'))
343

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

    
348
                            _img = app_doc_data.imgSrc[round(item.getSp()[1]):round(item.getSp()[1] + item.getHeight()),
349
                                   round(item.getSp()[0]):round(item.getSp()[0] + item.getWidth())]
350
                            cv2.imwrite(os.path.join(path, str(uuid.uuid4()) + '.png'), _img)
351
                        continue
352

    
353
                    pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER)
354

    
355
                    for sym in searchedSymbolList:
356
                        pool.submit(Worker.removeDetectedSymbol, sym, app_doc_data.imgSrc)
357
                    pool.shutdown(wait=True)
358
                    # print(searchedSymbolList[0].getOcrOption())
359
                else:
360
                    # if symbol is excluded from recognition, keep it
361
                    '''
362
                    import math
363

364
                    symbolItems = [item for item in worker.graphicsView.scene.items() if issubclass(type(item), SymbolSvgItem)]
365
                    
366
                    for symbolItem in symbolItems:
367
                        symbolName = symbolItem.name
368
                        symbolType = symbolItem.type
369
                        searchedItemSp = [int(symbolItem.loc[0]), int(symbolItem.loc[1])]
370
                        sw = symbolItem.size[0] # check later
371
                        sh = symbolItem.size[1] # check later
372

373
                        symbolThreshold = 50 # not necessary
374
                        symbolMinMatchCount = 0 # not necessary
375
                        hitRate = 80 # not necessary
376
                        
377
                        allowed_error = 0.0001
378
                        #degree 0
379
                        if abs(symbolItem.angle - 0) <= allowed_error:
380
                            symbolRotatedAngle = 0
381
                        #degree 90
382
                        elif abs(symbolItem.angle - 1.57) <= allowed_error:
383
                            symbolRotatedAngle = 90
384
                        #degree 180
385
                        elif abs(symbolItem.angle - 3.14) <= allowed_error:
386
                            symbolRotatedAngle = 180
387
                        #degree 270
388
                        elif abs(symbolItem.angle - 4.71) <= allowed_error:
389
                            symbolRotatedAngle = 270
390
                        else:
391
                            symbolRotatedAngle = math.pi / 180 * symbolItem.angle
392

393
                        isDetectOnOrigin = 80 # not necessary
394
                        symbolRotateCount = 0 # not necessary
395
                        symbolOcrOption = 0 # not necessary
396
                        isContainChild = 0 # not necessary
397
                        originalPoint = [symbolItem.origin[0] - symbolItem.loc[0], symbolItem.origin[1] - symbolItem.loc[1]]
398
                        symbolConnectionPoint = []
399
                        baseSymbol = str(symbolItem.parentSymbol)
400
                        additionalSymbol = str(symbolItem.childSymbol)
401
                        isExceptDetect = 0 # not necessary
402
                        detectFlip = symbolItem.flip
403

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

    
419
                    # for sym in searchedSymbolList:
420
                    #    pool.submit(Worker.removeDetectedSymbol, sym, appDocData.imgSrc)
421
                    # pool.shutdown(wait = True)
422

    
423
                worker.updateBatchProgress.emit(len(srcList), 1)
424

    
425
                area = app_doc_data.getArea('Drawing')
426
                if area is not None:
427
                    area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height),
428
                               round(area.x):round(area.x + area.width)]
429

    
430
                offset = (area.x, area.y) if area is not None else (0, 0)
431

    
432
                otherTextInfoList = None
433
                titleBlockTextInfoList = None
434
                if worker.isTextChecked:
435
                    textAreas = textDetector.detectTextAreas(area.img if area is not None else app_doc_data.imgSrc,
436
                                                             offset)
437
                    if maxProgressValue < 2 * len(textAreas):
438
                        for _ in range(len(textAreas) - int(maxProgressValue * 0.5)):
439
                            worker.updateProgress.emit(2 * len(textAreas), None)
440
                        maxProgressValue = 2 * len(textAreas)
441
                    else:
442
                        maxProgressValue = int(maxProgressValue * 0.5) + len(textAreas)
443

    
444
                    worker.displayTitle.emit(worker.tr('Detecting texts...'))
445
                    textDetector.recognizeText(app_doc_data.imgSrc, offset, textAreas, searchedSymbolList, worker,
446
                                               listWidget, maxProgressValue)
447
                    textInfoList = textDetector.textInfoList.copy() if textDetector.textInfoList is not None else None
448
                    otherTextInfoList = textDetector.otherTextInfoList.copy() if textDetector.otherTextInfoList is not None else None
449
                    titleBlockTextInfoList = textDetector.titleBlockTextInfoList.copy() if textDetector.titleBlockTextInfoList is not None else None
450

    
451
                    app_doc_data.activeDrawing.width, app_doc_data.activeDrawing.height = app_doc_data.imgSrc.shape[::-1]
452
                    Worker.drawFoundSymbolsOnCanvas(mainRes, textInfoList, listWidget)
453

    
454
                    # remove text from image
455
                    textDetector.removeTextFromImage(app_doc_data.imgSrc, offset)
456
                    # up to here
457

    
458
                    app_doc_data.imgName = os.path.splitext(os.path.basename(mainRes))[0]
459
                else:
460
                    textItems = [item for item in worker.graphicsView.scene.items() if
461
                                 issubclass(type(item), QEngineeringTextItem)]
462
                    app_doc_data.texts.extend(textItems)
463
                    app_doc_data.allItems.extend(textItems)
464

    
465
                    lineNoTextItems = [item for item in textItems if type(item) is QEngineeringLineNoTextItem]
466
                    for lineNoTextItem in lineNoTextItems:
467
                        lineNoTextItem.set_property('Freeze', False)
468
                        # lineNoTextItem.freeze_item.update_freeze(lineNoTextItem.prop('Freeze'))
469
                        lineNoTextItem.explode()
470

    
471
                    for textItem in textItems:
472
                        textItem.owner = None
473
                        worker.graphicsView.scene.removeItem(textItem)
474

    
475
                worker.updateBatchProgress.emit(len(srcList), 2)
476

    
477
                removedSymbolImgPath = os.path.join(project.getTempPath(), os.path.basename(mainRes))
478
                cv2.imwrite(removedSymbolImgPath, app_doc_data.imgSrc)
479

    
480
                area = AppDocData.instance().getArea('Drawing')
481
                if area is not None:
482
                    area.img = app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height),
483
                               round(area.x + 1):round(area.x + area.width)]
484
                cv2.imwrite(os.path.join(project.getTempPath(), "RECT_" + os.path.basename(mainRes)), app_doc_data.imgSrc)
485

    
486
                listWidget.addItem("Recognized symbol count : " + str(len(searchedSymbolList)))
487

    
488
                # get difference between original and recognized image
489
                foundFilePath = os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(mainRes))
490
                Worker.getDifference(mainRes, foundFilePath)
491
                # up to here
492

    
493
                listWidget.addItem(worker.tr('Creating detected infos...'))
494
                worker.displayTitle.emit(worker.tr('Creating detected infos...'))
495
                createDetectedItems(searchedSymbolList, textInfoList,
496
                                    otherTextInfoList if otherTextInfoList is not None else [],
497
                                    titleBlockTextInfoList if titleBlockTextInfoList is not None else [])
498

    
499
                if isLineChecked:
500
                    Worker.recognizeLine(mainRes, listWidget, worker.graphicsView, worker, batch)
501
                else:
502
                    lineItems = [item for item in worker.graphicsView.scene.items() if
503
                                 issubclass(type(item), QEngineeringLineItem)]
504
                    app_doc_data.lines.extend(lineItems)
505
                    app_doc_data.allItems.extend(lineItems)
506

    
507
                    for lineItem in lineItems:
508
                        lineItem.owner = None
509
                        for conn in lineItem.connectors:
510
                            conn.connectedItem = None
511
                        worker.graphicsView.scene.removeItem(lineItem)
512

    
513
                    listWidget.addItem('Connecting lines')
514
                    area = app_doc_data.getArea('Drawing')
515
                    detector = LineDetector(area.img)
516
                    symbols = app_doc_data.symbols
517
                    configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line')
518
                    toler = int(configs[0].value) if configs else 20
519
                    if app_doc_data.lines:
520
                        # connect line to line
521
                        configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line')
522
                        toler = int(configs[0].value) if configs else 20
523
                        try:
524
                            for line in app_doc_data.lines:
525
                                matches = [it for it in app_doc_data.lines if
526
                                           (it is not line) and (not line.isParallel(it))]
527

    
528
                                # get closest line
529
                                selected = []
530
                                for match in matches:
531
                                    dist = [line.distanceTo(match.startPoint()), line.distanceTo(match.endPoint())]
532
                                    if dist[0] < toler or dist[1] < toler:
533
                                        selected.append(match)
534
                                # up to here
535

    
536
                                for match in matches:
537
                                    detector.connectLineToLine(match, line, toler)
538
                        except Exception as ex:
539
                            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[
540
                                -1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
541
                            worker.displayLog.emit(MessageType.Error, message)
542
                        # up to here
543

    
544
                        # connect line to symbol
545
                        try:
546
                            for line in app_doc_data.lines:
547
                                matches = [symbol for symbol in symbols if symbol.is_connectable(line, toler=toler)]
548
                                for symbol in matches:
549
                                    detector.connectLineToSymbol(line, symbol, toler=round(toler / 2))
550
                        except Exception as ex:
551
                            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[
552
                                -1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
553
                            worker.displayLog.emit(MessageType.Error, message)
554
                        # up to here
555

    
556
                # connect symbol to symbol
557
                try:
558
                    for symbol in app_doc_data.symbols:
559
                        matches = [it for it in app_doc_data.symbols if it is not symbol and symbol.is_connectable(it)]
560
                        # print(str(symbol))
561
                        # print(matches)
562
                        for match in matches:
563
                            symbol.connect_if_possible(match)
564
                except Exception as ex:
565
                    message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
566
                                                                  sys.exc_info()[-1].tb_lineno)
567
                    worker.displayLog.emit(MessageType.Error, message)
568
                # up to here
569

    
570
                createUnknownItems(mainRes)
571

    
572
                worker.updateBatchProgress.emit(len(srcList), 1)
573
        except Exception as ex:
574
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
575
                                                          sys.exc_info()[-1].tb_lineno)
576
            worker.displayLog.emit(MessageType.Error, message)
577
        finally:
578
            pass
579

    
580
    '''
581
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
582
                    2018.05.28  Jeongwoo    Add xmlPath Parameter and append LineInfo into xml
583
                    2018.05.29  Jeongwoo    Change method to add item
584
                    2018.05.30  Jeongwoo    Remove parameter (xmlPath)
585
                    humkyung 2018.06.11 add drawing path to parameter and write recognized lines to image
586
                    humkyung 2018.07.04 call arrangeLinePosition after creating line
587
    '''
588

    
589
    @staticmethod
590
    def recognizeLine(path, listWidget, graphicsView, worker, batch):
591
        from shapely.geometry import Point, LineString
592
        from SymbolSvgItem import SymbolSvgItem
593
        from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
594
        from EngineeringLineNoTextItem import QEngineeringLineNoTextItem
595
        from EngineeringTextItem import QEngineeringTextItem
596
        from EngineeringLineItem import QEngineeringLineItem
597
        from LineDetector import LineDetector
598

    
599
        try:
600
            listWidget.addItem('Starting line recognization')
601
            worker.displayTitle.emit(worker.tr('Detecting lines...'))
602

    
603
            appDocData = AppDocData.instance()
604
            # remove already existing line and flow arrow item
605
            if not batch:
606
                items = [item for item in worker.graphicsView.scene.items() if (type(item) is QEngineeringLineItem)]
607
                for item in items:
608
                    item.transfer.onRemoved.emit(item)
609
                    # worker.graphicsView.scene.removeItem(item)
610

    
611
            # up to here
612

    
613
            # detect line
614
            connectedLines = []
615

    
616
            area = appDocData.getArea('Drawing')
617
            area.img = worker.removeSmallObjects(area.img)
618
            detector = LineDetector(area.img)
619

    
620
            symbols = []
621
            for item in appDocData.symbols:
622
                if issubclass(type(item), SymbolSvgItem):
623
                    symbols.append(item)
624
                    res = detector.detectConnectedLine(item, round(area.x), round(area.y))
625
                    if res is not None:
626
                        connectedLines.extend(res)
627

    
628
            # line detection without symbol connection point info
629
            remainLines = detector.detectLineWithoutSymbol(area)
630
            windowSize = appDocData.getSlidingWindowSize()
631
            thickness = int(windowSize[1])
632
            for line in remainLines:
633
                line.append(thickness)
634
            connectedLines.extend(remainLines)
635

    
636
            configs = appDocData.getConfigs('Line Detector', 'Length to connect line')
637
            toler = int(configs[0].value) if configs else 20
638
            detector.mergeLines(connectedLines, toler=toler)
639

    
640
            for pts in connectedLines:
641
                processLine = QEngineeringLineItem(
642
                    vertices=[(area.x + param[0], area.y + param[1]) for param in pts[:-1]], thickness=pts[2])
643
                processLine.area = 'Drawing'
644

    
645
                appDocData.lines.append(processLine)
646
                appDocData.allItems.append(processLine)
647

    
648
                # if processLine.length() > 100: # TODO: check critical length
649
                #    processLine.addFlowArrow()
650

    
651
            listWidget.addItem('Connecting lines')
652
            if appDocData.lines:
653
                # connect line to symbol
654
                try:
655
                    for line in appDocData.lines:
656
                        matches = [symbol for symbol in symbols if symbol.is_connectable(line, toler=toler)]
657
                        for symbol in matches:
658
                            detector.connectLineToSymbol(line, symbol, toler=round(toler / 2))
659
                except Exception as ex:
660
                    message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
661
                                                                  sys.exc_info()[-1].tb_lineno)
662
                    worker.displayLog.emit(MessageType.Error, message)
663
                # up to here
664

    
665
                # connect line to line
666
                configs = appDocData.getConfigs('Line Detector', 'Length to connect line')
667
                toler = int(configs[0].value) if configs else 20
668
                try:
669
                    for line in appDocData.lines:
670
                        matches = [it for it in appDocData.lines if (it is not line) and (not line.isParallel(it))]
671

    
672
                        # get closest line
673
                        selected = []
674
                        for match in matches:
675
                            dist = [line.distanceTo(match.startPoint()), line.distanceTo(match.endPoint())]
676
                            if dist[0] < toler or dist[1] < toler:
677
                                selected.append(match)
678
                        # up to here
679

    
680
                        for match in matches:
681
                            detector.connectLineToLine(match, line, toler)
682
                except Exception as ex:
683
                    message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
684
                                                                  sys.exc_info()[-1].tb_lineno)
685
                    worker.displayLog.emit(MessageType.Error, message)
686
                # up to here
687

    
688
            listWidget.addItem(worker.tr('Creating lines...'))
689

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

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

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

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

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

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

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

    
736
        return targetSymbolList
737

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

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

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

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

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

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

    
805
            foundSymbolCount = 0
806

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1062
        return []
1063

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

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

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

    
1093
            foundSymbolCount = 0
1094

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1252
        return []
1253

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

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

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

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

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

    
1281
        return res
1282

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

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

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

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

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

    
1317
        return res
1318

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

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

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

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

    
1357
        symbol_idx = connPt[3]
1358

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

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

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

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

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

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

    
1394
        return newSym
1395

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1469
        return matchCount
1470

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1556
            cv2.imwrite(os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(drawingPath)), canvas)
1557
        except Exception as ex:
1558
            from App import App
1559

    
1560
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1561
                                                          sys.exc_info()[-1].tb_lineno)
1562
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1563

    
1564
    '''
1565
        @history    2018.04.27  Jeongwoo    Remove Tesseract Log on listWidget
1566
                    2018.05.04  Jeongwoo    Change method to OCR with tesseract_ocr_module.py
1567
                    2018.05.09  Jeongwoo    Add global variable textInfoList, Remove text in symbol and Add tesseract result text
1568
                    2018.05.10  Jeongwoo    Remove not used if-statement
1569
                    2018.06.19  Jeongwoo    When detect text in symbol, use getTextAreaInfo() and Tesseract
1570
                    2018.06.21  Jeongwoo    Add if-statement for way to detect text by Type A
1571
    '''
1572

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

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

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

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

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

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

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

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

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

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

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

    
1638
        global ocrCompletedSrc
1639
        global textInfoList
1640

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

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

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

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

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

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

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

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

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

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

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

    
1704

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

    
1709

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

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

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

    
1723
        QDialog.__init__(self, parent)
1724

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

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

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

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

    
1764
    def checkBoxChanged(self, checkState):
1765
        '''
1766
        @brief      line and text cannot be reocognized alone
1767
        @author     euisung
1768
        @date       2019.05.14
1769
        '''
1770
        if checkState is int(Qt.Checked):
1771
            if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked():
1772
                self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
1773
            elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked():
1774
                self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
1775
        elif checkState is int(Qt.Unchecked):
1776
            if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked():
1777
                self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
1778
            elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked():
1779
                self.ui.checkBoxText.setCheckState(Qt.Unchecked)
1780

    
1781
    '''
1782
        @brief      QListWidget Row Inserted Listener
1783
                    Whenever row inserted, scroll to bottom
1784
        @author     Jeongwoo
1785
        @date       18.04.12
1786
    '''
1787

    
1788
    def rowInserted(self, item):
1789
        self.ui.listWidget.scrollToBottom()
1790

    
1791
    def accept(self):
1792
        self.isAccepted = True
1793
        QDialog.accept(self)
1794

    
1795
    def recognizeButtonClicked(self, event):
1796
        """
1797
        @brief      start recognization
1798
        @author     humkyung
1799
        @history    humkyung 2018.10.05 clear imgSrc before recognizing
1800
        """
1801
        if self.ui.checkBoxSymbol.isChecked():  # or self.ui.checkBoxText.isChecked() or self.ui.lineCheckBox.isChecked():
1802
            appDocData = AppDocData.instance()
1803
            appDocData.imgSrc = None
1804
            area = appDocData.getArea('Drawing')
1805
            if area is None:
1806
                QMessageBox.about(self, self.tr("Notice"), self.tr('Please select drawing area.'))
1807
                return
1808

    
1809
            self.ui.recognizeButton.setEnabled(False)
1810
            self.ui.buttonBox.setEnabled(False)
1811
            self.ui.progressBar.setValue(0)
1812
            self.ui.listWidget.addItem("Initializing...")
1813
            self.startThread()
1814

    
1815
    '''
1816
        @brief  add item to list widget
1817
        @author humkyung
1818
        @date   2018.06.19
1819
    '''
1820

    
1821
    def addListItem(self, item):
1822
        self.ui.listWidget.addItem(item)
1823

    
1824
    '''
1825
        @brief  update progressbar with given value
1826
        @author humkyung
1827
        @date   2018.06.08
1828
    '''
1829

    
1830
    def updateProgress(self, maxValue, image_path):
1831
        self.ui.progressBar.setMaximum(maxValue)
1832
        self.ui.progressBar.setValue(self.ui.progressBar.value() + 1)
1833

    
1834
        if image_path is not None and os.path.isfile(image_path):
1835
            self.ui.labelImage.setPixmap(QPixmap(image_path))
1836
        elif image_path is not None:
1837
            self.ui.labelImage.setText(image_path)
1838

    
1839
    def updateBatchProgress(self, maxValue, weight):
1840
        '''
1841
            @brief  update batch progressbar
1842
            @author euisung
1843
            @date   2018.11.28
1844
        '''
1845
        self.ui.progressBarBatch.setMaximum(maxValue * 5)
1846
        self.ui.progressBarBatch.setValue(self.ui.progressBarBatch.value() + weight)
1847

    
1848
    '''
1849
        @brief      display title
1850
        @author     humkyung
1851
        @date       2018.08.20
1852
    '''
1853

    
1854
    def displayTitle(self, title):
1855
        self.ui.labelTitle.setText(title)
1856

    
1857
    def startThread(self):
1858
        """
1859
        @brief  start thread
1860
        @author humkyung
1861
        @date   2018.04.??
1862
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
1863
                    2018.05.28  Jeongwoo    Add connects (self.loadRecognitionResult, recognizeLine)
1864
                    2018.05.30  Jeongwoo    Change signal name (drawDetectedItems)
1865
                    humkyung 2018.06.08 connect signal to self.updateProgress
1866
        """
1867
        import timeit
1868
        from PyQt5 import QtWidgets
1869
        from App import App
1870

    
1871
        self.ui.buttonBox.setDisabled(True)
1872

    
1873
        # 1 - create Worker and Thread inside the Form
1874
        self.obj = Worker()  # no parent!
1875
        self.obj.path = self.path
1876
        self.obj.listWidget = self.ui.listWidget
1877
        self.obj.graphicsView = self.graphicsView
1878
        self.obj.isSymbolChecked = self.ui.checkBoxSymbol.isChecked()
1879
        self.obj.isTextChecked = self.ui.checkBoxText.isChecked()
1880
        self.obj.isLineChecked = self.ui.lineCheckBox.isChecked()
1881
        self.obj.isTrainingChecked = self.ui.checkBoxTraining.isChecked()
1882
        self.obj.batch = self.batch
1883
        self.obj.createDetectedItems = self.parent.createDetectedItems
1884
        self.obj.createUnknownItems = self.parent.createUnknownItems
1885
        self.thread = QThread()  # no parent!
1886

    
1887
        # 2 - Move the Worker object to the Thread object
1888
        self.obj.moveToThread(self.thread)
1889

    
1890
        # 3 - Connect Worker Signals to the Thread slots
1891
        self.obj.finished.connect(self.thread.quit)
1892
        # self.obj.drawDetectedItems.connect(self.drawDetectedItems)
1893
        # self.obj.drawDetectedLines.connect(self.drawDetectedLines)
1894
        # self.obj.drawUnknownItems.connect(self.drawUnknownItems)
1895
        self.obj.displayMessage.connect(self.addListItem)
1896
        self.obj.updateProgress.connect(self.updateProgress)
1897
        self.obj.updateBatchProgress.connect(self.updateBatchProgress)
1898
        self.obj.displayLog.connect(App.mainWnd().addMessage)
1899
        self.obj.displayTitle.connect(self.displayTitle)
1900

    
1901
        # 4 - Connect Thread started signal to Worker operational slot method
1902
        self.thread.started.connect(self.obj.procCounter)
1903

    
1904
        # 5 - Thread finished signal will close the app if you want!
1905
        self.thread.finished.connect(self.dlgExit)
1906

    
1907
        # 6 - Start the thread
1908
        self.thread.start()
1909

    
1910
        self.tmStart = timeit.default_timer()
1911

    
1912
    '''
1913
        @brief set buttonbox's enabled flag
1914
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
1915
                    2018.06.14  Jeongwoo    Change sentence order
1916
                    2018.11.26  euisung     add drawing part
1917
                    2018.11.26  euising     move save and unknown part into executerecognition
1918
    '''
1919

    
1920
    def dlgExit(self):
1921
        import timeit
1922
        import XmlGenerator as xg
1923

    
1924
        try:
1925
            self.ui.buttonBox.setEnabled(True)
1926

    
1927
            self.ui.progressBar.setValue(self.ui.progressBar.maximum())
1928
            # if not self.batch:
1929
            #    self.parent.drawDetectedItemsToScene()
1930
        except Exception as ex:
1931
            from App import App
1932

    
1933
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1934
                                                          sys.exc_info()[-1].tb_lineno)
1935
            App.mainWnd().addMessage.emit(MessageType.Error, message)
1936
        finally:
1937
            self.tmStop = timeit.default_timer()
1938
            seconds = self.tmStop - self.tmStart
1939
            self.ui.listWidget.addItem("\nRunning Time : {} min".format(str(round(seconds / 60, 1))) + "\n")
1940

    
1941
    '''
1942
        @history    2018.05.29  Jeongwoo    Call parent's method
1943
                    2018.05.30  Jeongwoo    Change method name
1944
                    2018.06.09  humkyung    set progressbar value to maximum
1945
                    2018.11.12  euisung     add title block properties
1946
                    2018.11.29  euisung     no more used
1947
    '''
1948

    
1949
    def drawDetectedItems(self, symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList, loop):
1950
        try:
1951
            self.ui.progressBar.setValue(self.ui.progressBar.maximum())
1952
            self.parent.drawDetectedItems(symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList)
1953
        finally:
1954
            loop.quit()
1955

    
1956
    '''
1957
        @brief      draw detected lines
1958
        @author     humkyung
1959
        @date       2018.08.23
1960
        @history    2018.11.27  euisung     no more used
1961
    '''
1962

    
1963
    def drawDetectedLines(self, lineList, loop):
1964
        try:
1965
            self.parent.drawDetectedLines(lineList, self.obj)
1966
        finally:
1967
            loop.quit()
1968

    
1969
    '''
1970
        @brief      draw detected lines
1971
        @author     euisung
1972
        @date       2018.11.27
1973
        @history    2018.11.27  euisung     no more used
1974
    '''
1975

    
1976
    def drawUnknownItems(self, path, loop):
1977
        try:
1978
            self.parent.drawUnknownItems(path)
1979
        finally:
1980
            loop.quit()
클립보드 이미지 추가 (최대 크기: 500 MB)