프로젝트

일반

사용자정보

통계
| 개정판:

hytos / DTI_PID / DTI_PID / QtImageViewer.py @ dc05dcb9

이력 | 보기 | 이력해설 | 다운로드 (23.4 KB)

1
# coding: utf-8
2
import sys
3
import os.path
4
try:
5
    from PyQt5.QtCore import Qt, QRectF, pyqtSignal, QT_VERSION_STR, QPointF
6
    from PyQt5.QtGui import QImage, QPixmap, QPainterPath, QPainter, QCursor, QPen, QBrush, QColor, QTransform
7
    from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QFileDialog, QApplication
8
except ImportError:
9
    try:
10
        from PyQt4.QtCore import *
11
        from PyQt4.QtGui import *
12
    except ImportError:
13
        raise ImportError("ImageViewerQt: Requires PyQt5 or PyQt4.")
14
    
15
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '\\Commands')
16
import DefaultCommand
17

    
18
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '\\Shapes')
19
from EngineeringLineItem import QEngineeringLineItem
20
from SymbolSvgItem import SymbolSvgItem
21

    
22
__author__ = "Marcel Goldschen-Ohm <marcel.goldschen@gmail.com>"
23
__version__ = '0.9.0'
24

    
25

    
26
class QtImageViewer(QGraphicsView):
27
    """ PyQt image viewer widget for a QPixmap in a QGraphicsView scene with mouse zooming and panning.
28
    Displays a QImage or QPixmap (QImage is internally converted to a QPixmap).
29
    To display any other image format, you must first convert it to a QImage or QPixmap.
30
    Some useful image format conversion utilities:
31
        qimage2ndarray: NumPy ndarray <==> QImage    (https://github.com/hmeine/qimage2ndarray)
32
        ImageQt: PIL Image <==> QImage  (https://github.com/python-pillow/Pillow/blob/master/PIL/ImageQt.py)
33
    Mouse interaction:
34
        Left mouse button drag: Pan image.
35
        Right mouse button drag: Zoom box.
36
        Right mouse button doubleclick: Zoom to show entire image.
37
    """
38

    
39
    # Mouse button signals emit image scene (x, y) coordinates.
40
    # !!! For image (row, column) matrix indexing, row = y and column = x.
41
    leftMouseButtonPressed = pyqtSignal(float, float)
42
    rightMouseButtonPressed = pyqtSignal(float, float)
43
    leftMouseButtonMoved = pyqtSignal(float, float)
44
    rightMouseButtonMoved = pyqtSignal(float, float)
45
    leftMouseButtonReleased = pyqtSignal(float, float)
46
    rightMouseButtonReleased = pyqtSignal(float, float)
47
    leftMouseButtonDoubleClicked = pyqtSignal(float, float)
48
    rightMouseButtonDoubleClicked = pyqtSignal(float, float)
49
    #itemRemoved = pyqtSignal(QGraphicsItem)
50
    startPointChanged = pyqtSignal(float, float)
51

    
52
    '''
53
        @history    2018.06.27  Jeongwoo    Change zoom rule (Qt.KeepAspectRatioByExpanding → Qt.KeepAspectRatio)
54
    '''
55
    def __init__(self, mainWindow = None):
56
        QGraphicsView.__init__(self)
57

    
58
        self.mainWindow = mainWindow
59
        # Image is displayed as a QPixmap in a QGraphicsScene attached to this QGraphicsView.
60
        self.command = None
61
        self.scene = QGraphicsScene(self)
62
        self.setScene(self.scene)
63
        self.scene.setBackgroundBrush(Qt.gray)
64
        self.crosshairPos = None
65

    
66
        self.isPressCtrl = False
67
        self.scaleFactor = 1.0
68
        self.numScheduledScalings = 0
69
        self.isOriginalPointSelected = False
70

    
71
        # Store a local handle to the scene's current image pixmap.
72
        self._pixmapHandle = None
73

    
74
        # Image aspect ratio mode.
75
        # !!! ONLY applies to full image. Aspect ratio is always ignored when zooming.
76
        #   Qt.IgnoreAspectRatio: Scale image to fit viewport.
77
        #   Qt.KeepAspectRatio: Scale image to fit inside viewport, preserving aspect ratio.
78
        #   Qt.KeepAspectRatioByExpanding: Scale image to fill the viewport, preserving aspect ratio.
79
        self.aspectRatioMode = Qt.KeepAspectRatio
80

    
81
        # Scroll bar behaviour.
82
        #   Qt.ScrollBarAlwaysOff: Never shows a scroll bar.
83
        #   Qt.ScrollBarAlwaysOn: Always shows a scroll bar.
84
        #   Qt.ScrollBarAsNeeded: Shows a scroll bar only when zoomed.
85
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
86
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
87

    
88
        # Stack of QRectF zoom boxes in scene coordinates.
89
        self.zoomStack = []
90

    
91
        self.setRenderHint(QPainter.Antialiasing)
92

    
93
        self.setAcceptDrops(True)  # enable drop
94

    
95
        # Flags for enabling/disabling mouse interaction.
96
        self.canZoom = True
97
        self.canPan = True
98
        self.setMouseTracking(True)
99
        self.command = None
100

    
101
        # set currentAttribute
102
        self.currentAttribute = ''
103

    
104
    '''
105
        @brief      Return Pixmap Handler
106
        @author     Jeongwoo
107
        @date       2018.06.11
108
    '''
109
    def getPixmapHandle(self):
110
        return self._pixmapHandle
111

    
112
    '''
113
        @brief      Use Default ImageViewer Command
114
        @author     Jeongwoo
115
        @date       18.04.10
116
        @history    .
117
    '''
118
    def useDefaultCommand(self):
119
        """ Use Default Command
120
        """
121
        self.command = DefaultCommand.DefaultCommand(self)
122

    
123
    def hasImage(self):
124
        """ Returns whether or not the scene contains an image pixmap.
125
        """
126
        return self._pixmapHandle is not None
127

    
128
    def clearImage(self):
129
        """ Removes the current image pixmap from the scene if it exists.
130
        """
131
        if self.hasImage():
132
            self.scene.removeItem(self._pixmapHandle)
133
            self._pixmapHandle = None
134

    
135
    def pixmap(self):
136
        """ Returns the scene's current image pixmap as a QPixmap, or else None if no image exists.
137
        :rtype: QPixmap | None
138
        """
139
        if self.hasImage():
140
            return self._pixmapHandle.pixmap()
141
        return None
142

    
143
    def image(self):
144
        """ Returns the scene's current image pixmap as a QImage, or else None if no image exists.
145
        :rtype: QImage | None
146
        """
147
        if self.hasImage():
148
            return self._pixmapHandle.pixmap().toImage()
149
        return None
150

    
151
    def setImage(self, image):
152
        """ Set the scene's current image pixmap to the input QImage or QPixmap.
153
        Raises a RuntimeError if the input image has type other than QImage or QPixmap.
154
        :type image: QImage | QPixmap
155
        """
156
        try:
157
            if type(image) is QPixmap:
158
                pixmap = image
159
            elif type(image) is QImage:
160
                pixmap = QPixmap.fromImage(image)
161
            else:
162
                raise RuntimeError("ImageViewer.setImage: Argument must be a QImage or QPixmap.")
163

    
164
            self.clearImage()
165
            self.scene.clear()
166

    
167
            if self.hasImage():
168
                self._pixmapHandle.setPixmap(pixmap)
169
            else:
170
                self._pixmapHandle = self.scene.addPixmap(pixmap)
171

    
172
            self.setSceneRect(QRectF(pixmap.rect()))  # Set scene size to image size.
173
            self.updateViewer()
174
        except Exception as ex:
175
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
176

    
177
    '''
178
        @brief  open a image file selected by user
179
        @author 
180
        @date
181
    '''
182
    def loadImageFromFile(self, folder='', fileName=""):
183
        import cv2
184

    
185
        """ Load an image from file.
186
        Without any arguments, loadImageFromFile() will popup a file dialog to choose the image file.
187
        With a fileName argument, loadImageFromFile(fileName) will attempt to load the specified image file directly.
188
        """
189
        try:
190
            if len(fileName) == 0:
191
                options = QFileDialog.Options()
192
                options |= QFileDialog.DontUseNativeDialog
193
                if QT_VERSION_STR[0] == '4':
194
                    fileName = QFileDialog.getOpenFileName(self, "Open image file", os.getcwd() if folder == '' else folder, "Image files(*.png *.jpg)", options=options)
195
                elif QT_VERSION_STR[0] == '5':
196
                    fileName, dummy = QFileDialog.getOpenFileName(self, "Open image file", os.getcwd() if folder == '' else folder, "Image files(*.png *.jpg)", options=options)
197
            if len(fileName) and os.path.isfile(fileName):
198
                cvImg = cv2.cvtColor(cv2.imread(fileName), cv2.COLOR_BGR2GRAY)
199
                blur = cv2.GaussianBlur(cvImg, (5,5),0)
200
                cvImg = cv2.threshold(cvImg, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
201
                bytesPerLine = cvImg.shape[1]
202
                image = QImage(cvImg.data, cvImg.shape[1], cvImg.shape[0], bytesPerLine, QImage.Format_Indexed8)
203
                self.setImage(image)
204
        except Exception as ex:
205
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
206

    
207
        return fileName
208

    
209
    '''
210
        @history    2018.06.27  Jeongwoo    Change zoom rule (Qt.KeepAspectRatioByExpanding → Qt.KeepAspectRatio)
211
    '''
212
    def updateViewer(self):
213
        """ Show current zoom (if showing entire image, apply current aspect ratio mode).
214
        """
215
        if not self.hasImage():
216
            return
217
        if len(self.zoomStack):# and self.sceneRect().contains(self.zoomStack[-1]):
218
            self.fitInView(self.zoomStack[-1], Qt.KeepAspectRatio)  # Show zoomed rect (ignore aspect ratio).
219
        else:
220
            self.zoomStack = []  # Clear the zoom stack (in case we got here because of an invalid zoom).
221
            self.fitInView(self.sceneRect(), self.aspectRatioMode)  # Show entire image (use current aspect ratio mode).
222

    
223
    def zoomImageInit(self):
224
        if self.hasImage():
225
            self.zoomStack = []
226
            self.updateViewer()
227
            self.setCursor(QCursor(Qt.ArrowCursor))
228

    
229
    '''
230
        @brief      Zoom in & out image
231
        @author     Jeongwoo
232
        @date       -
233
        @history    18.04.11    Jeongwoo    add parameter 'adjust' (@ref ResultTreeWidget.itemClickEvent(self, item, columnNo))
234
    '''
235
    def zoomImage(self, isZoomIn, event, adjust = 1):
236
        """ Zoom in & out
237
        """
238
        HALF_SIZE = 300
239
        clickPos = event.pos()
240
        scenePos1 = self.mapToScene(clickPos.x() - HALF_SIZE//adjust, clickPos.y() - HALF_SIZE//adjust)
241
        scenePos2 = self.mapToScene(clickPos.x() + HALF_SIZE//adjust, clickPos.y() + HALF_SIZE//adjust)
242
        if isZoomIn:
243
            zoomArea = QRectF(QPointF(scenePos1.x() if scenePos1.x() > 0 else 0, scenePos1.y() if scenePos1.y() > 0 else 0), QPointF(scenePos2.x(), scenePos2.y()))
244
            #self.fitInView(zoomArea, Qt.KeepAspectRatioByExpanding)
245
            viewBBox = self.zoomStack[-1] if len(self.zoomStack) else self.sceneRect()
246
            selectionBBox = zoomArea.intersected(viewBBox)
247
            self.scene.setSelectionArea(QPainterPath())  # Clear current selection area.
248
            if selectionBBox.width() > HALF_SIZE*2 and selectionBBox.height() > HALF_SIZE*2:
249
                if selectionBBox.isValid() and (selectionBBox != viewBBox):
250
                    self.zoomStack.append(selectionBBox)
251
                    self.updateViewer()
252
        else:
253
            self.scene.setSelectionArea(QPainterPath())  # Clear current selection area.
254
            if len(self.zoomStack):
255
                self.zoomStack.pop()
256
            self.updateViewer()
257

    
258
    def resizeEvent(self, event):
259
        """ Maintain current zoom on resize.
260
        """
261
        self.updateViewer()
262

    
263
    '''
264
        @brief  mouse move event
265
    '''
266
    def mouseMoveEvent(self, event):
267
        try:
268
            if self.command is not None:
269
                scenePos = self.mapToScene(event.pos())
270
                self.command.execute(['mouseMoveEvent', event, scenePos])
271
                if self.command.isTreated == True: return
272
        except Exception as ex:
273
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
274

    
275
        QGraphicsView.mouseMoveEvent(self, event)
276

    
277
    '''
278
        @brief      
279
        @author     
280
        @date       
281
        @history    block clear selection when right mouse button is clicked
282
    '''
283
    def mousePressEvent(self, event):
284
        try:
285
            if self.command is not None:
286
                scenePos = self.mapToScene(event.pos())
287
                self.command.execute(['mousePressEvent', event, scenePos])
288
                if self.command.isTreated == True: return
289
        except Exception as ex:
290
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
291

    
292
        if event.button() != Qt.RightButton:
293
            QGraphicsView.mousePressEvent(self, event)
294

    
295
    def mouseReleaseEvent(self, event):
296
        """ Stop mouse pan or zoom mode (apply zoom if valid).
297
        """
298

    
299
        try:
300
            if self.command is not None:
301
                scenePos = self.mapToScene(event.pos())
302
                instance = self.command.execute(['mouseReleaseEvent', event, scenePos])
303
                if instance is not None:
304
                    self.scene.addItem(instance)
305

    
306
                if self.command is not None and self.command.isTreated == True: 
307
                    # SelectAttribute 경우만 추가
308
                    if self.command.name == "SelectAttribute":
309
                        items = self.scene.selectedItems()
310
                        if self.command.resultItem is not None and items is not None and len(items) == 1:
311
                            attrItem = self.command.resultItem
312
                            self.command.resultItem.attribute = self.currentAttribute
313
                            # 기존 연결되있는 다른 SymbolItem의 Attr 제거
314
                            items[0].removeAttr(attrItem)
315
                            # add attr
316
                            items[0].attrs.append(attrItem)
317
                            #resultPropertyTable refresh
318
                            self.mainWindow.refreshResultPropertyTableWidget()
319
                    elif self.command.name == 'Default' and self.command.isCopy:
320
                        return
321
                    self.command = DefaultCommand.DefaultCommand(self)
322
                    cursor = QCursor(Qt.ArrowCursor)
323
                    QApplication.instance().setOverrideCursor(cursor)
324
                    return
325
        except Exception as ex:
326
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
327

    
328
        QGraphicsView.mouseReleaseEvent(self, event)
329
        #self.mainWindow.refreshResultPropertyTableWidget()
330

    
331
    def mouseDoubleClickEvent(self, event):
332
        """ Show entire image.
333
        """
334

    
335
        scenePos = self.mapToScene(event.pos())
336
        if self.command is not None:
337
            instance = self.command.execute(['mouseDoubleClickEvent', event, scenePos])
338
            if self.command.isTreated == True: return
339

    
340
        if event.button() == Qt.LeftButton:
341
            self.leftMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y())
342
        elif event.button() == Qt.RightButton:
343
            if self.canZoom:
344
                self.zoomStack = []  # Clear zoom stack.
345
                self.updateViewer()
346
            self.rightMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y())
347
        
348
        QGraphicsView.mouseDoubleClickEvent(self, event)
349

    
350
    '''
351
        @brief      key press event
352
        @author     Jeongwoo
353
        @date       2018.??.??
354
        @history    send escape key event to command
355
    '''
356
    def keyPressEvent(self, event):
357
        try:
358
            if event.key() == Qt.Key_Control:
359
                self.isPressCtrl = True
360
            elif event.key() == Qt.Key_Delete:
361
                for item in self.scene.selectedItems():
362
                    item.transfer.onRemoved.emit(item)
363
            elif event.key() == Qt.Key_Escape:
364
                if self.command is not None:
365
                    self.command.execute(['keyPressEvent', event, []])
366
            else:
367
                if self.command is not None:
368
                    self.command.execute(['keyPressEvent', event, []])
369

    
370
            QGraphicsView.keyPressEvent(self, event)
371
        except Exception as ex:
372
            print('error occured({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno))
373

    
374
    '''
375
        @brief  key release event
376
        @author Jeongwoo
377
        @date   2018.??.??
378
    '''
379
    def keyReleaseEvent(self, event):
380
        if event.key() == Qt.Key_Control:
381
            self.isPressCtrl = False
382
        elif event.key() == Qt.Key_Delete:
383
            pass
384

    
385
        QGraphicsView.keyReleaseEvent(self, event)
386

    
387
    def wheelEvent(self, event):
388
        if self.isPressCtrl == True:
389
            if self.canZoom and self.hasImage():
390
                numDegrees = event.angleDelta() / 8
391
                if numDegrees is not None:
392
                    if numDegrees.y() > 0:
393
                        self.zoomImage(True, event)
394
                    elif numDegrees.y() < 0:
395
                        self.zoomImage(False, event)
396
                #print("Zoomable")
397
                #numDegrees = event.angleDelta().y() // 8
398
                #numSteps = numDegrees // 15
399
                #self.numScheduledScalings = self.numScheduledScalings + numSteps
400
                #if self.numScheduledScalings * numSteps < 0:
401
                #    self.numScheduledScalings = numSteps
402
                #self.scaleFactor = 1.0 + (self.numScheduledScalings / 300.0)
403
                #print("scaleFactor : " + str(self.scaleFactor))
404
                #self.scale(self.scaleFactor, self.scaleFactor)
405
        else:
406
            super().wheelEvent(event)
407

    
408
    def drawForeground(self, painter, rect):
409
        image = self.image()
410
        if image is not None:
411
            width = image.width()
412
            height = image.height()
413
            if self.crosshairPos is not None:
414
                pen = QPen()
415
                pen.setColor(QColor(180, 180, 180))
416
                pen.setStyle(Qt.DashLine)
417
                pen.setWidthF(0.3)
418
                painter.setPen(pen)
419
                painter.drawLine(self.crosshairPos.x(), 0, self.crosshairPos.x(), height)#Vertical
420
                painter.drawLine(0, self.crosshairPos.y(), width, self.crosshairPos.y())#Horizontal
421
            #else:
422
            #    painter.eraseRect(QRectF(0, 0, width, height))
423

    
424
    '''
425
        @brief      draw background
426
        @author     humkyung
427
        @date       2018.07.23
428
    '''        
429
    def drawBackground(self, painter, rect):
430
        QGraphicsView.drawBackground(self, painter, rect) 
431
        '''
432
        gridSize = max(int(rect.width() / 100), int(rect.height() / 100))
433
        left = int(rect.left()) - (int(rect.left()) % gridSize)
434
        top = int(rect.top()) - (int(rect.top()) % gridSize)
435

436
        pen = QPen()
437
        pen.setColor(Qt.black)
438
        pen.setWidthF(1.5)
439
        painter.setPen(pen)
440

441
        points = []
442
        for x in range(left, int(rect.right()), gridSize):
443
            for y in range(top, int(rect.bottom()), gridSize):
444
                painter.drawPoint(QPointF(x, y))
445
        '''
446

    
447
    '''
448
        @history    2018.06.11  Jeongwoo    Change method to manage guideline items
449
    '''
450
    GUIDELINE_ITEMS = []
451
    def showGuideline(self, isShow):
452
        image = self.image()
453
        width = image.width()
454
        height = image.height()
455
        pen = QPen()
456
        pen.setColor(QColor(180, 180, 180))
457
        pen.setStyle(Qt.DashLine)
458
        pen.setWidthF(0.5)
459
        if isShow:
460
            verticalLine = self.scene.addLine(width/2, 0, width/2, height, pen)
461
            horizontalLine = self.scene.addLine(0, height/2, width, height/2, pen)
462
            self.GUIDELINE_ITEMS.clear()
463
            self.GUIDELINE_ITEMS.append(verticalLine)
464
            self.GUIDELINE_ITEMS.append(horizontalLine)
465
        else:
466
            items = self.scene.items()
467
            for item in self.GUIDELINE_ITEMS:
468
                if item in items:
469
                    self.scene.removeItem(item)
470
            self.GUIDELINE_ITEMS.clear()
471

    
472
    '''
473
        @brief  drag enter event
474
        @author humkyung
475
        @date   2018.04.17
476
    '''
477
    def dragEnterEvent(self, event):
478
        event.acceptProposedAction()
479

    
480
    '''
481
        @brief  drag move event
482
        @author humkyung
483
        @date   2018.04.17
484
    '''
485
    def dragMoveEvent(self, event):
486
        event.acceptProposedAction()
487

    
488
    '''
489
        @brief  drop event
490
        @author humkyung
491
        @date   2018.04.17
492
        @history    2018.06.08  Jeongwoo    Add Paramter on SymbolSvgItem.buildItem()
493
    '''
494
    def dropEvent(self, event):
495
        from AppDocData import AppDocData
496
        import symbol
497
        
498
        scenePos = self.mapToScene(event.pos())
499
        svgFileName = event.mimeData().text()
500
        svg = self.createSymbolObject(svgFileName)
501
        self.matchSymbolToLine(svg, scenePos)
502
        event.acceptProposedAction()
503

    
504
    '''
505
        @brief  drop create Symbol
506
        @author kyouho
507
        @date   2018.07.27
508
    '''
509
    def createSymbolObject(self, svgFileName):
510
        from AppDocData import AppDocData
511
        import symbol
512
        
513
        symbol = AppDocData.instance().getSymbolByQuery('name', svgFileName)
514
        svgFilePath = os.path.join(AppDocData.instance().getCurrentProject().getSvgFilePath(), symbol.getType(), svgFileName+'.svg')
515
        svg = SymbolSvgItem.createItem(symbol.getType(), svgFilePath)
516
        connPts = None
517
        strConnPts = symbol.getConnectionPoint()
518
        if strConnPts is not None:
519
            connPts = [(float(x.split(',')[0]), float(x.split(',')[1])) for x in strConnPts.split('/')]
520

    
521
        svg.buildItem(svgFileName, symbol.getType(), 0, None, None, [float(x) for x in symbol.getOriginalPoint().split(',')], connPts, symbol.getBaseSymbol(), symbol.getAdditionalSymbol(), symbol.getHasInstrumentLabel())
522
        
523
        return svg
524
        
525
    '''
526
        @brief  match symbol to line
527
        @author kyouho
528
        @date   2018.07.27
529
    '''
530
    def matchSymbolToLine(self, svg, scenePos):
531
        matches = [item for item in self.scene.items() if (type(item) is QEngineeringLineItem) and (item.distanceTo((scenePos.x(), scenePos.y())) < 20)]
532
        if len(matches) == 1:
533
            matches[0].insertSymbol(svg, scenePos)
534
        else:
535
            transform = QTransform()
536
            transform.translate(scenePos.x() - svg.symbolOrigin[0], scenePos.y() - svg.symbolOrigin[1])
537
            svg.setTransform(transform)
538
            svg.loc = [scenePos.x() - svg.symbolOrigin[0], scenePos.y() - svg.symbolOrigin[1]]
539
            svg.size = [svg.boundingRect().width(), svg.boundingRect().height()]
540
            self.scene.addItem(svg)
541

    
542
        svg.transfer.onRemoved.connect(self.mainWindow.itemRemoved)
543

    
544
        self.scene.clearFocus()
545
        for item in self.scene.selectedItems():
546
            item.setSelected(False)
547

    
548
        self.setFocus()
549
        svg.setSelected(True)
550
        self.scene.setFocusItem(svg)
551

    
552
    '''
553
        @brief  find item by uid (SymbolSvgItem 기반, QEngineeringConnectorItem 제외, QEngineeringLineItem 포함)
554
        @author kyouho
555
        @date   2018.07.31
556
    '''
557
    def findItemByUid(self, uid):
558
        from EngineeringConnectorItem import QEngineeringConnectorItem
559
        items = [item for item in self.scene.items() if hasattr(item, 'uid')]
560
        for item in items:
561
            if item.uid == uid:
562
                return item
563
        
564
        return None
565
            
566

    
567

    
568
if __name__ == '__main__':
569
    import sys
570
    try:
571
        from PyQt5.QtWidgets import QApplication
572
    except ImportError:
573
        try:
574
            from PyQt4.QtGui import QApplication
575
        except ImportError:
576
            raise ImportError("ImageViewerQt: Requires PyQt5 or PyQt4.")
577
    print('Using Qt ' + QT_VERSION_STR)
578

    
579
    def handleLeftClick(x, y):
580
        row = int(y)
581
        column = int(x)
582
        print("Clicked on image pixel (row="+str(row)+", column="+str(column)+")")
583

    
584
    # Create the application.
585
    app = QApplication(sys.argv)
586

    
587
    # Create image viewer and load an image file to display.
588
    viewer = QtImageViewer(None)
589
    viewer.loadImageFromFile()  # Pops up file dialog.
590

    
591
    # Handle left mouse clicks with custom slot.
592
    viewer.leftMouseButtonPressed.connect(handleLeftClick)
593

    
594
    # Show viewer and run application.
595
    viewer.show()
596
    sys.exit(app.exec_())