add epics widgets, CustomROI
This commit is contained in:
627
CustomROI.py
Normal file
627
CustomROI.py
Normal file
@@ -0,0 +1,627 @@
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtGui, QtCore
|
||||
from PyQt5.QtGui import QColor, QTransform
|
||||
from PyQt5.QtWidgets import QMenu, QAction, QSpinBox, QMenu
|
||||
|
||||
from GenericDialog import GenericDialog
|
||||
from pyqtgraph import ROI, Point, RectROI, mkPen
|
||||
from pyqtgraph import functions as fn
|
||||
|
||||
from app_config import settings
|
||||
|
||||
from math import *
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
class Retangulo(ROI):
|
||||
"""
|
||||
Rectangular ROI subclass with a single scale handle at the top-right corner.
|
||||
|
||||
============== =============================================================
|
||||
**Arguments**
|
||||
pos (length-2 sequence) The position of the ROI origin.
|
||||
See ROI().
|
||||
size (length-2 sequence) The size of the ROI. See ROI().
|
||||
centered (bool) If True, scale handles affect the ROI relative to its
|
||||
center, rather than its origin.
|
||||
sideScalers (bool) If True, extra scale handles are added at the top and
|
||||
right edges.
|
||||
\**args All extra keyword arguments are passed to ROI()
|
||||
============== =============================================================
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, pos, size, centered=False, sideScalers=False, **args):
|
||||
ROI.__init__(self, pos, size, **args)
|
||||
if centered:
|
||||
center = [0.5, 0.5]
|
||||
else:
|
||||
center = [0, 0]
|
||||
|
||||
self.addScaleRotateHandle([0, 0.5], [1, 0.5])
|
||||
self.addScaleHandle([1, 1], center)
|
||||
if sideScalers:
|
||||
self.addScaleHandle([1, 0.5], [center[0], 0.5])
|
||||
self.addScaleHandle([0.5, 1], [0.5, center[1]])
|
||||
|
||||
|
||||
class Grid(ROI):
|
||||
"""a grid"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
x_step,
|
||||
y_step,
|
||||
x_offset,
|
||||
y_offset,
|
||||
grid_index=0,
|
||||
parent=None,
|
||||
gonio_xy=(0, 0),
|
||||
**kargs
|
||||
):
|
||||
self._grid_index = grid_index
|
||||
self._size = None
|
||||
self._pos = None
|
||||
self._shape = None
|
||||
ROI.__init__(self, (500, 500), (300, 300), **kargs)
|
||||
self.parent = parent
|
||||
|
||||
self.addScaleHandle([1, 1], [0, 0])
|
||||
self.addScaleHandle([0, 0], [1, 1])
|
||||
self.addScaleRotateHandle([1, 0], [0, 0])
|
||||
# self.addScaleRotateHandle([1, 0.5], [0.5, 0.5])
|
||||
|
||||
self._steps = x_step / 1000., y_step / 1000.
|
||||
self._offsets = x_offset / 1000, y_offset / 1000
|
||||
self._beam_position = parent.get_beam_mark_on_camera_xy()
|
||||
self.set_gonio_xy(*gonio_xy)
|
||||
|
||||
self.setToolTip("grid")
|
||||
|
||||
parent.pixelsPerMillimeter.connect(self.update_ppm)
|
||||
parent.beamCameraCoordinatesChanged.connect(self.update_beam_position)
|
||||
self.enable_stage_tracking()
|
||||
|
||||
self._ppm = parent.getPpm()
|
||||
self._mm_size = 300 / self._ppm, 300 / self._ppm
|
||||
self._original_ppm = self._ppm # marker only works at original zoom
|
||||
# because zoom axis is not aligned
|
||||
# with viewing axis
|
||||
self._fx, self._fy = self.parent.getFastX(), self.parent.getFastY()
|
||||
self.invertible = True
|
||||
self.sigRegionChanged.connect(self.invalidate)
|
||||
self.sigRegionChangeFinished.connect(self.calculate_gonio_xy)
|
||||
self.scaleSnap = False
|
||||
self.aspectLocked = False
|
||||
self.translatable = True
|
||||
self.rotateAllowed = False
|
||||
self.removable = True
|
||||
|
||||
self.follow_stage()
|
||||
# self.calculate_gonio_xy()
|
||||
|
||||
def enable_stage_tracking(self):
|
||||
self.parent.fast_x_position.connect(self.follow_x)
|
||||
self.parent.fast_y_position.connect(self.follow_y)
|
||||
|
||||
def disable_stage_tracking(self):
|
||||
self.parent.fast_x_position.disconnect(self.follow_x)
|
||||
self.parent.fast_y_position.disconnect(self.follow_y)
|
||||
|
||||
def follow_x(self, v):
|
||||
self.follow_stage(fx=v)
|
||||
|
||||
def follow_y(self, v):
|
||||
self.follow_stage(fy=v)
|
||||
|
||||
def removeClicked(self):
|
||||
self.parent.pixelsPerMillimeter.disconnect(self.update_ppm)
|
||||
self.parent.beamCameraCoordinatesChanged.disconnect(self.update_beam_position)
|
||||
self.parent.fast_x_position.disconnect(self.follow_x)
|
||||
self.parent.fast_y_position.disconnect(self.follow_y)
|
||||
super(Grid, self).removeClicked()
|
||||
|
||||
def calculate_gonio_xy(self):
|
||||
self.update_grid_pos()
|
||||
state = self.getState()
|
||||
angle = state["angle"]
|
||||
w, h = state["size"]
|
||||
w, h = 1000 * w / self._ppm, 1000 * h / self._ppm
|
||||
self._mm_size = w / 1000, h / 1000
|
||||
self.calculate_targets()
|
||||
|
||||
def update_ppm(self, ppm):
|
||||
self._ppm = ppm
|
||||
self.update_grid_size(ppm)
|
||||
self.update_grid_pos()
|
||||
self.stateChanged()
|
||||
|
||||
def update_grid_size(self, ppm, finish=False):
|
||||
mmw, mmh = self._mm_size
|
||||
w, h = mmw * ppm, mmh * ppm
|
||||
self.setSize((w, h), update=False)
|
||||
|
||||
def follow_stage(self, fx=None, fy=None):
|
||||
self._fx, self._fy = self.parent.getFastX(), self.parent.getFastY()
|
||||
self.update_grid_pos()
|
||||
self.stateChanged()
|
||||
|
||||
def set_gonio_xy(self, x, y):
|
||||
self._gx, self._gy = x, y
|
||||
|
||||
def update_grid_pos(self):
|
||||
idx = self._grid_index
|
||||
ppm = self._ppm
|
||||
bx, by = self.parent.get_beam_mark_on_camera_xy()
|
||||
gx, gy = self._gx, self._gy # marker position in gonio coords
|
||||
fx, fy = self._fx, self._fy
|
||||
dx, dy = (
|
||||
ppm * (fx - gx),
|
||||
ppm * (fy - gy),
|
||||
) # distance to beam in pixels for new ppm
|
||||
x, y = bx - dx, by + dy
|
||||
self.setPos(x, y, update=False)
|
||||
mmw, mmh = self._mm_size
|
||||
|
||||
def calculate_targets(self):
|
||||
state = self.getState()
|
||||
angle = radians(state["angle"])
|
||||
cosa = cos(angle)
|
||||
sina = sin(angle)
|
||||
w, h = state["size"]
|
||||
xs, ys = self._steps
|
||||
w, h = w / self._ppm, h / self._ppm # mm
|
||||
gx, gy = self._gx, self._gy
|
||||
nx = int(abs(w) / xs)
|
||||
ny = int(abs(h) / ys)
|
||||
points = []
|
||||
dx = xs
|
||||
dy = ys
|
||||
for yi in range(ny):
|
||||
ly = yi * dy
|
||||
y = gy + ly
|
||||
for xi in range(nx):
|
||||
lx = xi * dx
|
||||
x = gx + lx
|
||||
l = sqrt((lx) ** 2 + (ly) ** 2)
|
||||
try:
|
||||
alpha = acos((lx) / l)
|
||||
except:
|
||||
alpha = 0
|
||||
x2 = gx + l * cos(alpha + angle)
|
||||
y2 = gy - l * sin(alpha + angle)
|
||||
# print(
|
||||
# "l={:8.3f} alpha={:.3f} xy={:8.3f}{:8.3f} xy2={:8.3f}{:8.3f}".format(
|
||||
# l, degrees(alpha), x, y, x2, y2
|
||||
# )
|
||||
# )
|
||||
|
||||
points.append((x2, y2))
|
||||
self._grid_dimensions = (nx, ny)
|
||||
self._points = points
|
||||
self.parent.gridUpdated.emit(self._grid_index)
|
||||
|
||||
def get_sorted_grid_targets(self):
|
||||
pass
|
||||
|
||||
def get_grid_targets(self):
|
||||
return self._points
|
||||
|
||||
def update_beam_position(self, pos):
|
||||
self._beam_position = pos
|
||||
|
||||
def invalidate(self):
|
||||
state = self.getState()
|
||||
x, y = state["pos"]
|
||||
w, h = state["size"]
|
||||
self._mm_size = w / self._ppm, h / self._ppm
|
||||
fx, fy = self.camera2gonio(x, y)
|
||||
self.set_gonio_xy(fx, fy)
|
||||
self._shape = None
|
||||
self.prepareGeometryChange()
|
||||
|
||||
def boundingRect(self):
|
||||
return self.shape().boundingRect()
|
||||
|
||||
def shape(self):
|
||||
w, h = self.getState()["size"]
|
||||
if self._shape is None:
|
||||
radius = self.getState()["size"][1]
|
||||
p = QtGui.QPainterPath()
|
||||
p.addRect(0, 0, w, h)
|
||||
stroker = QtGui.QPainterPathStroker()
|
||||
stroker.setWidth(10)
|
||||
outline = stroker.createStroke(p)
|
||||
self._shape = outline
|
||||
return self._shape
|
||||
|
||||
def paint(self, p, *args):
|
||||
state = self.getState()
|
||||
w, h = state["size"]
|
||||
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
p.setPen(fn.mkPen(width=5, color=[200, 200, 100]))
|
||||
p.drawRect(0, 0, w, h)
|
||||
|
||||
p.setPen(fn.mkPen(width=3, color=[200, 100, 100]))
|
||||
ppm = self._ppm
|
||||
xs, ys = self._steps
|
||||
w1, h1 = w / ppm, h / ppm # mm
|
||||
nx = int(abs(w1) / xs)
|
||||
ny = int(abs(h1) / ys)
|
||||
sw = (xs) * ppm
|
||||
sh = (ys) * ppm
|
||||
# print((nx,ny), (w1, h1), (sw, sh))
|
||||
r = 10
|
||||
a, b = state["pos"]
|
||||
if nx * ny > 1000:
|
||||
return
|
||||
for y in range(ny):
|
||||
for x in range(nx):
|
||||
p.drawEllipse((x * sw) - r, (y * sh) - r, 2 * r, 2 * r)
|
||||
|
||||
def camera2gonio(self, x, y):
|
||||
bx, by = self.parent.get_beam_mark_on_camera_xy()
|
||||
ppm = self._ppm
|
||||
dx, dy = (x - bx) / ppm, (y - by) / ppm
|
||||
fx, fy = self._fx + dx, self._fy - dy
|
||||
return fx, fy
|
||||
|
||||
def gonio2camera(self, x, y):
|
||||
ppm = self._ppm
|
||||
bx, by = self.parent.get_beam_mark_on_camera_xy()
|
||||
gx, gy = self._gx, self._gy # marker position in gonio coords
|
||||
fx, fy = self._fx, self._fy
|
||||
dx, dy = (
|
||||
ppm * (fx - gx),
|
||||
ppm * (fy - gy),
|
||||
) # distance to beam in pixels for new ppm
|
||||
x, y = bx - dx, by + dy
|
||||
return x, y
|
||||
|
||||
|
||||
class BeamMark(ROI):
|
||||
"""A crosshair ROI whose position is at the center of the crosshairs. By default, it is scalable, rotatable and translatable."""
|
||||
|
||||
def __init__(self, pos=None, size=None, parent=None, **kargs):
|
||||
if size is None:
|
||||
size = [20, 20]
|
||||
if pos is None:
|
||||
pos = [0, 0]
|
||||
self._size = size
|
||||
self._pos = pos
|
||||
self._shape = None
|
||||
ROI.__init__(self, pos, size, **kargs)
|
||||
|
||||
parent.pixelsPerMillimeter.connect(self.my_update_ppm)
|
||||
parent.beamCameraCoordinatesChanged.connect(lambda x, y: self.setPos((x, y)))
|
||||
|
||||
self._scale_handler = None
|
||||
self.sigRegionChanged.connect(self.invalidate)
|
||||
self.aspectLocked = False
|
||||
self.translatable = False
|
||||
self.rotateAllowed = False
|
||||
self.invertible = False
|
||||
self.removable = False
|
||||
self.scaleSnap = True
|
||||
self.snapSize = 1
|
||||
self.sigRegionChangeFinished.connect(self.show_size)
|
||||
self.flip_ud()
|
||||
|
||||
def show_size(self):
|
||||
try:
|
||||
ppm = self._ppm
|
||||
except:
|
||||
return
|
||||
w, h = self.getState()["size"]
|
||||
# convert to micrometer
|
||||
nw, nh = (w * 1000) / ppm, (h * 1000) / ppm
|
||||
logger.debug("persisting new beam marker size: {}".format((nw, nh)))
|
||||
settings.setValue("beam/size", (nw, nh))
|
||||
|
||||
def toggle_handle(self):
|
||||
if self._scale_handler is None:
|
||||
logger.debug("adding beam marker resize handle")
|
||||
self._scale_handler = self.addScaleHandle([0.5, 0.5], [0, 0])
|
||||
else:
|
||||
logger.debug("removing beam marker resize handle")
|
||||
self.removeHandle(self._scale_handler)
|
||||
self._scale_handler = None
|
||||
|
||||
def get_camera_beam_position(self):
|
||||
return self.getState()["pos"]
|
||||
|
||||
def set_beam_size_marker_dialog(self):
|
||||
w, h = settings.value("beam/size")
|
||||
d = GenericDialog(
|
||||
title="Beamsize",
|
||||
message="Enter the size of the beam in microns",
|
||||
inputs={
|
||||
"width": ("Width (\u00B5)", int(w), QSpinBox()),
|
||||
"height": ("Height (\u00B5)", int(h), QSpinBox()),
|
||||
},
|
||||
)
|
||||
if d.exec():
|
||||
results = d.results
|
||||
logger.info("Updating beamsize to {}".format(results))
|
||||
w, h = results["width"], results["height"]
|
||||
logger.debug("types {}".format(type(w)))
|
||||
settings.setValue("beam/size", (w, h))
|
||||
self.set_beam_size((w, h))
|
||||
|
||||
def flip_ud(self):
|
||||
"""flip up-down"""
|
||||
t = self.transform()
|
||||
m11 = t.m11()
|
||||
m12 = t.m12()
|
||||
m13 = t.m13()
|
||||
m21 = t.m21()
|
||||
m22 = t.m22()
|
||||
m23 = t.m23()
|
||||
m31 = t.m31()
|
||||
m32 = t.m32()
|
||||
m33 = t.m33()
|
||||
t.setMatrix(m11, m12, m13, m21, -m22, m23, m31, m32, m33)
|
||||
self.setTransform(t)
|
||||
|
||||
def set_beam_size(self, size=None):
|
||||
if size is not None:
|
||||
self._size = size
|
||||
ppm = self._ppm
|
||||
w, h = self._size
|
||||
nw, nh = (w / 1000) * ppm, (h / 1000) * ppm
|
||||
self.setSize((nw, nh))
|
||||
self.setToolTip("Beamsize (W x H): {:.1f}\u00B5 x {:.1f}\u00B5".format(w, h))
|
||||
|
||||
def my_update_ppm(self, ppm):
|
||||
self._ppm = ppm
|
||||
self.set_beam_size()
|
||||
|
||||
def invalidate(self):
|
||||
self._shape = None
|
||||
self.prepareGeometryChange()
|
||||
|
||||
def boundingRect(self):
|
||||
return self.shape().boundingRect()
|
||||
|
||||
def shape(self):
|
||||
w, h = self.size()
|
||||
if self._shape is None:
|
||||
p = QtGui.QPainterPath()
|
||||
p.moveTo(Point(0, -h))
|
||||
p.lineTo(Point(0, h))
|
||||
p.moveTo(Point(-w, 0))
|
||||
p.lineTo(Point(w, 0))
|
||||
#
|
||||
p.moveTo(Point(-w, -h))
|
||||
p.lineTo(Point(w, -h))
|
||||
p.lineTo(Point(w, h))
|
||||
p.lineTo(Point(-w, -h))
|
||||
stroker = QtGui.QPainterPathStroker()
|
||||
outline = stroker.createStroke(p)
|
||||
self._shape = outline
|
||||
|
||||
return self._shape
|
||||
|
||||
def paint(self, p, *args):
|
||||
w, h = self.getState()["size"]
|
||||
|
||||
v1, v2 = -(h * 0.8) / 2, (h * 0.8) / 2
|
||||
h1, h2 = -(w * 0.8) / 2, (w * 0.8) / 2
|
||||
|
||||
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
p.setPen(fn.mkPen(width=3, color=[200, 100, 100]))
|
||||
p.drawLine(Point(0, v1), Point(0, v2))
|
||||
p.drawLine(Point(h1, 0), Point(h2, 0))
|
||||
|
||||
p.setPen(fn.mkPen(width=3, color=[200, 200, 100]))
|
||||
p.drawRect(-w / 2, -h / 2, w, h)
|
||||
# p.drawText(-w, -h, '{:.0f}x{:.0f}'.format(*self._size))
|
||||
|
||||
|
||||
class CrystalCircle(ROI):
|
||||
"""A crosshair ROI whose position is at the center of the crosshairs. By default, it is scalable, rotatable and translatable."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
pos=None,
|
||||
size=None,
|
||||
parent=None,
|
||||
gonio_xy=(0, 0),
|
||||
model_row_index=1,
|
||||
is_fiducial=False,
|
||||
ppm=False,
|
||||
**kargs
|
||||
):
|
||||
if size is None:
|
||||
size = [20, 20]
|
||||
if pos is None:
|
||||
pos = [0, 0]
|
||||
self._size = size
|
||||
self._shape = None
|
||||
self.parent = parent
|
||||
self._model_row_index = model_row_index
|
||||
self._is_fiducial = is_fiducial
|
||||
ROI.__init__(self, pos, size, **kargs)
|
||||
|
||||
self._beam_position = parent.get_beam_mark_on_camera_xy()
|
||||
self.set_gonio_xy(*gonio_xy)
|
||||
|
||||
self.setToolTip(f"{'fiducial' if is_fiducial else 'crystal'} {model_row_index}")
|
||||
|
||||
# parent.pixelsPerMillimeter.connect(self.update_pixels_per_millimeter)
|
||||
# parent.beamCameraCoordinatesChanged.connect(self.update_beam_position)
|
||||
# parent.fast_x_position.connect(self.follow_x)
|
||||
# parent.fast_y_position.connect(self.follow_y)
|
||||
self._ppm = ppm
|
||||
self._original_ppm = ppm # marker only works at original zoom
|
||||
# because zoom axis is not aligned
|
||||
# with viewing axis
|
||||
|
||||
# self.calculate_gonio_xy()
|
||||
self.sigRegionChanged.connect(self.invalidate)
|
||||
# self.sigRegionChangeFinished.connect(self.calculate_gonio_xy)
|
||||
# self.sigHoverEvent.connect(self.my_hovering)
|
||||
self.aspectLocked = False
|
||||
self.translatable = True
|
||||
self.rotateAllowed = False
|
||||
self.invertible = False
|
||||
self.deletable = False
|
||||
|
||||
def enable_stage_tracking(self):
|
||||
self.parent.fast_x_position.connect(self.follow_x)
|
||||
self.parent.fast_y_position.connect(self.follow_y)
|
||||
|
||||
def disable_stage_tracking(self):
|
||||
self.parent.fast_x_position.disconnect(self.follow_x)
|
||||
self.parent.fast_y_position.disconnect(self.follow_y)
|
||||
|
||||
def follow_x(self, v):
|
||||
self.follow_stage(fx=v)
|
||||
|
||||
def follow_y(self, v):
|
||||
self.follow_stage(fy=v)
|
||||
|
||||
def my_hovering(self, event):
|
||||
print("hovering: {}".format(self.mouseHovering))
|
||||
|
||||
def get_model_row(self):
|
||||
return self._model_row_index
|
||||
|
||||
def update_ppm(self, ppm):
|
||||
self._ppm = ppm
|
||||
self.recalc_camera_position()
|
||||
|
||||
def follow_stage(self, fx=None, fy=None):
|
||||
self._fx, self._fy = self.parent.getFastX(), self.parent.getFastY()
|
||||
self.recalc_camera_position()
|
||||
|
||||
def set_gonio_xy(self, x, y):
|
||||
self._gx, self._gy = x, y
|
||||
|
||||
def disconnect_signals(self):
|
||||
self.parent.pixelsPerMillimeter.disconnect(self.update_ppm)
|
||||
self.parent.beamCameraCoordinatesChanged.disconnect(self.update_beam_position)
|
||||
self.parent.fast_x_position.disconnect(self.follow_x)
|
||||
self.parent.fast_y_position.disconnect(self.follow_y)
|
||||
|
||||
def reconnect_signals(self):
|
||||
self.parent.pixelsPerMillimeter.connect(self.update_ppm)
|
||||
self.parent.beamCameraCoordinatesChanged.connect(self.update_beam_position)
|
||||
self.parent.fast_x_position.connect(self.follow_x)
|
||||
self.parent.fast_y_position.connect(self.follow_y)
|
||||
|
||||
def removeClicked(self):
|
||||
self.disconnect_signals()
|
||||
super(CrystalCircle, self).removeClicked()
|
||||
|
||||
def recalc_camera_position(self):
|
||||
idx = self._model_row_index
|
||||
ppm = self._ppm
|
||||
bx, by = self.parent.get_beam_mark_on_camera_xy()
|
||||
gx, gy = self._gx, self._gy # marker position in gonio coords
|
||||
fx, fy = self._fx, self._fy
|
||||
# distance to beam in pixels for new ppm
|
||||
dx, dy = (ppm * (fx - gx), ppm * (fy - gy))
|
||||
x, y = bx - dx, by + dy
|
||||
# logger.debug(f"recalc {idx}: cam = {(x,y)}, gonio = {(gx, gy)}")
|
||||
self.setPos(x, y)
|
||||
|
||||
def update_beam_position(self, pos):
|
||||
self._beam_position = pos
|
||||
|
||||
def show_size(self):
|
||||
try:
|
||||
ppm = self._ppm
|
||||
except:
|
||||
return
|
||||
w, h = self.getState()["size"]
|
||||
# convert to micrometer
|
||||
nw, nh = (w * 1000) / ppm, (h * 1000) / ppm
|
||||
|
||||
def invalidate(self):
|
||||
self._shape = None
|
||||
self.prepareGeometryChange()
|
||||
|
||||
def boundingRect(self):
|
||||
return self.shape().boundingRect()
|
||||
|
||||
def shape(self):
|
||||
if self._shape is None:
|
||||
radius = self.getState()["size"][1]
|
||||
p = QtGui.QPainterPath()
|
||||
p.moveTo(Point(0, -radius))
|
||||
p.lineTo(Point(0, radius))
|
||||
p.moveTo(Point(-radius, 0))
|
||||
p.lineTo(Point(radius, 0))
|
||||
stroker = QtGui.QPainterPathStroker()
|
||||
stroker.setWidth(10)
|
||||
outline = stroker.createStroke(p)
|
||||
self._shape = outline
|
||||
|
||||
return self._shape
|
||||
|
||||
def paint(self, p, *args):
|
||||
if abs(self._ppm - self._original_ppm) < 1.:
|
||||
if self._is_fiducial:
|
||||
pen = mkPen(color="r", width=2)
|
||||
else:
|
||||
pen = mkPen(color="y", width=2)
|
||||
else:
|
||||
pen = mkPen(color="#ffffaa88", width=2)
|
||||
x, y = self.getState()["size"]
|
||||
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
p.setPen(pen)
|
||||
|
||||
p.drawLine(Point(0, -x), Point(0, x))
|
||||
p.drawLine(Point(-x, 0), Point(x, 0))
|
||||
p.drawEllipse(-x, -y, 2 * x, 2 * y)
|
||||
|
||||
|
||||
class Crosshair(ROI):
|
||||
"""A crosshair ROI whose position is at the center of the crosshairs. By default, it is scalable, rotatable and translatable."""
|
||||
|
||||
def __init__(self, pos=None, size=None, **kargs):
|
||||
if size is None:
|
||||
size = [10, 10]
|
||||
if pos is None:
|
||||
pos = [0, 0]
|
||||
self._shape = None
|
||||
ROI.__init__(self, pos, size, **kargs)
|
||||
|
||||
self.sigRegionChanged.connect(self.invalidate)
|
||||
self.aspectLocked = True
|
||||
|
||||
def invalidate(self):
|
||||
self._shape = None
|
||||
self.prepareGeometryChange()
|
||||
|
||||
def boundingRect(self):
|
||||
return self.shape().boundingRect()
|
||||
|
||||
def shape(self):
|
||||
if self._shape is None:
|
||||
radius = self.getState()["size"][1]
|
||||
p = QtGui.QPainterPath()
|
||||
p.moveTo(Point(0, -radius))
|
||||
p.lineTo(Point(0, radius))
|
||||
p.moveTo(Point(-radius, 0))
|
||||
p.lineTo(Point(radius, 0))
|
||||
# p = self.mapToDevice(p)
|
||||
stroker = QtGui.QPainterPathStroker()
|
||||
stroker.setWidth(10)
|
||||
outline = stroker.createStroke(p)
|
||||
# self._shape = self.mapFromDevice(outline)
|
||||
self._shape = outline
|
||||
|
||||
return self._shape
|
||||
|
||||
def paint(self, p, *args):
|
||||
radius = self.getState()["size"][1]
|
||||
p.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
p.setPen(self.currentPen)
|
||||
|
||||
p.drawLine(Point(0, -radius), Point(0, radius))
|
||||
p.drawLine(Point(-radius, 0), Point(radius, 0))
|
||||
Reference in New Issue
Block a user