Source code for qudi.util.widgets.plotting.roi

# -*- coding: utf-8 -*-

"""
Copyright (c) 2021, the qudi developers. See the AUTHORS.md file at the top-level directory of this
distribution and on <https://github.com/Ulm-IQO/qudi-core/>

This file is part of qudi.

Qudi is free software: you can redistribute it and/or modify it under the terms of
the GNU Lesser General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.

Qudi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with qudi.
If not, see <https://www.gnu.org/licenses/>.
"""

__all__ = ['RectangleROI']

from math import isinf
from typing import Union, Tuple, Optional, Sequence, List
from PySide2 import QtCore
from pyqtgraph import ROI


[docs] class RectangleROI(ROI): """ """
[docs] def __init__(self, pos=(0, 0), size=(1, 1), bounds=None, apply_bounds_to_center=False, parent=None, pen=None, hoverPen=None, handlePen=None, handleHoverPen=None, movable=True, resizable=True, aspectLocked=False ) -> None: ROI.__init__(self, (0, 0), size=(1, 1), angle=0, invertible=True, maxBounds=None, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=parent, pen=pen, hoverPen=hoverPen, handlePen=handlePen, handleHoverPen=handleHoverPen, movable=movable, rotatable=False, resizable=resizable, removable=False, aspectLocked=aspectLocked) self.__center_position = (0, 0) self.__norm_size = (1, 1) self.__min_norm_size = (0, 0) self.__start_pos = (0, 0) self._apply_bounds_to_center = bool(apply_bounds_to_center) self._bounds = self.normalize_bounds(bounds) self.set_area(position=pos, size=size)
@property def min_size(self) -> Tuple[float, float]: return self.__min_norm_size def set_min_size(self, size: Union[None, Tuple[float, float]]) -> None: self.__min_norm_size = (0, 0) if size is None else (abs(size[0]), abs(size[1])) self.set_area(size=self.__norm_size) @property def area(self) -> Tuple[Tuple[float, float], Tuple[float, float]]: return self.__center_position, self.__norm_size def set_area(self, position: Optional[Tuple[float, float]] = None, size: Optional[Tuple[float, float]] = None ) -> None: if position is not None: constr_size = (max(self.__norm_size[0], self.__min_norm_size[0]), max(self.__norm_size[1], self.__min_norm_size[1])) self.setPos(QtCore.QPointF(position[0] - constr_size[0] / 2, position[1] + constr_size[1] / 2), update=False, finish=False) self.__center_position = (position[0], position[1]) if size is not None: size = (abs(size[0]), abs(size[1])) constr_size = (max(size[0], self.__min_norm_size[0]), max(size[1], self.__min_norm_size[1])) self.setSize(QtCore.QPointF(constr_size[0], -constr_size[1]), center=(0.5, 0.5), update=False, finish=False) self.__norm_size = size if position is not None and size is not None: self._clip_area(update=True, finish=True) @property def bounds(self) -> List[Tuple[Union[None, float], Union[None, float]]]: return self._bounds.copy() def set_bounds(self, bounds: Union[None, Sequence[Tuple[Union[None, float], Union[None, float]]]] ) -> None: self._bounds = self.normalize_bounds(bounds) self._clip_area(update=True, finish=True) def _clip_area(self, update: Optional[bool] = True, finish: Optional[bool] = True) -> None: position = list(self.__center_position) size = list(self.__norm_size) x_min, x_max = self._bounds[0] y_min, y_max = self._bounds[1] if self._apply_bounds_to_center: if (x_min is not None) and (position[0] < x_min): position[0] = x_min elif (x_max is not None) and (position[0] > x_max): position[0] = x_max if (y_min is not None) and (position[1] < y_min): position[1] = y_min elif (y_max is not None) and (position[1] > y_max): position[1] = y_max else: left = position[0] - size[0] / 2 right = position[0] + size[0] / 2 top = position[1] + size[1] / 2 bottom = position[1] - size[1] / 2 if (x_min is not None) and (left < x_min): position[0] = x_min + size[0] / 2 elif (x_max is not None) and (right > x_max): position[0] = x_max - size[0] / 2 if (y_min is not None) and (bottom < y_min): position[1] = y_min + size[1] / 2 elif (y_max is not None) and (top > y_max): position[1] = y_max - size[1] / 2 translate = (position[0] - self.__center_position[0], position[1] - self.__center_position[1]) self.__center_position = tuple(position) self.translate(translate, update=update, finish=finish) @staticmethod def normalize_bounds(bounds: Union[None, Sequence[Tuple[Union[None, float], Union[None, float]]]] ) -> List[Tuple[Union[None, float], Union[None, float]]]: if bounds is None: bounds = [(None, None), (None, None)] else: bounds = [list(span) for span in bounds] # Replace inf values by None for span in bounds: for ii, val in enumerate(span): try: if isinf(val): span[ii] = None except TypeError: pass # Sort spans in ascending order try: bounds[0] = tuple(sorted(bounds[0])) except TypeError: bounds[0] = tuple(bounds[0]) try: bounds[1] = tuple(sorted(bounds[1])) except TypeError: bounds[1] = tuple(bounds[1]) return bounds def checkPointMove(self, handle, pos, modifiers): if not self._apply_bounds_to_center: pos = self.mapSceneToParent(pos) x_min, x_max = self._bounds[0] y_min, y_max = self._bounds[1] if (x_min is not None) and pos.x() < x_min: return False if (x_max is not None) and pos.x() > x_max: return False if (y_min is not None) and pos.y() < y_min: return False if (y_max is not None) and pos.y() > y_max: return False return True def cancelMove(self) -> None: if self.isMoving: self.__center_position = self.__start_pos return super().cancelMove() def mouseDragEvent(self, ev) -> None: if not ev.isAccepted(): if self.translatable and ev.button() == QtCore.Qt.LeftButton and ev.modifiers() == QtCore.Qt.NoModifier: is_start = ev.isStart() is_finish = ev.isFinish() ev.accept() if is_start: self.setSelected(True) self._moveStarted() self.__start_pos = self.__center_position if self.isMoving: total_move = self.mapToParent(ev.pos()) - self.mapToParent(ev.buttonDownPos()) self.__center_position = (self.__start_pos[0] + total_move.x(), self.__start_pos[1] + total_move.y()) new_pos = self.preMoveState['pos'] + total_move self.setPos(new_pos, update=False, finish=False) self._clip_area(update=True, finish=False) if is_finish: self._moveFinished() else: ev.ignore()