hytos / DTI_PID / DTI_PID / QtImageViewer.py @ 25879e36
이력 | 보기 | 이력해설 | 다운로드 (32.5 KB)
1 |
# coding: utf-8
|
---|---|
2 |
import sys |
3 |
import os.path |
4 |
|
5 |
try:
|
6 |
from PyQt5.QtCore import * |
7 |
from PyQt5.QtGui import * |
8 |
from PyQt5.QtWidgets import * |
9 |
except ImportError: |
10 |
try:
|
11 |
from PyQt4.QtCore import * |
12 |
from PyQt4.QtGui import * |
13 |
except ImportError: |
14 |
raise ImportError("ImageViewerQt: Requires PyQt5 or PyQt4.") |
15 |
|
16 |
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '\\Commands') |
17 |
import DefaultCommand |
18 |
|
19 |
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '\\Shapes') |
20 |
from EngineeringLineItem import QEngineeringLineItem |
21 |
from EngineeringTextItem import QEngineeringTextItem |
22 |
from EngineeringSpecBreakItem import QEngineeringSpecBreakItem |
23 |
from SymbolSvgItem import SymbolSvgItem |
24 |
|
25 |
__author__ = "Marcel Goldschen-Ohm <marcel.goldschen@gmail.com>"
|
26 |
__version__ = '0.9.0'
|
27 |
|
28 |
|
29 |
class QtImageViewer(QGraphicsView): |
30 |
""" PyQt image viewer widget for a QPixmap in a QGraphicsView scene with mouse zooming and panning.
|
31 |
Displays a QImage or QPixmap (QImage is internally converted to a QPixmap).
|
32 |
To display any other image format, you must first convert it to a QImage or QPixmap.
|
33 |
Some useful image format conversion utilities:
|
34 |
qimage2ndarray: NumPy ndarray <==> QImage (https://github.com/hmeine/qimage2ndarray)
|
35 |
ImageQt: PIL Image <==> QImage (https://github.com/python-pillow/Pillow/blob/master/PIL/ImageQt.py)
|
36 |
Mouse interaction:
|
37 |
Left mouse button drag: Pan image.
|
38 |
Right mouse button drag: Zoom box.
|
39 |
Right mouse button doubleclick: Zoom to show entire image.
|
40 |
"""
|
41 |
|
42 |
# Mouse button signals emit image scene (x, y) coordinates.
|
43 |
# !!! For image (row, column) matrix indexing, row = y and column = x.
|
44 |
leftMouseButtonPressed = pyqtSignal(float, float) |
45 |
rightMouseButtonPressed = pyqtSignal(float, float) |
46 |
leftMouseButtonMoved = pyqtSignal(float, float) |
47 |
rightMouseButtonMoved = pyqtSignal(float, float) |
48 |
leftMouseButtonReleased = pyqtSignal(float, float) |
49 |
rightMouseButtonReleased = pyqtSignal(float, float) |
50 |
leftMouseButtonDoubleClicked = pyqtSignal(float, float) |
51 |
rightMouseButtonDoubleClicked = pyqtSignal(float, float) |
52 |
# itemRemoved = pyqtSignal(QGraphicsItem)
|
53 |
startPointChanged = pyqtSignal(float, float) |
54 |
|
55 |
'''
|
56 |
@history 2018.06.27 Jeongwoo Change zoom rule (Qt.KeepAspectRatioByExpanding → Qt.KeepAspectRatio)
|
57 |
'''
|
58 |
|
59 |
def __init__(self, mainWindow=None): |
60 |
QGraphicsView.__init__(self)
|
61 |
|
62 |
self.mainWindow = mainWindow
|
63 |
# Image is displayed as a QPixmap in a QGraphicsScene attached to this QGraphicsView.
|
64 |
self.command = None |
65 |
|
66 |
self.scaleFactor = 1.0 |
67 |
self.numScheduledScalings = 0 |
68 |
self.isOriginalPointSelected = False |
69 |
|
70 |
# Store a local handle to the scene's current image pixmap.
|
71 |
self._pixmapHandle = None |
72 |
|
73 |
# Image aspect ratio mode.
|
74 |
# !!! ONLY applies to full image. Aspect ratio is always ignored when zooming.
|
75 |
# Qt.IgnoreAspectRatio: Scale image to fit viewport.
|
76 |
# Qt.KeepAspectRatio: Scale image to fit inside viewport, preserving aspect ratio.
|
77 |
# Qt.KeepAspectRatioByExpanding: Scale image to fill the viewport, preserving aspect ratio.
|
78 |
self.aspectRatioMode = Qt.KeepAspectRatio
|
79 |
|
80 |
# Scroll bar behaviour.
|
81 |
# Qt.ScrollBarAlwaysOff: Never shows a scroll bar.
|
82 |
# Qt.ScrollBarAlwaysOn: Always shows a scroll bar.
|
83 |
# Qt.ScrollBarAsNeeded: Shows a scroll bar only when zoomed.
|
84 |
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
85 |
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
86 |
# self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
87 |
# self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
88 |
|
89 |
# Stack of QRectF zoom boxes in scene coordinates.
|
90 |
self.zoomStack = []
|
91 |
|
92 |
self.setRenderHint(QPainter.Antialiasing)
|
93 |
|
94 |
self.setAcceptDrops(True) # enable drop |
95 |
|
96 |
# Flags for enabling/disabling mouse interaction.
|
97 |
self.canZoom = True |
98 |
self.canPan = True |
99 |
self.setMouseTracking(True) |
100 |
self.command = None |
101 |
|
102 |
# set currentAttribute
|
103 |
self.currentAttribute = '' |
104 |
|
105 |
'''
|
106 |
@brief Return Pixmap Handler
|
107 |
@author Jeongwoo
|
108 |
@date 2018.06.11
|
109 |
'''
|
110 |
|
111 |
def getPixmapHandle(self): |
112 |
return self._pixmapHandle |
113 |
|
114 |
'''
|
115 |
@brief Use Default ImageViewer Command
|
116 |
@author Jeongwoo
|
117 |
@date 18.04.10
|
118 |
@history .
|
119 |
'''
|
120 |
|
121 |
def useDefaultCommand(self): |
122 |
""" Use Default Command
|
123 |
"""
|
124 |
self.command = DefaultCommand.DefaultCommand(self) |
125 |
|
126 |
def hasImage(self): |
127 |
""" Returns whether or not the scene contains an image pixmap.
|
128 |
"""
|
129 |
return self._pixmapHandle is not None |
130 |
|
131 |
def clearImage(self): |
132 |
""" Removes the current image pixmap from the scene if it exists.
|
133 |
"""
|
134 |
if self.hasImage(): |
135 |
self.scene().removeItem(self._pixmapHandle) |
136 |
self._pixmapHandle = None |
137 |
|
138 |
def pixmap(self): |
139 |
""" Returns the scene's current image pixmap as a QPixmap, or else None if no image exists.
|
140 |
:rtype: QPixmap | None
|
141 |
"""
|
142 |
if self.hasImage(): |
143 |
return self._pixmapHandle.pixmap() |
144 |
return None |
145 |
|
146 |
def image(self): |
147 |
""" Returns the scene's current image pixmap as a QImage, or else None if no image exists.
|
148 |
:rtype: QImage | None
|
149 |
"""
|
150 |
if self.hasImage(): |
151 |
return self._pixmapHandle.pixmap().toImage() |
152 |
return None |
153 |
|
154 |
def setImage(self, image): |
155 |
""" Set the scene's current image pixmap to the input QImage or QPixmap.
|
156 |
Raises a RuntimeError if the input image has type other than QImage or QPixmap.
|
157 |
:type image: QImage | QPixmap
|
158 |
"""
|
159 |
try:
|
160 |
if type(image) is QPixmap: |
161 |
pixmap = image |
162 |
elif type(image) is QImage: |
163 |
pixmap = QPixmap.fromImage(image) |
164 |
else:
|
165 |
raise RuntimeError("ImageViewer.setImage: Argument must be a QImage or QPixmap.") |
166 |
|
167 |
self.clearImage()
|
168 |
self.scene().clear()
|
169 |
|
170 |
if self.hasImage(): |
171 |
self._pixmapHandle.setPixmap(pixmap)
|
172 |
else:
|
173 |
self._pixmapHandle = self.scene().addPixmap(pixmap) |
174 |
self._pixmapHandle.setFlags(QGraphicsItem.ItemClipsChildrenToShape)
|
175 |
|
176 |
self.setSceneRect(QRectF(pixmap.rect())) # Set scene size to image size. |
177 |
self.updateViewer()
|
178 |
except Exception as ex: |
179 |
print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
180 |
sys.exc_info()[-1].tb_lineno))
|
181 |
|
182 |
'''
|
183 |
@brief open a image file selected by user
|
184 |
@author
|
185 |
@date
|
186 |
'''
|
187 |
|
188 |
def loadImageFromFile(self, drawing): |
189 |
import cv2 |
190 |
import numpy as np |
191 |
from AppDocData import AppDocData |
192 |
""" Load an image from file.
|
193 |
Without any arguments, loadImageFromFile() will popup a file dialog to choose the image file.
|
194 |
With a fileName argument, loadImageFromFile(fileName) will attempt to load the specified image file directly.
|
195 |
"""
|
196 |
|
197 |
file_path = None
|
198 |
try:
|
199 |
app_doc_data = AppDocData.instance() |
200 |
|
201 |
cvImg = None
|
202 |
if drawing:
|
203 |
file_path = drawing.file_path |
204 |
cvImg = drawing.image |
205 |
else:
|
206 |
options = QFileDialog.Options() |
207 |
options |= QFileDialog.DontUseNativeDialog |
208 |
if QT_VERSION_STR[0] == '4': |
209 |
file_path = QFileDialog.getOpenFileName(self, "Open image file", |
210 |
app_doc_data.project.getDrawingFilePath(), |
211 |
"Image files(*.png *.jpg)", options=options)
|
212 |
elif QT_VERSION_STR[0] == '5': |
213 |
file_path, _ = QFileDialog.getOpenFileName(self, "Open image file", |
214 |
app_doc_data.project.getDrawingFilePath(), |
215 |
"Image files(*.png *.jpg)", options=options)
|
216 |
|
217 |
_bytes = None
|
218 |
with open(file_path.encode('utf-8'), 'rb') as stream: |
219 |
_bytes = stream.read() |
220 |
|
221 |
numpyArray = np.asarray(bytearray(_bytes), dtype=np.uint8)
|
222 |
image = cv2.imdecode(numpyArray, cv2.IMREAD_UNCHANGED) |
223 |
cvImg = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) |
224 |
cvImg = cv2.threshold(cvImg, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1] |
225 |
|
226 |
configs = app_doc_data.getConfigs('Filter', 'DilateSize') |
227 |
if 1 == len(configs) and int(configs[0].value) is not 0: |
228 |
size = int(configs[0].value) |
229 |
kernel = np.ones((size, size), np.uint8) |
230 |
cvImg = cv2.erode(cvImg, kernel, iterations=1)
|
231 |
|
232 |
configs = app_doc_data.getConfigs('Filter', 'FlatSize') |
233 |
if 1 == len(configs) and int(configs[0].value) is not 0: |
234 |
size = int(configs[0].value) |
235 |
kernel = np.ones((size, size), np.uint8) |
236 |
cvImg = cv2.morphologyEx(cvImg, cv2.MORPH_CLOSE, kernel) |
237 |
cvImg = cv2.morphologyEx(cvImg, cv2.MORPH_OPEN, kernel) |
238 |
|
239 |
bytesPerLine = cvImg.shape[1]
|
240 |
image = QImage(cvImg.data, cvImg.shape[1], cvImg.shape[0], bytesPerLine, QImage.Format_Indexed8) |
241 |
self.setImage(image)
|
242 |
except Exception as ex: |
243 |
from App import App |
244 |
from AppDocData import MessageType |
245 |
|
246 |
message = 'error occurred({}) in {}:{}'.format(repr(ex), sys.exc_info()[-1].tb_frame.f_code.co_filename, |
247 |
sys.exc_info()[-1].tb_lineno)
|
248 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
249 |
|
250 |
return file_path
|
251 |
|
252 |
'''
|
253 |
@history 2018.06.27 Jeongwoo Change zoom rule (Qt.KeepAspectRatioByExpanding → Qt.KeepAspectRatio)
|
254 |
'''
|
255 |
|
256 |
def updateViewer(self, zoomNewRect=None): |
257 |
"""Show current zoom (if showing entire image, apply current aspect ratio mode)."""
|
258 |
if not self.hasImage(): |
259 |
return
|
260 |
|
261 |
if zoomNewRect is not None: |
262 |
self.fitInView(zoomNewRect, Qt.KeepAspectRatio)
|
263 |
else:
|
264 |
if self.zoomStack: |
265 |
if zoomNewRect is None: |
266 |
self.fitInView(self.zoomStack[-1], Qt.KeepAspectRatio) # Show zoomed rect (ignore aspect ratio). |
267 |
else:
|
268 |
self.zoomStack = [] # Clear the zoom stack (in case we got here because of an invalid zoom). |
269 |
self.fitInView(self.sceneRect(), |
270 |
self.aspectRatioMode) # Show entire image (use current aspect ratio mode). |
271 |
|
272 |
def zoomImageInit(self): |
273 |
#from EngineeringConnectorItem import QEngineeringConnectorItem
|
274 |
|
275 |
if self.hasImage(): |
276 |
self.zoomStack = []
|
277 |
self.updateViewer()
|
278 |
self.setCursor(QCursor(Qt.ArrowCursor))
|
279 |
|
280 |
#for conn in [item for item in self.scene().items() if type(item) is QEngineeringConnectorItem]:
|
281 |
# conn.setVisible(False)
|
282 |
|
283 |
'''
|
284 |
@brief Zoom in & out image
|
285 |
@author Jeongwoo
|
286 |
@date -
|
287 |
@history 18.04.11 Jeongwoo add parameter 'adjust' (@ref ResultTreeWidget.itemClickEvent(self, item, columnNo))
|
288 |
'''
|
289 |
def zoomImage(self, isZoomIn, event, adjust=1): |
290 |
"""Zoom in & out """
|
291 |
|
292 |
HALF_SIZE = 300
|
293 |
clickPos = event.pos() |
294 |
if isZoomIn:
|
295 |
left_top = self.mapToScene(clickPos.x() - HALF_SIZE // adjust, clickPos.y() - HALF_SIZE // adjust)
|
296 |
right_bottom = self.mapToScene(clickPos.x() + HALF_SIZE // adjust, clickPos.y() + HALF_SIZE // adjust)
|
297 |
|
298 |
zoomArea = QRectF(left_top, right_bottom) |
299 |
self.scene().setSelectionArea(QPainterPath()) # Clear current selection area. |
300 |
if zoomArea.isValid():
|
301 |
self.zoomStack.append(zoomArea)
|
302 |
self.updateViewer(zoomArea)
|
303 |
else:
|
304 |
zoomNewRect = None
|
305 |
clickPos = self.mapToScene(clickPos.x() // adjust, clickPos.y() // adjust)
|
306 |
self.scene().clearSelection()
|
307 |
if self.zoomStack: |
308 |
zoomNewRect = self.zoomStack.pop()
|
309 |
zoomNewRect = QRectF(clickPos.x() - zoomNewRect.width() / 2, clickPos.y() - zoomNewRect.height() / 2, zoomNewRect.width(), zoomNewRect.height()) |
310 |
self.updateViewer(zoomNewRect)
|
311 |
|
312 |
'''
|
313 |
@brief mouse move event
|
314 |
'''
|
315 |
def mouseMoveEvent(self, event): |
316 |
try:
|
317 |
scenePos = self.mapToScene(event.pos())
|
318 |
if self.command is not None: |
319 |
self.command.execute(['mouseMoveEvent', event, scenePos]) |
320 |
if self.command.name == "SelectAttribute": |
321 |
QGraphicsView.mouseMoveEvent(self, event)
|
322 |
if self.command.isTreated: |
323 |
event.accept() |
324 |
return
|
325 |
except Exception as ex: |
326 |
print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
327 |
sys.exc_info()[-1].tb_lineno))
|
328 |
|
329 |
if self.scene().guidesEnabled: |
330 |
self.scene().coords = self.mapToScene(event.pos()) |
331 |
self.scene().invalidate()
|
332 |
|
333 |
QGraphicsView.mouseMoveEvent(self, event)
|
334 |
|
335 |
'''
|
336 |
@brief
|
337 |
@author
|
338 |
@date
|
339 |
@history block clear selection when right mouse button is clicked
|
340 |
'''
|
341 |
|
342 |
def mousePressEvent(self, event): |
343 |
try:
|
344 |
if self.command is not None: |
345 |
scenePos = self.mapToScene(event.pos())
|
346 |
self.command.execute(['mousePressEvent', event, scenePos]) |
347 |
if self.command.isTreated: |
348 |
event.accept() |
349 |
return
|
350 |
except Exception as ex: |
351 |
print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
352 |
sys.exc_info()[-1].tb_lineno))
|
353 |
|
354 |
if event.button() != Qt.RightButton:
|
355 |
QGraphicsView.mousePressEvent(self, event)
|
356 |
|
357 |
'''
|
358 |
@brief
|
359 |
@author
|
360 |
@date
|
361 |
'''
|
362 |
|
363 |
def mouseReleaseEvent(self, event): |
364 |
try:
|
365 |
if self.command is not None: |
366 |
scenePos = self.mapToScene(event.pos())
|
367 |
instance = self.command.execute(['mouseReleaseEvent', event, scenePos]) |
368 |
if instance is not None: |
369 |
self.scene().addItem(instance)
|
370 |
|
371 |
if self.command is not None and self.command.isTreated == True: |
372 |
if self.command.name == 'Default' and self.command.isCopy: |
373 |
return
|
374 |
self.command = DefaultCommand.DefaultCommand(self) |
375 |
cursor = QCursor(Qt.ArrowCursor) |
376 |
QApplication.instance().setOverrideCursor(cursor) |
377 |
return
|
378 |
except Exception as ex: |
379 |
print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
380 |
sys.exc_info()[-1].tb_lineno))
|
381 |
|
382 |
QGraphicsView.mouseReleaseEvent(self, event)
|
383 |
|
384 |
"""
|
385 |
@brief Show entire image.
|
386 |
"""
|
387 |
|
388 |
def mouseDoubleClickEvent(self, event): |
389 |
scenePos = self.mapToScene(event.pos())
|
390 |
if self.command is not None: |
391 |
instance = self.command.execute(['mouseDoubleClickEvent', event, scenePos]) |
392 |
if self.command.isTreated == True: return |
393 |
|
394 |
if event.button() == Qt.LeftButton:
|
395 |
self.leftMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y())
|
396 |
"""
|
397 |
elif event.button() == Qt.RightButton:
|
398 |
if self.canZoom:
|
399 |
self.zoomStack = [] # Clear zoom stack.
|
400 |
self.updateViewer()
|
401 |
self.rightMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y())
|
402 |
"""
|
403 |
|
404 |
QGraphicsView.mouseDoubleClickEvent(self, event)
|
405 |
|
406 |
'''
|
407 |
@brief key press event
|
408 |
@author Jeongwoo
|
409 |
@date 2018.??.??
|
410 |
@history send escape key event to command
|
411 |
'''
|
412 |
|
413 |
def keyPressEvent(self, event): |
414 |
from TrainingEditorDialog import QTrainingEditorDialog |
415 |
from TrainingSymbolEditorDialog import QTrainingSymbolEditorDialog |
416 |
|
417 |
try:
|
418 |
if event.key() == Qt.Key_Escape:
|
419 |
if self.command is not None: |
420 |
self.command.execute(['keyPressEvent', event, []]) |
421 |
if self.command.isTreated: return |
422 |
else:
|
423 |
if self.command is not None: |
424 |
self.command.execute(['keyPressEvent', event, []]) |
425 |
if self.command.isTreated: return |
426 |
if type(self.mainWindow) is QTrainingEditorDialog or type(self.mainWindow) is QTrainingSymbolEditorDialog: |
427 |
self.mainWindow.keyPressEvent(event)
|
428 |
|
429 |
QGraphicsView.keyPressEvent(self, event)
|
430 |
except Exception as ex: |
431 |
print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
432 |
sys.exc_info()[-1].tb_lineno))
|
433 |
|
434 |
'''
|
435 |
@brief key release event
|
436 |
@author Jeongwoo
|
437 |
@date 2018.??.??
|
438 |
'''
|
439 |
|
440 |
def keyReleaseEvent(self, event): |
441 |
if event.key() == Qt.Key_Delete:
|
442 |
pass
|
443 |
|
444 |
QGraphicsView.keyReleaseEvent(self, event)
|
445 |
|
446 |
'''
|
447 |
@brief mouse wheel event
|
448 |
@autor humkyung
|
449 |
@date
|
450 |
'''
|
451 |
|
452 |
def wheelEvent(self, event): |
453 |
if event.modifiers() == Qt.ControlModifier:
|
454 |
if self.canZoom and self.hasImage(): |
455 |
numDegrees = event.angleDelta() / 8
|
456 |
if numDegrees is not None: |
457 |
if numDegrees.y() > 0: |
458 |
self.zoomImage(True, event) |
459 |
elif numDegrees.y() < 0: |
460 |
self.zoomImage(False, event) |
461 |
else:
|
462 |
super().wheelEvent(event)
|
463 |
|
464 |
'''
|
465 |
@brief draw background
|
466 |
@author humkyung
|
467 |
@date 2018.07.23
|
468 |
'''
|
469 |
|
470 |
"""
|
471 |
def drawBackground(self, painter, rect):
|
472 |
QGraphicsView.drawBackground(self, painter, rect)
|
473 |
"""
|
474 |
|
475 |
'''
|
476 |
@history 2018.06.11 Jeongwoo Change method to manage guideline items
|
477 |
humkyung 2018.08.28 remove guide lines before drawing
|
478 |
'''
|
479 |
GUIDELINE_ITEMS = [] |
480 |
|
481 |
def showGuideline(self, pos, isShow): |
482 |
image = self.image()
|
483 |
width = image.width() |
484 |
height = image.height() |
485 |
pen = QPen() |
486 |
pen.setColor(QColor(180, 180, 180)) |
487 |
pen.setStyle(Qt.DashLine) |
488 |
pen.setWidthF(0.5)
|
489 |
if isShow:
|
490 |
items = self.scene().items()
|
491 |
for item in self.GUIDELINE_ITEMS: |
492 |
if item in items: |
493 |
self.scene().removeItem(item)
|
494 |
self.GUIDELINE_ITEMS.clear()
|
495 |
|
496 |
if pos is not None: |
497 |
verticalLine = self.scene().addLine(pos.x(), 0, pos.x(), height, pen) |
498 |
horizontalLine = self.scene().addLine(0, pos.y(), width, pos.y(), pen) |
499 |
else:
|
500 |
verticalLine = self.scene().addLine(round(width * 0.5), 0, round(width * 0.5), height, pen) |
501 |
horizontalLine = self.scene().addLine(0, round(height * 0.5), width, round(height * 0.5), pen) |
502 |
|
503 |
self.GUIDELINE_ITEMS.append(verticalLine)
|
504 |
self.GUIDELINE_ITEMS.append(horizontalLine)
|
505 |
else:
|
506 |
items = self.scene().items()
|
507 |
for item in self.GUIDELINE_ITEMS: |
508 |
if item in items: |
509 |
self.scene().removeItem(item)
|
510 |
self.GUIDELINE_ITEMS.clear()
|
511 |
|
512 |
'''
|
513 |
@brief drag enter event
|
514 |
@author humkyung
|
515 |
@date 2018.04.17
|
516 |
'''
|
517 |
|
518 |
def dragEnterEvent(self, event): |
519 |
event.acceptProposedAction() |
520 |
|
521 |
'''
|
522 |
@brief drag move event
|
523 |
@author humkyung
|
524 |
@date 2018.04.17
|
525 |
@history humkyung 2018.08.21 highlight item under mouse
|
526 |
'''
|
527 |
|
528 |
def dragMoveEvent(self, event): |
529 |
scenePos = self.mapToScene(event.pos())
|
530 |
items = [item for item in self.scene().items(scenePos) if |
531 |
type(item) is not QGraphicsPixmapItem and type(item) is not QGraphicsTextItem] |
532 |
if len(items) > 0: |
533 |
if not hasattr(self, '_underItem') or self._underItem is not items[0]: |
534 |
if hasattr(self, '_underItem') and self._underItem is not None: |
535 |
if hasattr(self._underItem, 'highlight') and self._underItem in self.scene().items(): |
536 |
self._underItem.highlight(False) |
537 |
else:
|
538 |
self._underItem = None |
539 |
|
540 |
self._underItem = items[0] |
541 |
if hasattr(self._underItem, 'highlight'): |
542 |
self._underItem.highlight(True) |
543 |
# elif hasattr(self, '_underItem') and self._underItem is not None:
|
544 |
# self._underItem.hoverLeaveEvent(event)
|
545 |
# self._underItem = None
|
546 |
|
547 |
event.acceptProposedAction() |
548 |
|
549 |
def dropEvent(self, event): |
550 |
"""drop a symbol"""
|
551 |
from AppDocData import AppDocData |
552 |
import symbol |
553 |
|
554 |
if len(self.scene().items()) is 0: |
555 |
return
|
556 |
if hasattr(self, '_underItem') and self._underItem is not None and self._underItem in self.scene().items(): |
557 |
self._underItem.hoverLeaveEvent(None) |
558 |
self._underItem = None |
559 |
else:
|
560 |
self._underItem = None |
561 |
|
562 |
scenePos = self.mapToScene(event.pos())
|
563 |
name = event.mimeData().text() |
564 |
svg = QtImageViewer.createSymbolObject(name) |
565 |
QtImageViewer.matchSymbolToLine(self.scene(), svg, scenePos)
|
566 |
if svg:
|
567 |
svg.setSelected(True)
|
568 |
svg.setFocus() |
569 |
self.setFocus() # set focus to graphicview |
570 |
|
571 |
event.acceptProposedAction() |
572 |
|
573 |
'''
|
574 |
@brief drop create Symbol
|
575 |
@author kyouho
|
576 |
@date 2018.07.27
|
577 |
'''
|
578 |
@staticmethod
|
579 |
def createSymbolObject(name): |
580 |
"""create a symbol object has given uid"""
|
581 |
from AppDocData import AppDocData |
582 |
|
583 |
app_doc_data = AppDocData.instance() |
584 |
|
585 |
symbol = app_doc_data.getSymbolByQuery('Name', name)
|
586 |
if symbol:
|
587 |
svg_file_name = symbol.sName |
588 |
svgFilePath = os.path.join(app_doc_data.getCurrentProject().getSvgFilePath(), symbol.getType(), |
589 |
svg_file_name + '.svg')
|
590 |
svg = SymbolSvgItem.createItem(symbol.getType(), None, svgFilePath)
|
591 |
connPts = None
|
592 |
strConnPts = symbol.getConnectionPoint() |
593 |
if strConnPts is not None and strConnPts != '': |
594 |
connPts = [(float(x.split(',')[0]), float(x.split(',')[1])) if len(x.split(',')) == 2 else ( |
595 |
x.split(',')[0], float(x.split(',')[1]), float(x.split(',')[2])) \ |
596 |
for x in strConnPts.split('/')] |
597 |
|
598 |
svg.buildItem(svg_file_name, symbol.getType(), 0, None, None, None, connPts, symbol.getBaseSymbol(), |
599 |
symbol.getAdditionalSymbol(), symbol.getHasInstrumentLabel()) |
600 |
|
601 |
return svg
|
602 |
else:
|
603 |
return None |
604 |
|
605 |
'''
|
606 |
@brief match symbol to line
|
607 |
@author kyouho
|
608 |
@date 2018.07.27
|
609 |
@history humkyung 2018.08.23 change scenePos to connector's center when symbol is placed on connector
|
610 |
'''
|
611 |
@staticmethod
|
612 |
def matchSymbolToLine(scene, svg, scenePos, angle=None, flip=None, strict=False, auto=False): |
613 |
from EngineeringConnectorItem import QEngineeringConnectorItem |
614 |
from EngineeringLineItem import QEngineeringLineItem |
615 |
from SymbolSvgItem import SymbolSvgItem |
616 |
import math |
617 |
from App import App |
618 |
|
619 |
try:
|
620 |
svg.transfer.onRemoved.connect(App.mainWnd().itemRemoved) |
621 |
|
622 |
items = [item for item in scene.items(scenePos) if |
623 |
type(item) is not QGraphicsPixmapItem and type(item) is not QGraphicsTextItem] |
624 |
connectors = [] |
625 |
if len(items) > 0 and type(items[0]) is QEngineeringConnectorItem: |
626 |
scenePos = QPointF(items[0].center()[0], items[0].center()[1]) |
627 |
connectors = [connector for connector in items if issubclass(type(connector.parentItem()), SymbolSvgItem) \ |
628 |
and type(connector) is QEngineeringConnectorItem] |
629 |
if not connectors: |
630 |
connectors = [connector for connector in items if type(connector.parentItem()) is QEngineeringLineItem \ |
631 |
and type(connector) is QEngineeringConnectorItem] |
632 |
|
633 |
matches = [item for item in scene.items() if |
634 |
(type(item) is QEngineeringLineItem) and (item.distanceTo((scenePos.x(), scenePos.y())) < 20)] |
635 |
allowed_error = 0.0001
|
636 |
if False: # len(matches) == 1: |
637 |
matches[0].insertSymbol(svg, scenePos)
|
638 |
elif len(connectors) == 1 and len(svg.connectors) >= 2 and len(connectors[0].parentItem().connectors): |
639 |
# item assistant with line connection
|
640 |
xl = connectors[0].parentItem().symbolOrigin[0] - connectors[0].connectPoint[0] |
641 |
yl = connectors[0].parentItem().symbolOrigin[1] - connectors[0].connectPoint[1] |
642 |
length = math.sqrt(xl * xl + yl * yl) |
643 |
ddx = (connectors[0].sceneBoundingRect().center().x() - connectors[0].parentItem().origin[0]) / length |
644 |
ddy = (connectors[0].sceneBoundingRect().center().y() - connectors[0].parentItem().origin[1]) / length |
645 |
dx, dy = abs(svg.connectors[1].connectPoint[0] - svg.symbolOrigin[0]), abs( |
646 |
svg.connectors[1].connectPoint[1] - svg.symbolOrigin[1]) |
647 |
length = math.sqrt(dx * dx + dy * dy) |
648 |
dx, dy = length * ddx, length * ddy |
649 |
|
650 |
# if abs(connectors[0].parentItem().angle - math.pi / 2) < allowed_error or abs(connectors[0].parentItem().angle - math.pi / 2 * 3) < allowed_error:
|
651 |
# dx, dy = ddx * dy, ddy * dx
|
652 |
# else:
|
653 |
# dx, dy = ddx * dx, ddy * dy
|
654 |
|
655 |
xxl = connectors[0].parentItem().origin[0] - connectors[0].center()[0] |
656 |
yyl = connectors[0].parentItem().origin[1] - connectors[0].center()[1] |
657 |
rAngle = -math.atan2(yyl, xxl)# if flip == 0 else math.atan2(yl, xl)
|
658 |
rAngle = abs(rAngle) if rAngle < 0 + allowed_error else 2 * math.pi - rAngle |
659 |
svg.angle = rAngle |
660 |
|
661 |
x, y = connectors[0].sceneBoundingRect().center().x() + dx, \
|
662 |
connectors[0].sceneBoundingRect().center().y() + dy
|
663 |
svg.loc = [x - svg.symbolOrigin[0], y - svg.symbolOrigin[1]] |
664 |
svg.origin = [x, y] |
665 |
svg.addSvgItemToScene(scene, True if not auto else False) |
666 |
|
667 |
items = [item for item in scene.items(scenePos) if |
668 |
type(item) is not QGraphicsPixmapItem and type(item) is not QGraphicsTextItem] |
669 |
items = [item for item in items if item.parentItem() is svg and type(item) is QEngineeringConnectorItem] |
670 |
if items and connectors[0].connectedItem and type(connectors[0].connectedItem) is QEngineeringLineItem: |
671 |
items[0].connect(connectors[0].parentItem()) |
672 |
anotherConns = [conn for conn in svg.connectors if conn is not items[0]] |
673 |
anotherConns[0].connect(connectors[0].connectedItem) |
674 |
for lineConn in connectors[0].connectedItem.connectors: |
675 |
if lineConn.connectedItem is connectors[0].parentItem(): |
676 |
lineConn.connect(svg) |
677 |
lineConn.setPos(anotherConns[0].center())
|
678 |
break
|
679 |
connectors[0].connect(svg)
|
680 |
lineConn.transfer.onPosChanged.emit(lineConn) |
681 |
elif items:
|
682 |
items[0].connect(connectors[0].parentItem()) |
683 |
#items[0].highlight(False)
|
684 |
if connectors[0].connectedItem: |
685 |
for conn in connectors[0].connectedItem.connectors: |
686 |
if conn.connectedItem is connectors[0].parentItem(): |
687 |
conn.connect(None)
|
688 |
#conn.highlight(False)
|
689 |
break
|
690 |
connectors[0].connect(svg)
|
691 |
#connectors[0].highlight(False)
|
692 |
elif not strict: |
693 |
svg.angle = angle if angle else 0.0 |
694 |
svg.flip = flip if flip else 0 |
695 |
svg.loc = [round(scenePos.x() - svg.symbolOrigin[0], 1), round(scenePos.y() - svg.symbolOrigin[1], 1)] |
696 |
svg.origin = [round(scenePos.x(), 1), round(scenePos.y(), 1)] |
697 |
if len(svg.connectors) == 1: |
698 |
# single connection item assistant
|
699 |
connectors = [connector for connector in connectors if connector.parentItem() is not svg and not connector.connectedItem] |
700 |
|
701 |
if len(connectors) == 1: |
702 |
xxl = connectors[0].parentItem().origin[0] - connectors[0].center()[0] |
703 |
yyl = connectors[0].parentItem().origin[1] - connectors[0].center()[1] |
704 |
rAngle = -math.atan2(yyl, xxl) |
705 |
rAngle = abs(rAngle) if rAngle < 0 + allowed_error else 2 * math.pi - rAngle |
706 |
rAngle = rAngle + math.pi if rAngle + math.pi < 2 * math.pi else rAngle - math.pi |
707 |
svg.angle = rAngle |
708 |
|
709 |
|
710 |
svg.connectors[0].connect(connectors[0].parentItem()) |
711 |
#svg.connectors[0].highlight(False)
|
712 |
connectors[0].connect(svg)
|
713 |
#items[0].highlight(False)
|
714 |
|
715 |
svg.addSvgItemToScene(scene, True if not auto else False) |
716 |
|
717 |
# svg.reSettingConnetors()
|
718 |
|
719 |
'''
|
720 |
if not strict:
|
721 |
# need fix
|
722 |
App.mainWnd().symbolTreeWidget.clearFocus()
|
723 |
scene.setFocus()
|
724 |
#scene.clearFocus()
|
725 |
for item in scene.selectedItems():
|
726 |
item.setSelected(False)
|
727 |
|
728 |
svg.setSelected(True)
|
729 |
scene.setFocusItem(svg)
|
730 |
'''
|
731 |
except Exception as ex: |
732 |
from App import App |
733 |
from AppDocData import MessageType |
734 |
|
735 |
message = 'error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, sys.exc_info()[-1].tb_lineno) |
736 |
App.mainWnd().addMessage.emit(MessageType.Error, message) |
737 |
|
738 |
'''
|
739 |
@brief find item by uid (SymbolSvgItem 기반, QEngineeringConnectorItem 제외, QEngineeringLineItem 포함)
|
740 |
@author kyouho
|
741 |
@date 2018.07.31
|
742 |
'''
|
743 |
|
744 |
def findItemByUid(self, uid): |
745 |
from EngineeringConnectorItem import QEngineeringConnectorItem |
746 |
|
747 |
items = [item for item in self.scene().items() if hasattr(item, 'uid') and str(item.uid) == str(uid)] |
748 |
return items[0] if items else None |
749 |
|
750 |
def convertQImageToMat(self, incomingImage): |
751 |
'''Converts a QImage into an opencv MAT format'''
|
752 |
import numpy as np |
753 |
|
754 |
try:
|
755 |
incomingImage = incomingImage.convertToFormat(QImage.Format_RGBA8888) |
756 |
|
757 |
width = incomingImage.width() |
758 |
height = incomingImage.height() |
759 |
|
760 |
ptr = incomingImage.bits() |
761 |
ptr.setsize(incomingImage.byteCount()) |
762 |
arr = np.array(ptr).reshape(height, width, 4) # Copies the data |
763 |
return arr
|
764 |
except Exception as ex: |
765 |
print('error occurred({}) in {}:{}'.format(ex, sys.exc_info()[-1].tb_frame.f_code.co_filename, |
766 |
sys.exc_info()[-1].tb_lineno))
|
767 |
|
768 |
|
769 |
if __name__ == '__main__': |
770 |
import sys |
771 |
|
772 |
try:
|
773 |
from PyQt5.QtWidgets import QApplication |
774 |
except ImportError: |
775 |
try:
|
776 |
from PyQt4.QtGui import QApplication |
777 |
except ImportError: |
778 |
raise ImportError("ImageViewerQt: Requires PyQt5 or PyQt4.") |
779 |
print('Using Qt ' + QT_VERSION_STR)
|
780 |
|
781 |
|
782 |
def handleLeftClick(x, y): |
783 |
row = int(y)
|
784 |
column = int(x)
|
785 |
print("Clicked on image pixel (row=" + str(row) + ", column=" + str(column) + ")") |
786 |
|
787 |
|
788 |
# Create the application.
|
789 |
app = QApplication(sys.argv) |
790 |
|
791 |
# Create image viewer and load an image file to display.
|
792 |
viewer = QtImageViewer(None)
|
793 |
viewer.loadImageFromFile() # Pops up file dialog.
|
794 |
|
795 |
# Handle left mouse clicks with custom slot.
|
796 |
viewer.leftMouseButtonPressed.connect(handleLeftClick) |
797 |
|
798 |
# Show viewer and run application.
|
799 |
viewer.show() |
800 |
sys.exit(app.exec_()) |