Files
John Henry BealeandClaude Opus 5.5 2d9bbdac9d replace runtime print statements with logging
- all print calls outside the __main__ test blocks are now log calls:
  info for run information (CTA configuration summary on one line, run
  number, CTA start pid), warning/error for aborts and failures, debug for
  dumps (object/transform info, autofocus measurements, logger tree)
- psi_device: remove the import-time logging.basicConfig(level=DEBUG) and
  print('past logging'); the standalone test configures logging itself
- startup import timings are logged at info level
- ctadaq added to the -l log level list

Prints from PBTools/PBSwissMX, slic and the JFJ client are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 11:56:49 +02:00

837 lines
34 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,os
_log=logging.getLogger(__name__)
os.environ["PYQTGRAPH_QT_LIB"] = "PyQt5" # needed to be able to debug in pycharm
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=''):
_log.debug(f"{p}obj_info:{obj}")
try:
pos=obj.pos()
_log.debug(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()
_log.debug(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())):
_log.debug(
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()
_log.debug(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()
_log.debug(f"{p}QTransform:{t.m11():8.5g} {t.m12():8.5g} {t.m13():8.5g}")
_log.debug(f"{p} {t.m21():8.5g} {t.m22():8.5g} {t.m23():8.5g}")
_log.debug(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'
cnt=np.array(self._cnt, np.int32)
grid={'pos':tuple(self.pos()), 'pitch':tuple(np.array(self.size())/cnt), 'count':self._cnt}
use_trf=self._param.get('use_trf', True) # do not use coordinate transformation
mode=self._param.get('mode',1)
num_pts=np.array(self._cnt, np.int32).prod()
param={'num_pts':num_pts}
param.update(self._param)
# TODO: simplify !!!
t=self.transform() #obj_info(t)
p=np.array(self.pos())
s=1#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((0,0)) #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]
if mode in (1,3): # needs all points, not grid
scan=1 # snake motion Y fast, X slow (default)
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
if not use_trf:
pts=(np.hstack((pts, np.ones((pts.shape[0], 1))))*trf).A
param['trf']=trf
param['points']=pts
else:
if use_trf:
param.update({'grid':grid, 'trf':trf})
else:
g=grid.copy() #has not be tested !
g['pos']=tuple((np.array((0,0,1))*trf).A.reshape(-1).tolist())
param.update({'grid':p, 'trf':trf})
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,:]=(trf[:2,:].T*s).T
# trf*'gridpos in um' -> motor pos in mm
param={'num_pts':len(self._path),'trf':trf,'points':self._path}
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
}
},
'23.0x23.0_160x156':{
'size':(23000+120*4, 23000+120*4),
'fiducial':{
'type':0,
'pos':((240, 240), (240+23000, 240), (240, 240+23000), (240+23000, 240+23000))
},
'grid':{
# removed 1 column from each side and 6 rows from top
'pos':(2200, 2800),
'pitch':(120, 120),
'count':(160, 156)
}
}
}
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'
grid=self._dscr['grid']
use_trf=self._param.get('use_trf', True) # do not use coordinate transformation
mode=self._param.get('mode',1)
cnt=np.array(grid['count'], np.int32)
num_pts=cnt.prod()
param={'num_pts':num_pts}
param.update(self._param)
# 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]
if mode in (1,3): # needs all points, not grid
scan=1 # snake motion Y fast, X slow (default)
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
if not use_trf:
pts=(np.hstack((pts, np.ones((pts.shape[0], 1))))*trf).A
param['trf']=trf
param['points']=pts
else:
if use_trf:
param.update({'grid':grid, 'trf':trf})
else:
g=grid.copy() #has not be tested !
g['pos']=tuple((np.array((0,0,1))*trf).A.reshape(-1).tolist())
param.update({'grid':p, 'trf':trf})
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_()