780 lines
32 KiB
Python
780 lines
32 KiB
Python
#!/usr/bin/env python
|
|
# *-----------------------------------------------------------------------*
|
|
# | |
|
|
# | Copyright (c) 2022 by Paul Scherrer Institute (http://www.psi.ch) |
|
|
# | Based on Zac great first implementation |
|
|
# | Author Thierry Zamofing (thierry.zamofing@psi.ch) |
|
|
# *-----------------------------------------------------------------------*
|
|
'''
|
|
User pyqtgraph objects and functions.
|
|
|
|
TODO: Dimension of pyqtgraph is in um not pixel (as now)
|
|
'''
|
|
|
|
#import initExample ## Add path to library (just for examples; you do not need this)
|
|
import logging
|
|
_log=logging.getLogger(__name__)
|
|
|
|
import pyqtgraph as pg
|
|
from pyqtgraph.Qt import QtCore, QtGui
|
|
import numpy as np
|
|
from PyQt5.QtGui import QPolygonF
|
|
from PyQt5.QtCore import Qt,QPointF,QLineF
|
|
from PyQt5.QtWidgets import QMenu
|
|
|
|
|
|
def itr2str(itr):
|
|
return '('+', '.join(tuple(map(lambda x:f'{x:.6g}', itr)))+')'
|
|
|
|
|
|
def obj_tree(obj,p=''):
|
|
obj_info(obj,p)
|
|
for o in obj.childItems():
|
|
obj_tree(o,p+'.')
|
|
|
|
def obj_info(obj,p=''):
|
|
print(f"{p}obj_info:{obj}")
|
|
try:
|
|
pos=obj.pos()
|
|
print(f"{p}pos:({pos.x():.6g},{pos.y():.6g})") # in coordinate value on the scene (no change by zooming)
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
sz=obj.size()
|
|
print(f"{p}size:({sz.x():.6g},{sz.y():.6g})") # in coordinate value on the scene (no change by zooming)
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
for k, v in (('Viewport', obj.viewport()), ('Window', obj.window())):
|
|
print(
|
|
f"{p}{k} (x,y)(w,h):({v.x():.6g},{v.y():.6g})({v.width():.6g},{v.height():.6g})") # in coordinate value on the scene (no change by zooming)
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
scnPos=obj.scenePos()
|
|
print(f"{p}scenePos:({scnPos.x():.6g},{scnPos.y():.6g})") # in pixel on the scene (changes by zooming)
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
if type(obj)==QtGui.QTransform:
|
|
t=obj
|
|
else:
|
|
t=obj.transform()
|
|
print(f"{p}QTransform:{t.m11():8.5g} {t.m12():8.5g} {t.m13():8.5g}")
|
|
print(f"{p} {t.m21():8.5g} {t.m22():8.5g} {t.m23():8.5g}")
|
|
print(f"{p} {t.m31():8.5g} {t.m32():8.5g} {t.m33():8.5g}")
|
|
except AttributeError:
|
|
pass
|
|
|
|
class UsrROI(pg.ROI):
|
|
def __init__(self, pos, size, **kargs):
|
|
pg.ROI.__init__(self, pos, size, **kargs)
|
|
|
|
def cb_toggle_movable(self):
|
|
self.translatable=not self.translatable
|
|
|
|
def contextMenuEvent(self, event):
|
|
#pg.ROI.contextMenuEvent(self,event)
|
|
_log.debug('ContextMenu')
|
|
menu = QMenu("ctx")
|
|
act=menu.addAction('locked')
|
|
act.setCheckable(True)
|
|
act.setChecked(not self.translatable)
|
|
act.triggered.connect(self.cb_toggle_movable)
|
|
#menu.addAction('center in view')
|
|
#menu.addAction('delete')
|
|
menu.exec(event.screenPos())
|
|
|
|
class Marker(UsrROI):
|
|
"""A crosshair ROI whose position is at the center of the crosshairs. By default, it is scalable, rotatable and translatable."""
|
|
|
|
def __init__(self, pos, size, mode, **kargs):
|
|
UsrROI.__init__(self, pos, size, **kargs)
|
|
self._mode=mode
|
|
#widget.signal.connect(slot_function)
|
|
#self.sigRegionChangeFinished.connect(self.OnRgnChanged)
|
|
|
|
def paint(self, p, *args):
|
|
#pg.ROI.paint(self, p, *args)
|
|
r=QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized()
|
|
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
|
p.setPen(self.currentPen)
|
|
p.translate(r.left(), r.top())
|
|
|
|
p.scale(.01*r.width(), .01*r.height()) # -> values x,y 0 to 100
|
|
m=self._mode
|
|
if m==0:
|
|
p.drawEllipse(0, 0, 100, 100)
|
|
p.drawRect(0, 0, 100, 100)
|
|
p.drawRect(0, 0, 5, 5)
|
|
p.setPen(pg.mkPen(width=3, color=[200, 100, 100]))
|
|
p.drawLine(pg.Point(50, 0), pg.Point(50, 100))
|
|
p.drawLine(pg.Point( 0,50), pg.Point(100, 50))
|
|
|
|
ofx,ofy=Marker.txtTrf(p)
|
|
f=p.font()
|
|
f.setPixelSize(10)
|
|
p.setFont(f)
|
|
p.drawText(ofx+24, ofy+20, 'beam marker')
|
|
ctr=tuple((self.pos()+self.size()/2)*1000)
|
|
sz=tuple(self.size()*1000)
|
|
p.drawText(ofx+5, ofy+45, '{:.1f}x{:.1f} um'.format(*sz))
|
|
p.drawText(ofx+5, ofy+55,42,30,Qt.AlignRight, '{:.1f}'.format(ctr[0]))
|
|
p.drawText(ofx+55, ofy+65, '{:.1f} um'.format(ctr[1]))
|
|
|
|
elif m==1:
|
|
p.drawEllipse(20,20,60,60)
|
|
p.drawRect(0, 0, 5, 5)
|
|
p.drawRect(0, 0, 100, 100)
|
|
p.setPen(pg.mkPen(width=2, color=[10, 255, 0]))
|
|
p.drawLine(pg.Point(50, 0), pg.Point( 50,100))
|
|
p.drawLine(pg.Point( 0,50), pg.Point(100, 50))
|
|
ofx,ofy=Marker.txtTrf(p)
|
|
|
|
f=p.font();
|
|
f.setPixelSize(10)
|
|
p.setFont(f)
|
|
px=tuple(self.pos()+self.size()/2)
|
|
p.drawText(ofx+18, ofy+10, 'optical center')
|
|
p.drawText(ofx+5, ofy+80,42,30,Qt.AlignRight, '{:.1f}'.format(px[0]))
|
|
p.drawText(ofx+55,ofy+90, '{:.1f}'.format(px[1]))
|
|
|
|
@staticmethod
|
|
def txtTrf(p):
|
|
tr=p.transform()
|
|
assert (p.transform()==p.worldTransform())
|
|
m11, m12, m13, m21, m22, m23, m31, m32, m33=tr.m11(), tr.m12(), tr.m13(), tr.m21(), tr.m22(), tr.m23(), tr.m31(), tr.m32(), tr.m33()
|
|
ofx=ofy=0
|
|
if m11<0:
|
|
m11=-m11; m12=-m12; ofx-=100
|
|
if m22<0:
|
|
m22=-m22; m21=-m21; ofy=-100
|
|
tr.setMatrix(m11, m12, m13, m21, m22, m23, m31, m32, m33)
|
|
p.setTransform(tr)
|
|
return ofx,ofy
|
|
|
|
|
|
class Fiducial(UsrROI):
|
|
def __init__(self, pos, size, z:float, **kargs):
|
|
UsrROI.__init__(self, pos, size, **kargs)
|
|
self._z=z
|
|
|
|
def paint(self, p, *args):
|
|
#pg.ROI.paint(self, p, *args)
|
|
r=QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized()
|
|
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
|
p.setPen(self.currentPen)
|
|
p.translate(r.left(), r.top())
|
|
p.scale(.01*r.width(), .01*r.height()) # -> values x,y 0 to 100
|
|
p.drawRect(0, 0, 100, 100)
|
|
p.drawEllipse(45, 45, 10, 10)
|
|
p.setPen(pg.mkPen(width=1, color=[255, 0, 0]))
|
|
p.drawLine(pg.Point(50,10), pg.Point(50, 90))
|
|
p.drawLine(pg.Point(10,50), pg.Point(90, 50))
|
|
f=p.font()
|
|
f.setPixelSize(10)
|
|
p.setFont(f)
|
|
ctr=tuple(self.pos()+self.size()/2)
|
|
sz=tuple(self.size())
|
|
p.drawText(52, 10,40,40,Qt.TextWordWrap, 'x{:.5g}\ny{:.5g}\nz{:.5g}'.format(*ctr,self._z))
|
|
|
|
def ctr(self):
|
|
ctr=tuple(self.pos()+self.size()/2)+(self._z,)
|
|
return ctr
|
|
|
|
def __repr__(self):
|
|
ctr=self.ctr()
|
|
s=f'{self.__class__.__name__}:(ctr:{itr2str(ctr)}, size:{itr2str(self.size())})'
|
|
return s
|
|
|
|
def obj2json(self,encoder):
|
|
jsn= {
|
|
'__class__':self.__class__.__name__,
|
|
'pos':tuple(self.pos()),
|
|
'size':tuple(self.size()),
|
|
'z':self._z,
|
|
}
|
|
return jsn
|
|
|
|
|
|
class Grid(UsrROI):
|
|
'''a grid'''
|
|
|
|
def __init__( self, pos=(0,0), size=(30,20), cnt=(6,4), fiducialSize=.2, **kwargs):
|
|
self._param=kwargs
|
|
pg.ROI.__init__(self, pos, size)#, **kwargs)
|
|
self._cnt=cnt
|
|
self._fidSz=fiducialSize
|
|
self.addScaleHandle([1, 1], [0, 0])
|
|
self.addScaleHandle([0, 0], [1, 1])
|
|
self.addScaleRotateHandle([1, 0], [0, 0])
|
|
|
|
def paint(self, p, *args):
|
|
#pg.ROI.paint(self, p, *args)
|
|
sz=self.state['size']
|
|
nx, ny=self._cnt
|
|
px, py=sz[0]/(nx-1), sz[1]/(ny-1)
|
|
r=QtCore.QRectF(0, 0, sz[0], sz[1]).normalized()
|
|
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
|
p.setPen(self.currentPen)
|
|
for i in range(nx):
|
|
x=i*px
|
|
p.drawLine(pg.Point(x, 0), pg.Point(x, sz[1]))
|
|
|
|
for i in range(ny):
|
|
y=i*py
|
|
p.drawLine(pg.Point(0, y), pg.Point(sz[0] ,y ))
|
|
|
|
fidSz=self._fidSz
|
|
rx=ry=fidSz/2
|
|
p.setPen(pg.mkPen(width=1, color=(255, 0, 0)))
|
|
for j in range(ny):
|
|
y=j*py
|
|
for i in range(nx):
|
|
x=i*px
|
|
p.drawEllipse(QPointF(x,y), rx, ry)
|
|
|
|
def __repr__(self):
|
|
s=f'{self.__class__.__name__}:(pos:{itr2str(self.pos())}, size:{itr2str(self.size())}, cnt:{self._cnt}, fidSize:{self._fidSz}, param:{self._param})'
|
|
return s
|
|
|
|
def obj2json(self,encoder):
|
|
jsn= {
|
|
'__class__':self.__class__.__name__,
|
|
'pos':tuple(self.pos()),
|
|
'size':tuple(self.size()),
|
|
'cnt':self._cnt,
|
|
'fiducialSize':self._fidSz
|
|
}
|
|
jsn.update(self._param)
|
|
return jsn
|
|
|
|
def get_scan_param(self):
|
|
'returns scan parameters for scanning with deltatau. the format is as used for shapepath'
|
|
scan=1 # snake motion Y fast, X slow (default)
|
|
cnt=np.array(self._cnt,np.int32)
|
|
sz=np.array(self.size())
|
|
pitch=sz/cnt
|
|
xx, yy=np.meshgrid(range(cnt[0]), range(cnt[1]))
|
|
|
|
|
|
if scan==0: # snake motion X fast, Y slow
|
|
for i in range(1,cnt[1],2):
|
|
xx[i]=xx[i][::-1]
|
|
else: # scan==1 # snake motion Y fast, X slow (default)
|
|
xx=xx.T
|
|
yy=yy.T
|
|
for i in range(1, cnt[0], 2):
|
|
yy[i]=yy[i][::-1]
|
|
pts=np.array([xx.reshape(-1), yy.reshape(-1)], dtype=np.float64).transpose()*pitch
|
|
param={'points':pts}
|
|
param.update(self._param)
|
|
assert(param.get('code_gen',0)==0) # this provides fully x,y motor coordinates
|
|
return param
|
|
|
|
|
|
class Path(UsrROI):
|
|
'''
|
|
a path object with fiducials
|
|
path=np.array(n,2) of path points
|
|
ficucialScale=size for fiducials in dimensions of path
|
|
fiducial = np.array(n,2) of fiducial points
|
|
a circle is plot at the path positions
|
|
a cross is plot at the fiducial positions
|
|
'''
|
|
def __init__( self, pos=(0,0), size=(30,20), path=np.array(((6,4),(16,24),(-5,7),(3,12))), fiducial=None, ficucialScale=5, **kwargs):
|
|
trf=kwargs.pop('trf',None)
|
|
self._param=kwargs
|
|
pg.ROI.__init__(self, pos, size)#, **kwargs)
|
|
self.szOrig=size
|
|
if type(path)==list:
|
|
path=np.array(path)
|
|
if fiducial is None:
|
|
all=path
|
|
else:
|
|
if type(fiducial)==list:
|
|
fiducial=np.array(fiducial)
|
|
|
|
if trf is not None:
|
|
t=self.transform()
|
|
t.setMatrix(trf[0][0], trf[0][1], 0,
|
|
trf[1][0], trf[1][1], 0,
|
|
0, 0, 1)
|
|
self.setTransform(t)
|
|
|
|
self._fiducial=fiducial
|
|
self._path=path
|
|
self._fidScl=ficucialScale
|
|
self._rect=r=QtCore.QRectF(0, 0, size[0], size[1])
|
|
self._qpath=qPth=QPolygonF()
|
|
for pt in path:
|
|
qPth.append(QPointF(*pt))
|
|
|
|
self.addFreeHandle([.1, .1])
|
|
self.addScaleHandle([1, 1], [0, 0])
|
|
self.addScaleHandle([0, 0], [1, 1])
|
|
self.addScaleRotateHandle([1, 0], [0, 0])
|
|
|
|
def paint(self, p, *args):
|
|
#pg.ROI.paint(self, p, *args)
|
|
pos=self.state['pos']
|
|
sz=self.state['size']
|
|
r=self._rect
|
|
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
|
p.setPen(self.currentPen)
|
|
p.drawRect(QtCore.QRectF(0, 0, sz[0], sz[1]).normalized())
|
|
#obj_info(p)
|
|
p.scale(sz[0]/r.width(), sz[1]/r.height())
|
|
p.translate(-r.left(), -r.top())
|
|
|
|
qpath=self._qpath
|
|
p.drawPolyline(qpath)
|
|
fidScl=self._fidScl
|
|
rx=fidScl*r.width()/sz[0]
|
|
ry=fidScl*r.height()/sz[1]
|
|
p.setPen(pg.mkPen(width=1, color=(255, 0, 0)))
|
|
for ctr in qpath:
|
|
p.drawEllipse(ctr, rx, ry)
|
|
|
|
fid=self._fiducial
|
|
for i in range(fid.shape[0]):
|
|
x,y=fid[i,:] #;print(x,y)
|
|
lh=QLineF(x-5*rx,y,x+5*rx,y)
|
|
lv=QLineF(x, y-5*ry, x, y+5*ry)
|
|
p.drawLines(lh,lv)
|
|
|
|
def __repr__(self):
|
|
s=f'{self.__class__.__name__}:(pos:{itr2str(self.pos())}, size:{itr2str(self.size())}, numFid:{self._fiducial.shape[0]}, numPts:{self._path.shape[0]}, ficucialScale:{self._fidScl}, param:{self._param})'
|
|
return s
|
|
|
|
def obj2json(self,encoder):
|
|
jsn= {
|
|
'__class__':self.__class__.__name__,
|
|
'pos':tuple(self.pos()),
|
|
'size':tuple(self.szOrig),
|
|
'fiducial': self._fiducial.tolist(),
|
|
'ficucialScale':self._fidScl,
|
|
'path':self._path.tolist(),
|
|
}
|
|
jsn.update(self._param)
|
|
trf=self.transform()
|
|
if not trf.isIdentity():
|
|
#obj_info(trf)
|
|
trf=((trf.m11(),trf.m12()),(trf.m21(),trf.m22()))
|
|
jsn['trf']=trf
|
|
return jsn
|
|
|
|
def get_scan_param(self):
|
|
'returns scan parameters for scanning with deltatau. the format is as used for shapepath'
|
|
t=self.transform()
|
|
p=np.array(self.pos())
|
|
s=self.size()/self.szOrig
|
|
trf=np.array(((t.m11(),t.m12()),(t.m21(),t.m22()),(0,0)))
|
|
trf[2,:]=p # shift origin
|
|
#trf[:2, 0]*=s[0];trf[:2, 1]*=s[1] #scaling (before rotation shear)
|
|
trf[:2,:]=(trf[:2,:].T*s).T # same as np.asmatrix(np.diag(s))*trf[:2,:], trf[:2,:]*=s not working, scale before rot / shear
|
|
|
|
# trf*'gridpos in um' -> motor pos in mm
|
|
param={'points':self._path,'trf':trf}
|
|
param.update(self._param)
|
|
return param
|
|
|
|
class FixTargetFrame(UsrROI):
|
|
'''fixed target frame'''
|
|
tpl={
|
|
'test':{
|
|
'size':(120*15, 120*11),
|
|
'fiducial':{
|
|
'type':0,
|
|
'pos':((120*2, 120*2), (120*13, 120*2), (120*2, 120*9), (120*13, 120*9))
|
|
},
|
|
'grid':{
|
|
'pos':(120*4, 120*3),
|
|
'pitch':(120, 120),
|
|
'count':(8, 6)
|
|
}
|
|
},
|
|
'12.5x12.5':{
|
|
'size':(12500+120*4, 12500+120*4),
|
|
'fiducial':{
|
|
'type':0,
|
|
'pos':((240, 240), (240+12500, 240), (240, 240+12500), (240+12500, 240+12500))
|
|
},
|
|
'grid':{
|
|
# 240+(12500-78*120)/2=1810
|
|
'pos':(1750, 1750),
|
|
'pitch':(120, 120),
|
|
'count':(78, 78) #(84,84)... but sone covered
|
|
}
|
|
},
|
|
'23.0x23.0':{
|
|
'size':(23000+120*4, 23000+120*4),
|
|
'fiducial':{
|
|
'type':0,
|
|
'pos':((240, 240), (240+23000, 240), (240, 240+23000), (240+23000, 240+23000))
|
|
},
|
|
'grid':{
|
|
# 240+(23000-162*120)/2+60?=2020+60?
|
|
'pos':(2080, 2080),
|
|
'pitch':(120, 120),
|
|
'count':(162, 162) #(172,172)... but sone covered
|
|
}
|
|
}
|
|
}
|
|
|
|
def __init__( self, pos=(0,0), size=(100,100), tpl='test', dscr=None, **kwargs):
|
|
trf=kwargs.pop('trf',None)
|
|
self._param=kwargs
|
|
pg.ROI.__init__(self, pos, size)#, **kwargs)
|
|
if trf is not None:
|
|
t=self.transform()
|
|
t.setMatrix(trf[0][0], trf[0][1], 0,
|
|
trf[1][0], trf[1][1], 0,
|
|
0, 0, 1)
|
|
self.setTransform(t)
|
|
|
|
#fiducial type 0: 5 squares with pitch 120 um
|
|
if dscr is not None:
|
|
self._dscr=dscr
|
|
else:
|
|
self._dscr=FixTargetFrame.tpl[tpl]
|
|
|
|
self.addScaleHandle([1, 1], [0, 0])
|
|
self.addScaleHandle([0, 0], [1, 1])
|
|
self.addScaleRotateHandle([1, 0], [0, 0])
|
|
|
|
def paint(self, p, *args):
|
|
sz=self.state['size']
|
|
r=QtCore.QRectF(0, 0, sz[0], sz[1]).normalized()
|
|
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
|
p.setPen(self.currentPen)
|
|
p.drawRect(r)
|
|
|
|
dscr=self._dscr
|
|
objSz=dscr['size']
|
|
p.translate(r.left(), r.top())
|
|
p.scale(r.width()/objSz[0], r.height()/objSz[1]) # -> values x,y 0 to 13000
|
|
|
|
dscr=self._dscr
|
|
|
|
g=dscr['grid']
|
|
ox,oy=g['pos']
|
|
px,py=g['pitch']
|
|
nx,ny=g['count']
|
|
x0=ox-.5*px; x1=ox+(nx-.5)*px
|
|
y0=oy-.5*py; y1=oy+(ny-.5)*py
|
|
for i in range(nx):
|
|
x=ox+i*px
|
|
p.drawLine(pg.Point(x, y0), pg.Point(x, y1))
|
|
|
|
for i in range(ny):
|
|
y=oy+i*py
|
|
p.drawLine(pg.Point(x0, y), pg.Point(x1 ,y ))
|
|
|
|
f=dscr['fiducial']
|
|
rx=50
|
|
ry=50
|
|
|
|
p.setPen(pg.mkPen(width=1, color=(255, 0, 0)))
|
|
if f['type']==0:
|
|
for p0 in f['pos']:
|
|
p0=np.array(p0)
|
|
for p1 in ((-120,-120),(120,-120),(0,0),(-120,120),(120,120),):
|
|
p1=np.array(p1)
|
|
x, y=p0+p1 #;print(x,y)
|
|
lh=QLineF(x-rx, y, x+rx, y)
|
|
lv=QLineF(x, y-ry, x, y+ry)
|
|
p.drawLines(lh, lv)
|
|
|
|
#plot center of all feducials
|
|
ctr=np.array(f['pos']).mean(axis=0)
|
|
x, y=ctr # ;print(x,y)
|
|
lh=QLineF(x-rx, y, x+rx, y)
|
|
lv=QLineF(x, y-ry, x, y+ry)
|
|
p.drawLines(lh, lv)
|
|
p.drawEllipse(int(x-rx/2), int(y-ry/2), int(rx), int(ry))
|
|
|
|
else:
|
|
assert('unknown feducial type')
|
|
|
|
def __repr__(self):
|
|
s=f'{self.__class__.__name__}:(pos:{itr2str(self.pos())}, size:{itr2str(self.size())}, dscr:{self._dscr}, param:{self._param})'
|
|
return s
|
|
|
|
def obj2json(self,encoder):
|
|
jsn= {
|
|
'__class__':self.__class__.__name__,
|
|
'pos':tuple(self.pos()),
|
|
'size':tuple(self.size()),
|
|
'dscr': self._dscr,
|
|
}
|
|
jsn.update(self._param)
|
|
trf=self.transform()
|
|
if not trf.isIdentity():
|
|
#obj_info(trf)
|
|
trf=((trf.m11(),trf.m12()),(trf.m21(),trf.m22()))
|
|
jsn['trf']=trf
|
|
return jsn
|
|
|
|
def get_scan_param(self):
|
|
'returns scan parameters for scanning with deltatau. the format is as used for shapepath'
|
|
scan=1 # snake motion Y fast, X slow (default)
|
|
grid=self._dscr['grid']
|
|
self._dscr['size']
|
|
cnt =np.array(grid['count'],np.int32)
|
|
xx, yy=np.meshgrid(range(cnt[0]), range(cnt[1]))
|
|
if scan==0: # snake motion X fast, Y slow
|
|
for i in range(1,cnt[1],2):
|
|
xx[i]=xx[i][::-1]
|
|
else: # scan==1 # snake motion Y fast, X slow (default)
|
|
xx=xx.T
|
|
yy=yy.T
|
|
for i in range(1, cnt[0], 2):
|
|
yy[i]=yy[i][::-1]
|
|
|
|
pts=np.array([xx.reshape(-1), yy.reshape(-1)], dtype=np.float64).transpose() #*pitch
|
|
|
|
# TODO: simplify !!!
|
|
t=self.transform() #obj_info(t)
|
|
p=np.array(self.pos())
|
|
s=self.size()/self._dscr['size']
|
|
trf=np.array(((t.m11(),t.m12()),(t.m21(),t.m22()),(0,0)))
|
|
trf[2,:]=p # shift origin
|
|
trf[:2,:]=(trf[:2,:].T*s).T # same as np.asmatrix(np.diag(s))*trf[:2,:], trf[:2,:]*=s not working, scale before rot / shear
|
|
|
|
pos=np.array(grid['pos']) # in um
|
|
pitch=np.array(grid['pitch']) # in um
|
|
trf2=np.asmatrix(np.identity(3))
|
|
trf2[:, :2]=trf
|
|
trf2*=np.asmatrix(((1000, 0, 0), (0, 1000, 0), (0, 0, 1)))
|
|
trf3=np.asmatrix(((pitch[0], 0, 0), (0, pitch[1], 0), (pos[0], pos[1], 1)))
|
|
trf=(trf3*trf2)[:, :2]
|
|
param={'grid':grid, 'points':pts, 'trf':trf}
|
|
param.update(self._param)
|
|
|
|
return param
|
|
|
|
|
|
## Start Qt event loop unless running in interactive mode or using pyside.
|
|
if __name__=='__main__':
|
|
def set_fiducial(pic):
|
|
# fiducial test
|
|
f=np.array(((0, 0, 0, 0, 0),
|
|
(0, 1, 1, 1, 0),
|
|
(0, 1, 0, 0, 0),
|
|
(0, 1, 1, 0, 0),
|
|
(0, 1, 0, 0, 0),
|
|
(0, 0, 0, 0, 0),), pic.dtype)
|
|
pic[0:6, 0:5]=f*pic.max()
|
|
|
|
|
|
# TODO: pg.ItemGroup does not support bounding box and therefore vb.autoRange() does not work
|
|
class ItemGroup(pg.ItemGroup):
|
|
# own item group that supports bounding rect
|
|
def __init__(self, *args, **kargs):
|
|
pg.ItemGroup.__init__(self, *args, **kargs)
|
|
|
|
def boundingRect(self):
|
|
# tr=self.transform()
|
|
boRects=[]
|
|
for item in self.childItems():
|
|
boRects.append(item.boundingRect())
|
|
if boRects:
|
|
# r=QtCore.QRectF(tr.map(boRects[0].bottomRight()),tr.map(boRects[0].topLeft()))
|
|
return boRects[0]
|
|
else:
|
|
return pg.ItemGroup.boundingRect(self)
|
|
|
|
def addItem(self, *args, **kargs):
|
|
self.setFlag(self.ItemHasNoContents, False)
|
|
pg.ItemGroup.addItem(self, *args, **kargs)
|
|
|
|
|
|
class TxtROI(pg.ROI):
|
|
def __init__(self, pos, size, **kargs):
|
|
pg.ROI.__init__(self, pos, size, **kargs)
|
|
|
|
def paint(self, p, *args):
|
|
# pg.ROI.paint(self, p, *args)
|
|
r=QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized()
|
|
|
|
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
|
p.setPen(self.currentPen)
|
|
p.drawRect(r)
|
|
tr=p.worldTransform()
|
|
obj_info(tr)
|
|
tr.setMatrix(tr.m11(), tr.m12(), tr.m13(), tr.m21(), -tr.m22(), tr.m23(), tr.m31(), tr.m32(), tr.m33())
|
|
p.setWorldTransform(tr)
|
|
obj_info(tr)
|
|
obj_info(p.transform())
|
|
obj_info(p.worldTransform())
|
|
f=p.font();
|
|
f.setPixelSize(15)
|
|
p.setFont(f)
|
|
p.drawText(0, 5, 'Thierry')
|
|
|
|
|
|
import sys
|
|
import argparse
|
|
#(h, t)=os.path.split(sys.argv[0]);cmd='\n '+(t if len(h)>20 else sys.argv[0])+' '
|
|
#exampleCmd=('', '-m0xf -v0')
|
|
epilog=__doc__ #+'\nExamples:'+''.join(map(lambda s:cmd+s, exampleCmd))+'\n'
|
|
parser=argparse.ArgumentParser(epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument("--mode", "-m", type=lambda x: int(x,0), help="mode default=0x%(default)x", default=0)
|
|
args = parser.parse_args()
|
|
|
|
pg.setConfigOptions(imageAxisOrder='row-major')
|
|
|
|
def pt2str(p):
|
|
return (f'({p.x():.5g},{p.y():.5g})')
|
|
|
|
def childTree(obj, lvl=0):
|
|
print('+'*lvl+str(obj))
|
|
for child in obj.childItems():
|
|
childTree(child, lvl+1)
|
|
|
|
def gen_swissmx_points(flipx=False,flipy=False,ofs=(-200,0),width=1000):
|
|
'generathe a path that writes swissfel'
|
|
#string from inkscape path of the drawing
|
|
d="m 524.7061,637.31536 3.54883,0 3.54882,0 3.54883,0 0,-4.20801 0,-4.20801 0,-4.208 0,-4.20801 4.22949,0 4.22949,0 4.2295,0 4.22949,0 0,-3.55957 0,-3.55957 0,-3.55957 0,-3.55957 -4.22949,0 -4.2295,0 -4.22949,0 -4.22949,0 0,-4.22949 0,-4.2295 0,-4.22949 0,-4.22949 -3.54883,0 -3.54882,0 -3.54883,0 -3.54883,0 0,4.22949 0,4.22949 0,4.2295 0,4.22949 -4.20752,0 -4.20752,0 -4.20752,0 -4.20752,0 0,3.55957 0,3.55957 0,3.55957 0,3.55957 4.20752,0 4.20752,0 4.20752,0 4.20752,0 0,4.20801 0,4.208 0,4.20801 0,4.20801 -11.87126,0.36152 -12.12171,-0.13934 -2.52941,3.93977 -2.57238,3.94369 -2.50854,3.88614 -2.50731,3.91767 -2.49035,3.88268 -2.50987,3.91244 -2.50453,3.88732 -2.51897,3.9189 -6.39782,5.72802 -6.63782,6.70894 -3.21517,5.11464 -3.3404,5.32333 -3.08995,5.11464 -3.17343,5.15637 -16.69223,0.0698 5.55908,0 5.55909,0 5.55908,0 3.18604,-5.17432 3.18603,-5.17431 3.18604,-5.17432 3.18603,-5.17431 3.17481,5.17431 3.1748,5.17432 3.17481,5.17431 3.1748,5.17432 5.59229,0 5.59228,0 5.59229,0 5.59228,0 -2.74121,-4.15283 -2.74121,-4.15283 -2.74121,-4.15283 -2.74121,-4.15284 -2.74122,-4.15283 -2.74121,-4.15283 -2.74121,-4.15283 -2.74121,-4.15283 2.50488,-3.90015 2.50489,-3.90015 2.50488,-3.90014 2.50488,-3.90015 2.50488,-3.90015 2.50489,-3.90015 2.50488,-3.90014 2.50488,-3.90015 -5.42724,0 -5.42725,0 -5.42724,0 -5.42725,0 -2.76855,4.95508 -2.76856,4.95508 -2.76855,4.95508 -2.76856,4.95508 -2.85644,-4.95508 -2.85645,-4.95508 -2.85644,-4.95508 -2.85645,-4.95508 -5.48193,0 -5.48194,0 -5.48194,0 -5.48193,0 2.52686,3.8562 2.52685,3.8562 2.52686,3.8562 2.52686,3.85621 2.52685,3.8562 2.52686,3.8562 2.52685,3.8562 2.52686,3.8562 -2.77954,4.19678 -2.77954,4.19678 -2.77954,4.19677 -2.77955,4.19678 -2.77954,4.19678 -2.77954,4.19678 -2.77954,4.19677 -2.77954,4.19678 -4.91638,0 -4.91638,0 -4.91639,0 -4.91638,0 -4.91638,0 -4.91638,0 -4.91638,0 -4.91638,0 -4.91639,0 -4.91638,0 -4.91638,0 -4.91638,0 -4.91638,0 -4.91639,0 -4.91638,0 -4.91638,0 4.07568,0 4.07568,0 4.07569,0 4.07568,0 0,-6.14136 0,-6.14135 0,-6.14136 0,-6.14136 0,-6.14136 0,-6.14135 0,-6.14136 0,-6.14136 1.57105,6.14136 1.57104,6.14136 1.57104,6.14135 1.57105,6.14136 1.57105,6.14136 1.57104,6.14136 1.57104,6.14135 1.57105,6.14136 3.68066,0 3.68067,0 3.68067,0 3.68066,0 1.57642,-6.14136 1.57641,-6.14135 1.57642,-6.14136 1.57641,-6.14136 1.57642,-6.14136 1.57642,-6.14135 1.57641,-6.14136 1.57642,-6.14136 0,6.14136 0,6.14136 0,6.14135 0,6.14136 0,6.14136 0,6.14136 0,6.14135 0,6.14136 4.06494,0 4.06494,0 4.06494,0 4.06494,0 0,-8.05298 0,-8.05298 0,-8.05298 0,-8.05297 0,-8.05298 0,-8.05298 0,-8.05298 0,-8.05298 -6.52588,0 -6.52588,0 -6.52587,0 -6.52588,0 -1.25781,4.8999 -1.25782,4.89991 -1.25781,4.8999 -1.25781,4.8999 -1.25781,4.8999 -1.25782,4.89991 -1.25781,4.8999 -1.25781,4.8999 -1.26343,-4.8999 -1.26343,-4.8999 -1.26343,-4.89991 -1.26343,-4.8999 -1.26342,-4.8999 -1.26343,-4.8999 -1.26343,-4.89991 -1.26343,-4.8999 -6.54785,0 -6.54785,0 -6.54785,0 -6.54785,0 0,8.05298 0,8.05298 0,8.05298 0,8.05298 0,8.05297 0,8.05298 0,8.05298 -4.25755,8.13646 -8.40743,0.19687 -8.40743,0.19687 -8.40743,0.19687 -8.40743,0.19687 5.93521,0.22812 8.09742,-0.56079 6.18579,-1.6814 4.55883,-2.66919 3.13062,-3.43823 1.84571,-3.87866 0.61523,-3.98853 -0.58179,-3.83373 -1.74634,-3.50416 -2.802,-2.95581 -3.83472,-2.18676 -5.49316,-1.60401 -7.77832,-1.20849 -7.64649,-1.58204 -1.75781,-2.59179 1.36328,-2.59375 4.4375,-1.09766 5.09766,1.40625 2.19727,3.29492 4.24072,-0.41748 4.24073,-0.41748 4.24072,-0.41748 4.24072,-0.41748 -1.98804,-4.09741 -2.44946,-3.15259 -2.97778,-2.3291 -3.65894,-1.62598 -5.05371,-0.95629 -7.25098,-0.3191 -7.10766,0.41748 -5.50367,1.25244 -4.19677,2.05494 -3.18604,2.91186 -2.01099,3.65796 -0.67065,4.29517 0.61523,3.98852 1.84571,3.5271 2.78002,2.823 3.32935,1.87817 5.06421,1.42822 7.89868,1.56006 7.69141,1.84571 2.02148,2.98828 -1.53906,2.85742 -5.58008,1.53711 -5.27344,-1.36133 -3.07617,-4.52734 -4.43847,0.41748 -4.43848,0.41748 -4.43848,0.41748 -4.43847,0.41748 2.50488,5.95459 4.43848,4.4165 3.18313,1.59592 4.10031,1.14017 -3.65979,0.0939 -5.9713,6e-5 -5.97131,5e-5 -5.9713,6e-5 -5.9713,6e-5 -5.9713,5e-5 -5.97131,6e-5 -5.9713,5e-5 -5.9713,6e-5 5.34491,0.81842 8.09742,-0.56079 6.18579,-1.6814 4.55883,-2.66919 3.13062,-3.43823 1.84571,-3.87866 0.61523,-3.98853 -0.58179,-3.83373 -1.74634,-3.50416 -2.802,-2.95581 -3.83472,-2.18676 -5.49316,-1.60401 -7.77832,-1.20849 -7.64649,-1.58204 -1.75781,-2.59179 1.36328,-2.59375 4.4375,-1.09766 5.09766,1.40625 2.19727,3.29492 4.24072,-0.41748 4.24073,-0.41748 4.24072,-0.41748 4.24072,-0.41748 -1.98804,-4.09741 -2.44946,-3.15259 -2.97778,-2.3291 -3.65894,-1.62598 -5.05371,-0.95629 -7.25098,-0.3191 -7.10766,0.41748 -5.50367,1.25244 -4.19677,2.05494 -3.18604,2.91186 -2.01099,3.65796 -0.67065,4.29517 0.61523,3.98852 1.84571,3.5271 2.78002,2.823 3.32935,1.87817 5.06421,1.42822 7.89868,1.56006 7.69141,1.84571 2.02148,2.98828 -1.53906,2.85742 -5.58008,1.53711 -5.27344,-1.36133 -3.07617,-4.52734 -4.43847,0.41748 -4.43848,0.41748 -4.43848,0.41748 -4.43847,0.41748 2.50488,5.95459 4.43848,4.4165 3.18313,1.59592 4.10031,1.14017 -3.06953,-0.0416 -3.06952,-0.0416 -8.58102,-0.0261 -10.12782,-0.0261 -7.03422,-0.0261 -8.58102,-0.0261 4.47168,0 6.6151,0 2.32826,0 4.47168,0 0,-5.83374 0,-5.83374 0,-5.83374 0,-5.83374 0,-5.83374 0,-5.83374 0,-5.83374 0,-5.83374 -4.47168,0 -4.47168,0 -4.47168,0 0,-5.5796 4.47168,0 4.47168,0 4.47168,0 0,-6.08691 0,-6.08692 -4.47168,0 -4.47168,0 -4.47168,0 -4.47168,0 0,6.08692 0,6.08691 0,5.5796 0,5.83374 0,5.83374 0,5.83374 0,5.83374 0,5.83374 0,5.83374 0,5.83374 -3.67318,5.83374 -8.7308,0 -10.73079,0 -6.7308,0 -9.10563,0 -2.25201,0.007 -8.72971,0.0266 -7.53755,-0.0442 -9.68477,0.0107 -6.3443,0 3.99902,0 3.99902,0 3.99903,0 3.99902,0 2.28516,-7.02002 2.28516,-7.02002 2.28516,-7.02002 2.28516,-7.02002 2.36181,7.02002 2.36182,7.02002 2.36181,7.02002 2.36182,7.02002 3.97705,0 3.97705,0 3.97705,0 3.97705,0 2.14795,-5.83374 2.14795,-5.83374 2.14795,-5.83374 2.14795,-5.83374 2.14795,-5.83374 2.14795,-5.83374 2.14795,-5.83374 2.14795,-5.83374 -4.2959,0 -4.2959,0 -4.2959,0 -4.2959,0 -0.93921,3.67505 -0.93921,3.67505 -0.93921,3.67505 -0.93921,3.67505 -0.9392,3.67504 -0.93921,3.67505 -0.93921,3.67505 -0.93921,3.67505 -1.23047,-3.67505 -1.23047,-3.67505 -1.23047,-3.67505 -1.23047,-3.67504 -1.23046,-3.67505 -1.23047,-3.67505 -1.23047,-3.67505 -1.23047,-3.67505 -4.03223,0 -4.03222,0 -4.03223,0 -4.03223,0 -1.18652,3.67505 -1.18653,3.67505 -1.18652,3.67505 -1.18653,3.67505 -1.18652,3.67504 -1.18652,3.67505 -1.18653,3.67505 -1.18652,3.67505 -0.93921,-3.67505 -0.93921,-3.67505 -0.93921,-3.67505 -0.93921,-3.67504 -0.9392,-3.67505 -0.93921,-3.67505 -0.93921,-3.67505 -0.93921,-3.67505 -4.32862,0 -4.32861,0 -4.32862,0 -4.32861,0 2.16431,5.83374 2.1643,5.83374 2.16431,5.83374 2.16431,5.83374 2.16431,5.83374 2.1643,5.83374 2.16431,5.83374 -3.84635,5.83374 -5.60781,0.003 -5.6078,0.003 -5.60781,0.003 -5.6078,0.003 -5.4839,-1.59358 0,0 5.47119,-3.35034 4.10888,-4.60278 2.5708,-5.4712 0.85694,-5.95459 -0.64868,-5.02123 -1.94507,-4.51587 -3.32837,-3.91114 -4.88843,-3.20801 -7.482173,-2.87842 -5.1337,-1.42273 -6.06186,-1.41174 -6.67969,-2.37304 -1.44922,-2.76758 1.75782,-3.56055 5.22851,-1.49414 6.5918,1.97852 1.99951,2.5708 1.16455,3.75732 4.69141,-0.2749 4.691403,-0.2749 4.6914,-0.27491 4.69141,-0.2749 -0.94483,-4.66918 -1.604,-3.98804 -2.26318,-3.30688 -2.92236,-2.62574 -3.59802,-2.01858 -4.334103,-1.44162 -5.0702,-0.86484 -5.80627,-0.28824 -4.76547,0.1593 -4.23282,0.47791 -6.86695,1.91162 -5.04223,2.98828 -3.61401,3.95507 -2.14283,4.53687 -0.7146,4.82251 1.40625,6.88892 4.21875,5.54858 3.26035,2.31812 4.19986,2.07641 5.13919,1.83472 6.07834,1.59302 6.54785,1.81226 3.64746,1.92211 2.19727,4.48242 -2.33008,4.65821 -6.54688,1.97851 -5.05371,-0.97827 -3.73535,-2.93384 -1.57153,-2.9663 -0.93433,-4.06495 -4.73486,0.29688 -4.73487,0.29687 -4.73486,0.29688 -4.73486,0.29687 0.76065,4.6637 1.44711,4.23523 2.13376,3.80676 2.82059,3.3783 3.79577,2.76855 5.0592,1.97754 6.32264,1.18652 7.58606,0.39551 9.481626,-0.95145 -7.224723,-0.043 -7.224724,-0.043 -7.224723,-0.043 -7.224723,-0.043 -7.224723,-0.043 -7.224723,-0.043 -7.224724,-0.043 -7.224723,-0.043"
|
|
d=d.split()
|
|
pts=np.ndarray((len(d)-1,2),dtype=np.float64)
|
|
for i in range(pts.shape[0]):
|
|
pts[i,:]=tuple(map(float,d[i+1].split(',')))
|
|
|
|
pts[0,:]=(0,0)
|
|
pts=pts.cumsum(0)
|
|
pts=pts[::-1,:]
|
|
pts=pts-pts[0]
|
|
pts*=width/pts[:,0].max()
|
|
if flipx: pts[:,0]=-pts[:,0]
|
|
if not flipy: pts[:,1]=-pts[:,1]
|
|
pts+=ofs
|
|
return pts
|
|
|
|
|
|
def mouse_click_event(event):
|
|
pos=event.pos()
|
|
scn=event.scenePos() # there is a small black border, that makes the difference
|
|
print(f'mouse-> scene pos:{pt2str(scn)} currentItem pos:{pt2str(pos)}')
|
|
m=int(event.modifiers())
|
|
o=event.currentItem
|
|
if m&Qt.ShiftModifier:
|
|
o.setPos((100,200))
|
|
o.setSize((200,100))
|
|
o.rotate(10)
|
|
pass
|
|
elif m&Qt.ControlModifier:
|
|
tr=o.transform()
|
|
obj_info(tr)
|
|
tr.setMatrix(-tr.m11(),tr.m12(),tr.m13(),tr.m21(),-tr.m22(),tr.m23(),tr.m31(),tr.m32(),tr.m33())
|
|
obj_info(tr)
|
|
o.setTransform(tr)
|
|
elif m&Qt.AltModifier:
|
|
import matplotlib.pyplot as plt
|
|
import numpy as np
|
|
|
|
x=np.linspace(0.1, 2*np.pi, 41)
|
|
y=np.exp(np.sin(x))
|
|
|
|
plt.stem(x, y)
|
|
plt.show()
|
|
pass
|
|
else:
|
|
obj_info(o)
|
|
#print(o.state)
|
|
|
|
|
|
## Create image to display
|
|
arr=np.ones((100, 100), dtype=float)
|
|
arr[45:55, 45:55]=0
|
|
arr[25, :]=5
|
|
arr[:, 25]=5
|
|
arr[75, :]=5
|
|
arr[:, 75]=5
|
|
arr[50, :]=10
|
|
arr[:, 50]=10
|
|
arr+=np.sin(np.linspace(0, 20, 100)).reshape(1, 100)
|
|
arr+=np.random.normal(size=(100, 100))
|
|
set_fiducial(arr)
|
|
# add an arrow for asymmetry
|
|
arr[10, :50]=10
|
|
arr[9:12, 44:48]=10
|
|
arr[8:13, 44:46]=10
|
|
|
|
## create GUI
|
|
app=QtGui.QApplication([])
|
|
w=pg.GraphicsLayoutWidget(show=True, size=(1000, 800), border=True)
|
|
w.setWindowTitle('pyqtgraph example: ROI Examples')
|
|
|
|
vb=w.addViewBox(row=1, col=0, lockAspect=True,invertY=False,enableMenu=False)
|
|
vb.enableAutoRange(enable=False)
|
|
try:
|
|
g=pg.GridItem(pen=(0, 255, 0), textPen=(0, 255, 0)) # green grid and labels
|
|
except:
|
|
g=pg.GridItem()
|
|
vb.addItem(g)
|
|
|
|
viImg=pg.ImageItem(arr, border='y')
|
|
vb.addItem(viImg)
|
|
|
|
viUsrRoi=Marker([50, 120], [30, 20],mode=0)
|
|
vb.addItem(viUsrRoi)
|
|
|
|
vi=Grid( (120,-100), (200,150), (30,20),2)
|
|
tr=QtGui.QTransform() # prepare ImageItem transformation:
|
|
tr.setMatrix(1, -.1, 0,
|
|
.2, 1, 0,
|
|
10, 10, 1)
|
|
vi.setTransform(tr) # assign transform
|
|
vb.addItem(vi) #vi= visual item
|
|
|
|
grp=pg.ItemGroup()
|
|
vb.addItem(grp)
|
|
tr.setMatrix(-1, -.1, 0,
|
|
.2, -1, 0,
|
|
10, 10, 1)
|
|
grp.setTransform(tr) # assign transform
|
|
obj=Marker([20, 40], [30, 20],mode=1)
|
|
grp.addItem(obj)
|
|
|
|
obj=Marker([20, 40], [30, 20],mode=1)
|
|
vb.addItem(obj)
|
|
|
|
|
|
fidScl=.5
|
|
fiducial=np.array(((18, 7), (25, 16), (70, 20)))
|
|
path=gen_swissmx_points(ofs=(10, 5), width=200)
|
|
vi=Path((120,100),path,fiducial,fidScl)
|
|
vb.addItem(vi)
|
|
|
|
vi=FixTargetFrame((100,300),(100,100),tpl='test')
|
|
vb.addItem(vi)
|
|
vi=FixTargetFrame((400,-200),(400,400),tpl='12.5x12.5')
|
|
vb.addItem(vi)
|
|
|
|
vi=Fiducial((0,200),(40,40),3,movable=False,removable=True)
|
|
vb.addItem(vi)
|
|
|
|
vi=pg.PolyLineROI([(22, -19), (40, -30), (23, -10), (22, -19)], closed=True)
|
|
vb.addItem(vi)
|
|
|
|
|
|
viRoi=pg.ROI([-200, -200], [100, 80],movable=True, rotatable=True, resizable=True)
|
|
|
|
viRoi.addFreeHandle(pos=[.7, .5], axes=None, item=None, name=None, index=None) # rechteck , frei beweglich ??? verschwinden anch bewegung
|
|
#viRoi.addScaleRotateHandle([0, .5], [1, .5], item=None, name=None, index=None) # kreis
|
|
#viRoi.addTranslateHandle([.7, .5], axes=None, item=None, name=None, index=None) #quadrat
|
|
vb.addItem(viRoi)
|
|
|
|
#autorange not working, as all rois are in ItemGroup
|
|
vb.setRange(QtCore.QRectF(-300, -400, 900+300, 500+400))
|
|
|
|
childTree(vb)
|
|
|
|
w.scene().sigMouseClicked.connect(mouse_click_event)
|
|
|
|
if (sys.flags.interactive!=1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
|
QtGui.QApplication.instance().exec_()
|
|
|