Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # -*- coding: utf-8 -*-
- # !/usr/bin/env python3
- from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE
- import pyqtgraph.graphicsItems.ROI as pgROI
- from pyqtgraph.Point import Point
- #----------------ROI-------------------#
- class Handle(pgROI.Handle):
- """ Sub-class of pyqtgraph ROI handle. Main purpose is to deactivate the hoverEvent,
- mouseClickEvent and mouseDragEvent when the associated ROI is not active. This is
- required to allow ROIs to be drawn over the top of each other """
- def __init__(self, radius, typ=None, pen=(200, 200, 220), parent=None, deletable=False):
- pgROI.Handle.__init__(self, radius, typ, pen, parent, deletable)
- self.setSelectable(True)
- def setSelectable(self, isActive):
- self.isActive = isActive
- def raiseContextMenu(self, ev):
- menu = self.getMenu()
- ## 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 hoverEvent(self, ev):
- # Ignore events if handle is not active
- if not self.isActive: return
- 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()
- def mouseClickEvent(self, ev):
- # Ignore events if handle is not active
- if not self.isActive:
- ev.ignore()
- return
- if ev.button() == QtCore.Qt.RightButton and self.isMoving:
- self.isMoving = False
- self.movePoint(self.startPos, finish=True)
- 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()
- def mouseDragEvent(self, ev):
- # Ignore events if handle is not active
- if not self.isActive: return
- if ev.button() != QtCore.Qt.LeftButton:
- return
- ev.accept()
- 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:
- pos = ev.scenePos() + self.cursorOffset
- self.movePoint(pos, ev.modifiers(), finish=False)
- class ROI(pgROI.ROI):
- """ Sub-class of pyqtgraph ROI class. Main purpose is to customise the ROI handles """
- def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, maxBounds=None, snapSize=1.0,
- scaleSnap=False,
- translateSnap=False, rotateSnap=False, parent=None, pen=None, movable=True, removable=False):
- pgROI.ROI.__init__(self, pos, size, angle, invertible, maxBounds, snapSize, scaleSnap, translateSnap,
- rotateSnap, parent, pen, movable, removable)
- 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
- 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 paint(self, p, opt, widget):
- p.save()
- r = self.boundingRect()
- # 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()
- class PolylineSegment(pgROI.LineSegmentROI):
- """ Sub-class of pyqtgraph LineSegmentROI class. Main purpose is to deactivate functions if not active """
- def __init__(self, positions=(None, None), pos=None, handles=(None, None), **args):
- pgROI.LineSegmentROI.__init__(self, positions, pos, handles, **args)
- self.penHover = fn.mkPen(255, 255, 0)
- self.translatable = False
- self.setSelectable(True)
- self.setAcceptsHandles(True)
- def setSelectable(self, isActive):
- self.isActive = isActive
- if isActive:
- self.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
- else:
- self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
- def setAcceptsHandles(self, acceptsHandles):
- self.acceptsHandles = acceptsHandles
- if acceptsHandles:
- self.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
- else:
- self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
- def hoverEvent(self, ev):
- if not self.isActive: return
- if (self.translatable or self.acceptsHandles) and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton):
- self.setMouseHover(True)
- self.sigHoverEvent.emit(self)
- 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
- if hover:
- self.currentPen = self.penHover
- else:
- self.currentPen = self.pen
- self.update()
- def paint(self, p, *args):
- # p.setRenderHint(QtGui.QPainter.Antialiasing)
- p.setPen(self.currentPen)
- h1 = self.handles[0]['item'].pos()
- h2 = self.handles[1]['item'].pos()
- p.drawLine(h1, h2)
- class selectableROI(object):
- """ A ROI class that can be used with a multiRoiViewbox """
- sigRemoveRequested = QtCore.Signal(object)
- sigCopyRequested = QtCore.Signal(object)
- sigSaveRequested = QtCore.Signal(object)
- def __init__(self):
- self.penActive = fn.mkPen(0, 255, 0)
- self.penInactive = fn.mkPen(255, 0, 0)
- self.penHover = fn.mkPen(255, 255, 0)
- self.penActive.setWidth(1)
- self.penInactive.setWidth(1)
- self.penHover.setWidth(1)
- self.setName(str(uuid.uuid4()))
- self.isSelected = False
- self.menu = None
- self.setActive(True)
- def set_color(self, r, g, b):
- self.penActive.setColor(QtGui.QColor(r, g, b))
- def setActive(self, isActive):
- self.isActive = isActive
- if isActive:
- self.setAcceptedMouseButtons(QtCore.Qt.LeftButton or QtCore.Qt.RightButton)
- else:
- self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
- def __lt__(self, other):
- selfid = int(self.name.split('-')[-1])
- otherid = int(other.name.split('-')[-1])
- return selfid < otherid
- def setName(self, name):
- if name is not None:
- self.name = name
- else:
- raise ValueError('No ROI name')
- def removeClicked(self):
- self.sigRemoveRequested.emit(self)
- def copyClicked(self):
- self.sigCopyRequested.emit(self)
- def saveClicked(self):
- self.sigSaveRequested.emit(self)
- 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.RightButton)
- else:
- self.setMouseHover(False)
- def mouseDragEvent(self, ev):
- if ev.isStart():
- # Drag using left button only if selected
- if ev.button() == QtCore.Qt.LeftButton:
- 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()
- elif 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 contextMenuEnabled(self):
- return (self.removable and self.isActive)
- def raiseContextMenu(self, ev):
- if not self.contextMenuEnabled():
- return
- menu = self.getMenu()
- pos = ev.screenPos()
- menu.popup(QtCore.QPoint(pos.x(), pos.y()))
- def getMenu(self):
- # Setup menu
- if self.menu is None:
- self.menu = QMenuCustom()
- self.menuTitle = QtGui.QAction(self.name, self.menu)
- self.copyAct = QtGui.QAction("Copy", self.menu)
- self.saveAct = QtGui.QAction("Save", self.menu)
- self.remAct = QtGui.QAction("Remove", self.menu)
- self.menu.actions = [self.menuTitle, self.copyAct, self.saveAct, self.remAct]
- # Connect signals to actions
- self.copyAct.triggered.connect(self.copyClicked)
- self.remAct.triggered.connect(self.removeClicked)
- self.saveAct.triggered.connect(self.saveClicked)
- # Add actions to menu
- self.menu.addAction(self.menuTitle)
- self.menu.addSeparator()
- for action in self.menu.actions[1:]:
- self.menu.addAction(action)
- # Set default properties
- self.menuTitle.setDisabled(True)
- self.menu.setStyleSheet("QMenu::item {color: black; font-weight:normal;}")
- font = QtGui.QFont()
- font.setBold(True)
- self.menuTitle.setFont(font)
- # Enable menus only for selected roi
- if self.isSelected:
- self.copyAct.setVisible(True)
- self.saveAct.setVisible(True)
- self.remAct.setVisible(True)
- else:
- self.copyAct.setVisible(False)
- self.saveAct.setVisible(False)
- self.remAct.setVisible(False)
- return self.menu
- class PolyLineROIcustom(selectableROI, ROI):
- # class PolyLineROIcustom(selectableROI):
- sigRemoveRequested = QtCore.Signal(object)
- sigCopyRequested = QtCore.Signal(object)
- sigSaveRequested = QtCore.Signal(object)
- def __init__(self, pos=[0, 0], handlePositions=None, **args):
- ROI.__init__(self, pos, size=[1, 1], **args)
- selectableROI.__init__(self)
- self.segments = []
- self.translatable = False
- self.closed = True
- self.pen = self.penActive
- self.handlePen = self.penActive
- self.createROI(handlePositions)
- self.setActive(False)
- def createROI(self, handlePositions):
- # Create ROI from handle positions. Used mainly for copy and save
- if handlePositions is None: return
- for hp in handlePositions:
- self.addFreeHandle(hp)
- for i in range(-1, len(self.handles) - 1):
- self.addSegment(self.handles[i]['item'], self.handles[i + 1]['item'])
- def addSegment(self, h1, h2, index=None):
- seg = PolylineSegment(handles=(h1, h2), pen=self.pen, parent=self, movable=False)
- if index is None:
- self.segments.append(seg)
- else:
- self.segments.insert(index, seg)
- seg.sigClicked.connect(self.segmentClicked)
- seg.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
- seg.setZValue(self.zValue() + 1)
- for h in seg.handles:
- h['item'].setDeletable(True)
- h['item'].setAcceptedMouseButtons(h[
- 'item'].acceptedMouseButtons() | QtCore.Qt.LeftButton) ## have these handles take left clicks too, so that handles cannot be added on top of other handles
- def setMouseHover(self, hover):
- # self.counter += 1
- # print 'setMouseHover ', self.counter, " ", hover
- ROI.setMouseHover(self, hover)
- for s in self.segments:
- s.setMouseHover(hover)
- # print s
- self.update()
- def addHandle(self, info, index=None):
- h = ROI.addHandle(self, info, index=index)
- h.sigRemoveRequested.connect(self.removeHandle)
- return h
- def segmentClicked(self, segment, ev=None, pos=None): ## pos should be in this item's coordinate system
- if ev != None:
- pos = segment.mapToParent(ev.pos())
- elif pos != None:
- pos = pos
- else:
- raise Exception("Either an event or a position must be given.")
- h2 = segment.handles[1]['item']
- i = self.segments.index(segment)
- h3 = self.addFreeHandle(pos, index=self.indexOfHandle(h2))
- self.addSegment(h3, h2, index=i + 1)
- segment.replaceHandle(h2, h3)
- def removeHandle(self, handle, updateSegments=True):
- ROI.removeHandle(self, handle)
- handle.sigRemoveRequested.disconnect(self.removeHandle)
- if not updateSegments:
- return
- segments = handle.rois[:]
- if len(segments) == 1:
- self.removeSegment(segments[0])
- else:
- handles = [h['item'] for h in segments[1].handles]
- handles.remove(handle)
- segments[0].replaceHandle(handle, handles[0])
- self.removeSegment(segments[1])
- def removeSegment(self, seg):
- for handle in seg.handles[:]:
- seg.removeHandle(handle['item'])
- self.segments.remove(seg)
- seg.sigClicked.disconnect(self.segmentClicked)
- self.scene().removeItem(seg)
- def checkRemoveHandle(self, h):
- if self.closed:
- return len(self.handles) > 3
- else:
- return len(self.handles) > 2
- def paint(self, p, *args):
- pass
- def boundingRect(self):
- return self.shape().boundingRect()
- def shape(self):
- p = QtGui.QPainterPath()
- p.moveTo(self.handles[0]['item'].pos())
- for i in range(len(self.handles)):
- p.lineTo(self.handles[i]['item'].pos())
- p.lineTo(self.handles[0]['item'].pos())
- return p
- def setSelected(self, s):
- QtGui.QGraphicsItem.setSelected(self, s)
- if s:
- self.isSelected = True
- self.translatable = True
- for seg in self.segments:
- seg.setPen(self.penActive)
- seg.setSelectable(True)
- seg.setAcceptsHandles(True)
- seg.update()
- for h in self.handles:
- h['item'].currentPen = self.penActive
- h['item'].pen = h['item'].currentPen
- h['item'].show()
- h['item'].update()
- else:
- self.isSelected = False
- self.translatable = False
- for seg in self.segments:
- seg.setPen(self.penInactive)
- seg.setSelectable(False)
- seg.setAcceptsHandles(False)
- seg.update()
- for h in self.handles:
- h['item'].currentPen = self.penInactive
- h['item'].pen = h['item'].currentPen
- h['item'].hide()
- h['item'].update()
- def getArrayRegion(self, data, img, axes=(0, 1), returnMappedCoords=False, **kwds):
- sl = self.getArraySlice(data, img, axes=(0, 1))
- if sl is None:
- return None
- sliced = data[sl[0]]
- im = QtGui.QImage(sliced.shape[axes[0]], sliced.shape[axes[1]], QtGui.QImage.Format_ARGB32)
- im.fill(0x0)
- p = QtGui.QPainter(im)
- p.setPen(fn.mkPen(None))
- p.setBrush(fn.mkBrush('w'))
- p.setTransform(self.itemTransform(img)[0])
- bounds = self.mapRectToItem(img, self.boundingRect())
- p.translate(-bounds.left(), -bounds.top())
- p.drawPath(self.shape())
- p.end()
- mask = imageToArray(im)[:, :, 0].astype(float) / 255.
- # Old code that doesn't seem to do what it should
- # shape = [1] * data.ndim
- # shape[axes[0]] = sliced.shape[axes[0]]
- # shape[axes[1]] = sliced.shape[axes[1]]
- # return sliced * mask.reshape(shape)
- # Return image with mask applied
- reshaped_mask = mask.T[:, :, np.newaxis]
- return sliced * reshaped_mask
- def getROIMask(self, data, img, axes=(0, 1), returnMappedCoords=False, **kwds):
- # sl = self.getArraySlice(data, img, axes=(0, 1))
- # if sl is None:
- # return None
- # sliced = data[sl[0]]
- sliced = data
- im = QtGui.QImage(sliced.shape[axes[0]], sliced.shape[axes[1]], QtGui.QImage.Format_ARGB32)
- im.fill(0x0)
- p = QtGui.QPainter(im)
- p.setPen(fn.mkPen(None))
- p.setBrush(fn.mkBrush('w'))
- p.setTransform(self.itemTransform(img)[0])
- # No moving
- # bounds = self.mapRectToItem(img, self.boundingRect())
- # p.translate(-bounds.left(), -bounds.top())
- p.drawPath(self.shape())
- p.end()
- mask = imageToArray(im)[:, :, 0].astype(float) / 255.
- # Old code that doesn't seem to do what it should
- # shape = [1] * data.ndim
- # shape[axes[0]] = sliced.shape[axes[0]]
- # shape[axes[1]] = sliced.shape[axes[1]]
- # return sliced * mask.reshape(shape)
- # Return image with mask applied
- # reshaped_mask = mask.T[:, :, np.newaxis]
- return np.flipud(mask)
- # def getROISlice(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds):
- # sl = self.getArraySlice(data, img, axes=(0, 1))
- # if sl is None:
- # return None
- # sliced = data[sl[0]]
- # im = QtGui.QImage(sliced.shape[axes[0]], sliced.shape[axes[1]], QtGui.QImage.Format_ARGB32)
- # im.fill(0x0)
- # p = QtGui.QPainter(im)
- # p.setPen(fn.mkPen(None))
- # p.setBrush(fn.mkBrush('w'))
- # p.setTransform(self.itemTransform(img)[0])
- # bounds = self.mapRectToItem(img, self.boundingRect())
- # p.translate(-bounds.left(), -bounds.top())
- # p.drawPath(self.shape())
- # p.end()
- # mask = imageToArray(im)[:,:,0].astype(float) / 255.
- # # Old code that doesn't seem to do what it should
- # #shape = [1] * data.ndim
- # #shape[axes[0]] = sliced.shape[axes[0]]
- # #shape[axes[1]] = sliced.shape[axes[1]]
- # #return sliced * mask.reshape(shape)
- #
- # return sliced
- def imageToArray(img):
- """
- Replaces imageToArray in pyqtgraph functions.py. Due to errors in Python 2.6
- Error appears to be due to numpy and converting ptr to arr. Early versions create
- an array of empty strings and later versions create an array of the wrong size.
- The following method works, although arr in read-only i.e. changing the values in
- the arr will not change the pixel values in the original QImage.
- """
- fmt = img.format()
- ptr = img.bits()
- if USE_PYSIDE:
- arr = np.frombuffer(ptr, dtype=np.ubyte)
- else:
- ptr.setsize(img.byteCount())
- # buf = buffer(ptr, 0, img.byteCount())
- buf = memoryview(ptr)
- arr = np.frombuffer(buf, dtype=np.ubyte)
- if fmt == img.Format_RGB32:
- arr = arr.reshape(img.height(), img.width(), 3)
- elif fmt == img.Format_ARGB32 or fmt == img.Format_ARGB32_Premultiplied:
- arr = arr.reshape(img.height(), img.width(), 4)
- return arr
- class RectROIcustom(selectableROI, ROI):
- sigRemoveRequested = QtCore.Signal(object)
- sigCopyRequested = QtCore.Signal(object)
- sigSaveRequested = QtCore.Signal(object)
- def __init__(self, pos, size, angle, **args):
- ROI.__init__(self, pos, size, angle, **args)
- selectableROI.__init__(self)
- self.addScaleHandle([0.0, 0.5], [1.0, 0.5])
- self.addScaleHandle([1.0, 0.5], [0.0, 0.5])
- self.addScaleHandle([0.5, 0.0], [0.5, 1.0])
- self.addScaleHandle([0.5, 1.0], [0.5, 0.0])
- self.addRotateHandle([0.0, 0.0], [1.0, 1.0])
- self.addRotateHandle([0.0, 1.0], [1.0, 0.0])
- self.addRotateHandle([1.0, 0.0], [0.0, 1.0])
- self.addRotateHandle([1.0, 1.0], [0.0, 0.0])
- def setSelected(self, s):
- QtGui.QGraphicsItem.setSelected(self, s)
- if s:
- self.isSelected = True
- self.translatable = True
- self.setPen(self.penActive)
- for h in self.handles:
- h['item'].currentPen = self.penActive
- h['item'].pen = h['item'].currentPen
- h['item'].show()
- h['item'].update()
- else:
- self.isSelected = False
- self.translatable = False
- self.setPen(self.penInactive)
- for h in self.handles:
- h['item'].currentPen = self.penInactive
- h['item'].pen = h['item'].currentPen
- h['item'].hide()
- h['item'].update()
- from PyQt4.QtGui import *
- import pyqtgraph as pg
- # --------------------- Mygraphicsview ----------------------- #
- class MyGraphicsView(pg.GraphicsView):
- def __init__(self, project, parent=None):
- super(MyGraphicsView, self).__init__(parent)
- self.project = project
- self.shape = 0, 0
- self.setup_ui()
- self.update()
- def setup_ui(self):
- self.setMinimumSize(200, 200)
- l = QGraphicsGridLayout()
- self.centralWidget.setLayout(l)
- l.setHorizontalSpacing(0)
- l.setVerticalSpacing(0)
- self.vb = MultiRoiViewBox(lockAspect=True, enableMenu=True)
- self.vb.enableAutoRange()
- l.addItem(self.vb, 0, 1)
- self.xScale = pg.AxisItem(orientation='bottom', linkView=self.vb)
- self.xScale.setLabel(text="<span style='color: #ff0000; font-weight: bold'>X</span>"
- "<i>Width</i>", units="mm")
- l.addItem(self.xScale, 1, 1)
- self.yScale = pg.AxisItem(orientation='left', linkView=self.vb)
- self.yScale.setLabel(text="<span style='color: #ff0000; font-weight: bold'>Y</span>"
- "<i>Height</i>", units='mm')
- l.addItem(self.yScale, 0, 0)
- self.centralWidget.setLayout(l)
- def show(self, frame):
- self.shape = frame.shape
- self.vb.showImage(frame)
- self.update()
- def _update_rect(self):
- w, h = self.shape[0], self.shape[1]
- if not (not self.project):
- ox, oy = self.project['origin']
- mmpixel = self.project['mmpixel']
- x = -ox * mmpixel
- y = -oy * mmpixel
- w = w * mmpixel
- h = h * mmpixel
- self.vb.update_rect(x, y, w, h)
- def update(self):
- self._update_rect()
- import pyqtgraph as pg
- from pyqtgraph.Qt import QtCore, QtGui
- import numpy as np
- import matplotlib.pyplot as plt
- import pickle
- import pyqtgraph.functions as fn
- import types
- # ---------------------------- viewboxcustom ------------------------- #
- class UnsupportedRoiTypeError(Exception):
- pass
- class ImageAnalysisViewBox(pg.ViewBox):
- """
- Custom ViewBox used to over-ride the context menu. I don't want the full context menu,
- just a view all and an export. Export does not call a dialog, just prompts user for filename.
- """
- def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, enableMenu=True,
- name=None):
- pg.ViewBox.__init__(self, parent, border, lockAspect, enableMouse, invertY, enableMenu, name)
- self.menu = None # Override pyqtgraph ViewBoxMenu
- self.menu = self.getMenu(None)
- def raiseContextMenu(self, ev):
- if not self.menuEnabled(): return
- menu = self.getMenu(ev)
- pos = ev.screenPos()
- menu.popup(QtCore.QPoint(pos.x(), pos.y()))
- def export(self):
- self.exp = ImageExporterCustom(self)
- self.exp.export()
- def getMenu(self, event):
- if self.menu is None:
- self.menu = QMenuCustom()
- self.viewAll = QtGui.QAction("View All", self.menu)
- self.exportImage = QtGui.QAction("Export image", self.menu)
- self.viewAll.triggered[()].connect(self.autoRange)
- self.exportImage.triggered[()].connect(self.export)
- self.menu.addAction(self.viewAll)
- self.menu.addAction(self.exportImage)
- return self.menu
- class ViewMode():
- def __init__(self, id, cmap):
- self.id = id
- self.cmap = cmap
- self.getLookupTable()
- def getLookupTable(self):
- lut = [[int(255 * val) for val in self.cmap(i)[:3]] for i in xrange(256)]
- lut = np.array(lut, dtype=np.ubyte)
- self.lut = lut
- class MultiRoiViewBox(pg.ViewBox):
- sigROIchanged = QtCore.Signal(object)
- clicked = QtCore.pyqtSignal(float, float)
- hovering = QtCore.pyqtSignal(float, float)
- roi_placed = QtCore.pyqtSignal(PolyLineROIcustom)
- def __init__(self, parent=None, border=None, lockAspect=False,
- enableMouse=True, invertY=False, enableMenu=True, name=None):
- pg.ViewBox.__init__(self, parent, border, lockAspect,
- enableMouse, invertY, enableMenu, name)
- self.crosshair_visible = True
- self.rois = []
- self.currentROIindex = None
- self.img = None
- self.menu = None # Override pyqtgraph ViewBoxMenu
- self.menu = self.getMenu(None)
- self.NORMAL = ViewMode(0, plt.cm.gray)
- self.DEXA = ViewMode(1, plt.cm.jet)
- self.viewMode = self.NORMAL
- self.drawROImode = False
- self.drawingROI = None
- self.vLine = pg.InfiniteLine(angle=90, movable=False)
- self.hLine = pg.InfiniteLine(angle=0, movable=False)
- self.mouseclickeventCount = 0
- l = QtGui.QGraphicsGridLayout()
- l.setHorizontalSpacing(0)
- l.setVerticalSpacing(0)
- xScale = pg.AxisItem(orientation='bottom', linkView=self)
- l.addItem(xScale, 1, 1)
- yScale = pg.AxisItem(orientation='left', linkView=self)
- l.addItem(yScale, 0, 0)
- xScale.setLabel(text="<span style='color: #ff0000; font-weight: bold'>X</span> <i>Axis</i>", units="s")
- yScale.setLabel('Y Axis', units='V')
- def getContextMenus(self, ev):
- return None
- def raiseContextMenu(self, ev):
- if not self.menuEnabled(): return
- menu = self.getMenu(ev)
- pos = ev.screenPos()
- menu.popup(QtCore.QPoint(pos.x(), pos.y()))
- def export(self):
- self.exp = ImageExporterCustom(self)
- self.exp.export()
- def mouseClickEvent(self, ev):
- if self.drawROImode:
- ev.accept()
- # print('mouseClickEvent->drawPolygonRoi')
- self.drawPolygonRoi(ev)
- elif ev.button() == QtCore.Qt.RightButton and self.menuEnabled():
- ev.accept()
- self.raiseContextMenu(ev)
- elif ev.button() == QtCore.Qt.LeftButton:
- ev.accept()
- pos = self.mapToItem(self.img, ev.pos())
- self.clicked.emit(pos.x(), pos.y())
- # if self.mouseclickeventCount <= 1:
- # proxy = pg.SignalProxy(self.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)
- # self.scene().sigMouseMoved.connect(self.mouseMoved)
- def mouseMoved(self, ev):
- # pos = ev ## using signal proxy turns original arguments into a tuple (or not...)
- if self.sceneBoundingRect().contains(ev) and not self.drawROImode:
- mousePoint = self.mapSceneToView(ev)
- index = int(mousePoint.x())
- # if index > 0 and index < len(data1):
- # label.setText("<span style='font-size: 12pt'>x=%0.1f, <span style='color: red'>y1=%0.1f</span>, <span style='color: green'>y2=%0.1f</span>" % (mousePoint.x(), data1[index], data2[index]))
- self.vLine.setPos(mousePoint.x())
- self.hLine.setPos(mousePoint.y())
- # pos = self.mapToItem(self.img, ev.pos())
- mousePoint = self.mapSceneToView(ev)
- # print(str(mousePoint.x())+","+str(mousePoint.y()))
- self.hovering.emit(mousePoint.x(), mousePoint.y())
- def addPolyRoiRequest(self):
- """Function to add a Polygon ROI"""
- # print('addPolyRoiRequest')
- self.drawROImode = True
- for roi in self.rois:
- roi.setActive(False)
- def endPolyRoiRequest(self):
- self.drawROImode = False # Deactivate drawing mode
- self.drawingROI = None # No roi being drawn, so set to None
- for r in self.rois:
- r.setActive(True)
- def addPolyLineROI(self, handlePositions, name=None):
- roi = PolyLineROIcustom(handlePositions=handlePositions, removable=True)
- roi.setName(name)
- self.addItem(roi) # Add roi to viewbox
- self.rois.append(roi) # Add to list of rois
- self.selectROI(roi)
- self.setCurrentROIindex(roi)
- roi.translatable = True
- # roi.setAcceptedMouseButtons(QtCore.Qt.LeftButton or QtCore.Qt.RightButton)
- roi.setActive(True)
- for seg in roi.segments:
- seg.setSelectable(True)
- for h in roi.handles:
- h['item'].setSelectable(True)
- # Setup signals
- roi.sigClicked.connect(self.selectROI)
- roi.sigRegionChanged.connect(self.roiChanged)
- roi.sigRemoveRequested.connect(self.removeROI)
- roi.sigCopyRequested.connect(self.copyROI)
- roi.sigSaveRequested.connect(self.saveROI)
- return roi
- def drawPolygonRoi(self, ev):
- "Function to draw a polygon ROI"
- roi = self.drawingROI
- pos = self.mapSceneToView(ev.scenePos())
- if ev.button() == QtCore.Qt.LeftButton:
- if roi is None:
- roi = PolyLineROIcustom(removable=False)
- # roi.setName('ROI-%i'% self.getROIid()) # Do this before self.selectROIs(roi)
- self.drawingROI = roi
- roi.addFreeHandle(pos)
- roi.addFreeHandle(pos)
- self.addItem(roi) # Add roi to viewbox
- self.rois.append(roi) # Add to list of rois
- self.selectROI(roi)
- self.sortROIs()
- self.setCurrentROIindex(roi)
- roi.translatable = False
- h = roi.handles[-1]['item']
- h.scene().sigMouseMoved.connect(h.movePoint)
- else:
- h = roi.handles[-1]['item']
- h.scene().sigMouseMoved.disconnect()
- roi.addFreeHandle(pos)
- h = roi.handles[-1]['item']
- h.scene().sigMouseMoved.connect(h.movePoint)
- # Add a segment between the handles
- roi.addSegment(roi.handles[-2]['item'], roi.handles[-1]['item'])
- # Set segment and handles to non-selectable
- seg = roi.segments[-1]
- seg.setSelectable(False)
- for h in seg.handles:
- h['item'].setSelectable(False)
- elif (ev.button() == QtCore.Qt.MiddleButton) or \
- (ev.button() == QtCore.Qt.RightButton and (roi == None or len(roi.segments) < 3)):
- if roi != None:
- # Remove handle and disconnect from scene
- h = roi.handles[-1]['item']
- h.scene().sigMouseMoved.disconnect()
- roi.removeHandle(h)
- # Removed roi from viewbox
- self.removeItem(roi)
- self.rois.pop(self.currentROIindex)
- self.setCurrentROIindex(None)
- # Exit ROI drawing mode
- self.endPolyRoiRequest()
- elif ev.button() == QtCore.Qt.RightButton:
- # Remove last handle
- h = roi.handles[-1]['item']
- h.scene().sigMouseMoved.disconnect()
- roi.removeHandle(h)
- # Add segment to close ROI
- roi.addSegment(roi.handles[-1]['item'], roi.handles[0]['item'])
- # Setup signals
- roi.sigClicked.connect(self.selectROI)
- roi.sigRegionChanged.connect(self.roiChanged)
- roi.sigRemoveRequested.connect(self.removeROI)
- roi.sigCopyRequested.connect(self.copyROI)
- roi.sigSaveRequested.connect(self.saveROI)
- # Re-activate mouse clicks for all roi, segments and handles
- roi.removable = True
- roi.translatable = True
- for seg in roi.segments:
- seg.setSelectable(True)
- for h in roi.handles:
- h['item'].setSelectable(True)
- # Exit ROI drawing mode
- self.endPolyRoiRequest()
- self.roi_placed.emit(roi)
- def autoDrawPolygonRoi(self, name, pos=QtCore.QPointF(0, 0), finished=False):
- "Function to draw a polygon ROI"
- roi = self.drawingROI
- if not finished:
- if roi is None:
- roi = PolyLineROIcustom(removable=False)
- roi.setName(name) # Do this before self.selectROIs(roi)
- self.drawingROI = roi
- roi.addFreeHandle(pos)
- roi.addFreeHandle(pos)
- self.addItem(roi) # Add roi to viewbox
- self.rois.append(roi) # Add to list of rois
- self.selectROI(roi)
- self.sortROIs()
- self.setCurrentROIindex(roi)
- roi.translatable = False
- h = roi.handles[-1]['item']
- h.scene().sigMouseMoved.connect(h.movePoint)
- else:
- h = roi.handles[-1]['item']
- h.scene().sigMouseMoved.disconnect()
- roi.addFreeHandle(pos)
- h = roi.handles[-1]['item']
- h.scene().sigMouseMoved.connect(h.movePoint)
- # Add a segment between the handles
- roi.addSegment(roi.handles[-2]['item'], roi.handles[-1]['item'])
- # Set segment and handles to non-selectable
- seg = roi.segments[-1]
- seg.setSelectable(False)
- for h in seg.handles:
- h['item'].setSelectable(False)
- elif finished:
- # Remove last handle
- h = roi.handles[-1]['item']
- h.scene().sigMouseMoved.disconnect()
- roi.removeHandle(h)
- # Add segment to close ROI
- roi.addSegment(roi.handles[-1]['item'], roi.handles[0]['item'])
- # Setup signals
- roi.sigClicked.connect(self.selectROI)
- roi.sigRegionChanged.connect(self.roiChanged)
- roi.sigRemoveRequested.connect(self.removeROI)
- roi.sigCopyRequested.connect(self.copyROI)
- roi.sigSaveRequested.connect(self.saveROI)
- # Re-activate mouse clicks for all roi, segments and handles
- roi.removable = True
- roi.translatable = True
- for seg in roi.segments:
- seg.setSelectable(True)
- for h in roi.handles:
- h['item'].setSelectable(True)
- # Exit ROI drawing mode
- self.endPolyRoiRequest()
- def getMenu(self, event):
- if self.menu is None:
- self.menu = QtGui.QMenu()
- # Submenu to add ROIs
- self.submenu = QtGui.QMenu("Add ROI", self.menu)
- # self.addROIRectAct = QActionCustom("Rectangular", self.submenu)
- self.addROIRectAct = QtGui.QAction("Rectangular", self.submenu)
- # self.addROIPolyAct = QActionCustom("Polygon", self.submenu)
- self.addROIPolyAct = QtGui.QAction("Polygon", self.submenu)
- # self.addROIRectAct.clickEvent.connect(self.addRoiRequest)
- self.addROIRectAct.triggered.connect(self.addROI)
- # self.addROIPolyAct.clickEvent.connect(self.addPolyRoiRequest)
- self.addROIPolyAct.triggered.connect(self.addPolyRoiRequest)
- self.submenu.addAction(self.addROIRectAct)
- self.submenu.addAction(self.addROIPolyAct)
- # self.loadROIAct = QActionCustom("Load ROI", self.menu)
- self.loadROIAct = QtGui.QAction("Load ROI", self.menu)
- self.dexaMode = QtGui.QAction("DEXA mode", self.menu)
- self.viewAll = QtGui.QAction("View All", self.menu)
- self.exportImage = QtGui.QAction("Export image", self.menu)
- # self.loadROIAct.clickEvent.connect(self.loadROI)
- self.loadROIAct.triggered[()].connect(self.loadROI)
- self.dexaMode.toggled.connect(self.toggleViewMode)
- self.viewAll.triggered[()].connect(self.autoRange)
- self.exportImage.triggered[()].connect(self.export)
- self.menu.addAction(self.viewAll)
- self.menu.addAction(self.dexaMode)
- self.menu.addAction(self.exportImage)
- self.menu.addSeparator()
- self.menu.addMenu(self.submenu)
- self.menu.addAction(self.loadROIAct)
- self.dexaMode.setCheckable(True)
- # Update action event. This enables passing of the event to the fuction connected to the
- # action i.e. event will be passed to self.addRoiRequest when a Rectangular ROI is clicked
- # self.addROIRectAct.updateEvent(event)
- # self.addROIPolyAct.updateEvent(event)
- return self.menu
- def getMenu(self, event):
- if self.menu is None:
- self.menu = QtGui.QMenu()
- # Submenu to add ROIs
- self.submenu = QtGui.QMenu("Add ROI", self.menu)
- # self.addROIRectAct = QActionCustom("Rectangular", self.submenu)
- self.addROIRectAct = QtGui.QAction("Rectangular", self.submenu)
- # self.addROIPolyAct = QActionCustom("Polygon", self.submenu)
- self.addROIPolyAct = QtGui.QAction("Polygon", self.submenu)
- # self.addROIRectAct.clickEvent.connect(self.addRoiRequest)
- self.addROIRectAct.triggered.connect(self.addROI)
- # self.addROIPolyAct.clickEvent.connect(self.addPolyRoiRequest)
- self.addROIPolyAct.triggered.connect(self.addPolyRoiRequest)
- self.submenu.addAction(self.addROIRectAct)
- self.submenu.addAction(self.addROIPolyAct)
- # self.loadROIAct = QActionCustom("Load ROI", self.menu)
- self.loadROIAct = QtGui.QAction("Load ROI", self.menu)
- self.dexaMode = QtGui.QAction("DEXA mode", self.menu)
- self.viewAll = QtGui.QAction("View All", self.menu)
- self.exportImage = QtGui.QAction("Export image", self.menu)
- # self.loadROIAct.clickEvent.connect(self.loadROI)
- self.loadROIAct.triggered[()].connect(self.loadROI)
- self.dexaMode.toggled.connect(self.toggleViewMode)
- self.viewAll.triggered[()].connect(self.autoRange)
- self.exportImage.triggered[()].connect(self.export)
- self.menu.addAction(self.viewAll)
- self.menu.addAction(self.dexaMode)
- self.menu.addAction(self.exportImage)
- self.menu.addSeparator()
- self.menu.addMenu(self.submenu)
- self.menu.addAction(self.loadROIAct)
- self.dexaMode.setCheckable(True)
- # Update action event. This enables passing of the event to the fuction connected to the
- # action i.e. event will be passed to self.addRoiRequest when a Rectangular ROI is clicked
- # self.addROIRectAct.updateEvent(event)
- # self.addROIPolyAct.updateEvent(event)
- return self.menu
- def setCurrentROIindex(self, roi=None):
- """ Use this function to change currentROIindex value to ensure a signal is emitted"""
- if roi == None:
- self.currentROIindex = None
- else:
- self.currentROIindex = self.rois.index(roi)
- self.sigROIchanged.emit(roi)
- def roiChanged(self, roi):
- self.sigROIchanged.emit(roi)
- def getCurrentROIindex(self):
- return self.currentROIindex
- def selectROI(self, roi):
- """ Selection control of ROIs """
- # If no ROI is currently selected (currentROIindex is None), select roi
- if self.currentROIindex == None:
- roi.setSelected(True)
- self.setCurrentROIindex(roi)
- # If an ROI is already selected...
- else:
- roiSelected = self.rois[self.currentROIindex]
- roiSelected.setSelected(False)
- # If a different roi is already selected, then select roi
- if self.currentROIindex != self.rois.index(roi):
- self.setCurrentROIindex(roi)
- roi.setSelected(True)
- # If roi is already selected, then unselect
- else:
- self.setCurrentROIindex(None)
- def addRoiRequest(self, ev):
- """ Function to addROI at an event screen position """
- # Get position
- pos = self.mapSceneToView(ev.scenePos())
- xpos = pos.x()
- ypos = pos.y()
- # Shift down by size
- xr, yr = self.viewRange()
- xsize = 0.25 * (xr[1] - xr[0])
- ysize = 0.25 * (yr[1] - yr[0])
- xysize = min(xsize, ysize)
- if xysize == 0: xysize = 100
- ypos -= xysize
- # Create ROI
- xypos = (xpos, ypos)
- self.addROI(pos=xypos)
- def addROI(self, name, pos=None, size=None, angle=0.0):
- """ Add an ROI to the ViewBox """
- if name is None:
- raise ValueError('ROIs must have names. A nameless ROI was loaded')
- xr, yr = self.viewRange()
- if pos is None:
- posx = xr[0] + 0.05 * (xr[1] - xr[0])
- posy = yr[0] + 0.05 * (yr[1] - yr[0])
- pos = [posx, posy]
- if size is None:
- xsize = 0.25 * (xr[1] - xr[0])
- ysize = 0.25 * (yr[1] - yr[0])
- xysize = min(xsize, ysize)
- if xysize == 0: xysize = 100
- size = [xysize, xysize]
- roi = RectROIcustom(pos, size, angle, removable=True, pen=(255, 0, 0))
- roi.setName(name)
- # Setup signals
- # roi.setName('ROI-%i' % self.getROIid())
- roi.sigClicked.connect(self.selectROI)
- roi.sigRegionChanged.connect(self.roiChanged)
- roi.sigRemoveRequested.connect(self.removeROI)
- roi.sigCopyRequested.connect(self.copyROI)
- roi.sigSaveRequested.connect(self.saveROI)
- # Keep track of rois
- self.addItem(roi)
- self.rois.append(roi)
- self.selectROI(roi)
- self.sortROIs()
- self.setCurrentROIindex(roi)
- return roi
- def sortROIs(self):
- """ Sort self.rois by roi name and adjust self.currentROIindex as necessary """
- if len(self.rois) == 0: return
- if self.currentROIindex == None:
- self.rois.sort()
- else:
- roiCurrent = self.rois[self.currentROIindex]
- # self.rois.sort()
- self.currentROIindex = self.rois.index(roiCurrent)
- def getROIid(self):
- """ Get available and unique number for ROI name """
- if not self.rois:
- return 1
- parseable_rois = [roi for roi in self.rois if '-' in roi.name]
- nums = [(roi.name.split('-')[-1]) for roi in parseable_rois if roi.name != None]
- nid = 1
- if len(nums) > 0:
- while (True):
- if nid not in nums: break
- nid += 1
- return nid
- def copyROI(self, offset=0.0):
- """ Copy current ROI. Offset from original for visibility """
- if self.currentROIindex != None:
- osFract = 0.05
- roi = self.rois[self.currentROIindex]
- # For rectangular ROI, offset by a fraction of the rotated size
- if type(roi) == RectROIcustom:
- roiState = roi.getState()
- pos = roiState['pos']
- size = roiState['size']
- angle = roiState['angle']
- dx, dy = np.array(size) * osFract
- ang = np.radians(angle)
- cosa = np.cos(ang)
- sina = np.sin(ang)
- dxt = dx * cosa - dy * sina
- dyt = dx * sina + dy * cosa
- offset = QtCore.QPointF(dxt, dyt)
- self.addROI(pos + offset, size, angle)
- # For a polyline ROI, offset by a fraction of the bounding rectangle
- if type(roi) == PolyLineROIcustom:
- br = roi.shape().boundingRect()
- size = np.array([br.width(), br.height()])
- osx, osy = size * osFract
- offset = QtCore.QPointF(osx, osy)
- hps = [i[-1] for i in roi.getSceneHandlePositions(index=None)]
- hpsOffset = [self.mapSceneToView(hp) + offset for hp in hps]
- self.addPolyLineROI(hpsOffset)
- def saveROI(self, fileName=''):
- """ Save the highlighted ROI to file """
- if self.currentROIindex != None:
- roi = self.rois[self.currentROIindex]
- if not fileName:
- fileName = QtGui.QFileDialog.getSaveFileName(None, self.tr("Save ROI"), QtCore.QDir.currentPath(),
- self.tr("ROI (*.roi)"))
- # Fix for PyQt/PySide compatibility. PyQt returns a QString, whereas PySide returns a tuple (first entry is filename as string)
- if isinstance(fileName, types.TupleType): fileName = fileName[0]
- if hasattr(QtCore, 'QString') and isinstance(fileName, QtCore.QString): fileName = str(fileName)
- if not fileName == '':
- if type(roi) == RectROIcustom:
- roiState = roi.saveState()
- roiState['type'] = 'RectROIcustom'
- roiState['name'] = roi.name
- elif type(roi) == PolyLineROIcustom:
- roiState = {}
- hps = [self.mapSceneToView(i[-1]) for i in roi.getSceneHandlePositions(index=None)]
- hps = [[hp.x(), hp.y()] for hp in hps]
- roiState['type'] = 'PolyLineROIcustom'
- roiState['handlePositions'] = hps
- roiState['name'] = roi.name
- pickle.dump(roiState, open(fileName, "wb"))
- def addRoi(self, roipath, roiname, roimode='static'):
- roistate = pickle.load(open(roipath, 'rb'))
- if roistate['type'] == 'RectROIcustom':
- roi = self.addROI(roistate['pos'], roistate['size'], roistate['angle'])
- elif roistate['type'] == 'PolyLineROIcustom':
- roi = self.addPolyLineROI(roistate['handlePositions'], roistate['name'])
- else:
- raise UnsupportedRoiTypeError()
- roi.setName(roiname)
- self.selectROI(roi)
- def getRoi(self, roiname):
- rois = [roi for roi in self.rois if roi.name == roiname]
- assert (len(rois) == 1)
- return rois[0]
- def removeRoi(self, roiname):
- rois = [roi for roi in self.rois if roi.name == roiname]
- if not rois:
- return
- assert (len(rois) == 1)
- roi = rois[0]
- self.rois.remove(roi)
- self.removeItem(roi)
- def loadROI(self, fileNames=None):
- """ Load a previously saved ROI from file """
- if fileNames == None:
- fileNames = QtGui.QFileDialog.getOpenFileNames(None, self.tr("Load ROI"),
- QtCore.QDir.currentPath(),
- self.tr("ROI (*.roi)"))
- if hasattr(QtCore, 'QStringList') and \
- isinstance(fileNames, QtCore.QStringList): fileNames = [str(i) for i in fileNames]
- if len(fileNames) > 0:
- for fileName in fileNames:
- if fileName != '':
- roiState = pickle.load(open(fileName, "rb"))
- if roiState['type'] == 'RectROIcustom':
- self.addROI(roiState['name'], roiState['pos'], roiState['size'], roiState['angle'])
- elif roiState['type'] == 'PolyLineROIcustom':
- self.addPolyLineROI(roiState['handlePositions'], roiState['name'])
- def removeROI(self):
- """ Delete the highlighted ROI """
- if self.currentROIindex != None:
- roi = self.rois[self.currentROIindex]
- self.rois.pop(self.currentROIindex)
- self.removeItem(roi)
- self.setCurrentROIindex(None)
- def toggleViewMode(self, isChecked):
- """ Toggles between NORMAL (Black/White) and DEXA mode (colour) """
- if isChecked:
- viewMode = self.DEXA
- else:
- viewMode = self.NORMAL
- self.setViewMode(viewMode)
- def setViewMode(self, viewMode):
- self.viewMode = viewMode
- self.updateView()
- def updateView(self):
- self.background.setBrush(fn.mkBrush(self.viewMode.lut[0]))
- self.background.show()
- if self.img == None: return
- # todo: validate safe removal of line
- # else: self.img.setLookupTable(self.viewMode.lut)
- def update_rect(self, x1, y1, x2, y2):
- if not self.img:
- return
- self.img.setRect(QtCore.QRectF(x1, y1, x2, y2))
- def showImage(self, arr):
- arr = arr.astype("float64")
- if arr is None:
- self.img = None
- return
- if self.img == None:
- self.img = pg.ImageItem(arr, autoRange=False, autoLevels=False)
- self.addItem(self.img)
- # Add/readd crosshair
- if self.crosshair_visible:
- self.addItem(self.vLine, ignoreBounds=True)
- self.addItem(self.hLine, ignoreBounds=True)
- proxy = pg.SignalProxy(self.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)
- self.scene().sigMouseMoved.connect(self.mouseMoved)
- self.img.setImage(arr)
- self.updateView()
- import pyqtgraph as pg
- from pyqtgraph.Qt import QtCore, QtGui
- from pyqtgraph.exporters.ImageExporter import ImageExporter
- import numpy as np
- # ------------------------- custom_items.py --------------------------- #
- class QMenuCustom(QtGui.QMenu):
- """ Custum QMenu that closes on leaveEvent """
- def __init__(self, parent=None):
- QtGui.QMenu.__init__(self, parent)
- def leaveEvent(self, ev):
- self.hide()
- class QActionCustom(QtGui.QAction):
- """ QAction class modified to emit a single argument (an event)"""
- clickEvent = QtCore.Signal(object)
- def __init__(self, name="", parent=None):
- QtGui.QAction.__init__(self, name, parent)
- self.triggered.connect(self.clicked)
- # self.event = None
- def updateEvent(self, event):
- self.event = event
- def clicked(self):
- self.clickEvent.emit(self.event)
- class ImageExporterCustom(ImageExporter):
- """
- Subclass to change preferred image output to bmp. Currently there are some issues
- with png, as it creates some lines around the image
- """
- def __init__(self, item):
- ImageExporter.__init__(self, item)
- def export(self, fileName=None, toBytes=False, copy=False):
- if fileName is None and not toBytes and not copy:
- filter = ["*." + str(f) for f in QtGui.QImageWriter.supportedImageFormats()]
- preferred = ['*.bmp', '*.png', '*.tif', '*.jpg']
- for p in preferred[::-1]:
- if p in filter:
- filter.remove(p)
- filter.insert(0, p)
- self.fileSaveDialog(filter=filter)
- return
- targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height'])
- sourceRect = self.getSourceRect()
- bg = np.empty((self.params['width'], self.params['height'], 4), dtype=np.ubyte)
- color = self.params['background']
- bg[:, :, 0] = color.blue()
- bg[:, :, 1] = color.green()
- bg[:, :, 2] = color.red()
- bg[:, :, 3] = color.alpha()
- self.png = pg.makeQImage(bg, alpha=True)
- origTargetRect = self.getTargetRect()
- resolutionScale = targetRect.width() / origTargetRect.width()
- painter = QtGui.QPainter(self.png)
- try:
- self.setExportMode(True, {'antialias': self.params['antialias'], 'background': self.params['background'],
- 'painter': painter, 'resolutionScale': resolutionScale})
- painter.setRenderHint(QtGui.QPainter.Antialiasing, self.params['antialias'])
- self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect))
- finally:
- self.setExportMode(False)
- painter.end()
- if copy:
- QtGui.QApplication.clipboard().setImage(self.png)
- elif toBytes:
- return self.png
- else:
- self.png.save(fileName)
- import os
- import sys
- import numpy as np
- import pyqtgraph as pg
- from PyQt4.QtGui import *
- from PyQt4.QtCore import *
- from PyQt4 import QtCore
- from PyQt4 import QtGui
- import uuid
- import csv
- # ------------------------ main.py ----------------------- #
- class RoiItemModel(QAbstractListModel):
- textChanged = pyqtSignal(str, str)
- def __init__(self, parent=None):
- super(RoiItemModel, self).__init__(parent)
- self.rois = []
- def appendRoi(self, name):
- self.rois.append(name)
- row = len(self.rois) - 1
- self.dataChanged.emit(self.index(row), self.index(row))
- def rowCount(self, parent):
- return len(self.rois)
- def data(self, index, role):
- if role == Qt.DisplayRole:
- return self.rois[index.row()]
- return
- def setData(self, index, value, role):
- if role in [Qt.DisplayRole, Qt.EditRole]:
- self.textChanged.emit(self.rois[index.row()], value)
- self.rois[index.row()] = str(value)
- return True
- return super(RoiItemModel, self).setData(index, value, role)
- def flags(self, index):
- return Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled
- def removeRow(self, roi_to_remove):
- for roi in self.rois:
- if roi == roi_to_remove:
- del roi
- break
- class Widget(QWidget):
- def __init__(self, project, parent=None):
- super(Widget, self).__init__(parent)
- if not project:
- return
- if project == "standalone":
- self.project = None
- else:
- self.project = project
- anatomy_rois = {"M1": [3, (1.0, 2.5)], "M2": [3, (1.5, 1.75)],
- "AC": [3, (0.5, 0.0)], "HL": [3, (2.0, 0.0)],
- "BC": [3, (3.5, -1.0)], "RS": [3, (0.5, -2.5)], "V1": [3, (2.5, -2.5)]}
- roi_names = list(anatomy_rois.keys())
- roi_sizes = [anatomy_rois[x][0] for x in anatomy_rois.keys()]
- roi_coord_x = [anatomy_rois[x][1][0] for x in anatomy_rois.keys()]
- roi_coord_y = [anatomy_rois[x][1][1] for x in anatomy_rois.keys()]
- self.headers = ["1) ROI Name", "2) Length", "3) X Coordinate", "4) Y Coordinate"]
- self.data = {self.headers[0]: roi_names, self.headers[1]: roi_sizes,
- self.headers[2]: roi_coord_x, self.headers[3]: roi_coord_y}
- self.open_dialogs = []
- self.setup_ui()
- self.listview.setModel(QStandardItemModel())
- for f in ['f1', 'f2', 'f3']:
- self.listview.model().appendRow(QStandardItem(str(f)))
- self.listview.setCurrentIndex(self.listview.model().index(0, 0))
- model = RoiItemModel()
- model.textChanged.connect(self.update_project_roi)
- self.roi_list.setModel(model)
- self.roi_list.setSelectionMode(QAbstractItemView.ExtendedSelection)
- # A flag to see whether selected_roi_changed is being entered for the first time
- self.selected_roi_changed_flag = 0
- self.roi_list.selectionModel().selectionChanged[QItemSelection,
- QItemSelection].connect(self.selected_roi_changed)
- def show_table(self):
- locs = zip(self.data[self.headers[0]], self.data[self.headers[1]],
- self.data[self.headers[2]], self.data[self.headers[3]])
- model = AutoROICoords(self.data, len(list(locs)), 4)
- model.itemChanged.connect(self.update_auto_rois)
- model.show()
- self.open_dialogs.append(model)
- def roi_item_edited(self, item):
- new_name = item.text()
- prev_name = item.data(Qt.UserRole)
- # disconnect and reconnect signal
- self.roi_list.itemChanged.disconnect()
- item.setData(new_name, Qt.UserRole)
- self.roi_list.model().itemChanged[QStandardItem.setData].connect(self.roi_item_edited)
- def setup_ui(self):
- hbox = QHBoxLayout()
- self.view = MyGraphicsView(self.project)
- hbox.addWidget(self.view)
- vbox = QVBoxLayout()
- vbox.addWidget(QLabel('Choose video:'))
- self.listview = QListView()
- self.listview.setStyleSheet('QListView::item { height: 26px; }')
- vbox.addWidget(self.listview)
- vbox.addWidget(QLabel('ROI size NxN'))
- self.roi_size = QSpinBox()
- self.roi_size.setMinimum(1)
- self.roi_size.setValue(3)
- vbox.addWidget(self.roi_size)
- pb = QPushButton('auto ROI')
- pb.clicked.connect(self.auto_ROI)
- vbox.addWidget(pb)
- pb = QPushButton('auto ROI table')
- pb.clicked.connect(self.show_table)
- vbox.addWidget(pb)
- pb = QPushButton('Load anatomical coordinates (relative to selected origin)')
- pb.clicked.connect(self.load_ROI_table)
- vbox.addWidget(pb)
- vbox2 = QVBoxLayout()
- w = QWidget()
- w.setLayout(vbox2)
- vbox.addWidget(w)
- vbox.addWidget(QLabel('ROIs'))
- self.roi_list = QListView()
- vbox.addWidget(self.roi_list)
- hbox.addLayout(vbox)
- hbox.setStretch(0, 1)
- hbox.setStretch(1, 0)
- self.setLayout(hbox)
- def remove_all_rois(self):
- rois = self.view.vb.rois[:]
- for roi in rois:
- if not roi.isSelected:
- self.view.vb.selectROI(roi)
- self.view.vb.removeROI()
- def selected_roi_changed(self, selection):
- if self.selected_roi_changed_flag == 0:
- self.selected_roi_changed_flag = self.selected_roi_changed_flag + 1
- return
- if not selection.indexes() or self.view.vb.drawROImode:
- return
- self.remove_all_rois()
- rois_selected = [str(self.roi_list.selectionModel().selectedIndexes()[x].data(Qt.DisplayRole))
- for x in range(len(self.roi_list.selectionModel().selectedIndexes()))]
- rois_in_view = [self.view.vb.rois[x].name for x in range(len(self.view.vb.rois))]
- rois_to_add = [x for x in rois_selected if x not in rois_in_view]
- for roi_to_add in rois_to_add:
- self.view.vb.loadROI([os.getcwd() + '/' + roi_to_add + '.roi'])
- def update_project_roi(self, roi):
- name = roi.name
- if not name:
- raise ValueError('ROI has no name')
- if self.view.vb.drawROImode:
- return
- path = os.path.join(os.getcwd(), name + '.roi')
- self.view.vb.saveROI(path)
- self.roi_list.model().appendRoi(name)
- def load_ROI_table(self):
- text_file = QFileDialog.getOpenFileName(
- self, 'Load images', QSettings().value('last_load_text_path'),
- 'Video files (*.csv *.txt)')
- if not text_file:
- return
- QSettings().setValue('last_load_text_path', os.path.dirname(text_file))
- roi_table = [] # np.empty(shape=(4, ))
- with open(text_file, 'rt', encoding='ascii') as csvfile:
- roi_table_it = csv.reader(csvfile, delimiter=',')
- for row in roi_table_it:
- roi_table = roi_table + [row]
- roi_table = np.array(roi_table)
- self.headers = [str.strip(x) for x in roi_table[0,]]
- roi_table_range = range(len(roi_table))[1:]
- roi_names = [roi_table[x, 0] for x in roi_table_range]
- roi_sizes = [int(roi_table[x, 1]) for x in roi_table_range]
- roi_coord_x = [float(roi_table[x, 2]) for x in roi_table_range]
- roi_coord_y = [float(roi_table[x, 3]) for x in roi_table_range]
- self.data = {self.headers[0]: roi_names, self.headers[1]: roi_sizes,
- self.headers[2]: roi_coord_x, self.headers[3]: roi_coord_y}
- self.auto_ROI()
- def update_auto_rois(self, item):
- col = item.column()
- row = item.row()
- try:
- val = float(item.text())
- except:
- val = str(item.text())
- header = item.tableWidget().horizontalHeaderItem(col).text()
- header = str(header)
- col_to_change = self.data[header]
- col_to_change[row] = val
- self.data[header] = col_to_change
- self.auto_ROI()
- def auto_ROI(self):
- locs = zip(self.data[self.headers[0]], self.data[self.headers[1]],
- self.data[self.headers[2]], self.data[self.headers[3]])
- # Warning: size must always be the second column
- for quad in list(locs):
- half_length = quad[1]
- self.remove_all_rois()
- x1 = (quad[2] - half_length)
- x2 = (quad[2] - half_length)
- x3 = (quad[2] + half_length)
- x4 = (quad[2] + half_length)
- y1 = (quad[3] - half_length)
- y2 = (quad[3] + half_length)
- y3 = (quad[3] + half_length)
- y4 = (quad[3] - half_length)
- self.view.vb.addPolyRoiRequest()
- self.view.vb.autoDrawPolygonRoi(quad[0], pos=QtCore.QPointF(x1, y1))
- self.view.vb.autoDrawPolygonRoi(quad[0], pos=QtCore.QPointF(x2, y2))
- self.view.vb.autoDrawPolygonRoi(quad[0], pos=QtCore.QPointF(x3, y3))
- self.view.vb.autoDrawPolygonRoi(quad[0], pos=QtCore.QPointF(x4, y4))
- self.view.vb.autoDrawPolygonRoi(quad[0], pos=QtCore.QPointF(x4, y4))
- self.view.vb.autoDrawPolygonRoi(quad[0], finished=True)
- roi = self.view.vb.rois[0]
- self.update_project_roi(roi)
- class AutoROICoords(QTableWidget):
- def __init__(self, data, *args):
- QTableWidget.__init__(self, *args)
- self.data = data
- self.resizeColumnsToContents()
- self.resizeRowsToContents()
- self.horizontalHeader().setResizeMode(QHeaderView.Stretch)
- self.verticalHeader().setResizeMode(QHeaderView.Stretch)
- self.setmydata()
- def setmydata(self):
- horHeaders = self.data.keys()
- for n, key in enumerate(sorted(horHeaders)):
- for m, item in enumerate(self.data[key]):
- newitem = QTableWidgetItem(str(item))
- self.setItem(m, n, newitem)
- self.setHorizontalHeaderLabels(sorted(horHeaders))
- class MyPlugin:
- def __init__(self, project):
- self.name = 'Auto ROI placer'
- self.widget = Widget(project)
- def run(self):
- pass
- if __name__ == '__main__':
- app = QApplication(sys.argv)
- app.aboutToQuit.connect(app.deleteLater)
- w = QMainWindow()
- w.setCentralWidget(Widget("standalone"))
- w.show()
- app.exec_()
- sys.exit()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement