프로젝트

일반

사용자정보

통계
| 개정판:

hytos / DTI_PID / DTI_PID / RecognitionDialog.py @ 168c1988

이력 | 보기 | 이력해설 | 다운로드 (192 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
import timeit
11
from PyQt5.QtCore import *
12
from PyQt5.QtGui import *
13
from PyQt5.QtWidgets import *
14
import Recognition_UI
15

    
16
import concurrent.futures as futures
17

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

    
35
from MainWindow import MainWindow
36

    
37
# region Symbol Image path List for test
38
targetSymbolList = []
39
# endregion
40

    
41
# region Global variables
42
searchedSymbolList = []
43
textInfoList = []
44

    
45
src = []
46

    
47
ocrCompletedSrc = []
48
afterDenoising = []
49
canvas = []
50

    
51
#WHITE_LIST_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-"
52

    
53
MIN_TEXT_SIZE = 10
54

    
55
THREAD_MAX_WORKER = os.cpu_count()
56
threadLock = threading.Lock()
57

    
58
ACCEPT_OVERLAY_AREA = 20
59
# endregion
60

    
61
'''
62
    @history    2018.05.25  Jeongwoo    Add pyqtSignal(recognizeLine, loadRecognitionResult)
63
'''
64
from enum import Enum
65
class Arrow(Enum):
66
    NULL = 1
67
    DOWN = 2
68
    UP = 3
69
    LEFT = 4
70
    RIGHT = 5
71

    
72
class Worker(QObject):
73
    from PyQt5.QtCore import QThread
74
    from PyQt5.QtCore import QTranslator
75
    from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout, QListWidget
76
    import sys
77

    
78
    '''
79
        @history    2018.05.30  Jeongwoo    Remove parameter on recognizeLine signal / Change signal name (drawDetectedItems)
80
        @history    humkyung 2018.06.08 add signal for progressbar
81
        @history    euisung 2018.11.27 add signal for unknown items
82
    '''
83
    finished = pyqtSignal()
84
    intReady = pyqtSignal(int)
85
    displayTitle = pyqtSignal(str)
86
    displayMessage = pyqtSignal(QListWidgetItem)
87
    updateProgress = pyqtSignal(int, str)
88
    updateBatchProgress = pyqtSignal(int, int)
89
    displayLog = pyqtSignal(MessageType, str)
90
    add_detected_items_to_scene = pyqtSignal(QGraphicsScene, list)
91
    save_scene = pyqtSignal(QGraphicsScene)
92
    add_predata_to_scene = pyqtSignal(Drawing, QGraphicsScene, bool, bool, bool, bool, bool)
93
    clear_scene = pyqtSignal(QGraphicsScene, QGraphicsScene, QGraphicsScene)
94
    preset_execute = pyqtSignal(QGraphicsScene, str, str, str, str)
95
    item_remove = pyqtSignal(list)
96

    
97
    def __init__(self, mutex, cond):
98
        super(Worker, self).__init__()
99
        self.mutex = mutex
100
        self.cond = cond
101

    
102
    '''
103
        @history    2018.05.25  Jeongwoo    Add if-statements by isSymbolTextChecked and isLineChecked variable
104
                    2018.05.28  Jeongwoo    Add Parameter 'xmlPath[0]'
105
                    2018.05.30  Jeongwoo    Remove import recognizeLine and self.xmlPath and remove parameter on recognizeLine.emit() (xmlPath)
106
                                            Change signal name (drawDetectedItems)
107
    '''
108

    
109
    def procCounter(self):  # A slot takes no params
110
        try:
111
            self.mutex.lock()
112
            if self.isSymbolChecked or self.isTrainingChecked:
113
                Worker.executeRecognition(self.drawings, self.listWidget, self.isLineChecked, self)
114
        except Exception as ex:
115
            from App import App
116
            from AppDocData import MessageType
117

    
118
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
119
                      f"{sys.exc_info()[-1].tb_lineno}"
120
            App.mainWnd().addMessage.emit(MessageType.Error, message)
121
            self.displayLog.emit(MessageType.Error, message)
122
        except:
123
            (_type, value, traceback) = sys.exc_info()
124
            sys.excepthook(_type, value, traceback)
125
        finally:
126
            self.mutex.unlock()
127
            self.finished.emit()
128

    
129
    '''
130
        @brief  remove small objects from given image
131
        @author humkyung
132
        @date   2018.04.26
133
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
134
    '''
135

    
136
    @staticmethod
137
    def remove_small_objects(img, minArea=None, maxArea=None):
138
        try:
139
            image = img.copy()
140

    
141
            if not minArea or not maxArea:
142
                app_doc_data = AppDocData.instance()
143
                configs = app_doc_data.getConfigs('Small Object Size', 'Min Area')
144
                minArea = int(configs[0].value) if 1 == len(configs) else 20
145
                configs = app_doc_data.getConfigs('Small Object Size', 'Max Area')
146
                maxArea = int(configs[0].value) if 1 == len(configs) else 50
147

    
148
            ## try to convert grayscale to binary
149
            #image = cv2.threshold(image, 200, 255, cv2.THRESH_BINARY)[1]
150

    
151
            contours, _ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
152
            selectedContours = []
153
            for contour in contours:
154
                area = cv2.contourArea(contour)
155
                if minArea < area < maxArea:
156
                    selectedContours.append(contour)
157

    
158
            # draw contour with white color
159
            cv2.drawContours(image, selectedContours, -1, (255, 255, 255), -1)
160
        except Exception as ex:
161
            from App import App
162

    
163
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
164
                      f"{sys.exc_info()[-1].tb_lineno}"
165
            App.mainWnd().addMessage.emit(MessageType.Error, message)
166

    
167
        return image
168

    
169
    '''
170
        @brief  arrange line's position
171
        @author humkyung
172
        @date   2018.07.04
173
    '''
174

    
175
    @staticmethod
176
    def arrangeLinePosition(lines, symbols, listWidget):
177
        try:
178
            listWidget.addItem('Apply flow direction')
179
            pool = [line for line in lines if line.flowMark is not None]
180
            visited = []
181
            visited.extend(pool)
182
            while len(pool) > 0:
183
                line = pool.pop()
184
                print('{} - ({})'.format(line, len(pool)))
185
                rhs = [item for item in lines if item not in visited and item.is_connected(line)]
186
                if rhs:
187
                    pool.extend(rhs)
188
                    visited.extend(rhs)
189
                    for item in rhs:
190
                        item.arrangeVertexOrder(line)
191

    
192
                # skip jointed symbols
193
                symbolPool = [item for item in symbols if item not in visited and item.is_connected(line)]
194
                if symbolPool:
195
                    selected = []
196
                    visited.extend(symbolPool)
197
                    while len(symbolPool) > 0:
198
                        symbol = symbolPool.pop()
199

    
200
                        rhs = [item for item in symbols if item not in visited and item.is_connected(symbol)]
201
                        if rhs:
202
                            symbolPool.extend(rhs)
203
                            visited.extend(rhs)
204
                            selected.extend(rhs)
205
                        else:
206
                            selected.append(symbol)
207

    
208
                    # find lines which are connected last symbol
209
                    for symbol in selected:
210
                        rhs = [item for item in lines if item not in visited and item.is_connected(symbol)]
211
                        if rhs:
212
                            pool.extend(rhs)
213
                            visited.extend(rhs)
214
                            for item in rhs:
215
                                item.arrangeVertexOrder(line)
216
                # up to here
217
            # up to here
218
        except Exception as ex:
219
            from App import App
220
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
221
                                                           sys.exc_info()[-1].tb_lineno)
222
            App.mainWnd().addMessage.emit(MessageType.Error, message)
223

    
224
    def create_detected_items(self, symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList):
225
        try:
226
            QApplication.processEvents()
227
            self.create_detected_symbol_item(symbolList)
228
            QApplication.processEvents()
229
            self.create_detected_text_item(textInfoList)
230
            QApplication.processEvents()
231
            self.create_detected_other_text_item(otherTextInfoList)
232
            QApplication.processEvents()
233
            self.create_detected_title_block_text_item(titleBlockTextInfoList)
234

    
235
            # update scene
236
            # self.graphicsView.scene().update(self.graphicsView.sceneRect())
237
        except Exception as ex:
238
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
239
                                                           sys.exc_info()[-1].tb_lineno)
240
            self.displayLog.emit(MessageType.Error, message)
241

    
242
    '''
243
            history     2018.06.09  humkyung    check length of original and connection point is 2 while parsing
244
                        2018.11.26  euisung     remove scene dependency
245
                        2018.11.29  euisung     change name drawDetectedSymbolItem() -> createDetectedSymbolItem
246
        '''
247

    
248
    def create_detected_symbol_item(self, symbolList):
249
        from GraphicsBoundingBoxItem import QGraphicsBoundingBoxItem
250
        from SymbolSvgItem import SymbolSvgItem
251
        import math
252

    
253
        try:
254
            app_doc_data = AppDocData.instance()
255
            project = app_doc_data.getCurrentProject()
256

    
257
            searchedMap = []
258
            for symbol in symbolList:
259
                pt = [float(x) for x in symbol.getSp()]
260
                size = [symbol.getWidth(), symbol.getHeight()]
261
                name = symbol.getName()
262
                angle = round(math.radians(symbol.getRotatedAngle()), 2)
263
                _type = symbol.getType()
264
                flip = symbol.getDetectFlip()
265
                origin = [0, 0]
266
                if 2 == len(symbol.getOriginalPoint().split(',')):
267
                    tokens = symbol.getOriginalPoint().split(',')
268
                    origin = [pt[0] + float(tokens[0]), pt[1] + float(tokens[1])]
269
                connPts = []
270
                if symbol.getConnectionPoint() is not None and symbol.getConnectionPoint() != '':
271
                    for param in symbol.getConnectionPoint().split('/'):
272
                        tokens = param.split(',')
273
                        connPts.append(
274
                            ('AUTO', pt[0] + float(tokens[0]), pt[1] + float(tokens[1]), '0') if len(tokens) == 2 else \
275
                                (tokens[0], pt[0] + float(tokens[1]), pt[1] + float(tokens[2]), '0') if len(
276
                                    tokens) == 3 else \
277
                                    (tokens[0], pt[0] + float(tokens[1]), pt[1] + float(tokens[2]), tokens[3]) if len(
278
                                        tokens) == 4 else None)
279

    
280
                parentSymbol = symbol.getBaseSymbol()
281
                childSymbol = symbol.getAdditionalSymbol()
282
                hasInstrumentLabel = symbol.getHasInstrumentLabel()
283

    
284
                svgFilePath = os.path.join(project.getSvgFilePath(), _type, name + '.svg')
285
                if os.path.isfile(svgFilePath):
286
                    svg = SymbolSvgItem.createItem(_type, None, svgFilePath, owner=None, flip=flip)
287
                    svg.hit_ratio = symbol.hitRate
288
                    svg.buildItem(name, _type, angle, pt, size, origin, connPts, parentSymbol, childSymbol,
289
                                  hasInstrumentLabel)
290
                    svg.area = 'Drawing'
291

    
292
                    # set owner - 2018.07.20 added by humkyung
293
                    matches = [searched for searched in searchedMap if searched[0] == symbol.owner]
294
                    if len(matches) == 1:
295
                        svg.owner = matches[0][1]
296
                    searchedMap.append((symbol, svg))
297
                    # up to here
298

    
299
                    # self.addSvgItemToScene(svg)
300
                    app_doc_data.symbols.append(svg)
301
                    app_doc_data.allItems.append(svg)
302
                else:
303
                    item = QGraphicsBoundingBoxItem(pt[0], pt[1], size[0], size[1])
304
                    item.isSymbol = True
305
                    item.angle = angle
306
                    item.setPen(QPen(Qt.red, 5, Qt.SolidLine))
307
                    # self.graphicsView.scene().addItem(item)
308
                    # appDocData.symbols.append(item)
309
                    app_doc_data.allItems.append(item)
310
            # up to here
311
        except Exception as ex:
312
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
313
                                                           sys.exc_info()[-1].tb_lineno)
314
            self.displayLog.emit(MessageType.Error, message)
315

    
316
    '''
317
            @history    2018.06.08  Jeongwoo    Add parameter on round method
318
            @history    2018.11.02  euisung     Add save note text item
319
            @history    2018.11.05  euisung     delete save note text item and move to drawDetectedItems()
320
                        2018.11.26  euisung     remove scene dependency
321
                        2018.11.29  euisung     change name drawDetectedTextItem() -> createDetectedTextItem
322
        '''
323

    
324
    def create_detected_text_item(self, textInfoList):
325
        from TextItemFactory import TextItemFactory
326
        import math
327

    
328
        try:
329
            app_doc_data = AppDocData.instance()
330

    
331
            # parse texts
332
            for textInfo in textInfoList:
333
                x = textInfo.getX()
334
                y = textInfo.getY()
335
                width = textInfo.getW()
336
                height = textInfo.getH()
337
                angle = round(math.radians(textInfo.getAngle()), 2)
338
                text = textInfo.getText()
339
                if not text: continue
340

    
341
                item = TextItemFactory.instance().createTextItem(textInfo)
342
                if item is not None:
343
                    item.loc = [x, y]
344
                    item.size = (width, height)
345
                    item.angle = angle
346
                    item.area = 'Drawing'
347
                    #item.transfer.onRemoved.connect(self.itemRemoved)
348
                    # self.addTextItemToScene(item)
349
                    app_doc_data.texts.append(item)
350
                    app_doc_data.allItems.append(item)
351
        except Exception as ex:
352
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
353
                                                           sys.exc_info()[-1].tb_lineno)
354
            self.displayLog.emit(MessageType.Error, message)
355

    
356
    '''
357
        @brief      draw detected texts except which in drawing area
358
        @history    2018.11.29  euisung     change name drawDetectedOtherTextItem() -> createDetectedOtherTextItem
359
    '''
360

    
361
    def create_detected_other_text_item(self, otherTextInfoList):
362
        from TextItemFactory import TextItemFactory
363
        import math
364

    
365
        try:
366
            app_doc_data = AppDocData.instance()
367

    
368
            # parse notes
369
            for textInfoMap in otherTextInfoList:
370
                if textInfoMap[0] == 'Note' or textInfoMap[1] is None:
371
                    pass
372

    
373
                for textInfo in textInfoMap[1]:
374
                    x = textInfo.getX()
375
                    y = textInfo.getY()
376
                    width = textInfo.getW()
377
                    height = textInfo.getH()
378
                    angle = round(math.radians(textInfo.getAngle()))
379
                    text = textInfo.getText()
380

    
381
                    item = TextItemFactory.instance().createTextItem(textInfo)
382

    
383
                    item.loc = [x, y]
384
                    item.size = (width, height)
385
                    item.angle = angle
386
                    item.area = textInfoMap[0]
387
                    #item.transfer.onRemoved.connect(self.itemRemoved)
388
                    app_doc_data.texts.append(item)
389
                    app_doc_data.allItems.append(item)
390
        except Exception as ex:
391
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
392
                                                           sys.exc_info()[-1].tb_lineno)
393
            self.displayLog.emit(MessageType.Error, message)
394

    
395
    def create_detected_title_block_text_item(self, textInfoList):
396
        """draw title block"""
397
        from TextItemFactory import TextItemFactory
398
        import math
399

    
400
        try:
401
            app_doc_data = AppDocData.instance()
402

    
403
            # parse texts
404
            for textInfos in textInfoList:
405
                if len(textInfos[1]) is 0:
406
                    continue
407

    
408
                for textInfo in textInfos[1]:
409
                    x = textInfo.getX()
410
                    y = textInfo.getY()
411
                    width = textInfo.getW()
412
                    height = textInfo.getH()
413
                    angle = round(math.radians(textInfo.getAngle()), 2)
414
                    text = textInfo.getText()
415
                    if not text: continue
416
                    item = TextItemFactory.instance().createTextItem(textInfo)
417

    
418
                    if item is not None:
419
                        item.loc = [x, y]
420
                        item.size = (width, height)
421
                        item.angle = angle
422
                        item.area = textInfos[0]
423
                        # self.addTextItemToScene(item)
424
                        app_doc_data.texts.append(item)
425
                        app_doc_data.allItems.append(item)
426
        except Exception as ex:
427
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
428
                                                           sys.exc_info()[-1].tb_lineno)
429
            self.displayLog.emit(MessageType.Error, message)
430

    
431
    '''
432
        @brief  draw unknown items 
433
        @author humkyung
434
        @date   2018.06.12
435
        @history    2018.06.14  Jeongwoo    Change method to add unknown item
436
                    2018.06.18  Jeongwoo    Add connect on unknown item
437
                                            Add [transfer] for using pyqtSignal
438
                    2018.11.26  euisung     remove scene dependency
439
                    2018.11.26  euisung     isolate scene adding part -> drawDetectedItemsToScene()
440
                    2018.11.27  euisung     add save to xml
441
                    2018.11.29  euisung     change name drawUnknownItems() -> createUnknownItems
442
    '''
443

    
444
    def create_unknown_items(self, path):
445
        from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
446
        from EngineeringLineItem import QEngineeringLineItem
447
        from EngineeringUnknownItem import QEngineeringUnknownItem
448

    
449
        try:
450
            app_doc_data = AppDocData.instance()
451
            project = app_doc_data.getCurrentProject()
452
            windowSize = app_doc_data.getSlidingWindowSize()
453

    
454
            thickness = int(windowSize[1] / 2)
455

    
456
            area = app_doc_data.getArea('Drawing')
457
            diffFilePath = os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(path))
458

    
459
            # remove line from image
460
            imgDiff = np.ones(app_doc_data.imgSrc.shape, np.uint8) * 255
461
            imgDiff[round(area.y + 1):round(area.y + area.height),
462
                           round(area.x + 1):round(area.x + area.width)] = \
463
                app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height),
464
                           round(area.x + 1):round(area.x + area.width)]
465

    
466
            lines = app_doc_data.lines
467
            for line in lines:
468
                line.drawToImage(imgDiff, 255, thickness) if line.thickness is None else \
469
                    line.drawToImage(imgDiff, 255, line.thickness)
470
            # up to here
471
            cv2.imwrite(diffFilePath, imgDiff)
472

    
473
            imgNot = np.ones(imgDiff.shape, np.uint8)
474
            cv2.bitwise_not(imgDiff, imgNot)
475
            configs = app_doc_data.getConfigs('Filter', 'ErodeSize')
476
            kernel = int(configs[0].value) if 1 == len(configs) else 3
477
            imgNot = cv2.erode(imgNot, np.ones((kernel, kernel), np.uint8))
478
            imgNot = cv2.dilate(imgNot, np.ones((8 + kernel, 8 + kernel), np.uint8))
479

    
480
            contours, hierarchy = cv2.findContours(imgNot, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
481

    
482
            ##
483
            idx = 0
484
            ##
485
            smallContours = []
486
            minimumSize = app_doc_data.getConfigs('Filter', 'MinimumSize')
487
            for contour in contours:
488
                [x, y, w, h] = cv2.boundingRect(contour)
489

    
490
                # remove too small one
491
                if len(minimumSize) is 1:
492
                    if w * h < int(minimumSize[0].value) * int(minimumSize[0].value):
493
                        smallContours.append(contour)
494
                        idx += 1
495
                        continue
496

    
497
                # create unknown item
498
                epsilon = cv2.arcLength(contour, True) * 0.001
499
                approx = cv2.approxPolyDP(contour, epsilon, True)
500
                approx = [pt[0] for pt in approx]
501
                resultStr, resultList = self.determine_remain_object(idx, contours, imgNot)
502
                if resultStr == 'LineIndicator':
503
                    item = QEngineeringUnknownItem(approx, 'True', resultList[0], resultList[1])
504
                    app_doc_data.lineIndicators.append(item)
505
                elif resultStr == 'MissingLine':
506
                    idx += 1
507
                    continue
508
                elif resultStr == 'Unknown':
509
                    item = QEngineeringUnknownItem(approx, 'False')
510
                    app_doc_data.unknowns.append(item)
511
                item.area = 'Drawing'
512
                app_doc_data.allItems.append(item)
513
                idx += 1
514
                # up to here
515

    
516
            """
517
            if app_doc_data.needReOpening is not None:
518
                app_doc_data.needReOpening = True
519
            """
520

    
521
            """
522
            diffFilePath = os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(path))
523
            if os.path.isfile(diffFilePath):
524
                imgDiff = cv2.threshold(cv2.cvtColor(cv2.imread(diffFilePath, 1), cv2.COLOR_BGR2GRAY), 0, 255,
525
                                        cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
526

527
                # remove line from image
528
                lines = app_doc_data.lines
529
                for line in lines:
530
                    line.drawToImage(imgDiff, 255, thickness) if line.thickness is None else \
531
                        line.drawToImage(imgDiff, 255, line.thickness)
532
                cv2.imwrite(diffFilePath, imgDiff)
533
                # up to here
534

535
                imgNot = np.ones(imgDiff.shape, np.uint8)
536
                cv2.bitwise_not(imgDiff, imgNot)
537
                configs = app_doc_data.getConfigs('Filter', 'ErodeSize')
538
                kernel = int(configs[0].value) if 1 == len(configs) else 3
539
                imgNot = cv2.erode(imgNot, np.ones((kernel, kernel), np.uint8))
540
                imgNot = cv2.dilate(imgNot, np.ones((8 + kernel, 8 + kernel), np.uint8))
541

542
                contours, hierarchy = cv2.findContours(imgNot, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
543

544
                ##
545
                idx = 0
546
                ##
547
                smallContours = []
548
                minimumSize = app_doc_data.getConfigs('Filter', 'MinimumSize')
549
                for contour in contours:
550
                    [x, y, w, h] = cv2.boundingRect(contour)
551

552
                    # remove too small one
553
                    if len(minimumSize) is 1:
554
                        if w * h < int(minimumSize[0].value) * int(minimumSize[0].value):
555
                            smallContours.append(contour)
556
                            idx += 1
557
                            continue
558

559
                    '''
560
                    rect = QRectF(x, y, w, h)
561
                    items = [item for item in diffItems if item.boundingRect().contains(rect)]
562
                    if len(items) > 0: continue
563

564
                    items = [item for item in diffItems if rect.contains(item.boundingRect())]
565
                    for item in items:
566
                        diffItems.remove(item)
567
                    '''
568

569
                    # create unknown item
570
                    epsilon = cv2.arcLength(contour, True) * 0.001
571
                    approx = cv2.approxPolyDP(contour, epsilon, True)
572
                    approx = [pt[0] for pt in approx]
573
                    resultStr, resultList = self.determine_remain_object(idx, contours, imgNot)
574
                    if resultStr == 'LineIndicator':
575
                        item = QEngineeringUnknownItem(approx, 'True', resultList[0], resultList[1])
576
                        app_doc_data.lineIndicators.append(item)
577
                    elif resultStr == 'MissingLine':
578
                        pass
579
                    elif resultStr == 'Unknown':
580
                        item = QEngineeringUnknownItem(approx, 'False')
581
                        app_doc_data.unknowns.append(item)
582
                    item.area = 'Drawing'
583
                    app_doc_data.allItems.append(item)
584
                    #item.transfer.onRemoved.connect(self.itemRemoved)
585
                    idx += 1
586
                    # up to here
587

588
                imgNotRemoveSmall = cv2.drawContours(imgNot, smallContours, -1, 0, -1)
589
                notFilePath = os.path.join(project.getTempPath(), "NOT_" + os.path.basename(path))
590
                cv2.imwrite(notFilePath, imgNotRemoveSmall)
591
            else:
592
                message = 'can\'t found {}'.format(diffFilePath)
593
                self.displayLog.emit(MessageType.Normal, message)
594
            """
595

    
596
        except Exception as ex:
597
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
598
                                                           sys.exc_info()[-1].tb_lineno)
599
            self.displayLog.emit(MessageType.Error, message)
600

    
601
    def determine_remain_object(self, idx, contours, imgNot):
602
        """determine remain objects -> line no indicator or unknown"""
603
        import math
604
        [x, y, w, h] = cv2.boundingRect(contours[idx])
605

    
606
        if w < 250 and h < 250:
607
            return ('Unknown', [])
608

    
609
        fLines = []
610
        maxDifAngle = 3
611
        mask = np.zeros_like(imgNot)
612
        cv2.drawContours(mask, contours, idx, 123, -1)  # Draw filled contour in mask
613
        out = np.zeros_like(imgNot)  # Extract out the object and place into output image
614
        out[mask == 123] = imgNot[mask == 123]
615

    
616
        # Now crop
617
        ##print(out)
618
        (x, y) = np.where(mask == 123)
619
        (topx, topy) = (np.min(x), np.min(y))
620
        (bottomx, bottomy) = (np.max(x), np.max(y))
621
        out = out[topx:bottomx + 1, topy:bottomy + 1]
622
        h, w = out.shape[0], out.shape[1]
623
        maxDifH, maxDifW = math.ceil(math.tan(4 * math.pi / 180) / 2 * w), math.ceil(
624
            math.tan(4 * math.pi / 180) / 2 * h)
625

    
626
        # detection lines
627
        edged2 = cv2.Canny(out, 100, 200)
628
        lines = cv2.HoughLinesP(image=edged2, rho=1, theta=np.pi / 180, threshold=25, minLineLength=30, maxLineGap=25)
629
        # lines = cv2.HoughLines(edged2, 1, np.pi/180, 60)
630
        if lines is None:
631
            return ('Unknown', [])
632
        for line in lines:
633
            # r, theta = line[0]
634
            # a, b = np.cos(theta), np.sin(theta)
635
            # x0, y0 = a * r, b * r
636
            # x1, y1 = int(x0 + 1000 * (-b)), int(y0 + 1000 * a)
637
            # x2, y2 = int(x0 - 1000 * (-b)), int(y0 - 1000 * a)
638
            # cv2.line(out, (x1, y1), (x2, y2), (0, 255, 0), 3)
639
            x1, y1, x2, y2 = line[0]
640
            degree = math.atan2(y2 - y1, x2 - x1) * 180 / math.pi
641
            fLine = [x1, y1, x2, y2, degree]
642
            # print(fLine)
643
            fLines.append(fLine)
644

    
645
        horLines = []
646
        verLines = []
647
        otherLines = []
648
        isVH = None
649
        for fLine in fLines:
650
            degree = math.fabs(fLine[4])
651
            if degree >= 90 - maxDifAngle:
652
                verLines.append(fLine)
653
            elif degree <= maxDifAngle:
654
                horLines.append(fLine)
655
            else:
656
                otherLines.append(fLine)
657

    
658
        baseLines = []
659
        baseDifV = 0
660
        if len(horLines):
661
            x, y = w / 2, 0
662
            baseDifV = maxDifH
663
            for horLine in horLines:
664
                x1, y1, x2, y2 = horLine[0], horLine[1], horLine[2], horLine[3]
665
                y = ((y2 - y1) / (x2 - x1)) * x + y1 - ((y2 - y1) / (x2 - x1)) * x1
666
                horLine.append(y)
667
            baseLines = horLines
668
            isVH = 'H'
669
        if len(verLines):
670
            x, y = 0, h / 2
671
            baseDifV = maxDifW
672
            for verLine in verLines:
673
                x1, y1, x2, y2 = verLine[0], verLine[1], verLine[2], verLine[3]
674
                x = ((x2 - x1) / (y2 - y1)) * y + x1 - ((x2 - x1) / (y2 - y1)) * y1
675
                verLine.append(x)
676
            baseLines = verLines
677
            isVH = 'V'
678

    
679
        for otherLine in otherLines:
680
            x, y = w / 2, 0
681
            x1, y1, x2, y2 = otherLine[0], otherLine[1], otherLine[2], otherLine[3]
682
            y = ((y2 - y1) / (x2 - x1)) * x + y1 - ((y2 - y1) / (x2 - x1)) * x1
683
            otherLine.append(y)
684

    
685
        # determine line no indicator
686
        if not ((len(horLines) > 0 and len(verLines) > 0) or len(otherLines) is 0 or (
687
                len(horLines) == 0 and len(verLines) == 0)):
688
            result, mergedOtherLine = self.is_line_no_indicator(w, h, maxDifAngle, baseDifV, baseLines, otherLines)
689
            if result:
690
                # print(fLines)
691
                return ('LineIndicator', [isVH, mergedOtherLine])
692

    
693
        return ('Unknown', [])
694

    
695
    def is_line_no_indicator(self, w, h, maxDifAngle, baseDifV, baseLines, otherLines):
696
        """determine line no indicator"""
697
        import math
698

    
699
        if w < 250 and h < 250:
700
            return (False, None)
701

    
702
        isSameLine = True
703
        i = 0
704
        for baseLine in baseLines:
705
            if not isSameLine: break
706
            j = 0
707
            for baseLinee in baseLines:
708
                if i == j:
709
                    j += 1
710
                    continue
711
                difV = math.fabs(baseLine[5] - baseLinee[5])
712
                if difV > baseDifV:
713
                    isSameLine = False
714
                    break
715
                j += 1
716
            i += 1
717
        if not isSameLine:
718
            return (False, None)
719

    
720
        isSameLine = True
721
        i = 0
722
        maxY = 0
723
        for otherLine in otherLines:
724
            y = otherLine[5]
725
            if math.fabs(y) > maxY:
726
                maxY = math.fabs(y)
727
            if not isSameLine: break
728
            j = 0
729
            for otherLinee in otherLines:
730
                if i == j:
731
                    j += 1
732
                    continue
733
                difV = math.fabs(otherLine[4] - otherLinee[4])
734
                if difV > maxDifAngle:
735
                    isSameLine = False
736
                    break
737
                j += 1
738
            i += 1
739
        if not isSameLine:
740
            return (False, None)
741

    
742
        isSameLine = True
743
        mergedOtherLine = [0, 0, 0, 0]
744
        i = 0
745
        maxDif = math.ceil(math.tan(4 * math.pi / 180) * maxY)
746
        for otherLine in otherLines:
747
            if not isSameLine: break
748
            j = 0
749
            for otherLinee in otherLines:
750
                if i == j:
751
                    j += 1
752
                    continue
753
                angle = math.fabs(otherLine[4] + otherLinee[4]) / 2
754
                difV = math.fabs(otherLine[5] - otherLinee[5])
755
                dist = math.sin((90 - angle) * math.pi / 180) * difV
756
                if dist > maxDif:
757
                    isSameLine = False
758
                    break
759
                j += 1
760
            i += 1
761
            mergedOtherLine[0] += otherLine[0]
762
            mergedOtherLine[1] += otherLine[1]
763
            mergedOtherLine[2] += otherLine[2]
764
            mergedOtherLine[3] += otherLine[3]
765
        if not isSameLine:
766
            (False, None)
767

    
768
        # Show the output image
769
        # print('line no indicator')
770
        mergedOtherLine[0] = round(mergedOtherLine[0] / len(otherLines))
771
        mergedOtherLine[1] = round(mergedOtherLine[1] / len(otherLines))
772
        mergedOtherLine[2] = round(mergedOtherLine[2] / len(otherLines))
773
        mergedOtherLine[3] = round(mergedOtherLine[3] / len(otherLines))
774
        # cv2.line(out, (mergedOtherLine[0], mergedOtherLine[1]), (mergedOtherLine[2], mergedOtherLine[3]), (255, 255, 255), 3)
775
        # cv2.imshow('Output', out)
776
        # cv2.waitKey(0)
777
        # cv2.destroyAllWindows()
778
        return (True, mergedOtherLine)
779

    
780
    @staticmethod
781
    def executeRecognition(drawings, listWidget, isLineChecked, worker):
782
        """recognize symbol, text, line from image p&id"""
783
        import re
784
        import timeit
785
        from TextDetector import TextDetector
786
        from CodeTables import CodeTable
787
        from datetime import datetime
788

    
789
        global ocrCompletedSrc
790
        global threadLock
791
        global searchedSymbolList
792
        global textInfoList
793
        global maxProgressValue
794

    
795
        try:
796
            app_doc_data = AppDocData.instance()
797
            #drawings = app_doc_data.getDrawings()
798
            project = app_doc_data.getCurrentProject()
799
            textDetector = TextDetector()
800

    
801
            Worker.initTargetSymbolDataList()
802

    
803
            for drawing in drawings:
804
                ocrCompletedSrc = []
805
                searchedSymbolList = []
806
                textInfoList = []
807

    
808
                mainRes = drawing.file_path
809
                if not os.path.isfile(mainRes):
810
                    item = QListWidgetItem('{} file is not found'.format(os.path.basename(mainRes)))
811
                    item.setBackground(Qt.red)
812
                    listWidget.addItem(item)
813
                    continue
814

    
815
                #worker.scene.clear()
816
                #worker.text_scene.clear()
817
                #worker.line_scene.clear()
818
                worker.clear_scene.emit(worker.scene, worker.text_scene, worker.line_scene)
819
                worker.cond.wait(worker.mutex)
820

    
821
                app_doc_data.clearItemList(True)
822

    
823
                app_doc_data.setImgFilePath(mainRes)
824
                app_doc_data.imgSrc = None
825
                matches = [drawing for drawing in drawings if app_doc_data.imgName == os.path.splitext(drawing.name)[0]]
826
                app_doc_data.activeDrawing = drawing # matches[0] if matches else Drawing(None, app_doc_data.imgName, None)
827
                #app_doc_data.activeDrawing.set_pid_source(Image.open(mainRes))
828

    
829
                # Load equipment package
830
                worker.add_predata_to_scene.emit(app_doc_data.activeDrawing, worker.scene, False, False, False, False, True)
831
                worker.cond.wait(worker.mutex)
832

    
833
                # remove not drawing area
834
                configs = app_doc_data.getConfigs('{} Equipment Desc Area'.format(app_doc_data.imgName))
835
                for config in configs:
836
                    found = re.findall('\\d+', config.value)
837
                    if len(found) == 4:
838
                        cv2.rectangle(app_doc_data.imgSrc, (int(found[0]), int(found[1])),
839
                                      (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
840

    
841
                configs = app_doc_data.getConfigs('{} Typical Area'.format(app_doc_data.imgName))
842
                for config in configs:
843
                    found = re.findall('\\d+', config.value)
844
                    if len(found) == 4:
845
                        cv2.rectangle(app_doc_data.imgSrc, (int(found[0]), int(found[1])),
846
                                      (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
847

    
848
                noteArea = app_doc_data.getArea('Note')
849
                if noteArea is not None:
850
                    noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height),
851
                                   round(noteArea.x):round(noteArea.x + noteArea.width)].copy()
852
                    cv2.rectangle(app_doc_data.imgSrc, (round(noteArea.x), round(noteArea.y)),
853
                                  (round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1)
854
                    #cv2.imwrite('c:\\temp\\note.png', noteArea.img)
855
                    #cv2.imwrite('c:\\temp\\removed_note.png', app_doc_data.imgSrc)
856
                # up to here
857

    
858
                area = app_doc_data.getArea('Drawing')
859
                if area is not None:
860
                    # copy image
861
                    area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height),
862
                               round(area.x):round(area.x + area.width)]
863

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

    
866
                    # area.contours, area.hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
867
                    area.not_img = cv2.bitwise_not(area.img)
868
                    area.contours, area.hierachy = cv2.findContours(area.not_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
869

    
870
                # remove Equipment Package area
871
                configs = app_doc_data.getConfigs('Symbol', 'Detect Inside Package')
872
                if (configs and int(configs[0].value) == -1):
873
                    Worker.remove_equipment_package(worker.scene, area)
874

    
875
                maxProgressValue = 0
876
                start_time = timeit.default_timer()
877
                listWidget.addItem(f"Start recognition {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} : {os.path.basename(mainRes).split('.')[0]}")
878
                threadLock.acquire()
879

    
880
                worker.updateBatchProgress.emit(len(drawings), 1)
881

    
882
                if worker.isSymbolChecked:
883
                    worker.symbol_time = timeit.default_timer()
884
                    
885
                    # calculate total count of symbol
886
                    for targetItem in targetSymbolList:
887
                        if type(targetItem) is list:
888
                            maxProgressValue += len(targetItem)
889
                        else:
890
                            maxProgressValue += 1
891
                    # up to here
892
                    maxProgressValue = maxProgressValue * 2
893
                threadLock.release()
894

    
895
                if worker.isSymbolChecked:
896
                    worker.displayTitle.emit(worker.tr('Detecting symbols...'))
897
                 
898
                    configs = app_doc_data.getConfigs('Engine', 'Symbol')
899
                    if (configs and int(configs[0].value) is 1) or not configs or worker.isTrainingChecked:
900
                    # get symbol original way
901
            
902
                        # detect only equipments
903
                        with futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as pool:
904
                            future_equipment = {pool.submit(Worker.detectSymbolsOnPid, mainRes, symbol, listWidget, worker):
905
                                                symbol for symbol in targetSymbolList[0]}
906
                            futures.wait(future_equipment)
907
                        # up to here
908

    
909
                        # detect normal symbols
910
                        with futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as pool:
911
                            future_symbol = {pool.submit(Worker.detectSymbolsOnPid, mainRes, symbol, listWidget, worker):
912
                                            symbol for symbol in targetSymbolList[2]}
913
                            futures.wait(future_symbol)
914
                        # up to here
915
                    else:
916
                        # using AI
917
                        listWidget.addItem(worker.tr('Waiting for symbol information from the server...'))
918
                        Worker.detect_symbol_using_server(targetSymbolList, listWidget, worker)
919

    
920
                    if worker.isTrainingChecked:
921
                        import uuid
922
                        worker.displayTitle.emit(worker.tr('Generating Data...'))
923
                        listWidget.addItem(worker.tr('Generating Data...'))
924

    
925
                        for item in searchedSymbolList:
926
                            path = os.path.join(project.getTempPath(), 'Tile', item.getName())
927
                            if not os.path.exists(path): os.makedirs(path)
928

    
929
                            _img = app_doc_data.imgSrc[round(item.getSp()[1]):round(item.getSp()[1] + item.getHeight()),
930
                                   round(item.getSp()[0]):round(item.getSp()[0] + item.getWidth())]
931
                            cv2.imwrite(os.path.join(path, str(uuid.uuid4()) + '.png'), _img)
932

    
933
                        app_doc_data.activeDrawing.image = None
934
                        worker.updateBatchProgress.emit(len(drawings), 4)
935
                        continue
936

    
937
                    #cv2.imwrite('c:\\temp\\before-imgSrc.png', area.img)
938
                    ## save original image before remove symbol
939
                    #worker.copy_imgSrc = app_doc_data.imgSrc.copy()
940

    
941
                    # roll back because equipment package was deleted from image
942
                    app_doc_data.imgSrc = app_doc_data.activeDrawing.image_origin
943

    
944
                    searchedTextSymList = []
945
                    for sym in searchedSymbolList:
946
                        Worker.remove_detected_symbol_image(sym, app_doc_data.imgSrc)
947

    
948
                    worker.symbol_time = timeit.default_timer() - worker.symbol_time
949
                else:
950
                    '''
951
                    import math
952

953
                    symbolItems = [item for item in worker.scene.items() if issubclass(type(item), SymbolSvgItem)]
954
                    
955
                    for symbolItem in symbolItems:
956
                        symbolName = symbolItem.name
957
                        symbolType = symbolItem.type
958
                        searchedItemSp = [int(symbolItem.loc[0]), int(symbolItem.loc[1])]
959
                        sw = symbolItem.size[0] # check later
960
                        sh = symbolItem.size[1] # check later
961

962
                        symbolThreshold = 50 # not necessary
963
                        symbolMinMatchCount = 0 # not necessary
964
                        hitRate = 80 # not necessary
965
                        
966
                        allowed_error = 0.0001
967
                        #degree 0
968
                        if abs(symbolItem.angle - 0) <= allowed_error:
969
                            symbolRotatedAngle = 0
970
                        #degree 90
971
                        elif abs(symbolItem.angle - 1.57) <= allowed_error:
972
                            symbolRotatedAngle = 90
973
                        #degree 180
974
                        elif abs(symbolItem.angle - 3.14) <= allowed_error:
975
                            symbolRotatedAngle = 180
976
                        #degree 270
977
                        elif abs(symbolItem.angle - 4.71) <= allowed_error:
978
                            symbolRotatedAngle = 270
979
                        else:
980
                            symbolRotatedAngle = math.pi / 180 * symbolItem.angle
981

982
                        isDetectOnOrigin = 80 # not necessary
983
                        symbolRotateCount = 0 # not necessary
984
                        symbolOcrOption = 0 # not necessary
985
                        isContainChild = 0 # not necessary
986
                        originalPoint = [symbolItem.origin[0] - symbolItem.loc[0], symbolItem.origin[1] - symbolItem.loc[1]]
987
                        symbolConnectionPoint = []
988
                        baseSymbol = str(symbolItem.parentSymbol)
989
                        additionalSymbol = str(symbolItem.childSymbol)
990
                        isExceptDetect = 0 # not necessary
991
                        detectFlip = symbolItem.flip
992

993
                        searchedSymbolList.append(Symbol(symbolName, symbolType , 
994
                            searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, hitRate, symbolRotatedAngle , 
995
                            isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild , 
996
                            ','.join(str(x) for x in originalPoint), 
997
                            [], 
998
                            baseSymbol, additionalSymbol, isExceptDetect, detectFlip))
999
                        
1000
                        worker.scene.removeItem(symbolItem)
1001
                    
1002
                    pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER)
1003
                    '''
1004
                    # symbolItems = [item for item in worker.scene.items() if issubclass(type(item), SymbolSvgItem)]
1005
                    # appDocData.symbols.extend(symbolItems)
1006
                    # appDocData.allItems.extend(symbolItems)
1007

    
1008
                    # for sym in searchedSymbolList:
1009
                    #    pool.submit(Worker.remove_detected_symbol_image, sym, appDocData.imgSrc)
1010
                    # pool.shutdown(wait = True)
1011

    
1012
                worker.updateBatchProgress.emit(len(drawings), 1)
1013

    
1014
                area = app_doc_data.getArea('Drawing')
1015
                if area is not None:
1016
                    # copy area image
1017
                    area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height),
1018
                               round(area.x):round(area.x + area.width)]
1019

    
1020
                offset = (area.x, area.y) if area is not None else (0, 0)
1021

    
1022
                otherTextInfoList = None
1023
                titleBlockTextInfoList = None
1024
                textInfoList = []
1025
                if worker.isTextChecked:
1026
                    from TextInfo import TextInfo
1027

    
1028
                    worker.text_time = timeit.default_timer()
1029

    
1030
                    textAreas, ocr_image = textDetector.detectTextAreas(area.img if area is not None else app_doc_data.imgSrc,
1031
                                                             offset, listWidget=listWidget, worker=worker)
1032

    
1033
                    # get text area by using symbol setting
1034
                    text_area_symbols = []
1035
                    for symbol in [symbol for symbol in searchedSymbolList if symbol.text_area]:
1036
                        symbol_angle = symbol.getRotatedAngle()
1037
                        for text_area in symbol.text_area:
1038
                            so = [symbol.getWidth(), symbol.getHeight()] if symbol_angle is 0 or symbol_angle is 180\
1039
                                            else [symbol.getHeight(), symbol.getWidth()]
1040
                            text_area_origin = Worker.getCalculatedOriginalPoint(None, str(text_area.center()[0]) + ',' + str(text_area.center()[1]),\
1041
                                                                      symbol_angle, None, None, so[0], so[1], symbol.getDetectFlip())
1042
                            x = text_area_origin[0] - round(text_area.width / 2) if symbol_angle is 0 or symbol_angle is 180\
1043
                                else text_area_origin[0] - round(text_area.height / 2)
1044
                            x = x + symbol.sp[0]
1045
                            y = text_area_origin[1] - round(text_area.height / 2) if symbol_angle is 0 or symbol_angle is 180\
1046
                                else text_area_origin[1] - round(text_area.width / 2)
1047
                            y = y + symbol.sp[1]
1048
                            w = text_area.width if symbol_angle is 0 or symbol_angle is 180 else text_area.height
1049
                            h = text_area.height if symbol_angle is 0 or symbol_angle is 180 else text_area.width
1050

    
1051
                            if symbol_angle is 180 or symbol_angle is 0:
1052
                                text_angle = 0
1053
                            else:
1054
                                text_angle = 90
1055
                            text_area_symbols.append(TextInfo('', x, y, w, h, text_angle))
1056

    
1057
                    for textArea_index in reversed(range(len(textAreas))):
1058
                        is_pop = False
1059
                        for text_area_index in reversed(range(len(text_area_symbols))):
1060
                            if text_area_symbols[text_area_index].contains(textAreas[textArea_index].center) or \
1061
                                    textAreas[textArea_index].contains(text_area_symbols[text_area_index].center):
1062
                                if True: ## select text area from symbol
1063
                                #if text_area_symbols[text_area_index].area > textAreas[textArea_index].area: ## select bigger one
1064
                                    textAreas.pop(textArea_index)
1065
                                    is_pop = True
1066
                                    break
1067
                                else:
1068
                                    text_area_symbols.pop(text_area_index)
1069
                                    break
1070
                            if is_pop:
1071
                                break
1072
                    textAreas.extend(text_area_symbols)
1073
                    # up to here
1074

    
1075
                    if maxProgressValue < 2 * len(textAreas):
1076
                        for _ in range(len(textAreas) - int(maxProgressValue * 0.5)):
1077
                            worker.updateProgress.emit(2 * len(textAreas), None)
1078
                        maxProgressValue = 2 * len(textAreas)
1079
                    else:
1080
                        maxProgressValue = int(maxProgressValue * 0.5) + len(textAreas)
1081

    
1082
                    worker.displayTitle.emit(worker.tr('Detecting texts...'))
1083
                    textDetector.recognizeText(area.img, offset, textAreas, searchedSymbolList, worker,
1084
                                               listWidget, maxProgressValue)
1085
                    textInfoList = textDetector.textInfoList.copy() if textDetector.textInfoList is not None else None
1086
                    otherTextInfoList = textDetector.otherTextInfoList.copy() if textDetector.otherTextInfoList is not None else None
1087
                    titleBlockTextInfoList = textDetector.titleBlockTextInfoList.copy() if textDetector.titleBlockTextInfoList is not None else None
1088

    
1089
                    app_doc_data.activeDrawing.width, app_doc_data.activeDrawing.height = \
1090
                        app_doc_data.imgSrc.shape[::-1]
1091

    
1092
                    app_doc_data.imgName = os.path.splitext(os.path.basename(mainRes))[0]
1093

    
1094
                    worker.text_time = timeit.default_timer() - worker.text_time
1095
                else:
1096
                    import math
1097
                    from TextInfo import TextInfo
1098

    
1099
                    """load texts"""
1100
                    worker.add_predata_to_scene.emit(app_doc_data.activeDrawing, worker.text_scene,  False, True, False, False, False)
1101
                    worker.cond.wait(worker.mutex)
1102
                    """up to here"""
1103
                    textItems = [item for item in worker.text_scene.items() if issubclass(type(item), QEngineeringTextItem)]
1104
                    app_doc_data.texts.extend(textItems)
1105
                    app_doc_data.allItems.extend(textItems)
1106

    
1107
                    lineNoTextItems = [item for item in textItems if type(item) is QEngineeringLineNoTextItem]
1108
                    for lineNoTextItem in lineNoTextItems:
1109
                        lineNoTextItem.set_property('Freeze', False)
1110
                        # lineNoTextItem.freeze_item.update_freeze(lineNoTextItem.prop('Freeze'))
1111
                        lineNoTextItem.explode()
1112

    
1113
                    for textItem in textItems:
1114
                        textInfoList.append(
1115
                            TextInfo(textItem.text(), textItem.loc[0], textItem.loc[1], textItem.size[0],
1116
                                     textItem.size[1], round(math.degrees(textItem.angle))))
1117

    
1118
                        #textItem.owner = None
1119
                        #worker.scene.removeItem(textItem)
1120

    
1121
                worker.updateBatchProgress.emit(len(drawings), 2)
1122

    
1123
                # check symbol validate
1124
                #valid_sym = []
1125
                for index in reversed(range(len(searchedTextSymList))):
1126
                    sym_xmin = searchedTextSymList[index].getSp()[0]
1127
                    sym_ymin = searchedTextSymList[index].getSp()[1]
1128
                    sym_xmax = searchedTextSymList[index].getSp()[0] + searchedTextSymList[index].getWidth()
1129
                    sym_ymax = searchedTextSymList[index].getSp()[1] + searchedTextSymList[index].getHeight()
1130
                    valid_count = searchedTextSymList[index].getHasInstrumentLabel()
1131
                    valid = 0
1132
                    for text_info in textInfoList:
1133
                        info_center = text_info.center
1134
                        if sym_xmin < info_center[0] < sym_xmax and sym_ymin < info_center[1] < sym_ymax:
1135
                            valid += 1
1136
                            break
1137
                            #if valid >= valid_count:
1138
                            #    valid_sym.append(searchedTextSymList[index])
1139
                            #    break
1140
                    if valid < valid_count:
1141
                        searchedSymbolList.pop(searchedSymbolList.index(searchedTextSymList[index]))
1142

    
1143
                # roll back because invalidated symbol was deleted for text detection
1144
                app_doc_data.imgSrc = app_doc_data.activeDrawing.image_origin
1145

    
1146
                for sym in searchedSymbolList:
1147
                        Worker.remove_detected_symbol_image(sym, app_doc_data.imgSrc)
1148
                #pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER)
1149
                #for sym in valid_sym:
1150
                #    pool.submit(Worker.remove_detected_symbol_image, sym, app_doc_data.imgSrc)
1151
                #pool.shutdown(wait=True)
1152
                # up to here
1153

    
1154
                # remove text from image
1155
                textDetector.remove_text_from_image(app_doc_data.imgSrc, [0, 0])
1156
                #textDetector.remove_text_from_image(area.img, offset)
1157
                if not worker.isTextChecked:
1158
                    textInfoList.clear()
1159
                # up to here
1160

    
1161
                removedSymbolImgPath = os.path.join(project.getTempPath(), os.path.basename(mainRes))
1162
                cv2.imwrite(removedSymbolImgPath, app_doc_data.imgSrc)
1163

    
1164
                listWidget.addItem("Recognized symbol count : " + str(len(searchedSymbolList)))
1165

    
1166
                listWidget.addItem(worker.tr('Creating detected infos...'))
1167
                worker.displayTitle.emit(worker.tr('Creating detected infos...'))
1168
                worker.create_detected_items(searchedSymbolList, textInfoList,
1169
                                    otherTextInfoList if otherTextInfoList is not None else [],
1170
                                    titleBlockTextInfoList if titleBlockTextInfoList is not None else [])
1171

    
1172
                if isLineChecked:
1173
                    Worker.recognizeLine(mainRes, listWidget, worker.scene, worker)
1174
                else:
1175
                    """load lines"""
1176
                    worker.add_predata_to_scene.emit(app_doc_data.activeDrawing, worker.line_scene,  False, False, True, False, False)
1177
                    worker.cond.wait(worker.mutex)
1178
                    """up to here"""
1179
                    lineItems = [item for item in worker.line_scene.items()
1180
                                 if issubclass(type(item), QEngineeringLineItem)]
1181
                    app_doc_data.lines.extend(lineItems)
1182
                    app_doc_data.allItems.extend(lineItems)
1183

    
1184
                    for lineItem in lineItems:
1185
                        lineItem.owner = None
1186
                        for conn in lineItem.connectors:
1187
                            conn.connectedItem = None
1188
                        #worker.scene.removeItem(lineItem)
1189

    
1190
                # try to detect nozzle from image drawing
1191
                if worker.isSymbolChecked:
1192
                    worker.displayTitle.emit(worker.tr('Detecting nozzle and flange...'))
1193

    
1194
                    nozzles = []
1195
                    with futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as pool:
1196
                        future_nozzle = {pool.submit(Worker.detect_nozzles, mainRes, symbol, listWidget, worker):
1197
                                           symbol for symbol in targetSymbolList[1]}
1198
                        for future in futures.as_completed(future_nozzle):
1199
                            data = future.result()
1200
                            if data:
1201
                                nozzles.extend(data)
1202

    
1203
                    worker.create_detected_items(nozzles, [], [], [])
1204

    
1205
                    for nozzle in nozzles:
1206
                        Worker.remove_detected_symbol_image(nozzle, app_doc_data.imgSrc)
1207
                # up to here
1208

    
1209
                area = app_doc_data.getArea('Drawing')
1210
                if area is not None:
1211
                    area.img = app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height),
1212
                               round(area.x + 1):round(area.x + area.width)]
1213
                cv2.imwrite(os.path.join(project.getTempPath(), "RECT_" + os.path.basename(mainRes)),
1214
                            app_doc_data.imgSrc)
1215

    
1216
                Worker.drawFoundSymbolsOnCanvas(mainRes, searchedSymbolList, textInfoList, listWidget)
1217

    
1218
                # get difference between original and recognized image
1219
                foundFilePath = os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(mainRes))
1220
                Worker.getDifference(mainRes, foundFilePath)
1221
                # up to here
1222

    
1223
                # connect symbol to symbol
1224
                try:
1225
                    configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line')
1226
                    toler = int(configs[0].value) if configs else 20
1227
                    for symbol in app_doc_data.symbols:
1228
                        matches = [it for it in app_doc_data.symbols if
1229
                                   it is not symbol and symbol.is_connectable(it, toler=toler)]
1230
                        for match in matches:
1231
                            symbol.connect_if_possible(match, toler=int(toler / 5 * 3))
1232
                except Exception as ex:
1233
                    message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1234
                                                                   sys.exc_info()[-1].tb_lineno)
1235
                    worker.displayLog.emit(MessageType.Error, message)
1236
                # up to here
1237

    
1238
                listWidget.addItem('Connecting lines')
1239
                #area = app_doc_data.getArea('Drawing')
1240
                detector = LineDetector(app_doc_data.imgSrc)
1241
                symbols = app_doc_data.symbols
1242
                configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line')
1243
                toler = int(configs[0].value) if configs else 20
1244
                
1245
                # detect flange
1246
                flange_list = []
1247
                blind_list = []
1248
                
1249
                print('flag3.9')
1250
                configs = app_doc_data.getConfigs('Project', 'Operation')
1251
                instrument = int(configs[0].value) if configs else 1
1252
                if app_doc_data.lines:
1253
                    # connect line to symbol
1254
                    try:
1255
                        for line in app_doc_data.lines:
1256
                            matches = [_symbol for _symbol in symbols if _symbol.is_connectable(line, toler=toler)]
1257
                            for _symbol in matches:
1258
                                detector.connectLineToSymbol(line, _symbol, toler=toler)
1259
                    except Exception as ex:
1260
                        message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[
1261
                            -1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
1262
                        worker.displayLog.emit(MessageType.Error, message)
1263
                    # up to here
1264
                    print('flag3.98')
1265
                    # connect line to line
1266
                    try:
1267
                        for line in app_doc_data.lines:
1268
                            matches = [it for it in app_doc_data.lines if
1269
                                       (it is not line) and (not line.isParallel(it))]
1270

    
1271
                            for match in matches:
1272
                                detector.connectLineToLine(match, line, toler)
1273
                        print('flag3.989')
1274
                        # change line type using symbol connection type(info)
1275
                        for sym in symbols:
1276
                            if sym.conn_type:
1277
                                for index in range(len(sym.conn_type)):
1278
                                    item = sym.connectors[index].connectedItem
1279
                                    if item and type(item) is QEngineeringLineItem and sym.conn_type[index] != 'Secondary' and sym.conn_type[index] != 'Primary':
1280
                                        Worker.changeConnectedLineType(item, sym.conn_type[index])
1281

    
1282
                    except Exception as ex:
1283
                        message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1284
                                  f"{sys.exc_info()[-1].tb_lineno}"
1285
                        worker.displayLog.emit(MessageType.Error, message)
1286
                    # up to here
1287
                    print('flag3.99')
1288

    
1289
                    # make short line that can not detected, symbol to symbol
1290
                    try:
1291
                        conns = []
1292
                        for sym in symbols:
1293
                            if sym.conn_type:
1294
                                for index in range(len(sym.conn_type)):
1295
                                    if sym.conn_type[index] == 'Secondary' or sym.conn_type[index] == 'Primary':
1296
                                        item = sym.connectors[index].connectedItem
1297
                                        if item is None:
1298
                                            conns.append(sym.connectors[index])
1299
                        
1300
                        Worker.make_short_lines_sts(conns, worker)
1301

    
1302
                    except Exception as ex:
1303
                        message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1304
                                  f"{sys.exc_info()[-1].tb_lineno}"
1305
                        worker.displayLog.emit(MessageType.Error, message)
1306

    
1307
                    # detect flange
1308
                    try:
1309
                        not_conn = []
1310
                        if instrument == 1:
1311
                            flange = None
1312
                            configs = app_doc_data.getConfigs('Default', 'Flange')
1313
                            flange_name = configs[0].value if 1 == len(configs) else 'flange'
1314
                            flange = QtImageViewer.createSymbolObject(flange_name)
1315

    
1316
                            blind = None
1317
                            configs = app_doc_data.getConfigs('Default', 'Blind')
1318
                            flange_name = configs[0].value if 1 == len(configs) else 'blind flange'
1319
                            blind = QtImageViewer.createSymbolObject(flange_name)
1320

    
1321
                            for sym in symbols:
1322
                                if sym.conn_type:
1323
                                    for index in range(len(sym.conn_type)):
1324
                                        item = sym.connectors[index].connectedItem
1325
                                        if flange and item and type(item) is QEngineeringLineItem and (sym.conn_type[index] == 'Secondary' or sym.conn_type[index] == 'Primary') \
1326
                                            and item.is_piping(True):
1327
                                            point = worker.detectFlangeOnPid(sym, sym.connectors[index], item, app_doc_data.activeDrawing.image_origin)
1328
                                            if point is not None:
1329
                                                flange_list.append(point)
1330

    
1331
                                        elif blind and not item and (sym.conn_type[index] == 'Secondary' or sym.conn_type[index] == 'Primary'):
1332
                                            point = worker.detectFlangeOnPid(sym, sym.connectors[index], None, app_doc_data.activeDrawing.image_origin)
1333
                                            if point is not None:
1334
                                                blind_list.append(point)
1335
                                                not_conn.append(sym.connectors[index])
1336

    
1337
                    except Exception as ex:
1338
                        message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1339
                                  f"{sys.exc_info()[-1].tb_lineno}"
1340
                        worker.displayLog.emit(MessageType.Error, message)
1341
                    # up to here
1342

    
1343
                    # make short line that can not detected, symbol to line
1344
                    try:
1345
                        conns = []
1346
                        for sym in symbols:
1347
                            if sym.conn_type:
1348
                                for index in range(len(sym.conn_type)):
1349
                                    if sym.conn_type[index] == 'Secondary' or sym.conn_type[index] == 'Primary':
1350
                                        item = sym.connectors[index].connectedItem
1351
                                        if item is None and sym.connectors[index] not in not_conn:
1352
                                            conns.append(sym.connectors[index])
1353
                        
1354
                        Worker.make_short_lines_stl(app_doc_data.lines, conns, worker)
1355

    
1356
                    except Exception as ex:
1357
                        message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1358
                                  f"{sys.exc_info()[-1].tb_lineno}"
1359
                        worker.displayLog.emit(MessageType.Error, message)
1360

    
1361
                    # remove line has not connected item
1362
                    try:
1363
                        count = 1
1364
                        while count > 0:
1365
                            count = 0
1366
                            for line in reversed(app_doc_data.lines):
1367
                                if not line.has_connection:
1368
                                    app_doc_data.lines.remove(line)
1369
                                    app_doc_data.allItems.remove(line)
1370
                                    count += 1
1371
                                    for item in app_doc_data.lines + symbols:
1372
                                        for conn in item.connectors:
1373
                                            if conn.connectedItem is line:
1374
                                                conn.connectedItem = None
1375
                    except Exception as ex:
1376
                        message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1377
                                  f"{sys.exc_info()[-1].tb_lineno}"
1378
                        worker.displayLog.emit(MessageType.Error, message)
1379
                    # up to here
1380

    
1381
                    # change line type using visual pattern
1382
                    try:
1383
                        lines = []
1384
                        for sym in symbols:
1385
                            if sym.conn_type:
1386
                                for index in range(len(sym.conn_type)):
1387
                                    if sym.conn_type[index] != 'Secondary' and sym.conn_type[index] != 'Primary':
1388
                                        item = sym.connectors[index].connectedItem
1389
                                        if item and type(item) is QEngineeringLineItem:
1390
                                            if item in lines:
1391
                                                continue
1392
                                            else:
1393
                                                run = [item]
1394
                                                Worker.find_connected_line(run, item)
1395
                                                lines.extend(run)
1396
                        
1397
                        Worker.changeVisualLineType(lines, worker)
1398

    
1399
                    except Exception as ex:
1400
                        message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1401
                                  f"{sys.exc_info()[-1].tb_lineno}"
1402
                        worker.displayLog.emit(MessageType.Error, message)
1403
                    # up to here
1404
                print('flag4')
1405
                worker.create_unknown_items(mainRes)
1406
                worker.add_detected_items_to_scene.emit(worker.scene, [flange_list, blind_list])
1407
                worker.cond.wait(worker.mutex)
1408

    
1409
                # run preset
1410
                presets = app_doc_data.getSymbolPreset()
1411
                for preset in presets:
1412
                    worker.preset_execute.emit(worker.scene, preset[0], preset[1], preset[2], preset[3])
1413
                    worker.cond.wait(worker.mutex)
1414

    
1415
                # remove piping line and text for instrument mode
1416
                try:
1417
                    if instrument == -1:
1418
                        # remove piping line
1419
                        remove_items = []
1420

    
1421
                        for line in reversed(app_doc_data.lines):
1422
                            if line.is_piping(True):
1423
                                app_doc_data.lines.remove(line)
1424
                                app_doc_data.allItems.remove(line)
1425
                                remove_items.append(line)
1426
                                '''
1427
                                for item in app_doc_data.lines + app_doc_data.symbols:
1428
                                    for conn in item.connectors:
1429
                                        if conn.connectedItem is line:
1430
                                            conn.connectedItem = None
1431
                                '''
1432

    
1433
                        # remove text
1434
                        for text in reversed([text for text in app_doc_data.texts if type(text) is QEngineeringTextItem and text.area == 'Drawing']):
1435
                            contained = False
1436
                            for symbol in app_doc_data.symbols:
1437
                                if symbol.includes([text.sceneBoundingRect().center().x(), text.sceneBoundingRect().center().y()]):
1438
                                    contained = True
1439
                                    break
1440
                            if not contained:
1441
                                remove_items.append(text)
1442

    
1443
                        ## load code tables
1444
                        app_doc_data.getReplaceTables()
1445
                        app_doc_data.getCustomTables()
1446
                        app_doc_data.loadSymbolAttributeCodeTables()
1447
                        for index in reversed(range(len(remove_items))):
1448
                            if issubclass(type(remove_items[index]), QEngineeringTextItem):
1449
                                found = False
1450
                                for table in CodeTable.TABLES.values():
1451
                                    if table.find_match_exactly(remove_items[index].text()):
1452
                                        found = True
1453
                                        break
1454
                                if found:
1455
                                    remove_items.pop(index)
1456

    
1457
                        worker.item_remove.emit(remove_items)
1458
                        worker.cond.wait(worker.mutex)
1459
                        # up to here
1460

    
1461
                except Exception as ex:
1462
                    message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1463
                                  f"{sys.exc_info()[-1].tb_lineno}"
1464
                    worker.displayLog.emit(MessageType.Error, message)
1465
                # up to here
1466

    
1467
                print('flag5')
1468
                worker.save_scene.emit(worker.scene)
1469
                worker.cond.wait(worker.mutex)
1470

    
1471
                worker.scene._end = False
1472
                # clear drawing
1473
                app_doc_data.activeDrawing.image = None
1474

    
1475
                worker.updateBatchProgress.emit(len(drawings), 1)
1476
        except Exception as ex:
1477
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1478
                      f"{sys.exc_info()[-1].tb_lineno}"
1479
            worker.displayLog.emit(MessageType.Error, message)
1480
        finally:
1481
            pass
1482

    
1483
    @staticmethod
1484
    def make_short_lines_sts(connectors, worker):
1485
        """ make short lines logically, symbol to symbol """
1486

    
1487
        import math
1488
        from EngineeringAbstractItem import QEngineeringAbstractItem
1489

    
1490
        try:
1491
            app_doc_data = AppDocData.instance()
1492
            lineLengthConfigs = app_doc_data.getConfigs('Small Line Minimum Length', 'Min Length')
1493
            lineMinLength = int(lineLengthConfigs[0].value) if 1 == len(lineLengthConfigs) else 25
1494
            lineMaxLength = lineMinLength * 3
1495
            windowSize = app_doc_data.getSlidingWindowSize()
1496
            thickness = int(windowSize[1] / 2)
1497

    
1498
            new_lines = []
1499

    
1500
            # symbol to symbol
1501
            connectors = [conn for conn in connectors if conn.connectedItem is None]
1502
            for connector1 in connectors:
1503
                if connector1.connectedItem:
1504
                    continue
1505

    
1506
                selected = None
1507
                min_dist = sys.maxsize
1508

    
1509
                for connector2 in connectors:
1510
                    if connector2.connectedItem or connector1 is connector2:
1511
                        continue
1512

    
1513
                    dx = connector1.center()[0] - connector2.center()[0]
1514
                    dy = connector1.center()[1] - connector2.center()[1]
1515
                    dist = math.sqrt(dx * dx + dy * dy)
1516
                    if dist < lineMaxLength and dist < min_dist and min(abs(dx), abs(dy)) < thickness:
1517
                        selected = connector2
1518
                        min_dist = dist
1519

    
1520
                if selected:
1521
                    new_line = QEngineeringLineItem(vertices=[connector1.center(), selected.center()], thickness=thickness)
1522
                    new_line.area = 'Drawing'
1523

    
1524
                    connector1.connect(new_line)
1525
                    selected.connect(new_line)
1526
                    new_line.connectors[0].connect(connector1.parentItem())
1527
                    new_line.connectors[1].connect(selected.parentItem())
1528
                    new_lines.append(new_line)
1529

    
1530
            app_doc_data.lines.extend(new_lines)
1531
            app_doc_data.allItems.extend(new_lines)
1532

    
1533
        except Exception as ex:
1534
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1535
                                                           sys.exc_info()[-1].tb_lineno)
1536
            worker.displayLog.emit(MessageType.Error, message)
1537

    
1538
    @staticmethod
1539
    def make_short_lines_stl(lines, connectors, worker):
1540
        """ make short lines logically, symbol to line """
1541

    
1542
        import math
1543
        from EngineeringAbstractItem import QEngineeringAbstractItem
1544

    
1545
        try:
1546
            app_doc_data = AppDocData.instance()
1547
            lineLengthConfigs = app_doc_data.getConfigs('Small Line Minimum Length', 'Min Length')
1548
            lineMinLength = int(lineLengthConfigs[0].value) if 1 == len(lineLengthConfigs) else 25
1549
            lineMaxLength = lineMinLength * 3
1550
            windowSize = app_doc_data.getSlidingWindowSize()
1551
            thickness = int(windowSize[1] / 2)
1552

    
1553
            new_lines = []
1554

    
1555
            verticals = []
1556
            horizontals = []
1557

    
1558
            for line in lines:
1559
                if line.isVertical():
1560
                    verticals.append(line)
1561
                else:
1562
                    horizontals.append(line)
1563

    
1564
            # symbol to line
1565
            for connector in connectors:
1566
                direction = connector.dir()
1567
                symbol = connector.parentItem()
1568
                if direction is None:
1569
                    dx = connector.center()[0] - symbol.origin[0]
1570
                    dy = connector.center()[1] - symbol.origin[1]
1571
                    length = math.sqrt(dx * dx + dy * dy)
1572
                    if length <= 0:
1573
                        continue
1574
                    dx /= length
1575
                    dy /= length
1576
                else:
1577
                    dx, dy = direction.x(), direction.y()
1578

    
1579
                _lines = None
1580
                if abs(dx) < 0.1:
1581
                    _lines =  horizontals
1582
                elif abs(dy) < 0.1:
1583
                    _lines = verticals
1584
                
1585
                if not _lines:
1586
                    continue
1587

    
1588
                selected = None
1589
                min_dist = sys.maxsize
1590
                for line in _lines:
1591
                    dist = line.distanceTo(connector.center())
1592
                    if dist < lineMaxLength and dist < min_dist:
1593
                        selected = line
1594
                        min_dist = dist
1595

    
1596
                if selected:
1597
                    if selected in verticals:
1598
                        min_x_y = min(selected.start_point()[1], selected.end_point()[1])
1599
                        max_x_y = max(selected.start_point()[1], selected.end_point()[1])
1600
                        con_x_y = connector.center()[1]
1601
                        start_point = [selected.start_point()[0], connector.center()[1]]
1602
                    else:
1603
                        min_x_y = min(selected.start_point()[0], selected.end_point()[0])
1604
                        max_x_y = max(selected.start_point()[0], selected.end_point()[0])
1605
                        con_x_y = connector.center()[0]
1606
                        start_point = [connector.center()[0], selected.start_point()[1]]
1607

    
1608
                    end_point = connector.center()
1609

    
1610
                    if min_x_y < con_x_y and con_x_y < max_x_y:
1611
                        new_line = QEngineeringLineItem(vertices=[start_point, end_point], thickness=thickness)
1612
                        new_line.area = 'Drawing'
1613

    
1614
                        connector.connect(new_line)
1615
                        new_line.connectors[0].connect(selected, at=QEngineeringAbstractItem.CONNECTED_AT_BODY)
1616
                        new_line.connectors[1].connect(symbol)
1617
                        new_lines.append(new_line)
1618

    
1619
            app_doc_data.lines.extend(new_lines)
1620
            app_doc_data.allItems.extend(new_lines)
1621

    
1622
        except Exception as ex:
1623
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1624
                                                           sys.exc_info()[-1].tb_lineno)
1625
            worker.displayLog.emit(MessageType.Error, message)
1626

    
1627
    @staticmethod
1628
    def changeVisualLineType(all_lines, worker):
1629
        """ change line type by using visual data"""
1630

    
1631
        from LineDetector import LineDetector
1632
        from EngineeringAbstractItem import QEngineeringAbstractItem
1633

    
1634
        try:
1635
            app_doc_data = AppDocData.instance()
1636
            image = app_doc_data.activeDrawing.image_origin
1637
            imgNot = np.ones(image.shape, np.uint8) * 255
1638
            image = cv2.bitwise_xor(image, imgNot)
1639

    
1640
            #electric = [137, [1,1,1,1,1,1,1,1,1], sys.maxsize]
1641
            #software = [187, [0.948,1.081,0.932,1.081,0.932,1.068,0.932,1.081,0.929], sys.maxsize]
1642
            #line_patterns = {'Electric':electric, 'Software':software }
1643
            line_patterns = []
1644
            line_shapes = []
1645

    
1646
            # can not determine piping line cuz always matched
1647
            lines = [line for line in all_lines if line.lineType != 'Secondary' and line.lineType != 'Primary']
1648

    
1649
            line_names = app_doc_data.getSymbolListByType('type', 'Line')
1650
            if len(line_names) != 0:
1651
                max_avg_area = 0
1652
                for line_name in line_names:
1653
                    line = line_name
1654
                    line_path = line.getPath()
1655
                    if not os.path.isfile(line_path):
1656
                        continue
1657

    
1658
                    line_img = cv2.cvtColor(cv2.imread(line_path), cv2.COLOR_BGR2GRAY)
1659
                    imgNot = np.ones(line_img.shape, np.uint8) * 255
1660
                    line_img = cv2.bitwise_xor(line_img, imgNot)
1661

    
1662
                    contours, _ = cv2.findContours(line_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
1663

    
1664
                    line_thickness = 0
1665

    
1666
                    if len(contours) < 3:
1667
                        max_x, max_y, min_x, min_y = 0, 0, sys.maxsize, sys.maxsize
1668
                        for rect in [cv2.boundingRect(contour) for contour in contours]:
1669
                            if rect[0] < min_x:
1670
                                min_x = rect[0]
1671
                            if rect[0] + rect[2] > max_x:
1672
                                max_x = rect[0] + rect[2]
1673
                            if rect[1] < min_y:
1674
                                min_y = rect[1]
1675
                            if rect[1] + rect[3] > max_y:
1676
                                max_y = rect[1] + rect[3]
1677

    
1678
                        thickness = min(max_y - min_y, max_x - max_x)
1679
                        if line_thickness < thickness:
1680
                            line_thickness = thickness
1681

    
1682
                        line_shapes.append([line_img[min_y:max_y, min_x:max_x], sys.maxsize, line.getThreshold(), line.getBaseSymbol()])
1683
                        continue
1684

    
1685
                    i = 1 if line_img.shape[0] > line_img.shape[1] else 0
1686
                    boundingBoxes = [cv2.boundingRect(contour) for contour in contours]
1687
                    (contours, boundingBoxes) = zip(*sorted(zip(contours, boundingBoxes), key=lambda b:b[1][i], reverse=False))
1688

    
1689
                    avg_area = sum([cv2.contourArea(contour) for contour in contours]) / len(contours)
1690
                    ratio_area = [cv2.contourArea(contour) / avg_area for contour in contours]
1691
                    if avg_area > max_avg_area:
1692
                        max_avg_area = avg_area
1693

    
1694
                    line_patterns.append([avg_area, ratio_area, sys.maxsize, line.getThreshold(), line.getBaseSymbol()])
1695
            else:
1696
                return
1697

    
1698
            lines_found = []
1699
            lines_shape = []
1700

    
1701
            # detemine line type : broken
1702
            for line in lines:
1703
                rect = line.boundingRect()
1704
                image_line = image[round(rect.y() - 1):round(rect.y() + rect.height() + 1), round(rect.x() - 1):round(rect.x() + rect.width() + 1)]
1705
                contours, _ = cv2.findContours(image_line, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
1706

    
1707
                # skip piping line
1708
                if len(contours) < 3 or (sum([cv2.contourArea(contour) for contour in contours]) / len(contours)) > max_avg_area * 2:
1709
                    if rect.width() > rect.height():
1710
                        image_line = image[round(rect.y() - line_thickness / 2):round(rect.y() + rect.height() + line_thickness / 2), round(rect.x() - 1):round(rect.x() + rect.width() + 1)]
1711
                    else:
1712
                        image_line = image[round(rect.y() - 1):round(rect.y() + rect.height() + 1), round(rect.x() - line_thickness / 2):round(rect.x() + rect.width() + line_thickness / 2)]
1713

    
1714
                    contours, _ = cv2.findContours(image_line, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
1715
                    max_x, max_y, min_x, min_y = 0, 0, sys.maxsize, sys.maxsize
1716
                    for rect in [cv2.boundingRect(contour) for contour in contours]:
1717
                        if rect[0] < min_x:
1718
                            min_x = rect[0]
1719
                        if rect[0] + rect[2] > max_x:
1720
                            max_x = rect[0] + rect[2]
1721
                        if rect[1] < min_y:
1722
                            min_y = rect[1]
1723
                        if rect[1] + rect[3] > max_y:
1724
                            max_y = rect[1] + rect[3]
1725
                    if max_y == 0 or max_x == 0:
1726
                        continue
1727
                    
1728
                    lines_shape.append([line, image_line[min_y:max_y, min_x:max_x]])
1729
                    continue
1730

    
1731
                vertical = LineDetector.is_vertical([line.start_point()[0], line.start_point()[1], line.end_point()[0], line.end_point()[1]])
1732

    
1733
                i = 1 if vertical else 0
1734
                boundingBoxes = [cv2.boundingRect(contour) for contour in contours]
1735
                (contours, boundingBoxes) = zip(*sorted(zip(contours, boundingBoxes), key=lambda b:b[1][i], reverse=False))
1736

    
1737
                avg_area = sum([cv2.contourArea(contour) for contour in contours]) / len(contours)
1738
                ratio_area = [cv2.contourArea(contour) / avg_area for contour in contours]
1739

    
1740
                for line_pattern in line_patterns:
1741
                    line_type = line_pattern[-1]
1742

    
1743
                    line_pattern[2] = sys.maxsize
1744
                    ratio_area_cal = [ratio * avg_area / line_pattern[0] for ratio in ratio_area]
1745
                    long_line = ratio_area_cal if len(ratio_area_cal) > len(line_pattern[1]) else line_pattern[1]
1746
                    short_line = line_pattern[1] if len(ratio_area_cal) > len(line_pattern[1]) else ratio_area_cal
1747

    
1748
                    min_error = sys.maxsize
1749
                    for offset in range(len(long_line) - len(short_line) + 1):
1750
                        error = 0
1751
                        for index in range(len(short_line)):
1752
                            error += abs(short_line[index] - long_line[index + offset])
1753
                        error = error / len(short_line)
1754
                        if error < min_error:
1755
                            min_error = error
1756

    
1757
                    line_pattern[2] = min_error
1758

    
1759
                line_type_founds = sorted([(line_pattern[-1], line_pattern[2]) for line_pattern in line_patterns], key=lambda error:error[1])
1760
                if line_type_founds:
1761
                    lines_found.append([line, line_type_founds[0]])
1762

    
1763
                '''
1764
                # feature matching not work
1765
                orb = cv2.ORB_create()
1766
                kp1, des1 = orb.detectAndCompute(image_line, None)
1767
                kp2, des2 = orb.detectAndCompute(image_line, None)
1768
    
1769
                bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
1770
                matches = bf.match(des1, des2)
1771
                matches = sorted(matches, key=lambda x: x.distance)
1772
    
1773
                good = []
1774
                for m, n in matches:
1775
                    if m.distance < 0.75 * n.distance:
1776
                        good.append([m])
1777
    
1778
                sift = cv2.xfeatures2d.SIFT_create()
1779
                kp1, des1 = sift.detectAndCompute(image_line, None)
1780
                kp2, des2 = sift.detectAndCompute(image_line, None)
1781
    
1782
                bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)
1783
                matches = bf.match(des1, des2)
1784
                matches = sorted(matches, key=lambda x: x.distance)
1785
    
1786
                good = []
1787
                for m, n in matches:
1788
                    if m.distance < 0.75 * n.distance:
1789
                        good.append([m])
1790
                '''
1791
            # determine line type : solid
1792
            for line, image_line in lines_shape:
1793
                for line_shape in line_shapes:
1794
                    line_type = line_shape[-1]
1795

    
1796
                    shape = line_shape[0].copy()
1797
                    line_shape[1] = sys.maxsize
1798

    
1799
                    big = shape if max(shape.shape) > max(image_line.shape) else image_line
1800
                    small = image_line if max(shape.shape) > max(image_line.shape) else shape
1801

    
1802
                    if big.shape[0] == max(max(big.shape), max(small.shape)):
1803
                        mask = np.zeros([big.shape[0] + 40, max(big.shape[1], max(small.shape)) + 40], np.uint8)
1804
                    else:
1805
                        mask = np.zeros([max(big.shape[0], max(small.shape)) + 40, big.shape[1] + 40], np.uint8)
1806

    
1807
                    mask[20:big.shape[0] + 20, 20:big.shape[1] + 20] = big
1808
                    big = mask
1809

    
1810
                    searchedInfos = []
1811
                    steps = [False, True]
1812
                    for flipped in steps:
1813
                        symGray = small.copy()
1814
                        if flipped:
1815
                            symGray = cv2.flip(symGray, 1)
1816

    
1817
                        symbolRotatedAngle = 0
1818
                        for rc in range(4):
1819
                            sw, sh = symGray.shape[::-1]
1820

    
1821
                            r_w, r_h = big.shape[::-1]
1822
                            if r_w < sw or r_h < sh:
1823
                                symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
1824
                                symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
1825
                                continue
1826

    
1827
                            tmRes = cv2.matchTemplate(big, symGray, cv2.TM_CCOEFF_NORMED)
1828
                            _, max_val, __, max_loc = cv2.minMaxLoc(tmRes)
1829

    
1830
                            if max_val > line_shape[-2]:
1831
                                searchedInfos.append(1 - max_val)
1832

    
1833
                            symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
1834
                            symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
1835

    
1836
                    if searchedInfos:
1837
                        line_shape[1] = sorted(searchedInfos)[0]
1838

    
1839
                line_type_founds = sorted([(line_shape[-1], (1 - line_shape[-2] - line_shape[1]) * 2) for line_shape in line_shapes \
1840
                                            if line_shape[1] < (1 - line_shape[-2])], key=lambda error: error[1], reverse=True)
1841
                if line_type_founds:
1842
                    lines_found.append([line, line_type_founds[0]])
1843
                '''
1844
                if line_type_founds and line_type_founds[0][1] < 0.4:
1845
                    if line_type_founds[0][0] == 'Connect To Process' and len(line_type_founds) > 1:
1846
                        if line_type_founds[1][1] < 0.4:
1847
                            lines_found.append([line, line_type_founds[1]])
1848
                        else:
1849
                            lines_found.append([line, line_type_founds[0]])
1850
                    else:
1851
                        lines_found.append([line, line_type_founds[0]])
1852
                '''
1853

    
1854
            line_runs = []
1855
            for line_found in lines_found:
1856
                inserted = False
1857
                for line_run in line_runs:
1858
                    if line_found[0] in line_run:
1859
                        inserted = True
1860
                        break
1861

    
1862
                if inserted:
1863
                    continue
1864
                else:
1865
                    run = [line_found[0]]
1866
                    Worker.find_connected_line(run, line_found[0])
1867
                    line_runs.append(run)
1868

    
1869
            for line_run in line_runs:
1870
                _lines_found = []
1871
                for _line in line_run:
1872
                    _lines = [line_found[0] for line_found in lines_found]
1873
                    if _line in _lines:
1874
                        index = _lines.index(_line)
1875
                        _lines_found.append(lines_found[index])
1876
                _line_found = sorted(_lines_found, key=lambda param:param[1][1])[0]
1877
                Worker.changeConnectedLineType(_line_found[0], _line_found[1][0], visual_marker=True)
1878

    
1879
            # change line type: connected at body
1880
            remains = [line for line in app_doc_data.lines if not hasattr(line, 'visual_marker')]
1881
            while True:
1882
                remains_count = len(remains)
1883

    
1884
                for _line in remains:
1885
                    matches = [conn for conn in _line.connectors if conn.connectedItem and \
1886
                                    conn._connected_at == QEngineeringAbstractItem.CONNECTED_AT_BODY and hasattr(conn.connectedItem, 'visual_marker')]
1887
                    if matches:
1888
                        Worker.changeConnectedLineType(_line, matches[0].connectedItem.lineType, visual_marker=True)
1889
                        break
1890
                    
1891
                remains = [line for line in app_doc_data.lines if not hasattr(line, 'visual_marker')]
1892
                if remains_count == len(remains):
1893
                    break
1894

    
1895
        except Exception as ex:
1896
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1897
                                                           sys.exc_info()[-1].tb_lineno)
1898
            worker.displayLog.emit(MessageType.Error, message)
1899

    
1900
    @staticmethod
1901
    def find_connected_line(lines, line):
1902
        if type(line.connectors[0].connectedItem) is QEngineeringLineItem and line.connectors[0].connectedItem not in lines and\
1903
            (line.connectors[0].connectedItem.connectors[0].connectedItem is line or
1904
                line.connectors[0].connectedItem.connectors[1].connectedItem is line):
1905
            current_line = line.connectors[0].connectedItem
1906
            lines.append(current_line)
1907
            Worker.find_connected_line(lines, current_line)
1908

    
1909
        if type(line.connectors[1].connectedItem) is QEngineeringLineItem and line.connectors[1].connectedItem not in lines and\
1910
            (line.connectors[1].connectedItem.connectors[0].connectedItem is line or
1911
                line.connectors[1].connectedItem.connectors[1].connectedItem is line):
1912
            current_line = line.connectors[1].connectedItem
1913
            lines.append(current_line)
1914
            Worker.find_connected_line(lines, current_line)
1915

    
1916
    @staticmethod
1917
    def changeConnectedLineType(line, lineType, visual_marker=False):
1918
        # for visual line type detection
1919
        if visual_marker:
1920
            line.visual_marker = visual_marker
1921

    
1922
        line.lineType = lineType
1923
        if type(line.connectors[0].connectedItem) is QEngineeringLineItem and \
1924
                (line.connectors[0].connectedItem.connectors[0].connectedItem is line or
1925
                 line.connectors[0].connectedItem.connectors[1].connectedItem is line) and \
1926
                line.connectors[0].connectedItem.lineType is not lineType:
1927
            Worker.changeConnectedLineType(line.connectors[0].connectedItem, lineType, visual_marker=visual_marker)
1928

    
1929
        if type(line.connectors[1].connectedItem) is QEngineeringLineItem and \
1930
                (line.connectors[1].connectedItem.connectors[0].connectedItem is line or
1931
                 line.connectors[1].connectedItem.connectors[1].connectedItem is line) and \
1932
                line.connectors[1].connectedItem.lineType is not lineType:
1933
            Worker.changeConnectedLineType(line.connectors[1].connectedItem, lineType, visual_marker=visual_marker)
1934

    
1935
    '''
1936
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
1937
                    2018.05.28  Jeongwoo    Add xmlPath Parameter and append LineInfo into xml
1938
                    2018.05.29  Jeongwoo    Change method to add item
1939
                    2018.05.30  Jeongwoo    Remove parameter (xmlPath)
1940
                    humkyung 2018.06.11 add drawing path to parameter and write recognized lines to image
1941
                    humkyung 2018.07.04 call arrangeLinePosition after creating line
1942
    '''
1943

    
1944
    @staticmethod
1945
    def recognizeLine(path, listWidget, graphicsView, worker):
1946
        from shapely.geometry import Point, LineString
1947
        from SymbolSvgItem import SymbolSvgItem
1948
        from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
1949
        from EngineeringLineNoTextItem import QEngineeringLineNoTextItem
1950
        from EngineeringTextItem import QEngineeringTextItem
1951
        from EngineeringLineItem import QEngineeringLineItem
1952
        from LineDetector import LineDetector
1953

    
1954
        try:
1955
            listWidget.addItem('Starting line recognition')
1956
            worker.displayTitle.emit(worker.tr('Detecting lines...'))
1957

    
1958
            app_doc_data = AppDocData.instance()
1959
            project = app_doc_data.getCurrentProject()
1960

    
1961
            # detect line
1962
            connectedLines = []
1963

    
1964
            area = app_doc_data.getArea('Drawing')
1965
            if area is not None:
1966
                area.img = app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height),
1967
                            round(area.x + 1):round(area.x + area.width)]
1968

    
1969
            Worker.remove_equipment_package(worker.scene, area)
1970

    
1971
            area.img = worker.remove_small_objects(area.img)
1972
            detector = LineDetector(area.img)
1973

    
1974
            symbols = []
1975
            for item in app_doc_data.symbols:
1976
                if issubclass(type(item), SymbolSvgItem):
1977
                    symbols.append(item)
1978
                    res = detector.detectConnectedLine(item, round(area.x), round(area.y))
1979
                    if res is not None:
1980
                        connectedLines.extend(res)
1981

    
1982
            # line detection without symbol connection point info
1983
            configs = app_doc_data.getConfigs('Line', 'Gap')
1984
            if configs and int(configs[0].value) is 1:
1985
                remainLines = detector.detect_line_without_symbol()
1986
                windowSize = app_doc_data.getSlidingWindowSize()
1987
                thickness = int(windowSize[1] / 2)
1988

    
1989
                for line in remainLines:
1990
                    line.append(thickness)
1991
                connectedLines.extend(remainLines)
1992

    
1993
            configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line')
1994
            toler = int(configs[0].value) if configs else 20
1995

    
1996
            symbol_areas = []
1997
            for symbol in symbols:
1998
                symbol_areas.append(QRect(symbol.loc[0] - area.x, symbol.loc[1] - area.y, symbol.size[0], symbol.size[1]))
1999

    
2000
            detector.mergeLines(connectedLines, toler=toler, symbol_areas=symbol_areas)
2001

    
2002
            for pts in connectedLines:
2003
                line = QEngineeringLineItem(
2004
                    vertices=[(area.x + param[0], area.y + param[1]) for param in pts[:-1]], thickness=pts[2])
2005
                line.area = 'Drawing'
2006

    
2007
                app_doc_data.lines.append(line)
2008
                app_doc_data.allItems.append(line)
2009
        except Exception as ex:
2010
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
2011
                                                           sys.exc_info()[-1].tb_lineno)
2012
            worker.displayLog.emit(MessageType.Error, message)
2013
        finally:
2014
            listWidget.addItem('Finishing line recognition')
2015
            worker.finished.emit()
2016

    
2017
    '''
2018
        @history    2018.04.24  Jeongwoo    Add isExceptDetect Field
2019
                    2018.05.09  Jeongwoo    Add targetSymbolList clear
2020
                    humkyung 2018.07.07 store symbols to list as like [equipments],[nozzles],[symbols]
2021
    '''
2022

    
2023
    @staticmethod
2024
    def initTargetSymbolDataList():
2025
        global targetSymbolList
2026

    
2027
        targetSymbolList.clear()
2028
        app_doc_data = AppDocData.instance()
2029
        symbolList = app_doc_data.getTargetSymbolList()
2030
        equipments = [item for item in symbolList if app_doc_data.getSymbolCategoryByType(item.getType()) == 'Equipment']
2031
        nozzles = [item for item in symbolList if item.getType() == 'Nozzles']
2032
        # [[equipments],[nozzles],[symbols]]
2033
        targetSymbolList.append(equipments)
2034
        targetSymbolList.append(nozzles)
2035
        targetSymbolList.append([item for item in symbolList if item not in equipments and item not in nozzles])
2036

    
2037
        return targetSymbolList
2038

    
2039
    '''
2040
        @brief  detect equipment
2041
        @author humkyung
2042
        @date   2018.07.07
2043
    '''
2044

    
2045
    @staticmethod
2046
    def detect_nozzles(mainRes, targetSymbol, listWidget, worker):
2047
        res = []
2048
        try:
2049
            app_doc_data = AppDocData.instance()
2050

    
2051
            nozzles = Worker.detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker)
2052
            equipments = [symbol for symbol in searchedSymbolList if app_doc_data.isEquipmentType(symbol.getType())]
2053
            for equipment in equipments:
2054
                rect = QRectF(equipment.sp[0], equipment.sp[1], equipment.width, equipment.height)
2055
                rect.adjust(-equipment.width*0.1, -equipment.height*0.1, equipment.width*0.1, equipment.height*0.1)
2056
                matches = [nozzle for nozzle in nozzles if rect.contains(nozzle.rect)]
2057
                res.extend(matches)
2058
                for match in matches:
2059
                    nozzles.remove(match)
2060
        except Exception as ex:
2061
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
2062
                                                           sys.exc_info()[-1].tb_lineno)
2063
            worker.displayLog(MessageType.Error, message)
2064

    
2065
        return res
2066

    
2067
    '''
2068
        @brief      detect symbol on PID
2069
        @author     jwkim
2070
        @date   
2071
        @history    humkyung 2018.04.06 check if symbol file exists
2072
                    Jeongwoo 2018.05.29 Change method to adjust detail symbol location with hit-rate. Not feature point count
2073
                                        Change parameter on add symbol part (mpCount → hitRate)
2074
                                        Remove unusing calculation (avg)
2075
                    Jeongwoo 2018.06.27 Remove part to split P&ID image and for loop
2076
                    humkyung 2018.07.07 return searched symbols
2077
    '''
2078

    
2079
    @staticmethod
2080
    def detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker):
2081
        import copy
2082
        global ocrCompletedSrc
2083
        global threadLock
2084
        global maxProgressValue
2085

    
2086
        try:
2087
            forTraining = worker.isTrainingChecked
2088
            symbolName = targetSymbol.getName()
2089
            symbolType = targetSymbol.getType()
2090
            symbolPath = targetSymbol.getPath()
2091
            symbolThreshold = targetSymbol.getThreshold() if not forTraining else targetSymbol.getThreshold() * 0.90
2092
            symbolMinMatchCount = targetSymbol.getMinMatchCount()
2093
            isDetectOnOrigin = targetSymbol.getIsDetectOnOrigin()
2094
            symbolRotateCount = targetSymbol.getRotationCount()
2095
            symbolOcrOption = targetSymbol.getOcrOption()
2096
            isContainChild = targetSymbol.getIsContainChild()
2097
            symbolOriginalPoint = targetSymbol.getOriginalPoint()
2098
            symbolConnectionPoint = targetSymbol.getConnectionPoint()
2099
            baseSymbol = targetSymbol.getBaseSymbol()
2100
            additionalSymbol = targetSymbol.getAdditionalSymbol()
2101
            isExceptDetect = targetSymbol.getIsExceptDetect()
2102
            detectFlip = targetSymbol.getDetectFlip()
2103
            hasInstrumentLabel = targetSymbol.getHasInstrumentLabel()
2104
            text_area = targetSymbol.getText_area()
2105

    
2106
            # check if symbol file is target or not
2107
            if isExceptDetect == 1:
2108
                item = QListWidgetItem('{} file is not target'.format(symbolName))
2109
                item.setBackground(QColor('green'))
2110
                listWidget.addItem(item)
2111
                return
2112

    
2113
            foundSymbolCount = 0
2114

    
2115
            # check if symbol file exists
2116
            if not os.path.isfile(symbolPath):
2117
                item = QListWidgetItem('{} file not found'.format(symbolName))
2118
                item.setBackground(QColor('red'))
2119
                listWidget.addItem(item)
2120
                return
2121
            # up to here
2122

    
2123
            sym = cv2.imread(symbolPath, 1)
2124
            symGray = Worker.cvtGrayImage(sym)
2125
            symGrayOri = copy.copy(symGray)
2126
            ## TODO: 이진화 시켰을때 심볼이 검출되지 않음
2127
            ## symGray = cv2.threshold(cvtGrayImage(sym), 127, 255, cv2.THRESH_BINARY)[1]
2128
            ## cv2.imshow('symbol', symGray)
2129
            ## cv2.waitKey(0)
2130
            sow, soh = symGray.shape[::-1]  # symbol original w, h
2131

    
2132
            offsetDrawingArea = []
2133
            app_doc_data = AppDocData.instance()
2134
            area = app_doc_data.getArea('Drawing')
2135
            if area is not None:
2136
                roiItem = Worker.remove_small_objects(area.img, 0, sow * soh * 0.5) if hasInstrumentLabel else area.img
2137
                offsetDrawingArea.append(area.x)
2138
                offsetDrawingArea.append(area.y)
2139
            else:
2140
                offsetDrawingArea.append(0)
2141
                offsetDrawingArea.append(0)
2142
                if isDetectOnOrigin == 1:
2143
                    roiItem = app_doc_data.imgSrc
2144
                else:
2145
                    roiItem = ocrCompletedSrc
2146
            srcWidth, srcHeight = roiItem.shape[::-1]
2147

    
2148
            roiItemSp = (0, 0)
2149
            roiItemEp = (srcWidth, srcHeight)
2150

    
2151
            # try to recognize symbol twice(first one is normal, second one is flipped)
2152
            steps = [False, True] if detectFlip else [False]
2153
            for flipped in steps:
2154
                if flipped:
2155
                    symGray = symGrayOri
2156
                    symGray = cv2.flip(symGray, 1)
2157

    
2158
                symbolRotatedAngle = 0
2159
                #for rc in range((symbolRotateCount + 1) * 2):  # Rotation count를 사용자 기준으로 받아서 1을 더한 후 사용
2160
                for rc in range(symbolRotateCount + 1):  # Rotation count를 사용자 기준으로 받아서 1을 더한 후 사용
2161
                    sw, sh = symGray.shape[::-1]
2162
                    roiw = (roiItemEp[0] - roiItemSp[0])
2163
                    roih = (roiItemEp[1] - roiItemSp[1])
2164

    
2165
                    # get Rotated Original Point
2166
                    originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint,
2167
                                                                      symbolRotatedAngle, sw, sh, sow, soh, flipped)
2168
                    connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw,
2169
                                                                          sh, sow, soh, flipped)
2170

    
2171
                    # Case : symbol is bigger than roi
2172
                    if roiw < sw or roih < sh:
2173
                        symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
2174
                        symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
2175
                        #cX, cY = originalPoint[0], originalPoint[0]
2176
                        #M = cv2.getRotationMatrix2D((cX, cY), 45, 1.0)
2177
                        #symGray = cv2.warpAffine(symGray, M, (sw, sh))
2178
                        #symbolRotatedAngle = (symbolRotatedAngle + 45) % 360
2179

    
2180
                        if baseSymbol is not None and additionalSymbol is not None:
2181
                            additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol)
2182
                        continue
2183

    
2184
                    # For OPC
2185
                    drawing_area = app_doc_data.getArea('Drawing')
2186
                    if drawing_area is not None and (symbolType == "Piping OPC\'s" or symbolType == "Instrument OPC\'s"):
2187
                        customMatch = worker.detectOPCOnPid(drawing_area, symGray)
2188
                        if customMatch and len(customMatch) > 0:
2189
                            for custom in customMatch:
2190
                                hitRate = custom[0]
2191
                                searchedItemSp = (custom[1][0] + round(offsetDrawingArea[0]), custom[1][1] + round(offsetDrawingArea[1]))
2192

    
2193
                                is_add = True
2194
                                for searched in searchedSymbolList:
2195
                                    if Worker.IsOverlap((searchedItemSp[0], searchedItemSp[1], sw, sh), (
2196
                                            searched.getSp()[0], searched.getSp()[1], searched.getWidth(),
2197
                                            searched.getHeight())):
2198
                                        if searched.getHitRate() > hitRate:
2199
                                            is_add = False
2200
                                        else:
2201
                                            searchedSymbolList.remove(searched)
2202
                                            break
2203

    
2204
                                if is_add:
2205
                                    threadLock.acquire()
2206
                                    foundSymbolCount = foundSymbolCount + 1
2207
                                    Worker.addSearchedSymbol(symbolName, symbolType,
2208
                                                             searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount,
2209
                                                             hitRate, symbolRotatedAngle,
2210
                                                             isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
2211
                                                             isContainChild,
2212
                                                             originalPoint, connectionPoint, baseSymbol, additionalSymbol,
2213
                                                             isExceptDetect, detectFlip=1 if flipped else 0,
2214
                                                             hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
2215
                                    threadLock.release()
2216

    
2217
                    if forTraining:
2218
                        sw, sh = sw + 20, sh + 20
2219

    
2220
                    # Template Matching
2221
                    tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED)
2222
                    loc = np.where(tmRes >= symbolThreshold)
2223

    
2224
                    for pt in zip(*loc[::-1]):
2225
                        '''
2226
                        # no more used 
2227
                        mpCount = 0  # Match Point Count
2228
                        roi = roiItem[pt[1]:pt[1] + sh, pt[0]:pt[0] + sw]
2229

2230
                        if symbolMinMatchCount > 0:
2231
                            mpCount = Worker.getMatchPointCount(roi, symGray)
2232
                            if not (mpCount >= symbolMinMatchCount):
2233
                                continue
2234
                        '''
2235

    
2236
                        searchedItemSp = (roiItemSp[0] + pt[0] + round(offsetDrawingArea[0]),
2237
                                          roiItemSp[1] + pt[1] + round(offsetDrawingArea[1]))
2238
                        
2239
                        if forTraining:
2240
                            searchedItemSp = [searchedItemSp[0] - 10, searchedItemSp[1] - 10]
2241

    
2242
                        overlapArea = 0
2243
                        symbolIndex = -1
2244
                        for i in range(len(searchedSymbolList) - 1, -1, -1):
2245
                            area = Worker.contains(searchedSymbolList[i], searchedItemSp, sw, sh)
2246
                            if area > ACCEPT_OVERLAY_AREA:
2247
                                # if area > overlapArea:
2248
                                #    overlapArea = area
2249
                                #    symbolIndex = i
2250
                                overlapArea = area
2251
                                symbolIndex = i
2252
                                break
2253
                                """
2254
                                categories = [appDocData.isEquipmentType(symbolType), appDocData.isEquipmentType(searchedSymbolList[i].getType())]
2255
                                if categories[0] == categories[1]:
2256
                                    symbolIndex = i
2257
                                    break
2258
                                """
2259

    
2260
                        hitRate = tmRes[pt[1], pt[0]]
2261

    
2262
                        # 겹치는 영역이 기준값보다 작을 경우
2263
                        if overlapArea <= ACCEPT_OVERLAY_AREA:
2264
                            threadLock.acquire()
2265
                            foundSymbolCount = foundSymbolCount + 1
2266
                            Worker.addSearchedSymbol(symbolName, symbolType,
2267
                                                     searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount,
2268
                                                     hitRate, symbolRotatedAngle,
2269
                                                     isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
2270
                                                     isContainChild,
2271
                                                     originalPoint, connectionPoint, baseSymbol, additionalSymbol,
2272
                                                     isExceptDetect,
2273
                                                     detectFlip=1 if flipped else 0,
2274
                                                     hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
2275
                            threadLock.release()
2276
                        else:  # 겹치는 영역이 기준값보다 클 경우
2277
                            if symbolIndex != -1 and symbolIndex < len(searchedSymbolList):
2278
                                searchedSymbol = searchedSymbolList[symbolIndex]
2279
                                # 현재 심볼과 검출된 심볼이 같을 경우 Match Point가 더 높은 정보로 교체
2280
                                if symbolName == searchedSymbol.getName():
2281
                                    symbolHitRate = searchedSymbol.getHitRate()
2282
                                    if symbolHitRate - searchedSymbol.getThreshold() < hitRate - symbolThreshold:
2283
                                        threadLock.acquire()
2284
                                        # replace existing symbol with new symbol has high accuracy
2285
                                        searchedSymbolList[symbolIndex] = symbol.Symbol(symbolName, symbolType,
2286
                                                                                        searchedItemSp, sw, sh,
2287
                                                                                        symbolThreshold,
2288
                                                                                        symbolMinMatchCount, hitRate,
2289
                                                                                        symbolRotatedAngle,
2290
                                                                                        isDetectOnOrigin,
2291
                                                                                        symbolRotateCount,
2292
                                                                                        symbolOcrOption, isContainChild,
2293
                                                                                        ','.join(str(x) for x in
2294
                                                                                                 originalPoint),
2295
                                                                                        '/'.join('{},{},{},{}'.format(
2296
                                                                                            param[0], param[1],
2297
                                                                                            param[2], param[3]) for
2298
                                                                                                 param in
2299
                                                                                                 connectionPoint),
2300
                                                                                        baseSymbol, additionalSymbol,
2301
                                                                                        isExceptDetect,
2302
                                                                                        detectFlip=1 if flipped else 0,
2303
                                                                                        hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
2304
                                        threadLock.release()
2305
                                # 현재 심볼과 검출된 심볼이 같지 않을 경우 (포함)
2306
                                elif app_doc_data.isEquipmentType(searchedSymbol.getType()) and not app_doc_data.isEquipmentType(symbolType):
2307
                                    if searchedSymbol.area > sw * sh * 10:  # searched equipment area is greather than 10 times of symbol's area
2308
                                        threadLock.acquire()
2309
                                        foundSymbolCount = foundSymbolCount + 1
2310
                                        Worker.addSearchedSymbol(symbolName, symbolType,
2311
                                                                 searchedItemSp, sw, sh, symbolThreshold, hitRate,
2312
                                                                 hitRate, symbolRotatedAngle,
2313
                                                                 isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
2314
                                                                 isContainChild,
2315
                                                                 originalPoint, connectionPoint, baseSymbol,
2316
                                                                 additionalSymbol, isExceptDetect,
2317
                                                                 detectFlip=1 if flipped else 0,
2318
                                                                 hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
2319
                                        threadLock.release()
2320
                                # 현재 심볼과 검출된 심볼이 같지 않을 경우 (교체)
2321
                                elif not forTraining:
2322
                                    searchedSymbol = searchedSymbolList[symbolIndex]
2323
                                    symbolHitRate = searchedSymbol.getHitRate()
2324
                                    if symbolHitRate - searchedSymbol.getThreshold() < hitRate - symbolThreshold:
2325
                                        threadLock.acquire()
2326
                                        searchedSymbolList[symbolIndex] = symbol.Symbol(symbolName, symbolType,
2327
                                                                                        searchedItemSp, sw, sh,
2328
                                                                                        symbolThreshold,
2329
                                                                                        symbolMinMatchCount, hitRate,
2330
                                                                                        symbolRotatedAngle,
2331
                                                                                        isDetectOnOrigin,
2332
                                                                                        symbolRotateCount,
2333
                                                                                        symbolOcrOption, isContainChild,
2334
                                                                                        ','.join(str(x) for x in
2335
                                                                                                 originalPoint),
2336
                                                                                        '/'.join('{},{},{},{}'.format(
2337
                                                                                            param[0], param[1],
2338
                                                                                            param[2], param[3]) for
2339
                                                                                                 param in
2340
                                                                                                 connectionPoint),
2341
                                                                                        baseSymbol, additionalSymbol,
2342
                                                                                        isExceptDetect,
2343
                                                                                        detectFlip=1 if flipped else 0,
2344
                                                                                        hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
2345
                                        threadLock.release()
2346
                                # 학습용 데이터 생성을 위해 교체하지 않고 추가함
2347
                                elif forTraining:
2348
                                    threadLock.acquire()
2349
                                    foundSymbolCount = foundSymbolCount + 1
2350
                                    Worker.addSearchedSymbol(symbolName, symbolType,
2351
                                                             searchedItemSp, sw, sh, symbolThreshold,
2352
                                                             symbolMinMatchCount, hitRate, symbolRotatedAngle,
2353
                                                             isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
2354
                                                             isContainChild,
2355
                                                             originalPoint, connectionPoint, baseSymbol,
2356
                                                             additionalSymbol, isExceptDetect,
2357
                                                             detectFlip=1 if flipped else 0,
2358
                                                             hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
2359
                                    threadLock.release()
2360

    
2361
                    # rotate symbol
2362
                    symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
2363
                    symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
2364
                    #cX, cY = originalPoint[0], originalPoint[0]
2365
                    #M = cv2.getRotationMatrix2D((cX, cY), 45, 1.0)
2366
                    #symGray = cv2.warpAffine(symGray, M, (sw, sh))
2367
                    #symbolRotatedAngle = (symbolRotatedAngle + 45) % 360
2368

    
2369
                    if additionalSymbol is not None:
2370
                        additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol)
2371

    
2372
            threadLock.acquire()
2373
            listWidget.addItem('Found Symbol   : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str(
2374
                foundSymbolCount) + ')')
2375
            threadLock.release()
2376

    
2377
            """
2378
            if area is not None and hasInstrumentLabel:
2379
                # restore objects smaller than symbol
2380
                roiItem = cv2.drawContours(roiItem, outside_contours, -1, (0, 0, 0), -1)
2381
                roiItem = cv2.drawContours(roiItem, hole_contours, -1, (255, 255, 255), -1)
2382
                # up to here
2383
                cv2.imwrite('c:\\Temp\\contour2.png', roiItem)
2384
            """
2385

    
2386
            worker.updateProgress.emit(maxProgressValue, symbolPath)
2387

    
2388
            return [symbol for symbol in searchedSymbolList if symbol.getName() == symbolName]
2389
        except Exception as ex:
2390
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
2391
                                                           sys.exc_info()[-1].tb_lineno)
2392
            worker.displayLog.emit(MessageType.Error, message)
2393

    
2394
        return []
2395

    
2396
    @staticmethod
2397
    def IsOverlap(range1, range2):
2398
        if range1[0] <= range2[0] + range2[2] and range1[0] + range1[2] >= range2[0] and range1[1] <= range2[1] + \
2399
                range2[3] and range1[1] + range1[3] >= range2[1]:
2400
            range = (min(range1[0] + range1[2], range2[0] + range2[2]) - max(range1[0], range2[0])) * (
2401
                    min(range1[1] + range1[3], range2[1] + range2[3]) - max(range1[1], range2[1]))
2402
            if range >= range1[2] * range1[3] * 0.4 and range >= range2[2] * range2[3] * 0.4:
2403
                return True
2404
            else:
2405
                return False
2406
        else:
2407
            return False
2408

    
2409
    @staticmethod
2410
    def detectOPCOnPid(area, symGray):
2411
        results = []
2412

    
2413
        try:
2414
            symbol = cv2.copyMakeBorder(symGray, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=255)
2415
            not_symbol = cv2.bitwise_not(symbol)
2416
            symbol_contours, symbol_hierachy = cv2.findContours(not_symbol, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
2417
            if symbol_hierachy[0][0][0] != -1:
2418
                return results
2419

    
2420
            contour_count = len(symbol_contours)
2421
            if contour_count != 2:
2422
                return results
2423

    
2424
            for i in range(1, contour_count):
2425
                # contour size
2426
                symbol_area = cv2.contourArea(symbol_contours[i])
2427
                # moments mass center
2428
                symbol_moments = cv2.moments(symbol_contours[i])
2429
                symbol_x = int(symbol_moments['m10'] / (symbol_moments['m00'] + 1e-5))
2430
                symbol_y = int(symbol_moments['m01'] / (symbol_moments['m00'] + 1e-5))
2431
                rect_x, rect_y, rect_w, rect_h = cv2.boundingRect(symbol_contours[i])
2432
                symbol_x = symbol_x - rect_x
2433
                symbol_y = symbol_y - rect_y
2434
                # percent x, y
2435
                percent_x = symbol_x / rect_w
2436
                percent_y = symbol_y / rect_h
2437

    
2438
                for contour in area.contours:
2439
                    area_area = cv2.contourArea(contour)
2440
                    if area_area * 1.2 >= symbol_area >= area_area * 0.8:
2441
                        I1 = cv2.matchShapes(symbol_contours[i], contour, 1, 0)
2442
                        I2 = cv2.matchShapes(symbol_contours[i], contour, 2, 0)
2443
                        I3 = cv2.matchShapes(symbol_contours[i], contour, 3, 0)
2444
                        if I1 < 1 and I2 < 1 and I3 < 0.1:
2445
                            rect_x2, rect_y2, rect_w2, rect_h2 = cv2.boundingRect(contour)
2446
                            if rect_w * 1.2 >= rect_w2 >= rect_w * 0.8 and rect_h * 1.2 >= rect_h2 >= rect_h * 0.8:
2447
                                # moments mass center
2448
                                moments = cv2.moments(contour)
2449
                                x = int(moments['m10'] / (moments['m00'] + 1e-5))
2450
                                y = int(moments['m01'] / (moments['m00'] + 1e-5))
2451

    
2452
                                x = x - rect_x2
2453
                                y = y - rect_y2
2454
                                percent_x2 = x / rect_w2
2455
                                percent_y2 = y / rect_h2
2456

    
2457
                                value_x = abs(percent_x - percent_x2)
2458
                                value_y = abs(percent_y - percent_y2)
2459

    
2460
                                results.append([1 - (value_x + value_y), [rect_x2, rect_y2]])
2461
                break
2462
        except Exception as ex:
2463
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
2464
                                                           sys.exc_info()[-1].tb_lineno)
2465
        return results
2466

    
2467
    # TODO: detect flange
2468
    @staticmethod
2469
    def detectFlangeOnPid(symbol, connector, line, image):
2470
        pt = connector.center()
2471
        x = int(pt[0])
2472
        y = int(pt[1])
2473
        center_x = int(symbol.loc[0] + symbol.size[0] / 2)
2474
        center_y = int(symbol.loc[1] + symbol.size[1] / 2)
2475

    
2476
        arrow = Arrow.NULL
2477
        if line:
2478
            line_center_x = int((line.start_point()[0] + line.end_point()[0]) / 2)
2479
            line_center_y = int((line.start_point()[1] + line.end_point()[1]) / 2)
2480

    
2481
            if line.isHorizontal():
2482
                if center_x < line_center_x:
2483
                    arrow = Arrow.RIGHT
2484
                else:
2485
                    arrow = Arrow.LEFT
2486
            elif line.isVertical():
2487
                if center_y < line_center_y:
2488
                    arrow = Arrow.DOWN
2489
                else:
2490
                    arrow = Arrow.UP
2491

    
2492
            if arrow is not Arrow.NULL:
2493
                result = Worker.detectFlangeOnPidArrow(symbol, x, y, arrow, image)
2494
                if result:
2495
                    return [x, y]
2496
        else:
2497
            _dir = [round(connector.dir().x()),  round(connector.dir().y())]
2498
            if abs(_dir[0]) == 1:
2499
                if _dir[0] > 0:
2500
                    arrow = Arrow.RIGHT
2501
                else:
2502
                    arrow = Arrow.LEFT
2503
            elif abs(_dir[1]) == 1:
2504
                if _dir[1] > 0:
2505
                    arrow = Arrow.DOWN
2506
                else:
2507
                    arrow = Arrow.UP
2508

    
2509
            result = Worker.detectFlangeBlindOnPid(x, y, arrow, image)
2510
            if result:
2511
                return [x, y]
2512
           
2513
        return None
2514

    
2515
    @staticmethod
2516
    def detectFlangeBlindOnPid(start_x, start_y, arrow, image):
2517
        loopRange = []
2518
        if arrow is Arrow.DOWN:
2519
            loopRange = range(start_y - 10, start_y + 25)
2520
        elif arrow is Arrow.UP:
2521
            loopRange = range(start_y + 10, start_y - 25, -1)
2522
        elif arrow is Arrow.LEFT:
2523
            loopRange = range(start_x + 10, start_x - 25, -1)
2524
        elif arrow is Arrow.RIGHT:
2525
            loopRange = range(start_x - 10, start_x + 25)
2526
        else:
2527
            return None
2528

    
2529
        find_forward = False
2530
        find_backward = False
2531
        find = None
2532
        # 검은색 점을 찾는 범위
2533
        search_length = 30
2534
        # Line 최소 Length
2535
        checkLineLength = 20
2536
        # Line 최대 Width
2537
        line_width = 18
2538
        # flange min length
2539
        flange_min = 25
2540
        # flange max length
2541
        flange_max = 50
2542
        # flange max width
2543
        flange_count_max = 10
2544
        # noise max count
2545
        noise_count_max = 5
2546

    
2547
        # 임시
2548
        temp_count = 0
2549
        noise_count = 0
2550
        find_list_x = []
2551
        find_list_y = []
2552
        for i in loopRange:
2553
            loop_find = False
2554
            for j in range(search_length):
2555
                width = 0
2556
                color_1 = 0
2557
                color_2 = 0
2558
                find_x = 0
2559
                find_y = 0
2560

    
2561
                if arrow is Arrow.DOWN or arrow is Arrow.UP:
2562
                    color_1 = image[i, start_x - j]
2563
                    color_2 = image[i, start_x + j]
2564
                    if int(color_1) is 0:
2565
                        width = Worker.getWidth(start_x - j, i, arrow, image)
2566
                        find_x = start_x - j
2567
                        find_y = i
2568
                    elif int(color_2) is 0:
2569
                        width = Worker.getWidth(start_x + j, i, arrow, image)
2570
                        find_x = start_x + j
2571
                        find_y = i
2572

    
2573
                elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2574
                    color_1 = image[start_y - j, i]
2575
                    color_2 = image[start_y + j, i]
2576
                    if int(color_1) is 0:
2577
                        width = Worker.getWidth(i, start_y - j, arrow, image)
2578
                        find_x = i
2579
                        find_y = start_y - j
2580
                    elif int(color_2) is 0:
2581
                        width = Worker.getWidth(i, start_y + j, arrow, image)
2582
                        find_x = i
2583
                        find_y = start_y + j
2584

    
2585
                if flange_min < width < flange_max:
2586
                    loop_find = True
2587
                    find_list_x.append(find_x)
2588
                    find_list_y.append(find_y)
2589
                    break
2590
                elif width > 0:
2591
                    if temp_count > 0:
2592
                        noise_count += 1
2593
                    break
2594

    
2595
            if loop_find:
2596
                if temp_count > flange_count_max:
2597
                    break
2598
                temp_count = temp_count + 1
2599
            elif 0 < temp_count < flange_count_max and width > 0 and noise_count <= noise_count_max:
2600
                continue
2601
            elif noise_count > noise_count_max:
2602
                break
2603
            elif 0 < temp_count < flange_count_max and width == 0 and noise_count <= noise_count_max:
2604
                find_forward = True
2605
                break
2606
            else:
2607
                find_list_x.clear()
2608
                find_list_y.clear()
2609
                temp_count = 0
2610

    
2611
        if not find_forward:
2612
            return False
2613

    
2614
        # 임시
2615
        temp_count = 0
2616
        noise_count = 0
2617
        find_list_x = []
2618
        find_list_y = []
2619
        for i in reversed(loopRange):
2620
            loop_find = False
2621
            for j in range(search_length):
2622
                width = 0
2623
                color_1 = 0
2624
                color_2 = 0
2625
                find_x = 0
2626
                find_y = 0
2627

    
2628
                if arrow is Arrow.DOWN or arrow is Arrow.UP:
2629
                    color_1 = image[i, start_x - j]
2630
                    color_2 = image[i, start_x + j]
2631
                    if int(color_1) is 0:
2632
                        width = Worker.getWidth(start_x - j, i, arrow, image)
2633
                        find_x = start_x - j
2634
                        find_y = i
2635
                    elif int(color_2) is 0:
2636
                        width = Worker.getWidth(start_x + j, i, arrow, image)
2637
                        find_x = start_x + j
2638
                        find_y = i
2639

    
2640
                elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2641
                    color_1 = image[start_y - j, i]
2642
                    color_2 = image[start_y + j, i]
2643
                    if int(color_1) is 0:
2644
                        width = Worker.getWidth(i, start_y - j, arrow, image)
2645
                        find_x = i
2646
                        find_y = start_y - j
2647
                    elif int(color_2) is 0:
2648
                        width = Worker.getWidth(i, start_y + j, arrow, image)
2649
                        find_x = i
2650
                        find_y = start_y + j
2651

    
2652
                if flange_min < width < flange_max:
2653
                    loop_find = True
2654
                    find_list_x.append(find_x)
2655
                    find_list_y.append(find_y)
2656
                    break
2657
                elif width > 0:
2658
                    if temp_count > 0:
2659
                        noise_count += 1
2660
                    break
2661

    
2662
            if loop_find:
2663
                if temp_count > flange_count_max:
2664
                    break
2665
                temp_count = temp_count + 1
2666
            elif 0 < temp_count < flange_count_max and width > 0 and noise_count <= noise_count_max:
2667
                continue
2668
            elif noise_count > noise_count_max:
2669
                break
2670
            elif 0 < temp_count < flange_count_max and width == 0 and noise_count <= noise_count_max:
2671
                find_backward = True
2672
                break
2673
            else:
2674
                find_list_x.clear()
2675
                find_list_y.clear()
2676
                temp_count = 0
2677
        
2678
        if find_forward and find_backward:
2679
            return True
2680

    
2681
    @staticmethod
2682
    def detectFlangeOnPidArrow(symbol, start_x, start_y, arrow, image):
2683
        loopRange = []
2684
        if arrow is Arrow.DOWN:
2685
            loopRange = range(start_y - 20, start_y + 40)
2686
        elif arrow is Arrow.UP:
2687
            loopRange = range(start_y + 20, start_y - 40, -1)
2688
        elif arrow is Arrow.LEFT:
2689
            loopRange = range(start_x + 20, start_x - 40, -1)
2690
        elif arrow is Arrow.RIGHT:
2691
            loopRange = range(start_x - 20, start_x + 40)
2692
        else:
2693
            return None
2694

    
2695
        find = False
2696
        # 검은색 점을 찾는 범위
2697
        search_length = 10
2698
        # Line 최소 Length
2699
        checkLineLength = 20
2700
        # Line 최대 Width
2701
        line_width = 18
2702
        # flange min length
2703
        flange_min = 25
2704
        # flange max length
2705
        flange_max = 50
2706
        # flange max width
2707
        flange_count_max = 10
2708
        # 임시
2709
        temp_count = 0
2710
        find_list_x = []
2711
        find_list_y = []
2712
        for i in loopRange:
2713
            loop_find = False
2714
            for j in range(search_length):
2715
                width = 0
2716
                color_1 = 0
2717
                color_2 = 0
2718
                find_x = 0
2719
                find_y = 0
2720

    
2721
                if arrow is Arrow.DOWN or arrow is Arrow.UP:
2722
                    color_1 = image[i, start_x - j]
2723
                    color_2 = image[i, start_x + j]
2724
                    if int(color_1) is 0:
2725
                        width = Worker.getWidth(start_x - j, i, arrow, image)
2726
                        find_x = start_x - j
2727
                        find_y = i
2728
                    elif int(color_2) is 0:
2729
                        width = Worker.getWidth(start_x + j, i, arrow, image)
2730
                        find_x = start_x + j
2731
                        find_y = i
2732

    
2733
                elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2734
                    color_1 = image[start_y - j, i]
2735
                    color_2 = image[start_y + j, i]
2736
                    if int(color_1) is 0:
2737
                        width = Worker.getWidth(i, start_y - j, arrow, image)
2738
                        find_x = i
2739
                        find_y = start_y - j
2740
                    elif int(color_2) is 0:
2741
                        width = Worker.getWidth(i, start_y + j, arrow, image)
2742
                        find_x = i
2743
                        find_y = start_y + j
2744

    
2745
                if 0 < width <= line_width:
2746
                    loop_find = True
2747
                    find_list_x.append(find_x)
2748
                    find_list_y.append(find_y)
2749
                    break
2750

    
2751
            if loop_find:
2752
                if temp_count > checkLineLength:
2753
                    find = True
2754
                    break
2755
                temp_count = temp_count + 1
2756
            else:
2757
                find_list_x.clear()
2758
                find_list_y.clear()
2759
                temp_count = 0
2760
        
2761
        if find:
2762
            count = 0
2763
            temp_list = []
2764
            find_white = False
2765
            average = 0
2766
            if arrow is Arrow.DOWN or arrow is Arrow.UP:
2767
                average = Worker.getAverage(find_list_x)
2768
            elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2769
                average = Worker.getAverage(find_list_y)
2770
            else:
2771
                pass
2772

    
2773
            flange_count = 0
2774
            for i in range(20):
2775
                width = 0
2776
                if arrow is Arrow.DOWN:
2777
                    width = Worker.getWidth(average, find_list_y[0] - i, arrow, image)
2778
                elif arrow is Arrow.UP:
2779
                    width = Worker.getWidth(average, find_list_y[0] + i, arrow, image)
2780
                elif arrow is Arrow.LEFT:
2781
                    width = Worker.getWidth(find_list_x[0] + i, average, arrow, image)
2782
                elif arrow is Arrow.RIGHT:
2783
                    width = Worker.getWidth(find_list_x[0] - i, average, arrow, image)
2784
                else:
2785
                    pass
2786
                
2787
                if flange_min < width < flange_max:
2788
                    flange_count = flange_count + 1
2789
                elif width <= 0:
2790
                    find_white = True
2791
                    break
2792
                else:
2793
                    pass
2794

    
2795
            if 0 < flange_count < flange_count_max and find_white:
2796
                crop_image = None
2797
                x = find_list_x[0] - (int(symbol.loc[0]) - int(symbol.size[0]) - 1)
2798
                y = find_list_y[0] - (int(symbol.loc[1]) - int(symbol.size[1]) - 1)
2799
                crop_image = Worker.removeLine(symbol, find_list_x[0], find_list_y[0], arrow, image)
2800
                if arrow is Arrow.DOWN:
2801
                    y = y - 1
2802
                elif arrow is Arrow.UP:
2803
                    y = y + 1
2804
                elif arrow is Arrow.LEFT:
2805
                    x = x + 1
2806
                elif arrow is Arrow.RIGHT:
2807
                    x = x - 1
2808
                else:
2809
                    return None
2810
                crop_image = cv2.copyMakeBorder(crop_image, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=255)
2811
                not_image = cv2.bitwise_not(crop_image)
2812
                image_contours, image_hierachy = cv2.findContours(not_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
2813
                for contour in image_contours:
2814
                    c_x, c_y, c_w, c_h = cv2.boundingRect(contour)
2815
                    area = cv2.contourArea(contour)
2816
                    if c_x <= x and c_x + c_w >= x and c_y <= y and c_y + c_h >= y:
2817
                        if flange_count * flange_max * 2.5 >= area:
2818
                            #not_image[y, x] = 0
2819
                            #from PIL import Image
2820
                            #Image.fromarray(not_image).show()
2821
                            return True
2822

    
2823
            else:
2824
                return False
2825

    
2826
        return False
2827

    
2828
    @staticmethod
2829
    def getWidth(x, y, arrow, image):
2830
        width = 0
2831
        move_x1 = 0
2832
        move_y1 = 0
2833
        move_x2 = 0
2834
        move_y2 = 0
2835

    
2836
        if arrow is Arrow.DOWN or arrow is Arrow.UP:
2837
            while True:
2838
                color = image[y, x - move_x1]
2839
                if int(color) is 0:
2840
                    move_x1 = move_x1 + 1
2841
                else:
2842
                    break
2843
            while True:
2844
                color = image[y, x + move_x2]
2845
                if int(color) is 0:
2846
                    move_x2 = move_x2 + 1
2847
                else:
2848
                    break
2849
            width = move_x1 + move_x2 - 1
2850
        elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2851
            while True:
2852
                color = image[y - move_y1, x]
2853
                if int(color) is 0:
2854
                    move_y1 = move_y1 + 1
2855
                else:
2856
                    break
2857
            while True:
2858
                color = image[y + move_y2, x]
2859
                if int(color) is 0:
2860
                    move_y2 = move_y2 + 1
2861
                else:
2862
                    break
2863
            width = move_y1 + move_y2 - 1
2864

    
2865
        return width
2866

    
2867
    @staticmethod
2868
    def removeLine(symbol, x, y, arrow, image):
2869
        symbol_width = int(symbol.size[0])
2870
        symbol_height = int(symbol.size[1])
2871

    
2872
        if arrow is Arrow.DOWN or arrow is Arrow.UP:
2873
            for i in range(symbol_width):
2874
                image[y, x - i] = 255
2875
                image[y, x + i] = 255
2876
        elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2877
            for i in range(symbol_height):
2878
                image[y - i, x] = 255
2879
                image[y + i, x] = 255
2880
                  
2881
        crop_x1 = int(symbol.loc[0]) - symbol_width
2882
        crop_y1 = int(symbol.loc[1]) - symbol_height
2883
        crop_x2 = int(symbol.loc[0]) + symbol_width * 2
2884
        crop_y2 = int(symbol.loc[1]) + symbol_height * 2
2885
        image = image[crop_y1:crop_y2, crop_x1:crop_x2]
2886
        
2887
        return image
2888

    
2889
    @staticmethod
2890
    def getAverage(datas):
2891
        result = 0
2892

    
2893
        for x in datas:
2894
            result = result + x
2895

    
2896
        result = result / len(datas)
2897

    
2898
        return int(result)
2899

    
2900

    
2901
    # Convert into Grayscale image
2902
    @staticmethod
2903
    def cvtGrayImage(img):
2904
        return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
2905

    
2906
    '''
2907
        @history    2018.06.12  Jeongwoo    Type changed (int → float)
2908
                    humkyung 2018.07.07 change return type as like [x,y]
2909
    '''
2910

    
2911
    @staticmethod
2912
    def getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolRotatedAngle, rotateSymbolWidth,
2913
                                   rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight, flipped=False):
2914
        res = []
2915

    
2916
        if additionalSymbol is None and symbolOriginalPoint is None:
2917
            res.append(rotateSymbolWidth // 2)
2918
            res.append(rotateSymbolHeight // 2)
2919
        else:
2920
            if flipped:
2921
                opx = originalSymbolWidth - float(symbolOriginalPoint.split(',')[0])
2922
                opy = float(symbolOriginalPoint.split(',')[1])
2923
            else:
2924
                opx = float(symbolOriginalPoint.split(',')[0])
2925
                opy = float(symbolOriginalPoint.split(',')[1])
2926

    
2927
            rPt = Worker.getCoordOnRotatedImage(symbolRotatedAngle, ('AUTO', opx, opy, '0'), originalSymbolWidth,
2928
                                                originalSymbolHeight)
2929

    
2930
            res.append(rPt[1])
2931
            res.append(rPt[2])
2932

    
2933
        return res
2934

    
2935
    '''
2936
        @history    2018.06.12  Jeongwoo    Type changed (int → float)
2937
                    humkyung 2018.07.07 change return type as like [[x,y],...]
2938
                    humkyung 2019.01.04 get symbol index
2939
    '''
2940

    
2941
    @staticmethod
2942
    def getCalculatedConnectionPoint(symbolConnectionPointStr, symbolRotatedAngle, rotateSymbolWidth,
2943
                                     rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight, flipped=0):
2944
        res = []
2945

    
2946
        if symbolConnectionPointStr is not None and symbolConnectionPointStr != '':
2947
            splitConnectionPointStr = symbolConnectionPointStr.split("/")
2948
            for strConnPt in splitConnectionPointStr:
2949
                tokens = strConnPt.split(',')
2950

    
2951
                direction = 'AUTO'
2952
                symbol_idx = '0'
2953
                if flipped:
2954
                    converted = {'AUTO': 'AUTO', 'LEFT': 'RIGHT', 'RIGHT': 'LEFT', 'UP': 'UP', 'DOWN': 'DOWN'}
2955

    
2956
                    if len(tokens) == 2:
2957
                        cpx = originalSymbolWidth - float(tokens[0])
2958
                        cpy = float(tokens[1])
2959
                    elif len(tokens) == 3:
2960
                        #direction = converted[tokens[0]]
2961
                        direction = tokens[0]
2962
                        cpx = originalSymbolWidth - float(tokens[1])
2963
                        cpy = float(tokens[2])
2964
                    elif len(tokens) >= 4:
2965
                        #direction = converted[tokens[0]]
2966
                        direction = tokens[0]
2967
                        cpx = originalSymbolWidth - float(tokens[1])
2968
                        cpy = float(tokens[2])
2969
                        symbol_idx = tokens[3]
2970
                else:
2971
                    if len(tokens) == 2:
2972
                        cpx = float(tokens[0])
2973
                        cpy = float(tokens[1])
2974
                    elif len(tokens) == 3:
2975
                        direction = tokens[0]
2976
                        cpx = float(tokens[1])
2977
                        cpy = float(tokens[2])
2978
                    elif len(tokens) >= 4:
2979
                        direction = tokens[0]
2980
                        cpx = float(tokens[1])
2981
                        cpy = float(tokens[2])
2982
                        symbol_idx = tokens[3]
2983

    
2984
                res.append(Worker.getCoordOnRotatedImage(symbolRotatedAngle, (direction, cpx, cpy, symbol_idx),
2985
                                                         originalSymbolWidth, originalSymbolHeight))
2986

    
2987
        return res
2988

    
2989
    '''
2990
        @brief      rotate (x,y) by given angle
2991
        @author     Jeongwoo
2992
        @date       2018.??.??
2993
        @history    humkyung 2018.04.13 fixed code when angle is 90 or 270    
2994
                    Jeongwoo 2018.04.27 Change calculation method with QTransform
2995
                    humkyung 2018.09.01 calculate rotated direction
2996
    '''
2997

    
2998
    @staticmethod
2999
    def getCoordOnRotatedImage(angle, connPt, originImageWidth, originImageHeight):
3000
        import math
3001

    
3002
        rx = None
3003
        ry = None
3004

    
3005
        # calculate rotated direction
3006
        direction = connPt[0]
3007
        '''
3008
        if direction == 'LEFT':
3009
            direction = 'DOWN' if angle == 90 else 'RIGHT' if angle == 180 else 'UP' if angle == 270 else direction
3010
        elif direction == 'RIGHT':
3011
            direction = 'UP' if angle == 90 else 'LEFT' if angle == 180 else 'DOWN' if angle == 270 else direction
3012
        elif direction == 'UP':
3013
            direction = 'LEFT' if angle == 90 else 'DOWN' if angle == 180 else 'RIGHT' if angle == 270 else direction
3014
        elif direction == 'DOWN':
3015
            direction = 'RIGHT' if angle == 90 else 'UP' if angle == 180 else 'LEFT' if angle == 270 else direction
3016
        '''
3017
        # up to here
3018

    
3019
        '''
3020
        transform = QTransform()
3021
        if angle == 90 or angle == 270:
3022
            transform.translate(originImageHeight * 0.5, originImageWidth * 0.5)
3023
        elif angle == 0 or angle == 180:
3024
            transform.translate(originImageWidth * 0.5, originImageHeight * 0.5)
3025
        transform.rotate(abs(angle))
3026
        transform.translate(-originImageWidth * 0.5, -originImageHeight * 0.5)
3027
        point = QPoint(connPt[1], connPt[2])
3028
        point = transform.map(point)
3029
        rx = point.x()
3030
        ry = point.y()
3031
        '''
3032

    
3033
        rect = QRectF(0, 0, originImageWidth, originImageHeight)
3034
        points = [rect.bottomLeft(), rect.topLeft(), rect.topRight(), rect.bottomRight()]
3035
        transform2 = QTransform()
3036
        transform2.rotate(abs(angle))
3037
        points = [transform2.map(point) for point in points]
3038
        offset_x = abs(min([point.x() for point in points]))
3039
        offset_y = abs(min([point.y() for point in points]))
3040
        point = QPoint(connPt[1], connPt[2])
3041
        point = transform2.map(point)
3042
        rx, ry = point.x() + offset_x, point.y() + offset_y
3043

    
3044
        '''
3045
        pX, pY = connPt[1], originImageHeight - connPt[2]
3046
        rad = math.radians(-angle)
3047
        rot_ce_X = originImageWidth * 0.5
3048
        rot_ce_Y = originImageHeight * 0.5
3049
        rX = (pX - rot_ce_X) * math.cos(rad) - (pY - rot_ce_Y) * math.sin(rad) + rot_ce_X
3050
        rY = (pX - rot_ce_X) * math.sin(rad) + (pY - rot_ce_Y) * math.cos(rad) + rot_ce_Y
3051
        rx = rX
3052
        ry = originImageHeight - rY
3053

3054
        if abs(point.x() - rx) > 0.1 or abs(point.y() - ry) > 0.1:
3055
            print('a')
3056
        '''
3057

    
3058
        symbol_idx = connPt[3]
3059

    
3060
        return (direction, rx, ry, symbol_idx)
3061

    
3062
    '''
3063
        @brief      Add symbols
3064
        @author     jwkim
3065
        @date
3066
        @history    Change parameter (mpCount → hitRate)
3067
                    Yecheol 2018.07.04 Delete Symbol Id 
3068
    '''
3069

    
3070
    @staticmethod
3071
    def addSearchedSymbol(sName, sType
3072
                          , sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle
3073
                          , isDetectOnOrigin, rotateCount, ocrOption, isContainChild
3074
                          , originalPoint, connectionPoint, baseSymbol, additionalSymbol, isExceptDetect, detectFlip
3075
                          , hasInstrumentLabel, text_area):
3076
        global searchedSymbolList
3077

    
3078
        newSym = None
3079
        try:
3080
            newSym = symbol.Symbol(sName, sType
3081
                                   , sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle,
3082
                                   isDetectOnOrigin, rotateCount, ocrOption, isContainChild,
3083
                                   ','.join(str(x) for x in originalPoint),
3084
                                   '/'.join('{},{},{},{}'.format(param[0], param[1], param[2], param[3]) for param in
3085
                                            connectionPoint),
3086
                                   baseSymbol, additionalSymbol, isExceptDetect, detectFlip=1 if detectFlip else 0,
3087
                                   hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
3088

    
3089
            searchedSymbolList.append(newSym)
3090
        except Exception as ex:
3091
            from App import App
3092
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
3093
                                                           sys.exc_info()[-1].tb_lineno)
3094
            App.mainWnd().addMessage.emit(MessageType.Error, message)
3095

    
3096
        return newSym
3097

    
3098
    '''
3099
        @brief      Check object contains pt
3100
        @param      obj is item in searchedSymbolList
3101
    '''
3102

    
3103
    @staticmethod
3104
    def contains(obj, pt, tw, th):
3105
        sp = obj.getSp()
3106
        width = obj.getWidth()
3107
        height = obj.getHeight()
3108

    
3109
        if sp[0] > pt[0] + tw:
3110
            return 0
3111
        if sp[0] + width < pt[0]:
3112
            return 0
3113
        if sp[1] > pt[1] + th:
3114
            return 0
3115
        if sp[1] + height < pt[1]:
3116
            return 0
3117

    
3118
        # shared area
3119
        x = max(sp[0], pt[0])
3120
        y = max(sp[1], pt[1])
3121
        w = min(sp[0] + width, pt[0] + tw) - x
3122
        h = min(sp[1] + height, pt[1] + th) - y
3123

    
3124
        return float((w * h)) / float((tw * th)) * 100
3125

    
3126
    # Calculate count of keypoint match result
3127
    @staticmethod
3128
    def getMatchPointCount(src, cmp):
3129
        matchCount = 0
3130

    
3131
        try:
3132
            orb = cv2.ORB_create(1000, 2.0, 2, 1)
3133

    
3134
            kp1, des1 = orb.detectAndCompute(src, None)
3135
            kp2, des2 = orb.detectAndCompute(cmp, None)
3136

    
3137
            FLANN_INDEX_LSH = 6
3138
            # table_number      : The number of hash tables use
3139
            # key_size          : The length of the key in the hash tables
3140
            # multi_probe_level : Number of levels to use in multi-probe (0 for standard LSH)
3141
            #                     It controls how neighboring buckets are searched
3142
            #                     Recommended value is 2
3143
            # checks            : specifies the maximum leafs to visit when searching for neighbours.
3144
            # LSH : Locality-Sensitive Hashing
3145
            # ref : https://www.cs.ubc.ca/research/flann/uploads/FLANN/flann_manual-1.8.4.pdf
3146
            index_params = dict(algorithm=FLANN_INDEX_LSH, table_number=20, key_size=10, multi_probe_level=4)
3147
            search_params = dict(checks=100)
3148

    
3149
            flann = cv2.FlannBasedMatcher(index_params, search_params)
3150

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

    
3154
            count = 0
3155
            # ratio test as per Lowe's paper
3156
            for i in range(len(matches)):
3157
                if len(matches[i]) == 2:
3158
                    m = matches[i][0]
3159
                    n = matches[i][1]
3160
                    if m.distance < 0.85 * n.distance:
3161
                        count = count + 1
3162

    
3163
            matchCount = count
3164
        except Exception as ex:
3165
            from App import App
3166
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
3167
                                                           sys.exc_info()[-1].tb_lineno)
3168
            App.mainWnd().addMessage.emit(MessageType.Error, message)
3169

    
3170
        return matchCount
3171

    
3172
    '''
3173
        @brief      Remake rotated child symbol info
3174
    '''
3175

    
3176
    @staticmethod
3177
    def getRotatedChildInfo(additionalSymbol):
3178
        tempChildInfo = ""
3179
        if additionalSymbol:
3180
            childList = additionalSymbol.split("/")
3181
            for index in range(len(childList)):
3182
                child = childList[index]
3183
                direction = Worker.convertDirectionCodeToValue(child.split(",")[0])
3184
                childName = child.split(",")[1]
3185
                direction = (direction - 1) if direction > 0 else 3
3186
                if index != 0:
3187
                    tempChildInfo = tempChildInfo + "/"
3188
                tempChildInfo = tempChildInfo + Worker.convertValueToDirectionCode(direction) + "," + childName
3189
        return tempChildInfo
3190

    
3191
    @staticmethod
3192
    def calculate_exact_position(area, symGray, symbol, rect, worker):
3193
        import copy
3194

    
3195
        try:
3196
            tilt = False
3197

    
3198
            symbolThreshold = symbol.getThreshold()
3199
            symbolRotateCount = symbol.getRotationCount()
3200
            detectFlip = symbol.getDetectFlip()
3201
            hasInstrumentLabel = symbol.getHasInstrumentLabel()
3202
            symbolOriginalPoint = symbol.getOriginalPoint()
3203
            symbolConnectionPoint = symbol.getConnectionPoint()
3204
            additionalSymbol = symbol.getAdditionalSymbol()
3205

    
3206
            sow, soh = symGray.shape[::-1]
3207

    
3208
            # symbol is bigger than roi -> detected symbol area is too big
3209
            if rect[3] * rect[4] > sow * soh * 1.8:
3210
                return (None, None, None, None, None, None, None, None)
3211
            # detected symbol area is too small
3212
            elif rect[3] * rect[4] * 1.8 < sow * soh:
3213
                return (None, None, None, None, None, None, None, None)
3214

    
3215
            # get Rotated Original Point
3216
            sow, soh = symGray.shape[::-1]
3217
            offset_x, offset_y = round(rect[3] * 0.5), round(rect[4] * 0.5)#int(max(sow, soh) / 2)
3218

    
3219
            roiItem = Worker.remove_small_objects(area.img, 0, sow * soh * 0.5) if hasInstrumentLabel else area.img.copy()
3220
            x_start = round(rect[1]) - round(offset_x) if round(rect[1]) - round(offset_x) > 0 else 0
3221
            y_start = round(rect[2]) - round(offset_y) if round(rect[2]) - round(offset_y) > 0 else 0
3222
            x_max = round(rect[1]) + round(rect[3]) + round(offset_x) if round(rect[1]) + round(rect[3]) + round(offset_x) < len(roiItem[0]) else len(roiItem[0]) - 1
3223
            y_max = round(rect[2]) + round(rect[4]) + round(offset_y) if round(rect[2]) + round(rect[4]) + round(offset_y) < len(roiItem) else len(roiItem) - 1
3224
            roiItem = roiItem[y_start:y_max, x_start:x_max]
3225

    
3226
            symGrayOri = copy.copy(symGray)
3227

    
3228
            searchedInfos = [] # score, x, y, angle, flip, originalPoint, connectionPoint, sw, sh
3229

    
3230

    
3231
            steps = [False, True] if detectFlip else [False]
3232
            for flipped in steps:
3233
                if flipped:
3234
                    symGray = symGrayOri
3235
                    symGray = cv2.flip(symGray, 1)
3236

    
3237
                # 45 test
3238
                symGray_0 = copy.copy(symGray)
3239
                if tilt:
3240
                    rect = QRectF(0, 0, symGray.shape[::-1][0], symGray.shape[::-1][1])
3241
                    points = [rect.bottomLeft(), rect.topLeft(), rect.topRight(), rect.bottomRight()]
3242
                    transform2 = QTransform()
3243
                    transform2.rotate(abs(45))
3244
                    points = [transform2.map(point) for point in points]
3245
                    offset_x = int(max([point.x() for point in points]) - min([point.x() for point in points]))
3246
                    offset_y = int(max([point.y() for point in points]) - min([point.y() for point in points]))
3247
                    s_x, s_y = round((offset_x - symGray.shape[::-1][0]) / 2), round((offset_y - symGray.shape[::-1][1]) / 2)
3248
                    mask = np.ones((offset_x, offset_y), dtype=np.uint8) * 255
3249
                    mask[s_y:s_y + symGray.shape[::-1][1], s_x:s_x + symGray.shape[::-1][0]] = symGray
3250

    
3251
                    symGray_45 = mask
3252
                    cX, cY = int(symGray_45.shape[::-1][0] / 2), int(symGray_45.shape[::-1][1] / 2)
3253
                    M = cv2.getRotationMatrix2D((cX, cY), -45, 1.0)
3254
                    symGray_45 = cv2.warpAffine(symGray_45, M, (symGray_45.shape[::-1][0], symGray_45.shape[::-1][1]), borderValue=(255))
3255

    
3256
                symbolRotatedAngle = 0
3257
                for rc in range((symbolRotateCount + 1) * 2):
3258
                #for rc in range(symbolRotateCount + 1):
3259
                    if rc % 2 == 0:
3260
                        symGray = symGray_0
3261
                    else:
3262
                        if tilt:
3263
                            symGray = symGray_45
3264
                        else:
3265
                            symbolRotatedAngle = (symbolRotatedAngle + 45) % 360
3266
                            continue
3267
                        
3268
                    sw, sh = symGray.shape[::-1]
3269

    
3270
                    originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint,
3271
                                                    symbolRotatedAngle, sw, sh, sow, soh, flipped)
3272
                    connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw,
3273
                                                    sh, sow, soh, 0)
3274

    
3275
                    r_w, r_h = roiItem.shape[::-1]
3276
                    if r_w < sw or r_h < sh:
3277
                        if rc % 2 == 0:
3278
                            symGray_0 = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
3279
                        else:
3280
                            if tilt:
3281
                                symGray_45 = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
3282
                        #symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
3283
                        #symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
3284
                        #cX, cY = originalPoint[0], originalPoint[0]
3285
                        #M = cv2.getRotationMatrix2D((cX, cY), 45, 1.0)
3286
                        #symGray = cv2.warpAffine(symGray, M, (sw, sh))
3287
                        symbolRotatedAngle = (symbolRotatedAngle + 45) % 360
3288
                        continue
3289

    
3290
                    tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED)
3291
                    _, max_val, __, max_loc = cv2.minMaxLoc(tmRes)
3292
                    #maxIndex = tmRes.argmax()
3293
                    #colCount = len(tmRes[0])
3294
                    #col, row = divmod(maxIndex, colCount)
3295

    
3296
                    if max_val > 0.25:
3297
                        searchedInfos.append([max_val, max_loc[0] + x_start, max_loc[1] + y_start, symbolRotatedAngle, flipped, originalPoint, connectionPoint, sw, sh])
3298

    
3299
                    if rc % 2 == 0:
3300
                        symGray_0 = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
3301
                    else:
3302
                        if tilt:
3303
                            symGray_45 = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
3304
                    #symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
3305
                    #symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
3306
                    #cX, cY = originalPoint[0], originalPoint[0]
3307
                    #M = cv2.getRotationMatrix2D((cX, cY), 45, 1.0)
3308
                    #symGray = cv2.warpAffine(symGray, M, (sw, sh))
3309
                    symbolRotatedAngle = (symbolRotatedAngle + 45) % 360
3310

    
3311
            if searchedInfos:
3312
                searchedInfos = sorted(searchedInfos, key=lambda param: param[0], reverse=True)
3313
                searchedInfo = searchedInfos[0]
3314
                return ((searchedInfo[1] + area.x, searchedInfo[2] + area.y), searchedInfo[3], searchedInfo[4], \
3315
                        searchedInfo[5], searchedInfo[6], searchedInfo[7], searchedInfo[8], searchedInfo[0])
3316
            else:
3317
                return (None, None, None, None, None, None, None, None)
3318

    
3319
        except Exception as ex:
3320
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
3321
                                                            sys.exc_info()[-1].tb_lineno)
3322
            worker.displayLog.emit(MessageType.Error, message)
3323

    
3324
            return (None, None, None, None, None, None, None, None)
3325

    
3326
    @staticmethod
3327
    def detect_symbol_using_server(targetSymbols, listWidget, worker):
3328
        from AppWebService import AppWebService
3329

    
3330
        res = []
3331

    
3332
        app_doc_data = AppDocData.instance()
3333
        project = app_doc_data.getCurrentProject()
3334
        area = app_doc_data.getArea('Drawing')
3335
        
3336
        app_web_service = AppWebService()
3337
        symbols = app_web_service.request_symbol_box(project.name, area.img)
3338

    
3339
        # merge symbol box
3340
        overlap_merges = []
3341
        for rect1 in symbols:
3342
            for rect2 in symbols:
3343
                if rect1 is rect2 or rect1[0] != rect2[0]:
3344
                    continue
3345
                rect1_x_gap = int(rect1[3] / 10)
3346
                rect1_y_gap = int(rect1[4] / 10)
3347
                rect2_x_gap = int(rect1[3] / 10)
3348
                rect2_y_gap = int(rect1[4] / 10)
3349

    
3350
                l1, l2 = rect1[1] + rect1_x_gap, rect2[1] + rect2_x_gap
3351
                r1, r2 = rect1[1] + rect1[3] - rect1_x_gap, rect2[1] + rect2[3] - rect2_x_gap
3352
                l_x, s_x = [l1, r1], [l2, r2]
3353
                t1, t2 = rect1[2] + rect1_y_gap, rect2[2] + rect2_y_gap
3354
                b1, b2 = rect1[2] + rect1[4] - rect1_y_gap, rect2[2] + rect2[4] - rect2_y_gap
3355
                l_y, s_y = [t1, b1], [t2, b2]
3356
                if not (max(l_x) < min(s_x) or max(s_x) < min(l_x)) and \
3357
                    not (max(l_y) < min(s_y) or max(s_y) < min(l_y)):
3358
                    inserted = False
3359
                    for merge in overlap_merges:
3360
                        if (rect1 in merge) and (rect2 in merge):
3361
                            inserted = True
3362
                            break
3363
                        elif (rect1 in merge) and (rect2 not in merge):
3364
                            merge.append(rect2)
3365
                            inserted = True
3366
                            break
3367
                        elif (rect2 in merge) and (rect1 not in merge):
3368
                            merge.append(rect1)
3369
                            inserted = True
3370
                            break
3371
                    if not inserted:
3372
                        overlap_merges.append([rect1, rect2])
3373

    
3374
        for merge in overlap_merges:
3375
            for rect in merge:
3376
                if rect in symbols:
3377
                    symbols.remove(rect)
3378
                else:
3379
                    pass
3380
                    #print(str(rect))
3381

    
3382
        for merge in overlap_merges:
3383
            max_x, max_y, min_x, min_y = 0, 0, sys.maxsize, sys.maxsize
3384
            ratio = 0
3385
            for rect in merge:
3386
                if rect[5] > ratio:
3387
                    ratio = rect[5]
3388

    
3389
                if rect[1] < min_x:
3390
                    min_x = rect[1]
3391
                if rect[1] + rect[3] > max_x:
3392
                    max_x = rect[1] + rect[3]
3393
                if rect[2] < min_y:
3394
                    min_y = rect[2]
3395
                if rect[2] + rect[4] > max_y:
3396
                    max_y = rect[2] + rect[4]
3397

    
3398
            rect = [rect[0], min_x, min_y, max_x - min_x, max_y - min_y, ratio]
3399
            symbols.append(rect)
3400
        # up to here
3401

    
3402
        for targetSymbol in targetSymbols[2]:
3403
            symbolName = targetSymbol.getName()
3404
            symbolType = targetSymbol.getType()
3405
            symbolPath = targetSymbol.getPath()
3406
            symbolThreshold = targetSymbol.getThreshold()
3407
            symbolMinMatchCount = targetSymbol.getMinMatchCount()
3408
            isDetectOnOrigin = targetSymbol.getIsDetectOnOrigin()
3409
            symbolRotateCount = targetSymbol.getRotationCount()
3410
            symbolOcrOption = targetSymbol.getOcrOption()
3411
            isContainChild = targetSymbol.getIsContainChild()
3412
            baseSymbol = targetSymbol.getBaseSymbol()
3413
            additionalSymbol = targetSymbol.getAdditionalSymbol()
3414
            isExceptDetect = targetSymbol.getIsExceptDetect()
3415
            hasInstrumentLabel = targetSymbol.getHasInstrumentLabel()
3416
            text_area = targetSymbol.getText_area()
3417

    
3418
            # check if symbol file is target or not
3419
            if isExceptDetect == 1:
3420
                item = QListWidgetItem('{} file is not target'.format(symbolName))
3421
                item.setBackground(QColor('green'))
3422
                listWidget.addItem(item)
3423
                continue
3424

    
3425
            foundSymbolCount = 0
3426

    
3427
            # check if symbol file exists
3428
            if not os.path.isfile(symbolPath):
3429
                item = QListWidgetItem('{} file not found'.format(symbolName))
3430
                item.setBackground(QColor('red'))
3431
                listWidget.addItem(item)
3432
                continue
3433
            # up to here
3434

    
3435
            sym = cv2.imread(symbolPath, 1)
3436
            symGray = Worker.cvtGrayImage(sym)
3437

    
3438
            for symbol in symbols:
3439
                if symbol[0] == symbolName:
3440
                    searchedItemSp, symbolRotatedAngle, flipped, originalPoint, connectionPoint, sw, sh, score = \
3441
                                Worker.calculate_exact_position(area, symGray, targetSymbol, symbol, worker)
3442
                    if not searchedItemSp:
3443
                        continue
3444

    
3445
                    #hitRate = symbol[5]
3446
                    hitRate = score
3447

    
3448
                    Worker.addSearchedSymbol(symbolName, symbolType,
3449
                                    searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount,
3450
                                    hitRate, symbolRotatedAngle,
3451
                                    isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
3452
                                    isContainChild,
3453
                                    originalPoint, connectionPoint, baseSymbol, additionalSymbol,
3454
                                    isExceptDetect,
3455
                                    detectFlip=1 if flipped else 0,
3456
                                    hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
3457

    
3458
        return symbols
3459
    '''
3460
        @brief   detect symbols on PID
3461
        @history humkyung 2018.06.08 add parameteres for signal
3462
    '''
3463
    @staticmethod
3464
    def detectSymbolsOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal):
3465
        res = []
3466

    
3467
        if type(targetSymbols) is list:
3468
            for detailTarget in targetSymbols:
3469
                res.extend(Worker.detectSymbolOnPid(mainRes, detailTarget, listWidget, updateProgressSignal))
3470
        else:
3471
            res = Worker.detectSymbolOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal)
3472

    
3473
        return res
3474

    
3475
    @staticmethod
3476
    def convertDirectionCodeToValue(directionCode):
3477
        if directionCode == "UP":
3478
            return 0
3479
        elif directionCode == "RIGHT":
3480
            return 1
3481
        elif directionCode == "DOWN":
3482
            return 2
3483
        elif directionCode == "LEFT":
3484
            return 3
3485
        else:
3486
            return -1
3487

    
3488
    @staticmethod
3489
    def convertValueToDirectionCode(value):
3490
        if value == 0:
3491
            return "UP"
3492
        elif value == 1:
3493
            return "RIGHT"
3494
        elif value == 2:
3495
            return "DOWN"
3496
        elif value == 3:
3497
            return "LEFT"
3498
        else:
3499
            return "NONE"
3500

    
3501
    @staticmethod
3502
    def drawFoundSymbolsOnCanvas(drawingPath, symbols, textInfos, listWidget):
3503
        """draw found symbols and texts to image"""
3504

    
3505
        global src
3506
        global ocrCompletedSrc
3507
        global canvas
3508

    
3509
        app_doc_data = AppDocData.instance()
3510
        canvas = np.zeros(app_doc_data.imgSrc.shape, np.uint8)
3511
        canvas[::] = 255
3512

    
3513
        try:
3514
            project = app_doc_data.getCurrentProject()
3515

    
3516
            for symbol in symbols:
3517
                Worker.drawFoundSymbols(symbol, listWidget)
3518

    
3519
            for text in textInfos:
3520
                left = text.getX()
3521
                top = text.getY()
3522
                right = text.getX() + text.getW()
3523
                bottom = text.getY() + text.getH()
3524

    
3525
                canvas[top:bottom, left:right] = app_doc_data.imgSrc[top:bottom, left:right]
3526

    
3527
            cv2.imwrite(os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(drawingPath)), canvas)
3528
        except Exception as ex:
3529
            from App import App
3530
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
3531
                                                           sys.exc_info()[-1].tb_lineno)
3532
            App.mainWnd().addMessage.emit(MessageType.Error, message)
3533

    
3534
    '''
3535
        @history    2018.04.27  Jeongwoo    Remove Tesseract Log on listWidget
3536
                    2018.05.04  Jeongwoo    Change method to OCR with tesseract_ocr_module.py
3537
                    2018.05.09  Jeongwoo    Add global variable textInfoList, Remove text in symbol and Add tesseract result text
3538
                    2018.05.10  Jeongwoo    Remove not used if-statement
3539
                    2018.06.19  Jeongwoo    When detect text in symbol, use getTextAreaInfo() and Tesseract
3540
                    2018.06.21  Jeongwoo    Add if-statement for way to detect text by Type A
3541
    '''
3542

    
3543
    @staticmethod
3544
    def drawFoundSymbols(symbol, listWidget):
3545
        global src
3546
        global canvas
3547
        #global WHITE_LIST_CHARS
3548
        global searchedSymbolList
3549
        global textInfoList
3550

    
3551
        # symbolId = symbol.getId()
3552
        symbolPath = symbol.getPath()
3553
        symbolSp = symbol.getSp()
3554
        symbolRotatedAngle = symbol.getRotatedAngle()
3555

    
3556
        symImg = cv2.cvtColor(cv2.imread(symbolPath, 1), cv2.COLOR_BGR2GRAY)
3557
        if symbol.getDetectFlip() is 1:
3558
            symImg = cv2.flip(symImg, 1)
3559
        for i in range(symbolRotatedAngle // 90):
3560
            symImg = cv2.rotate(symImg, cv2.ROTATE_90_COUNTERCLOCKWISE)
3561

    
3562
        w, h = symImg.shape[::-1]
3563
        canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w] = cv2.bitwise_and(
3564
            canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w], symImg)
3565

    
3566
    @staticmethod
3567
    def remove_equipment_package(scene, area):
3568
        """ remove equipment package area from drawing image """
3569
        for item in scene.items():
3570
            if issubclass(type(item), QEngineeringVendorItem) and item.pack_type =='Equipment Package':
3571
                points = []
3572
                for conn in item.connectors:
3573
                    points.append([round(conn.center()[0] - area.x), round(conn.center()[1] - area.y)])
3574
                
3575
                points = np.array(points, np.int32)
3576
                cv2.fillConvexPoly(area.img, points, 255)
3577

    
3578
    @staticmethod
3579
    def remove_detected_symbol_image(sym, imgSrc, lock=True):
3580
        """remove detected symbol image from drawing image"""
3581
        if lock:
3582
            global threadLock
3583

    
3584
        try:
3585
            path = sym.getPath()
3586
            sp = (int(sym.getSp()[0]), int(sym.getSp()[1]))
3587
            sw = int(sym.getWidth())
3588
            sh = int(sym.getHeight())
3589
            angle = int(sym.getRotatedAngle())
3590
            # get symbol image
3591
            sym_img = cv2.imread(path)
3592
            sym_img = cv2.cvtColor(sym_img, cv2.COLOR_BGR2GRAY)
3593
            # symImg = cv2.threshold(Worker.cvtGrayImage(symImg), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
3594
            if sym.getDetectFlip() is 1:
3595
                sym_img = cv2.flip(sym_img, 1)
3596

    
3597
            for i in range(angle // 90):
3598
                sym_img = cv2.rotate(sym_img, cv2.ROTATE_90_COUNTERCLOCKWISE)
3599
            # up to here
3600

    
3601
            if lock:
3602
                threadLock.acquire()
3603
            temp = imgSrc[sp[1]:sp[1] + sh, sp[0]:sp[0] + sw]
3604
            sym_img = cv2.erode(sym_img, np.ones((5, 5), np.uint8))
3605
            mask = cv2.bitwise_or(temp, sym_img)
3606
            imgXOR = cv2.bitwise_xor(temp, mask)
3607
            imgSrc[sp[1]:sp[1] + sh, sp[0]:sp[0] + sw] = cv2.bitwise_not(imgXOR)
3608
        except Exception as ex:
3609
            from App import App
3610
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
3611
                                                           sys.exc_info()[-1].tb_lineno)
3612
            App.mainWnd().addMessage.emit(MessageType.Error, message)
3613
        finally:
3614
            if lock:
3615
                threadLock.release()
3616

    
3617
    '''
3618
        @brief  get difference between given original and recognized image
3619
        @author humkyung
3620
        @date   2018.06.11
3621
    '''
3622

    
3623
    @staticmethod
3624
    def getDifference(orgImagePath, recImagePath):
3625
        import re
3626

    
3627
        global ocrCompletedSrc
3628
        global textInfoList
3629

    
3630
        try:
3631
            app_doc_data = AppDocData.instance()
3632
            imgOriginal = app_doc_data.imgSrc
3633

    
3634
            # remove not drawing area
3635
            configs = app_doc_data.getConfigs('{} Equipment Desc Area'.format(app_doc_data.imgName))
3636
            for config in configs:
3637
                found = re.findall('\\d+', config.value)
3638
                if len(found) == 4:
3639
                    cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
3640
                                    (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
3641

    
3642
            configs = app_doc_data.getConfigs('{} Typical Area'.format(app_doc_data.imgName))
3643
            for config in configs:
3644
                found = re.findall('\\d+', config.value)
3645
                if len(found) == 4:
3646
                    cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
3647
                                    (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
3648

    
3649
            noteArea = app_doc_data.getArea('Note')
3650
            if noteArea is not None:
3651
                noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height),
3652
                                round(noteArea.x):round(noteArea.x + noteArea.width)].copy()
3653
                cv2.rectangle(imgOriginal, (round(noteArea.x), round(noteArea.y)),
3654
                                (round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1)
3655
            # up to here
3656

    
3657
            """
3658
            if os.path.isfile(orgImagePath) and os.path.isfile(recImagePath):
3659
                imgOriginal = \
3660
                    cv2.threshold(Worker.cvtGrayImage(cv2.imread(orgImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1]
3661

3662
                configs = app_doc_data.getConfigs('Filter', 'DilateSize')
3663
                if 1 == len(configs) and int(configs[0].value) is not 0:
3664
                    size = int(configs[0].value)
3665
                    kernel = np.ones((size, size), np.uint8)
3666
                    imgOriginal = cv2.erode(imgOriginal, kernel, iterations=1)
3667

3668
                # remove not drawing area
3669
                configs = app_doc_data.getConfigs('{} Equipment Desc Area'.format(app_doc_data.imgName))
3670
                for config in configs:
3671
                    found = re.findall('\\d+', config.value)
3672
                    if len(found) == 4:
3673
                        cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
3674
                                      (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
3675

3676
                configs = app_doc_data.getConfigs('{} Typical Area'.format(app_doc_data.imgName))
3677
                for config in configs:
3678
                    found = re.findall('\\d+', config.value)
3679
                    if len(found) == 4:
3680
                        cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
3681
                                      (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
3682

3683
                noteArea = app_doc_data.getArea('Note')
3684
                if noteArea is not None:
3685
                    noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height),
3686
                                   round(noteArea.x):round(noteArea.x + noteArea.width)].copy()
3687
                    cv2.rectangle(imgOriginal, (round(noteArea.x), round(noteArea.y)),
3688
                                  (round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1)
3689
                # up to here
3690

3691
                imgRecognized = \
3692
                    cv2.threshold(Worker.cvtGrayImage(cv2.imread(recImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1]
3693

3694
                imgDiff = np.ones(imgOriginal.shape, np.uint8) * 255
3695

3696
                area = app_doc_data.getArea('Drawing')
3697
                if area is not None:
3698
                    x = round(area.x)
3699
                    y = round(area.y)
3700
                    width = round(area.width)
3701
                    height = round(area.height)
3702
                    imgNotOper = cv2.bitwise_not(imgRecognized[y:y + height, x:x + width])
3703
                    imgDiff[y:y + height, x:x + width] = cv2.bitwise_xor(imgOriginal[y:y + height, x:x + width],
3704
                                                                         imgNotOper)
3705

3706
                # remove noise
3707
                imgDiff = cv2.dilate(imgDiff, np.ones((2, 2), np.uint8))
3708

3709
                project = app_doc_data.getCurrentProject()
3710
                cv2.imwrite(os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(orgImagePath)), imgDiff)
3711
            """
3712
        except Exception as ex:
3713
            from App import App
3714
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
3715
                                                           sys.exc_info()[-1].tb_lineno)
3716
            App.mainWnd().addMessage.emit(MessageType.Error, message)
3717

    
3718

    
3719
'''
3720
    @history    2018.05.25  Jeongwoo    Add pyqtSignal(svgItemClicked, itemRemoved)
3721
'''
3722

    
3723

    
3724
class QRecognitionDialog(QDialog):
3725
    svgItemClicked = pyqtSignal(SymbolSvgItem)
3726
    itemRemoved = pyqtSignal(QGraphicsItem)
3727
    unBlockEvent = pyqtSignal()
3728

    
3729
    '''
3730
        @history    2018.05.25  Jeongwoo    Add parameter and initialize / Connect recognizeButton signal and slot
3731
                    2018.05.29  Jeongwoo    Chnage parameter(graphicsView → parent) and Get graphicsView from parent
3732
    '''
3733

    
3734
    def __init__(self, parent, drawings):  # Parent is MainWindow
3735
        from AppDocData import AppDocData
3736

    
3737
        QDialog.__init__(self, parent)
3738

    
3739
        self.parent = parent
3740
        self._scene = QGraphicsScene()
3741
        self._text_scene = QGraphicsScene()
3742
        self._line_scene = QGraphicsScene()
3743
        self.drawings = drawings
3744
        self.xmlPath = None
3745
        self.ui = Recognition_UI.Ui_Recognition()
3746
        self.ui.setupUi(self)
3747
        self.isTreated = False
3748

    
3749
        self.ui.buttonBox.setEnabled(True)
3750
        self.ui.listWidget.model().rowsInserted.connect(self.rowInserted)
3751
        self.ui.recognizeButton.clicked.connect(self.recognizeButtonClicked)
3752
        self.ui.lineCheckBox.stateChanged.connect(self.checkBoxChanged)
3753
        self.ui.checkBoxSymbol.stateChanged.connect(self.checkBoxChanged)
3754
        self.ui.checkBoxText.stateChanged.connect(self.checkBoxChanged)
3755
        self.ui.checkBoxTraining.stateChanged.connect(self.checkBoxChanged)
3756
        self.isAccepted = False
3757

    
3758
        appDocData = AppDocData.instance()
3759
        configs = appDocData.getAppConfigs('app', 'mode')
3760
        if configs and 1 == len(configs) and 'advanced' == configs[0].value:
3761
            pass
3762
        else:
3763
            self.ui.checkBoxTraining.setVisible(False)
3764

    
3765
        if False:#len(self.drawings) == 1 and appDocData.activeDrawing and appDocData.activeDrawing == self.drawings[0]:
3766
            self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
3767
            self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
3768
            self.ui.checkBoxText.setCheckState(Qt.Unchecked)
3769
        else:
3770
            self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
3771
            self.ui.lineCheckBox.setCheckState(Qt.Checked)
3772
            self.ui.checkBoxText.setCheckState(Qt.Checked)
3773
            #self.ui.checkBoxSymbol.setEnabled(False)
3774
            #self.ui.lineCheckBox.setEnabled(False)
3775
            #self.ui.checkBoxText.setEnabled(False)
3776

    
3777
        self.ui.progressBarBatch.setFormat('{}/{}'.format('0', str(len(self.drawings))))
3778

    
3779
    def checkBoxChanged(self, checkState):
3780
        '''
3781
        @brief      line and text cannot be reocognized alone
3782
        @author     euisung
3783
        @date       2019.05.14
3784
        '''
3785
        if self.ui.checkBoxTraining.isChecked():
3786
            self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
3787
            self.ui.checkBoxText.setCheckState(Qt.Unchecked)
3788
            self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
3789
            self.ui.lineCheckBox.setEnabled(False)
3790
            self.ui.checkBoxText.setEnabled(False)
3791
            self.ui.checkBoxSymbol.setEnabled(False)
3792
            self.ui.recognizeButton.setText('Extract')
3793
            return
3794
        else:
3795
            self.ui.lineCheckBox.setEnabled(True)
3796
            self.ui.checkBoxText.setEnabled(True)
3797
            self.ui.checkBoxSymbol.setEnabled(True)
3798
            self.ui.recognizeButton.setText('Recognize')
3799

    
3800
        if checkState is int(Qt.Checked):
3801
            if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked():
3802
                self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
3803
            elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked():
3804
                self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
3805
        elif checkState is int(Qt.Unchecked):
3806
            if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked():
3807
                self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
3808
            elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked():
3809
                self.ui.checkBoxText.setCheckState(Qt.Unchecked)
3810

    
3811
    '''
3812
        @brief      QListWidget Row Inserted Listener
3813
                    Whenever row inserted, scroll to bottom
3814
        @author     Jeongwoo
3815
        @date       18.04.12
3816
    '''
3817

    
3818
    def rowInserted(self, item):
3819
        self.ui.listWidget.scrollToBottom()
3820

    
3821
    def accept(self):
3822
        self.isAccepted = True
3823
        QDialog.accept(self)
3824

    
3825
    def recognizeButtonClicked(self, event):
3826
        """
3827
        @brief      start recognization
3828
        @author     humkyung
3829
        @history    humkyung 2018.10.05 clear imgSrc before recognizing
3830
        """
3831
        if self.ui.checkBoxSymbol.isChecked():  # or self.ui.checkBoxText.isChecked() or self.ui.lineCheckBox.isChecked():
3832
            appDocData = AppDocData.instance()
3833
            appDocData.imgSrc = None
3834
            area = appDocData.getArea('Drawing')
3835
            if area is None:
3836
                QMessageBox.about(self, self.tr("Notice"), self.tr('Please select drawing area.'))
3837
                return
3838

    
3839
            self.ui.recognizeButton.setEnabled(False)
3840
            self.ui.buttonBox.setEnabled(False)
3841
            self.ui.progressBar.setValue(0)
3842
            self.ui.listWidget.addItem("Initializing...")
3843
            self.startThread()
3844

    
3845
            self.isTreated = True
3846

    
3847
    '''
3848
        @brief  add item to list widget
3849
        @author humkyung
3850
        @date   2018.06.19
3851
    '''
3852

    
3853
    def addListItem(self, item):
3854
        self.ui.listWidget.addItem(item)
3855

    
3856
    '''
3857
        @brief  update progressbar with given value
3858
        @author humkyung
3859
        @date   2018.06.08
3860
    '''
3861

    
3862
    def updateProgress(self, maxValue, image_path):
3863
        self.ui.progressBar.setMaximum(maxValue)
3864
        self.ui.progressBar.setValue(self.ui.progressBar.value() + 1)
3865

    
3866
        if image_path is not None and os.path.isfile(image_path):
3867
            self.ui.labelImage.setPixmap(QPixmap(image_path))
3868
        elif image_path is not None:
3869
            self.ui.labelImage.setText(image_path)
3870

    
3871
    def updateBatchProgress(self, maxValue, weight):
3872
        """update batch progressbar"""
3873
        self.ui.progressBarBatch.setMaximum(maxValue * 5)
3874
        value = self.ui.progressBarBatch.value() + weight
3875
        self.ui.progressBarBatch.setValue(value)
3876
        self.ui.progressBarBatch.setFormat('{}/{}'.format(str(int(value / 5)), str(maxValue)))
3877

    
3878
    '''
3879
        @brief      display title
3880
        @author     humkyung
3881
        @date       2018.08.20
3882
    '''
3883

    
3884
    def displayTitle(self, title):
3885
        self.ui.labelTitle.setText(title)
3886

    
3887
    def startThread(self):
3888
        """start thread"""
3889
        from PyQt5 import QtWidgets
3890
        from App import App
3891

    
3892
        self.ui.buttonBox.setDisabled(True)
3893

    
3894
        self.mutex = QMutex()
3895
        self.cond = QWaitCondition()
3896

    
3897
        # 1 - create Worker and Thread inside the Form
3898
        self.obj = Worker(self.mutex, self.cond)  # no parent!
3899
        self.obj.symbol_time = None
3900
        self.obj.text_time = None
3901
        self.obj.drawings = self.drawings
3902
        self.obj.listWidget = self.ui.listWidget
3903
        self.obj.scene = self._scene
3904
        self.obj.text_scene = self._text_scene
3905
        self.obj.line_scene = self._line_scene
3906
        self.obj.scene._end = False  # for waiting each drawing finished
3907
        self.obj.isSymbolChecked = self.ui.checkBoxSymbol.isChecked()
3908
        self.obj.isTextChecked = self.ui.checkBoxText.isChecked()
3909
        self.obj.isLineChecked = self.ui.lineCheckBox.isChecked()
3910
        self.obj.isTrainingChecked = self.ui.checkBoxTraining.isChecked()
3911
        self.thread = QThread()  # no parent!
3912

    
3913
        # 2 - Move the Worker object to the Thread object
3914
        self.obj.moveToThread(self.thread)
3915

    
3916
        # 3 - Connect Worker Signals to the Thread slots
3917
        self.obj.finished.connect(self.thread.quit)
3918
        self.obj.displayMessage.connect(self.addListItem)
3919
        self.obj.updateProgress.connect(self.updateProgress)
3920
        self.obj.updateBatchProgress.connect(self.updateBatchProgress)
3921
        self.obj.displayLog.connect(App.mainWnd().addMessage)
3922
        self.obj.displayTitle.connect(self.displayTitle)
3923
        self.obj.add_detected_items_to_scene.connect(self.add_detected_items_to_scene)
3924
        self.obj.save_scene.connect(self.save_scene)
3925
        self.obj.preset_execute.connect(self.preset_execute)
3926
        self.obj.item_remove.connect(self.item_remove)
3927
        self.obj.add_predata_to_scene.connect(self.add_predata_to_scene)
3928
        self.obj.clear_scene.connect(self.clear_scene)
3929

    
3930
        # 4 - Connect Thread started signal to Worker operational slot method
3931
        self.thread.started.connect(self.obj.procCounter)
3932

    
3933
        # 5 - Thread finished signal will close the app if you want!
3934
        self.thread.finished.connect(self.dlgExit)
3935

    
3936
        # 6 - Start the thread
3937
        self.thread.start()
3938

    
3939
        self.tmStart = timeit.default_timer()
3940

    
3941
    '''
3942
        @brief set buttonbox's enabled flag
3943
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
3944
                    2018.06.14  Jeongwoo    Change sentence order
3945
                    2018.11.26  euisung     add drawing part
3946
                    2018.11.26  euising     move save and unknown part into executerecognition
3947
    '''
3948

    
3949
    def dlgExit(self):
3950
        import timeit
3951

    
3952
        try:
3953
            self.ui.buttonBox.setEnabled(True)
3954

    
3955
            self.ui.progressBar.setValue(self.ui.progressBar.maximum())
3956
        except Exception as ex:
3957
            from App import App
3958
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
3959
                                                           sys.exc_info()[-1].tb_lineno)
3960
            App.mainWnd().addMessage.emit(MessageType.Error, message)
3961
        finally:
3962
            self.tmStop = timeit.default_timer()
3963
            seconds = self.tmStop - self.tmStart
3964
            self.ui.listWidget.addItem("\nRunning Time(total) : {} min".format(str(round(seconds / 60, 1))))
3965
            if self.obj.symbol_time:
3966
                self.ui.listWidget.addItem("Running Time(symbol) : {} min".format(str(round(self.obj.symbol_time / 60, 1))))
3967
            if self.obj.text_time:
3968
                self.ui.listWidget.addItem("Running Time(text) : {} min".format(str(round(self.obj.text_time / 60, 1))))
3969
            self.ui.listWidget.addItem("\n")
3970

    
3971
    '''
3972
        @history    2018.05.29  Jeongwoo    Call parent's method
3973
                    2018.05.30  Jeongwoo    Change method name
3974
                    2018.06.09  humkyung    set progressbar value to maximum
3975
                    2018.11.12  euisung     add title block properties
3976
                    2018.11.29  euisung     no more used
3977
    '''
3978

    
3979
    def drawDetectedItems(self, symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList, loop):
3980
        try:
3981
            self.ui.progressBar.setValue(self.ui.progressBar.maximum())
3982
            self.parent.drawDetectedItems(symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList)
3983
        finally:
3984
            loop.quit()
3985

    
3986
    '''
3987
        @brief      draw detected lines
3988
        @author     humkyung
3989
        @date       2018.08.23
3990
        @history    2018.11.27  euisung     no more used
3991
    '''
3992

    
3993
    def drawDetectedLines(self, lineList, loop):
3994
        try:
3995
            self.parent.drawDetectedLines(lineList, self.obj)
3996
        finally:
3997
            loop.quit()
3998

    
3999
    '''
4000
        @brief      draw detected lines
4001
        @author     euisung
4002
        @date       2018.11.27
4003
        @history    2018.11.27  euisung     no more used
4004
    '''
4005

    
4006
    def drawUnknownItems(self, path, loop):
4007
        try:
4008
            self.parent.drawUnknownItems(path)
4009
        finally:
4010
            loop.quit()
4011

    
4012
    def add_predata_to_scene(self, drawing, scene, symbol: bool, text: bool, line: bool, unknown: bool, package: bool) \
4013
            -> None:
4014
        """add predata to scene"""
4015
        from LoadCommand import LoadCommand
4016
        from App import App
4017

    
4018
        try:
4019
            cmd = LoadCommand()
4020
            cmd.display_message.connect(App.mainWnd().onAddMessage)
4021
            cmd.execute((drawing, scene), symbol=symbol, text=text, line=line, unknown=unknown,
4022
                        package=package, update=False)
4023
        except Exception as ex:
4024
            from App import App
4025
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
4026
                      f"{sys.exc_info()[-1].tb_lineno}"
4027
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4028
        finally:
4029
            self.cond.wakeAll()
4030

    
4031
    def clear_scene(self, scene1, scene2, scene3) -> None:
4032
        """clear scenes"""
4033

    
4034
        try:
4035
            scene1.clear()
4036
            scene2.clear()
4037
            scene3.clear()
4038

    
4039
        except Exception as ex:
4040
            from App import App
4041
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
4042
                      f"{sys.exc_info()[-1].tb_lineno}"
4043
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4044
        finally:
4045
            self.cond.wakeAll()
4046

    
4047
    def item_remove(self, items):
4048
        """ remove useless items """
4049
        from App import App
4050

    
4051
        try:
4052
            for item in items:
4053
                #item.transfer.onRemoved.emit(item)
4054
                App.mainWnd().itemRemoved(item)
4055
            '''
4056
            from ReplaceInsertCommand import ReplaceInsertCommand
4057

4058
            cmd = ReplaceInsertCommand()
4059
            cmd.display_message.connect(App.mainWnd().onAddMessage)
4060
            cmd.item_remove(items)
4061
            '''
4062

    
4063
        except Exception as ex:
4064
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
4065
                                                           sys.exc_info()[-1].tb_lineno)
4066
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4067
        finally:
4068
            self.cond.wakeAll()
4069
    
4070
    def preset_execute(self, scene, find_symbol, replace_symbol, replace_action, condition):
4071
        """ run preset """
4072
        from App import App
4073

    
4074
        try:
4075
            from ReplaceInsertCommand import ReplaceInsertCommand
4076

    
4077
            cmd = ReplaceInsertCommand()
4078
            cmd.display_message.connect(App.mainWnd().onAddMessage)
4079
            cmd.execute(scene, find_symbol, replace_symbol, replace_action, condition)
4080
        except Exception as ex:
4081
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
4082
                      f"{sys.exc_info()[-1].tb_lineno}"
4083
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4084
        finally:
4085
            self.cond.wakeAll()
4086

    
4087
    def save_scene(self, scene):
4088
        """ save scene """
4089
        from App import App
4090

    
4091
        try:
4092
            from SaveWorkCommand import SaveWorkCommand
4093

    
4094
            save = SaveWorkCommand(scene)
4095
            save.run()
4096
            #SaveWorkCommand.save_to_database()
4097
            #SaveWorkCommand.save_to_xml()
4098
        except Exception as ex:
4099
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
4100
                                                           sys.exc_info()[-1].tb_lineno)
4101
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4102
        finally:
4103
            self.cond.wakeAll()
4104

    
4105
    def add_detected_items_to_scene(self, scene, flanges) -> None:
4106
        """add detected items to scene"""
4107
        from App import App
4108

    
4109
        app_doc_data = AppDocData.instance()
4110

    
4111
        try:
4112
            for item in scene.items():
4113
                if issubclass(type(item), QEngineeringVendorItem):
4114
                    app_doc_data.allItems.append(item)
4115

    
4116
            # symbol
4117
            for symbol in app_doc_data.symbols:
4118
                if issubclass(type(symbol), SymbolSvgItem):
4119
                    symbol.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
4120
                    symbol.addSvgItemToScene(scene)
4121
                else:
4122
                    scene.addItem(symbol)
4123

    
4124
            # text
4125
            for text in app_doc_data.texts:
4126
                text.addTextItemToScene(scene)
4127

    
4128
            #for lineNo in app_doc_data.tracerLineNos:
4129
            #    lineNo.addTextItemToScene(scene)
4130

    
4131
            # remove lines which is located inside symbol
4132
            for symbol in app_doc_data.symbols:
4133
                rect = symbol.sceneBoundingRect()
4134
                rect.adjust(-10, -10, 10, 10)
4135
                matches = [line for line in app_doc_data.lines if rect.contains(line.line().p1()) and
4136
                           rect.contains(line.line().p2())]# and not line.has_connection]
4137
                for line in matches:
4138
                    app_doc_data.allItems.remove(line)
4139
                    app_doc_data.lines.remove(line)
4140
            # up to here
4141

    
4142
            for line in app_doc_data.lines:
4143
                scene.addItem(line)
4144
                # line.transfer.onRemoved.connect(self.itemRemoved)
4145
                for conn in line.connectors:
4146
                    conn.transfer.onPosChanged.connect(line.onConnectorPosChaned)
4147

    
4148
            # insert flange
4149
            configs = app_doc_data.getConfigs('Project', 'Operation')
4150
            instrument = int(configs[0].value) if configs else 1
4151
            if instrument == 1:
4152
                configs = app_doc_data.getConfigs('Default', 'Flange')
4153
                flange_name = configs[0].value if 1 == len(configs) else 'flange'
4154
                for flange in flanges[0]:
4155
                    svg = QtImageViewer.createSymbolObject(flange_name)
4156
                    if svg:
4157
                        QtImageViewer.matchSymbolToLine(scene, svg, QPointF(flange[0], flange[1]), strict=True, auto=True)
4158

    
4159
                configs = app_doc_data.getConfigs('Default', 'Blind')
4160
                flange_name = configs[0].value if 1 == len(configs) else 'blind flange'
4161
                for flange in flanges[1]:
4162
                    svg = QtImageViewer.createSymbolObject(flange_name)
4163
                    if svg:
4164
                        QtImageViewer.matchSymbolToLine(scene, svg, QPointF(flange[0], flange[1]), strict=False, auto=True)
4165

    
4166
                for unknown in app_doc_data.unknowns + app_doc_data.lineIndicators:
4167
                    scene.addItem(unknown)
4168

    
4169
        except Exception as ex:
4170
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
4171
                                                           sys.exc_info()[-1].tb_lineno)
4172
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4173
        finally:
4174
            self.cond.wakeAll()
4175
            scene._end = True