예제 #1
0
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 
예제 #2
0
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
예제 #3
0
class MultiRoiViewBox(pg.ViewBox):

    sigROIchanged = QtCore.Signal(object)

    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.rois = []
        self.currentROIindex = None
        self.img = None
        self.menu = None  # Override pyqtgraph ViewBoxMenu
        self.menu = self.getMenu(None)
        self.NORMAL = ViewMode(0, matplotlib.cm.gray)
        self.DEXA = ViewMode(1, matplotlib.cm.jet)
        self.viewMode = self.NORMAL
        self.drawROImode = False
        self.drawingROI = None

    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()
            self.drawPolygonRoi(ev)
        elif ev.button() == QtCore.Qt.RightButton and self.menuEnabled():
            ev.accept()
            self.raiseContextMenu(ev)

    def addPolyRoiRequest(self):
        """Function to add a Polygon ROI"""
        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):
        roi = PolyLineROIcustom(handlePositions=handlePositions,
                                removable=True)
        roi.setName('ROI-%i' % self.getROIid())
        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 = 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)

    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
                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
                roi.addFreeHandle(pos)
                roi.addFreeHandle(pos)
                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()

    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, pos=None, size=None, angle=0.0):
        """ Add an ROI to the ViewBox """
        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))
        # 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)

    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 """
        nums = [
            int(roi.name.split('-')[-1]) for roi in self.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):
        """ Save the highlighted ROI to file """
        if self.currentROIindex != None:
            roi = self.rois[self.currentROIindex]
            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'
                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
                pickle.dump(roiState, open(fileName, "wb"))

    def loadROI(self):
        """ Load a previously saved ROI from file """
        fileNames = QtGui.QFileDialog.getOpenFileNames(
            None, self.tr("Load 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(fileNames, types.TupleType): fileNames = fileNames[0]
        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['pos'], roiState['size'],
                                    roiState['angle'])
                    elif roiState['type'] == 'PolyLineROIcustom':
                        self.addPolyLineROI(roiState['handlePositions'])

    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
        else: self.img.setLookupTable(self.viewMode.lut)

    def showImage(self, arr):
        if arr == None:
            self.img = None
            return
        if self.img == None:
            self.img = pg.ImageItem(arr, autoRange=False, autoLevels=False)
            self.addItem(self.img)
        self.img.setImage(arr, autoLevels=False)
        self.updateView()
예제 #4
0
 def export(self):
     self.exp = ImageExporterCustom(self)
     self.exp.export()
예제 #5
0
 def export(self):
     ''' Export viewbox image '''
     self.exp = ImageExporterCustom(self)
     self.exp.export()
예제 #6
0
class MultiRoiViewBox(pg.ViewBox):
    
    ''' Custom Viewbox for multiple ROIs '''

    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)
        # Set default values
        self.rois = []
        self.currentROIindex = None
        self.img         = None    
        self.NORMAL      = ViewMode(0,matplotlib.cm.gray)  
        self.DEXA        = ViewMode(1,matplotlib.cm.jet)
        self.viewMode    = self.NORMAL
        self.drawROImode = False
        self.drawingROI  = None
        self.menu        = None
        
    def getContextMenus(self,ev):
        return None
        
    def raiseContextMenu(self, ev):
        ''' Display context menu at location of right mouse click '''
        if not self.menuEnabled(): return
        menu = self.getMenu(ev)
        pos  = ev.screenPos()
        menu.popup(QtCore.QPoint(pos.x(), pos.y()))
        
    def export(self):
        ''' Export viewbox image '''
        self.exp = ImageExporterCustom(self)
        self.exp.export()
        
    def raiseRoiSelectMenuLeft(self,ev,roiList):
        ''' Raise roi menu on left mouse click '''        
        self.roimenu = QtGui.QMenu()
        for roi in roiList:
            action = QtGui.QAction(roi.name, self.roimenu)
            action.triggered[()].connect(lambda arg=roi: self.selectROI(arg))     
            self.roimenu.addAction(action)
        pos = ev.screenPos()
        self.roimenu.popup(QtCore.QPoint(pos.x(), pos.y()))
        
    def raiseRoiSelectMenuRight(self,ev,roiList):
        ''' Raise roi menu on right mouse click 
            Must use functools.partial (not lamda) here to get signals to work here '''
        self.roimenu = QtGui.QMenu()
        for roi in roiList:
            action = QtGui.QAction(roi.name, self.roimenu)
            action.triggered[()].connect(partial(roi.raiseContextMenu, ev))
            self.roimenu.addAction(action)
        pos = ev.screenPos()
        self.roimenu.popup(QtCore.QPoint(pos.x(), pos.y()))
            
    def mouseClickEvent(self, ev):
        ''' Mouse click event handler '''        
        # Check if click is over any rois
        roisUnderMouse = []
        pos = ev.scenePos()
        for roi in self.rois:
            if roi.isUnderMouse(pos):
                roisUnderMouse.append(roi)
        numRois = len(roisUnderMouse)
        # Drawing mode (all buttons)        
        if self.drawROImode:
            ev.accept()
            self.drawPolygonRoi(ev)
        # Click not over any rois
        elif numRois==0:
            # Context menu (right mouse button)
            if ev.button() == QtCore.Qt.RightButton and self.menuEnabled():
                self.raiseContextMenu(ev)         
        # Click over any rois      
        else:
            if ev.button() == QtCore.Qt.LeftButton:       
                if numRois==1:
                    self.selectROI(roisUnderMouse[0])
                elif numRois>1:
                    self.raiseRoiSelectMenuLeft(ev,roisUnderMouse)
            elif ev.button() == QtCore.Qt.RightButton: 
                if numRois==1:
                    roisUnderMouse[0].raiseContextMenu(ev)
                elif numRois>1:
                    self.raiseRoiSelectMenuRight(ev,roisUnderMouse)            
           
    def addPolyRoiRequest(self):
        ''' Function to add a Polygon ROI '''
        self.drawROImode = True

    def endPolyRoiRequest(self):
        ''' Called at the completion of drawing Polygon ROI '''
        self.drawROImode = False  # Deactivate drawing mode
        self.drawingROI  = None   # No roi being drawn, so set to None
         
    def addPolyLineROI(self,handlePositions):
        ''' Add Polygon ROI - Used for copy and load operations '''
        roi = PolyLineROIcustom(handlePositions=handlePositions,removable=True)
        roi.setName('ROI-%i'% self.getROIid())
        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 = True
        for seg in roi.segments:
            seg.setSelectable(True)
        for h in roi.handles:
            h['item'].setSelectable(True)
        # Setup signals
        roi.sigRemoveRequested.connect(self.removeROI)
        roi.sigCopyRequested.connect(self.copyROI)
        roi.sigSaveRequested.connect(self.saveROI)            

    def drawPolygonRoi(self,ev):
        ''' Function to draw a Polygon ROI - Called directly by MouseClickEvent '''
        roi = self.drawingROI
        pos = self.mapSceneToView(ev.scenePos())
        # TO DRAW ROI
        if ev.button() == QtCore.Qt.LeftButton:
            if roi is None:                                  # To start drawing a new roi
                roi = PolyLineROIcustom(removable = False)   # Create new roi
                roi.setName('ROI-%i'% self.getROIid())       # Set name. Do this before self.selectROIs(roi)
                self.drawingROI = roi                      
                self.addItem(roi)                            # Add roi to viewbox
                self.rois.append(roi)                        # Add to list of rois
                self.selectROI(roi)                          # Make selected
                self.sortROIs()                              # Sort list of rois
                self.setCurrentROIindex(roi)                 # Make current 
                roi.translatable = False                     # Deactivate translation during drawing
                roi.addFreeHandle(pos)                       # Add two handles on first click (1 fixed, 1 draggable)
                roi.addFreeHandle(pos)
                h = roi.handles[-1]['item']                  # Get draggable handle
                h.scene().sigMouseMoved.connect(h.movePoint) # Connect signal to move handle with mouse
            else:                                            # To continue drawing an existing roi
                h = roi.handles[-1]['item']                  # Get last handle
                h.scene().sigMouseMoved.disconnect()         # Make last handle non-draggable  
                roi.addFreeHandle(pos)                       # Add new handle
                h = roi.handles[-1]['item']                  # Get new handle
                h.scene().sigMouseMoved.connect(h.movePoint) # Make new handle draggable  
            # 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)
        # TO STOP DRAWING ROI
        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()
        # TO COMPLETE ROI
        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 on completed roi
            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,ev):
        '''Create and return context menu '''
        # Menu and submenus
        self.menu          = QtGui.QMenu()
        self.submenu       = QtGui.QMenu("Add ROI", self.menu)           
        # Actions
        self.addROIRectAct = QtGui.QAction("Rectangular", self.submenu)
        self.addROIPolyAct = QtGui.QAction("Polygon", self.submenu)
        self.loadROIAct    = QtGui.QAction("Load ROI", self.menu)
        self.dexaMode      = QtGui.QAction("Toggle normal/DEXA view", self.menu)
        self.viewAll       = QtGui.QAction("View All", self.menu)
        self.exportImage   = QtGui.QAction("Export image", self.menu)
        # Signals
        self.loadROIAct.triggered[()].connect(self.loadROI)
        self.dexaMode.triggered.connect(self.toggleViewMode)
        self.viewAll.triggered[()].connect(self.autoRange)
        self.exportImage.triggered[()].connect(self.export)
        self.addROIRectAct.triggered[()].connect(lambda arg=ev: self.addRoiRequest(arg))
        self.addROIPolyAct.triggered.connect(self.addPolyRoiRequest)
        # Add actions to menu and submenus
        self.submenu.addAction(self.addROIRectAct)
        self.submenu.addAction(self.addROIPolyAct)
        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)
        # Return menu
        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)

    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,pos=None,size=None,angle=0.0):
        ''' Add an ROI to the ViewBox '''    
        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))
        # Setup signals
        roi.setName('ROI-%i'% self.getROIid()) 
        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)

    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 '''
        nums = [ int(roi.name.split('-')[-1]) for roi in self.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):
        ''' Copy current ROI. Offset from original for visibility '''
        osFract = 0.05
        if self.currentROIindex!=None:
            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):
        ''' Save the highlighted ROI to file '''   
        if self.currentROIindex!=None:
            roi = self.rois[self.currentROIindex]
            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'
                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
                pickle.dump( roiState, open( fileName, "wb" ) )
                          
    def loadROI(self):
        ''' Load a previously saved ROI from file '''
        fileNames = QtGui.QFileDialog.getOpenFileNames(None,self.tr("Load 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(fileNames,types.TupleType): fileNames = fileNames[0]
        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['pos'],roiState['size'],roiState['angle'])    
                    elif roiState['type']=='PolyLineROIcustom':
                        self.addPolyLineROI(roiState['handlePositions'])
            
    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):
        ''' Toggles between NORMAL (Black/White) and DEXA mode (colour) '''
        if self.viewMode == self.NORMAL:
            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 is None: return
        else: self.img.setLookupTable(self.viewMode.lut)            
       
    def showImage(self,arr):
        if arr is None: 
            self.img = None
            return
        if self.img is None: 
            self.img = pg.ImageItem(arr,autoRange=False,autoLevels=False)
            self.addItem(self.img)      
        self.img.setImage(arr,autoLevels=False)
        self.updateView()  
예제 #7
0
 def export(self):
     self.exp = ImageExporterCustom(self)
     self.exp.export()
예제 #8
0
class MultiRoiViewBox(pg.ViewBox):

    sigROIchanged = QtCore.Signal(object)

    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.rois = []
        self.currentROIindex = None
        self.img      = None
        self.menu     = None # Override pyqtgraph ViewBoxMenu 
        self.menu     = self.getMenu(None)       
        self.NORMAL   = ViewMode(0,matplotlib.cm.gray)  
        self.DEXA     = ViewMode(1,matplotlib.cm.jet)
        self.viewMode = self.NORMAL
        self.drawROImode = False
        self.drawingROI  = None
        
    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()
            self.drawPolygonRoi(ev)            
        elif ev.button() == QtCore.Qt.RightButton and self.menuEnabled():
            ev.accept()
            self.raiseContextMenu(ev) 
            
    def addPolyRoiRequest(self):
        """Function to add a Polygon ROI"""
        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):
        roi = PolyLineROIcustom(handlePositions=handlePositions,removable=True)
        roi.setName('ROI-%i'% self.getROIid())
        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 = 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)            

    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                  
                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 
                roi.addFreeHandle(pos)
                roi.addFreeHandle(pos)
                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()                
                
    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,pos=None,size=None,angle=0.0):
        """ Add an ROI to the ViewBox """    
        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))
        # 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)

    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 """
        nums = [ int(roi.name.split('-')[-1]) for roi in self.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):
        """ Save the highlighted ROI to file """    
        if self.currentROIindex!=None:
            roi = self.rois[self.currentROIindex]
            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'
                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
                pickle.dump( roiState, open( fileName, "wb" ) )
                          
    def loadROI(self):
        """ Load a previously saved ROI from file """
        fileNames = QtGui.QFileDialog.getOpenFileNames(None,self.tr("Load 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(fileNames,types.TupleType): fileNames = fileNames[0]
        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['pos'],roiState['size'],roiState['angle'])    
                    elif roiState['type']=='PolyLineROIcustom':
                        self.addPolyLineROI(roiState['handlePositions'])
            
    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
        else: self.img.setLookupTable(self.viewMode.lut)            
       
    def showImage(self,arr):
        if arr==None: 
            self.img = None
            return
        if self.img==None: 
            self.img = pg.ImageItem(arr,autoRange=False,autoLevels=False)
            self.addItem(self.img)      
        self.img.setImage(arr,autoLevels=False)
        self.updateView()