프로젝트

일반

사용자정보

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

hytos / DTI_PID / DTI_PID / RecognitionDialog.py @ bab64619

이력 | 보기 | 이력해설 | 다운로드 (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:
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
        except Exception as ex:
1552
            if worker:
1553
                message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
1554
                                                               sys.exc_info()[-1].tb_lineno)
1555
                worker.displayLog.emit(MessageType.Error, message)
1556
            else:
1557
                from App import App
1558
                from AppDocData import MessageType
1559

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

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

    
1568
        import math
1569
        from EngineeringAbstractItem import QEngineeringAbstractItem
1570

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

    
1579
            new_lines = []
1580

    
1581
            verticals = []
1582
            horizontals = []
1583

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

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

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

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

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

    
1634
                    end_point = connector.center()
1635

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

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

    
1645
            app_doc_data.lines.extend(new_lines)
1646
            app_doc_data.allItems.extend(new_lines)
1647

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

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

    
1661
    @staticmethod
1662
    def changeVisualLineType(all_lines, worker):
1663
        """ change line type by using visual data"""
1664

    
1665
        from LineDetector import LineDetector
1666
        from EngineeringAbstractItem import QEngineeringAbstractItem
1667

    
1668
        try:
1669
            app_doc_data = AppDocData.instance()
1670
            image = app_doc_data.activeDrawing.image_origin
1671
            imgNot = np.ones(image.shape, np.uint8) * 255
1672
            image = cv2.bitwise_xor(image, imgNot)
1673

    
1674
            #electric = [137, [1,1,1,1,1,1,1,1,1], sys.maxsize]
1675
            #software = [187, [0.948,1.081,0.932,1.081,0.932,1.068,0.932,1.081,0.929], sys.maxsize]
1676
            #line_patterns = {'Electric':electric, 'Software':software }
1677
            line_patterns = []
1678
            line_shapes = []
1679

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

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

    
1692
                    line_img = cv2.cvtColor(cv2.imread(line_path), cv2.COLOR_BGR2GRAY)
1693
                    imgNot = np.ones(line_img.shape, np.uint8) * 255
1694
                    line_img = cv2.bitwise_xor(line_img, imgNot)
1695

    
1696
                    contours, _ = cv2.findContours(line_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
1697

    
1698
                    line_thickness = 0
1699

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

    
1712
                        thickness = min(max_y - min_y, max_x - min_x)
1713
                        if line_thickness < thickness:
1714
                            line_thickness = thickness
1715

    
1716
                        line_shapes.append([line_img[min_y:max_y, min_x:max_x], sys.maxsize, line.getThreshold(), line.getBaseSymbol()])
1717
                        continue
1718

    
1719
                    i = 1 if line_img.shape[0] > line_img.shape[1] else 0
1720
                    boundingBoxes = [cv2.boundingRect(contour) for contour in contours]
1721
                    (contours, boundingBoxes) = zip(*sorted(zip(contours, boundingBoxes), key=lambda b:b[1][i], reverse=False))
1722

    
1723
                    avg_area = sum([cv2.contourArea(contour) for contour in contours]) / len(contours)
1724
                    ratio_area = [cv2.contourArea(contour) / avg_area for contour in contours]
1725
                    if avg_area > max_avg_area:
1726
                        max_avg_area = avg_area
1727

    
1728
                    line_patterns.append([avg_area, ratio_area, sys.maxsize, line.getThreshold(), line.getBaseSymbol()])
1729
            else:
1730
                return
1731

    
1732
            lines_found = []
1733
            lines_shape = []
1734

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

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

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

    
1765
                vertical = LineDetector.is_vertical([line.start_point()[0], line.start_point()[1], line.end_point()[0], line.end_point()[1]])
1766

    
1767
                i = 1 if vertical else 0
1768
                boundingBoxes = [cv2.boundingRect(contour) for contour in contours]
1769
                (contours, boundingBoxes) = zip(*sorted(zip(contours, boundingBoxes), key=lambda b:b[1][i], reverse=False))
1770

    
1771
                avg_area = sum([cv2.contourArea(contour) for contour in contours]) / len(contours)
1772
                ratio_area = [cv2.contourArea(contour) / avg_area for contour in contours]
1773

    
1774
                for line_pattern in line_patterns:
1775
                    line_type = line_pattern[-1]
1776

    
1777
                    line_pattern[2] = sys.maxsize
1778
                    ratio_area_cal = [ratio * avg_area / line_pattern[0] for ratio in ratio_area]
1779
                    long_line = ratio_area_cal if len(ratio_area_cal) > len(line_pattern[1]) else line_pattern[1]
1780
                    short_line = line_pattern[1] if len(ratio_area_cal) > len(line_pattern[1]) else ratio_area_cal
1781

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

    
1791
                    line_pattern[2] = min_error
1792

    
1793
                line_type_founds = sorted([(line_pattern[-1], line_pattern[2]) for line_pattern in line_patterns], key=lambda error:error[1])
1794
                if line_type_founds:
1795
                    lines_found.append([line, line_type_founds[0]])
1796

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

    
1830
                    shape = line_shape[0].copy()
1831
                    line_shape[1] = sys.maxsize
1832

    
1833
                    big = shape if max(shape.shape) > max(image_line.shape) else image_line
1834
                    small = image_line if max(shape.shape) > max(image_line.shape) else shape
1835

    
1836
                    if big.shape[0] == max(max(big.shape), max(small.shape)):
1837
                        mask = np.zeros([big.shape[0] + 40, max(big.shape[1], max(small.shape)) + 40], np.uint8)
1838
                    else:
1839
                        mask = np.zeros([max(big.shape[0], max(small.shape)) + 40, big.shape[1] + 40], np.uint8)
1840

    
1841
                    mask[20:big.shape[0] + 20, 20:big.shape[1] + 20] = big
1842
                    big = mask
1843

    
1844
                    searchedInfos = []
1845
                    steps = [False, True]
1846
                    for flipped in steps:
1847
                        symGray = small.copy()
1848
                        if flipped:
1849
                            symGray = cv2.flip(symGray, 1)
1850

    
1851
                        symbolRotatedAngle = 0
1852
                        for rc in range(4):
1853
                            sw, sh = symGray.shape[::-1]
1854

    
1855
                            r_w, r_h = big.shape[::-1]
1856
                            if r_w < sw or r_h < sh:
1857
                                symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
1858
                                symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
1859
                                continue
1860

    
1861
                            tmRes = cv2.matchTemplate(big, symGray, cv2.TM_CCOEFF_NORMED)
1862
                            _, max_val, __, max_loc = cv2.minMaxLoc(tmRes)
1863

    
1864
                            if max_val > line_shape[-2]:
1865
                                searchedInfos.append(1 - max_val)
1866

    
1867
                            symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
1868
                            symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
1869

    
1870
                    if searchedInfos:
1871
                        line_shape[1] = sorted(searchedInfos)[0]
1872

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

    
1888
            line_runs = []
1889
            for line_found in lines_found:
1890
                inserted = False
1891
                for line_run in line_runs:
1892
                    if line_found[0] in line_run:
1893
                        inserted = True
1894
                        break
1895

    
1896
                if inserted:
1897
                    continue
1898
                else:
1899
                    run = [line_found[0]]
1900
                    Worker.find_connected_line(run, line_found[0])
1901
                    line_runs.append(run)
1902

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

    
1913
            # change line type: connected at body
1914
            remains = [line for line in app_doc_data.lines if not hasattr(line, 'visual_marker')]
1915
            while True:
1916
                remains_count = len(remains)
1917

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

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

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

    
1943
        if type(line.connectors[1].connectedItem) is QEngineeringLineItem and line.connectors[1].connectedItem not in lines and\
1944
            (line.connectors[1].connectedItem.connectors[0].connectedItem is line or
1945
                line.connectors[1].connectedItem.connectors[1].connectedItem is line):
1946
            current_line = line.connectors[1].connectedItem
1947
            lines.append(current_line)
1948
            Worker.find_connected_line(lines, current_line)
1949

    
1950
    @staticmethod
1951
    def changeConnectedLineType(line, lineType, visual_marker=False):
1952
        # for visual line type detection
1953
        if visual_marker:
1954
            line.visual_marker = visual_marker
1955

    
1956
        line.lineType = lineType
1957
        if type(line.connectors[0].connectedItem) is QEngineeringLineItem and \
1958
                (line.connectors[0].connectedItem.connectors[0].connectedItem is line or
1959
                 line.connectors[0].connectedItem.connectors[1].connectedItem is line) and \
1960
                line.connectors[0].connectedItem.lineType is not lineType:
1961
            Worker.changeConnectedLineType(line.connectors[0].connectedItem, lineType, visual_marker=visual_marker)
1962

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

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

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

    
1988
        try:
1989
            listWidget.addItem('Starting line recognition')
1990
            worker.displayTitle.emit(worker.tr('Detecting lines...'))
1991

    
1992
            app_doc_data = AppDocData.instance()
1993
            project = app_doc_data.getCurrentProject()
1994

    
1995
            # detect line
1996
            connectedLines = []
1997

    
1998
            area = app_doc_data.getArea('Drawing')
1999
            if area is not None:
2000
                area.img = app_doc_data.imgSrc[round(area.y + 1):round(area.y + area.height),
2001
                            round(area.x + 1):round(area.x + area.width)]
2002

    
2003
            Worker.remove_equipment_package(worker.scene, area)
2004

    
2005
            area.img = worker.remove_small_objects(area.img)
2006
            detector = LineDetector(area.img)
2007

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

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

    
2023
                for line in remainLines:
2024
                    line.append(thickness)
2025
                connectedLines.extend(remainLines)
2026

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

    
2030
            symbol_areas = []
2031
            for symbol in symbols:
2032
                symbol_areas.append(QRect(symbol.loc[0] - area.x, symbol.loc[1] - area.y, symbol.size[0], symbol.size[1]))
2033

    
2034
            detector.mergeLines(connectedLines, toler=toler, symbol_areas=symbol_areas)
2035

    
2036
            for pts in connectedLines:
2037
                line = QEngineeringLineItem(
2038
                    vertices=[(area.x + param[0], area.y + param[1]) for param in pts[:-1]], thickness=pts[2])
2039
                line.area = 'Drawing'
2040

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

    
2051
    '''
2052
        @history    2018.04.24  Jeongwoo    Add isExceptDetect Field
2053
                    2018.05.09  Jeongwoo    Add targetSymbolList clear
2054
                    humkyung 2018.07.07 store symbols to list as like [equipments],[nozzles],[symbols]
2055
    '''
2056

    
2057
    @staticmethod
2058
    def initTargetSymbolDataList(all=False):
2059
        global targetSymbolList
2060

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

    
2071
        return targetSymbolList
2072

    
2073
    '''
2074
        @brief  detect equipment
2075
        @author humkyung
2076
        @date   2018.07.07
2077
    '''
2078

    
2079
    @staticmethod
2080
    def detect_nozzles(mainRes, targetSymbol, listWidget, worker):
2081
        res = []
2082
        try:
2083
            app_doc_data = AppDocData.instance()
2084

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

    
2099
        return res
2100

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

    
2113
    @staticmethod
2114
    def detectSymbolOnPid(mainRes, targetSymbol, listWidget, worker):
2115
        import copy
2116
        global ocrCompletedSrc
2117
        global threadLock
2118
        global maxProgressValue
2119

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

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

    
2147
            foundSymbolCount = 0
2148

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

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

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

    
2182
            roiItemSp = (0, 0)
2183
            roiItemEp = (srcWidth, srcHeight)
2184

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

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

    
2199
                    # get Rotated Original Point
2200
                    originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint,
2201
                                                                      symbolRotatedAngle, sw, sh, sow, soh, flipped)
2202
                    connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw,
2203
                                                                          sh, sow, soh, flipped)
2204

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

    
2214
                        if baseSymbol is not None and additionalSymbol is not None:
2215
                            additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol)
2216
                        continue
2217

    
2218
                    # For OPC
2219
                    drawing_area = app_doc_data.getArea('Drawing')
2220
                    # disable opc detection
2221
                    if drawing_area is not None and (symbolType == "Piping OPC\'s" or symbolType == "Instrument OPC\'s"):
2222
                        customMatch = worker.detectOPCOnPid(drawing_area, symGray)
2223
                        if customMatch and len(customMatch) > 0:
2224
                            for custom in customMatch:
2225
                                hitRate = custom[0]
2226
                                searchedItemSp = (custom[1][0] + round(offsetDrawingArea[0]), custom[1][1] + round(offsetDrawingArea[1]))
2227

    
2228
                                is_add = True
2229
                                for searched in searchedSymbolList:
2230
                                    if Worker.IsOverlap((searchedItemSp[0], searchedItemSp[1], sw, sh), (
2231
                                            searched.getSp()[0], searched.getSp()[1], searched.getWidth(),
2232
                                            searched.getHeight())):
2233
                                        if searched.getHitRate() > hitRate:
2234
                                            is_add = False
2235
                                        else:
2236
                                            searchedSymbolList.remove(searched)
2237
                                            break
2238

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

    
2252
                    if forTraining:
2253
                        sw, sh = sw + 20, sh + 20
2254

    
2255
                    # Template Matching
2256
                    tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED)
2257
                    loc = np.where(tmRes >= symbolThreshold)
2258

    
2259
                    for pt in zip(*loc[::-1]):
2260
                        '''
2261
                        # no more used 
2262
                        mpCount = 0  # Match Point Count
2263
                        roi = roiItem[pt[1]:pt[1] + sh, pt[0]:pt[0] + sw]
2264

2265
                        if symbolMinMatchCount > 0:
2266
                            mpCount = Worker.getMatchPointCount(roi, symGray)
2267
                            if not (mpCount >= symbolMinMatchCount):
2268
                                continue
2269
                        '''
2270

    
2271
                        searchedItemSp = (roiItemSp[0] + pt[0] + round(offsetDrawingArea[0]),
2272
                                          roiItemSp[1] + pt[1] + round(offsetDrawingArea[1]))
2273
                        
2274
                        if forTraining:
2275
                            searchedItemSp = [searchedItemSp[0] - 10, searchedItemSp[1] - 10]
2276

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

    
2295
                        hitRate = tmRes[pt[1], pt[0]]
2296

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

    
2396
                    # rotate symbol
2397
                    symGray = cv2.rotate(symGray, cv2.ROTATE_90_CLOCKWISE)
2398
                    symbolRotatedAngle = (symbolRotatedAngle + 90) % 360
2399
                    #cX, cY = originalPoint[0], originalPoint[0]
2400
                    #M = cv2.getRotationMatrix2D((cX, cY), 45, 1.0)
2401
                    #symGray = cv2.warpAffine(symGray, M, (sw, sh))
2402
                    #symbolRotatedAngle = (symbolRotatedAngle + 45) % 360
2403

    
2404
                    if additionalSymbol is not None:
2405
                        additionalSymbol = Worker.getRotatedChildInfo(additionalSymbol)
2406

    
2407
            threadLock.acquire()
2408
            listWidget.addItem('Found Symbol   : ' + os.path.splitext(os.path.basename(symbolPath))[0] + ' - (' + str(
2409
                foundSymbolCount) + ')')
2410
            threadLock.release()
2411

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

    
2421
            worker.updateProgress.emit(maxProgressValue, symbolPath)
2422

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

    
2429
        return []
2430

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

    
2444
    @staticmethod
2445
    def detectOPCOnPid(area, symGray):
2446
        results = []
2447

    
2448
        try:
2449
            symbol = cv2.copyMakeBorder(symGray, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=255)
2450
            not_symbol = cv2.bitwise_not(symbol)
2451
            symbol_contours, symbol_hierachy = cv2.findContours(not_symbol, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
2452
            if symbol_hierachy[0][0][0] != -1:
2453
                return results
2454

    
2455
            contour_count = len(symbol_contours)
2456
            if contour_count != 2:
2457
                return results
2458

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

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

    
2487
                                x = x - rect_x2
2488
                                y = y - rect_y2
2489
                                percent_x2 = x / rect_w2
2490
                                percent_y2 = y / rect_h2
2491

    
2492
                                value_x = abs(percent_x - percent_x2)
2493
                                value_y = abs(percent_y - percent_y2)
2494

    
2495
                                results.append([1 - (value_x + value_y), [rect_x2, rect_y2]])
2496
                break
2497
        except Exception as ex:
2498
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
2499
                                                           sys.exc_info()[-1].tb_lineno)
2500
        return results
2501

    
2502
    # TODO: detect flange
2503
    @staticmethod
2504
    def detectFlangeOnPid(symbol, connector, line, image):
2505
        pt = connector.center()
2506
        x = int(pt[0])
2507
        y = int(pt[1])
2508
        center_x = int(symbol.loc[0] + symbol.size[0] / 2)
2509
        center_y = int(symbol.loc[1] + symbol.size[1] / 2)
2510

    
2511
        arrow = Arrow.NULL
2512
        if line:
2513
            line_center_x = int((line.start_point()[0] + line.end_point()[0]) / 2)
2514
            line_center_y = int((line.start_point()[1] + line.end_point()[1]) / 2)
2515

    
2516
            if line.isHorizontal():
2517
                if center_x < line_center_x:
2518
                    arrow = Arrow.RIGHT
2519
                else:
2520
                    arrow = Arrow.LEFT
2521
            elif line.isVertical():
2522
                if center_y < line_center_y:
2523
                    arrow = Arrow.DOWN
2524
                else:
2525
                    arrow = Arrow.UP
2526

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

    
2544
            result = Worker.detectFlangeBlindOnPid(x, y, arrow, image)
2545
            if result:
2546
                return [x, y]
2547
           
2548
        return None
2549

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

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

    
2582
        # 임시
2583
        temp_count = 0
2584
        noise_count = 0
2585
        find_list_x = []
2586
        find_list_y = []
2587
        for i in loopRange:
2588
            loop_find = False
2589
            for j in range(search_length):
2590
                width = 0
2591
                color_1 = 0
2592
                color_2 = 0
2593
                find_x = 0
2594
                find_y = 0
2595

    
2596
                if arrow is Arrow.DOWN or arrow is Arrow.UP:
2597
                    color_1 = image[i, start_x - j]
2598
                    color_2 = image[i, start_x + j]
2599
                    if int(color_1) is 0:
2600
                        width = Worker.getWidth(start_x - j, i, arrow, image)
2601
                        find_x = start_x - j
2602
                        find_y = i
2603
                    elif int(color_2) is 0:
2604
                        width = Worker.getWidth(start_x + j, i, arrow, image)
2605
                        find_x = start_x + j
2606
                        find_y = i
2607

    
2608
                elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2609
                    color_1 = image[start_y - j, i]
2610
                    color_2 = image[start_y + j, i]
2611
                    if int(color_1) is 0:
2612
                        width = Worker.getWidth(i, start_y - j, arrow, image)
2613
                        find_x = i
2614
                        find_y = start_y - j
2615
                    elif int(color_2) is 0:
2616
                        width = Worker.getWidth(i, start_y + j, arrow, image)
2617
                        find_x = i
2618
                        find_y = start_y + j
2619

    
2620
                if flange_min < width < flange_max:
2621
                    loop_find = True
2622
                    find_list_x.append(find_x)
2623
                    find_list_y.append(find_y)
2624
                    break
2625
                elif width > 0:
2626
                    if temp_count > 0:
2627
                        noise_count += 1
2628
                    break
2629

    
2630
            if loop_find:
2631
                if temp_count > flange_count_max:
2632
                    break
2633
                temp_count = temp_count + 1
2634
            elif 0 < temp_count < flange_count_max and width > 0 and noise_count <= noise_count_max:
2635
                continue
2636
            elif noise_count > noise_count_max:
2637
                break
2638
            elif 0 < temp_count < flange_count_max and width == 0 and noise_count <= noise_count_max:
2639
                find_forward = True
2640
                break
2641
            else:
2642
                find_list_x.clear()
2643
                find_list_y.clear()
2644
                temp_count = 0
2645

    
2646
        if not find_forward:
2647
            return False
2648

    
2649
        # 임시
2650
        temp_count = 0
2651
        noise_count = 0
2652
        find_list_x = []
2653
        find_list_y = []
2654
        for i in reversed(loopRange):
2655
            loop_find = False
2656
            for j in range(search_length):
2657
                width = 0
2658
                color_1 = 0
2659
                color_2 = 0
2660
                find_x = 0
2661
                find_y = 0
2662

    
2663
                if arrow is Arrow.DOWN or arrow is Arrow.UP:
2664
                    color_1 = image[i, start_x - j]
2665
                    color_2 = image[i, start_x + j]
2666
                    if int(color_1) is 0:
2667
                        width = Worker.getWidth(start_x - j, i, arrow, image)
2668
                        find_x = start_x - j
2669
                        find_y = i
2670
                    elif int(color_2) is 0:
2671
                        width = Worker.getWidth(start_x + j, i, arrow, image)
2672
                        find_x = start_x + j
2673
                        find_y = i
2674

    
2675
                elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2676
                    color_1 = image[start_y - j, i]
2677
                    color_2 = image[start_y + j, i]
2678
                    if int(color_1) is 0:
2679
                        width = Worker.getWidth(i, start_y - j, arrow, image)
2680
                        find_x = i
2681
                        find_y = start_y - j
2682
                    elif int(color_2) is 0:
2683
                        width = Worker.getWidth(i, start_y + j, arrow, image)
2684
                        find_x = i
2685
                        find_y = start_y + j
2686

    
2687
                if flange_min < width < flange_max:
2688
                    loop_find = True
2689
                    find_list_x.append(find_x)
2690
                    find_list_y.append(find_y)
2691
                    break
2692
                elif width > 0:
2693
                    if temp_count > 0:
2694
                        noise_count += 1
2695
                    break
2696

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

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

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

    
2756
                if arrow is Arrow.DOWN or arrow is Arrow.UP:
2757
                    color_1 = image[i, start_x - j]
2758
                    color_2 = image[i, start_x + j]
2759
                    if int(color_1) is 0:
2760
                        width = Worker.getWidth(start_x - j, i, arrow, image)
2761
                        find_x = start_x - j
2762
                        find_y = i
2763
                    elif int(color_2) is 0:
2764
                        width = Worker.getWidth(start_x + j, i, arrow, image)
2765
                        find_x = start_x + j
2766
                        find_y = i
2767

    
2768
                elif arrow is Arrow.LEFT or arrow is Arrow.RIGHT:
2769
                    color_1 = image[start_y - j, i]
2770
                    color_2 = image[start_y + j, i]
2771
                    if int(color_1) is 0:
2772
                        width = Worker.getWidth(i, start_y - j, arrow, image)
2773
                        find_x = i
2774
                        find_y = start_y - j
2775
                    elif int(color_2) is 0:
2776
                        width = Worker.getWidth(i, start_y + j, arrow, image)
2777
                        find_x = i
2778
                        find_y = start_y + j
2779

    
2780
                if 0 < width <= line_width:
2781
                    loop_find = True
2782
                    find_list_x.append(find_x)
2783
                    find_list_y.append(find_y)
2784
                    break
2785

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

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

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

    
2858
            else:
2859
                return False
2860

    
2861
        return False
2862

    
2863
    @staticmethod
2864
    def getWidth(x, y, arrow, image):
2865
        width = 0
2866
        move_x1 = 0
2867
        move_y1 = 0
2868
        move_x2 = 0
2869
        move_y2 = 0
2870

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

    
2900
        return width
2901

    
2902
    @staticmethod
2903
    def removeLine(symbol, x, y, arrow, image):
2904
        symbol_width = int(symbol.size[0])
2905
        symbol_height = int(symbol.size[1])
2906

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

    
2924
    @staticmethod
2925
    def getAverage(datas):
2926
        result = 0
2927

    
2928
        for x in datas:
2929
            result = result + x
2930

    
2931
        result = result / len(datas)
2932

    
2933
        return int(result)
2934

    
2935

    
2936
    # Convert into Grayscale image
2937
    @staticmethod
2938
    def cvtGrayImage(img):
2939
        return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
2940

    
2941
    '''
2942
        @history    2018.06.12  Jeongwoo    Type changed (int → float)
2943
                    humkyung 2018.07.07 change return type as like [x,y]
2944
    '''
2945

    
2946
    @staticmethod
2947
    def getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint, symbolRotatedAngle, rotateSymbolWidth,
2948
                                   rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight, flipped=False):
2949
        res = []
2950

    
2951
        if additionalSymbol is None and symbolOriginalPoint is None:
2952
            res.append(rotateSymbolWidth // 2)
2953
            res.append(rotateSymbolHeight // 2)
2954
        else:
2955
            if flipped:
2956
                opx = originalSymbolWidth - float(symbolOriginalPoint.split(',')[0])
2957
                opy = float(symbolOriginalPoint.split(',')[1])
2958
            else:
2959
                opx = float(symbolOriginalPoint.split(',')[0])
2960
                opy = float(symbolOriginalPoint.split(',')[1])
2961

    
2962
            rPt = Worker.getCoordOnRotatedImage(symbolRotatedAngle, ('AUTO', opx, opy, '0'), originalSymbolWidth,
2963
                                                originalSymbolHeight)
2964

    
2965
            res.append(rPt[1])
2966
            res.append(rPt[2])
2967

    
2968
        return res
2969

    
2970
    '''
2971
        @history    2018.06.12  Jeongwoo    Type changed (int → float)
2972
                    humkyung 2018.07.07 change return type as like [[x,y],...]
2973
                    humkyung 2019.01.04 get symbol index
2974
    '''
2975

    
2976
    @staticmethod
2977
    def getCalculatedConnectionPoint(symbolConnectionPointStr, symbolRotatedAngle, rotateSymbolWidth,
2978
                                     rotateSymbolHeight, originalSymbolWidth, originalSymbolHeight, flipped=0):
2979
        res = []
2980

    
2981
        if symbolConnectionPointStr is not None and symbolConnectionPointStr != '':
2982
            splitConnectionPointStr = symbolConnectionPointStr.split("/")
2983
            for strConnPt in splitConnectionPointStr:
2984
                tokens = strConnPt.split(',')
2985

    
2986
                direction = 'AUTO'
2987
                symbol_idx = '0'
2988
                if flipped:
2989
                    converted = {'AUTO': 'AUTO', 'LEFT': 'RIGHT', 'RIGHT': 'LEFT', 'UP': 'UP', 'DOWN': 'DOWN'}
2990

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

    
3019
                res.append(Worker.getCoordOnRotatedImage(symbolRotatedAngle, (direction, cpx, cpy, symbol_idx),
3020
                                                         originalSymbolWidth, originalSymbolHeight))
3021

    
3022
        return res
3023

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

    
3033
    @staticmethod
3034
    def getCoordOnRotatedImage(angle, connPt, originImageWidth, originImageHeight):
3035
        import math
3036

    
3037
        rx = None
3038
        ry = None
3039

    
3040
        # calculate rotated direction
3041
        direction = connPt[0]
3042
        '''
3043
        if direction == 'LEFT':
3044
            direction = 'DOWN' if angle == 90 else 'RIGHT' if angle == 180 else 'UP' if angle == 270 else direction
3045
        elif direction == 'RIGHT':
3046
            direction = 'UP' if angle == 90 else 'LEFT' if angle == 180 else 'DOWN' if angle == 270 else direction
3047
        elif direction == 'UP':
3048
            direction = 'LEFT' if angle == 90 else 'DOWN' if angle == 180 else 'RIGHT' if angle == 270 else direction
3049
        elif direction == 'DOWN':
3050
            direction = 'RIGHT' if angle == 90 else 'UP' if angle == 180 else 'LEFT' if angle == 270 else direction
3051
        '''
3052
        # up to here
3053

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

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

    
3079
        '''
3080
        pX, pY = connPt[1], originImageHeight - connPt[2]
3081
        rad = math.radians(-angle)
3082
        rot_ce_X = originImageWidth * 0.5
3083
        rot_ce_Y = originImageHeight * 0.5
3084
        rX = (pX - rot_ce_X) * math.cos(rad) - (pY - rot_ce_Y) * math.sin(rad) + rot_ce_X
3085
        rY = (pX - rot_ce_X) * math.sin(rad) + (pY - rot_ce_Y) * math.cos(rad) + rot_ce_Y
3086
        rx = rX
3087
        ry = originImageHeight - rY
3088

3089
        if abs(point.x() - rx) > 0.1 or abs(point.y() - ry) > 0.1:
3090
            print('a')
3091
        '''
3092

    
3093
        symbol_idx = connPt[3]
3094

    
3095
        return (direction, rx, ry, symbol_idx)
3096

    
3097
    '''
3098
        @brief      Add symbols
3099
        @author     jwkim
3100
        @date
3101
        @history    Change parameter (mpCount → hitRate)
3102
                    Yecheol 2018.07.04 Delete Symbol Id 
3103
    '''
3104

    
3105
    @staticmethod
3106
    def addSearchedSymbol(sName, sType
3107
                          , sp, w, h, threshold, minMatchCount, hitRate, rotatedAngle
3108
                          , isDetectOnOrigin, rotateCount, ocrOption, isContainChild
3109
                          , originalPoint, connectionPoint, baseSymbol, additionalSymbol, isExceptDetect, detectFlip
3110
                          , hasInstrumentLabel, text_area):
3111
        global searchedSymbolList
3112

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

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

    
3131
        return newSym
3132

    
3133
    '''
3134
        @brief      Check object contains pt
3135
        @param      obj is item in searchedSymbolList
3136
    '''
3137

    
3138
    @staticmethod
3139
    def contains(obj, pt, tw, th):
3140
        sp = obj.getSp()
3141
        width = obj.getWidth()
3142
        height = obj.getHeight()
3143

    
3144
        if sp[0] > pt[0] + tw:
3145
            return 0
3146
        if sp[0] + width < pt[0]:
3147
            return 0
3148
        if sp[1] > pt[1] + th:
3149
            return 0
3150
        if sp[1] + height < pt[1]:
3151
            return 0
3152

    
3153
        # shared area
3154
        x = max(sp[0], pt[0])
3155
        y = max(sp[1], pt[1])
3156
        w = min(sp[0] + width, pt[0] + tw) - x
3157
        h = min(sp[1] + height, pt[1] + th) - y
3158

    
3159
        return float((w * h)) / float((tw * th)) * 100
3160

    
3161
    # Calculate count of keypoint match result
3162
    @staticmethod
3163
    def getMatchPointCount(src, cmp):
3164
        matchCount = 0
3165

    
3166
        try:
3167
            orb = cv2.ORB_create(1000, 2.0, 2, 1)
3168

    
3169
            kp1, des1 = orb.detectAndCompute(src, None)
3170
            kp2, des2 = orb.detectAndCompute(cmp, None)
3171

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

    
3184
            flann = cv2.FlannBasedMatcher(index_params, search_params)
3185

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

    
3189
            count = 0
3190
            # ratio test as per Lowe's paper
3191
            for i in range(len(matches)):
3192
                if len(matches[i]) == 2:
3193
                    m = matches[i][0]
3194
                    n = matches[i][1]
3195
                    if m.distance < 0.85 * n.distance:
3196
                        count = count + 1
3197

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

    
3205
        return matchCount
3206

    
3207
    '''
3208
        @brief      Remake rotated child symbol info
3209
    '''
3210

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

    
3226
    @staticmethod
3227
    def calculate_exact_position(area, symGray, symbol, rect, worker, threshold):
3228
        import copy
3229

    
3230
        try:
3231
            tilt = False
3232

    
3233
            symbolThreshold = symbol.getThreshold()
3234
            symbolRotateCount = symbol.getRotationCount()
3235
            detectFlip = symbol.getDetectFlip()
3236
            hasInstrumentLabel = symbol.getHasInstrumentLabel()
3237
            symbolOriginalPoint = symbol.getOriginalPoint()
3238
            symbolConnectionPoint = symbol.getConnectionPoint()
3239
            additionalSymbol = symbol.getAdditionalSymbol()
3240

    
3241
            sow, soh = symGray.shape[::-1]
3242

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

    
3254
            # get Rotated Original Point
3255
            sow, soh = symGray.shape[::-1]
3256
            offset_x, offset_y = round(rect[3] * 0.5), round(rect[4] * 0.5)#int(max(sow, soh) / 2)
3257

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

    
3265
            symGrayOri = copy.copy(symGray)
3266

    
3267
            searchedInfos = [] # score, x, y, angle, flip, originalPoint, connectionPoint, sw, sh
3268

    
3269

    
3270
            steps = [False, True] if detectFlip else [False]
3271
            for flipped in steps:
3272
                if flipped:
3273
                    symGray = symGrayOri
3274
                    symGray = cv2.flip(symGray, 1)
3275

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

    
3290
                    symGray_45 = mask
3291
                    cX, cY = int(symGray_45.shape[::-1][0] / 2), int(symGray_45.shape[::-1][1] / 2)
3292
                    M = cv2.getRotationMatrix2D((cX, cY), -45, 1.0)
3293
                    symGray_45 = cv2.warpAffine(symGray_45, M, (symGray_45.shape[::-1][0], symGray_45.shape[::-1][1]), borderValue=(255))
3294

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

    
3309
                    originalPoint = Worker.getCalculatedOriginalPoint(additionalSymbol, symbolOriginalPoint,
3310
                                                    symbolRotatedAngle, sw, sh, sow, soh, flipped)
3311
                    connectionPoint = Worker.getCalculatedConnectionPoint(symbolConnectionPoint, symbolRotatedAngle, sw,
3312
                                                    sh, sow, soh, flipped)
3313

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

    
3329
                    tmRes = cv2.matchTemplate(roiItem, symGray, cv2.TM_CCOEFF_NORMED)
3330
                    _, max_val, __, max_loc = cv2.minMaxLoc(tmRes)
3331
                    #maxIndex = tmRes.argmax()
3332
                    #colCount = len(tmRes[0])
3333
                    #col, row = divmod(maxIndex, colCount)
3334

    
3335
                    # for single class test
3336
                    #max_val = 80
3337

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

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

    
3353
            if searchedInfos:
3354
                searchedInfos = sorted(searchedInfos, key=lambda param: param[0], reverse=True)
3355
                searchedInfo = searchedInfos[0]
3356
                return ((searchedInfo[1] + area.x, searchedInfo[2] + area.y), searchedInfo[3], searchedInfo[4], \
3357
                        searchedInfo[5], searchedInfo[6], searchedInfo[7], searchedInfo[8], searchedInfo[0])
3358
            else:
3359
                return (None, None, None, None, None, None, None, None)
3360

    
3361
        except Exception as ex:
3362
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
3363
                                                            sys.exc_info()[-1].tb_lineno)
3364
            worker.displayLog.emit(MessageType.Error, message)
3365

    
3366
            return (None, None, None, None, None, None, None, None)
3367

    
3368
    @staticmethod
3369
    def detect_symbol_using_server(targetSymbols, listWidget, worker):
3370
        from AppWebService import AppWebService
3371

    
3372
        res = []
3373

    
3374
        app_doc_data = AppDocData.instance()
3375
        project = app_doc_data.getCurrentProject()
3376
        area = app_doc_data.getArea('Drawing')
3377
        
3378
        app_web_service = AppWebService()
3379
        symbols = app_web_service.request_symbol_box(project.name, area.img)
3380

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

    
3391
            time_delay = 10 if mode == 2 else 20
3392
            time_stamp = timeit.default_timer()
3393
            while True:
3394
                if time_stamp - timeit.default_timer() > time_delay:
3395
                    break
3396

    
3397
        # merge symbol box
3398
        overlap_merges = []
3399
        for rect1 in symbols:
3400
            for rect2 in symbols:
3401
                if rect1 is rect2 or rect1[0] != rect2[0]:
3402
                    continue
3403
                rect1_x_gap = int(rect1[3] / 10)
3404
                rect1_y_gap = int(rect1[4] / 10)
3405
                rect2_x_gap = int(rect1[3] / 10)
3406
                rect2_y_gap = int(rect1[4] / 10)
3407

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

    
3432
        for merge in overlap_merges:
3433
            for rect in merge:
3434
                if rect in symbols:
3435
                    symbols.remove(rect)
3436
                else:
3437
                    pass
3438
                    #print(str(rect))
3439

    
3440
        for merge in overlap_merges:
3441
            max_x, max_y, min_x, min_y = 0, 0, sys.maxsize, sys.maxsize
3442
            ratio = 0
3443
            for rect in merge:
3444
                if rect[5] > ratio:
3445
                    ratio = rect[5]
3446

    
3447
                if rect[1] < min_x:
3448
                    min_x = rect[1]
3449
                if rect[1] + rect[3] > max_x:
3450
                    max_x = rect[1] + rect[3]
3451
                if rect[2] < min_y:
3452
                    min_y = rect[2]
3453
                if rect[2] + rect[4] > max_y:
3454
                    max_y = rect[2] + rect[4]
3455

    
3456
            rect = [rect[0], min_x, min_y, max_x - min_x, max_y - min_y, ratio]
3457
            symbols.append(rect)
3458
        # up to here
3459

    
3460
        # for single class test
3461
        #for symbol in symbols:
3462
        #    symbol[0] = 'globe'
3463

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

    
3480
            '''
3481
            # check if symbol file is target or not
3482
            if isExceptDetect == 1:
3483
                item = QListWidgetItem('{} file is not target'.format(symbolName))
3484
                item.setBackground(QColor('green'))
3485
                listWidget.addItem(item)
3486
                continue
3487
            '''
3488

    
3489
            foundSymbolCount = 0
3490

    
3491
            # check if symbol file exists
3492
            if not os.path.isfile(symbolPath):
3493
                item = QListWidgetItem('{} file not found'.format(symbolName))
3494
                item.setBackground(QColor('red'))
3495
                listWidget.addItem(item)
3496
                continue
3497
            # up to here
3498

    
3499
            sym = cv2.imread(symbolPath, 1)
3500
            symGray = Worker.cvtGrayImage(sym)
3501

    
3502
            configs = app_doc_data.getConfigs('Engine', 'Threshold')
3503
            threshold = int(configs[0].value) / 100 if configs else 0.25
3504

    
3505
            for symbol in symbols:
3506
                if symbol[0] == symbolName:
3507
                    searchedItemSp, symbolRotatedAngle, flipped, originalPoint, connectionPoint, sw, sh, score = \
3508
                                Worker.calculate_exact_position(area, symGray, targetSymbol, symbol, worker, threshold)
3509
                    if not searchedItemSp:
3510
                        continue
3511

    
3512
                    #hitRate = symbol[5]
3513
                    hitRate = score
3514

    
3515
                    Worker.addSearchedSymbol(symbolName, symbolType,
3516
                                    searchedItemSp, sw, sh, symbolThreshold, symbolMinMatchCount,
3517
                                    hitRate, symbolRotatedAngle,
3518
                                    isDetectOnOrigin, symbolRotateCount, symbolOcrOption,
3519
                                    isContainChild,
3520
                                    originalPoint, connectionPoint, baseSymbol, additionalSymbol,
3521
                                    isExceptDetect,
3522
                                    detectFlip=1 if flipped else 0,
3523
                                    hasInstrumentLabel=hasInstrumentLabel, text_area=text_area)
3524

    
3525
        return symbols
3526
    '''
3527
        @brief   detect symbols on PID
3528
        @history humkyung 2018.06.08 add parameteres for signal
3529
    '''
3530
    @staticmethod
3531
    def detectSymbolsOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal):
3532
        res = []
3533

    
3534
        if type(targetSymbols) is list:
3535
            for detailTarget in targetSymbols:
3536
                res.extend(Worker.detectSymbolOnPid(mainRes, detailTarget, listWidget, updateProgressSignal))
3537
        else:
3538
            res = Worker.detectSymbolOnPid(mainRes, targetSymbols, listWidget, updateProgressSignal)
3539

    
3540
        return res
3541

    
3542
    @staticmethod
3543
    def convertDirectionCodeToValue(directionCode):
3544
        if directionCode == "UP":
3545
            return 0
3546
        elif directionCode == "RIGHT":
3547
            return 1
3548
        elif directionCode == "DOWN":
3549
            return 2
3550
        elif directionCode == "LEFT":
3551
            return 3
3552
        else:
3553
            return -1
3554

    
3555
    @staticmethod
3556
    def convertValueToDirectionCode(value):
3557
        if value == 0:
3558
            return "UP"
3559
        elif value == 1:
3560
            return "RIGHT"
3561
        elif value == 2:
3562
            return "DOWN"
3563
        elif value == 3:
3564
            return "LEFT"
3565
        else:
3566
            return "NONE"
3567

    
3568
    @staticmethod
3569
    def drawFoundSymbolsOnCanvas(drawingPath, symbols, textInfos, listWidget):
3570
        """draw found symbols and texts to image"""
3571

    
3572
        global src
3573
        global ocrCompletedSrc
3574
        global canvas
3575

    
3576
        app_doc_data = AppDocData.instance()
3577
        canvas = np.zeros(app_doc_data.imgSrc.shape, np.uint8)
3578
        canvas[::] = 255
3579

    
3580
        try:
3581
            project = app_doc_data.getCurrentProject()
3582

    
3583
            for symbol in symbols:
3584
                Worker.drawFoundSymbols(symbol, listWidget)
3585

    
3586
            for text in textInfos:
3587
                left = text.getX()
3588
                top = text.getY()
3589
                right = text.getX() + text.getW()
3590
                bottom = text.getY() + text.getH()
3591

    
3592
                canvas[top:bottom, left:right] = app_doc_data.imgSrc[top:bottom, left:right]
3593

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

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

    
3610
    @staticmethod
3611
    def drawFoundSymbols(symbol, listWidget):
3612
        global src
3613
        global canvas
3614
        #global WHITE_LIST_CHARS
3615
        global searchedSymbolList
3616
        global textInfoList
3617

    
3618
        # symbolId = symbol.getId()
3619
        symbolPath = symbol.getPath()
3620
        symbolSp = symbol.getSp()
3621
        symbolRotatedAngle = symbol.getRotatedAngle()
3622

    
3623
        symImg = cv2.cvtColor(cv2.imread(symbolPath, 1), cv2.COLOR_BGR2GRAY)
3624
        if symbol.getDetectFlip() is 1:
3625
            symImg = cv2.flip(symImg, 1)
3626
        for i in range(symbolRotatedAngle // 90):
3627
            symImg = cv2.rotate(symImg, cv2.ROTATE_90_COUNTERCLOCKWISE)
3628

    
3629
        w, h = symImg.shape[::-1]
3630
        canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w] = cv2.bitwise_and(
3631
            canvas[symbolSp[1]:symbolSp[1] + h, symbolSp[0]:symbolSp[0] + w], symImg)
3632

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

    
3645
    @staticmethod
3646
    def remove_detected_symbol_image(sym, imgSrc, lock=True):
3647
        """remove detected symbol image from drawing image"""
3648
        if lock:
3649
            global threadLock
3650

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

    
3664
            for i in range(angle // 90):
3665
                sym_img = cv2.rotate(sym_img, cv2.ROTATE_90_COUNTERCLOCKWISE)
3666
            # up to here
3667

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

    
3684
    '''
3685
        @brief  get difference between given original and recognized image
3686
        @author humkyung
3687
        @date   2018.06.11
3688
    '''
3689

    
3690
    @staticmethod
3691
    def getDifference(orgImagePath, recImagePath):
3692
        import re
3693

    
3694
        global ocrCompletedSrc
3695
        global textInfoList
3696

    
3697
        try:
3698
            app_doc_data = AppDocData.instance()
3699
            imgOriginal = app_doc_data.imgSrc
3700

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

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

    
3716
            noteArea = app_doc_data.getArea('Note')
3717
            if noteArea is not None:
3718
                noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height),
3719
                                round(noteArea.x):round(noteArea.x + noteArea.width)].copy()
3720
                cv2.rectangle(imgOriginal, (round(noteArea.x), round(noteArea.y)),
3721
                                (round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1)
3722
            # up to here
3723

    
3724
            """
3725
            if os.path.isfile(orgImagePath) and os.path.isfile(recImagePath):
3726
                imgOriginal = \
3727
                    cv2.threshold(Worker.cvtGrayImage(cv2.imread(orgImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1]
3728

3729
                configs = app_doc_data.getConfigs('Filter', 'DilateSize')
3730
                if 1 == len(configs) and int(configs[0].value) is not 0:
3731
                    size = int(configs[0].value)
3732
                    kernel = np.ones((size, size), np.uint8)
3733
                    imgOriginal = cv2.erode(imgOriginal, kernel, iterations=1)
3734

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

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

3750
                noteArea = app_doc_data.getArea('Note')
3751
                if noteArea is not None:
3752
                    noteArea.img = app_doc_data.imgSrc[round(noteArea.y):round(noteArea.y + noteArea.height),
3753
                                   round(noteArea.x):round(noteArea.x + noteArea.width)].copy()
3754
                    cv2.rectangle(imgOriginal, (round(noteArea.x), round(noteArea.y)),
3755
                                  (round(noteArea.x + noteArea.width), round(noteArea.y + noteArea.height)), 255, -1)
3756
                # up to here
3757

3758
                imgRecognized = \
3759
                    cv2.threshold(Worker.cvtGrayImage(cv2.imread(recImagePath, 1)), 127, 255, cv2.THRESH_BINARY)[1]
3760

3761
                imgDiff = np.ones(imgOriginal.shape, np.uint8) * 255
3762

3763
                area = app_doc_data.getArea('Drawing')
3764
                if area is not None:
3765
                    x = round(area.x)
3766
                    y = round(area.y)
3767
                    width = round(area.width)
3768
                    height = round(area.height)
3769
                    imgNotOper = cv2.bitwise_not(imgRecognized[y:y + height, x:x + width])
3770
                    imgDiff[y:y + height, x:x + width] = cv2.bitwise_xor(imgOriginal[y:y + height, x:x + width],
3771
                                                                         imgNotOper)
3772

3773
                # remove noise
3774
                imgDiff = cv2.dilate(imgDiff, np.ones((2, 2), np.uint8))
3775

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

    
3785

    
3786
'''
3787
    @history    2018.05.25  Jeongwoo    Add pyqtSignal(svgItemClicked, itemRemoved)
3788
'''
3789

    
3790

    
3791
class QRecognitionDialog(QDialog):
3792
    svgItemClicked = pyqtSignal(SymbolSvgItem)
3793
    itemRemoved = pyqtSignal(QGraphicsItem)
3794
    unBlockEvent = pyqtSignal()
3795

    
3796
    '''
3797
        @history    2018.05.25  Jeongwoo    Add parameter and initialize / Connect recognizeButton signal and slot
3798
                    2018.05.29  Jeongwoo    Chnage parameter(graphicsView → parent) and Get graphicsView from parent
3799
    '''
3800

    
3801
    def __init__(self, parent, drawings):  # Parent is MainWindow
3802
        from AppDocData import AppDocData
3803

    
3804
        QDialog.__init__(self, parent)
3805

    
3806
        self.parent = parent
3807
        self._scene = QGraphicsScene()
3808
        self._text_scene = QGraphicsScene()
3809
        self._line_scene = QGraphicsScene()
3810
        self.drawings = drawings
3811
        self.xmlPath = None
3812
        self.ui = Recognition_UI.Ui_Recognition()
3813
        self.ui.setupUi(self)
3814
        self.isTreated = False
3815

    
3816
        self.ui.buttonBox.setEnabled(True)
3817
        self.ui.listWidget.model().rowsInserted.connect(self.rowInserted)
3818
        self.ui.recognizeButton.clicked.connect(self.recognizeButtonClicked)
3819
        self.ui.lineCheckBox.stateChanged.connect(self.checkBoxChanged)
3820
        self.ui.checkBoxSymbol.stateChanged.connect(self.checkBoxChanged)
3821
        self.ui.checkBoxText.stateChanged.connect(self.checkBoxChanged)
3822
        self.ui.checkBoxTraining.stateChanged.connect(self.checkBoxChanged)
3823
        self.isAccepted = False
3824

    
3825
        appDocData = AppDocData.instance()
3826
        configs = appDocData.getAppConfigs('app', 'mode')
3827
        if configs and 1 == len(configs) and 'advanced' == configs[0].value:
3828
            pass
3829
        else:
3830
            self.ui.checkBoxTraining.setVisible(False)
3831

    
3832
        if False:#len(self.drawings) == 1 and appDocData.activeDrawing and appDocData.activeDrawing == self.drawings[0]:
3833
            self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
3834
            self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
3835
            self.ui.checkBoxText.setCheckState(Qt.Unchecked)
3836
        else:
3837
            self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
3838
            self.ui.lineCheckBox.setCheckState(Qt.Checked)
3839
            self.ui.checkBoxText.setCheckState(Qt.Checked)
3840
            #self.ui.checkBoxSymbol.setEnabled(False)
3841
            #self.ui.lineCheckBox.setEnabled(False)
3842
            #self.ui.checkBoxText.setEnabled(False)
3843

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

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

    
3867
        if checkState is int(Qt.Checked):
3868
            if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked():
3869
                self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
3870
            elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked():
3871
                self.ui.checkBoxSymbol.setCheckState(Qt.Checked)
3872
        elif checkState is int(Qt.Unchecked):
3873
            if self.ui.lineCheckBox.isChecked() and not self.ui.checkBoxSymbol.isChecked():
3874
                self.ui.lineCheckBox.setCheckState(Qt.Unchecked)
3875
            elif self.ui.checkBoxText.isChecked() and not self.ui.checkBoxSymbol.isChecked():
3876
                self.ui.checkBoxText.setCheckState(Qt.Unchecked)
3877

    
3878
    '''
3879
        @brief      QListWidget Row Inserted Listener
3880
                    Whenever row inserted, scroll to bottom
3881
        @author     Jeongwoo
3882
        @date       18.04.12
3883
    '''
3884

    
3885
    def rowInserted(self, item):
3886
        self.ui.listWidget.scrollToBottom()
3887

    
3888
    def accept(self):
3889
        self.isAccepted = True
3890
        QDialog.accept(self)
3891

    
3892
    def recognizeButtonClicked(self, event):
3893
        """
3894
        @brief      start recognization
3895
        @author     humkyung
3896
        @history    humkyung 2018.10.05 clear imgSrc before recognizing
3897
        """
3898
        if self.ui.checkBoxSymbol.isChecked():  # or self.ui.checkBoxText.isChecked() or self.ui.lineCheckBox.isChecked():
3899
            appDocData = AppDocData.instance()
3900
            appDocData.imgSrc = None
3901
            area = appDocData.getArea('Drawing')
3902
            if area is None:
3903
                QMessageBox.about(self, self.tr("Notice"), self.tr('Please select drawing area.'))
3904
                return
3905

    
3906
            self.ui.recognizeButton.setEnabled(False)
3907
            self.ui.buttonBox.setEnabled(False)
3908
            self.ui.progressBar.setValue(0)
3909
            self.ui.listWidget.addItem("Initializing...")
3910
            self.startThread()
3911

    
3912
            self.isTreated = True
3913

    
3914
    '''
3915
        @brief  add item to list widget
3916
        @author humkyung
3917
        @date   2018.06.19
3918
    '''
3919

    
3920
    def addListItem(self, item):
3921
        self.ui.listWidget.addItem(item)
3922

    
3923
    '''
3924
        @brief  update progressbar with given value
3925
        @author humkyung
3926
        @date   2018.06.08
3927
    '''
3928

    
3929
    def updateProgress(self, maxValue, image_path):
3930
        self.ui.progressBar.setMaximum(maxValue)
3931
        self.ui.progressBar.setValue(self.ui.progressBar.value() + 1)
3932

    
3933
        if image_path is not None and os.path.isfile(image_path):
3934
            self.ui.labelImage.setPixmap(QPixmap(image_path))
3935
        elif image_path is not None:
3936
            self.ui.labelImage.setText(image_path)
3937

    
3938
    def updateBatchProgress(self, maxValue, weight):
3939
        """update batch progressbar"""
3940
        self.ui.progressBarBatch.setMaximum(maxValue * 5)
3941
        value = self.ui.progressBarBatch.value() + weight
3942
        self.ui.progressBarBatch.setValue(value)
3943
        self.ui.progressBarBatch.setFormat('{}/{}'.format(str(int(value / 5)), str(maxValue)))
3944

    
3945
    '''
3946
        @brief      display title
3947
        @author     humkyung
3948
        @date       2018.08.20
3949
    '''
3950

    
3951
    def displayTitle(self, title):
3952
        self.ui.labelTitle.setText(title)
3953

    
3954
    def startThread(self):
3955
        """start thread"""
3956
        from PyQt5 import QtWidgets
3957
        from App import App
3958

    
3959
        self.ui.buttonBox.setDisabled(True)
3960

    
3961
        self.mutex = QMutex()
3962
        self.cond = QWaitCondition()
3963

    
3964
        # 1 - create Worker and Thread inside the Form
3965
        self.obj = Worker(self.mutex, self.cond)  # no parent!
3966
        self.obj.symbol_time = None
3967
        self.obj.text_time = None
3968
        self.obj.drawings = self.drawings
3969
        self.obj.listWidget = self.ui.listWidget
3970
        self.obj.scene = self._scene
3971
        self.obj.text_scene = self._text_scene
3972
        self.obj.line_scene = self._line_scene
3973
        self.obj.scene._end = False  # for waiting each drawing finished
3974
        self.obj.isSymbolChecked = self.ui.checkBoxSymbol.isChecked()
3975
        self.obj.isTextChecked = self.ui.checkBoxText.isChecked()
3976
        self.obj.isLineChecked = self.ui.lineCheckBox.isChecked()
3977
        self.obj.isTrainingChecked = self.ui.checkBoxTraining.isChecked()
3978
        self.thread = QThread()  # no parent!
3979

    
3980
        # 2 - Move the Worker object to the Thread object
3981
        self.obj.moveToThread(self.thread)
3982

    
3983
        # 3 - Connect Worker Signals to the Thread slots
3984
        self.obj.finished.connect(self.thread.quit)
3985
        self.obj.displayMessage.connect(self.addListItem)
3986
        self.obj.updateProgress.connect(self.updateProgress)
3987
        self.obj.updateBatchProgress.connect(self.updateBatchProgress)
3988
        self.obj.displayLog.connect(App.mainWnd().addMessage)
3989
        self.obj.displayTitle.connect(self.displayTitle)
3990
        self.obj.add_detected_items_to_scene.connect(self.add_detected_items_to_scene)
3991
        self.obj.save_scene.connect(self.save_scene)
3992
        self.obj.preset_execute.connect(self.preset_execute)
3993
        self.obj.item_remove.connect(self.item_remove)
3994
        self.obj.add_predata_to_scene.connect(self.add_predata_to_scene)
3995
        self.obj.clear_scene.connect(self.clear_scene)
3996

    
3997
        # 4 - Connect Thread started signal to Worker operational slot method
3998
        self.thread.started.connect(self.obj.procCounter)
3999

    
4000
        # 5 - Thread finished signal will close the app if you want!
4001
        self.thread.finished.connect(self.dlgExit)
4002

    
4003
        # 6 - Start the thread
4004
        self.thread.start()
4005

    
4006
        self.tmStart = timeit.default_timer()
4007

    
4008
    '''
4009
        @brief set buttonbox's enabled flag
4010
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
4011
                    2018.06.14  Jeongwoo    Change sentence order
4012
                    2018.11.26  euisung     add drawing part
4013
                    2018.11.26  euising     move save and unknown part into executerecognition
4014
    '''
4015

    
4016
    def dlgExit(self):
4017
        import timeit
4018

    
4019
        try:
4020
            self.ui.buttonBox.setEnabled(True)
4021

    
4022
            self.ui.progressBar.setValue(self.ui.progressBar.maximum())
4023
        except Exception as ex:
4024
            from App import App
4025
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
4026
                                                           sys.exc_info()[-1].tb_lineno)
4027
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4028
        finally:
4029
            self.tmStop = timeit.default_timer()
4030
            seconds = self.tmStop - self.tmStart
4031
            self.ui.listWidget.addItem("\nRunning Time(total) : {} min".format(str(round(seconds / 60, 1))))
4032
            if self.obj.symbol_time:
4033
                self.ui.listWidget.addItem("Running Time(symbol) : {} min".format(str(round(self.obj.symbol_time / 60, 1))))
4034
            if self.obj.text_time:
4035
                self.ui.listWidget.addItem("Running Time(text) : {} min".format(str(round(self.obj.text_time / 60, 1))))
4036
            self.ui.listWidget.addItem("\n")
4037

    
4038
    '''
4039
        @history    2018.05.29  Jeongwoo    Call parent's method
4040
                    2018.05.30  Jeongwoo    Change method name
4041
                    2018.06.09  humkyung    set progressbar value to maximum
4042
                    2018.11.12  euisung     add title block properties
4043
                    2018.11.29  euisung     no more used
4044
    '''
4045

    
4046
    def drawDetectedItems(self, symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList, loop):
4047
        try:
4048
            self.ui.progressBar.setValue(self.ui.progressBar.maximum())
4049
            self.parent.drawDetectedItems(symbolList, textInfoList, otherTextInfoList, titleBlockTextInfoList)
4050
        finally:
4051
            loop.quit()
4052

    
4053
    '''
4054
        @brief      draw detected lines
4055
        @author     humkyung
4056
        @date       2018.08.23
4057
        @history    2018.11.27  euisung     no more used
4058
    '''
4059

    
4060
    def drawDetectedLines(self, lineList, loop):
4061
        try:
4062
            self.parent.drawDetectedLines(lineList, self.obj)
4063
        finally:
4064
            loop.quit()
4065

    
4066
    '''
4067
        @brief      draw detected lines
4068
        @author     euisung
4069
        @date       2018.11.27
4070
        @history    2018.11.27  euisung     no more used
4071
    '''
4072

    
4073
    def drawUnknownItems(self, path, loop):
4074
        try:
4075
            self.parent.drawUnknownItems(path)
4076
        finally:
4077
            loop.quit()
4078

    
4079
    def add_predata_to_scene(self, drawing, scene, symbol: bool, text: bool, line: bool, unknown: bool, package: bool) \
4080
            -> None:
4081
        """add predata to scene"""
4082
        from LoadCommand import LoadCommand
4083
        from App import App
4084

    
4085
        try:
4086
            cmd = LoadCommand()
4087
            cmd.display_message.connect(App.mainWnd().onAddMessage)
4088
            cmd.execute((drawing, scene), symbol=symbol, text=text, line=line, unknown=unknown,
4089
                        package=package, update=False)
4090
        except Exception as ex:
4091
            from App import App
4092
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
4093
                      f"{sys.exc_info()[-1].tb_lineno}"
4094
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4095
        finally:
4096
            self.cond.wakeAll()
4097

    
4098
    def clear_scene(self, scene1, scene2, scene3) -> None:
4099
        """clear scenes"""
4100

    
4101
        try:
4102
            scene1.clear()
4103
            scene2.clear()
4104
            scene3.clear()
4105

    
4106
        except Exception as ex:
4107
            from App import App
4108
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
4109
                      f"{sys.exc_info()[-1].tb_lineno}"
4110
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4111
        finally:
4112
            self.cond.wakeAll()
4113

    
4114
    def item_remove(self, items):
4115
        """ remove useless items """
4116
        from App import App
4117

    
4118
        try:
4119
            for item in items:
4120
                App.mainWnd().itemRemoved(item)
4121

    
4122
        except Exception as ex:
4123
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
4124
                                                           sys.exc_info()[-1].tb_lineno)
4125
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4126
        finally:
4127
            self.cond.wakeAll()
4128
    
4129
    def preset_execute(self, scene, find_symbol, replace_symbol, replace_action, condition):
4130
        """ run preset """
4131
        from App import App
4132

    
4133
        try:
4134
            from ReplaceInsertCommand import ReplaceInsertCommand
4135

    
4136
            cmd = ReplaceInsertCommand()
4137
            cmd.display_message.connect(App.mainWnd().onAddMessage)
4138
            cmd.execute(scene, find_symbol, replace_symbol, replace_action, condition)
4139
        except Exception as ex:
4140
            message = f"error occurred({repr(ex)}) in {sys.exc_info()[-1].tb_frame.f_code.co_filename}:" \
4141
                      f"{sys.exc_info()[-1].tb_lineno}"
4142
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4143
        finally:
4144
            self.cond.wakeAll()
4145

    
4146
    def save_scene(self, scene):
4147
        """ save scene """
4148
        from App import App
4149

    
4150
        try:
4151
            from SaveWorkCommand import SaveWorkCommand
4152

    
4153
            save = SaveWorkCommand(scene)
4154
            save.run()
4155
            #SaveWorkCommand.save_to_database()
4156
            #SaveWorkCommand.save_to_xml()
4157
        except Exception as ex:
4158
            message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename,
4159
                                                           sys.exc_info()[-1].tb_lineno)
4160
            App.mainWnd().addMessage.emit(MessageType.Error, message)
4161
        finally:
4162
            self.cond.wakeAll()
4163

    
4164
    def add_detected_items_to_scene(self, scene, flanges) -> None:
4165
        """add detected items to scene"""
4166
        from App import App
4167

    
4168
        app_doc_data = AppDocData.instance()
4169

    
4170
        try:
4171
            for item in scene.items():
4172
                if issubclass(type(item), QEngineeringVendorItem):
4173
                    app_doc_data.allItems.append(item)
4174

    
4175
            # symbol
4176
            for symbol in app_doc_data.symbols:
4177
                if issubclass(type(symbol), SymbolSvgItem):
4178
                    symbol.transfer.onRemoved.connect(App.mainWnd().itemRemoved)
4179
                    symbol.addSvgItemToScene(scene)
4180
                else:
4181
                    scene.addItem(symbol)
4182

    
4183
            # text
4184
            for text in app_doc_data.texts:
4185
                text.addTextItemToScene(scene)
4186

    
4187
            #for lineNo in app_doc_data.tracerLineNos:
4188
            #    lineNo.addTextItemToScene(scene)
4189

    
4190
            # remove lines which is located inside symbol
4191
            for symbol in app_doc_data.symbols:
4192
                rect = symbol.sceneBoundingRect()
4193
                rect.adjust(int(rect.width() / -10), int(rect.height() / -10), int(rect.width() / 10), int(rect.height() / 10))
4194
                matches = [line for line in app_doc_data.lines if rect.contains(line.line().p1()) and
4195
                           rect.contains(line.line().p2())]# and not line.has_connection]
4196
                for line in matches:
4197
                    app_doc_data.allItems.remove(line)
4198
                    app_doc_data.lines.remove(line)
4199
            # up to here
4200

    
4201
            for line in app_doc_data.lines:
4202
                scene.addItem(line)
4203
                # line.transfer.onRemoved.connect(self.itemRemoved)
4204
                for conn in line.connectors:
4205
                    conn.transfer.onPosChanged.connect(line.onConnectorPosChaned)
4206

    
4207
            # insert flange
4208
            configs = app_doc_data.getConfigs('Project', 'Operation')
4209
            instrument = int(configs[0].value) if configs else 1
4210
            if instrument == 1:
4211
                configs = app_doc_data.getConfigs('Default', 'Flange')
4212
                flange_name = configs[0].value if 1 == len(configs) else 'flange'
4213
                for flange in flanges[0]:
4214
                    svg = QtImageViewer.createSymbolObject(flange_name)
4215
                    if svg:
4216
                        QtImageViewer.matchSymbolToLine(scene, svg, QPointF(flange[0], flange[1]), strict=True, auto=True)
4217

    
4218
                configs = app_doc_data.getConfigs('Default', 'Blind')
4219
                flange_name = configs[0].value if 1 == len(configs) else 'blind flange'
4220
                for flange in flanges[1]:
4221
                    svg = QtImageViewer.createSymbolObject(flange_name)
4222
                    if svg:
4223
                        QtImageViewer.matchSymbolToLine(scene, svg, QPointF(flange[0], flange[1]), strict=False, auto=True)
4224

    
4225
                for unknown in app_doc_data.unknowns + app_doc_data.lineIndicators:
4226
                    scene.addItem(unknown)
4227

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