from pyqtgraph.opengl.GLGraphicsItem import GLGraphicsItem
import pyqtgraph.opengl as gl
from pyqtgraph.Point import Point
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
from pyqtgraph import functions as fn
from pyqtgraph.graphicsItems.GraphicsObject import GraphicsObject
from pyqtgraph.graphicsItems.UIGraphicsItem import UIGraphicsItem
from math import cos, sin


class GLEllipseROI(gl.GLLinePlotItem):
    """
    **Signals**
    sigRegionChangeFinished Emitted when the user stops dragging the ROI (or
                            one of its handles) or if the ROI is changed
                            programatically.
    sigRegionChangeStarted  Emitted when the user starts dragging the ROI (or
                            one of its handles).
    sigRegionChanged        Emitted any time the position of the ROI changes,
                            including while it is being dragged by the user.
    sigHoverEvent           Emitted when the mouse hovers over the ROI.
    sigClicked              Emitted when the user clicks on the ROI.
                            Note that clicking is disabled by default to prevent
                            stealing clicks from objects behind the ROI. To
                            enable clicking, call
                            roi.setAcceptedMouseButtons(QtCore.Qt.LeftButton).
                            See QtGui.QGraphicsItem documentation for more
                            details.
    sigRemoveRequested      Emitted when the user selects 'remove' from the
                            ROI's context menu (if available).

    """
    sigRegionChangeFinished = QtCore.Signal(object)
    sigRegionChangeStarted = QtCore.Signal(object)
    sigRegionChanged = QtCore.Signal(object)
    sigHoverEvent = QtCore.Signal(object)
    sigClicked = QtCore.Signal(object, object)
    sigRemoveRequested = QtCore.Signal(object)

    def __init_(self, **kwds):
        gl.GLLinePlotItem.__init__(self, **kwds)

        self.freeHandleMoved = False  ## keep track of whether free handles have moved since last change signal was emitted.
        self.mouseHovering = False

        self.handles = []
        self.state = {'pos': Point(0, 0), 'size': Point(1, 1),
                      'angle': 0}  ## angle is in degrees for ease of Qt integration

        self.lastState = None
        self.isMoving = False

    def getState(self):
        return self.stateCopy()

    def stateCopy(self):
        sc = {}
        sc['pos'] = Point(self.state['pos'])
        sc['size'] = Point(self.state['size'])
        sc['angle'] = self.state['angle']
        return sc

    def saveState(self):
        """Return the state of the widget in a format suitable for storing to
        disk. (Points are converted to tuple)

        Combined with setState(), this allows ROIs to be easily saved and
        restored."""
        state = {}
        state['pos'] = tuple(self.state['pos'])
        state['size'] = tuple(self.state['size'])
        state['angle'] = self.state['angle']
        return state

    def setState(self, state, update=True):
        """
        Set the state of the ROI from a structure generated by saveState() or
        getState().
        """
        self.setPos(state['pos'], update=False)
        self.setSize(state['size'], update=False)
        self.setAngle(state['angle'], update=update)

    def size(self):
        """Return the size (w,h) of the ROI."""
        return self.getState()['size']

    def pos(self):
        """Return the position (x,y) of the ROI's origin.
        For most ROIs, this will be the lower-left corner."""
        return self.getState()['pos']

    def angle(self):
        """Return the angle of the ROI in degrees."""
        return self.getState()['angle']

    def setPos(self, pos, y=None, update=True, finish=True):
        """Set the position of the ROI (in the parent's coordinate system).

        Accepts either separate (x, y) arguments or a single :class:`Point` or
        ``QPointF`` argument.

        By default, this method causes both ``sigRegionChanged`` and
        ``sigRegionChangeFinished`` to be emitted. If *finish* is False, then
        ``sigRegionChangeFinished`` will not be emitted. You can then use
        stateChangeFinished() to cause the signal to be emitted after a series
        of state changes.

        If *update* is False, the state change will be remembered but not processed and no signals
        will be emitted. You can then use stateChanged() to complete the state change. This allows
        multiple change functions to be called sequentially while minimizing processing overhead
        and repeated signals. Setting ``update=False`` also forces ``finish=False``.
        """
        if y is None:
            pos = Point(pos)
        else:
            # avoid ambiguity where update is provided as a positional argument
            if isinstance(y, bool):
                raise TypeError("Positional arguments to setPos() must be numerical.")
            pos = Point(pos, y)
        self.state['pos'] = pos
        QtGui.QGraphicsItem.setPos(self, pos)
        if update:
            self.stateChanged(finish=finish)

    def setSize(self, size, update=True, finish=True):
        """Set the size of the ROI. May be specified as a QPoint, Point, or list of two values.
        See setPos() for an explanation of the update and finish arguments.
        """
        size = Point(size)
        self.prepareGeometryChange()
        self.state['size'] = size
        if update:
            self.stateChanged(finish=finish)

    def setAngle(self, angle, update=True, finish=True):
        """Set the angle of rotation (in degrees) for this ROI.
        See setPos() for an explanation of the update and finish arguments.
        """
        self.state['angle'] = angle
        tr = QtGui.QTransform()
        # tr.rotate(-angle * 180 / np.pi)
        tr.rotate(angle)
        self.setTransform(tr)
        if update:
            self.stateChanged(finish=finish)

    def scale(self, s, center=[0, 0], update=True, finish=True):
        """
        Resize the ROI by scaling relative to *center*.
        See setPos() for an explanation of the *update* and *finish* arguments.
        """
        c = self.mapToParent(Point(center) * self.state['size'])
        self.prepareGeometryChange()
        newSize = self.state['size'] * s
        c1 = self.mapToParent(Point(center) * newSize)
        newPos = self.state['pos'] + c - c1

        self.setSize(newSize, update=False)
        self.setPos(newPos, update=update, finish=finish)

    def translate(self, *args, **kargs):
        """
        Move the ROI to a new position.
        Accepts either (x, y, snap) or ([x,y], snap) as arguments
        If the ROI is bounded and the move would exceed boundaries, then the ROI
        is moved to the nearest acceptable position instead.

        *snap* can be:

        =============== ==========================================================================
        None (default)  use self.translateSnap and self.snapSize to determine whether/how to snap
        False           do not snap
        Point(w,h)      snap to rectangular grid with spacing (w,h)
        True            snap using self.snapSize (and ignoring self.translateSnap)
        =============== ==========================================================================

        Also accepts *update* and *finish* arguments (see setPos() for a description of these).
        """

        if len(args) == 1:
            pt = args[0]
        else:
            pt = args

        newState = self.stateCopy()
        newState['pos'] = newState['pos'] + pt

        ## snap position
        # snap = kargs.get('snap', None)
        # if (snap is not False)   and   not (snap is None and self.translateSnap is False):

        snap = kargs.get('snap', None)
        if snap is None:
            snap = self.translateSnap
        if snap is not False:
            newState['pos'] = self.getSnapPosition(newState['pos'], snap=snap)

        # d = ev.scenePos() - self.mapToScene(self.pressPos)
        if self.maxBounds is not None:
            r = self.stateRect(newState)
            # r0 = self.sceneTransform().mapRect(self.boundingRect())
            d = Point(0, 0)
            if self.maxBounds.left() > r.left():
                d[0] = self.maxBounds.left() - r.left()
            elif self.maxBounds.right() < r.right():
                d[0] = self.maxBounds.right() - r.right()
            if self.maxBounds.top() > r.top():
                d[1] = self.maxBounds.top() - r.top()
            elif self.maxBounds.bottom() < r.bottom():
                d[1] = self.maxBounds.bottom() - r.bottom()
            newState['pos'] += d

        # self.state['pos'] = newState['pos']
        update = kargs.get('update', True)
        finish = kargs.get('finish', True)
        self.setPos(newState['pos'], update=update, finish=finish)
        # if 'update' not in kargs or kargs['update'] is True:
        # self.stateChanged()

    def rotate(self, angle, update=True, finish=True):
        """
        Rotate the ROI by *angle* degrees.

        Also accepts *update* and *finish* arguments (see setPos() for a
        description of these).
        """
        self.setAngle(self.angle() + angle, update=update, finish=finish)

    def handleMoveStarted(self):
        self.preMoveState = self.getState()

    def addTranslateHandle(self, pos, axes=None, item=None, name=None, index=None):
        """
        Add a new translation handle to the ROI. Dragging the handle will move
        the entire ROI without changing its angle or shape.

        Note that, by default, ROIs may be moved by dragging anywhere inside the
        ROI. However, for larger ROIs it may be desirable to disable this and
        instead provide one or more translation handles.

        =================== ====================================================
        **Arguments**
        pos                 (length-2 sequence) The position of the handle
                            relative to the shape of the ROI. A value of (0,0)
                            indicates the origin, whereas (1, 1) indicates the
                            upper-right corner, regardless of the ROI's size.
        item                The Handle instance to add. If None, a new handle
                            will be created.
        name                The name of this handle (optional). Handles are
                            identified by name when calling
                            getLocalHandlePositions and getSceneHandlePositions.
        =================== ====================================================
        """
        pos = Point(pos)
        return self.addHandle({'name': name, 'type': 't', 'pos': pos, 'item': item}, index=index)

    def addFreeHandle(self, pos=None, axes=None, item=None, name=None, index=None):
        """
        Add a new free handle to the ROI. Dragging free handles has no effect
        on the position or shape of the ROI.

        =================== ====================================================
        **Arguments**
        pos                 (length-2 sequence) The position of the handle
                            relative to the shape of the ROI. A value of (0,0)
                            indicates the origin, whereas (1, 1) indicates the
                            upper-right corner, regardless of the ROI's size.
        item                The Handle instance to add. If None, a new handle
                            will be created.
        name                The name of this handle (optional). Handles are
                            identified by name when calling
                            getLocalHandlePositions and getSceneHandlePositions.
        =================== ====================================================
        """
        if pos is not None:
            pos = Point(pos)
        return self.addHandle({'name': name, 'type': 'f', 'pos': pos, 'item': item}, index=index)

    def addScaleHandle(self, pos, center, axes=None, item=None, name=None, lockAspect=False, index=None):
        """
        Add a new scale handle to the ROI. Dragging a scale handle allows the
        user to change the height and/or width of the ROI.

        =================== ====================================================
        **Arguments**
        pos                 (length-2 sequence) The position of the handle
                            relative to the shape of the ROI. A value of (0,0)
                            indicates the origin, whereas (1, 1) indicates the
                            upper-right corner, regardless of the ROI's size.
        center              (length-2 sequence) The center point around which
                            scaling takes place. If the center point has the
                            same x or y value as the handle position, then
                            scaling will be disabled for that axis.
        item                The Handle instance to add. If None, a new handle
                            will be created.
        name                The name of this handle (optional). Handles are
                            identified by name when calling
                            getLocalHandlePositions and getSceneHandlePositions.
        =================== ====================================================
        """
        pos = Point(pos)
        center = Point(center)
        info = {'name': name, 'type': 's', 'center': center, 'pos': pos, 'item': item, 'lockAspect': lockAspect}
        if pos.x() == center.x():
            info['xoff'] = True
        if pos.y() == center.y():
            info['yoff'] = True
        return self.addHandle(info, index=index)

    def addRotateHandle(self, pos, center, item=None, name=None, index=None):
        """
        Add a new rotation handle to the ROI. Dragging a rotation handle allows
        the user to change the angle of the ROI.

        =================== ====================================================
        **Arguments**
        pos                 (length-2 sequence) The position of the handle
                            relative to the shape of the ROI. A value of (0,0)
                            indicates the origin, whereas (1, 1) indicates the
                            upper-right corner, regardless of the ROI's size.
        center              (length-2 sequence) The center point around which
                            rotation takes place.
        item                The Handle instance to add. If None, a new handle
                            will be created.
        name                The name of this handle (optional). Handles are
                            identified by name when calling
                            getLocalHandlePositions and getSceneHandlePositions.
        =================== ====================================================
        """
        pos = Point(pos)
        center = Point(center)
        return self.addHandle({'name': name, 'type': 'r', 'center': center, 'pos': pos, 'item': item}, index=index)

    def addScaleRotateHandle(self, pos, center, item=None, name=None, index=None):
        """
        Add a new scale+rotation handle to the ROI. When dragging a handle of
        this type, the user can simultaneously rotate the ROI around an
        arbitrary center point as well as scale the ROI by dragging the handle
        toward or away from the center point.

        =================== ====================================================
        **Arguments**
        pos                 (length-2 sequence) The position of the handle
                            relative to the shape of the ROI. A value of (0,0)
                            indicates the origin, whereas (1, 1) indicates the
                            upper-right corner, regardless of the ROI's size.
        center              (length-2 sequence) The center point around which
                            scaling and rotation take place.
        item                The Handle instance to add. If None, a new handle
                            will be created.
        name                The name of this handle (optional). Handles are
                            identified by name when calling
                            getLocalHandlePositions and getSceneHandlePositions.
        =================== ====================================================
        """
        pos = Point(pos)
        center = Point(center)
        if pos[0] != center[0] and pos[1] != center[1]:
            raise Exception("Scale/rotate handles must have either the same x or y coordinate as their center point.")
        return self.addHandle({'name': name, 'type': 'sr', 'center': center, 'pos': pos, 'item': item}, index=index)

    def addRotateFreeHandle(self, pos, center, axes=None, item=None, name=None, index=None):
        """
        Add a new rotation+free handle to the ROI. When dragging a handle of
        this type, the user can rotate the ROI around an
        arbitrary center point, while moving toward or away from the center
        point has no effect on the shape of the ROI.

        =================== ====================================================
        **Arguments**
        pos                 (length-2 sequence) The position of the handle
                            relative to the shape of the ROI. A value of (0,0)
                            indicates the origin, whereas (1, 1) indicates the
                            upper-right corner, regardless of the ROI's size.
        center              (length-2 sequence) The center point around which
                            rotation takes place.
        item                The Handle instance to add. If None, a new handle
                            will be created.
        name                The name of this handle (optional). Handles are
                            identified by name when calling
                            getLocalHandlePositions and getSceneHandlePositions.
        =================== ====================================================
        """
        pos = Point(pos)
        center = Point(center)
        return self.addHandle({'name': name, 'type': 'rf', 'center': center, 'pos': pos, 'item': item}, index=index)

    def addHandle(self, info, index=None):
        ## If a Handle was not supplied, create it now
        if 'item' not in info or info['item'] is None:
            h = Handle(self.handleSize, typ=info['type'], pen=self.handlePen, parent=self)
            h.setPos(info['pos'] * self.state['size'])
            info['item'] = h
        else:
            h = info['item']
            if info['pos'] is None:
                info['pos'] = h.pos()

        ## connect the handle to this ROI
        # iid = len(self.handles)
        h.connectROI(self)
        if index is None:
            self.handles.append(info)
        else:
            self.handles.insert(index, info)

        h.setZValue(self.zValue() + 1)
        self.stateChanged()
        return h

    def indexOfHandle(self, handle):
        """
        Return the index of *handle* in the list of this ROI's handles.
        """
        if isinstance(handle, Handle):
            index = [i for i, info in enumerate(self.handles) if info['item'] is handle]
            if len(index) == 0:
                raise Exception("Cannot return handle index; not attached to this ROI")
            return index[0]
        else:
            return handle

    def removeHandle(self, handle):
        """Remove a handle from this ROI. Argument may be either a Handle
        instance or the integer index of the handle."""
        index = self.indexOfHandle(handle)

        handle = self.handles[index]['item']
        self.handles.pop(index)
        handle.disconnectROI(self)
        if len(handle.rois) == 0:
            self.scene().removeItem(handle)
        self.stateChanged()

    def replaceHandle(self, oldHandle, newHandle):
        """Replace one handle in the ROI for another. This is useful when
        connecting multiple ROIs together.

        *oldHandle* may be a Handle instance or the index of a handle to be
        replaced."""
        index = self.indexOfHandle(oldHandle)
        info = self.handles[index]
        self.removeHandle(index)
        info['item'] = newHandle
        info['pos'] = newHandle.pos()
        self.addHandle(info, index=index)

    def checkRemoveHandle(self, handle):
        ## This is used when displaying a Handle's context menu to determine
        ## whether removing is allowed.
        ## Subclasses may wish to override this to disable the menu entry.
        ## Note: by default, handles are not user-removable even if this method returns True.
        return True

    def getLocalHandlePositions(self, index=None):
        """Returns the position of handles in the ROI's coordinate system.

        The format returned is a list of (name, pos) tuples.
        """
        if index == None:
            positions = []
            for h in self.handles:
                positions.append((h['name'], h['pos']))
            return positions
        else:
            return (self.handles[index]['name'], self.handles[index]['pos'])

    def getSceneHandlePositions(self, index=None):
        """Returns the position of handles in the scene coordinate system.

        The format returned is a list of (name, pos) tuples.
        """
        if index == None:
            positions = []
            for h in self.handles:
                positions.append((h['name'], h['item'].scenePos()))
            return positions
        else:
            return (self.handles[index]['name'], self.handles[index]['item'].scenePos())

    def getHandles(self):
        """
        Return a list of this ROI's Handles.
        """
        return [h['item'] for h in self.handles]

    def mapSceneToParent(self, pt):
        return self.mapToParent(self.mapFromScene(pt))

    def setSelected(self, s):
        QtGui.QGraphicsItem.setSelected(self, s)
        # print "select", self, s
        if s:
            for h in self.handles:
                h['item'].show()
        else:
            for h in self.handles:
                h['item'].hide()

    def hoverEvent(self, ev):
        hover = False
        if not ev.isExit():
            if self.translatable and ev.acceptDrags(QtCore.Qt.LeftButton):
                hover = True

            for btn in [QtCore.Qt.LeftButton, QtCore.Qt.RightButton, QtCore.Qt.MidButton]:
                if int(self.acceptedMouseButtons() & btn) > 0 and ev.acceptClicks(btn):
                    hover = True
            if self.contextMenuEnabled():
                ev.acceptClicks(QtCore.Qt.RightButton)

        if hover:
            self.setMouseHover(True)
            self.sigHoverEvent.emit(self)
            ev.acceptClicks(
                QtCore.Qt.LeftButton)  ## If the ROI is hilighted, we should accept all clicks to avoid confusion.
            ev.acceptClicks(QtCore.Qt.RightButton)
            ev.acceptClicks(QtCore.Qt.MidButton)
        else:
            self.setMouseHover(False)

    def setMouseHover(self, hover):
        ## Inform the ROI that the mouse is(not) hovering over it
        if self.mouseHovering == hover:
            return
        self.mouseHovering = hover
        self._updateHoverColor()

    def _updateHoverColor(self):
        pen = self._makePen()
        if self.currentPen != pen:
            self.currentPen = pen
            self.update()

    def _makePen(self):
        # Generate the pen color for this ROI based on its current state.
        if self.mouseHovering:
            return fn.mkPen(255, 255, 0)
        else:
            return self.pen

    def contextMenuEnabled(self):
        return self.removable

    def raiseContextMenu(self, ev):
        if not self.contextMenuEnabled():
            return
        menu = self.getMenu()
        menu = self.scene().addParentContextMenus(self, menu, ev)
        pos = ev.screenPos()
        menu.popup(QtCore.QPoint(pos.x(), pos.y()))

    def getMenu(self):
        if self.menu is None:
            self.menu = QtGui.QMenu()
            self.menu.setTitle("ROI")
            remAct = QtGui.QAction("Remove ROI", self.menu)
            remAct.triggered.connect(self.removeClicked)
            self.menu.addAction(remAct)
            self.menu.remAct = remAct
        return self.menu

    def removeClicked(self):
        ## Send remove event only after we have exited the menu event handler
        QtCore.QTimer.singleShot(0, lambda: self.sigRemoveRequested.emit(self))

    def mouseDragEvent(self, ev):
        if ev.isStart():
            # p = ev.pos()
            # if not self.isMoving and not self.shape().contains(p):
            # ev.ignore()
            # return
            if ev.button() == QtCore.Qt.LeftButton:
                self.setSelected(True)
                if self.translatable:
                    self.isMoving = True
                    self.preMoveState = self.getState()
                    self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos())
                    self.sigRegionChangeStarted.emit(self)
                    ev.accept()
                else:
                    ev.ignore()

        elif ev.isFinish():
            if self.translatable:
                if self.isMoving:
                    self.stateChangeFinished()
                self.isMoving = False
            return

        if self.translatable and self.isMoving and ev.buttons() == QtCore.Qt.LeftButton:
            snap = True if (ev.modifiers() & QtCore.Qt.ControlModifier) else None
            newPos = self.mapToParent(ev.pos()) + self.cursorOffset
            self.translate(newPos - self.pos(), snap=snap, finish=False)

    def mouseClickEvent(self, ev):
        if ev.button() == QtCore.Qt.RightButton and self.isMoving:
            ev.accept()
            self.cancelMove()
        if ev.button() == QtCore.Qt.RightButton and self.contextMenuEnabled():
            self.raiseContextMenu(ev)
            ev.accept()
        elif int(ev.button() & self.acceptedMouseButtons()) > 0:
            ev.accept()
            self.sigClicked.emit(self, ev)
        else:
            ev.ignore()

    def cancelMove(self):
        self.isMoving = False
        self.setState(self.preMoveState)

    def checkPointMove(self, handle, pos, modifiers):
        """When handles move, they must ask the ROI if the move is acceptable.
        By default, this always returns True. Subclasses may wish override.
        """
        return True

    def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True, coords='parent'):
        ## called by Handles when they are moved.
        ## pos is the new position of the handle in scene coords, as requested by the handle.

        newState = self.stateCopy()
        index = self.indexOfHandle(handle)
        h = self.handles[index]
        p0 = self.mapToParent(h['pos'] * self.state['size'])
        p1 = Point(pos)

        if coords == 'parent':
            pass
        elif coords == 'scene':
            p1 = self.mapSceneToParent(p1)
        else:
            raise Exception("New point location must be given in either 'parent' or 'scene' coordinates.")

        ## transform p0 and p1 into parent's coordinates (same as scene coords if there is no parent). I forget why.
        # p0 = self.mapSceneToParent(p0)
        # p1 = self.mapSceneToParent(p1)

        ## Handles with a 'center' need to know their local position relative to the center point (lp0, lp1)
        if 'center' in h:
            c = h['center']
            cs = c * self.state['size']
            lp0 = self.mapFromParent(p0) - cs
            lp1 = self.mapFromParent(p1) - cs

        if h['type'] == 't':
            snap = True if (modifiers & QtCore.Qt.ControlModifier) else None
            # if self.translateSnap or ():
            # snap = Point(self.snapSize, self.snapSize)
            self.translate(p1 - p0, snap=snap, update=False)

        elif h['type'] == 'f':
            newPos = self.mapFromParent(p1)
            h['item'].setPos(newPos)
            h['pos'] = newPos
            self.freeHandleMoved = True
            # self.sigRegionChanged.emit(self)  ## should be taken care of by call to stateChanged()

        elif h['type'] == 's':
            ## If a handle and its center have the same x or y value, we can't scale across that axis.
            if h['center'][0] == h['pos'][0]:
                lp1[0] = 0
            if h['center'][1] == h['pos'][1]:
                lp1[1] = 0

            ## snap
            if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier):
                lp1[0] = round(lp1[0] / self.snapSize) * self.snapSize
                lp1[1] = round(lp1[1] / self.snapSize) * self.snapSize

            ## preserve aspect ratio (this can override snapping)
            if h['lockAspect'] or (modifiers & QtCore.Qt.AltModifier):
                # arv = Point(self.preMoveState['size']) -
                lp1 = lp1.proj(lp0)

            ## determine scale factors and new size of ROI
            hs = h['pos'] - c
            if hs[0] == 0:
                hs[0] = 1
            if hs[1] == 0:
                hs[1] = 1
            newSize = lp1 / hs

            ## Perform some corrections and limit checks
            if newSize[0] == 0:
                newSize[0] = newState['size'][0]
            if newSize[1] == 0:
                newSize[1] = newState['size'][1]
            if not self.invertible:
                if newSize[0] < 0:
                    newSize[0] = newState['size'][0]
                if newSize[1] < 0:
                    newSize[1] = newState['size'][1]
            if self.aspectLocked:
                newSize[0] = newSize[1]

            ## Move ROI so the center point occupies the same scene location after the scale
            s0 = c * self.state['size']
            s1 = c * newSize
            cc = self.mapToParent(s0 - s1) - self.mapToParent(Point(0, 0))

            ## update state, do more boundary checks
            newState['size'] = newSize
            newState['pos'] = newState['pos'] + cc
            if self.maxBounds is not None:
                r = self.stateRect(newState)
                if not self.maxBounds.contains(r):
                    return

            self.setPos(newState['pos'], update=False)
            self.setSize(newState['size'], update=False)

        elif h['type'] in ['r', 'rf']:
            if h['type'] == 'rf':
                self.freeHandleMoved = True

            if not self.rotateAllowed:
                return
            ## If the handle is directly over its center point, we can't compute an angle.
            try:
                if lp1.length() == 0 or lp0.length() == 0:
                    return
            except OverflowError:
                return

            ## determine new rotation angle, constrained if necessary
            ang = newState['angle'] - lp0.angle(lp1)
            if ang is None:  ## this should never happen..
                return
            if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
                ang = round(ang / 15.) * 15.  ## 180/12 = 15

            ## create rotation transform
            tr = QtGui.QTransform()
            tr.rotate(ang)

            ## move ROI so that center point remains stationary after rotate
            cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos'])
            newState['angle'] = ang
            newState['pos'] = newState['pos'] + cc

            ## check boundaries, update
            if self.maxBounds is not None:
                r = self.stateRect(newState)
                if not self.maxBounds.contains(r):
                    return
            # self.setTransform(tr)
            self.setPos(newState['pos'], update=False)
            self.setAngle(ang, update=False)
            # self.state = newState

            ## If this is a free-rotate handle, its distance from the center may change.

            if h['type'] == 'rf':
                h['item'].setPos(self.mapFromScene(p1))  ## changes ROI coordinates of handle

        elif h['type'] == 'sr':
            if h['center'][0] == h['pos'][0]:
                scaleAxis = 1
                nonScaleAxis = 0
            else:
                scaleAxis = 0
                nonScaleAxis = 1

            try:
                if lp1.length() == 0 or lp0.length() == 0:
                    return
            except OverflowError:
                return

            ang = newState['angle'] - lp0.angle(lp1)
            if ang is None:
                return
            if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
                # ang = round(ang / (np.pi/12.)) * (np.pi/12.)
                ang = round(ang / 15.) * 15.

            hs = abs(h['pos'][scaleAxis] - c[scaleAxis])
            newState['size'][scaleAxis] = lp1.length() / hs
            # if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier):
            if self.scaleSnap:  ## use CTRL only for angular snap here.
                newState['size'][scaleAxis] = round(newState['size'][scaleAxis] / self.snapSize) * self.snapSize
            if newState['size'][scaleAxis] == 0:
                newState['size'][scaleAxis] = 1
            if self.aspectLocked:
                newState['size'][nonScaleAxis] = newState['size'][scaleAxis]

            c1 = c * newState['size']
            tr = QtGui.QTransform()
            tr.rotate(ang)

            cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos'])
            newState['angle'] = ang
            newState['pos'] = newState['pos'] + cc
            if self.maxBounds is not None:
                r = self.stateRect(newState)
                if not self.maxBounds.contains(r):
                    return
            # self.setTransform(tr)
            # self.setPos(newState['pos'], update=False)
            # self.prepareGeometryChange()
            # self.state = newState
            self.setState(newState, update=False)

        self.stateChanged(finish=finish)

    def stateChanged(self, finish=True):
        """Process changes to the state of the ROI.
        If there are any changes, then the positions of handles are updated accordingly
        and sigRegionChanged is emitted. If finish is True, then
        sigRegionChangeFinished will also be emitted."""

        changed = False
        if self.lastState is None:
            changed = True
        else:
            state = self.getState()
            for k in list(state.keys()):
                if state[k] != self.lastState[k]:
                    changed = True

        self.prepareGeometryChange()
        if changed:
            ## Move all handles to match the current configuration of the ROI
            for h in self.handles:
                if h['item'] in self.childItems():
                    p = h['pos']
                    h['item'].setPos(h['pos'] * self.state['size'])
                # else:
                #    trans = self.state['pos']-self.lastState['pos']
                #    h['item'].setPos(h['pos'] + h['item'].parentItem().mapFromParent(trans))

            self.update()
            self.sigRegionChanged.emit(self)
        elif self.freeHandleMoved:
            self.sigRegionChanged.emit(self)

        self.freeHandleMoved = False
        self.lastState = self.getState()

        if finish:
            self.stateChangeFinished()
            self.informViewBoundsChanged()

    def stateChangeFinished(self):
        self.sigRegionChangeFinished.emit(self)

    def stateRect(self, state):
        r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1])
        tr = QtGui.QTransform()
        # tr.rotate(-state['angle'] * 180 / np.pi)
        tr.rotate(-state['angle'])
        r = tr.mapRect(r)
        return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1])

    def getSnapPosition(self, pos, snap=None):
        ## Given that pos has been requested, return the nearest snap-to position
        ## optionally, snap may be passed in to specify a rectangular snap grid.
        ## override this function for more interesting snap functionality..

        if snap is None or snap is True:
            if self.snapSize is None:
                return pos
            snap = Point(self.snapSize, self.snapSize)

        return Point(
            round(pos[0] / snap[0]) * snap[0],
            round(pos[1] / snap[1]) * snap[1]
        )

    def boundingRect(self):
        return QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized()

    def paint(self, p, opt, widget):
        # p.save()
        # Note: don't use self.boundingRect here, because subclasses may need to redefine it.
        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(r.width(), r.height())
        p.drawRect(0, 0, 1, 1)
        # p.restore()

    def getArraySlice(self, data, img, axes=(0, 1), returnSlice=True):
        """Return a tuple of slice objects that can be used to slice the region
        from *data* that is covered by the bounding rectangle of this ROI.
        Also returns the transform that maps the ROI into data coordinates.

        If returnSlice is set to False, the function returns a pair of tuples with the values that would have
        been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop))

        If the slice cannot be computed (usually because the scene/transforms are not properly
        constructed yet), then the method returns None.
        """
        ## Determine shape of array along ROI axes
        dShape = (data.shape[axes[0]], data.shape[axes[1]])

        ## Determine transform that maps ROI bounding box to image coordinates
        try:
            tr = self.sceneTransform() * fn.invertQTransform(img.sceneTransform())
        except np.linalg.linalg.LinAlgError:
            return None

        ## Modify transform to scale from image coords to data coords
        axisOrder = img.axisOrder
        if axisOrder == 'row-major':
            tr.scale(float(dShape[1]) / img.width(), float(dShape[0]) / img.height())
        else:
            tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height())

        ## Transform ROI bounds into data bounds
        dataBounds = tr.mapRect(self.boundingRect())

        ## Intersect transformed ROI bounds with data bounds
        if axisOrder == 'row-major':
            intBounds = dataBounds.intersected(QtCore.QRectF(0, 0, dShape[1], dShape[0]))
        else:
            intBounds = dataBounds.intersected(QtCore.QRectF(0, 0, dShape[0], dShape[1]))

        ## Determine index values to use when referencing the array.
        bounds = (
            (int(min(intBounds.left(), intBounds.right())), int(1 + max(intBounds.left(), intBounds.right()))),
            (int(min(intBounds.bottom(), intBounds.top())), int(1 + max(intBounds.bottom(), intBounds.top())))
        )
        if axisOrder == 'row-major':
            bounds = bounds[::-1]

        if returnSlice:
            ## Create slice objects
            sl = [slice(None)] * data.ndim
            sl[axes[0]] = slice(*bounds[0])
            sl[axes[1]] = slice(*bounds[1])
            return tuple(sl), tr
        else:
            return bounds, tr

    def getArrayRegion(self, data, img, axes=(0, 1), returnMappedCoords=False, **kwds):
        """Use the position and orientation of this ROI relative to an imageItem
        to pull a slice from an array.

        =================== ====================================================
        **Arguments**
        data                The array to slice from. Note that this array does
                            *not* have to be the same data that is represented
                            in *img*.
        img                 (ImageItem or other suitable QGraphicsItem)
                            Used to determine the relationship between the
                            ROI and the boundaries of *data*.
        axes                (length-2 tuple) Specifies the axes in *data* that
                            correspond to the (x, y) axes of *img*. If the
                            image's axis order is set to
                            'row-major', then the axes are instead specified in
                            (y, x) order.
        returnMappedCoords  (bool) If True, the array slice is returned along
                            with a corresponding array of coordinates that were
                            used to extract data from the original array.
        \**kwds             All keyword arguments are passed to
                            :func:`affineSlice <pyqtgraph.affineSlice>`.
        =================== ====================================================

        This method uses :func:`affineSlice <pyqtgraph.affineSlice>` to generate
        the slice from *data* and uses :func:`getAffineSliceParams <pyqtgraph.ROI.getAffineSliceParams>`
        to determine the parameters to pass to :func:`affineSlice <pyqtgraph.affineSlice>`.

        If *returnMappedCoords* is True, then the method returns a tuple (result, coords)
        such that coords is the set of coordinates used to interpolate values from the original
        data, mapped into the parent coordinate system of the image. This is useful, when slicing
        data from images that have been transformed, for determining the location of each value
        in the sliced data.

        All extra keyword arguments are passed to :func:`affineSlice <pyqtgraph.affineSlice>`.
        """
        # this is a hidden argument for internal use
        fromBR = kwds.pop('fromBoundingRect', False)

        shape, vectors, origin = self.getAffineSliceParams(data, img, axes, fromBoundingRect=fromBR)
        if not returnMappedCoords:
            rgn = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds)
            return rgn
        else:
            kwds['returnCoords'] = True
            result, coords = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds)

            ### map coordinates and return
            mapped = fn.transformCoordinates(img.transform(), coords)
            return result, mapped

    def getAffineSliceParams(self, data, img, axes=(0, 1), fromBoundingRect=False):
        """
        Returns the parameters needed to use :func:`affineSlice <pyqtgraph.affineSlice>`
        (shape, vectors, origin) to extract a subset of *data* using this ROI
        and *img* to specify the subset.

        If *fromBoundingRect* is True, then the ROI's bounding rectangle is used
        rather than the shape of the ROI.

        See :func:`getArrayRegion <pyqtgraph.ROI.getArrayRegion>` for more information.
        """
        if self.scene() is not img.scene():
            raise Exception("ROI and target item must be members of the same scene.")

        origin = img.mapToData(self.mapToItem(img, QtCore.QPointF(0, 0)))

        ## vx and vy point in the directions of the slice axes, but must be scaled properly
        vx = img.mapToData(self.mapToItem(img, QtCore.QPointF(1, 0))) - origin
        vy = img.mapToData(self.mapToItem(img, QtCore.QPointF(0, 1))) - origin

        lvx = np.sqrt(vx.x() ** 2 + vx.y() ** 2)
        lvy = np.sqrt(vy.x() ** 2 + vy.y() ** 2)
        # pxLen = img.width() / float(data.shape[axes[0]])
        ##img.width is number of pixels, not width of item.
        ##need pxWidth and pxHeight instead of pxLen ?
        # sx =  pxLen / lvx
        # sy =  pxLen / lvy
        sx = 1.0 / lvx
        sy = 1.0 / lvy

        vectors = ((vx.x() * sx, vx.y() * sx), (vy.x() * sy, vy.y() * sy))
        if fromBoundingRect is True:
            shape = self.boundingRect().width(), self.boundingRect().height()
            origin = img.mapToData(self.mapToItem(img, self.boundingRect().topLeft()))
            origin = (origin.x(), origin.y())
        else:
            shape = self.state['size']
            origin = (origin.x(), origin.y())

        shape = [abs(shape[0] / sx), abs(shape[1] / sy)]

        if img.axisOrder == 'row-major':
            # transpose output
            vectors = vectors[::-1]
            shape = shape[::-1]

        return shape, vectors, origin

    def renderShapeMask(self, width, height):
        """Return an array of 0.0-1.0 into which the shape of the item has been drawn.

        This can be used to mask array selections.
        """
        if width == 0 or height == 0:
            return np.empty((width, height), dtype=float)

        # QImage(width, height, format)
        im = QtGui.QImage(width, height, QtGui.QImage.Format_ARGB32)
        im.fill(0x0)
        p = QtGui.QPainter(im)
        p.setPen(fn.mkPen(None))
        p.setBrush(fn.mkBrush('w'))
        shape = self.shape()
        bounds = shape.boundingRect()
        p.scale(im.width() / bounds.width(), im.height() / bounds.height())
        p.translate(-bounds.topLeft())
        p.drawPath(shape)
        p.end()
        mask = fn.imageToArray(im, transpose=True)[:, :, 0].astype(float) / 255.
        return mask


class Handle(UIGraphicsItem):
    """
    Handle represents a single user-interactable point attached to an ROI. They
    are usually created by a call to one of the ROI.add___Handle() methods.

    Handles are represented as a square, diamond, or circle, and are drawn with
    fixed pixel size regardless of the scaling of the view they are displayed in.

    Handles may be dragged to change the position, size, orientation, or other
    properties of the ROI they are attached to.


    """
    types = {  ## defines number of sides, start angle for each handle type
        't': (4, np.pi / 4),
        'f': (4, np.pi / 4),
        's': (4, 0),
        'r': (12, 0),
        'sr': (12, 0),
        'rf': (12, 0),
    }

    sigClicked = QtCore.Signal(object, object)  # self, event
    sigRemoveRequested = QtCore.Signal(object)  # self

    def __init__(self, radius, typ=None, pen=(200, 200, 220), parent=None, deletable=False):
        # print "   create item with parent", parent
        # self.bounds = QtCore.QRectF(-1e-10, -1e-10, 2e-10, 2e-10)
        # self.setFlags(self.ItemIgnoresTransformations | self.ItemSendsScenePositionChanges)
        self.rois = []
        self.radius = radius
        self.typ = typ
        self.pen = fn.mkPen(pen)
        self.currentPen = self.pen
        self.pen.setWidth(0)
        self.pen.setCosmetic(True)
        self.isMoving = False
        self.sides, self.startAng = self.types[typ]
        self.buildPath()
        self._shape = None
        self.menu = self.buildMenu()

        UIGraphicsItem.__init__(self, parent=parent)
        self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
        self.deletable = deletable
        if deletable:
            self.setAcceptedMouseButtons(QtCore.Qt.RightButton)
            # self.updateShape()
        self.setZValue(11)

    def connectROI(self, roi):
        ### roi is the "parent" roi, i is the index of the handle in roi.handles
        self.rois.append(roi)

    def disconnectROI(self, roi):
        self.rois.remove(roi)
        # for i, r in enumerate(self.roi):
        # if r[0] == roi:
        # self.roi.pop(i)

    # def close(self):
    # for r in self.roi:
    # r.removeHandle(self)

    def setDeletable(self, b):
        self.deletable = b
        if b:
            self.setAcceptedMouseButtons(self.acceptedMouseButtons() | QtCore.Qt.RightButton)
        else:
            self.setAcceptedMouseButtons(self.acceptedMouseButtons() & ~QtCore.Qt.RightButton)

    def removeClicked(self):
        self.sigRemoveRequested.emit(self)

    def hoverEvent(self, ev):
        hover = False
        if not ev.isExit():
            if ev.acceptDrags(QtCore.Qt.LeftButton):
                hover = True
            for btn in [QtCore.Qt.LeftButton, QtCore.Qt.RightButton, QtCore.Qt.MidButton]:
                if int(self.acceptedMouseButtons() & btn) > 0 and ev.acceptClicks(btn):
                    hover = True

        if hover:
            self.currentPen = fn.mkPen(255, 255, 0)
        else:
            self.currentPen = self.pen
        self.update()
        # if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton):
        # self.currentPen = fn.mkPen(255, 255,0)
        # else:
        # self.currentPen = self.pen
        # self.update()

    def mouseClickEvent(self, ev):
        ## right-click cancels drag
        if ev.button() == QtCore.Qt.RightButton and self.isMoving:
            self.isMoving = False  ## prevents any further motion
            self.movePoint(self.startPos, finish=True)
            # for r in self.roi:
            # r[0].cancelMove()
            ev.accept()
        elif int(ev.button() & self.acceptedMouseButtons()) > 0:
            ev.accept()
            if ev.button() == QtCore.Qt.RightButton and self.deletable:
                self.raiseContextMenu(ev)
            self.sigClicked.emit(self, ev)
        else:
            ev.ignore()

            # elif self.deletable:
            # ev.accept()
            # self.raiseContextMenu(ev)
            # else:
            # ev.ignore()

    def buildMenu(self):
        menu = QtGui.QMenu()
        menu.setTitle("Handle")
        self.removeAction = menu.addAction("Remove handle", self.removeClicked)
        return menu

    def getMenu(self):
        return self.menu

    def raiseContextMenu(self, ev):
        menu = self.scene().addParentContextMenus(self, self.getMenu(), ev)

        ## Make sure it is still ok to remove this handle
        removeAllowed = all([r.checkRemoveHandle(self) for r in self.rois])
        self.removeAction.setEnabled(removeAllowed)
        pos = ev.screenPos()
        menu.popup(QtCore.QPoint(pos.x(), pos.y()))

    def mouseDragEvent(self, ev):
        if ev.button() != QtCore.Qt.LeftButton:
            return
        ev.accept()

        ## Inform ROIs that a drag is happening
        ##  note: the ROI is informed that the handle has moved using ROI.movePoint
        ##  this is for other (more nefarious) purposes.
        # for r in self.roi:
        # r[0].pointDragEvent(r[1], ev)

        if ev.isFinish():
            if self.isMoving:
                for r in self.rois:
                    r.stateChangeFinished()
            self.isMoving = False
        elif ev.isStart():
            for r in self.rois:
                r.handleMoveStarted()
            self.isMoving = True
            self.startPos = self.scenePos()
            self.cursorOffset = self.scenePos() - ev.buttonDownScenePos()

        if self.isMoving:  ## note: isMoving may become False in mid-drag due to right-click.
            pos = ev.scenePos() + self.cursorOffset
            self.movePoint(pos, ev.modifiers(), finish=False)

    def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True):
        for r in self.rois:
            if not r.checkPointMove(self, pos, modifiers):
                return
        # print "point moved; inform %d ROIs" % len(self.roi)
        # A handle can be used by multiple ROIs; tell each to update its handle position
        for r in self.rois:
            r.movePoint(self, pos, modifiers, finish=finish, coords='scene')

    def buildPath(self):
        size = self.radius
        self.path = QtGui.QPainterPath()
        ang = self.startAng
        dt = 2 * np.pi / self.sides
        for i in range(0, self.sides + 1):
            x = size * cos(ang)
            y = size * sin(ang)
            ang += dt
            if i == 0:
                self.path.moveTo(x, y)
            else:
                self.path.lineTo(x, y)

    def paint(self, p, opt, widget):
        ### determine rotation of transform
        # m = self.sceneTransform()
        ##mi = m.inverted()[0]
        # v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0))
        # va = np.arctan2(v.y(), v.x())

        ### Determine length of unit vector in painter's coords
        ##size = mi.map(Point(self.radius, self.radius)) - mi.map(Point(0, 0))
        ##size = (size.x()*size.x() + size.y() * size.y()) ** 0.5
        # size = self.radius

        # bounds = QtCore.QRectF(-size, -size, size*2, size*2)
        # if bounds != self.bounds:
        # self.bounds = bounds
        # self.prepareGeometryChange()
        p.setRenderHints(p.Antialiasing, True)
        p.setPen(self.currentPen)

        # p.rotate(va * 180. / 3.1415926)
        # p.drawPath(self.path)
        p.drawPath(self.shape())
        # ang = self.startAng + va
        # dt = 2*np.pi / self.sides
        # for i in range(0, self.sides):
        # x1 = size * cos(ang)
        # y1 = size * sin(ang)
        # x2 = size * cos(ang+dt)
        # y2 = size * sin(ang+dt)
        # ang += dt
        # p.drawLine(Point(x1, y1), Point(x2, y2))

    def shape(self):
        if self._shape is None:
            s = self.generateShape()
            if s is None:
                return self.path
            self._shape = s
            self.prepareGeometryChange()  ## beware--this can cause the view to adjust, which would immediately invalidate the shape.
        return self._shape

    def boundingRect(self):
        # print 'roi:', self.roi
        s1 = self.shape()
        # print "   s1:", s1
        # s2 = self.shape()
        # print "   s2:", s2

        return self.shape().boundingRect()

    def generateShape(self):
        ## determine rotation of transform
        # m = self.sceneTransform()  ## Qt bug: do not access sceneTransform() until we know this object has a scene.
        # mi = m.inverted()[0]
        dt = self.deviceTransform()

        if dt is None:
            self._shape = self.path
            return None

        v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0))
        va = np.arctan2(v.y(), v.x())

        dti = fn.invertQTransform(dt)
        devPos = dt.map(QtCore.QPointF(0, 0))
        tr = QtGui.QTransform()
        tr.translate(devPos.x(), devPos.y())
        tr.rotate(va * 180. / 3.1415926)

        return dti.map(tr.map(self.path))

    def viewTransformChanged(self):
        GraphicsObject.viewTransformChanged(self)
        self._shape = None  ## invalidate shape, recompute later if requested.
        self.update()

    # def itemChange(self, change, value):
    # if change == self.ItemScenePositionHasChanged:
    # self.updateShape()