프로젝트

일반

사용자정보

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

hytos / DTI_PID / DTI_PID / RecognitionDialog.py @ d1994d14

이력 | 보기 | 이력해설 | 다운로드 (195 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
            factory = TextItemFactory.instance()
331

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

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

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

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

    
366
        try:
367
            app_doc_data = AppDocData.instance()
368
            factory = TextItemFactory.instance()
369

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

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

    
383
                    item = factory.createTextItem(textInfo)
384

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

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

    
402
        try:
403
            app_doc_data = AppDocData.instance()
404
            factory = TextItemFactory.instance()
405

    
406
            # parse texts
407
            for textInfos in textInfoList:
408
                if len(textInfos[1]) is 0:
409
                    continue
410

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

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

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

    
447
    def create_unknown_items(self, path):
448
        from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
449
        from EngineeringLineItem import QEngineeringLineItem
450
        from EngineeringUnknownItem import QEngineeringUnknownItem
451

    
452
        try:
453
            app_doc_data = AppDocData.instance()
454
            project = app_doc_data.getCurrentProject()
455
            windowSize = app_doc_data.getSlidingWindowSize()
456

    
457
            thickness = int(windowSize[1] / 2)
458

    
459
            area = app_doc_data.getArea('Drawing')
460
            diffFilePath = os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(path))
461

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

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

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

    
483
            contours, hierarchy = cv2.findContours(imgNot, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
484

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

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

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

    
519
            """
520
            if app_doc_data.needReOpening is not None:
521
                app_doc_data.needReOpening = True
522
            """
523

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

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

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

545
                contours, hierarchy = cv2.findContours(imgNot, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
546

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

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

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

567
                    items = [item for item in diffItems if rect.contains(item.boundingRect())]
568
                    for item in items:
569
                        diffItems.remove(item)
570
                    '''
571

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

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

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

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

    
609
        if w < 250 and h < 250:
610
            return ('Unknown', [])
611

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

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

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

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

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

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

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

    
696
        return ('Unknown', [])
697

    
698
    def is_line_no_indicator(self, w, h, maxDifAngle, baseDifV, baseLines, otherLines):
699
        """determine line no indicator"""
700
        import math
701

    
702
        if w < 250 and h < 250:
703
            return (False, None)
704

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

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

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

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

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

    
792
        global ocrCompletedSrc
793
        global threadLock
794
        global searchedSymbolList
795
        global textInfoList
796
        global maxProgressValue
797

    
798
        try:
799
            app_doc_data = AppDocData.instance()
800
            #drawings = app_doc_data.getDrawings()
801
            project = app_doc_data.getCurrentProject()
802
            textDetector = TextDetector()
803

    
804
            configs = app_doc_data.getConfigs('Engine', 'Symbol')
805
            if (configs and int(configs[0].value) is 1) or not configs or worker.isTrainingChecked:
806
                Worker.initTargetSymbolDataList()
807
            else:
808
                Worker.initTargetSymbolDataList(all=True)
809

    
810
            for drawing in drawings:
811
                ocrCompletedSrc = []
812
                searchedSymbolList = []
813
                textInfoList = []
814

    
815
                mainRes = drawing.file_path
816
                if not os.path.isfile(mainRes):
817
                    item = QListWidgetItem('{} file is not found'.format(os.path.basename(mainRes)))
818
                    item.setBackground(Qt.red)
819
                    listWidget.addItem(item)
820
                    continue
821

    
822
                #worker.scene.clear()
823
                #worker.text_scene.clear()
824
                #worker.line_scene.clear()
825
                worker.clear_scene.emit(worker.scene, worker.text_scene, worker.line_scene)
826
                worker.cond.wait(worker.mutex)
827

    
828
                app_doc_data.clearItemList(True)
829

    
830
                app_doc_data.setImgFilePath(mainRes)
831
                app_doc_data.imgSrc = None
832
                matches = [drawing for drawing in drawings if app_doc_data.imgName == os.path.splitext(drawing.name)[0]]
833
                app_doc_data.activeDrawing = drawing # matches[0] if matches else Drawing(None, app_doc_data.imgName, None)
834
                #app_doc_data.activeDrawing.set_pid_source(Image.open(mainRes))
835

    
836
                # Load equipment package
837
                worker.add_predata_to_scene.emit(app_doc_data.activeDrawing, worker.scene, False, False, False, False, True)
838
                worker.cond.wait(worker.mutex)
839

    
840
                # remove not drawing area
841
                configs = app_doc_data.getConfigs('{} Equipment Desc 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
                configs = app_doc_data.getConfigs('{} Typical Area'.format(app_doc_data.imgName))
849
                for config in configs:
850
                    found = re.findall('\\d+', config.value)
851
                    if len(found) == 4:
852
                        cv2.rectangle(app_doc_data.imgSrc, (int(found[0]), int(found[1])),
853
                                      (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
854

    
855
                noteArea = app_doc_data.getArea('Note')
856
                if noteArea is not None:
857
                    noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height),
858
                                   round(noteArea.x):round(noteArea.x + noteArea.width)].copy()
859
                    cv2.rectangle(app_doc_data.imgSrc, (round(noteArea.x), round(noteArea.y)),
860
                                  (round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1)
861
                    #cv2.imwrite('c:\\temp\\note.png', noteArea.img)
862
                    #cv2.imwrite('c:\\temp\\removed_note.png', app_doc_data.imgSrc)
863
                # up to here
864

    
865
                area = app_doc_data.getArea('Drawing')
866
                if area is not None:
867
                    # copy image
868
                    area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height),
869
                               round(area.x):round(area.x + area.width)]
870

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

    
873
                    # area.contours, area.hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
874
                    area.not_img = cv2.bitwise_not(area.img)
875
                    area.contours, area.hierachy = cv2.findContours(area.not_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
876

    
877
                # remove Equipment Package area
878
                configs = app_doc_data.getConfigs('Symbol', 'Detect Inside Package')
879
                if (configs and int(configs[0].value) == -1):
880
                    Worker.remove_equipment_package(worker.scene, area)
881

    
882
                maxProgressValue = 0
883
                start_time = timeit.default_timer()
884
                listWidget.addItem(f"Start recognition {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} : {os.path.basename(mainRes).split('.')[0]}")
885
                threadLock.acquire()
886

    
887
                worker.updateBatchProgress.emit(len(drawings), 1)
888

    
889
                if worker.isSymbolChecked:
890
                    if worker.symbol_time:
891
                        symbol_time_temp = worker.symbol_time
892
                    else:
893
                        symbol_time_temp = None
894
                    worker.symbol_time = timeit.default_timer()
895
                    
896
                    # calculate total count of symbol
897
                    for targetItem in targetSymbolList:
898
                        if type(targetItem) is list:
899
                            maxProgressValue += len(targetItem)
900
                        else:
901
                            maxProgressValue += 1
902
                    # up to here
903
                    maxProgressValue = maxProgressValue * 2
904
                threadLock.release()
905

    
906
                if worker.isSymbolChecked:
907
                    worker.displayTitle.emit(worker.tr('Detecting symbols...'))
908
                 
909
                    configs = app_doc_data.getConfigs('Engine', 'Symbol')
910
                    if (configs and int(configs[0].value) is 1) or not configs or worker.isTrainingChecked:
911
                    # get symbol original way
912
            
913
                        # detect only equipments
914
                        with futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as pool:
915
                            future_equipment = {pool.submit(Worker.detectSymbolsOnPid, mainRes, symbol, listWidget, worker):
916
                                                symbol for symbol in targetSymbolList[0]}
917
                            futures.wait(future_equipment)
918
                        # up to here
919

    
920
                        # detect normal symbols
921
                        with futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as pool:
922
                            future_symbol = {pool.submit(Worker.detectSymbolsOnPid, mainRes, symbol, listWidget, worker):
923
                                            symbol for symbol in targetSymbolList[2]}
924
                            futures.wait(future_symbol)
925
                        # up to here
926
                    else:
927
                        # using AI
928
                        listWidget.addItem(worker.tr('Waiting for symbol information from the server...'))
929
                        Worker.detect_symbol_using_server(targetSymbolList, listWidget, worker)
930

    
931
                    if worker.isTrainingChecked:
932
                        import uuid
933
                        worker.displayTitle.emit(worker.tr('Generating Data...'))
934
                        listWidget.addItem(worker.tr('Generating Data...'))
935

    
936
                        for item in searchedSymbolList:
937
                            path = os.path.join(project.getTempPath(), 'Tile', item.getName())
938
                            if not os.path.exists(path): os.makedirs(path)
939

    
940
                            _img = app_doc_data.imgSrc[round(item.getSp()[1]):round(item.getSp()[1] + item.getHeight()),
941
                                   round(item.getSp()[0]):round(item.getSp()[0] + item.getWidth())]
942
                            cv2.imwrite(os.path.join(path, str(uuid.uuid4()) + '.png'), _img)
943

    
944
                        app_doc_data.activeDrawing.image = None
945
                        worker.updateBatchProgress.emit(len(drawings), 4)
946
                        worker.symbol_time = timeit.default_timer() - worker.symbol_time if not symbol_time_temp else timeit.default_timer() - worker.symbol_time + symbol_time_temp
947
                        continue
948

    
949
                    #cv2.imwrite('c:\\temp\\before-imgSrc.png', area.img)
950
                    ## save original image before remove symbol
951
                    #worker.copy_imgSrc = app_doc_data.imgSrc.copy()
952

    
953
                    # roll back because equipment package was deleted from image
954
                    app_doc_data.imgSrc = app_doc_data.activeDrawing.image_origin
955

    
956
                    searchedTextSymList = []
957
                    for sym in searchedSymbolList:
958
                        Worker.remove_detected_symbol_image(sym, app_doc_data.imgSrc)
959

    
960
                    worker.symbol_time = timeit.default_timer() - worker.symbol_time if not symbol_time_temp else timeit.default_timer() - worker.symbol_time + symbol_time_temp
961
                else:
962
                    '''
963
                    import math
964

965
                    symbolItems = [item for item in worker.scene.items() if issubclass(type(item), SymbolSvgItem)]
966
                    
967
                    for symbolItem in symbolItems:
968
                        symbolName = symbolItem.name
969
                        symbolType = symbolItem.type
970
                        searchedItemSp = [int(symbolItem.loc[0]), int(symbolItem.loc[1])]
971
                        sw = symbolItem.size[0] # check later
972
                        sh = symbolItem.size[1] # check later
973

974
                        symbolThreshold = 50 # not necessary
975
                        symbolMinMatchCount = 0 # not necessary
976
                        hitRate = 80 # not necessary
977
                        
978
                        allowed_error = 0.0001
979
                        #degree 0
980
                        if abs(symbolItem.angle - 0) <= allowed_error:
981
                            symbolRotatedAngle = 0
982
                        #degree 90
983
                        elif abs(symbolItem.angle - 1.57) <= allowed_error:
984
                            symbolRotatedAngle = 90
985
                        #degree 180
986
                        elif abs(symbolItem.angle - 3.14) <= allowed_error:
987
                            symbolRotatedAngle = 180
988
                        #degree 270
989
                        elif abs(symbolItem.angle - 4.71) <= allowed_error:
990
                            symbolRotatedAngle = 270
991
                        else:
992
                            symbolRotatedAngle = math.pi / 180 * symbolItem.angle
993

994
                        isDetectOnOrigin = 80 # not necessary
995
                        symbolRotateCount = 0 # not necessary
996
                        symbolOcrOption = 0 # not necessary
997
                        isContainChild = 0 # not necessary
998
                        originalPoint = [symbolItem.origin[0] - symbolItem.loc[0], symbolItem.origin[1] - symbolItem.loc[1]]
999
                        symbolConnectionPoint = []
1000
                        baseSymbol = str(symbolItem.parentSymbol)
1001
                        additionalSymbol = str(symbolItem.childSymbol)
1002
                        isExceptDetect = 0 # not necessary
1003
                        detectFlip = symbolItem.flip
1004

1005
                        searchedSymbolList.append(Symbol(symbolName, symbolType , 
1006
                            searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount, hitRate, symbolRotatedAngle , 
1007
                            isDetectOnOrigin, symbolRotateCount, symbolOcrOption, isContainChild , 
1008
                            ','.join(str(x) for x in originalPoint), 
1009
                            [], 
1010
                            baseSymbol, additionalSymbol, isExceptDetect, detectFlip))
1011
                        
1012
                        worker.scene.removeItem(symbolItem)
1013
                    
1014
                    pool = futures.ThreadPoolExecutor(max_workers = THREAD_MAX_WORKER)
1015
                    '''
1016
                    # symbolItems = [item for item in worker.scene.items() if issubclass(type(item), SymbolSvgItem)]
1017
                    # appDocData.symbols.extend(symbolItems)
1018
                    # appDocData.allItems.extend(symbolItems)
1019

    
1020
                    # for sym in searchedSymbolList:
1021
                    #    pool.submit(Worker.remove_detected_symbol_image, sym, appDocData.imgSrc)
1022
                    # pool.shutdown(wait = True)
1023

    
1024
                worker.updateBatchProgress.emit(len(drawings), 1)
1025

    
1026
                area = app_doc_data.getArea('Drawing')
1027
                if area is not None:
1028
                    # copy area image
1029
                    area.img = app_doc_data.imgSrc[round(area.y):round(area.y + area.height),
1030
                               round(area.x):round(area.x + area.width)]
1031

    
1032
                offset = (area.x, area.y) if area is not None else (0, 0)
1033

    
1034
                otherTextInfoList = None
1035
                titleBlockTextInfoList = None
1036
                textInfoList = []
1037
                if worker.isTextChecked:
1038
                    from TextInfo import TextInfo
1039

    
1040
                    if worker.text_time:
1041
                        text_time_temp = worker.text_time
1042
                    else:
1043
                        text_time_temp = None
1044
                    worker.text_time = timeit.default_timer()
1045

    
1046
                    textAreas, ocr_image = textDetector.detectTextAreas(area.img if area is not None else app_doc_data.imgSrc,
1047
                                                             offset, listWidget=listWidget, worker=worker)
1048

    
1049
                    # get text area by using symbol setting
1050
                    text_area_symbols = []
1051
                    for symbol in [symbol for symbol in searchedSymbolList if symbol.text_area]:
1052
                        symbol_angle = symbol.getRotatedAngle()
1053
                        for text_area in symbol.text_area:
1054
                            so = [symbol.getWidth(), symbol.getHeight()] if symbol_angle is 0 or symbol_angle is 180\
1055
                                            else [symbol.getHeight(), symbol.getWidth()]
1056
                            text_area_origin = Worker.getCalculatedOriginalPoint(None, str(text_area.center()[0]) + ',' + str(text_area.center()[1]),\
1057
                                                                      symbol_angle, None, None, so[0], so[1], symbol.getDetectFlip())
1058
                            x = text_area_origin[0] - round(text_area.width / 2) if symbol_angle is 0 or symbol_angle is 180\
1059
                                else text_area_origin[0] - round(text_area.height / 2)
1060
                            x = x + symbol.sp[0]
1061
                            y = text_area_origin[1] - round(text_area.height / 2) if symbol_angle is 0 or symbol_angle is 180\
1062
                                else text_area_origin[1] - round(text_area.width / 2)
1063
                            y = y + symbol.sp[1]
1064
                            w = text_area.width if symbol_angle is 0 or symbol_angle is 180 else text_area.height
1065
                            h = text_area.height if symbol_angle is 0 or symbol_angle is 180 else text_area.width
1066

    
1067
                            if symbol_angle is 180 or symbol_angle is 0:
1068
                                text_angle = 0
1069
                            else:
1070
                                text_angle = 90
1071
                            text_area_symbols.append(TextInfo('', x, y, w, h, text_angle))
1072

    
1073
                    for textArea_index in reversed(range(len(textAreas))):
1074
                        is_pop = False
1075
                        for text_area_index in reversed(range(len(text_area_symbols))):
1076
                            if text_area_symbols[text_area_index].contains(textAreas[textArea_index].center) or \
1077
                                    textAreas[textArea_index].contains(text_area_symbols[text_area_index].center):
1078
                                if True: ## select text area from symbol
1079
                                #if text_area_symbols[text_area_index].area > textAreas[textArea_index].area: ## select bigger one
1080
                                    textAreas.pop(textArea_index)
1081
                                    is_pop = True
1082
                                    break
1083
                                else:
1084
                                    text_area_symbols.pop(text_area_index)
1085
                                    break
1086
                            if is_pop:
1087
                                break
1088
                    textAreas.extend(text_area_symbols)
1089
                    # up to here
1090

    
1091
                    if maxProgressValue < 2 * len(textAreas):
1092
                        for _ in range(len(textAreas) - int(maxProgressValue * 0.5)):
1093
                            worker.updateProgress.emit(2 * len(textAreas), None)
1094
                        maxProgressValue = 2 * len(textAreas)
1095
                    else:
1096
                        maxProgressValue = int(maxProgressValue * 0.5) + len(textAreas)
1097

    
1098
                    worker.displayTitle.emit(worker.tr('Detecting texts...'))
1099
                    textDetector.recognizeText(area.img, offset, textAreas, searchedSymbolList, worker,
1100
                                               listWidget, maxProgressValue)
1101
                    textInfoList = textDetector.textInfoList.copy() if textDetector.textInfoList is not None else None
1102
                    otherTextInfoList = textDetector.otherTextInfoList.copy() if textDetector.otherTextInfoList is not None else None
1103
                    titleBlockTextInfoList = textDetector.titleBlockTextInfoList.copy() if textDetector.titleBlockTextInfoList is not None else None
1104

    
1105
                    app_doc_data.activeDrawing.width, app_doc_data.activeDrawing.height = \
1106
                        app_doc_data.imgSrc.shape[::-1]
1107

    
1108
                    app_doc_data.imgName = os.path.splitext(os.path.basename(mainRes))[0]
1109

    
1110
                    worker.text_time = timeit.default_timer() - worker.text_time if not text_time_temp else timeit.default_timer() - worker.text_time + text_time_temp
1111
                else:
1112
                    import math
1113
                    from TextInfo import TextInfo
1114

    
1115
                    """load texts"""
1116
                    worker.add_predata_to_scene.emit(app_doc_data.activeDrawing, worker.text_scene,  False, True, False, False, False)
1117
                    worker.cond.wait(worker.mutex)
1118
                    """up to here"""
1119
                    textItems = [item for item in worker.text_scene.items() if issubclass(type(item), QEngineeringTextItem)]
1120
                    app_doc_data.texts.extend(textItems)
1121
                    app_doc_data.allItems.extend(textItems)
1122

    
1123
                    lineNoTextItems = [item for item in textItems if type(item) is QEngineeringLineNoTextItem]
1124
                    for lineNoTextItem in lineNoTextItems:
1125
                        lineNoTextItem.set_property('Freeze', False)
1126
                        # lineNoTextItem.freeze_item.update_freeze(lineNoTextItem.prop('Freeze'))
1127
                        lineNoTextItem.explode()
1128

    
1129
                    for textItem in textItems:
1130
                        textInfoList.append(
1131
                            TextInfo(textItem.text(), textItem.loc[0], textItem.loc[1], textItem.size[0],
1132
                                     textItem.size[1], round(math.degrees(textItem.angle))))
1133

    
1134
                        #textItem.owner = None
1135
                        #worker.scene.removeItem(textItem)
1136

    
1137
                worker.updateBatchProgress.emit(len(drawings), 2)
1138

    
1139
                # check symbol validate
1140
                #valid_sym = []
1141
                for index in reversed(range(len(searchedTextSymList))):
1142
                    sym_xmin = searchedTextSymList[index].getSp()[0]
1143
                    sym_ymin = searchedTextSymList[index].getSp()[1]
1144
                    sym_xmax = searchedTextSymList[index].getSp()[0] + searchedTextSymList[index].getWidth()
1145
                    sym_ymax = searchedTextSymList[index].getSp()[1] + searchedTextSymList[index].getHeight()
1146
                    valid_count = searchedTextSymList[index].getHasInstrumentLabel()
1147
                    valid = 0
1148
                    for text_info in textInfoList:
1149
                        info_center = text_info.center
1150
                        if sym_xmin < info_center[0] < sym_xmax and sym_ymin < info_center[1] < sym_ymax:
1151
                            valid += 1
1152
                            break
1153
                            #if valid >= valid_count:
1154
                            #    valid_sym.append(searchedTextSymList[index])
1155
                            #    break
1156
                    if valid < valid_count:
1157
                        searchedSymbolList.pop(searchedSymbolList.index(searchedTextSymList[index]))
1158

    
1159
                # roll back because invalidated symbol was deleted for text detection
1160
                app_doc_data.imgSrc = app_doc_data.activeDrawing.image_origin
1161

    
1162
                for sym in searchedSymbolList:
1163
                        Worker.remove_detected_symbol_image(sym, app_doc_data.imgSrc)
1164
                #pool = futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER)
1165
                #for sym in valid_sym:
1166
                #    pool.submit(Worker.remove_detected_symbol_image, sym, app_doc_data.imgSrc)
1167
                #pool.shutdown(wait=True)
1168
                # up to here
1169

    
1170
                # remove text from image
1171
                textDetector.remove_text_from_image(app_doc_data.imgSrc, [0, 0])
1172
                #textDetector.remove_text_from_image(area.img, offset)
1173
                if not worker.isTextChecked:
1174
                    textInfoList.clear()
1175
                # up to here
1176

    
1177
                #removedSymbolImgPath = os.path.join(project.getTempPath(), os.path.basename(mainRes))
1178
                #cv2.imwrite(removedSymbolImgPath, app_doc_data.imgSrc) for debug
1179

    
1180
                listWidget.addItem("Recognized symbol count : " + str(len(searchedSymbolList)))
1181

    
1182
                listWidget.addItem(worker.tr('Creating detected infos...'))
1183
                worker.displayTitle.emit(worker.tr('Creating detected infos...'))
1184
                worker.create_detected_items(searchedSymbolList, textInfoList,
1185
                                    otherTextInfoList if otherTextInfoList is not None else [],
1186
                                    titleBlockTextInfoList if titleBlockTextInfoList is not None else [])
1187

    
1188
                if isLineChecked:
1189
                    Worker.recognizeLine(mainRes, listWidget, worker.scene, worker)
1190
                else:
1191
                    """load lines"""
1192
                    worker.add_predata_to_scene.emit(app_doc_data.activeDrawing, worker.line_scene,  False, False, True, False, False)
1193
                    worker.cond.wait(worker.mutex)
1194
                    """up to here"""
1195
                    lineItems = [item for item in worker.line_scene.items()
1196
                                 if issubclass(type(item), QEngineeringLineItem)]
1197
                    app_doc_data.lines.extend(lineItems)
1198
                    app_doc_data.allItems.extend(lineItems)
1199

    
1200
                    for lineItem in lineItems:
1201
                        lineItem.owner = None
1202
                        for conn in lineItem.connectors:
1203
                            conn.connectedItem = None
1204
                        #worker.scene.removeItem(lineItem)
1205

    
1206
                '''
1207
                # try to detect nozzle from image drawing
1208
                if worker.isSymbolChecked:
1209
                    worker.displayTitle.emit(worker.tr('Detecting nozzle and flange...'))
1210

1211
                    nozzles = []
1212
                    with futures.ThreadPoolExecutor(max_workers=THREAD_MAX_WORKER) as pool:
1213
                        future_nozzle = {pool.submit(Worker.detect_nozzles, mainRes, symbol, listWidget, worker):
1214
                                           symbol for symbol in targetSymbolList[1]}
1215
                        for future in futures.as_completed(future_nozzle):
1216
                            data = future.result()
1217
                            if data:
1218
                                nozzles.extend(data)
1219

1220
                    worker.create_detected_items(nozzles, [], [], [])
1221

1222
                    for nozzle in nozzles:
1223
                        Worker.remove_detected_symbol_image(nozzle, app_doc_data.imgSrc)
1224
                # up to here
1225
                '''
1226

    
1227
                area = app_doc_data.getArea('Drawing')
1228
                if area is not None:
1229
                    area.img = app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height),
1230
                               round(area.x + 1):round(area.x + area.width)]
1231
                #cv2.imwrite(os.path.join(project.getTempPath(), "RECT_" + os.path.basename(mainRes)),
1232
                #            app_doc_data.imgSrc) for debug
1233

    
1234
                Worker.drawFoundSymbolsOnCanvas(mainRes, searchedSymbolList, textInfoList, listWidget)
1235

    
1236
                # get difference between original and recognized image
1237
                foundFilePath = os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(mainRes))
1238
                Worker.getDifference(mainRes, foundFilePath)
1239
                # up to here
1240

    
1241
                # connect symbol to symbol
1242
                try:
1243
                    configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line')
1244
                    toler = int(configs[0].value) if configs else 20
1245
                    for symbol in app_doc_data.symbols:
1246
                        matches = [it for it in app_doc_data.symbols if
1247
                                   it is not symbol and symbol.is_connectable(it, toler=toler)]
1248
                        for match in matches:
1249
                            symbol.connect_if_possible(match, toler=int(toler / 5 * 3))
1250
                except Exception as ex:
1251
                    message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1252
                                                                   sys.exc_info()[-1].tb_lineno)
1253
                    worker.displayLog.emit(MessageType.Error, message)
1254
                # up to here
1255

    
1256
                listWidget.addItem('Connecting lines')
1257
                #area = app_doc_data.getArea('Drawing')
1258
                detector = LineDetector(app_doc_data.imgSrc)
1259
                symbols = app_doc_data.symbols
1260
                configs = app_doc_data.getConfigs('Line Detector', 'Length to connect line')
1261
                toler = int(configs[0].value) if configs else 20
1262
                
1263
                # detect flange
1264
                flange_list = []
1265
                blind_list = []
1266
                
1267
                print('flag3.9')
1268
                configs = app_doc_data.getConfigs('Project', 'Operation')
1269
                instrument = int(configs[0].value) if configs else 1
1270
                if app_doc_data.lines:
1271
                    # connect line to symbol
1272
                    try:
1273
                        for line in app_doc_data.lines:
1274
                            matches = [_symbol for _symbol in symbols if _symbol.is_connectable(line, toler=toler)]
1275
                            for _symbol in matches:
1276
                                detector.connectLineToSymbol(line, _symbol, toler=toler)
1277
                    except Exception as ex:
1278
                        message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[
1279
                            -1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno)
1280
                        worker.displayLog.emit(MessageType.Error, message)
1281
                    # up to here
1282
                    print('flag3.98')
1283
                    # connect line to line
1284
                    try:
1285
                        for line in app_doc_data.lines:
1286
                            matches = [it for it in app_doc_data.lines if
1287
                                       (it is not line) and (not line.isParallel(it))]
1288

    
1289
                            for match in matches:
1290
                                detector.connectLineToLine(match, line, toler)
1291
                        print('flag3.989')
1292
                        # change line type using symbol connection type(info)
1293
                        for sym in symbols:
1294
                            if sym.conn_type:
1295
                                for index in range(len(sym.conn_type)):
1296
                                    item = sym.connectors[index].connectedItem
1297
                                    if item and type(item) is QEngineeringLineItem and sym.conn_type[index] != 'Secondary' and sym.conn_type[index] != 'Primary':
1298
                                        Worker.changeConnectedLineType(item, sym.conn_type[index])
1299

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

    
1307
                    # make short line that can not detected, symbol to symbol
1308
                    try:
1309
                        conns = []
1310
                        for sym in symbols:
1311
                            if sym.conn_type:
1312
                                for index in range(len(sym.conn_type)):
1313
                                    if sym.conn_type[index] == 'Secondary' or sym.conn_type[index] == 'Primary':
1314
                                        item = sym.connectors[index].connectedItem
1315
                                        if item is None:
1316
                                            conns.append(sym.connectors[index])
1317
                        
1318
                        Worker.make_short_lines_sts(conns, worker)
1319

    
1320
                    except Exception as ex:
1321
                        message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1322
                                  f"{sys.exc_info()[-1].tb_lineno}"
1323
                        worker.displayLog.emit(MessageType.Error, message)
1324

    
1325
                    # detect flange
1326
                    try:
1327
                        not_conn = []
1328
                        if instrument == 1:
1329
                            flange = None
1330
                            configs = app_doc_data.getConfigs('Default', 'Flange')
1331
                            flange_name = configs[0].value if 1 == len(configs) else 'flange'
1332
                            flange = QtImageViewer.createSymbolObject(flange_name)
1333

    
1334
                            blind = None
1335
                            configs = app_doc_data.getConfigs('Default', 'Blind')
1336
                            flange_name = configs[0].value if 1 == len(configs) else 'blind flange'
1337
                            blind = QtImageViewer.createSymbolObject(flange_name)
1338

    
1339
                            for sym in symbols:
1340
                                if sym.conn_type:
1341
                                    for index in range(len(sym.conn_type)):
1342
                                        item = sym.connectors[index].connectedItem
1343
                                        if flange and item and type(item) is QEngineeringLineItem and (sym.conn_type[index] == 'Secondary' or sym.conn_type[index] == 'Primary') \
1344
                                            and item.is_piping(True):
1345
                                            point = worker.detectFlangeOnPid(sym, sym.connectors[index], item, app_doc_data.activeDrawing.image_origin)
1346
                                            if point is not None:
1347
                                                flange_list.append(point)
1348

    
1349
                                        elif blind and not item and (sym.conn_type[index] == 'Secondary' or sym.conn_type[index] == 'Primary'):
1350
                                            point = worker.detectFlangeOnPid(sym, sym.connectors[index], None, app_doc_data.activeDrawing.image_origin)
1351
                                            if point is not None:
1352
                                                blind_list.append(point)
1353
                                                not_conn.append(sym.connectors[index])
1354

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

    
1361
                    # make short line that can not detected, symbol to line
1362
                    try:
1363
                        conns = []
1364
                        for sym in symbols:
1365
                            if sym.conn_type:
1366
                                for index in range(len(sym.conn_type)):
1367
                                    if sym.conn_type[index] == 'Secondary' or sym.conn_type[index] == 'Primary':
1368
                                        item = sym.connectors[index].connectedItem
1369
                                        if item is None and sym.connectors[index] not in not_conn:
1370
                                            conns.append(sym.connectors[index])
1371
                        
1372
                        Worker.make_short_lines_stl(app_doc_data.lines, conns, worker)
1373

    
1374
                    except Exception as ex:
1375
                        message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1376
                                  f"{sys.exc_info()[-1].tb_lineno}"
1377
                        worker.displayLog.emit(MessageType.Error, message)
1378

    
1379
                    # remove line has not connected item
1380
                    try:
1381
                        count = 1
1382
                        while count > 0:
1383
                            count = 0
1384
                            for line in reversed(app_doc_data.lines):
1385
                                if not line.has_connection:
1386
                                    app_doc_data.lines.remove(line)
1387
                                    app_doc_data.allItems.remove(line)
1388
                                    count += 1
1389
                                    for item in app_doc_data.lines + symbols:
1390
                                        for conn in item.connectors:
1391
                                            if conn.connectedItem is line:
1392
                                                conn.connectedItem = None
1393
                    except Exception as ex:
1394
                        message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1395
                                  f"{sys.exc_info()[-1].tb_lineno}"
1396
                        worker.displayLog.emit(MessageType.Error, message)
1397
                    # up to here
1398

    
1399
                    # change line type using visual pattern
1400
                    try:
1401
                        lines = []
1402
                        for sym in symbols:
1403
                            if sym.conn_type:
1404
                                for index in range(len(sym.conn_type)):
1405
                                    if sym.conn_type[index] != 'Secondary' and sym.conn_type[index] != 'Primary':
1406
                                        item = sym.connectors[index].connectedItem
1407
                                        if item and type(item) is QEngineeringLineItem:
1408
                                            if item in lines:
1409
                                                continue
1410
                                            else:
1411
                                                run = [item]
1412
                                                Worker.find_connected_line(run, item)
1413
                                                lines.extend(run)
1414
                        
1415
                        Worker.changeVisualLineType(lines, worker)
1416

    
1417
                    except Exception as ex:
1418
                        message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1419
                                  f"{sys.exc_info()[-1].tb_lineno}"
1420
                        worker.displayLog.emit(MessageType.Error, message)
1421
                    # up to here
1422
                print('flag4')
1423
                worker.create_unknown_items(mainRes)
1424
                worker.add_detected_items_to_scene.emit(worker.scene, [flange_list, blind_list])
1425
                worker.cond.wait(worker.mutex)
1426

    
1427
                # run preset
1428
                presets = app_doc_data.getSymbolPreset()
1429
                for preset in presets:
1430
                    worker.preset_execute.emit(worker.scene, preset[0], preset[1], preset[2], preset[3])
1431
                    worker.cond.wait(worker.mutex)
1432

    
1433
                # remove piping line and text for instrument mode
1434
                try:
1435
                    if instrument == -1:
1436
                        # remove piping line
1437
                        remove_items = []
1438

    
1439
                        for line in reversed(app_doc_data.lines):
1440
                            if line.is_piping(True):
1441
                                app_doc_data.lines.remove(line)
1442
                                app_doc_data.allItems.remove(line)
1443
                                remove_items.append(line)
1444
                                '''
1445
                                for item in app_doc_data.lines + app_doc_data.symbols:
1446
                                    for conn in item.connectors:
1447
                                        if conn.connectedItem is line:
1448
                                            conn.connectedItem = None
1449
                                '''
1450

    
1451
                        # remove text
1452
                        for text in reversed([text for text in app_doc_data.texts if type(text) is QEngineeringTextItem and text.area == 'Drawing']):
1453
                            contained = False
1454
                            for symbol in app_doc_data.symbols:
1455
                                if symbol.includes([text.sceneBoundingRect().center().x(), text.sceneBoundingRect().center().y()]):
1456
                                    contained = True
1457
                                    break
1458
                            if not contained:
1459
                                remove_items.append(text)
1460

    
1461
                        ## load code tables
1462
                        app_doc_data.getReplaceTables()
1463
                        app_doc_data.getCustomTables()
1464
                        app_doc_data.loadSymbolAttributeCodeTables()
1465
                        for index in reversed(range(len(remove_items))):
1466
                            if issubclass(type(remove_items[index]), QEngineeringTextItem):
1467
                                found = False
1468
                                for table in CodeTable.TABLES.values():
1469
                                    if table.find_match_exactly(remove_items[index].text()):
1470
                                        found = True
1471
                                        break
1472
                                if found:
1473
                                    remove_items.pop(index)
1474

    
1475
                        worker.item_remove.emit(remove_items)
1476
                        worker.cond.wait(worker.mutex)
1477
                        # up to here
1478

    
1479
                except Exception as ex:
1480
                    message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1481
                                  f"{sys.exc_info()[-1].tb_lineno}"
1482
                    worker.displayLog.emit(MessageType.Error, message)
1483
                # up to here
1484

    
1485
                print('flag5')
1486
                worker.save_scene.emit(worker.scene)
1487
                worker.cond.wait(worker.mutex)
1488

    
1489
                worker.scene._end = False
1490
                # clear drawing
1491
                app_doc_data.activeDrawing.image = None
1492

    
1493
                worker.updateBatchProgress.emit(len(drawings), 1)
1494
        except Exception as ex:
1495
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1496
                      f"{sys.exc_info()[-1].tb_lineno}"
1497
            worker.displayLog.emit(MessageType.Error, message)
1498
        finally:
1499
            pass
1500

    
1501
    @staticmethod
1502
    def make_short_lines_sts(connectors, worker=None):
1503
        """ make short lines logically, symbol to symbol """
1504

    
1505
        import math
1506
        from EngineeringAbstractItem import QEngineeringAbstractItem
1507

    
1508
        try:
1509
            app_doc_data = AppDocData.instance()
1510
            lineLengthConfigs = app_doc_data.getConfigs('Small Line Minimum Length', 'Min Length')
1511
            lineMinLength = int(lineLengthConfigs[0].value) if 1 == len(lineLengthConfigs) else 25
1512
            lineMaxLength = lineMinLength * 3
1513
            windowSize = app_doc_data.getSlidingWindowSize()
1514
            thickness = int(windowSize[1] / 2)
1515

    
1516
            new_lines = []
1517

    
1518
            # symbol to symbol
1519
            connectors = [conn for conn in connectors if conn.connectedItem is None]
1520
            for connector1 in connectors:
1521
                if connector1.connectedItem:
1522
                    continue
1523

    
1524
                selected = None
1525
                min_dist = sys.maxsize
1526

    
1527
                for connector2 in connectors:
1528
                    if connector2.connectedItem or connector1 is connector2 or connector1.parentItem() is connector2.parentItem():
1529
                        continue
1530

    
1531
                    dx = connector1.center()[0] - connector2.center()[0]
1532
                    dy = connector1.center()[1] - connector2.center()[1]
1533
                    dist = math.sqrt(dx * dx + dy * dy)
1534
                    if dist < lineMaxLength and dist < min_dist and min(abs(dx), abs(dy)) < thickness:
1535
                        selected = connector2
1536
                        min_dist = dist
1537

    
1538
                if selected:
1539
                    new_line = QEngineeringLineItem(vertices=[connector1.center(), selected.center()], thickness=thickness)
1540
                    new_line.area = 'Drawing'
1541

    
1542
                    connector1.connect(new_line)
1543
                    selected.connect(new_line)
1544
                    new_line.connectors[0].connect(connector1.parentItem())
1545
                    new_line.connectors[1].connect(selected.parentItem())
1546
                    new_lines.append(new_line)
1547

    
1548
            app_doc_data.lines.extend(new_lines)
1549
            app_doc_data.allItems.extend(new_lines)
1550

    
1551
            return new_lines
1552

    
1553
        except Exception as ex:
1554
            if worker:
1555
                message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1556
                                                               sys.exc_info()[-1].tb_lineno)
1557
                worker.displayLog.emit(MessageType.Error, message)
1558
            else:
1559
                from App import App
1560
                from AppDocData import MessageType
1561

    
1562
                message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1563
                          f"{sys.exc_info()[-1].tb_lineno}"
1564
                QMessageBox.warning(self, self._tr('Warning'), message)
1565

    
1566
    @staticmethod
1567
    def make_short_lines_stl(lines, connectors, worker=None):
1568
        """ make short lines logically, symbol to line """
1569

    
1570
        import math
1571
        from EngineeringAbstractItem import QEngineeringAbstractItem
1572

    
1573
        try:
1574
            app_doc_data = AppDocData.instance()
1575
            lineLengthConfigs = app_doc_data.getConfigs('Small Line Minimum Length', 'Min Length')
1576
            lineMinLength = int(lineLengthConfigs[0].value) if 1 == len(lineLengthConfigs) else 25
1577
            lineMaxLength = lineMinLength * 3
1578
            windowSize = app_doc_data.getSlidingWindowSize()
1579
            thickness = int(windowSize[1] / 2)
1580

    
1581
            new_lines = []
1582

    
1583
            verticals = []
1584
            horizontals = []
1585

    
1586
            for line in lines:
1587
                if line.isVertical():
1588
                    verticals.append(line)
1589
                else:
1590
                    horizontals.append(line)
1591

    
1592
            # symbol to line
1593
            for connector in connectors:
1594
                direction = connector.dir()
1595
                symbol = connector.parentItem()
1596
                if direction is None:
1597
                    dx = connector.center()[0] - symbol.origin[0]
1598
                    dy = connector.center()[1] - symbol.origin[1]
1599
                    length = math.sqrt(dx * dx + dy * dy)
1600
                    if length <= 0:
1601
                        continue
1602
                    dx /= length
1603
                    dy /= length
1604
                else:
1605
                    dx, dy = direction.x(), direction.y()
1606

    
1607
                _lines = None
1608
                if abs(dx) < 0.1:
1609
                    _lines =  horizontals
1610
                elif abs(dy) < 0.1:
1611
                    _lines = verticals
1612
                
1613
                if not _lines:
1614
                    continue
1615

    
1616
                selected = None
1617
                min_dist = sys.maxsize
1618
                for line in _lines:
1619
                    dist = line.distanceTo(connector.center())
1620
                    if dist < lineMaxLength and dist < min_dist:
1621
                        selected = line
1622
                        min_dist = dist
1623

    
1624
                if selected:
1625
                    if selected in verticals:
1626
                        min_x_y = min(selected.start_point()[1], selected.end_point()[1])
1627
                        max_x_y = max(selected.start_point()[1], selected.end_point()[1])
1628
                        con_x_y = connector.center()[1]
1629
                        start_point = [selected.start_point()[0], connector.center()[1]]
1630
                    else:
1631
                        min_x_y = min(selected.start_point()[0], selected.end_point()[0])
1632
                        max_x_y = max(selected.start_point()[0], selected.end_point()[0])
1633
                        con_x_y = connector.center()[0]
1634
                        start_point = [connector.center()[0], selected.start_point()[1]]
1635

    
1636
                    end_point = connector.center()
1637

    
1638
                    if min_x_y < con_x_y and con_x_y < max_x_y:
1639
                        new_line = QEngineeringLineItem(vertices=[start_point, end_point], thickness=thickness)
1640
                        new_line.area = 'Drawing'
1641

    
1642
                        connector.connect(new_line)
1643
                        new_line.connectors[0].connect(selected, at=QEngineeringAbstractItem.CONNECTED_AT_BODY)
1644
                        new_line.connectors[1].connect(symbol)
1645
                        new_lines.append(new_line)
1646

    
1647
            app_doc_data.lines.extend(new_lines)
1648
            app_doc_data.allItems.extend(new_lines)
1649

    
1650
            return new_lines
1651

    
1652
        except Exception as ex:
1653
            if worker:
1654
                message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1655
                                                            sys.exc_info()[-1].tb_lineno)
1656
                worker.displayLog.emit(MessageType.Error, message)
1657
            else:
1658
                from App import App
1659
                from AppDocData import MessageType
1660

    
1661
                message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
1662
                          f"{sys.exc_info()[-1].tb_lineno}"
1663
                QMessageBox.warning(self, self._tr('Warning'), message)
1664

    
1665
    @staticmethod
1666
    def changeVisualLineType(all_lines, worker):
1667
        """ change line type by using visual data"""
1668

    
1669
        from LineDetector import LineDetector
1670
        from EngineeringAbstractItem import QEngineeringAbstractItem
1671

    
1672
        try:
1673
            app_doc_data = AppDocData.instance()
1674
            image = app_doc_data.activeDrawing.image_origin
1675
            imgNot = np.ones(image.shape, np.uint8) * 255
1676
            image = cv2.bitwise_xor(image, imgNot)
1677

    
1678
            #electric = [137, [1,1,1,1,1,1,1,1,1], sys.maxsize]
1679
            #software = [187, [0.948,1.081,0.932,1.081,0.932,1.068,0.932,1.081,0.929], sys.maxsize]
1680
            #line_patterns = {'Electric':electric, 'Software':software }
1681
            line_patterns = []
1682
            line_shapes = []
1683

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

    
1687
            line_names = app_doc_data.getSymbolListByType('type', 'Line')
1688
            if len(line_names) != 0:
1689
                max_avg_area = 0
1690
                for line_name in line_names:
1691
                    line = line_name
1692
                    line_path = line.getPath()
1693
                    if not os.path.isfile(line_path):
1694
                        continue
1695

    
1696
                    line_img = cv2.cvtColor(cv2.imread(line_path), cv2.COLOR_BGR2GRAY)
1697
                    imgNot = np.ones(line_img.shape, np.uint8) * 255
1698
                    line_img = cv2.bitwise_xor(line_img, imgNot)
1699

    
1700
                    contours, _ = cv2.findContours(line_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
1701

    
1702
                    line_thickness = 0
1703

    
1704
                    if len(contours) < 3:
1705
                        max_x, max_y, min_x, min_y = 0, 0, sys.maxsize, sys.maxsize
1706
                        for rect in [cv2.boundingRect(contour) for contour in contours]:
1707
                            if rect[0] < min_x:
1708
                                min_x = rect[0]
1709
                            if rect[0] + rect[2] > max_x:
1710
                                max_x = rect[0] + rect[2]
1711
                            if rect[1] < min_y:
1712
                                min_y = rect[1]
1713
                            if rect[1] + rect[3] > max_y:
1714
                                max_y = rect[1] + rect[3]
1715

    
1716
                        thickness = min(max_y - min_y, max_x - min_x)
1717
                        if line_thickness < thickness:
1718
                            line_thickness = thickness
1719

    
1720
                        line_shapes.append([line_img[min_y:max_y, min_x:max_x], sys.maxsize, line.getThreshold(), line.getBaseSymbol()])
1721
                        continue
1722

    
1723
                    i = 1 if line_img.shape[0] > line_img.shape[1] else 0
1724
                    boundingBoxes = [cv2.boundingRect(contour) for contour in contours]
1725
                    (contours, boundingBoxes) = zip(*sorted(zip(contours, boundingBoxes), key=lambda b:b[1][i], reverse=False))
1726

    
1727
                    avg_area = sum([cv2.contourArea(contour) for contour in contours]) / len(contours)
1728
                    ratio_area = [cv2.contourArea(contour) / avg_area for contour in contours]
1729
                    if avg_area > max_avg_area:
1730
                        max_avg_area = avg_area
1731

    
1732
                    line_patterns.append([avg_area, ratio_area, sys.maxsize, line.getThreshold(), line.getBaseSymbol()])
1733
            else:
1734
                return
1735

    
1736
            lines_found = []
1737
            lines_shape = []
1738

    
1739
            # detemine line type : broken
1740
            for line in lines:
1741
                rect = line.boundingRect()
1742
                image_line = image[round(rect.y() - 1):round(rect.y() + rect.height() + 1), round(rect.x() - 1):round(rect.x() + rect.width() + 1)]
1743
                contours, _ = cv2.findContours(image_line, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
1744

    
1745
                # skip piping line
1746
                if len(contours) < 3 or (sum([cv2.contourArea(contour) for contour in contours]) / len(contours)) > max_avg_area * 2:
1747
                    if rect.width() > rect.height():
1748
                        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)]
1749
                    else:
1750
                        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)]
1751

    
1752
                    contours, _ = cv2.findContours(image_line, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
1753
                    max_x, max_y, min_x, min_y = 0, 0, sys.maxsize, sys.maxsize
1754
                    for rect in [cv2.boundingRect(contour) for contour in contours]:
1755
                        if rect[0] < min_x:
1756
                            min_x = rect[0]
1757
                        if rect[0] + rect[2] > max_x:
1758
                            max_x = rect[0] + rect[2]
1759
                        if rect[1] < min_y:
1760
                            min_y = rect[1]
1761
                        if rect[1] + rect[3] > max_y:
1762
                            max_y = rect[1] + rect[3]
1763
                    if max_y == 0 or max_x == 0:
1764
                        continue
1765
                    
1766
                    lines_shape.append([line, image_line[min_y:max_y, min_x:max_x]])
1767
                    continue
1768

    
1769
                vertical = LineDetector.is_vertical([line.start_point()[0], line.start_point()[1], line.end_point()[0], line.end_point()[1]])
1770

    
1771
                i = 1 if vertical else 0
1772
                boundingBoxes = [cv2.boundingRect(contour) for contour in contours]
1773
                (contours, boundingBoxes) = zip(*sorted(zip(contours, boundingBoxes), key=lambda b:b[1][i], reverse=False))
1774

    
1775
                avg_area = sum([cv2.contourArea(contour) for contour in contours]) / len(contours)
1776
                ratio_area = [cv2.contourArea(contour) / avg_area for contour in contours]
1777

    
1778
                for line_pattern in line_patterns:
1779
                    line_type = line_pattern[-1]
1780

    
1781
                    line_pattern[2] = sys.maxsize
1782
                    ratio_area_cal = [ratio * avg_area / line_pattern[0] for ratio in ratio_area]
1783
                    long_line = ratio_area_cal if len(ratio_area_cal) > len(line_pattern[1]) else line_pattern[1]
1784
                    short_line = line_pattern[1] if len(ratio_area_cal) > len(line_pattern[1]) else ratio_area_cal
1785

    
1786
                    min_error = sys.maxsize
1787
                    for offset in range(len(long_line) - len(short_line) + 1):
1788
                        error = 0
1789
                        for index in range(len(short_line)):
1790
                            error += abs(short_line[index] - long_line[index + offset])
1791
                        error = error / len(short_line)
1792
                        if error < min_error:
1793
                            min_error = error
1794

    
1795
                    line_pattern[2] = min_error
1796

    
1797
                line_type_founds = sorted([(line_pattern[-1], line_pattern[2]) for line_pattern in line_patterns], key=lambda error:error[1])
1798
                if line_type_founds:
1799
                    lines_found.append([line, line_type_founds[0]])
1800

    
1801
                '''
1802
                # feature matching not work
1803
                orb = cv2.ORB_create()
1804
                kp1, des1 = orb.detectAndCompute(image_line, None)
1805
                kp2, des2 = orb.detectAndCompute(image_line, None)
1806
    
1807
                bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
1808
                matches = bf.match(des1, des2)
1809
                matches = sorted(matches, key=lambda x: x.distance)
1810
    
1811
                good = []
1812
                for m, n in matches:
1813
                    if m.distance < 0.75 * n.distance:
1814
                        good.append([m])
1815
    
1816
                sift = cv2.xfeatures2d.SIFT_create()
1817
                kp1, des1 = sift.detectAndCompute(image_line, None)
1818
                kp2, des2 = sift.detectAndCompute(image_line, None)
1819
    
1820
                bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)
1821
                matches = bf.match(des1, des2)
1822
                matches = sorted(matches, key=lambda x: x.distance)
1823
    
1824
                good = []
1825
                for m, n in matches:
1826
                    if m.distance < 0.75 * n.distance:
1827
                        good.append([m])
1828
                '''
1829
            # determine line type : solid
1830
            for line, image_line in lines_shape:
1831
                for line_shape in line_shapes:
1832
                    line_type = line_shape[-1]
1833

    
1834
                    shape = line_shape[0].copy()
1835
                    line_shape[1] = sys.maxsize
1836

    
1837
                    big = shape if max(shape.shape) > max(image_line.shape) else image_line
1838
                    small = image_line if max(shape.shape) > max(image_line.shape) else shape
1839

    
1840
                    if big.shape[0] == max(max(big.shape), max(small.shape)):
1841
                        mask = np.zeros([big.shape[0] + 40, max(big.shape[1], max(small.shape)) + 40], np.uint8)
1842
                    else:
1843
                        mask = np.zeros([max(big.shape[0], max(small.shape)) + 40, big.shape[1] + 40], np.uint8)
1844

    
1845
                    mask[20:big.shape[0] + 20, 20:big.shape[1] + 20] = big
1846
                    big = mask
1847

    
1848
                    searchedInfos = []
1849
                    steps = [False, True]
1850
                    for flipped in steps:
1851
                        symGray = small.copy()
1852
                        if flipped:
1853
                            symGray = cv2.flip(symGray, 1)
1854

    
1855
                        symbolRotatedAngle = 0
1856
                        for rc in range(4):
1857
                            sw, sh = symGray.shape[::-1]
1858

    
1859
                            r_w, r_h = big.shape[::-1]
1860
                            if r_w < sw or r_h < sh:
1861
                                symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
1862
                                symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
1863
                                continue
1864

    
1865
                            tmRes = cv2.matchTemplate(big, symGray, cv2.TM_CCOEFF_NORMED)
1866
                            _, max_val, __, max_loc = cv2.minMaxLoc(tmRes)
1867

    
1868
                            if max_val > line_shape[-2]:
1869
                                searchedInfos.append(1 - max_val)
1870

    
1871
                            symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
1872
                            symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
1873

    
1874
                    if searchedInfos:
1875
                        line_shape[1] = sorted(searchedInfos)[0]
1876

    
1877
                line_type_founds = sorted([(line_shape[-1], (1 - line_shape[-2] - line_shape[1]) * 2) for line_shape in line_shapes \
1878
                                            if line_shape[1] < (1 - line_shape[-2])], key=lambda error: error[1], reverse=True)
1879
                if line_type_founds:
1880
                    lines_found.append([line, line_type_founds[0]])
1881
                '''
1882
                if line_type_founds and line_type_founds[0][1] < 0.4:
1883
                    if line_type_founds[0][0] == 'Connect To Process' and len(line_type_founds) > 1:
1884
                        if line_type_founds[1][1] < 0.4:
1885
                            lines_found.append([line, line_type_founds[1]])
1886
                        else:
1887
                            lines_found.append([line, line_type_founds[0]])
1888
                    else:
1889
                        lines_found.append([line, line_type_founds[0]])
1890
                '''
1891

    
1892
            line_runs = []
1893
            for line_found in lines_found:
1894
                inserted = False
1895
                for line_run in line_runs:
1896
                    if line_found[0] in line_run:
1897
                        inserted = True
1898
                        break
1899

    
1900
                if inserted:
1901
                    continue
1902
                else:
1903
                    run = [line_found[0]]
1904
                    Worker.find_connected_line(run, line_found[0])
1905
                    line_runs.append(run)
1906

    
1907
            for line_run in line_runs:
1908
                _lines_found = []
1909
                for _line in line_run:
1910
                    _lines = [line_found[0] for line_found in lines_found]
1911
                    if _line in _lines:
1912
                        index = _lines.index(_line)
1913
                        _lines_found.append(lines_found[index])
1914
                _line_found = sorted(_lines_found, key=lambda param:param[1][1])[0]
1915
                Worker.changeConnectedLineType(_line_found[0], _line_found[1][0], visual_marker=True)
1916

    
1917
            # change line type: connected at body
1918
            remains = [line for line in app_doc_data.lines if not hasattr(line, 'visual_marker')]
1919
            while True:
1920
                remains_count = len(remains)
1921

    
1922
                for _line in remains:
1923
                    matches = [conn for conn in _line.connectors if conn.connectedItem and \
1924
                                    conn._connected_at == QEngineeringAbstractItem.CONNECTED_AT_BODY and hasattr(conn.connectedItem, 'visual_marker')]
1925
                    if matches:
1926
                        Worker.changeConnectedLineType(_line, matches[0].connectedItem.lineType, visual_marker=True)
1927
                        break
1928
                    
1929
                remains = [line for line in app_doc_data.lines if not hasattr(line, 'visual_marker')]
1930
                if remains_count == len(remains):
1931
                    break
1932

    
1933
        except Exception as ex:
1934
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1935
                                                           sys.exc_info()[-1].tb_lineno)
1936
            worker.displayLog.emit(MessageType.Error, message)
1937

    
1938
    @staticmethod
1939
    def find_connected_line(lines, line):
1940
        if type(line.connectors[0].connectedItem) is QEngineeringLineItem and line.connectors[0].connectedItem not in lines and\
1941
            (line.connectors[0].connectedItem.connectors[0].connectedItem is line or
1942
                line.connectors[0].connectedItem.connectors[1].connectedItem is line):
1943
            current_line = line.connectors[0].connectedItem
1944
            lines.append(current_line)
1945
            Worker.find_connected_line(lines, current_line)
1946

    
1947
        if type(line.connectors[1].connectedItem) is QEngineeringLineItem and line.connectors[1].connectedItem not in lines and\
1948
            (line.connectors[1].connectedItem.connectors[0].connectedItem is line or
1949
                line.connectors[1].connectedItem.connectors[1].connectedItem is line):
1950
            current_line = line.connectors[1].connectedItem
1951
            lines.append(current_line)
1952
            Worker.find_connected_line(lines, current_line)
1953

    
1954
    @staticmethod
1955
    def changeConnectedLineType(line, lineType, visual_marker=False):
1956
        # for visual line type detection
1957
        if visual_marker:
1958
            line.visual_marker = visual_marker
1959

    
1960
        line.lineType = lineType
1961
        if type(line.connectors[0].connectedItem) is QEngineeringLineItem and \
1962
                (line.connectors[0].connectedItem.connectors[0].connectedItem is line or
1963
                 line.connectors[0].connectedItem.connectors[1].connectedItem is line) and \
1964
                line.connectors[0].connectedItem.lineType is not lineType:
1965
            Worker.changeConnectedLineType(line.connectors[0].connectedItem, lineType, visual_marker=visual_marker)
1966

    
1967
        if type(line.connectors[1].connectedItem) is QEngineeringLineItem and \
1968
                (line.connectors[1].connectedItem.connectors[0].connectedItem is line or
1969
                 line.connectors[1].connectedItem.connectors[1].connectedItem is line) and \
1970
                line.connectors[1].connectedItem.lineType is not lineType:
1971
            Worker.changeConnectedLineType(line.connectors[1].connectedItem, lineType, visual_marker=visual_marker)
1972

    
1973
    '''
1974
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
1975
                    2018.05.28  Jeongwoo    Add xmlPath Parameter and append LineInfo into xml
1976
                    2018.05.29  Jeongwoo    Change method to add item
1977
                    2018.05.30  Jeongwoo    Remove parameter (xmlPath)
1978
                    humkyung 2018.06.11 add drawing path to parameter and write recognized lines to image
1979
                    humkyung 2018.07.04 call arrangeLinePosition after creating line
1980
    '''
1981

    
1982
    @staticmethod
1983
    def recognizeLine(path, listWidget, graphicsView, worker):
1984
        from shapely.geometry import Point, LineString
1985
        from SymbolSvgItem import SymbolSvgItem
1986
        from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
1987
        from EngineeringLineNoTextItem import QEngineeringLineNoTextItem
1988
        from EngineeringTextItem import QEngineeringTextItem
1989
        from EngineeringLineItem import QEngineeringLineItem
1990
        from LineDetector import LineDetector
1991

    
1992
        try:
1993
            listWidget.addItem('Starting line recognition')
1994
            worker.displayTitle.emit(worker.tr('Detecting lines...'))
1995

    
1996
            app_doc_data = AppDocData.instance()
1997
            project = app_doc_data.getCurrentProject()
1998

    
1999
            # detect line
2000
            connectedLines = []
2001

    
2002
            area = app_doc_data.getArea('Drawing')
2003
            if area is not None:
2004
                area.img = app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height),
2005
                            round(area.x + 1):round(area.x + area.width)]
2006

    
2007
            Worker.remove_equipment_package(worker.scene, area)
2008

    
2009
            area.img = worker.remove_small_objects(area.img)
2010
            detector = LineDetector(area.img)
2011

    
2012
            symbols = []
2013
            for item in app_doc_data.symbols:
2014
                if issubclass(type(item), SymbolSvgItem):
2015
                    symbols.append(item)
2016
                    res = detector.detectConnectedLine(item, round(area.x), round(area.y))
2017
                    if res is not None:
2018
                        connectedLines.extend(res)
2019

    
2020
            # line detection without symbol connection point info
2021
            configs = app_doc_data.getConfigs('Line', 'Gap')
2022
            if configs and int(configs[0].value) is 1:
2023
                remainLines = detector.detect_line_without_symbol()
2024
                windowSize = app_doc_data.getSlidingWindowSize()
2025
                thickness = int(windowSize[1] / 2)
2026

    
2027
                for line in remainLines:
2028
                    line.append(thickness)
2029
                connectedLines.extend(remainLines)
2030

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

    
2034
            symbol_areas = []
2035
            for symbol in symbols:
2036
                symbol_areas.append(QRect(symbol.loc[0] - area.x, symbol.loc[1] - area.y, symbol.size[0], symbol.size[1]))
2037

    
2038
            detector.mergeLines(connectedLines, toler=toler, symbol_areas=symbol_areas)
2039

    
2040
            for pts in connectedLines:
2041
                line = QEngineeringLineItem(
2042
                    vertices=[(area.x + param[0], area.y + param[1]) for param in pts[:-1]], thickness=pts[2])
2043
                line.area = 'Drawing'
2044

    
2045
                app_doc_data.lines.append(line)
2046
                app_doc_data.allItems.append(line)
2047
        except Exception as ex:
2048
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
2049
                                                           sys.exc_info()[-1].tb_lineno)
2050
            worker.displayLog.emit(MessageType.Error, message)
2051
        finally:
2052
            listWidget.addItem('Finishing line recognition')
2053
            worker.finished.emit()
2054

    
2055
    '''
2056
        @history    2018.04.24  Jeongwoo    Add isExceptDetect Field
2057
                    2018.05.09  Jeongwoo    Add targetSymbolList clear
2058
                    humkyung 2018.07.07 store symbols to list as like [equipments],[nozzles],[symbols]
2059
    '''
2060

    
2061
    @staticmethod
2062
    def initTargetSymbolDataList(all=False):
2063
        global targetSymbolList
2064

    
2065
        targetSymbolList.clear()
2066
        app_doc_data = AppDocData.instance()
2067
        symbolList = app_doc_data.getTargetSymbolList(all=all)
2068
        equipments = [item for item in symbolList if app_doc_data.getSymbolCategoryByType(item.getType()) == 'Equipment']
2069
        nozzles = [item for item in symbolList if item.getType() == 'Nozzles']
2070
        # [[equipments],[nozzles],[symbols]]
2071
        targetSymbolList.append(equipments)
2072
        targetSymbolList.append(nozzles)
2073
        targetSymbolList.append([item for item in symbolList if item not in equipments and item not in nozzles])
2074

    
2075
        return targetSymbolList
2076

    
2077
    '''
2078
        @brief  detect equipment
2079
        @author humkyung
2080
        @date   2018.07.07
2081
    '''
2082

    
2083
    @staticmethod
2084
    def detect_nozzles(mainRes, targetSymbol, listWidget, worker):
2085
        res = []
2086
        try:
2087
            app_doc_data = AppDocData.instance()
2088

    
2089
            nozzles = Worker.detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker)
2090
            equipments = [symbol for symbol in searchedSymbolList if app_doc_data.isEquipmentType(symbol.getType())]
2091
            for equipment in equipments:
2092
                rect = QRectF(equipment.sp[0], equipment.sp[1], equipment.width, equipment.height)
2093
                rect.adjust(-equipment.width*0.1, -equipment.height*0.1, equipment.width*0.1, equipment.height*0.1)
2094
                matches = [nozzle for nozzle in nozzles if rect.contains(nozzle.rect)]
2095
                res.extend(matches)
2096
                for match in matches:
2097
                    nozzles.remove(match)
2098
        except Exception as ex:
2099
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
2100
                                                           sys.exc_info()[-1].tb_lineno)
2101
            worker.displayLog(MessageType.Error, message)
2102

    
2103
        return res
2104

    
2105
    '''
2106
        @brief      detect symbol on PID
2107
        @author     jwkim
2108
        @date   
2109
        @history    humkyung 2018.04.06 check if symbol file exists
2110
                    Jeongwoo 2018.05.29 Change method to adjust detail symbol location with hit-rate. Not feature point count
2111
                                        Change parameter on add symbol part (mpCount → hitRate)
2112
                                        Remove unusing calculation (avg)
2113
                    Jeongwoo 2018.06.27 Remove part to split P&ID image and for loop
2114
                    humkyung 2018.07.07 return searched symbols
2115
    '''
2116

    
2117
    @staticmethod
2118
    def detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker):
2119
        import copy
2120
        global ocrCompletedSrc
2121
        global threadLock
2122
        global maxProgressValue
2123

    
2124
        try:
2125
            forTraining = worker.isTrainingChecked
2126
            symbolName = targetSymbol.getName()
2127
            symbolType = targetSymbol.getType()
2128
            symbolPath = targetSymbol.getPath()
2129
            symbolThreshold = targetSymbol.getThreshold() if not forTraining else targetSymbol.getThreshold() * 0.90
2130
            symbolMinMatchCount = targetSymbol.getMinMatchCount()
2131
            isDetectOnOrigin = targetSymbol.getIsDetectOnOrigin()
2132
            symbolRotateCount = targetSymbol.getRotationCount()
2133
            symbolOcrOption = targetSymbol.getOcrOption()
2134
            isContainChild = targetSymbol.getIsContainChild()
2135
            symbolOriginalPoint = targetSymbol.getOriginalPoint()
2136
            symbolConnectionPoint = targetSymbol.getConnectionPoint()
2137
            baseSymbol = targetSymbol.getBaseSymbol()
2138
            additionalSymbol = targetSymbol.getAdditionalSymbol()
2139
            isExceptDetect = targetSymbol.getIsExceptDetect()
2140
            detectFlip = targetSymbol.getDetectFlip()
2141
            hasInstrumentLabel = targetSymbol.getHasInstrumentLabel()
2142
            text_area = targetSymbol.getText_area()
2143

    
2144
            # check if symbol file is target or not
2145
            if isExceptDetect == 1:
2146
                item = QListWidgetItem('{} file is not target'.format(symbolName))
2147
                item.setBackground(QColor('green'))
2148
                listWidget.addItem(item)
2149
                return
2150

    
2151
            foundSymbolCount = 0
2152

    
2153
            # check if symbol file exists
2154
            if not os.path.isfile(symbolPath):
2155
                item = QListWidgetItem('{} file not found'.format(symbolName))
2156
                item.setBackground(QColor('red'))
2157
                listWidget.addItem(item)
2158
                return
2159
            # up to here
2160

    
2161
            sym = cv2.imread(symbolPath, 1)
2162
            symGray = Worker.cvtGrayImage(sym)
2163
            symGrayOri = copy.copy(symGray)
2164
            ## TODO: 이진화 시켰을때 심볼이 검출되지 않음
2165
            ## symGray = cv2.threshold(cvtGrayImage(sym), 127, 255, cv2.THRESH_BINARY)[1]
2166
            ## cv2.imshow('symbol', symGray)
2167
            ## cv2.waitKey(0)
2168
            sow, soh = symGray.shape[::-1]  # symbol original w, h
2169

    
2170
            offsetDrawingArea = []
2171
            app_doc_data = AppDocData.instance()
2172
            area = app_doc_data.getArea('Drawing')
2173
            if area is not None:
2174
                roiItem = Worker.remove_small_objects(area.img, 0, sow * soh * 0.5) if hasInstrumentLabel else area.img
2175
                offsetDrawingArea.append(area.x)
2176
                offsetDrawingArea.append(area.y)
2177
            else:
2178
                offsetDrawingArea.append(0)
2179
                offsetDrawingArea.append(0)
2180
                if isDetectOnOrigin == 1:
2181
                    roiItem = app_doc_data.imgSrc
2182
                else:
2183
                    roiItem = ocrCompletedSrc
2184
            srcWidth, srcHeight = roiItem.shape[::-1]
2185

    
2186
            roiItemSp = (0, 0)
2187
            roiItemEp = (srcWidth, srcHeight)
2188

    
2189
            # try to recognize symbol twice(first one is normal, second one is flipped)
2190
            steps = [False, True] if detectFlip else [False]
2191
            for flipped in steps:
2192
                if flipped:
2193
                    symGray = symGrayOri
2194
                    symGray = cv2.flip(symGray, 1)
2195

    
2196
                symbolRotatedAngle = 0
2197
                #for rc in range((symbolRotateCount + 1) * 2):  # Rotation count를 사용자 기준으로 받아서 1을 더한 후 사용
2198
                for rc in range(symbolRotateCount + 1):  # Rotation count를 사용자 기준으로 받아서 1을 더한 후 사용
2199
                    sw, sh = symGray.shape[::-1]
2200
                    roiw = (roiItemEp[0] - roiItemSp[0])
2201
                    roih = (roiItemEp[1] - roiItemSp[1])
2202

    
2203
                    # get Rotated Original Point
2204
                    originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint,
2205
                                                                      symbolRotatedAngle, sw, sh, sow, soh, flipped)
2206
                    connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw,
2207
                                                                          sh, sow, soh, flipped)
2208

    
2209
                    # Case : symbol is bigger than roi
2210
                    if roiw < sw or roih < sh:
2211
                        symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
2212
                        symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
2213
                        #cX, cY = originalPoint[0], originalPoint[0]
2214
                        #M = cv2.getRotationMatrix2D((cX, cY), 45, 1.0)
2215
                        #symGray = cv2.warpAffine(symGray, M, (sw, sh))
2216
                        #symbolRotatedAngle = (symbolRotatedAngle + 45) % 360
2217

    
2218
                        if baseSymbol is not None and additionalSymbol is not None:
2219
                            additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol)
2220
                        continue
2221

    
2222
                    # For OPC
2223
                    drawing_area = app_doc_data.getArea('Drawing')
2224
                    configs = app_doc_data.getConfigs('Symbol', 'OPC')
2225
                    if drawing_area is not None and (symbolType == "Piping OPC\'s" or symbolType == "Instrument OPC\'s") and configs and int(configs[0].value) == 1:
2226
                        customMatch = worker.detectOPCOnPid(drawing_area, symGray)
2227
                        customMatch = [match for match in customMatch if match[0] > symbolThreshold]
2228
                        if customMatch and len(customMatch) > 0:
2229
                            for custom in customMatch:
2230
                                hitRate = custom[0]
2231
                                searchedItemSp = (custom[1][0] + round(offsetDrawingArea[0]), custom[1][1] + round(offsetDrawingArea[1]))
2232

    
2233
                                is_add = True
2234
                                for searched in searchedSymbolList:
2235
                                    if Worker.IsOverlap((searchedItemSp[0], searchedItemSp[1], sw, sh), (
2236
                                            searched.getSp()[0], searched.getSp()[1], searched.getWidth(),
2237
                                            searched.getHeight())):
2238
                                        if searched.getHitRate() > hitRate:
2239
                                            is_add = False
2240
                                        else:
2241
                                            searchedSymbolList.remove(searched)
2242
                                            break
2243

    
2244
                                if is_add:
2245
                                    threadLock.acquire()
2246
                                    foundSymbolCount = foundSymbolCount + 1
2247
                                    Worker.addSearchedSymbol(symbolName, symbolType,
2248
                                                             searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount,
2249
                                                             hitRate, symbolRotatedAngle,
2250
                                                             isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
2251
                                                             isContainChild,
2252
                                                             originalPoint, connectionPoint, baseSymbol, additionalSymbol,
2253
                                                             isExceptDetect, detectFlip=1 if flipped else 0,
2254
                                                             hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
2255
                                    threadLock.release()
2256

    
2257
                    if forTraining:
2258
                        sw, sh = sw + 20, sh + 20
2259

    
2260
                    # Template Matching
2261
                    tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED)
2262
                    loc = np.where(tmRes >= symbolThreshold)
2263

    
2264
                    for pt in zip(*loc[::-1]):
2265
                        '''
2266
                        # no more used 
2267
                        mpCount = 0  # Match Point Count
2268
                        roi = roiItem[pt[1]:pt[1] + sh, pt[0]:pt[0] + sw]
2269

2270
                        if symbolMinMatchCount > 0:
2271
                            mpCount = Worker.getMatchPointCount(roi, symGray)
2272
                            if not (mpCount >= symbolMinMatchCount):
2273
                                continue
2274
                        '''
2275

    
2276
                        searchedItemSp = (roiItemSp[0] + pt[0] + round(offsetDrawingArea[0]),
2277
                                          roiItemSp[1] + pt[1] + round(offsetDrawingArea[1]))
2278
                        
2279
                        if forTraining:
2280
                            searchedItemSp = [searchedItemSp[0] - 10, searchedItemSp[1] - 10]
2281

    
2282
                        overlapArea = 0
2283
                        symbolIndex = -1
2284
                        for i in range(len(searchedSymbolList) - 1, -1, -1):
2285
                            area = Worker.contains(searchedSymbolList[i], searchedItemSp, sw, sh)
2286
                            if area > ACCEPT_OVERLAY_AREA:
2287
                                # if area > overlapArea:
2288
                                #    overlapArea = area
2289
                                #    symbolIndex = i
2290
                                overlapArea = area
2291
                                symbolIndex = i
2292
                                break
2293
                                """
2294
                                categories = [appDocData.isEquipmentType(symbolType), appDocData.isEquipmentType(searchedSymbolList[i].getType())]
2295
                                if categories[0] == categories[1]:
2296
                                    symbolIndex = i
2297
                                    break
2298
                                """
2299

    
2300
                        hitRate = tmRes[pt[1], pt[0]]
2301

    
2302
                        # 겹치는 영역이 기준값보다 작을 경우
2303
                        if overlapArea <= ACCEPT_OVERLAY_AREA:
2304
                            threadLock.acquire()
2305
                            foundSymbolCount = foundSymbolCount + 1
2306
                            Worker.addSearchedSymbol(symbolName, symbolType,
2307
                                                     searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount,
2308
                                                     hitRate, symbolRotatedAngle,
2309
                                                     isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
2310
                                                     isContainChild,
2311
                                                     originalPoint, connectionPoint, baseSymbol, additionalSymbol,
2312
                                                     isExceptDetect,
2313
                                                     detectFlip=1 if flipped else 0,
2314
                                                     hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
2315
                            threadLock.release()
2316
                        else:  # 겹치는 영역이 기준값보다 클 경우
2317
                            if symbolIndex != -1 and symbolIndex < len(searchedSymbolList):
2318
                                searchedSymbol = searchedSymbolList[symbolIndex]
2319
                                # 현재 심볼과 검출된 심볼이 같을 경우 Match Point가 더 높은 정보로 교체
2320
                                if symbolName == searchedSymbol.getName():
2321
                                    symbolHitRate = searchedSymbol.getHitRate()
2322
                                    if symbolHitRate - searchedSymbol.getThreshold() < hitRate - symbolThreshold:
2323
                                        threadLock.acquire()
2324
                                        # replace existing symbol with new symbol has high accuracy
2325
                                        searchedSymbolList[symbolIndex] = symbol.Symbol(symbolName, symbolType,
2326
                                                                                        searchedItemSp, sw, sh,
2327
                                                                                        symbolThreshold,
2328
                                                                                        symbolMinMatchCount, hitRate,
2329
                                                                                        symbolRotatedAngle,
2330
                                                                                        isDetectOnOrigin,
2331
                                                                                        symbolRotateCount,
2332
                                                                                        symbolOcrOption, isContainChild,
2333
                                                                                        ','.join(str(x) for x in
2334
                                                                                                 originalPoint),
2335
                                                                                        '/'.join('{},{},{},{}'.format(
2336
                                                                                            param[0], param[1],
2337
                                                                                            param[2], param[3]) for
2338
                                                                                                 param in
2339
                                                                                                 connectionPoint),
2340
                                                                                        baseSymbol, additionalSymbol,
2341
                                                                                        isExceptDetect,
2342
                                                                                        detectFlip=1 if flipped else 0,
2343
                                                                                        hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
2344
                                        threadLock.release()
2345
                                # 현재 심볼과 검출된 심볼이 같지 않을 경우 (포함)
2346
                                elif app_doc_data.isEquipmentType(searchedSymbol.getType()) and not app_doc_data.isEquipmentType(symbolType):
2347
                                    if searchedSymbol.area > sw * sh * 10:  # searched equipment area is greather than 10 times of symbol's area
2348
                                        threadLock.acquire()
2349
                                        foundSymbolCount = foundSymbolCount + 1
2350
                                        Worker.addSearchedSymbol(symbolName, symbolType,
2351
                                                                 searchedItemSp, sw, sh, symbolThreshold, hitRate,
2352
                                                                 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
                                elif not forTraining:
2362
                                    searchedSymbol = searchedSymbolList[symbolIndex]
2363
                                    symbolHitRate = searchedSymbol.getHitRate()
2364
                                    if symbolHitRate - searchedSymbol.getThreshold() < hitRate - symbolThreshold:
2365
                                        threadLock.acquire()
2366
                                        searchedSymbolList[symbolIndex] = symbol.Symbol(symbolName, symbolType,
2367
                                                                                        searchedItemSp, sw, sh,
2368
                                                                                        symbolThreshold,
2369
                                                                                        symbolMinMatchCount, hitRate,
2370
                                                                                        symbolRotatedAngle,
2371
                                                                                        isDetectOnOrigin,
2372
                                                                                        symbolRotateCount,
2373
                                                                                        symbolOcrOption, isContainChild,
2374
                                                                                        ','.join(str(x) for x in
2375
                                                                                                 originalPoint),
2376
                                                                                        '/'.join('{},{},{},{}'.format(
2377
                                                                                            param[0], param[1],
2378
                                                                                            param[2], param[3]) for
2379
                                                                                                 param in
2380
                                                                                                 connectionPoint),
2381
                                                                                        baseSymbol, additionalSymbol,
2382
                                                                                        isExceptDetect,
2383
                                                                                        detectFlip=1 if flipped else 0,
2384
                                                                                        hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
2385
                                        threadLock.release()
2386
                                # 학습용 데이터 생성을 위해 교체하지 않고 추가함
2387
                                elif forTraining:
2388
                                    threadLock.acquire()
2389
                                    foundSymbolCount = foundSymbolCount + 1
2390
                                    Worker.addSearchedSymbol(symbolName, symbolType,
2391
                                                             searchedItemSp, sw, sh, symbolThreshold,
2392
                                                             symbolMinMatchCount, hitRate, symbolRotatedAngle,
2393
                                                             isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
2394
                                                             isContainChild,
2395
                                                             originalPoint, connectionPoint, baseSymbol,
2396
                                                             additionalSymbol, isExceptDetect,
2397
                                                             detectFlip=1 if flipped else 0,
2398
                                                             hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
2399
                                    threadLock.release()
2400

    
2401
                    # rotate symbol
2402
                    symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
2403
                    symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
2404
                    #cX, cY = originalPoint[0], originalPoint[0]
2405
                    #M = cv2.getRotationMatrix2D((cX, cY), 45, 1.0)
2406
                    #symGray = cv2.warpAffine(symGray, M, (sw, sh))
2407
                    #symbolRotatedAngle = (symbolRotatedAngle + 45) % 360
2408

    
2409
                    if additionalSymbol is not None:
2410
                        additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol)
2411

    
2412
            threadLock.acquire()
2413
            listWidget.addItem('Found Symbol   : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str(
2414
                foundSymbolCount) + ')')
2415
            threadLock.release()
2416

    
2417
            """
2418
            if area is not None and hasInstrumentLabel:
2419
                # restore objects smaller than symbol
2420
                roiItem = cv2.drawContours(roiItem, outside_contours, -1, (0, 0, 0), -1)
2421
                roiItem = cv2.drawContours(roiItem, hole_contours, -1, (255, 255, 255), -1)
2422
                # up to here
2423
                cv2.imwrite('c:\\Temp\\contour2.png', roiItem)
2424
            """
2425

    
2426
            worker.updateProgress.emit(maxProgressValue, symbolPath)
2427

    
2428
            return [symbol for symbol in searchedSymbolList if symbol.getName() == symbolName]
2429
        except Exception as ex:
2430
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
2431
                                                           sys.exc_info()[-1].tb_lineno)
2432
            worker.displayLog.emit(MessageType.Error, message)
2433

    
2434
        return []
2435

    
2436
    @staticmethod
2437
    def IsOverlap(range1, range2):
2438
        if range1[0] <= range2[0] + range2[2] and range1[0] + range1[2] >= range2[0] and range1[1] <= range2[1] + \
2439
                range2[3] and range1[1] + range1[3] >= range2[1]:
2440
            range = (min(range1[0] + range1[2], range2[0] + range2[2]) - max(range1[0], range2[0])) * (
2441
                    min(range1[1] + range1[3], range2[1] + range2[3]) - max(range1[1], range2[1]))
2442
            if range >= range1[2] * range1[3] * 0.4 and range >= range2[2] * range2[3] * 0.4:
2443
                return True
2444
            else:
2445
                return False
2446
        else:
2447
            return False
2448

    
2449
    @staticmethod
2450
    def detectOPCOnPid(area, symGray):
2451
        results = []
2452

    
2453
        try:
2454
            symbol = cv2.copyMakeBorder(symGray, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=255)
2455
            not_symbol = cv2.bitwise_not(symbol)
2456
            symbol_contours, symbol_hierachy = cv2.findContours(not_symbol, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
2457
            if symbol_hierachy[0][0][0] != -1:
2458
                return results
2459

    
2460
            contour_count = len(symbol_contours)
2461
            if contour_count != 2:
2462
                return results
2463

    
2464
            for i in range(1, contour_count):
2465
                # contour size
2466
                symbol_area = cv2.contourArea(symbol_contours[i])
2467
                # moments mass center
2468
                symbol_moments = cv2.moments(symbol_contours[i])
2469
                symbol_x = int(symbol_moments['m10'] / (symbol_moments['m00'] + 1e-5))
2470
                symbol_y = int(symbol_moments['m01'] / (symbol_moments['m00'] + 1e-5))
2471
                rect_x, rect_y, rect_w, rect_h = cv2.boundingRect(symbol_contours[i])
2472
                symbol_x = symbol_x - rect_x
2473
                symbol_y = symbol_y - rect_y
2474
                # percent x, y
2475
                percent_x = symbol_x / rect_w
2476
                percent_y = symbol_y / rect_h
2477

    
2478
                for contour in area.contours:
2479
                    area_area = cv2.contourArea(contour)
2480
                    if area_area * 1.2 >= symbol_area >= area_area * 0.8:
2481
                        I1 = cv2.matchShapes(symbol_contours[i], contour, 1, 0)
2482
                        I2 = cv2.matchShapes(symbol_contours[i], contour, 2, 0)
2483
                        I3 = cv2.matchShapes(symbol_contours[i], contour, 3, 0)
2484
                        if I1 < 1 and I2 < 1 and I3 < 0.1:
2485
                            rect_x2, rect_y2, rect_w2, rect_h2 = cv2.boundingRect(contour)
2486
                            if rect_w * 1.2 >= rect_w2 >= rect_w * 0.8 and rect_h * 1.2 >= rect_h2 >= rect_h * 0.8:
2487
                                # moments mass center
2488
                                moments = cv2.moments(contour)
2489
                                x = int(moments['m10'] / (moments['m00'] + 1e-5))
2490
                                y = int(moments['m01'] / (moments['m00'] + 1e-5))
2491

    
2492
                                x = x - rect_x2
2493
                                y = y - rect_y2
2494
                                percent_x2 = x / rect_w2
2495
                                percent_y2 = y / rect_h2
2496

    
2497
                                value_x = abs(percent_x - percent_x2)
2498
                                value_y = abs(percent_y - percent_y2)
2499

    
2500
                                results.append([1 - (value_x + value_y), [rect_x2, rect_y2]])
2501
                break
2502
        except Exception as ex:
2503
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
2504
                                                           sys.exc_info()[-1].tb_lineno)
2505
        return results
2506

    
2507
    # TODO: detect flange
2508
    @staticmethod
2509
    def detectFlangeOnPid(symbol, connector, line, image):
2510
        pt = connector.center()
2511
        x = int(pt[0])
2512
        y = int(pt[1])
2513
        center_x = int(symbol.loc[0] + symbol.size[0] / 2)
2514
        center_y = int(symbol.loc[1] + symbol.size[1] / 2)
2515

    
2516
        arrow = Arrow.NULL
2517
        if line:
2518
            line_center_x = int((line.start_point()[0] + line.end_point()[0]) / 2)
2519
            line_center_y = int((line.start_point()[1] + line.end_point()[1]) / 2)
2520

    
2521
            if line.isHorizontal():
2522
                if center_x < line_center_x:
2523
                    arrow = Arrow.RIGHT
2524
                else:
2525
                    arrow = Arrow.LEFT
2526
            elif line.isVertical():
2527
                if center_y < line_center_y:
2528
                    arrow = Arrow.DOWN
2529
                else:
2530
                    arrow = Arrow.UP
2531

    
2532
            if arrow is not Arrow.NULL:
2533
                result = Worker.detectFlangeOnPidArrow(symbol, x, y, arrow, image)
2534
                if result:
2535
                    return [x, y]
2536
        else:
2537
            _dir = [round(connector.dir().x()),  round(connector.dir().y())]
2538
            if abs(_dir[0]) == 1:
2539
                if _dir[0] > 0:
2540
                    arrow = Arrow.RIGHT
2541
                else:
2542
                    arrow = Arrow.LEFT
2543
            elif abs(_dir[1]) == 1:
2544
                if _dir[1] > 0:
2545
                    arrow = Arrow.DOWN
2546
                else:
2547
                    arrow = Arrow.UP
2548

    
2549
            result = Worker.detectFlangeBlindOnPid(x, y, arrow, image)
2550
            if result:
2551
                return [x, y]
2552
           
2553
        return None
2554

    
2555
    @staticmethod
2556
    def detectFlangeBlindOnPid(start_x, start_y, arrow, image):
2557
        loopRange = []
2558
        if arrow is Arrow.DOWN:
2559
            loopRange = range(start_y - 10, start_y + 25)
2560
        elif arrow is Arrow.UP:
2561
            loopRange = range(start_y + 10, start_y - 25, -1)
2562
        elif arrow is Arrow.LEFT:
2563
            loopRange = range(start_x + 10, start_x - 25, -1)
2564
        elif arrow is Arrow.RIGHT:
2565
            loopRange = range(start_x - 10, start_x + 25)
2566
        else:
2567
            return None
2568

    
2569
        find_forward = False
2570
        find_backward = False
2571
        find = None
2572
        # 검은색 점을 찾는 범위
2573
        search_length = 30
2574
        # Line 최소 Length
2575
        checkLineLength = 20
2576
        # Line 최대 Width
2577
        line_width = 18
2578
        # flange min length
2579
        flange_min = 25
2580
        # flange max length
2581
        flange_max = 50
2582
        # flange max width
2583
        flange_count_max = 10
2584
        # noise max count
2585
        noise_count_max = 5
2586

    
2587
        # 임시
2588
        temp_count = 0
2589
        noise_count = 0
2590
        find_list_x = []
2591
        find_list_y = []
2592
        for i in loopRange:
2593
            loop_find = False
2594
            for j in range(search_length):
2595
                width = 0
2596
                color_1 = 0
2597
                color_2 = 0
2598
                find_x = 0
2599
                find_y = 0
2600

    
2601
                if arrow is Arrow.DOWN or arrow is Arrow.UP:
2602
                    color_1 = image[i, start_x - j]
2603
                    color_2 = image[i, start_x + j]
2604
                    if int(color_1) is 0:
2605
                        width = Worker.getWidth(start_x - j, i, arrow, image)
2606
                        find_x = start_x - j
2607
                        find_y = i
2608
                    elif int(color_2) is 0:
2609
                        width = Worker.getWidth(start_x + j, i, arrow, image)
2610
                        find_x = start_x + j
2611
                        find_y = i
2612

    
2613
                elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2614
                    color_1 = image[start_y - j, i]
2615
                    color_2 = image[start_y + j, i]
2616
                    if int(color_1) is 0:
2617
                        width = Worker.getWidth(i, start_y - j, arrow, image)
2618
                        find_x = i
2619
                        find_y = start_y - j
2620
                    elif int(color_2) is 0:
2621
                        width = Worker.getWidth(i, start_y + j, arrow, image)
2622
                        find_x = i
2623
                        find_y = start_y + j
2624

    
2625
                if flange_min < width < flange_max:
2626
                    loop_find = True
2627
                    find_list_x.append(find_x)
2628
                    find_list_y.append(find_y)
2629
                    break
2630
                elif width > 0:
2631
                    if temp_count > 0:
2632
                        noise_count += 1
2633
                    break
2634

    
2635
            if loop_find:
2636
                if temp_count > flange_count_max:
2637
                    break
2638
                temp_count = temp_count + 1
2639
            elif 0 < temp_count < flange_count_max and width > 0 and noise_count <= noise_count_max:
2640
                continue
2641
            elif noise_count > noise_count_max:
2642
                break
2643
            elif 0 < temp_count < flange_count_max and width == 0 and noise_count <= noise_count_max:
2644
                find_forward = True
2645
                break
2646
            else:
2647
                find_list_x.clear()
2648
                find_list_y.clear()
2649
                temp_count = 0
2650

    
2651
        if not find_forward:
2652
            return False
2653

    
2654
        # 임시
2655
        temp_count = 0
2656
        noise_count = 0
2657
        find_list_x = []
2658
        find_list_y = []
2659
        for i in reversed(loopRange):
2660
            loop_find = False
2661
            for j in range(search_length):
2662
                width = 0
2663
                color_1 = 0
2664
                color_2 = 0
2665
                find_x = 0
2666
                find_y = 0
2667

    
2668
                if arrow is Arrow.DOWN or arrow is Arrow.UP:
2669
                    color_1 = image[i, start_x - j]
2670
                    color_2 = image[i, start_x + j]
2671
                    if int(color_1) is 0:
2672
                        width = Worker.getWidth(start_x - j, i, arrow, image)
2673
                        find_x = start_x - j
2674
                        find_y = i
2675
                    elif int(color_2) is 0:
2676
                        width = Worker.getWidth(start_x + j, i, arrow, image)
2677
                        find_x = start_x + j
2678
                        find_y = i
2679

    
2680
                elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2681
                    color_1 = image[start_y - j, i]
2682
                    color_2 = image[start_y + j, i]
2683
                    if int(color_1) is 0:
2684
                        width = Worker.getWidth(i, start_y - j, arrow, image)
2685
                        find_x = i
2686
                        find_y = start_y - j
2687
                    elif int(color_2) is 0:
2688
                        width = Worker.getWidth(i, start_y + j, arrow, image)
2689
                        find_x = i
2690
                        find_y = start_y + j
2691

    
2692
                if flange_min < width < flange_max:
2693
                    loop_find = True
2694
                    find_list_x.append(find_x)
2695
                    find_list_y.append(find_y)
2696
                    break
2697
                elif width > 0:
2698
                    if temp_count > 0:
2699
                        noise_count += 1
2700
                    break
2701

    
2702
            if loop_find:
2703
                if temp_count > flange_count_max:
2704
                    break
2705
                temp_count = temp_count + 1
2706
            elif 0 < temp_count < flange_count_max and width > 0 and noise_count <= noise_count_max:
2707
                continue
2708
            elif noise_count > noise_count_max:
2709
                break
2710
            elif 0 < temp_count < flange_count_max and width == 0 and noise_count <= noise_count_max:
2711
                find_backward = True
2712
                break
2713
            else:
2714
                find_list_x.clear()
2715
                find_list_y.clear()
2716
                temp_count = 0
2717
        
2718
        if find_forward and find_backward:
2719
            return True
2720

    
2721
    @staticmethod
2722
    def detectFlangeOnPidArrow(symbol, start_x, start_y, arrow, image):
2723
        loopRange = []
2724
        if arrow is Arrow.DOWN:
2725
            loopRange = range(start_y - 20, start_y + 40)
2726
        elif arrow is Arrow.UP:
2727
            loopRange = range(start_y + 20, start_y - 40, -1)
2728
        elif arrow is Arrow.LEFT:
2729
            loopRange = range(start_x + 20, start_x - 40, -1)
2730
        elif arrow is Arrow.RIGHT:
2731
            loopRange = range(start_x - 20, start_x + 40)
2732
        else:
2733
            return None
2734

    
2735
        find = False
2736
        # 검은색 점을 찾는 범위
2737
        search_length = 10
2738
        # Line 최소 Length
2739
        checkLineLength = 20
2740
        # Line 최대 Width
2741
        line_width = 18
2742
        # flange min length
2743
        flange_min = 25
2744
        # flange max length
2745
        flange_max = 50
2746
        # flange max width
2747
        flange_count_max = 10
2748
        # 임시
2749
        temp_count = 0
2750
        find_list_x = []
2751
        find_list_y = []
2752
        for i in loopRange:
2753
            loop_find = False
2754
            for j in range(search_length):
2755
                width = 0
2756
                color_1 = 0
2757
                color_2 = 0
2758
                find_x = 0
2759
                find_y = 0
2760

    
2761
                if arrow is Arrow.DOWN or arrow is Arrow.UP:
2762
                    color_1 = image[i, start_x - j]
2763
                    color_2 = image[i, start_x + j]
2764
                    if int(color_1) is 0:
2765
                        width = Worker.getWidth(start_x - j, i, arrow, image)
2766
                        find_x = start_x - j
2767
                        find_y = i
2768
                    elif int(color_2) is 0:
2769
                        width = Worker.getWidth(start_x + j, i, arrow, image)
2770
                        find_x = start_x + j
2771
                        find_y = i
2772

    
2773
                elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2774
                    color_1 = image[start_y - j, i]
2775
                    color_2 = image[start_y + j, i]
2776
                    if int(color_1) is 0:
2777
                        width = Worker.getWidth(i, start_y - j, arrow, image)
2778
                        find_x = i
2779
                        find_y = start_y - j
2780
                    elif int(color_2) is 0:
2781
                        width = Worker.getWidth(i, start_y + j, arrow, image)
2782
                        find_x = i
2783
                        find_y = start_y + j
2784

    
2785
                if 0 < width <= line_width:
2786
                    loop_find = True
2787
                    find_list_x.append(find_x)
2788
                    find_list_y.append(find_y)
2789
                    break
2790

    
2791
            if loop_find:
2792
                if temp_count > checkLineLength:
2793
                    find = True
2794
                    break
2795
                temp_count = temp_count + 1
2796
            else:
2797
                find_list_x.clear()
2798
                find_list_y.clear()
2799
                temp_count = 0
2800
        
2801
        if find:
2802
            count = 0
2803
            temp_list = []
2804
            find_white = False
2805
            average = 0
2806
            if arrow is Arrow.DOWN or arrow is Arrow.UP:
2807
                average = Worker.getAverage(find_list_x)
2808
            elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2809
                average = Worker.getAverage(find_list_y)
2810
            else:
2811
                pass
2812

    
2813
            flange_count = 0
2814
            for i in range(20):
2815
                width = 0
2816
                if arrow is Arrow.DOWN:
2817
                    width = Worker.getWidth(average, find_list_y[0] - i, arrow, image)
2818
                elif arrow is Arrow.UP:
2819
                    width = Worker.getWidth(average, find_list_y[0] + i, arrow, image)
2820
                elif arrow is Arrow.LEFT:
2821
                    width = Worker.getWidth(find_list_x[0] + i, average, arrow, image)
2822
                elif arrow is Arrow.RIGHT:
2823
                    width = Worker.getWidth(find_list_x[0] - i, average, arrow, image)
2824
                else:
2825
                    pass
2826
                
2827
                if flange_min < width < flange_max:
2828
                    flange_count = flange_count + 1
2829
                elif width <= 0:
2830
                    find_white = True
2831
                    break
2832
                else:
2833
                    pass
2834

    
2835
            if 0 < flange_count < flange_count_max and find_white:
2836
                crop_image = None
2837
                x = find_list_x[0] - (int(symbol.loc[0]) - int(symbol.size[0]) - 1)
2838
                y = find_list_y[0] - (int(symbol.loc[1]) - int(symbol.size[1]) - 1)
2839
                crop_image = Worker.removeLine(symbol, find_list_x[0], find_list_y[0], arrow, image)
2840
                if arrow is Arrow.DOWN:
2841
                    y = y - 1
2842
                elif arrow is Arrow.UP:
2843
                    y = y + 1
2844
                elif arrow is Arrow.LEFT:
2845
                    x = x + 1
2846
                elif arrow is Arrow.RIGHT:
2847
                    x = x - 1
2848
                else:
2849
                    return None
2850
                crop_image = cv2.copyMakeBorder(crop_image, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=255)
2851
                not_image = cv2.bitwise_not(crop_image)
2852
                image_contours, image_hierachy = cv2.findContours(not_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
2853
                for contour in image_contours:
2854
                    c_x, c_y, c_w, c_h = cv2.boundingRect(contour)
2855
                    area = cv2.contourArea(contour)
2856
                    if c_x <= x and c_x + c_w >= x and c_y <= y and c_y + c_h >= y:
2857
                        if flange_count * flange_max * 1.8 >= area:
2858
                            #not_image[y, x] = 0
2859
                            #from PIL import Image
2860
                            #Image.fromarray(not_image).show()
2861
                            return True
2862

    
2863
            else:
2864
                return False
2865

    
2866
        return False
2867

    
2868
    @staticmethod
2869
    def getWidth(x, y, arrow, image):
2870
        width = 0
2871
        move_x1 = 0
2872
        move_y1 = 0
2873
        move_x2 = 0
2874
        move_y2 = 0
2875

    
2876
        if arrow is Arrow.DOWN or arrow is Arrow.UP:
2877
            while True:
2878
                color = image[y, x - move_x1]
2879
                if int(color) is 0:
2880
                    move_x1 = move_x1 + 1
2881
                else:
2882
                    break
2883
            while True:
2884
                color = image[y, x + move_x2]
2885
                if int(color) is 0:
2886
                    move_x2 = move_x2 + 1
2887
                else:
2888
                    break
2889
            width = move_x1 + move_x2 - 1
2890
        elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2891
            while True:
2892
                color = image[y - move_y1, x]
2893
                if int(color) is 0:
2894
                    move_y1 = move_y1 + 1
2895
                else:
2896
                    break
2897
            while True:
2898
                color = image[y + move_y2, x]
2899
                if int(color) is 0:
2900
                    move_y2 = move_y2 + 1
2901
                else:
2902
                    break
2903
            width = move_y1 + move_y2 - 1
2904

    
2905
        return width
2906

    
2907
    @staticmethod
2908
    def removeLine(symbol, x, y, arrow, image):
2909
        symbol_width = int(symbol.size[0])
2910
        symbol_height = int(symbol.size[1])
2911

    
2912
        if arrow is Arrow.DOWN or arrow is Arrow.UP:
2913
            for i in range(symbol_width):
2914
                image[y, x - i] = 255
2915
                image[y, x + i] = 255
2916
        elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2917
            for i in range(symbol_height):
2918
                image[y - i, x] = 255
2919
                image[y + i, x] = 255
2920
                  
2921
        crop_x1 = int(symbol.loc[0]) - symbol_width
2922
        crop_y1 = int(symbol.loc[1]) - symbol_height
2923
        crop_x2 = int(symbol.loc[0]) + symbol_width * 2
2924
        crop_y2 = int(symbol.loc[1]) + symbol_height * 2
2925
        image = image[crop_y1:crop_y2, crop_x1:crop_x2]
2926
        
2927
        return image
2928

    
2929
    @staticmethod
2930
    def getAverage(datas):
2931
        result = 0
2932

    
2933
        for x in datas:
2934
            result = result + x
2935

    
2936
        result = result / len(datas)
2937

    
2938
        return int(result)
2939

    
2940

    
2941
    # Convert into Grayscale image
2942
    @staticmethod
2943
    def cvtGrayImage(img):
2944
        return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
2945

    
2946
    '''
2947
        @history    2018.06.12  Jeongwoo    Type changed (int → float)
2948
                    humkyung 2018.07.07 change return type as like [x,y]
2949
    '''
2950

    
2951
    @staticmethod
2952
    def getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolRotatedAngle, rotateSymbolWidth,
2953
                                   rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight, flipped=False):
2954
        res = []
2955

    
2956
        if additionalSymbol is None and symbolOriginalPoint is None:
2957
            res.append(rotateSymbolWidth // 2)
2958
            res.append(rotateSymbolHeight // 2)
2959
        else:
2960
            if flipped:
2961
                opx = originalSymbolWidth - float(symbolOriginalPoint.split(',')[0])
2962
                opy = float(symbolOriginalPoint.split(',')[1])
2963
            else:
2964
                opx = float(symbolOriginalPoint.split(',')[0])
2965
                opy = float(symbolOriginalPoint.split(',')[1])
2966

    
2967
            rPt = Worker.getCoordOnRotatedImage(symbolRotatedAngle, ('AUTO', opx, opy, '0'), originalSymbolWidth,
2968
                                                originalSymbolHeight)
2969

    
2970
            res.append(rPt[1])
2971
            res.append(rPt[2])
2972

    
2973
        return res
2974

    
2975
    '''
2976
        @history    2018.06.12  Jeongwoo    Type changed (int → float)
2977
                    humkyung 2018.07.07 change return type as like [[x,y],...]
2978
                    humkyung 2019.01.04 get symbol index
2979
    '''
2980

    
2981
    @staticmethod
2982
    def getCalculatedConnectionPoint(symbolConnectionPointStr, symbolRotatedAngle, rotateSymbolWidth,
2983
                                     rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight, flipped=0):
2984
        res = []
2985

    
2986
        if symbolConnectionPointStr is not None and symbolConnectionPointStr != '':
2987
            splitConnectionPointStr = symbolConnectionPointStr.split("/")
2988
            for strConnPt in splitConnectionPointStr:
2989
                tokens = strConnPt.split(',')
2990

    
2991
                direction = 'AUTO'
2992
                symbol_idx = '0'
2993
                if flipped:
2994
                    converted = {'AUTO': 'AUTO', 'LEFT': 'RIGHT', 'RIGHT': 'LEFT', 'UP': 'UP', 'DOWN': 'DOWN'}
2995

    
2996
                    if len(tokens) == 2:
2997
                        cpx = originalSymbolWidth - float(tokens[0])
2998
                        cpy = float(tokens[1])
2999
                    elif len(tokens) == 3:
3000
                        #direction = converted[tokens[0]]
3001
                        direction = tokens[0]
3002
                        cpx = originalSymbolWidth - float(tokens[1])
3003
                        cpy = float(tokens[2])
3004
                    elif len(tokens) >= 4:
3005
                        #direction = converted[tokens[0]]
3006
                        direction = tokens[0]
3007
                        cpx = originalSymbolWidth - float(tokens[1])
3008
                        cpy = float(tokens[2])
3009
                        symbol_idx = tokens[3]
3010
                else:
3011
                    if len(tokens) == 2:
3012
                        cpx = float(tokens[0])
3013
                        cpy = float(tokens[1])
3014
                    elif len(tokens) == 3:
3015
                        direction = tokens[0]
3016
                        cpx = float(tokens[1])
3017
                        cpy = float(tokens[2])
3018
                    elif len(tokens) >= 4:
3019
                        direction = tokens[0]
3020
                        cpx = float(tokens[1])
3021
                        cpy = float(tokens[2])
3022
                        symbol_idx = tokens[3]
3023

    
3024
                res.append(Worker.getCoordOnRotatedImage(symbolRotatedAngle, (direction, cpx, cpy, symbol_idx),
3025
                                                         originalSymbolWidth, originalSymbolHeight))
3026

    
3027
        return res
3028

    
3029
    '''
3030
        @brief      rotate (x,y) by given angle
3031
        @author     Jeongwoo
3032
        @date       2018.??.??
3033
        @history    humkyung 2018.04.13 fixed code when angle is 90 or 270    
3034
                    Jeongwoo 2018.04.27 Change calculation method with QTransform
3035
                    humkyung 2018.09.01 calculate rotated direction
3036
    '''
3037

    
3038
    @staticmethod
3039
    def getCoordOnRotatedImage(angle, connPt, originImageWidth, originImageHeight):
3040
        import math
3041

    
3042
        rx = None
3043
        ry = None
3044

    
3045
        # calculate rotated direction
3046
        direction = connPt[0]
3047
        '''
3048
        if direction == 'LEFT':
3049
            direction = 'DOWN' if angle == 90 else 'RIGHT' if angle == 180 else 'UP' if angle == 270 else direction
3050
        elif direction == 'RIGHT':
3051
            direction = 'UP' if angle == 90 else 'LEFT' if angle == 180 else 'DOWN' if angle == 270 else direction
3052
        elif direction == 'UP':
3053
            direction = 'LEFT' if angle == 90 else 'DOWN' if angle == 180 else 'RIGHT' if angle == 270 else direction
3054
        elif direction == 'DOWN':
3055
            direction = 'RIGHT' if angle == 90 else 'UP' if angle == 180 else 'LEFT' if angle == 270 else direction
3056
        '''
3057
        # up to here
3058

    
3059
        '''
3060
        transform = QTransform()
3061
        if angle == 90 or angle == 270:
3062
            transform.translate(originImageHeight * 0.5, originImageWidth * 0.5)
3063
        elif angle == 0 or angle == 180:
3064
            transform.translate(originImageWidth * 0.5, originImageHeight * 0.5)
3065
        transform.rotate(abs(angle))
3066
        transform.translate(-originImageWidth * 0.5, -originImageHeight * 0.5)
3067
        point = QPoint(connPt[1], connPt[2])
3068
        point = transform.map(point)
3069
        rx = point.x()
3070
        ry = point.y()
3071
        '''
3072

    
3073
        rect = QRectF(0, 0, originImageWidth, originImageHeight)
3074
        points = [rect.bottomLeft(), rect.topLeft(), rect.topRight(), rect.bottomRight()]
3075
        transform2 = QTransform()
3076
        transform2.rotate(abs(angle))
3077
        points = [transform2.map(point) for point in points]
3078
        offset_x = abs(min([point.x() for point in points]))
3079
        offset_y = abs(min([point.y() for point in points]))
3080
        point = QPoint(connPt[1], connPt[2])
3081
        point = transform2.map(point)
3082
        rx, ry = point.x() + offset_x, point.y() + offset_y
3083

    
3084
        '''
3085
        pX, pY = connPt[1], originImageHeight - connPt[2]
3086
        rad = math.radians(-angle)
3087
        rot_ce_X = originImageWidth * 0.5
3088
        rot_ce_Y = originImageHeight * 0.5
3089
        rX = (pX - rot_ce_X) * math.cos(rad) - (pY - rot_ce_Y) * math.sin(rad) + rot_ce_X
3090
        rY = (pX - rot_ce_X) * math.sin(rad) + (pY - rot_ce_Y) * math.cos(rad) + rot_ce_Y
3091
        rx = rX
3092
        ry = originImageHeight - rY
3093

3094
        if abs(point.x() - rx) > 0.1 or abs(point.y() - ry) > 0.1:
3095
            print('a')
3096
        '''
3097

    
3098
        symbol_idx = connPt[3]
3099

    
3100
        return (direction, rx, ry, symbol_idx)
3101

    
3102
    '''
3103
        @brief      Add symbols
3104
        @author     jwkim
3105
        @date
3106
        @history    Change parameter (mpCount → hitRate)
3107
                    Yecheol 2018.07.04 Delete Symbol Id 
3108
    '''
3109

    
3110
    @staticmethod
3111
    def addSearchedSymbol(sName, sType
3112
                          , sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle
3113
                          , isDetectOnOrigin, rotateCount, ocrOption, isContainChild
3114
                          , originalPoint, connectionPoint, baseSymbol, additionalSymbol, isExceptDetect, detectFlip
3115
                          , hasInstrumentLabel, text_area):
3116
        global searchedSymbolList
3117

    
3118
        newSym = None
3119
        try:
3120
            newSym = symbol.Symbol(sName, sType
3121
                                   , sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle,
3122
                                   isDetectOnOrigin, rotateCount, ocrOption, isContainChild,
3123
                                   ','.join(str(x) for x in originalPoint),
3124
                                   '/'.join('{},{},{},{}'.format(param[0], param[1], param[2], param[3]) for param in
3125
                                            connectionPoint),
3126
                                   baseSymbol, additionalSymbol, isExceptDetect, detectFlip=1 if detectFlip else 0,
3127
                                   hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
3128

    
3129
            searchedSymbolList.append(newSym)
3130
        except Exception as ex:
3131
            from App import App
3132
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
3133
                                                           sys.exc_info()[-1].tb_lineno)
3134
            App.mainWnd().addMessage.emit(MessageType.Error, message)
3135

    
3136
        return newSym
3137

    
3138
    '''
3139
        @brief      Check object contains pt
3140
        @param      obj is item in searchedSymbolList
3141
    '''
3142

    
3143
    @staticmethod
3144
    def contains(obj, pt, tw, th):
3145
        sp = obj.getSp()
3146
        width = obj.getWidth()
3147
        height = obj.getHeight()
3148

    
3149
        if sp[0] > pt[0] + tw:
3150
            return 0
3151
        if sp[0] + width < pt[0]:
3152
            return 0
3153
        if sp[1] > pt[1] + th:
3154
            return 0
3155
        if sp[1] + height < pt[1]:
3156
            return 0
3157

    
3158
        # shared area
3159
        x = max(sp[0], pt[0])
3160
        y = max(sp[1], pt[1])
3161
        w = min(sp[0] + width, pt[0] + tw) - x
3162
        h = min(sp[1] + height, pt[1] + th) - y
3163

    
3164
        return float((w * h)) / float((tw * th)) * 100
3165

    
3166
    # Calculate count of keypoint match result
3167
    @staticmethod
3168
    def getMatchPointCount(src, cmp):
3169
        matchCount = 0
3170

    
3171
        try:
3172
            orb = cv2.ORB_create(1000, 2.0, 2, 1)
3173

    
3174
            kp1, des1 = orb.detectAndCompute(src, None)
3175
            kp2, des2 = orb.detectAndCompute(cmp, None)
3176

    
3177
            FLANN_INDEX_LSH = 6
3178
            # table_number      : The number of hash tables use
3179
            # key_size          : The length of the key in the hash tables
3180
            # multi_probe_level : Number of levels to use in multi-probe (0 for standard LSH)
3181
            #                     It controls how neighboring buckets are searched
3182
            #                     Recommended value is 2
3183
            # checks            : specifies the maximum leafs to visit when searching for neighbours.
3184
            # LSH : Locality-Sensitive Hashing
3185
            # ref : https://www.cs.ubc.ca/research/flann/uploads/FLANN/flann_manual-1.8.4.pdf
3186
            index_params = dict(algorithm=FLANN_INDEX_LSH, table_number=20, key_size=10, multi_probe_level=4)
3187
            search_params = dict(checks=100)
3188

    
3189
            flann = cv2.FlannBasedMatcher(index_params, search_params)
3190

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

    
3194
            count = 0
3195
            # ratio test as per Lowe's paper
3196
            for i in range(len(matches)):
3197
                if len(matches[i]) == 2:
3198
                    m = matches[i][0]
3199
                    n = matches[i][1]
3200
                    if m.distance < 0.85 * n.distance:
3201
                        count = count + 1
3202

    
3203
            matchCount = count
3204
        except Exception as ex:
3205
            from App import App
3206
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
3207
                                                           sys.exc_info()[-1].tb_lineno)
3208
            App.mainWnd().addMessage.emit(MessageType.Error, message)
3209

    
3210
        return matchCount
3211

    
3212
    '''
3213
        @brief      Remake rotated child symbol info
3214
    '''
3215

    
3216
    @staticmethod
3217
    def getRotatedChildInfo(additionalSymbol):
3218
        tempChildInfo = ""
3219
        if additionalSymbol:
3220
            childList = additionalSymbol.split("/")
3221
            for index in range(len(childList)):
3222
                child = childList[index]
3223
                direction = Worker.convertDirectionCodeToValue(child.split(",")[0])
3224
                childName = child.split(",")[1]
3225
                direction = (direction - 1) if direction > 0 else 3
3226
                if index != 0:
3227
                    tempChildInfo = tempChildInfo + "/"
3228
                tempChildInfo = tempChildInfo + Worker.convertValueToDirectionCode(direction) + "," + childName
3229
        return tempChildInfo
3230

    
3231
    @staticmethod
3232
    def calculate_exact_position(area, symGray, symbol, rect, worker, threshold):
3233
        import copy
3234

    
3235
        try:
3236
            tilt = False
3237

    
3238
            symbolThreshold = symbol.getThreshold()
3239
            symbolRotateCount = symbol.getRotationCount()
3240
            detectFlip = symbol.getDetectFlip()
3241
            hasInstrumentLabel = symbol.getHasInstrumentLabel()
3242
            symbolOriginalPoint = symbol.getOriginalPoint()
3243
            symbolConnectionPoint = symbol.getConnectionPoint()
3244
            additionalSymbol = symbol.getAdditionalSymbol()
3245

    
3246
            sow, soh = symGray.shape[::-1]
3247

    
3248
            # symbol is bigger than roi -> detected symbol area is too big
3249
            if rect[3] * rect[4] > sow * soh * 2.5:
3250
                # for single class test
3251
                #pass
3252
                return (None, None, None, None, None, None, None, None)
3253
            # detected symbol area is too small
3254
            elif rect[3] * rect[4] * 1.8 < sow * soh:
3255
                # for single class test
3256
                #pass
3257
                return (None, None, None, None, None, None, None, None)
3258

    
3259
            # get Rotated Original Point
3260
            sow, soh = symGray.shape[::-1]
3261
            offset_x, offset_y = round(rect[3] * 0.5), round(rect[4] * 0.5)#int(max(sow, soh) / 2)
3262

    
3263
            roiItem = Worker.remove_small_objects(area.img, 0, sow * soh * 0.5) if hasInstrumentLabel else area.img.copy()
3264
            x_start = round(rect[1]) - round(offset_x) if round(rect[1]) - round(offset_x) > 0 else 0
3265
            y_start = round(rect[2]) - round(offset_y) if round(rect[2]) - round(offset_y) > 0 else 0
3266
            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
3267
            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
3268
            roiItem = roiItem[y_start:y_max, x_start:x_max]
3269

    
3270
            symGrayOri = copy.copy(symGray)
3271

    
3272
            searchedInfos = [] # score, x, y, angle, flip, originalPoint, connectionPoint, sw, sh
3273

    
3274

    
3275
            steps = [False, True] if detectFlip else [False]
3276
            for flipped in steps:
3277
                if flipped:
3278
                    symGray = symGrayOri
3279
                    symGray = cv2.flip(symGray, 1)
3280

    
3281
                # 45 test
3282
                symGray_0 = copy.copy(symGray)
3283
                if tilt:
3284
                    rect = QRectF(0, 0, symGray.shape[::-1][0], symGray.shape[::-1][1])
3285
                    points = [rect.bottomLeft(), rect.topLeft(), rect.topRight(), rect.bottomRight()]
3286
                    transform2 = QTransform()
3287
                    transform2.rotate(abs(45))
3288
                    points = [transform2.map(point) for point in points]
3289
                    offset_x = int(max([point.x() for point in points]) - min([point.x() for point in points]))
3290
                    offset_y = int(max([point.y() for point in points]) - min([point.y() for point in points]))
3291
                    s_x, s_y = round((offset_x - symGray.shape[::-1][0]) / 2), round((offset_y - symGray.shape[::-1][1]) / 2)
3292
                    mask = np.ones((offset_x, offset_y), dtype=np.uint8) * 255
3293
                    mask[s_y:s_y + symGray.shape[::-1][1], s_x:s_x + symGray.shape[::-1][0]] = symGray
3294

    
3295
                    symGray_45 = mask
3296
                    cX, cY = int(symGray_45.shape[::-1][0] / 2), int(symGray_45.shape[::-1][1] / 2)
3297
                    M = cv2.getRotationMatrix2D((cX, cY), -45, 1.0)
3298
                    symGray_45 = cv2.warpAffine(symGray_45, M, (symGray_45.shape[::-1][0], symGray_45.shape[::-1][1]), borderValue=(255))
3299

    
3300
                symbolRotatedAngle = 0
3301
                for rc in range((symbolRotateCount + 1) * 2):
3302
                #for rc in range(symbolRotateCount + 1):
3303
                    if rc % 2 == 0:
3304
                        symGray = symGray_0
3305
                    else:
3306
                        if tilt:
3307
                            symGray = symGray_45
3308
                        else:
3309
                            symbolRotatedAngle = (symbolRotatedAngle + 45) % 360
3310
                            continue
3311
                        
3312
                    sw, sh = symGray.shape[::-1]
3313

    
3314
                    originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint,
3315
                                                    symbolRotatedAngle, sw, sh, sow, soh, flipped)
3316
                    connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw,
3317
                                                    sh, sow, soh, flipped)
3318

    
3319
                    r_w, r_h = roiItem.shape[::-1]
3320
                    if r_w < sw or r_h < sh:
3321
                        if rc % 2 == 0:
3322
                            symGray_0 = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
3323
                        else:
3324
                            if tilt:
3325
                                symGray_45 = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
3326
                        #symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
3327
                        #symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
3328
                        #cX, cY = originalPoint[0], originalPoint[0]
3329
                        #M = cv2.getRotationMatrix2D((cX, cY), 45, 1.0)
3330
                        #symGray = cv2.warpAffine(symGray, M, (sw, sh))
3331
                        symbolRotatedAngle = (symbolRotatedAngle + 45) % 360
3332
                        continue
3333

    
3334
                    tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED)
3335
                    _, max_val, __, max_loc = cv2.minMaxLoc(tmRes)
3336
                    #maxIndex = tmRes.argmax()
3337
                    #colCount = len(tmRes[0])
3338
                    #col, row = divmod(maxIndex, colCount)
3339

    
3340
                    # for single class test
3341
                    #max_val = 80
3342

    
3343
                    if max_val > threshold:
3344
                        searchedInfos.append([max_val, max_loc[0] + x_start, max_loc[1] + y_start, symbolRotatedAngle, flipped, originalPoint, connectionPoint, sw, sh])
3345

    
3346
                    if rc % 2 == 0:
3347
                        symGray_0 = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
3348
                    else:
3349
                        if tilt:
3350
                            symGray_45 = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
3351
                    #symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
3352
                    #symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
3353
                    #cX, cY = originalPoint[0], originalPoint[0]
3354
                    #M = cv2.getRotationMatrix2D((cX, cY), 45, 1.0)
3355
                    #symGray = cv2.warpAffine(symGray, M, (sw, sh))
3356
                    symbolRotatedAngle = (symbolRotatedAngle + 45) % 360
3357

    
3358
            if searchedInfos:
3359
                searchedInfos = sorted(searchedInfos, key=lambda param: param[0], reverse=True)
3360
                searchedInfo = searchedInfos[0]
3361
                return ((searchedInfo[1] + area.x, searchedInfo[2] + area.y), searchedInfo[3], searchedInfo[4], \
3362
                        searchedInfo[5], searchedInfo[6], searchedInfo[7], searchedInfo[8], searchedInfo[0])
3363
            else:
3364
                return (None, None, None, None, None, None, None, None)
3365

    
3366
        except Exception as ex:
3367
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
3368
                                                            sys.exc_info()[-1].tb_lineno)
3369
            worker.displayLog.emit(MessageType.Error, message)
3370

    
3371
            return (None, None, None, None, None, None, None, None)
3372

    
3373
    @staticmethod
3374
    def detect_symbol_using_server(targetSymbols, listWidget, worker):
3375
        from AppWebService import AppWebService
3376

    
3377
        res = []
3378

    
3379
        app_doc_data = AppDocData.instance()
3380
        project = app_doc_data.getCurrentProject()
3381
        area = app_doc_data.getArea('Drawing')
3382
        
3383
        app_web_service = AppWebService()
3384
        symbols = app_web_service.request_symbol_box(project.name, area.img)
3385

    
3386
        configs = app_doc_data.getConfigs('Engine', 'AI')
3387
        mode = 1
3388
        if configs:
3389
            mode = int(configs[0].value)
3390
        if mode != 1:
3391
            count = 50 if mode == 2 else 100
3392
            for index in reversed(range(len(symbols))):
3393
                if index % count == 0:
3394
                    symbols.pop(index)
3395

    
3396
            time_delay = 10 if mode == 2 else 20
3397
            time_stamp = timeit.default_timer()
3398
            while True:
3399
                if time_stamp - timeit.default_timer() > time_delay:
3400
                    break
3401

    
3402
        # merge symbol box
3403
        overlap_merges = []
3404
        for rect1 in symbols:
3405
            for rect2 in symbols:
3406
                if rect1 is rect2 or rect1[0] != rect2[0]:
3407
                    continue
3408
                rect1_x_gap = int(rect1[3] / 10)
3409
                rect1_y_gap = int(rect1[4] / 10)
3410
                rect2_x_gap = int(rect1[3] / 10)
3411
                rect2_y_gap = int(rect1[4] / 10)
3412

    
3413
                l1, l2 = rect1[1] + rect1_x_gap, rect2[1] + rect2_x_gap
3414
                r1, r2 = rect1[1] + rect1[3] - rect1_x_gap, rect2[1] + rect2[3] - rect2_x_gap
3415
                l_x, s_x = [l1, r1], [l2, r2]
3416
                t1, t2 = rect1[2] + rect1_y_gap, rect2[2] + rect2_y_gap
3417
                b1, b2 = rect1[2] + rect1[4] - rect1_y_gap, rect2[2] + rect2[4] - rect2_y_gap
3418
                l_y, s_y = [t1, b1], [t2, b2]
3419
                if not (max(l_x) < min(s_x) or max(s_x) < min(l_x)) and \
3420
                    not (max(l_y) < min(s_y) or max(s_y) < min(l_y)):
3421
                    inserted = False
3422
                    for merge in overlap_merges:
3423
                        if (rect1 in merge) and (rect2 in merge):
3424
                            inserted = True
3425
                            break
3426
                        elif (rect1 in merge) and (rect2 not in merge):
3427
                            merge.append(rect2)
3428
                            inserted = True
3429
                            break
3430
                        elif (rect2 in merge) and (rect1 not in merge):
3431
                            merge.append(rect1)
3432
                            inserted = True
3433
                            break
3434
                    if not inserted:
3435
                        overlap_merges.append([rect1, rect2])
3436

    
3437
        for merge in overlap_merges:
3438
            for rect in merge:
3439
                if rect in symbols:
3440
                    symbols.remove(rect)
3441
                else:
3442
                    pass
3443
                    #print(str(rect))
3444

    
3445
        for merge in overlap_merges:
3446
            max_x, max_y, min_x, min_y = 0, 0, sys.maxsize, sys.maxsize
3447
            ratio = 0
3448
            for rect in merge:
3449
                if rect[5] > ratio:
3450
                    ratio = rect[5]
3451

    
3452
                if rect[1] < min_x:
3453
                    min_x = rect[1]
3454
                if rect[1] + rect[3] > max_x:
3455
                    max_x = rect[1] + rect[3]
3456
                if rect[2] < min_y:
3457
                    min_y = rect[2]
3458
                if rect[2] + rect[4] > max_y:
3459
                    max_y = rect[2] + rect[4]
3460

    
3461
            rect = [rect[0], min_x, min_y, max_x - min_x, max_y - min_y, ratio]
3462
            symbols.append(rect)
3463
        # up to here
3464

    
3465
        # for single class test
3466
        #for symbol in symbols:
3467
        #    symbol[0] = 'globe'
3468

    
3469
        for targetSymbol in targetSymbols[2]:
3470
            symbolName = targetSymbol.getName()
3471
            symbolType = targetSymbol.getType()
3472
            symbolPath = targetSymbol.getPath()
3473
            symbolThreshold = targetSymbol.getThreshold()
3474
            symbolMinMatchCount = targetSymbol.getMinMatchCount()
3475
            isDetectOnOrigin = targetSymbol.getIsDetectOnOrigin()
3476
            symbolRotateCount = targetSymbol.getRotationCount()
3477
            symbolOcrOption = targetSymbol.getOcrOption()
3478
            isContainChild = targetSymbol.getIsContainChild()
3479
            baseSymbol = targetSymbol.getBaseSymbol()
3480
            additionalSymbol = targetSymbol.getAdditionalSymbol()
3481
            isExceptDetect = targetSymbol.getIsExceptDetect()
3482
            hasInstrumentLabel = targetSymbol.getHasInstrumentLabel()
3483
            text_area = targetSymbol.getText_area()
3484

    
3485
            '''
3486
            # check if symbol file is target or not
3487
            if isExceptDetect == 1:
3488
                item = QListWidgetItem('{} file is not target'.format(symbolName))
3489
                item.setBackground(QColor('green'))
3490
                listWidget.addItem(item)
3491
                continue
3492
            '''
3493

    
3494
            foundSymbolCount = 0
3495

    
3496
            # check if symbol file exists
3497
            if not os.path.isfile(symbolPath):
3498
                item = QListWidgetItem('{} file not found'.format(symbolName))
3499
                item.setBackground(QColor('red'))
3500
                listWidget.addItem(item)
3501
                continue
3502
            # up to here
3503

    
3504
            sym = cv2.imread(symbolPath, 1)
3505
            symGray = Worker.cvtGrayImage(sym)
3506

    
3507
            configs = app_doc_data.getConfigs('Engine', 'Threshold')
3508
            threshold = int(configs[0].value) / 100 if configs else 0.25
3509

    
3510
            for symbol in symbols:
3511
                if symbol[0] == symbolName:
3512
                    searchedItemSp, symbolRotatedAngle, flipped, originalPoint, connectionPoint, sw, sh, score = \
3513
                                Worker.calculate_exact_position(area, symGray, targetSymbol, symbol, worker, threshold)
3514
                    if not searchedItemSp:
3515
                        continue
3516

    
3517
                    #hitRate = symbol[5]
3518
                    hitRate = score
3519

    
3520
                    Worker.addSearchedSymbol(symbolName, symbolType,
3521
                                    searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount,
3522
                                    hitRate, symbolRotatedAngle,
3523
                                    isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
3524
                                    isContainChild,
3525
                                    originalPoint, connectionPoint, baseSymbol, additionalSymbol,
3526
                                    isExceptDetect,
3527
                                    detectFlip=1 if flipped else 0,
3528
                                    hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
3529

    
3530
        return symbols
3531
    '''
3532
        @brief   detect symbols on PID
3533
        @history humkyung 2018.06.08 add parameteres for signal
3534
    '''
3535
    @staticmethod
3536
    def detectSymbolsOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal):
3537
        res = []
3538

    
3539
        if type(targetSymbols) is list:
3540
            for detailTarget in targetSymbols:
3541
                res.extend(Worker.detectSymbolOnPid(mainRes, detailTarget, listWidget, updateProgressSignal))
3542
        else:
3543
            res = Worker.detectSymbolOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal)
3544

    
3545
        return res
3546

    
3547
    @staticmethod
3548
    def convertDirectionCodeToValue(directionCode):
3549
        if directionCode == "UP":
3550
            return 0
3551
        elif directionCode == "RIGHT":
3552
            return 1
3553
        elif directionCode == "DOWN":
3554
            return 2
3555
        elif directionCode == "LEFT":
3556
            return 3
3557
        else:
3558
            return -1
3559

    
3560
    @staticmethod
3561
    def convertValueToDirectionCode(value):
3562
        if value == 0:
3563
            return "UP"
3564
        elif value == 1:
3565
            return "RIGHT"
3566
        elif value == 2:
3567
            return "DOWN"
3568
        elif value == 3:
3569
            return "LEFT"
3570
        else:
3571
            return "NONE"
3572

    
3573
    @staticmethod
3574
    def drawFoundSymbolsOnCanvas(drawingPath, symbols, textInfos, listWidget):
3575
        """draw found symbols and texts to image"""
3576

    
3577
        global src
3578
        global ocrCompletedSrc
3579
        global canvas
3580

    
3581
        app_doc_data = AppDocData.instance()
3582
        canvas = np.zeros(app_doc_data.imgSrc.shape, np.uint8)
3583
        canvas[::] = 255
3584

    
3585
        try:
3586
            project = app_doc_data.getCurrentProject()
3587

    
3588
            for symbol in symbols:
3589
                Worker.drawFoundSymbols(symbol, listWidget)
3590

    
3591
            for text in textInfos:
3592
                left = text.getX()
3593
                top = text.getY()
3594
                right = text.getX() + text.getW()
3595
                bottom = text.getY() + text.getH()
3596

    
3597
                canvas[top:bottom, left:right] = app_doc_data.imgSrc[top:bottom, left:right]
3598

    
3599
            #cv2.imwrite(os.path.join(project.getTempPath(), "FOUND_" + os.path.basename(drawingPath)), canvas) for debug
3600
        except Exception as ex:
3601
            from App import App
3602
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
3603
                                                           sys.exc_info()[-1].tb_lineno)
3604
            App.mainWnd().addMessage.emit(MessageType.Error, message)
3605

    
3606
    '''
3607
        @history    2018.04.27  Jeongwoo    Remove Tesseract Log on listWidget
3608
                    2018.05.04  Jeongwoo    Change method to OCR with tesseract_ocr_module.py
3609
                    2018.05.09  Jeongwoo    Add global variable textInfoList, Remove text in symbol and Add tesseract result text
3610
                    2018.05.10  Jeongwoo    Remove not used if-statement
3611
                    2018.06.19  Jeongwoo    When detect text in symbol, use getTextAreaInfo() and Tesseract
3612
                    2018.06.21  Jeongwoo    Add if-statement for way to detect text by Type A
3613
    '''
3614

    
3615
    @staticmethod
3616
    def drawFoundSymbols(symbol, listWidget):
3617
        global src
3618
        global canvas
3619
        #global WHITE_LIST_CHARS
3620
        global searchedSymbolList
3621
        global textInfoList
3622

    
3623
        # symbolId = symbol.getId()
3624
        symbolPath = symbol.getPath()
3625
        symbolSp = symbol.getSp()
3626
        symbolRotatedAngle = symbol.getRotatedAngle()
3627

    
3628
        symImg = cv2.cvtColor(cv2.imread(symbolPath, 1), cv2.COLOR_BGR2GRAY)
3629
        if symbol.getDetectFlip() is 1:
3630
            symImg = cv2.flip(symImg, 1)
3631
        for i in range(symbolRotatedAngle // 90):
3632
            symImg = cv2.rotate(symImg, cv2.ROTATE_90_COUNTERCLOCKWISE)
3633

    
3634
        w, h = symImg.shape[::-1]
3635
        canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w] = cv2.bitwise_and(
3636
            canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w], symImg)
3637

    
3638
    @staticmethod
3639
    def remove_equipment_package(scene, area):
3640
        """ remove equipment package area from drawing image """
3641
        for item in scene.items():
3642
            if issubclass(type(item), QEngineeringVendorItem) and item.pack_type =='Equipment Package':
3643
                points = []
3644
                for conn in item.connectors:
3645
                    points.append([round(conn.center()[0] - area.x), round(conn.center()[1] - area.y)])
3646
                
3647
                points = np.array(points, np.int32)
3648
                cv2.fillConvexPoly(area.img, points, 255)
3649

    
3650
    @staticmethod
3651
    def remove_detected_symbol_image(sym, imgSrc, lock=True):
3652
        """remove detected symbol image from drawing image"""
3653
        if lock:
3654
            global threadLock
3655

    
3656
        try:
3657
            path = sym.getPath()
3658
            sp = (int(sym.getSp()[0]), int(sym.getSp()[1]))
3659
            sw = int(sym.getWidth())
3660
            sh = int(sym.getHeight())
3661
            angle = int(sym.getRotatedAngle())
3662
            # get symbol image
3663
            sym_img = cv2.imread(path)
3664
            sym_img = cv2.cvtColor(sym_img, cv2.COLOR_BGR2GRAY)
3665
            # symImg = cv2.threshold(Worker.cvtGrayImage(symImg), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
3666
            if sym.getDetectFlip() is 1:
3667
                sym_img = cv2.flip(sym_img, 1)
3668

    
3669
            for i in range(angle // 90):
3670
                sym_img = cv2.rotate(sym_img, cv2.ROTATE_90_COUNTERCLOCKWISE)
3671
            # up to here
3672

    
3673
            if lock:
3674
                threadLock.acquire()
3675
            temp = imgSrc[sp[1]:sp[1] + sh, sp[0]:sp[0] + sw]
3676
            sym_img = cv2.erode(sym_img, np.ones((5, 5), np.uint8))
3677
            mask = cv2.bitwise_or(temp, sym_img)
3678
            imgXOR = cv2.bitwise_xor(temp, mask)
3679
            imgSrc[sp[1]:sp[1] + sh, sp[0]:sp[0] + sw] = cv2.bitwise_not(imgXOR)
3680
        except Exception as ex:
3681
            from App import App
3682
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
3683
                                                           sys.exc_info()[-1].tb_lineno)
3684
            App.mainWnd().addMessage.emit(MessageType.Error, message)
3685
        finally:
3686
            if lock:
3687
                threadLock.release()
3688

    
3689
    '''
3690
        @brief  get difference between given original and recognized image
3691
        @author humkyung
3692
        @date   2018.06.11
3693
    '''
3694

    
3695
    @staticmethod
3696
    def getDifference(orgImagePath, recImagePath):
3697
        import re
3698

    
3699
        global ocrCompletedSrc
3700
        global textInfoList
3701

    
3702
        try:
3703
            app_doc_data = AppDocData.instance()
3704
            imgOriginal = app_doc_data.imgSrc
3705

    
3706
            # remove not drawing area
3707
            configs = app_doc_data.getConfigs('{} Equipment Desc Area'.format(app_doc_data.imgName))
3708
            for config in configs:
3709
                found = re.findall('\\d+', config.value)
3710
                if len(found) == 4:
3711
                    cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
3712
                                    (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
3713

    
3714
            configs = app_doc_data.getConfigs('{} Typical Area'.format(app_doc_data.imgName))
3715
            for config in configs:
3716
                found = re.findall('\\d+', config.value)
3717
                if len(found) == 4:
3718
                    cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
3719
                                    (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
3720

    
3721
            noteArea = app_doc_data.getArea('Note')
3722
            if noteArea is not None:
3723
                noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height),
3724
                                round(noteArea.x):round(noteArea.x + noteArea.width)].copy()
3725
                cv2.rectangle(imgOriginal, (round(noteArea.x), round(noteArea.y)),
3726
                                (round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1)
3727
            # up to here
3728

    
3729
            """
3730
            if os.path.isfile(orgImagePath) and os.path.isfile(recImagePath):
3731
                imgOriginal = \
3732
                    cv2.threshold(Worker.cvtGrayImage(cv2.imread(orgImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1]
3733

3734
                configs = app_doc_data.getConfigs('Filter', 'DilateSize')
3735
                if 1 == len(configs) and int(configs[0].value) is not 0:
3736
                    size = int(configs[0].value)
3737
                    kernel = np.ones((size, size), np.uint8)
3738
                    imgOriginal = cv2.erode(imgOriginal, kernel, iterations=1)
3739

3740
                # remove not drawing area
3741
                configs = app_doc_data.getConfigs('{} Equipment Desc Area'.format(app_doc_data.imgName))
3742
                for config in configs:
3743
                    found = re.findall('\\d+', config.value)
3744
                    if len(found) == 4:
3745
                        cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
3746
                                      (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
3747

3748
                configs = app_doc_data.getConfigs('{} Typical Area'.format(app_doc_data.imgName))
3749
                for config in configs:
3750
                    found = re.findall('\\d+', config.value)
3751
                    if len(found) == 4:
3752
                        cv2.rectangle(imgOriginal, (int(found[0]), int(found[1])),
3753
                                      (int(found[0]) + int(found[2]), int(found[1]) + int(found[3])), 255, -1)
3754

3755
                noteArea = app_doc_data.getArea('Note')
3756
                if noteArea is not None:
3757
                    noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height),
3758
                                   round(noteArea.x):round(noteArea.x + noteArea.width)].copy()
3759
                    cv2.rectangle(imgOriginal, (round(noteArea.x), round(noteArea.y)),
3760
                                  (round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1)
3761
                # up to here
3762

3763
                imgRecognized = \
3764
                    cv2.threshold(Worker.cvtGrayImage(cv2.imread(recImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1]
3765

3766
                imgDiff = np.ones(imgOriginal.shape, np.uint8) * 255
3767

3768
                area = app_doc_data.getArea('Drawing')
3769
                if area is not None:
3770
                    x = round(area.x)
3771
                    y = round(area.y)
3772
                    width = round(area.width)
3773
                    height = round(area.height)
3774
                    imgNotOper = cv2.bitwise_not(imgRecognized[y:y + height, x:x + width])
3775
                    imgDiff[y:y + height, x:x + width] = cv2.bitwise_xor(imgOriginal[y:y + height, x:x + width],
3776
                                                                         imgNotOper)
3777

3778
                # remove noise
3779
                imgDiff = cv2.dilate(imgDiff, np.ones((2, 2), np.uint8))
3780

3781
                project = app_doc_data.getCurrentProject()
3782
                cv2.imwrite(os.path.join(project.getTempPath(), "DIFF_" + os.path.basename(orgImagePath)), imgDiff)
3783
            """
3784
        except Exception as ex:
3785
            from App import App
3786
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
3787
                                                           sys.exc_info()[-1].tb_lineno)
3788
            App.mainWnd().addMessage.emit(MessageType.Error, message)
3789

    
3790

    
3791
'''
3792
    @history    2018.05.25  Jeongwoo    Add pyqtSignal(svgItemClicked, itemRemoved)
3793
'''
3794

    
3795

    
3796
class QRecognitionDialog(QDialog):
3797
    svgItemClicked = pyqtSignal(SymbolSvgItem)
3798
    itemRemoved = pyqtSignal(QGraphicsItem)
3799
    unBlockEvent = pyqtSignal()
3800

    
3801
    '''
3802
        @history    2018.05.25  Jeongwoo    Add parameter and initialize / Connect recognizeButton signal and slot
3803
                    2018.05.29  Jeongwoo    Chnage parameter(graphicsView → parent) and Get graphicsView from parent
3804
    '''
3805

    
3806
    def __init__(self, parent, drawings):  # Parent is MainWindow
3807
        from AppDocData import AppDocData
3808

    
3809
        QDialog.__init__(self, parent)
3810

    
3811
        self.parent = parent
3812
        self._scene = QGraphicsScene()
3813
        self._text_scene = QGraphicsScene()
3814
        self._line_scene = QGraphicsScene()
3815
        self.drawings = drawings
3816
        self.xmlPath = None
3817
        self.ui = Recognition_UI.Ui_Recognition()
3818
        self.ui.setupUi(self)
3819
        self.isTreated = False
3820

    
3821
        self.ui.buttonBox.setEnabled(True)
3822
        self.ui.listWidget.model().rowsInserted.connect(self.rowInserted)
3823
        self.ui.recognizeButton.clicked.connect(self.recognizeButtonClicked)
3824
        self.ui.lineCheckBox.stateChanged.connect(self.checkBoxChanged)
3825
        self.ui.checkBoxSymbol.stateChanged.connect(self.checkBoxChanged)
3826
        self.ui.checkBoxText.stateChanged.connect(self.checkBoxChanged)
3827
        self.ui.checkBoxTraining.stateChanged.connect(self.checkBoxChanged)
3828
        self.isAccepted = False
3829

    
3830
        appDocData = AppDocData.instance()
3831
        configs = appDocData.getAppConfigs('app', 'mode')
3832
        if configs and 1 == len(configs) and 'advanced' == configs[0].value:
3833
            pass
3834
        else:
3835
            self.ui.checkBoxTraining.setVisible(False)
3836

    
3837
        if False:
3838
            self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
3839
            self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
3840
            self.ui.checkBoxText.setCheckState(Qt.Unchecked)
3841
        else:
3842
            self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
3843
            self.ui.lineCheckBox.setCheckState(Qt.Checked)
3844
            self.ui.checkBoxText.setCheckState(Qt.Checked)
3845

    
3846
        if len(self.drawings) != 1:
3847
            self.ui.checkBoxOCRUnknown.hide()
3848

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

    
3851
    def checkBoxChanged(self, checkState):
3852
        '''
3853
        @brief      line and text cannot be reocognized alone
3854
        @author     euisung
3855
        @date       2019.05.14
3856
        '''
3857
        if self.ui.checkBoxTraining.isChecked():
3858
            self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
3859
            self.ui.checkBoxText.setCheckState(Qt.Unchecked)
3860
            self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
3861
            self.ui.lineCheckBox.setEnabled(False)
3862
            self.ui.checkBoxText.setEnabled(False)
3863
            self.ui.checkBoxSymbol.setEnabled(False)
3864
            self.ui.recognizeButton.setText('Extract')
3865
            return
3866
        else:
3867
            self.ui.lineCheckBox.setEnabled(True)
3868
            self.ui.checkBoxText.setEnabled(True)
3869
            self.ui.checkBoxSymbol.setEnabled(True)
3870
            self.ui.recognizeButton.setText('Recognize')
3871

    
3872
        if checkState is int(Qt.Checked):
3873
            if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked():
3874
                self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
3875
            elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked():
3876
                self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
3877
        elif checkState is int(Qt.Unchecked):
3878
            if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked():
3879
                self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
3880
            elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked():
3881
                self.ui.checkBoxText.setCheckState(Qt.Unchecked)
3882

    
3883
        if self.ui.checkBoxSymbol.isChecked() and self.ui.checkBoxText.isChecked() and self.ui.lineCheckBox.isChecked() and len(self.drawings) == 1:
3884
            self.ui.checkBoxOCRUnknown.setHidden(False)
3885
        else:
3886
            self.ui.checkBoxOCRUnknown.setHidden(True)
3887
            self.ui.checkBoxOCRUnknown.setCheckState(Qt.Unchecked)
3888

    
3889
    '''
3890
        @brief      QListWidget Row Inserted Listener
3891
                    Whenever row inserted, scroll to bottom
3892
        @author     Jeongwoo
3893
        @date       18.04.12
3894
    '''
3895

    
3896
    def rowInserted(self, item):
3897
        self.ui.listWidget.scrollToBottom()
3898

    
3899
    def accept(self):
3900
        self.isAccepted = True
3901
        QDialog.accept(self)
3902

    
3903
    def recognizeButtonClicked(self, event):
3904
        """
3905
        @brief      start recognization
3906
        @author     humkyung
3907
        @history    humkyung 2018.10.05 clear imgSrc before recognizing
3908
        """
3909
        if self.ui.checkBoxSymbol.isChecked():  # or self.ui.checkBoxText.isChecked() or self.ui.lineCheckBox.isChecked():
3910
            appDocData = AppDocData.instance()
3911
            appDocData.imgSrc = None
3912
            area = appDocData.getArea('Drawing')
3913
            if area is None:
3914
                QMessageBox.about(self, self.tr("Notice"), self.tr('Please select drawing area.'))
3915
                return
3916

    
3917
            self.ui.recognizeButton.setEnabled(False)
3918
            self.ui.buttonBox.setEnabled(False)
3919
            self.ui.progressBar.setValue(0)
3920
            self.ui.listWidget.addItem("Initializing...")
3921
            self.startThread()
3922

    
3923
            self.isTreated = True
3924

    
3925
    '''
3926
        @brief  add item to list widget
3927
        @author humkyung
3928
        @date   2018.06.19
3929
    '''
3930

    
3931
    def addListItem(self, item):
3932
        self.ui.listWidget.addItem(item)
3933

    
3934
    '''
3935
        @brief  update progressbar with given value
3936
        @author humkyung
3937
        @date   2018.06.08
3938
    '''
3939

    
3940
    def updateProgress(self, maxValue, image_path):
3941
        self.ui.progressBar.setMaximum(maxValue)
3942
        self.ui.progressBar.setValue(self.ui.progressBar.value() + 1)
3943

    
3944
        if image_path is not None and os.path.isfile(image_path):
3945
            self.ui.labelImage.setPixmap(QPixmap(image_path))
3946
        elif image_path is not None:
3947
            self.ui.labelImage.setText(image_path)
3948

    
3949
    def updateBatchProgress(self, maxValue, weight):
3950
        """update batch progressbar"""
3951
        self.ui.progressBarBatch.setMaximum(maxValue * 5)
3952
        value = self.ui.progressBarBatch.value() + weight
3953
        self.ui.progressBarBatch.setValue(value)
3954
        self.ui.progressBarBatch.setFormat('{}/{}'.format(str(int(value / 5)), str(maxValue)))
3955

    
3956
    '''
3957
        @brief      display title
3958
        @author     humkyung
3959
        @date       2018.08.20
3960
    '''
3961

    
3962
    def displayTitle(self, title):
3963
        self.ui.labelTitle.setText(title)
3964

    
3965
    def startThread(self):
3966
        """start thread"""
3967
        from PyQt5 import QtWidgets
3968
        from App import App
3969

    
3970
        self.ui.buttonBox.setDisabled(True)
3971

    
3972
        self.mutex = QMutex()
3973
        self.cond = QWaitCondition()
3974

    
3975
        # 1 - create Worker and Thread inside the Form
3976
        self.obj = Worker(self.mutex, self.cond)  # no parent!
3977
        self.obj.symbol_time = None
3978
        self.obj.text_time = None
3979
        self.obj.drawings = self.drawings
3980
        self.obj.listWidget = self.ui.listWidget
3981
        self.obj.scene = self._scene
3982
        self.obj.text_scene = self._text_scene
3983
        self.obj.line_scene = self._line_scene
3984
        self.obj.scene._end = False  # for waiting each drawing finished
3985
        self.obj.isSymbolChecked = self.ui.checkBoxSymbol.isChecked()
3986
        self.obj.isTextChecked = self.ui.checkBoxText.isChecked()
3987
        self.obj.isLineChecked = self.ui.lineCheckBox.isChecked()
3988
        self.obj.isTrainingChecked = self.ui.checkBoxTraining.isChecked()
3989
        self.thread = QThread()  # no parent!
3990

    
3991
        # 2 - Move the Worker object to the Thread object
3992
        self.obj.moveToThread(self.thread)
3993

    
3994
        # 3 - Connect Worker Signals to the Thread slots
3995
        self.obj.finished.connect(self.thread.quit)
3996
        self.obj.displayMessage.connect(self.addListItem)
3997
        self.obj.updateProgress.connect(self.updateProgress)
3998
        self.obj.updateBatchProgress.connect(self.updateBatchProgress)
3999
        self.obj.displayLog.connect(App.mainWnd().addMessage)
4000
        self.obj.displayTitle.connect(self.displayTitle)
4001
        self.obj.add_detected_items_to_scene.connect(self.add_detected_items_to_scene)
4002
        self.obj.save_scene.connect(self.save_scene)
4003
        self.obj.preset_execute.connect(self.preset_execute)
4004
        self.obj.item_remove.connect(self.item_remove)
4005
        self.obj.add_predata_to_scene.connect(self.add_predata_to_scene)
4006
        self.obj.clear_scene.connect(self.clear_scene)
4007

    
4008
        # 4 - Connect Thread started signal to Worker operational slot method
4009
        self.thread.started.connect(self.obj.procCounter)
4010

    
4011
        # 5 - Thread finished signal will close the app if you want!
4012
        self.thread.finished.connect(self.dlgExit)
4013

    
4014
        # 6 - Start the thread
4015
        self.thread.start()
4016

    
4017
        self.tmStart = timeit.default_timer()
4018

    
4019
    '''
4020
        @brief set buttonbox's enabled flag
4021
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
4022
                    2018.06.14  Jeongwoo    Change sentence order
4023
                    2018.11.26  euisung     add drawing part
4024
                    2018.11.26  euising     move save and unknown part into executerecognition
4025
    '''
4026

    
4027
    def dlgExit(self):
4028
        import timeit
4029

    
4030
        try:
4031
            self.ui.buttonBox.setEnabled(True)
4032

    
4033
            self.ui.progressBar.setValue(self.ui.progressBar.maximum())
4034
        except Exception as ex:
4035
            from App import App
4036
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
4037
                                                           sys.exc_info()[-1].tb_lineno)
4038
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4039
        finally:
4040
            self.tmStop = timeit.default_timer()
4041
            seconds = self.tmStop - self.tmStart
4042
            self.ui.listWidget.addItem("\nRunning Time(total) : {} min".format(str(round(seconds / 60, 1))))
4043
            if self.obj.symbol_time:
4044
                self.ui.listWidget.addItem("Running Time(symbol) : {} min".format(str(round(self.obj.symbol_time / 60, 1))))
4045
            if self.obj.text_time:
4046
                self.ui.listWidget.addItem("Running Time(text) : {} min".format(str(round(self.obj.text_time / 60, 1))))
4047
            self.ui.listWidget.addItem("\n")
4048

    
4049
    '''
4050
        @history    2018.05.29  Jeongwoo    Call parent's method
4051
                    2018.05.30  Jeongwoo    Change method name
4052
                    2018.06.09  humkyung    set progressbar value to maximum
4053
                    2018.11.12  euisung     add title block properties
4054
                    2018.11.29  euisung     no more used
4055
    '''
4056

    
4057
    def drawDetectedItems(self, symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList, loop):
4058
        try:
4059
            self.ui.progressBar.setValue(self.ui.progressBar.maximum())
4060
            self.parent.drawDetectedItems(symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList)
4061
        finally:
4062
            loop.quit()
4063

    
4064
    '''
4065
        @brief      draw detected lines
4066
        @author     humkyung
4067
        @date       2018.08.23
4068
        @history    2018.11.27  euisung     no more used
4069
    '''
4070

    
4071
    def drawDetectedLines(self, lineList, loop):
4072
        try:
4073
            self.parent.drawDetectedLines(lineList, self.obj)
4074
        finally:
4075
            loop.quit()
4076

    
4077
    '''
4078
        @brief      draw detected lines
4079
        @author     euisung
4080
        @date       2018.11.27
4081
        @history    2018.11.27  euisung     no more used
4082
    '''
4083

    
4084
    def drawUnknownItems(self, path, loop):
4085
        try:
4086
            self.parent.drawUnknownItems(path)
4087
        finally:
4088
            loop.quit()
4089

    
4090
    def add_predata_to_scene(self, drawing, scene, symbol: bool, text: bool, line: bool, unknown: bool, package: bool) \
4091
            -> None:
4092
        """add predata to scene"""
4093
        from LoadCommand import LoadCommand
4094
        from App import App
4095

    
4096
        try:
4097
            cmd = LoadCommand()
4098
            cmd.display_message.connect(App.mainWnd().onAddMessage)
4099
            cmd.execute((drawing, scene), symbol=symbol, text=text, line=line, unknown=unknown,
4100
                        package=package, update=False)
4101
        except Exception as ex:
4102
            from App import App
4103
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
4104
                      f"{sys.exc_info()[-1].tb_lineno}"
4105
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4106
        finally:
4107
            self.cond.wakeAll()
4108

    
4109
    def clear_scene(self, scene1, scene2, scene3) -> None:
4110
        """clear scenes"""
4111

    
4112
        try:
4113
            scene1.clear()
4114
            scene2.clear()
4115
            scene3.clear()
4116

    
4117
        except Exception as ex:
4118
            from App import App
4119
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
4120
                      f"{sys.exc_info()[-1].tb_lineno}"
4121
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4122
        finally:
4123
            self.cond.wakeAll()
4124

    
4125
    def item_remove(self, items):
4126
        """ remove useless items """
4127
        from App import App
4128

    
4129
        try:
4130
            for item in items:
4131
                App.mainWnd().itemRemoved(item)
4132

    
4133
        except Exception as ex:
4134
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
4135
                                                           sys.exc_info()[-1].tb_lineno)
4136
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4137
        finally:
4138
            self.cond.wakeAll()
4139
    
4140
    def preset_execute(self, scene, find_symbol, replace_symbol, replace_action, condition):
4141
        """ run preset """
4142
        from App import App
4143

    
4144
        try:
4145
            from ReplaceInsertCommand import ReplaceInsertCommand
4146

    
4147
            cmd = ReplaceInsertCommand()
4148
            cmd.display_message.connect(App.mainWnd().onAddMessage)
4149
            cmd.execute(scene, find_symbol, replace_symbol, replace_action, condition)
4150
        except Exception as ex:
4151
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
4152
                      f"{sys.exc_info()[-1].tb_lineno}"
4153
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4154
        finally:
4155
            self.cond.wakeAll()
4156

    
4157
    def save_scene(self, scene):
4158
        """ save scene """
4159
        from App import App
4160

    
4161
        try:
4162
            from SaveWorkCommand import SaveWorkCommand
4163

    
4164
            save = SaveWorkCommand(scene)
4165
            save.run()
4166
            #SaveWorkCommand.save_to_database()
4167
            #SaveWorkCommand.save_to_xml()
4168
        except Exception as ex:
4169
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
4170
                                                           sys.exc_info()[-1].tb_lineno)
4171
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4172
        finally:
4173
            self.cond.wakeAll()
4174

    
4175
    def add_detected_items_to_scene(self, scene, flanges) -> None:
4176
        """add detected items to scene"""
4177
        from App import App
4178

    
4179
        app_doc_data = AppDocData.instance()
4180

    
4181
        try:
4182
            for item in scene.items():
4183
                if issubclass(type(item), QEngineeringVendorItem):
4184
                    app_doc_data.allItems.append(item)
4185

    
4186
            # symbol
4187
            for symbol in app_doc_data.symbols:
4188
                if issubclass(type(symbol), SymbolSvgItem):
4189
                    symbol.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
4190
                    symbol.addSvgItemToScene(scene)
4191
                else:
4192
                    scene.addItem(symbol)
4193

    
4194
            # text
4195
            for text in app_doc_data.texts:
4196
                text.addTextItemToScene(scene)
4197

    
4198
            #for lineNo in app_doc_data.tracerLineNos:
4199
            #    lineNo.addTextItemToScene(scene)
4200

    
4201
            # remove lines which is located inside symbol
4202
            for symbol in app_doc_data.symbols:
4203
                rect = symbol.sceneBoundingRect()
4204
                rect.adjust(int(rect.width() / -10), int(rect.height() / -10), int(rect.width() / 10), int(rect.height() / 10))
4205
                matches = [line for line in app_doc_data.lines if rect.contains(line.line().p1()) and
4206
                           rect.contains(line.line().p2())]# and not line.has_connection]
4207
                for line in matches:
4208
                    app_doc_data.allItems.remove(line)
4209
                    app_doc_data.lines.remove(line)
4210
            # up to here
4211

    
4212
            for line in app_doc_data.lines:
4213
                scene.addItem(line)
4214
                # line.transfer.onRemoved.connect(self.itemRemoved)
4215
                for conn in line.connectors:
4216
                    conn.transfer.onPosChanged.connect(line.onConnectorPosChaned)
4217

    
4218
            # insert flange
4219
            configs = app_doc_data.getConfigs('Project', 'Operation')
4220
            instrument = int(configs[0].value) if configs else 1
4221
            if instrument == 1:
4222
                configs = app_doc_data.getConfigs('Default', 'Flange')
4223
                flange_name = configs[0].value if 1 == len(configs) else 'flange'
4224
                for flange in flanges[0]:
4225
                    svg = QtImageViewer.createSymbolObject(flange_name)
4226
                    if svg:
4227
                        QtImageViewer.matchSymbolToLine(scene, svg, QPointF(flange[0], flange[1]), strict=True, auto=True)
4228

    
4229
                configs = app_doc_data.getConfigs('Default', 'Blind')
4230
                flange_name = configs[0].value if 1 == len(configs) else 'blind flange'
4231
                for flange in flanges[1]:
4232
                    svg = QtImageViewer.createSymbolObject(flange_name)
4233
                    if svg:
4234
                        QtImageViewer.matchSymbolToLine(scene, svg, QPointF(flange[0], flange[1]), strict=False, auto=True)
4235

    
4236
                for unknown in app_doc_data.unknowns + app_doc_data.lineIndicators:
4237
                    scene.addItem(unknown)
4238

    
4239
        except Exception as ex:
4240
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
4241
                                                           sys.exc_info()[-1].tb_lineno)
4242
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4243
        finally:
4244
            self.cond.wakeAll()
4245
            scene._end = True
클립보드 이미지 추가 (최대 크기: 500 MB)