프로젝트

일반

사용자정보

개정판 47bd6b4e

ID47bd6b4ed2c2292031463e69c2c1802fe7101566
상위 93ed7203
하위 17111ffd

김정우 이(가) 6년 이상 전에 추가함

QRecognitionDialog 에 Line 인식 추가 및 그에 따른 Signal 등의 구조 변경

차이점 보기:

DTI_PID/DTI_PID/DTI_PID.py
501 501
    
502 502
    threadLock.acquire()
503 503
    srcName = os.path.splitext(os.path.basename(mainRes))[0]    
504
    listWidget.addItem('Finish symbol   : ' + os.path.basename(symbolPath.replace('.png', '')) + ' - (' + str(foundSymbolCount) + ')')
504
    listWidget.addItem('Finish Symbol   : ' + os.path.basename(symbolPath.replace('.png', '')) + ' - (' + str(foundSymbolCount) + ')')
505 505
    threadLock.release()
506 506

  
507 507
'''
......
799 799
            searchedSymbolList = []
800 800
            textInfoList = []
801 801

  
802
            start = timeit.default_timer()
802
            #start = timeit.default_timer()
803 803

  
804 804
            initMainSrc(mainRes)
805 805

  
......
863 863
            
864 864
            listWidget.addItem("Searched symbol count : " + str(len(searchedSymbolList)))
865 865

  
866
            stop = timeit.default_timer()    
867
            seconds = stop - start
866
            #stop = timeit.default_timer()    
867
            #seconds = stop - start
868 868

  
869
            listWidget.addItem("\nRunning Time : " + str(seconds / 60) + "min\n")
869
            #listWidget.addItem("\nRunning Time : " + str(seconds / 60) + "min\n")
870 870
    except Exception as ex:
871 871
        print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
872 872
    
DTI_PID/DTI_PID/MainWindow.py
437 437
        @history    2018.04.16  humkyung    execute line no tracing
438 438
                    2018.05.02  Jeongwoo    Show MessageBox when imageviewer doesn't have image
439 439
                    2018.05.25  Jeongwoo    Add parameter on QRecognitionDialog.__init__() and Move thread to QRecognitionDialog
440
                                            Remove codes below if self.dlg.isAccepted == True
440 441
    '''
441 442
    def recognize(self, MainWindow):
442 443
        from QRecognitionDialog import QRecognitionDialog
......
446 447
            return
447 448

  
448 449
        try:
449
            self.dlg = QRecognitionDialog(self, self.path)
450
            self.dlg = QRecognitionDialog(self.graphicsView, self.path)
451
            self.dlg.svgItemClicked.connect(self.svgItemClicked)
452
            self.dlg.itemRemoved.connect(self.itemRemoved)
450 453
            self.dlg.exec_()
451 454
            if self.dlg.isAccepted == True:
452
                if self.dlg.xmlPath is not None:
453
                    self.loadRecognitionResult(self.dlg.xmlPath[0])
455
                '''DO NOTHING'''
456
                #if self.dlg.xmlPath is not None:
457
                #    self.loadRecognitionResult(self.dlg.xmlPath[0])
454 458
        except Exception as ex:
455 459
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
456 460

  
457 461
    '''
458
        @brief  remove small objects from given image
459
        @author humkyung
460
        @date   2018.04.26
462
        @brief
463
        @date       2018.05.25
464
        @author     Jeongwoo
461 465
    '''
462
    def removeSmallObjects(self, image):
463
        try:
464
            docData = AppDocData.instance()
465
            configs = docData.getConfigs('Small Object Size', 'Min Area')
466
            minArea = int(configs[0].value) if 1 == len(configs) else 20
467
            configs = docData.getConfigs('Small Object Size', 'Max Area')
468
            maxArea = int(configs[0].value) if 1 == len(configs) else 50
469

  
470
            #path = os.path.join(AppDocData.instance().getCurrentProject().getTempPath(), 'before_contours.png')
471
            #cv2.imwrite(path, image)
472

  
473
            _,contours,_ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE);
474
            selectedContours=[]
475
            for contour in contours:
476
                #if cv2.isContourConvex(contour):
477
                    #approx = cv2.approxPolyDP(contour, 0.2*cv2.arcLength(contour, True), True)
478
                area = cv2.contourArea(contour)
479
                if area > minArea and area < maxArea: selectedContours.append(contour)
480
            contourImage = cv2.drawContours(image, selectedContours, -1, (255,255,255), -1);
481
            #path = os.path.join(AppDocData.instance().getCurrentProject().getTempPath(), 'contours.png')
482
            #cv2.imwrite(path, contourImage)
483
        except Exception as ex:
484
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
485

  
486
        return contourImage
487

  
466
    def svgItemClicked(self, svgItem):
467
        self.resultTreeWidget.findItem(svgItem)
468
        
469
    '''
470
        @brief
471
        @date       2018.05.25
472
        @author     Jeongwoo
473
    '''
474
    def itemRemoved(self, item):
475
        self.resultTreeWidget.itemRemoved(item)
476
            
488 477
    '''
489 478
        @brief      recognize line
490 479
        @author     humkyung
......
493 482
                                        TextItem type changed (QEngineeringTextItem → QEngineeringLineNoTextItem)
494 483
                    humkyung 2018.04.26 remove small objects before recognizing line
495 484
                    Jeongwoo 2018.05.02 Show MessageBox when imageviewer doesn't have image
485
                    Jeongwoo 2018.05.25 Move codes about LineDetector
496 486
    '''
497 487
    def recognizeLine(self, MainWindow):
498
        from LineDetector import LineDetector
499 488
        from LineNoTracer import LineNoTracer
500
        from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
501 489

  
502 490
        if not self.graphicsView.hasImage():
503 491
            self.showImageSelectionMessageBox()
504 492
            return
505 493

  
506 494
        try:
507
            #remove already existing line and flow arrow item
508
            items = [item for item in self.graphicsView.scene.items() if (type(item) is QEngineeringLineItem) or (type(item) is QEngineeringFlowArrowItem)]
509
            for item in items:
510
                self.graphicsView.scene.removeItem(item)
511
            #up to here
512

  
513
            # detect line
514
            connectedLines = []
515

  
516
            area = AppDocData.instance().getArea('Drawing')
517
            area.img = self.removeSmallObjects(area.img)
518
            detector = LineDetector(area.img)
519

  
520 495
            symbols = []
496
            lines = []
497
            lineNos = []
521 498
            for item in self.graphicsView.scene.items():
522 499
                if issubclass(type(item), SymbolSvgItem):
523 500
                    symbols.append(item)
524
                    res = detector.detectConnectedLine(item, round(area.x), round(area.y))
525
                    if res is not None:
526
                        for line in res: connectedLines.append(line)
527

  
528
            texts = [item for item in self.graphicsView.scene.items() if issubclass(type(item), QEngineeringTextItem)]
529
            for symbol in symbols:
530
                symbol.connectAttribute(texts)
531

  
532
            lineNos = [item for item in self.graphicsView.scene.items() if type(item) is QEngineeringLineNoTextItem]
533

  
534
            if len(connectedLines) > 1:
535
                detector.mergeLines(connectedLines, toler=20)
536
                # connect line to symbol
537
                try:
538
                    for line in connectedLines:
539
                        matches = [symbol for symbol in symbols if symbol.isConnectable(line, (round(area.x), round(area.y)), toler=20)]
540
                        for symbol in matches:
541
                            detector.connectLineToSymbol(line, (round(area.x), round(area.y)), symbol)
542
                except Exception as ex:
543
                    print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
544
                # up to here
545

  
546
                # connect line to line
547
                try:
548
                    for line in connectedLines[:]:
549
                        matches = [it for it in connectedLines if (line != it) and (not detector.isParallel(line, it))]
550
                        for match in matches:
551
                            detector.connectLineToLine(match, line)
552
                except Exception as ex:
553
                    print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
554
                # up to here
555

  
556
            lines = []
557
            for pts in connectedLines:
558
                processLine = QEngineeringLineItem()
559
                lines.append(processLine)
560
                for pt in pts:
561
                    processLine._pol.append(QPointF(pt[0] + round(area.x), pt[1] + round(area.y)))
562
                processLine.buildItem()
563
                self.graphicsView.scene.addItem(processLine)
501
                elif type(item) is QEngineeringLineNoTextItem:
502
                    lineNos.append(item)
503
                elif type(item) is QEngineeringLineItem:
504
                    lines.append(item)
564 505

  
565 506
            # trace line no
566 507
            tracer = LineNoTracer(symbols, lines, lineNos)
DTI_PID/DTI_PID/QRecognitionDialog.py
3 3
from PyQt5.QtGui import *
4 4
from PyQt5.QtWidgets import *
5 5
import Recognition_UI
6
import sys
7
import os
8
import cv2
9
from AppDocData import AppDocData
10
from LineDetector import LineDetector
11
from QEngineeringLineItem import QEngineeringLineItem
12
from QEngineeringLineNoTextItem import QEngineeringLineNoTextItem
13
from QEngineeringFlowArrowItem import QEngineeringFlowArrowItem
14
from SymbolSvgItem import SymbolSvgItem
15
from QEngineeringTextItem import QEngineeringTextItem
6 16

  
17
'''
18
    @history    2018.05.25  Jeongwoo    Add pyqtSignal(recognizeLine, loadRecognitionResult)
19
'''
7 20
class Worker(QObject):
8 21
    from PyQt5.QtCore import QThread
9 22
    from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout
10 23
    import sys
11
    from DTI_PID import executeRecognition
24
    import timeit
12 25

  
13 26
    finished = pyqtSignal()
14 27
    intReady = pyqtSignal(int)
28
    recognizeLine = pyqtSignal()
29
    loadRecognitionResult = pyqtSignal(str)
15 30

  
31
    '''
32
        @history    2018.05.25  Jeongwoo    Add if-statements by isSymbolTextChecked and isLineChecked variable
33
    '''
16 34
    #pyqtSlot()
17 35
    def procCounter(self, isSymbolTextChecked, isLineChecked): # A slot takes no params
18 36
        from DTI_PID import executeRecognition
37
        import timeit
19 38

  
20 39
        try:
40
            start = timeit.default_timer()
21 41
            self.xmlPath = None
22 42
            if isSymbolTextChecked:
23 43
                self.xmlPath = executeRecognition(self.path, self.listWidget)
44
                if self.xmlPath is not None and len(self.xmlPath) > 0:
45
                    self.loadRecognitionResult.emit(self.xmlPath[0])
46
            if isLineChecked:
47
                self.recognizeLine.emit()
24 48
            self.finished.emit()
25 49
        except Exception as ex:
26 50
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
51
        finally:
52
            stop = timeit.default_timer()    
53
            seconds = stop - start
54
            self.listWidget.addItem("\nRunning Time : " + str(seconds / 60) + "min\n")
27 55

  
56
'''
57
    @history    2018.05.25  Jeongwoo    Add pyqtSignal(svgItemClicked, itemRemoved)
58
'''
28 59
class QRecognitionDialog(QDialog):
60
    svgItemClicked = pyqtSignal(SymbolSvgItem)
61
    itemRemoved = pyqtSignal(QGraphicsItem)
62

  
29 63
    '''
30 64
        @history    2018.05.25  Jeongwoo    Add parameter and initialize / Connect recognizeButton signal and slot
31 65
    '''
32
    def __init__(self, parent, path):
33
        QDialog.__init__(self, parent)
66
    def __init__(self, graphicsView, path):
67
        QDialog.__init__(self, graphicsView)
34 68

  
69
        self.graphicsView = graphicsView
35 70
        self.path = path
36 71
        self.xmlPath = None
37 72
        self.ui = Recognition_UI.Ui_Recognition()
......
80 115

  
81 116
        # 4 - Connect Worker Signals to the Thread slots
82 117
        self.obj.finished.connect(self.thread.quit)
118
        self.obj.recognizeLine.connect(self.recognizeLine)
119
        self.obj.loadRecognitionResult.connect(self.loadRecognitionResult)
83 120

  
84 121
        # 5 - Connect Thread started signal to Worker operational slot method
85 122
        self.thread.started.connect(lambda : self.obj.procCounter(self.ui.symbolTextCheckBox.isChecked(), self.ui.lineCheckBox.isChecked()))
......
96 133
    '''
97 134
    def dlgExit(self):
98 135
        self.xmlPath = self.obj.xmlPath
99
        self.ui.buttonBox.setEnabled(True)
136
        self.ui.buttonBox.setEnabled(True)
137

  
138
    '''
139
        @brief  remove small objects from given image
140
        @author humkyung
141
        @date   2018.04.26
142
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
143
    '''
144
    def removeSmallObjects(self, image):
145
        try:
146
            docData = AppDocData.instance()
147
            configs = docData.getConfigs('Small Object Size', 'Min Area')
148
            minArea = int(configs[0].value) if 1 == len(configs) else 20
149
            configs = docData.getConfigs('Small Object Size', 'Max Area')
150
            maxArea = int(configs[0].value) if 1 == len(configs) else 50
151

  
152
            #path = os.path.join(AppDocData.instance().getCurrentProject().getTempPath(), 'before_contours.png')
153
            #cv2.imwrite(path, image)
154

  
155
            _,contours,_ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE);
156
            selectedContours=[]
157
            for contour in contours:
158
                #if cv2.isContourConvex(contour):
159
                    #approx = cv2.approxPolyDP(contour, 0.2*cv2.arcLength(contour, True), True)
160
                area = cv2.contourArea(contour)
161
                if area > minArea and area < maxArea: selectedContours.append(contour)
162
            contourImage = cv2.drawContours(image, selectedContours, -1, (255,255,255), -1);
163
            #path = os.path.join(AppDocData.instance().getCurrentProject().getTempPath(), 'contours.png')
164
            #cv2.imwrite(path, contourImage)
165
        except Exception as ex:
166
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
167

  
168
        return contourImage
169

  
170
    '''
171
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
172
                                            SvgItem and TextItem Connect with method in this class
173
    '''
174
    def loadRecognitionResult(self, xmlPath):
175
        from xml.etree.ElementTree import Element, SubElement, dump, ElementTree, parse
176
        from TextItemFactory import TextItemFactory
177

  
178
        try:
179
            project = AppDocData.instance().getCurrentProject()
180

  
181
            xml = parse(xmlPath)
182
            root = xml.getroot();
183
            for symbol in root.iter('SYMBOL'):
184
                pt = [float(x) for x in symbol.find('STARTPOINT').text.split(',')]
185
                size = [float(x) for x in symbol.find('SIZE').text.split(',')]
186
                name = symbol.find('NAME').text
187
                angle = float(symbol.find('ANGLE').text)
188
                type = symbol.find('TYPE').text
189
                origin = [float(x) for x in symbol.find('ORIGINALPOINT').text.split(',')]
190
                connPts = []
191
                if symbol.find('CONNECTIONPOINT').text is not None:
192
                    connPts = [(float(x.split(',')[0]), float(x.split(',')[1])) for x in symbol.find('CONNECTIONPOINT').text.split('/')]
193
                
194
                svgFilePath = os.path.join(project.getSvgFilePath(), type, name + '.svg')
195
                if os.path.isfile(svgFilePath):
196
                    svg = SymbolSvgItem.createItem(type, svgFilePath)
197
                    svg.buildItem(name, type, angle, pt, size, origin, connPts)
198

  
199
                    #### lambda param=svg : bind 'svg' object to lambda('param')
200
                    #### If case of 'lambda svg=svg:', function uses the 'svg' value bound to lambda
201
                    svg.clicked.connect(lambda param=svg: self.svgItemClickedEvent(param))
202
                    svg.removed.connect(lambda param=svg: self.itemRemovedEvent(param))
203
                    svg.addSvgItemToScene(self.graphicsView.scene)
204
                    for connector in svg.connectors:
205
                        self.graphicsView.scene.addItem(connector)
206
                else:
207
                    item = QGraphicsBoundingBoxItem(pt[0], pt[1], size[0], size[1])
208
                    item.isSymbol = True
209
                    item.angle = angle
210
                    item.setPen(QPen(Qt.red, 20, Qt.SolidLine))
211
                    self.graphicsView.scene.addItem(item)
212

  
213
            docData = AppDocData.instance()
214
            configs = docData.getConfigs('Line No', 'Delimiter')
215
            delimiter = configs[0].value if 1 == len(configs) else '-'
216
            lineNoconfigs = docData.getConfigs('Line No', 'Configuration')
217
            # parse texts
218
            for text in root.iter('TEXTINFO'):
219
                x = float(text.find('X').text) if text.find('X') is not None else 0
220
                y = float(text.find('Y').text) if text.find('Y') is not None else 0
221
                width = float(text.find('WIDTH').text) if text.find('WIDTH') is not None else 0
222
                height = float(text.find('HEIGHT').text) if text.find('HEIGHT') is not None else 0
223
                angle = float(text.find('ANGLE').text) if text.find('ANGLE') is not None else 0
224
                text = text.find('TEXT').text
225
                item = TextItemFactory.instance().createTextItem(text, delimiter, lineNoconfigs)
226
                if item is not None:
227
                    item.loc = (x, y)
228
                    item.size = (width, height)
229
                    item.angle = angle
230
                    item.setPlainText(text)
231
                    item.removed.connect(lambda param=item: self.itemRemovedEvent(param))
232
                    item.addTextItemToScene(self.graphicsView.scene)
233

  
234
            # parse notes
235
            for note in root.iter('NOTE'):
236
                x = float(note.find('X').text) if note.find('X') is not None else 0
237
                y = float(note.find('Y').text) if note.find('Y') is not None else 0
238
                width = float(note.find('WIDTH').text) if note.find('WIDTH') is not None else 0
239
                height = float(note.find('HEIGHT').text) if note.find('HEIGHT') is not None else 0
240
                angle = float(note.find('ANGLE').text) if note.find('ANGLE') is not None else 0
241
                text = note.find('TEXT').text
242
                item = TextItemFactory.instance().createTextItem(text)
243
                item.loc = (x, y)
244
                item.size = (width, height)
245
                item.angle = angle
246
                item.setPlainText(text)
247
                item.addTextItemToScene(self.graphicsView.scene)
248
                
249
                #self.graphicsView.scene.addItem(item)
250
            # up to here
251
        except Exception as ex:
252
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
253
    
254
    '''
255
        @history    2018.05.25  Jeongwoo    Moved from MainWindow
256
    '''
257
    def recognizeLine(self):
258
        try:
259
            #remove already existing line and flow arrow item
260
            items = [item for item in self.graphicsView.scene.items() if (type(item) is QEngineeringLineItem) or (type(item) is QEngineeringFlowArrowItem)]
261
            for item in items:
262
                self.graphicsView.scene.removeItem(item)
263
            #up to here
264

  
265
            # detect line
266
            connectedLines = []
267

  
268
            area = AppDocData.instance().getArea('Drawing')
269
            area.img = self.removeSmallObjects(area.img)
270
            detector = LineDetector(area.img)
271

  
272
            symbols = []
273
            for item in self.graphicsView.scene.items():
274
                if issubclass(type(item), SymbolSvgItem):
275
                    symbols.append(item)
276
                    res = detector.detectConnectedLine(item, round(area.x), round(area.y))
277
                    if res is not None:
278
                        for line in res: connectedLines.append(line)
279

  
280
            texts = [item for item in self.graphicsView.scene.items() if issubclass(type(item), QEngineeringTextItem)]
281
            for symbol in symbols:
282
                symbol.connectAttribute(texts)
283

  
284
            lineNos = [item for item in self.graphicsView.scene.items() if type(item) is QEngineeringLineNoTextItem]
285

  
286
            if len(connectedLines) > 1:
287
                detector.mergeLines(connectedLines, toler=20)
288
                # connect line to symbol
289
                try:
290
                    for line in connectedLines:
291
                        matches = [symbol for symbol in symbols if symbol.isConnectable(line, (round(area.x), round(area.y)), toler=20)]
292
                        for symbol in matches:
293
                            detector.connectLineToSymbol(line, (round(area.x), round(area.y)), symbol)
294
                except Exception as ex:
295
                    print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
296
                # up to here
297

  
298
                # connect line to line
299
                try:
300
                    for line in connectedLines[:]:
301
                        matches = [it for it in connectedLines if (line != it) and (not detector.isParallel(line, it))]
302
                        for match in matches:
303
                            detector.connectLineToLine(match, line)
304
                except Exception as ex:
305
                    print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
306
                # up to here
307

  
308
            lines = []
309
            for pts in connectedLines:
310
                processLine = QEngineeringLineItem()
311
                lines.append(processLine)
312
                for pt in pts:
313
                    processLine._pol.append(QPointF(pt[0] + round(area.x), pt[1] + round(area.y)))
314
                processLine.buildItem()
315
                self.graphicsView.scene.addItem(processLine)
316
        except Exception as ex:
317
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
318
        finally:
319
            self.ui.listWidget.addItem('Finish Line searching')
320

  
321
    '''
322
        @brief      Emit svgItem to slot on MainWindow
323
        @date       2018.05.25
324
        @author     Jeongwoo
325
    '''
326
    def svgItemClickedEvent(self, svgItem):
327
        self.svgItemClicked.emit(svgItem)
328
        
329
    '''
330
        @brief      Emit svgItem to slot on MainWindow
331
        @date       2018.05.25
332
        @author     Jeongwoo
333
    '''
334
    def itemRemovedEvent(self, item):
335
        self.itemRemoved.emit(item)
DTI_PID/DTI_PID/Shapes/QEngineeringLineItem.py
24 24
                    2018.05.15  Jeongwoo    Change method to call parent's __init__
25 25
    '''
26 26
    def __init__(self, parent=None):
27
        super(QEngineeringLineItem, self).__init__()
28
        #QGraphicsPolylineItem.__init__(self, parent)
27
        #super(QEngineeringLineItem, self).__init__()
28
        QGraphicsPolylineItem.__init__(self, parent)
29 29

  
30 30
        self.conns = [None, None]
31 31
        self._owner = None
DTI_PID/DTI_PID/Shapes/QGraphicsPolylineItem.py
16 16
    '''
17 17
        @history    2018.05.11  Jeongwoo    Declare variable self.pen
18 18
                    2018.05.15  Jeongwoo    Change method to call parent's __init__
19
                    2018.05.25  Jeongwoo    Change self.pen's default color (red → blue)
19 20
    '''
20 21
    def __init__(self, parent=None):
21 22
        import uuid
......
31 32
            self._pt = None
32 33
            self._pol = QPolygonF()
33 34
            self._path = None
34
            self.pen = QPen(Qt.red, 4, Qt.SolidLine)   # default pen
35
            self.pen = QPen(Qt.blue, 4, Qt.SolidLine)   # default pen
35 36
            self.setPen(self.pen)
36 37
        except Exception as ex:
37 38
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))

내보내기 Unified diff

클립보드 이미지 추가 (최대 크기: 500 MB)