You want it add to OrthoView.py ?
A ToolBar Button with QAction triggered QFileDialog.getOpenFileName
new
OrthoView.py
# -*- coding: utf-8 -*-
"""
OrthoView
"""
__author__ = "Konstantin Klementiev"
__versioninfo__ = (1, 0, 2)
__version__ = '.'.join(map(str, __versioninfo__))
__date__ = "8 Feb 2020"
__license__ = "MIT license"
import os
import sys
import numpy as np
import cv2
from matplotlib.figure import Figure
from PyQt5 import QtGui as qtcore
from PyQt5 import QtWidgets as qtwidgets
from PyQt5 import Qt as qt
import matplotlib.backends.backend_qt5agg as mpl_qt
try:
from ConfigParser import ConfigParser
except ImportError:
from configparser import ConfigParser
isTest = True
if not isTest:
from taurus import Device as DeviceProxy
motorX = None # DeviceProxy('mp_x')
motorY = DeviceProxy('mp_y')
from taurus.qt.qtgui.display import TaurusLed
taurusLedModel = "b308a-eh/rpi/cam-01/status"
else:
TaurusLed = None
selfDir = os.path.dirname(__file__)
iniApp = (os.path.join(selfDir, 'OrthoView.ini'))
config = ConfigParser(
dict(pos='[0, 0]', corners='[None]*4', scalex=0, scaley=0))
config.add_section('rectangle')
config.add_section('beam')
config.add_section('colors')
config.read(iniApp)
def write_config():
with open(iniApp, 'w+') as cf:
config.write(cf)
class MyToolBar(mpl_qt.NavigationToolbar2QT):
def set_message(self, s):
try:
parent = self.parent()
except TypeError: # self.parent is not callable
parent = self.parent
try:
sstr = s.split()
# print(sstr, len(sstr))
while len(sstr) > 5: # when 'zoom rect' is present
del sstr[0]
# print(sstr)
x, y = float(sstr[0][2:]), float(sstr[1][2:])
if parent.canTransform():
xC, yC = parent.beamPosRectified
if not parent.buttonStraightRect.isChecked():
xP, yP = parent.transformPoint((x, y))
x0, y0 = (xP-xC)/parent.zoom, (yP-yC)/parent.zoom
s = u'image: x={0:.1f} px, y={1:.1f} px\nplate: '\
'x={2:.2f} mm, y={3:.2f} mm'.format(x, y, x0, y0)
else:
x0, y0 = (x-xC)/parent.zoom, (y-yC)/parent.zoom
s = 'plate: x={0:.2f} mm, y={1:.2f} mm'.format(x0, y0)
else:
s = u'image: x={0:.1f}, y={1:.1f}'.format(x, y)
except Exception as e:
pass
# print(e)
if self.coordinates:
self.locLabel.setText(s)
class MyMplCanvas(mpl_qt.FigureCanvasQTAgg):
def __init__(self, parent=None):
self.fig = Figure()
self.fig.patch.set_facecolor('white')
super(MyMplCanvas, self).__init__(self.fig)
self.setParent(parent)
self.updateGeometry()
self.setupPlot()
self.mpl_connect('button_press_event', self.onPress)
self.img = None
self.setContextMenuPolicy(qt.Qt.CustomContextMenu)
self.mouseClickPos = None
self.beamPos = eval(config.get('beam', 'pos'))
self.customContextMenuRequested.connect(self.viewMenu)
self.menu = qt.QMenu()
self.actionMove = self.menu.addAction(
'move this point to beam', self.moveToBeam)
self.actionDefineBeam = self.menu.addAction(
'define beam position here', self.setBeamPosition)
self.actionShowBeam = self.menu.addAction(
'show beam position', self.showBeam)
self.actionShowBeam.setCheckable(True)
self.isBeamPositionVisible = True
self.actionShowBeam.setChecked(self.isBeamPositionVisible)
self.actionShowRect = self.menu.addAction(
'show reference rectangle', self.showRect)
self.actionShowRect.setCheckable(True)
self.isRectVisible = True
self.actionShowRect.setChecked(self.isRectVisible)
def setupPlot(self):
rect = [0., 0., 1., 1.]
self.axes = self.fig.add_axes(rect)
self.axes.xaxis.set_visible(False)
self.axes.yaxis.set_visible(False)
for spine in ['left', 'right', 'bottom', 'top']:
self.axes.spines[spine].set_visible(False)
self.axes.set_zorder(20)
def imshow(self, img):
if self.img is None:
self.img = self.axes.imshow(img)
else:
prev = self.img.get_array()
self.img.set_data(img)
if prev.shape != img.shape:
self.img.set_extent(
[-0.5, img.shape[1]-0.5, img.shape[0]-0.5, -0.5])
self.axes.set_xlim((0, img.shape[1]))
self.axes.set_ylim((img.shape[0], 0))
self.toolbar.update()
self.draw()
def onPress(self, event):
if (event.xdata is None) or (event.ydata is None):
self.mouseClickPos = None
return
self.mouseClickPos = int(round(event.xdata)), int(round(event.ydata))
if not self.parent().buttonBaseRect.isChecked():
return
self.parent().buttonBaseRect.setCorner(*self.mouseClickPos)
def viewMenu(self, position):
if self.mouseClickPos is None:
return
self.actionDefineBeam.setEnabled(
not self.parent().buttonStraightRect.isChecked())
self.actionMove.setEnabled(self.parent().canTransform())
self.menu.exec_(self.mapToGlobal(position))
self.parent().updateFrame()
def setBeamPosition(self):
if (self.beamPos[0] > 0) or (self.beamPos[1] > 0):
msgBox = qt.QMessageBox()
reply = msgBox.question(
self, 'Confirm',
'Do you really want to re-define beam position?',
qt.QMessageBox.Yes | qt.QMessageBox.No, qt.QMessageBox.Yes)
if reply == qt.QMessageBox.No:
return
self.beamPos[:] = self.mouseClickPos
config.set('beam', 'pos', str(self.beamPos))
write_config()
self.parent().buttonStraightRect.update()
def showBeam(self):
self.isBeamPositionVisible = not self.isBeamPositionVisible
def showRect(self):
self.isRectVisible = not self.isRectVisible
def moveToBeam(self):
x, y = self.mouseClickPos
parent = self.parent()
xC, yC = parent.beamPosRectified
if not parent.buttonStraightRect.isChecked():
xP, yP = parent.transformPoint((x, y))
x0, y0 = (xP-xC)/parent.zoom, (yP-yC)/parent.zoom
else:
x0, y0 = (x-xC)/parent.zoom, (y-yC)/parent.zoom
if isTest:
print(-x0, y0)
else:
if motorX is not None:
try:
curX = motorX.read_attribute('position').value
motorX.write_attribute('position', curX-x0)
except Exception as e:
lines = str(e).splitlines()
for line in reversed(lines):
if 'desc =' in line:
msgBox = qt.QMessageBox()
msgBox.critical(
self, 'Motion has failed', line.strip()[7:])
return
if motorY is not None:
curY = motorY.read_attribute('position').value
motorY.write_attribute('position', curY+y0)
class PerspectiveRectButton(qt.QPushButton):
prect = (qt.QPoint(12, 10), qt.QPoint(50, 8), qt.QPoint(47, 30),
qt.QPoint(11, 26))
def __init__(self, text='', parent=None):
super(PerspectiveRectButton, self).__init__(text, parent)
self.polygon = qt.QPolygonF()
for pt in self.prect:
self.polygon.append(pt)
self.corners = [0] * 4
self.currentDefCorner = -1
self.wantVisibleCorners = True
self.clicked.connect(self.clickedSlot)
self.setCheckable(True)
self.installEventFilter(self)
self.setStyleSheet(
"QPushButton:checked{background-color: deepskyblue;}"
"QPushButton:pressed{background-color: deepskyblue;}")
def clickedSlot(self):
if self.isChecked():
pos = qt.QCursor.pos()
qt.QCursor.setPos(pos.x()-300, pos.y()+200)
if None not in self.corners:
self.currentDefCorner = 0
else:
self.currentDefCorner = self.corners.index(None)
else:
self.currentDefCorner = -1
self.parent().plotCanvas.isRectVisible = True
self.parent().plotCanvas.actionShowRect.setChecked(True)
self.parent().updateFrame()
self.update()
def setCorner(self, xdata, ydata):
self.corners[self.currentDefCorner] = int(xdata), int(ydata)
self.currentDefCorner += 1
if self.currentDefCorner > 3:
self.currentDefCorner = 0
srtPts = sorted(self.corners, key=lambda ls: ls[1])
topPts, bottomPts = srtPts[:2], srtPts[2:]
spt1, spt2 = sorted(topPts, key=lambda lst: lst[0])
spt4, spt3 = sorted(bottomPts, key=lambda lst: lst[0])
self.corners = [spt1, spt2, spt3, spt4]
config.set('rectangle', 'corners', str(self.corners))
write_config()
self.setChecked(False)
self.parent().buttonStraightRect.update()
self.parent().updateFrame()
self.update()
def eventFilter(self, widget, event):
if event.type() == qt.QEvent.KeyPress:
key = event.key()
if key == qt.Qt.Key_Escape:
self.setChecked(False)
self.clicked.emit(False)
return super(PerspectiveRectButton, self).eventFilter(widget, event)
def paintEvent(self, event):
super(PerspectiveRectButton, self).paintEvent(event)
if not self.wantVisibleCorners:
return
paint = qt.QPainter()
paint.begin(self)
paint.setRenderHint(qt.QPainter.Antialiasing)
paint.drawPolygon(self.polygon)
for ipt, (pt, corner) in enumerate(zip(self.prect, self.corners)):
color = qt.Qt.darkGreen if corner is not None else qt.Qt.red
if self.isChecked():
if ipt == self.currentDefCorner:
color = qt.Qt.blue
paint.setPen(color)
paint.setBrush(color)
paint.drawEllipse(pt, 4, 4)
paint.end()
class StraightRectButton(PerspectiveRectButton):
prect = (qt.QPoint(9, 10), qt.QPoint(51, 10), qt.QPoint(51, 30),
qt.QPoint(9, 30))
def __init__(self, parent=None, buddies=[]):
super(StraightRectButton, self).__init__('check', parent)
self.wantVisibleCorners = False
self.corners = [0] * 4
self.currentDefCorner = -1
self.buddies = buddies
self.setEnabled(False)
self.setStyleSheet(
"QPushButton:checked{background-color: lime;}"
"QPushButton:pressed{background-color: lime;}")
def update(self):
super(StraightRectButton, self).update()
if self.parent() is not None:
self.wantVisibleCorners = self.parent().canTransform()
if self.wantVisibleCorners:
self.parent().getTransform()
self.setEnabled(self.wantVisibleCorners)
def clickedSlot(self):
self.parent().buttonBaseRect.setEnabled(not self.isChecked())
self.parent().getTransform()
self.parent().updateFrame()
class ScaleXButton(qt.QPushButton):
path = (qt.QPoint(8, 30), qt.QPoint(16, 32), qt.QPoint(16, 28),
qt.QPoint(8, 30),
qt.QPoint(52, 30), qt.QPoint(44, 32), qt.QPoint(44, 28),
qt.QPoint(52, 30))
def __init__(self, text='', parent=None):
super(ScaleXButton, self).__init__(text, parent)
self.polygon = qt.QPolygonF()
for pt in self.path:
self.polygon.append(pt)
self.scale = 0
self.buddyEdit = None
self.clicked.connect(self.clickedSlot)
def clickedSlot(self):
if self.buddyEdit is not None:
self.setVisible(False)
self.buddyEdit.setVisible(True)
self.buddyEdit.setFocus()
def drawText(self, event, painter):
p = qt.QPoint(event.rect().center().x()-2, 22)
painter.drawText(p, "X")
def paintEvent(self, event):
super(ScaleXButton, self).paintEvent(event)
paint = qt.QPainter()
paint.begin(self)
paint.setRenderHint(qt.QPainter.Antialiasing)
color = qt.Qt.darkGreen if self.scale else qt.Qt.red
paint.setPen(color)
paint.drawPolygon(self.polygon)
self.drawText(event, paint)
paint.end()
class ScaleYButton(ScaleXButton):
path = (qt.QPoint(20, 6), qt.QPoint(18, 14), qt.QPoint(22, 14),
qt.QPoint(20, 6),
qt.QPoint(20, 34), qt.QPoint(18, 26), qt.QPoint(22, 26),
qt.QPoint(20, 34))
def drawText(self, event, painter):
painter.drawText(event.rect(), qt.Qt.AlignCenter, " Y")
class ScaleEdit(qt.QDoubleSpinBox):
def __init__(self, name, parent=None, buddyButton=None):
super(ScaleEdit, self).__init__(parent)
self.name = name
self.buddyButton = buddyButton
self.setVisible(False)
self.setSuffix("mm")
self.setDecimals(1)
self.setMaximum(1000)
self.setValue(buddyButton.scale)
self.installEventFilter(self)
self.setStyleSheet(
"QDoubleSpinBox:up-button {width: 0;}"
"QDoubleSpinBox:down-button {width: 0;}")
def eventFilter(self, widget, event):
if event.type() == qt.QEvent.KeyPress:
key = event.key()
if key in (qt.Qt.Key_Enter, qt.Qt.Key_Return, qt.Qt.Key_Escape):
self.buddyButton.setVisible(True)
self.setVisible(False)
if key in (qt.Qt.Key_Enter, qt.Qt.Key_Return):
self.buddyButton.scale = self.value()
config.set('rectangle', 'scale'+self.name,
str(self.buddyButton.scale))
write_config()
self.parent().buttonStraightRect.update()
return super(ScaleEdit, self).eventFilter(widget, event)
class OrthoView(qt.QWidget):
def __init__(self, parent=None):
super(OrthoView, self).__init__(parent)
self.setWindowTitle('OrthoView')
self.setMinimumSize(800, 600+53)
self.move(0, 30)
self.image_file_path = "/home/UbuntuUser/Desktop/UAV.tif" ###"/Daten_HD/Fotos/Krakau/krakau 072.tif"
self.tb = qtwidgets.QToolBar("File")
open_btn = qtwidgets.QAction("Open Image File", self, triggered=self.openFile)
open_btn.setIcon(qtcore.QIcon.fromTheme("document-open"))
self.tb.addAction(open_btn)
self.beamPosRectified = [0, 0]
self.plotCanvas = MyMplCanvas(self)
self.plotCanvas.setSizePolicy(
qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding)
self.toolbar = MyToolBar(self.plotCanvas, self)
for action in self.toolbar.findChildren(qtwidgets.QAction):
if action.text() in ['Customize', 'Subplots']:
action.setVisible(False)
self.toolbar.locLabel.setAlignment(qt.Qt.AlignCenter)
layoutT = qt.QHBoxLayout()
self.buttonBaseRect = PerspectiveRectButton()
self.buttonBaseRect.corners = eval(config.get('rectangle', 'corners'))
self.buttonScaleX = ScaleXButton()
self.buttonScaleX.scale = float(config.get('rectangle', 'scalex'))
self.editScaleX = ScaleEdit('x', buddyButton=self.buttonScaleX)
self.buttonScaleX.buddyEdit = self.editScaleX
self.buttonScaleY = ScaleYButton()
self.buttonScaleY.scale = float(config.get('rectangle', 'scaley'))
self.editScaleY = ScaleEdit('y', buddyButton=self.buttonScaleY)
self.buttonScaleY.buddyEdit = self.editScaleY
self.buttonStraightRect = StraightRectButton(buddies=(
self.buttonBaseRect, self.buttonScaleX, self.buttonScaleY))
for but in (self.buttonBaseRect,
self.buttonScaleX, self.editScaleX,
self.buttonScaleY, self.editScaleY,
self.buttonStraightRect):
but.setFixedSize(60, 40)
layoutT.addWidget(self.tb)
layoutT.addWidget(self.toolbar)
if TaurusLed is not None:
led = TaurusLed()
led.setModel(taurusLedModel)
layoutT.addWidget(led)
layoutT.addWidget(self.buttonBaseRect)
layoutT.addWidget(self.buttonScaleX)
layoutT.addWidget(self.editScaleX)
layoutT.addWidget(self.buttonScaleY)
layoutT.addWidget(self.editScaleY)
layoutT.addWidget(self.buttonStraightRect)
layout = qt.QVBoxLayout(self)
layout.addLayout(layoutT)
layout.addWidget(self.plotCanvas)
# markers
self.beamMarkColor = (255, 0, 0)
self.cornerColor = (0, 192, 0)
self.currentCornerColor = (64, 64, 255)
self.gridColor = (192, 192, 192)
if not isTest:
self.refreshTimer = qtcore.QTimer()
self.refreshTimer.timeout.connect(self.updateFrame)
self.refreshTimer.start(500) # ms
try: # Camera tango device
self.camera = DeviceProxy('b308a-eh/rpi/cam-01')
except Exception as e:
raise Exception("Something is wrong with the tango device {0}".
format(e))
self.updateFrame()
self.buttonStraightRect.update()
def openFile(self):
print("open File Dialog")
path, _ = qt.QFileDialog.getOpenFileName(self, "Open File", self.image_file_path,"Image Files (*.tif *.tiff *.png *.jpg)")
if path:
self.image_file_path = path
print(self.image_file_path)
isTest = True
self.updateFrame()
def getFrame(self, path):
if isTest:
if os.path.isfile(path):
frame = cv2.imread(path)
# OpenCV uses BGR as its default colour order for images
self.img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
else:
self.openFile()
else:
frame = self.camera.read_attribute('Image').value
# for monochrome frames:
# self.img = cv2.normalize(
# src=frame, dst=None, alpha=0, beta=255,
# norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8UC1)
# self.img = cv2.cvtColor(self.img, cv2.COLOR_GRAY2BGR)
# for color frames:
unpacked = np.empty(list(frame.shape)+[3], dtype=np.uint8)
unpacked[:, :, 2] = (frame >> 16) & 0xff
unpacked[:, :, 1] = (frame >> 8) & 0xff
unpacked[:, :, 0] = frame & 0xff
self.img = unpacked
def updateFrame(self):
self.getFrame(self.image_file_path)
try:
CV_AA = cv2.CV_AA
except AttributeError:
CV_AA = cv2.LINE_AA
if self.canTransform() and self.buttonStraightRect.isChecked():
# draw rectified
self.img = cv2.warpPerspective(
self.img, self.perspectiveTransform2,
(self.boundingRect[2], self.boundingRect[3]))
overlay = self.img.copy()
ps = self.img.shape[0] * 0.02
# grid:
grid = np.arange(-10, 10) * 10 * self.zoom
xgrid = np.int16(self.targetRect[0][0] + grid)
ygrid = np.int16(self.targetRect[0][1] + grid)
for xg in xgrid:
cv2.line(overlay, (xg, ygrid[0]), (xg, ygrid[-1]),
self.gridColor, 2)
for yg in ygrid:
cv2.line(overlay, (xgrid[0], yg), (xgrid[-1], yg),
self.gridColor, 2)
# beam position mark:
if self.plotCanvas.isBeamPositionVisible:
cv2.circle(
overlay, tuple(int(p) for p in self.beamPosRectified),
int(ps*0.75), self.beamMarkColor, int(ps/3.), CV_AA)
# rectangle corners:
if self.plotCanvas.isRectVisible:
for corner in self.targetRect:
cv2.circle(overlay, corner, int(ps/3.), self.cornerColor,
-1, CV_AA)
else:
overlay = self.img.copy()
ps = self.img.shape[0] * 0.02
# beam position mark + text:
if self.plotCanvas.isBeamPositionVisible:
beamPos = tuple(self.plotCanvas.beamPos)
# beamMarkTextPos = (beamPos[0]+10, beamPos[1]+20)
cv2.circle(
overlay, beamPos,
int(ps), self.beamMarkColor, int(ps/3.), CV_AA)
# cv2.putText(
# overlay, 'beam', beamMarkTextPos,
# cv2.FONT_HERSHEY_SIMPLEX, 0.75, self.beamMarkColor, 1)
# rectangle corners:
if self.plotCanvas.isRectVisible:
for icorner, corner in enumerate(self.buttonBaseRect.corners):
if corner is None:
continue
color = self.cornerColor
if self.buttonBaseRect.isChecked():
if icorner == self.buttonBaseRect.currentDefCorner:
color = self.currentCornerColor
cv2.circle(overlay, corner, int(ps/3.), color, -1, CV_AA)
alpha = 0.75 # transparency factor
imageNew = cv2.addWeighted(overlay, alpha, self.img, 1-alpha, 0)
self.plotCanvas.imshow(imageNew)
def canTransform(self):
return ((None not in self.buttonBaseRect.corners) and
self.buttonScaleX.scale > 0 and self.buttonScaleY.scale > 0)
def getTransform(self):
dY2, dX2 = self.img.shape[:2]
dX, dY = self.buttonScaleX.scale, self.buttonScaleY.scale
self.zoom = dX2 / dX
dX = int(dX * self.zoom)
dY = int(dY * self.zoom)
pIn = self.buttonBaseRect.corners
pOut = [(0, 0), (dX, 0), (dX, dY), (0, dY)]
self.perspectiveTransform1 = cv2.getPerspectiveTransform(
np.float32(pIn), np.float32(pOut))
inCorners = [[(0, 0), (dX2, 0), (dX2, dY2), (0, dY2)]]
outCorners = cv2.perspectiveTransform(
np.float32(inCorners), self.perspectiveTransform1)
self.boundingRect = cv2.boundingRect(outCorners)
self.beamPosRectified = self.transformPoint(self.plotCanvas.beamPos)
self.targetRect = [(x-self.boundingRect[0], y-self.boundingRect[1])
for x, y in pOut]
self.perspectiveTransform2 = cv2.getPerspectiveTransform(
np.float32(pIn), np.float32(self.targetRect))
def transformPoint(self, p):
outPoint = cv2.perspectiveTransform(
np.float32([[p]]), self.perspectiveTransform1)
return (outPoint[0][-1][0]-self.boundingRect[0],
outPoint[0][-1][1]-self.boundingRect[1])
if __name__ == "__main__":
if isTest:
app = qt.QApplication(sys.argv)
else:
from taurus.qt.qtgui.application import TaurusApplication
app = TaurusApplication(sys.argv)
icon = qt.QIcon(os.path.join(selfDir, '_static', 'orthoview.ico'))
app.setWindowIcon(icon)
window = OrthoView()
window.show()
sys.exit(app.exec_())