class GraphicsWidget(QWidget): """Widget defined in Qt Designer""" def __init__(self, parent=None): # initialization of widget super(GraphicsWidget, self).__init__(parent) # Create a central Graphics Layout Widget self.widget = GraphicsLayoutWidget() # A plot area (ViewBox + axes) for displaying the image self.p1 = self.widget.addPlot() # Item for displaying an array of image data stacks self.stacks = [] img = ImageItem() self.p1.addItem(img) self.stacks.append(img) # create a vertical box layout self.vbl = QVBoxLayout() # add widget to vertical box self.vbl.addWidget(self.widget) # set the layout to the vertical box self.setLayout(self.vbl) # Levels/color control with a histogram # self.hist = HistogramLUTWidget() # self.hist.setImageItem(self.img) # parent.horizontalLayout_View.addWidget(self.hist) # # self.widget.addWidget(self.hist, 0, 1) # self.hist.vb.setMouseEnabled(y=False) # makes user interaction a little easier # Create an array of histograms self.histograms = [] # Levels/color control with a histogram hist = HistogramLUTItem() hist.vb.setMouseEnabled( y=False) # makes user interaction a little easier hist.setImageItem(img) self.widget.addItem(hist) self.histograms.append(hist)
class QtPlotArea(QtControl, ProxyPlotArea): """ PyQtGraph Plot Widget """ __weakref__ = None widget = Typed(GraphicsLayoutWidget) def create_widget(self): self.widget = GraphicsLayoutWidget(self.parent_widget()) def init_layout(self): for child in self.children(): self.child_added(child) def child_added(self, child): # TODO support layouts if isinstance(child,AbstractQtPlotItem): d = child.declaration kwargs = dict(row=d.row,col=d.column) if d.row or d.column else {} self.widget.addItem(child.widget,**kwargs) def child_removed(self, child): if isinstance(child,AbstractQtPlotItem): self.widget.removeItem(child.widget)
class GraphicsWidget(QWidget): """Widget defined in Qt Designer""" def __init__(self, parent=None): # initialization of widget super(GraphicsWidget, self).__init__(parent) # Create a central Graphics Layout Widget self.widget = GraphicsLayoutWidget() # A plot area (ViewBox + axes) for displaying the image self.p1 = self.widget.addPlot() # Item for displaying image data self.img_item = ImageItem() self.img_item.axisOrder = 'row-major' self.p1.addItem(self.img_item) self.p1.getViewBox().invertY(True) # create a vertical box layout self.vbl = QVBoxLayout() # add widget to vertical box self.vbl.addWidget(self.widget) # set the layout to the vertical box self.setLayout(self.vbl) # Levels/color control with a histogram # self.hist = HistogramLUTWidget() # self.hist.setImageItem(self.img) # parent.horizontalLayout_View.addWidget(self.hist) # # self.widget.addWidget(self.hist, 0, 1) # self.hist.vb.setMouseEnabled(y=False) # makes user interaction a little easier # Create histogram # Levels/color control with a histogram self.histogram = HistogramLUTItem() # TODO Halve histogram width self.histogram.vb.setMouseEnabled( y=False) # makes user interaction a little easier self.histogram.setImageItem(self.img_item) self.widget.addItem(self.histogram)
class MainWindow(QtGui.QMainWindow): def __init__(self, parent=None): QtGui.QMainWindow.__init__(self, parent) self.setupUserInterface() def setupUserInterface(self): frame = QtGui.QFrame() frameLayout = QtGui.QHBoxLayout() frame.setLayout(frameLayout) frame.setLineWidth(0) frame.setFrameStyle(QtGui.QFrame.Panel) frameLayout.setContentsMargins(0,0,5,0) self.viewMain = GraphicsLayoutWidget() self.viewMain.setMinimumSize(200,200) self.vb = MultiRoiViewBox(lockAspect=True,enableMenu=True) self.viewMain.addItem(self.vb) self.vb.disableAutoRange() self.setCentralWidget(self.viewMain)
class MainWindow(QtGui.QMainWindow): def __init__(self, parent=None): QtGui.QMainWindow.__init__(self, parent) self.loadIcons() self.setupUserInterface() self.setupSignals() self.__version__ = __version__ # Initialise variables self.imageFiles = {} self.timeData = None self.plotWin = None self.imageWin = None self.BMDchange = None self.roiNames = None def loadIcons(self): """ Load icons """ self.icons = dict([ ('BMDanalyseIcon', QtGui.QIcon(os.path.join(absDirPath,"icons","logo.png"))), ('imageAddIcon', QtGui.QIcon(os.path.join(absDirPath,"icons","file_add.png"))), ('imageRemIcon', QtGui.QIcon(os.path.join(absDirPath,"icons","file_delete2.png"))), ('imageDownIcon', QtGui.QIcon(os.path.join(absDirPath,"icons","arrow-up-2.png"))), ('imageUpIcon', QtGui.QIcon(os.path.join(absDirPath,"icons","arrow-down-2.png"))), ('imagePrevIcon', QtGui.QIcon(os.path.join(absDirPath,"icons","arrow-left.png"))), ('imageNextIcon', QtGui.QIcon(os.path.join(absDirPath,"icons","arrow-right.png"))), ('roiAddIcon', QtGui.QIcon(os.path.join(absDirPath,"icons","green-add3.png"))), ('roiRectIcon', QtGui.QIcon(os.path.join(absDirPath,"icons","rectangularIcon.png"))), ('roiPolyIcon', QtGui.QIcon(os.path.join(absDirPath,"icons","polygonIcon.png"))), ('roiRemIcon', QtGui.QIcon(os.path.join(absDirPath,"icons","red_delete.png"))), ('roiSaveIcon', QtGui.QIcon(os.path.join(absDirPath,"icons","filesave.png"))), ('roiCopyIcon', QtGui.QIcon(os.path.join(absDirPath,"icons","file_copy.png"))), ('roiLoadIcon', QtGui.QIcon(os.path.join(absDirPath,"icons","opened-folder.png")))]) def setupUserInterface(self): """ Initialise the User Interface """ # Left frame leftFrame = QtGui.QFrame() leftFrameLayout = QtGui.QHBoxLayout() leftFrame.setLayout(leftFrameLayout) leftFrame.setLineWidth(0) leftFrame.setFrameStyle(QtGui.QFrame.Panel) leftFrameLayout.setContentsMargins(0,0,5,0) # Left frame contents self.viewMain = GraphicsLayoutWidget() # A GraphicsLayout within a GraphicsView leftFrameLayout.addWidget(self.viewMain) self.viewMain.setMinimumSize(200,200) self.vb = MultiRoiViewBox(lockAspect=True,enableMenu=True) self.viewMain.addItem(self.vb) self.vb.disableAutoRange() # Right frame self.sidePanel = SidePanel(self) # UI window (containing left and right frames) UIwindow = QtGui.QWidget(self) UIwindowLayout = QtGui.QHBoxLayout() UIwindowSplitter = QtGui.QSplitter(QtCore.Qt.Horizontal) UIwindowLayout.addWidget(UIwindowSplitter) UIwindow.setLayout(UIwindowLayout) self.setCentralWidget(UIwindow) UIwindowSplitter.addWidget(leftFrame) UIwindowSplitter.addWidget(self.sidePanel) # Application window self.setWindowTitle('BMDanalyse') self.setWindowIcon(self.icons['BMDanalyseIcon']) self.setMinimumSize(600,500) self.resize(self.minimumSize()) # Window menus self.createMenus() self.createActions() def createMenus(self): # Menus menubar = self.menuBar() self.fileMenu = menubar.addMenu('&File') self.imageMenu = menubar.addMenu('&Images') self.roiMenu = menubar.addMenu('&ROIs') self.submenu = self.roiMenu.addMenu(self.icons['roiAddIcon'],"Add ROI") self.analyseMenu = menubar.addMenu('&Analysis') self.aboutMenu = menubar.addMenu('A&bout') def createActions(self): # Actions for File menu self.exitAct = QtGui.QAction("&Quit", self, shortcut="Ctrl+Q",statusTip="Exit the application") self.exitAct.triggered[()].connect(self.close) self.fileMenu.addAction(self.exitAct) # Actions for Images menu self.loadImageAct = QtGui.QAction(self.icons['imageAddIcon'], "&Load image(s)", self, shortcut="Ctrl+L") self.removeImageAct = QtGui.QAction(self.icons['imageRemIcon'], "&Remove current image", self, shortcut="Ctrl+X") imageMenuActions = [self.loadImageAct,self.removeImageAct] imageMenuActFuncs = [self.loadImages,self.removeImage] for i in xrange(len(imageMenuActions)): action = imageMenuActions[i] function = imageMenuActFuncs[i] action.triggered[()].connect(function) self.imageMenu.addAction(self.loadImageAct) self.imageMenu.addAction(self.removeImageAct) # Actions for ROI menu self.addROIRectAct = QtGui.QAction("Rectangular",self.submenu) self.addROIPolyAct = QtGui.QAction("Polygon",self.submenu) self.addROIRectAct.triggered[()].connect(self.vb.addROI) self.addROIPolyAct.triggered[()].connect(self.vb.addPolyRoiRequest) self.submenu.addAction(self.addROIRectAct) self.submenu.addAction(self.addROIPolyAct) self.addROIRectAct.setIcon(self.icons['roiRectIcon']) self.addROIPolyAct.setIcon(self.icons['roiPolyIcon']) self.addROIRectAct.setShortcut("Ctrl+Shift+R") self.addROIPolyAct.setShortcut("Ctrl+Shift+P") self.loadRoiAct = QtGui.QAction(self.icons['roiLoadIcon'], "L&oad ROI", self, shortcut="Ctrl+O") self.copyRoiAct = QtGui.QAction(self.icons['roiCopyIcon'], "&Copy ROI", self, shortcut="Ctrl+C") self.saveRoiAct = QtGui.QAction(self.icons['roiSaveIcon'], "&Save ROI", self, shortcut="Ctrl+S") self.remRoiAct = QtGui.QAction(self.icons['roiRemIcon'] , "&Remove ROI", self, shortcut="Ctrl+D") roiMenuActions = [self.loadRoiAct,self.copyRoiAct,self.saveRoiAct,self.remRoiAct] roiMenuActFuncs = [self.vb.loadROI,self.vb.copyROI,self.vb.saveROI,self.vb.removeROI] for i in xrange(len(roiMenuActions)): action = roiMenuActions[i] function = roiMenuActFuncs[i] action.triggered[()].connect(function) self.roiMenu.addAction(action) # Actions for Analyse menu self.roiAnalysisAct = QtGui.QAction("&ROI analysis", self.viewMain, shortcut="Ctrl+R",triggered=self.getBMD) self.imgAnalysisAct = QtGui.QAction("&Image analysis", self.viewMain, shortcut="Ctrl+I",triggered=self.imageAnalysis) self.analyseMenu.addAction(self.roiAnalysisAct) self.analyseMenu.addAction(self.imgAnalysisAct) # Actions for self.aboutAct = QtGui.QAction("&About", self.viewMain, shortcut='F1', triggered=self.onAbout) self.aboutMenu.addAction(self.aboutAct) def setupSignals(self): """ Setup signals """ self.sidePanel.imageFileList.itemSelectionChanged.connect(self.getImageToDisplay) self.sidePanel.buttImageAdd.clicked.connect(self.loadImages) self.sidePanel.buttImageRem.clicked.connect(self.removeImage) self.sidePanel.buttImageUp.clicked.connect(self.sidePanel.moveImageUp) self.sidePanel.buttImageDown.clicked.connect(self.sidePanel.moveImageDown) self.sidePanel.roiMenu.button1.clicked[()].connect(self.vb.addROI) self.sidePanel.roiMenu.button2.clicked[()].connect(self.vb.addPolyRoiRequest) self.sidePanel.buttRoiCopy.clicked[()].connect(self.vb.copyROI) self.sidePanel.buttRoiRem.clicked.connect(self.vb.removeROI) self.sidePanel.buttRoiLoad.clicked.connect(self.vb.loadROI) self.sidePanel.buttRoiSave.clicked.connect(self.vb.saveROI) self.sidePanel.buttRoiAnalysis.clicked.connect(self.getBMD) self.sidePanel.buttImgAnalysis.clicked.connect(self.imageAnalysis) def onAbout(self): """ About BMDanalyse message""" author ='Michael Hogg' date ='2016' version = self.__version__ QtGui.QMessageBox.about(self, 'About BMDanalyse', """ <b>BMDanalyse</b> <p>A simple program for the analysis of a time series of Bone Mineral Density (BMD) images.</p> <p>Used to evaluate the bone gain / loss in a number of regions of interest (ROIs) over time, typically due to bone remodelling as a result of stress shielding around an orthopaedic implant.</p> <p><table border="0" width="150"> <tr> <td>Author:</td> <td>%s</td> </tr> <tr> <td>Version:</td> <td>%s</td> </tr> <tr> <td>Date:</td> <td>%s</td> </tr> </table></p> """ % (author,version,date)) def loadImages(self): """ Load an image to be analysed """ newImages = {} fileNames = QtGui.QFileDialog.getOpenFileNames(self, self.tr("Load images"),QtCore.QDir.currentPath()) # Fix for PySide. PySide doesn't support QStringList types. PyQt4 getOpenFileNames returns a QStringList, whereas PySide # returns a type (the first entry being the list of filenames). 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!='': img = Image.open(str(fileName)) imgarr = np.array(img.convert('L')) # Convert to 8-bit imgarr = imgarr.swapaxes(0,1) imgarr = imgarr[:,::-1] newImages[fileName] = imgarr # Add filenames to list widget. Only add new filenames. If filename exists aready, then # it will not be added, but data will be updated for fileName in sorted(newImages.keys()): if not self.imageFiles.has_key(fileName): self.sidePanel.addImageToList(fileName) self.imageFiles[fileName] = newImages[fileName] # Show image in Main window self.vb.enableAutoRange() if self.sidePanel.imageFileList.currentRow()==-1: self.sidePanel.imageFileList.setCurrentRow(0) self.showImage(str(self.sidePanel.imageFileList.currentItem().text())) self.vb.disableAutoRange() def removeImage(self): """ Remove image from sidePanel imageFileList """ # Return if there is no image to remove if self.vb.img is None: return # Get current image in sidePanel imageFileList and remove from list currentRow = self.sidePanel.imageFileList.currentRow() image = self.sidePanel.imageFileList.takeItem(currentRow) imageName = str(image.text()) # Delete key and value from dictionary if imageName!='': del self.imageFiles[imageName] # Get image item in imageFileList to replace deleted image if self.sidePanel.imageFileList.count()==0: self.vb.enableAutoRange() self.vb.removeItem(self.vb.img) self.vb.showImage(None) self.vb.disableAutoRange() else: currentRow = self.sidePanel.imageFileList.currentRow() imageName = str(self.sidePanel.imageFileList.item(currentRow).text()) self.showImage(imageName) def showImage(self,imageFilename): """ Shows image in main view """ self.arr = self.imageFiles[imageFilename] self.vb.showImage(self.arr) def getImageToDisplay(self): """ Get current item in file list and display in main view""" try: imageFilename = str(self.sidePanel.imageFileList.currentItem().text()) except: pass else: self.showImage(imageFilename) def getBMD(self): """ Get change in BMD over time (e.g. for each image) for all ROIs. Revised function that converts the list of images into a 3D array and then uses the relative position of the ROIs to the current image, self.vb.img, to get the average BMD value e.g. it doesn't use setImage to change the image in the view. This requires that all images are the same size and in the same position. """ # Return if there is no image or rois in view if self.vb.img is None or len(self.vb.rois)==0: return # Collect all images into a 3D array imageFilenames = self.sidePanel.getListOfImages() images = [self.imageFiles[str(name.text())] for name in imageFilenames] imageData = np.dstack(images) # Doesn't work correctly if images are not all the same shape numImages = len(images) # Get BMD across image stack for each ROI numROIs = len(self.vb.rois) BMD = np.zeros((numImages,numROIs),dtype=float) self.roiNames = [] for i in xrange(numROIs): roi = self.vb.rois[i] self.roiNames.append(roi.name) arrRegion = roi.getArrayRegion(imageData,self.vb.img, axes=(0,1)) avgROIvalue = arrRegion.mean(axis=0).mean(axis=0) BMD[:,i] = avgROIvalue # Calculate the BMD change (percentage of original) tol = 1.0e-06 for i in xrange(numROIs): if abs(BMD[0,i])<tol: BMD[:,i] = 100. else: BMD[:,i] = BMD[:,i] / BMD[0,i] * 100. self.BMDchange = BMD-100. if self.timeData is None or self.timeData.size!=numImages: self.timeData = np.arange(numImages,dtype=float) # Plot results self.showResults() def imageAnalysis(self): # Generate images of BMD change if self.vb.img is None: return self.showImageWin() def sliderValueChanged(self,value): self.imageWin.sliderLabel.setText('BMD change: >= %d %s' % (value,'%')) self.setLookupTable(value) self.imageWin.vb.img2.setLookupTable(self.lut) self.imageWin.vb.img2.setLevels([0,255]) def setLookupTable(self,val): lut = [] for i in range(256): if i > 127+val: lut.append(matplotlib.cm.jet(255)) elif i < 127-val: lut.append(matplotlib.cm.jet(0)) else: lut.append((0.0,0.0,0.0,0.0)) lut = np.array(lut)*255 self.lut = np.array(lut,dtype=np.ubyte) def createImageWin(self): self.buttMinimumSize = QtCore.QSize(70,36) self.iconSize = QtCore.QSize(24,24) if self.imageWin==None: self.imageWin = QtGui.QDialog(self, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint | \ QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) self.imageWin.setWindowTitle('BMDanalyse') self.imageWin.setWindowIcon(self.icons['BMDanalyseIcon']) self.imageWin.setMinimumSize(250,500) self.imageWin.resize(self.imageWin.minimumSize()) # Create viewBox self.imageWin.glw = GraphicsLayoutWidget() # A GraphicsLayout within a GraphicsView self.imageWin.vb = ImageAnalysisViewBox(lockAspect=True,enableMenu=True) self.imageWin.vb.disableAutoRange() self.imageWin.glw.addItem(self.imageWin.vb) arr = self.imageFiles.values()[0] self.imageWin.vb.img1 = ImageItem(arr,autoRange=False,autoLevels=False) self.imageWin.vb.addItem(self.imageWin.vb.img1) self.imageWin.vb.img2 = ImageItem(None,autoRange=False,autoLevels=False) self.imageWin.vb.addItem(self.imageWin.vb.img2) self.imageWin.vb.autoRange() lut = [ [ int(255*val) for val in matplotlib.cm.gray(i)[:3] ] for i in xrange(256) ] lut = np.array(lut,dtype=np.ubyte) self.imageWin.vb.img1.setLookupTable(lut) # Label to show index of current image label self.imageCurrCont = QtGui.QFrame() self.imageCurrCont.setLineWidth(2) self.imageCurrCont.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Raised) self.imageCurrCont.setMinimumWidth(70) self.imageWin.currLabel = QtGui.QLabel("") self.imageWin.currLabel.setAlignment(QtCore.Qt.AlignHCenter) imageCurrContLayout = QtGui.QHBoxLayout() imageCurrContLayout.addWidget(self.imageWin.currLabel) self.imageCurrCont.setLayout(imageCurrContLayout) # Create buttons to select images self.imageWin.buttCont = QtGui.QWidget() self.imageWin.buttPrev = QtGui.QPushButton(self.icons['imagePrevIcon'],"") self.imageWin.buttNext = QtGui.QPushButton(self.icons['imageNextIcon'],"") self.buttLayout = QtGui.QHBoxLayout() self.buttLayout.addStretch(1) self.buttLayout.addWidget(self.imageWin.buttPrev) self.buttLayout.addWidget(self.imageCurrCont) self.buttLayout.addWidget(self.imageWin.buttNext) self.buttLayout.addStretch(1) self.imageWin.buttCont.setLayout(self.buttLayout) self.imageWin.buttPrev.setMinimumSize(self.buttMinimumSize) self.imageWin.buttNext.setMinimumSize(self.buttMinimumSize) self.imageWin.buttPrev.setIconSize(self.iconSize) self.imageWin.buttNext.setIconSize(self.iconSize) self.buttLayout.setContentsMargins(0,5,0,5) self.imageWin.buttPrev.clicked.connect(self.prevImage) self.imageWin.buttNext.clicked.connect(self.nextImage) # Create slider self.imageWin.sliderCon = QtGui.QWidget() self.imageWin.slider = QtGui.QSlider(self) self.imageWin.slider.setOrientation(QtCore.Qt.Horizontal) self.imageWin.slider.setMinimum(1) self.imageWin.slider.setMaximum(100) self.imageWin.slider.setMinimumWidth(100) self.imageWin.slider.valueChanged.connect(self.sliderValueChanged) self.imageWin.sliderLabel = QtGui.QLabel('1') self.imageWin.sliderLabel.setMinimumWidth(120) self.sliderLayout = QtGui.QHBoxLayout() self.sliderLayout.addStretch(1) self.sliderLayout.addWidget(self.imageWin.sliderLabel) self.sliderLayout.addWidget(self.imageWin.slider) self.sliderLayout.addStretch(1) self.imageWin.sliderCon.setLayout(self.sliderLayout) self.sliderLayout.setContentsMargins(0,0,0,5) # Format image window self.imageWinLayout = QtGui.QVBoxLayout() self.imageWinLayout.addWidget(self.imageWin.glw) self.imageWinLayout.addWidget(self.imageWin.buttCont) self.imageWinLayout.addWidget(self.imageWin.sliderCon) self.imageWin.setLayout(self.imageWinLayout) self.imageWin.imagesRGB = None # Show self.imageWin.show() self.imageWin.slider.setValue(10) self.sliderValueChanged(10) self.imageWinIndex = 0 def prevImage(self): minIndex = 0 currIndex = self.imageWinIndex prevIndex = currIndex - 1 self.imageWinIndex = max(prevIndex,minIndex) self.updateImageWin() def nextImage(self): numImages = len(self.imageFiles) maxIndex = numImages - 1 currIndex = self.imageWinIndex nextIndex = currIndex + 1 self.imageWinIndex = min(nextIndex,maxIndex) self.updateImageWin() def updateImageWin(self): imageFilenames = self.sidePanel.getListOfImages() imageName = imageFilenames[self.imageWinIndex] self.imageWin.vb.img1.setImage(self.imageFiles[str(imageName.text())],autoLevels=False) self.imageWin.vb.img2.setImage(self.imageWin.imagesRGB[self.imageWinIndex],autoLevels=False) self.imageWin.currLabel.setText("%i / %i" % (self.imageWinIndex+1,len(imageFilenames))) def showImageWin(self): self.createImageWin() self.imagesBMDpercentChange() self.updateImageWin() def imagesBMDpercentChange(self): # Get image arrays and convert to an array of floats imageFilenames = self.sidePanel.getListOfImages() images = [ self.imageFiles[str(name.text())] for name in imageFilenames ] imagesConv = [] for img in images: image = img.copy() image[np.where(image==0)] = 1 image = image.astype(np.float) imagesConv.append(image) # Calculate percentage change and set with limits -100% to +100% imagesPercCh = [] imageInitial = imagesConv[0] for image in imagesConv: imagePercCh = (image-imageInitial)/imageInitial*100. imagePercCh[np.where(imagePercCh> 100.)] = 100. imagePercCh[np.where(imagePercCh<-100.)] = -100. imagesPercCh.append(imagePercCh) numImages = len(imagesPercCh) self.imageWin.imagesRGB = [] for i in xrange(numImages): image = imagesPercCh[i] sx,sy = image.shape imageRGB = image*(255/200.)+(255/2.) self.imageWin.imagesRGB.append(imageRGB) def BMDtoCSVfile(self): """ Write BMD change to csv file """ fileName = QtGui.QFileDialog.getSaveFileName(None,self.tr("Export to CSV"),QtCore.QDir.currentPath(),self.tr("CSV (*.csv)")) # 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=='': textFile = open(fileName,'w') numFrames, numROIs = self.BMDchange.shape roiNames = self.roiNames header = "%10s," % 'Time' header += ((numROIs-1)*'%10s,'+'%10s\n') % tuple(roiNames) textFile.write(header) for i in xrange(numFrames): textFile.write('%10.1f,' % self.timeData[i]) for j in xrange(numROIs): if j<numROIs-1: fmt = '%10.3f,' else: fmt = '%10.3f\n' textFile.write(fmt % self.BMDchange[i,j]) textFile.close() def showResults(self,): """ Plots BMD change using matplotlib """ # Create plot window if self.plotWin==None: self.plotWin = QtGui.QDialog(self, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint | \ QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) self.plotWin.setWindowTitle('BMDanalyse') self.plotWin.setWindowIcon(self.icons['BMDanalyseIcon']) self.plotWin.setMinimumSize(600,500) self.plotWin.resize(self.minimumSize()) # Create Matplotlib widget self.mplw = MatplotlibWidget(size=(5,6)) self.fig = self.mplw.getFigure() self.editDataButton = QtGui.QPushButton('Edit plot') self.exportCSVButton = QtGui.QPushButton('Export data') self.mplw.toolbar.addWidget(self.editDataButton) self.mplw.toolbar.addWidget(self.exportCSVButton) self.editDataButton.clicked.connect(self.showEditBox) self.exportCSVButton.clicked.connect(self.BMDtoCSVfile) # Format plot window self.plotWinLayout = QtGui.QVBoxLayout() self.plotWinLayout.addWidget(self.mplw) self.plotWin.setLayout(self.plotWinLayout) self.createFigure() self.plotWin.show() self.mplw.draw() def createFigure(self): """ Creates plot of results """ self.ax1 = self.fig.add_subplot(111) self.ax1.clear() self.fig.subplots_adjust(bottom=0.15,top=0.85,left=0.15,right=0.925) numFrames, numROIs = self.BMDchange.shape t = self.timeData # Plot data for i in xrange(numROIs): roiname = self.roiNames[i] self.ax1.plot(t,self.BMDchange[:,i],'-o',label=roiname,linewidth=2.0) kwargs = dict(y=1.05) # Or kwargs = {'y':1.05} self.ax1.set_title('Change in Bone Mineral Density over time',fontsize=14,fontweight='roman',**kwargs) self.ax1.set_xlabel('Time',fontsize=10) self.ax1.set_ylabel('Change in BMD (%)',fontsize=10) self.ax1.legend(loc=0) matplotlib.pyplot.setp(self.ax1.get_xmajorticklabels(), fontsize=10) matplotlib.pyplot.setp(self.ax1.get_ymajorticklabels(), fontsize=10) matplotlib.pyplot.setp(self.ax1.get_legend().get_texts(),fontsize=10) self.ax1.grid() def fillEditBox(self): rows,cols = self.BMDchange.shape for i in xrange(rows): itmValue = '%.2f' % self.timeData[i] itm = QtGui.QTableWidgetItem(itmValue) self.tableResults.setItem(i,0,itm) for j in xrange(cols): itmValue = '%.2f' % self.BMDchange[i,j] itm = QtGui.QTableWidgetItem(itmValue) self.tableResults.setItem(i,j+1,itm) def showEditBox(self): self.plotWin.editBox = QtGui.QDialog(self.plotWin, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) self.plotWin.editBox.setWindowIcon(self.icons['BMDanalyseIcon']) self.plotWin.editBox.setWindowTitle('BMDanalyse') self.plotWin.editBox.setModal(True) # Add table layout = QtGui.QVBoxLayout() layout.setContentsMargins(10,10,10,10) layout.setSpacing(20) rows,cols = self.BMDchange.shape self.tableResults = TableWidget(rows,cols+1,self.plotWin.editBox) self.tableResults.verticalHeader().setVisible(True) # Set headers self.tableResults.setHorizontalHeaderItem(0,QtGui.QTableWidgetItem('Time')) for i in xrange(cols): header = QtGui.QTableWidgetItem(self.roiNames[i]) self.tableResults.setHorizontalHeaderItem(i+1,header) # Add values to table self.fillEditBox() # Set layout layout.addWidget(self.tableResults) self.buttonsFrame = QtGui.QFrame() self.buttonsLayout = QtGui.QHBoxLayout() self.buttonReset = QtGui.QPushButton('Reset') self.buttonSave = QtGui.QPushButton('Save') self.buttonClose = QtGui.QPushButton('Cancel') self.buttonReset.setFixedWidth(50) self.buttonSave.setFixedWidth(50) self.buttonClose.setFixedWidth(50) self.buttonClose.clicked.connect(self.plotWin.editBox.close) self.buttonSave.clicked.connect(self.updateTableValues) self.buttonReset.clicked.connect(self.fillEditBox) self.buttonsLayout.addStretch(1) self.buttonsLayout.addWidget(self.buttonReset) self.buttonsLayout.addWidget(self.buttonSave) self.buttonsLayout.addWidget(self.buttonClose) self.buttonsLayout.setContentsMargins(0,0,0,0) self.buttonsFrame.setLayout(self.buttonsLayout) layout.addWidget(self.buttonsFrame) self.plotWin.editBox.setLayout(layout) self.plotWin.editBox.setMaximumSize(layout.sizeHint()) self.plotWin.editBox.show() def updateTableValues(self): # Create temporary arrays timeData = self.timeData.copy() BMDchange = self.BMDchange.copy() # Put the values from the tables into the temporary arrays rows = self.tableResults.rowCount() cols = self.tableResults.columnCount() for r in xrange(rows): for c in xrange(cols): item = self.tableResults.item(r,c) itemValue = float(item.text()) if c==0: timeData[r] = itemValue else: BMDchange[r,c-1] = itemValue # Check that time values are in increasing order. If so, then update arrays if any(np.diff(timeData)<=0): self.errorMessage = QtGui.QMessageBox() self.errorMessage.setWindowIcon(self.icons['BMDanalyseIcon']) self.errorMessage.setWindowTitle('BMDanalyse') self.errorMessage.setText('Input error: Time values should be in order of increasing value') self.errorMessage.setIcon(QtGui.QMessageBox.Warning) self.errorMessage.open() else: self.timeData = timeData self.BMDchange = BMDchange self.createFigure() self.mplw.draw() self.plotWin.editBox.close()
class MainWindow(QtGui.QMainWindow): def __init__(self, parent=None): QtGui.QMainWindow.__init__(self, parent) self.loadIcons() self.setupUserInterface() self.setupSignals() self.__version__ = __version__ # Initialise variables self.imageFiles = {} self.timeData = None self.plotWin = None self.imageWin = None self.BMDchange = None self.roiNames = None def loadIcons(self): """ Load icons """ self.icons = dict([ ('BMDanalyseIcon', QtGui.QIcon(os.path.join(absDirPath, "icons", "logo.png"))), ('imageAddIcon', QtGui.QIcon(os.path.join(absDirPath, "icons", "file_add.png"))), ('imageRemIcon', QtGui.QIcon(os.path.join(absDirPath, "icons", "file_delete2.png"))), ('imageDownIcon', QtGui.QIcon(os.path.join(absDirPath, "icons", "arrow-up-2.png"))), ('imageUpIcon', QtGui.QIcon(os.path.join(absDirPath, "icons", "arrow-down-2.png"))), ('imagePrevIcon', QtGui.QIcon(os.path.join(absDirPath, "icons", "arrow-left.png"))), ('imageNextIcon', QtGui.QIcon(os.path.join(absDirPath, "icons", "arrow-right.png"))), ('roiAddIcon', QtGui.QIcon(os.path.join(absDirPath, "icons", "green-add3.png"))), ('roiRectIcon', QtGui.QIcon( os.path.join(absDirPath, "icons", "rectangularIcon.png"))), ('roiPolyIcon', QtGui.QIcon(os.path.join(absDirPath, "icons", "polygonIcon.png"))), ('roiRemIcon', QtGui.QIcon(os.path.join(absDirPath, "icons", "red_delete.png"))), ('roiSaveIcon', QtGui.QIcon(os.path.join(absDirPath, "icons", "filesave.png"))), ('roiCopyIcon', QtGui.QIcon(os.path.join(absDirPath, "icons", "file_copy.png"))), ('roiLoadIcon', QtGui.QIcon(os.path.join(absDirPath, "icons", "opened-folder.png"))) ]) def setupUserInterface(self): """ Initialise the User Interface """ # Left frame leftFrame = QtGui.QFrame() leftFrameLayout = QtGui.QHBoxLayout() leftFrame.setLayout(leftFrameLayout) leftFrame.setLineWidth(0) leftFrame.setFrameStyle(QtGui.QFrame.Panel) leftFrameLayout.setContentsMargins(0, 0, 5, 0) # Left frame contents self.viewMain = GraphicsLayoutWidget( ) # A GraphicsLayout within a GraphicsView leftFrameLayout.addWidget(self.viewMain) self.viewMain.setMinimumSize(200, 200) self.vb = MultiRoiViewBox(lockAspect=True, enableMenu=True) self.viewMain.addItem(self.vb) self.vb.disableAutoRange() # Right frame self.sidePanel = SidePanel(self) # UI window (containing left and right frames) UIwindow = QtGui.QWidget(self) UIwindowLayout = QtGui.QHBoxLayout() UIwindowSplitter = QtGui.QSplitter(QtCore.Qt.Horizontal) UIwindowLayout.addWidget(UIwindowSplitter) UIwindow.setLayout(UIwindowLayout) self.setCentralWidget(UIwindow) UIwindowSplitter.addWidget(leftFrame) UIwindowSplitter.addWidget(self.sidePanel) # Application window self.setWindowTitle('BMDanalyse') self.setWindowIcon(self.icons['BMDanalyseIcon']) self.setMinimumSize(600, 500) self.resize(self.minimumSize()) # Window menus self.createMenus() self.createActions() def createMenus(self): # Menus menubar = self.menuBar() self.fileMenu = menubar.addMenu('&File') self.roiMenu = menubar.addMenu('&ROIs') self.submenu = self.roiMenu.addMenu(self.icons['roiAddIcon'], "Add ROI") self.analyseMenu = menubar.addMenu('&Analyse') self.aboutMenu = menubar.addMenu('A&bout') def createActions(self): # Actions for File menu self.loadImageAct = QtGui.QAction(self.icons['imageAddIcon'], "&Load image(s)", self, shortcut="Ctrl+L") self.removeImageAct = QtGui.QAction(self.icons['imageRemIcon'], "&Remove current image", self, shortcut="Ctrl+X") self.exitAct = QtGui.QAction("&Quit", self, shortcut="Ctrl+Q", statusTip="Exit the application") fileMenuActions = [ self.loadImageAct, self.removeImageAct, self.exitAct ] fileMenuActFuncs = [self.loadImages, self.removeImage, self.close] for i in xrange(len(fileMenuActions)): action = fileMenuActions[i] function = fileMenuActFuncs[i] action.triggered[()].connect(function) self.fileMenu.addAction(self.loadImageAct) self.fileMenu.addAction(self.removeImageAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) # Actions for ROI menu # Submenu to add ROIs #self.addROIRectAct = QActionCustom("Rectangular",self.submenu) #self.addROIPolyAct = QActionCustom("Polygon",self.submenu) self.addROIRectAct = QtGui.QAction("Rectangular", self.submenu) self.addROIPolyAct = QtGui.QAction("Polygon", self.submenu) #self.addROIRectAct.clickEvent.connect(self.vb.addROI) #self.addROIPolyAct.clickEvent.connect(self.vb.addPolyRoiRequest) self.addROIRectAct.triggered[()].connect(self.vb.addROI) self.addROIPolyAct.triggered[()].connect(self.vb.addPolyRoiRequest) self.submenu.addAction(self.addROIRectAct) self.submenu.addAction(self.addROIPolyAct) self.addROIRectAct.setIcon(self.icons['roiRectIcon']) self.addROIPolyAct.setIcon(self.icons['roiPolyIcon']) self.addROIRectAct.setShortcut("Ctrl+Shift+R") self.addROIPolyAct.setShortcut("Ctrl+Shift+P") self.loadRoiAct = QtGui.QAction(self.icons['roiLoadIcon'], "L&oad ROI", self, shortcut="Ctrl+O") self.copyRoiAct = QtGui.QAction(self.icons['roiCopyIcon'], "&Copy ROI", self, shortcut="Ctrl+C") self.saveRoiAct = QtGui.QAction(self.icons['roiSaveIcon'], "&Save ROI", self, shortcut="Ctrl+S") self.remRoiAct = QtGui.QAction(self.icons['roiRemIcon'], "&Remove ROI", self, shortcut="Ctrl+D") roiMenuActions = [ self.loadRoiAct, self.copyRoiAct, self.saveRoiAct, self.remRoiAct ] roiMenuActFuncs = [ self.vb.loadROI, self.vb.copyROI, self.vb.saveROI, self.vb.removeROI ] for i in xrange(len(roiMenuActions)): action = roiMenuActions[i] function = roiMenuActFuncs[i] action.triggered[()].connect(function) self.roiMenu.addAction(action) # Actions for Analyse menu self.roiAnalysisAct = QtGui.QAction("&ROI analysis", self.viewMain, shortcut="Ctrl+R", triggered=self.getBMD) self.imgAnalysisAct = QtGui.QAction("&Image analysis", self.viewMain, shortcut="Ctrl+I", triggered=self.imageAnalysis) self.analyseMenu.addAction(self.roiAnalysisAct) self.analyseMenu.addAction(self.imgAnalysisAct) # Actions for self.aboutAct = QtGui.QAction("&About", self.viewMain, shortcut='F1', triggered=self.onAbout) self.aboutMenu.addAction(self.aboutAct) def setupSignals(self): """ Setup signals """ self.sidePanel.imageFileList.itemSelectionChanged.connect( self.getImageToDisplay) self.sidePanel.buttImageAdd.clicked.connect(self.loadImages) self.sidePanel.buttImageRem.clicked.connect(self.removeImage) self.sidePanel.buttImageUp.clicked.connect(self.sidePanel.moveImageUp) self.sidePanel.buttImageDown.clicked.connect( self.sidePanel.moveImageDown) self.sidePanel.roiMenu.button1.clicked[()].connect(self.vb.addROI) self.sidePanel.roiMenu.button2.clicked[()].connect( self.vb.addPolyRoiRequest) self.sidePanel.buttRoiCopy.clicked[()].connect(self.vb.copyROI) self.sidePanel.buttRoiRem.clicked.connect(self.vb.removeROI) self.sidePanel.buttRoiLoad.clicked.connect(self.vb.loadROI) self.sidePanel.buttRoiSave.clicked.connect(self.vb.saveROI) #self.vb.sigROIchanged.connect(self.updateROItools) def onAbout(self): """ About BMDanalyse message""" author = 'Michael Hogg' date = '2012 - 2013' version = self.__version__ QtGui.QMessageBox.about( self, 'About BMDanalyse', """ <b>BMDanalyse</b> <p>A simple program for the analysis of a time series of Bone Mineral Density (BMD) images.</p> <p>Used to evaluate the bone gain / loss in a number of regions of interest (ROIs) over time, typically due to bone remodelling as a result of stress shielding around an orthopaedic implant.</p> <p><table border="0" width="150"> <tr> <td>Author:</td> <td>%s</td> </tr> <tr> <td>Version:</td> <td>%s</td> </tr> <tr> <td>Date:</td> <td>%s</td> </tr> </table></p> """ % (author, version, date)) def updateROItools(self, roi=None): """ Update ROI info box in side panel """ if roi == None: self.sidePanel.updateRoiInfoBox() else: roiState = roi.getState() posx, posy = roiState['pos'] sizex, sizey = roiState['size'] angle = roiState['angle'] name = roi.name pos = '(%.3f, %.3f)' % (posx, posy) size = '(%.3f, %.3f)' % (sizex, sizey) angle = '%.3f' % angle self.sidePanel.updateRoiInfoBox(name, pos, size, angle) def loadImages(self): """ Load an image to be analysed """ newImages = {} fileNames = QtGui.QFileDialog.getOpenFileNames( self, self.tr("Load images"), QtCore.QDir.currentPath()) # Fix for PySide. PySide doesn't support QStringList types. PyQt4 getOpenFileNames returns a QStringList, whereas PySide # returns a type (the first entry being the list of filenames). 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 != '': imgarr = np.array(Image.open(str(fileName))) imgarr = imgarr.swapaxes(0, 1) if imgarr.ndim == 2: imgarr = imgarr[:, ::-1] elif imgarr.ndim == 3: imgarr = imgarr[:, ::-1, :] newImages[fileName] = imgarr # Add filenames to list widget. Only add new filenames. If filename exists aready, then # it will not be added, but data will be updated for fileName in sorted(newImages.keys()): if not self.imageFiles.has_key(fileName): self.sidePanel.addImageToList(fileName) self.imageFiles[fileName] = newImages[fileName] # Show image in Main window self.vb.enableAutoRange() if self.sidePanel.imageFileList.currentRow() == -1: self.sidePanel.imageFileList.setCurrentRow(0) self.showImage( str(self.sidePanel.imageFileList.currentItem().text())) self.vb.disableAutoRange() def removeImage(self): """ Remove image from sidePanel imageFileList """ # Return if there is no image to remove if self.vb.img == None: return # Get current image in sidePanel imageFileList and remove from list currentRow = self.sidePanel.imageFileList.currentRow() image = self.sidePanel.imageFileList.takeItem(currentRow) imageName = str(image.text()) # Delete key and value from dictionary if imageName != '': del self.imageFiles[imageName] #self.imageFiles.pop(imageName,None) # Get image item in imageFileList to replace deleted image if self.sidePanel.imageFileList.count() == 0: self.vb.enableAutoRange() self.vb.removeItem(self.vb.img) self.vb.showImage(None) #self.vb.img = None self.vb.disableAutoRange() else: currentRow = self.sidePanel.imageFileList.currentRow() imageName = str( self.sidePanel.imageFileList.item(currentRow).text()) self.showImage(imageName) def showImage(self, imageFilename): """ Shows image in main view """ self.arr = self.imageFiles[imageFilename] self.vb.showImage(self.arr) def getImageToDisplay(self): """ Get current item in file list and display in main view""" try: imageFilename = str( self.sidePanel.imageFileList.currentItem().text()) except: pass else: self.showImage(imageFilename) def getBMD(self): """ Get change in BMD over time (e.g. for each image) for all ROIs. Revised function that converts the list of images into a 3D array and then uses the relative position of the ROIs to the current image, self.vb.img, to get the average BMD value e.g. it doesn't use setImage to change the image in the view. This requires that all images are the same size and in the same position. """ # Return if there is no image or rois in view if self.vb.img == None or len(self.vb.rois) == 0: return # Collect all images into a 3D array imageFilenames = self.sidePanel.getListOfImages() images = [self.imageFiles[str(name.text())] for name in imageFilenames] imageData = np.dstack(images) numImages = len(images) # Get BMD across image stack for each ROI numROIs = len(self.vb.rois) BMD = np.zeros((numImages, numROIs), dtype=float) self.roiNames = [] for i in xrange(numROIs): roi = self.vb.rois[i] self.roiNames.append(roi.name) arrRegion = roi.getArrayRegion(imageData, self.vb.img, axes=(0, 1)) avgROIvalue = arrRegion.mean(axis=0).mean(axis=0) BMD[:, i] = avgROIvalue # Calculate the BMD change (percentage of original) tol = 1.0e-06 for i in xrange(numROIs): if abs(BMD[0, i]) < tol: BMD[:, i] = 100. else: BMD[:, i] = BMD[:, i] / BMD[0, i] * 100. self.BMDchange = BMD - 100. if self.timeData == None or self.timeData.size != numImages: self.timeData = np.arange(numImages, dtype=float) # Plot results self.showResults() def imageAnalysis(self): # Generate images of BMD change if self.vb.img == None: return self.showImageWin() def sliderValueChanged(self, value): self.imageWin.sliderLabel.setText('BMD change: >= %d %s' % (value, '%')) self.setLookupTable(value) self.imageWin.vb.img2.setLookupTable(self.lut) def setLookupTable(self, val): lut = [] for i in range(256): if i > 127 + val: lut.append(matplotlib.cm.jet(255)) elif i < 127 - val: lut.append(matplotlib.cm.jet(0)) else: lut.append((0.0, 0.0, 0.0, 0.0)) lut = np.array(lut) * 255 self.lut = np.array(lut, dtype=np.ubyte) def createImageWin(self): self.buttMinimumSize = QtCore.QSize(70, 36) self.iconSize = QtCore.QSize(24, 24) if self.imageWin == None: self.imageWin = QtGui.QDialog(self, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint | \ QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) self.imageWin.setWindowTitle('BMDanalyse') self.imageWin.setWindowIcon(self.icons['BMDanalyseIcon']) self.imageWin.setMinimumSize(250, 500) self.imageWin.resize(self.imageWin.minimumSize()) # Create viewBox self.imageWin.glw = GraphicsLayoutWidget( ) # A GraphicsLayout within a GraphicsView self.imageWin.vb = ImageAnalysisViewBox(lockAspect=True, enableMenu=True) self.imageWin.vb.disableAutoRange() self.imageWin.glw.addItem(self.imageWin.vb) arr = self.imageFiles.values()[0] self.imageWin.vb.img1 = pg.ImageItem(arr, autoRange=False, autoLevels=False) self.imageWin.vb.addItem(self.imageWin.vb.img1) self.imageWin.vb.img2 = pg.ImageItem(None, autoRange=False, autoLevels=False) self.imageWin.vb.addItem(self.imageWin.vb.img2) self.imageWin.vb.autoRange() lut = [[int(255 * val) for val in matplotlib.cm.gray(i)[:3]] for i in xrange(256)] lut = np.array(lut, dtype=np.ubyte) self.imageWin.vb.img1.setLookupTable(lut) # Label to show index of current image label self.imageCurrCont = QtGui.QFrame() self.imageCurrCont.setLineWidth(2) self.imageCurrCont.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Raised) self.imageCurrCont.setMinimumWidth(70) self.imageWin.currLabel = QtGui.QLabel("") self.imageWin.currLabel.setAlignment(QtCore.Qt.AlignHCenter) imageCurrContLayout = QtGui.QHBoxLayout() imageCurrContLayout.addWidget(self.imageWin.currLabel) self.imageCurrCont.setLayout(imageCurrContLayout) # Create buttons to select images self.imageWin.buttCont = QtGui.QWidget() self.imageWin.buttPrev = QtGui.QPushButton( self.icons['imagePrevIcon'], "") self.imageWin.buttNext = QtGui.QPushButton( self.icons['imageNextIcon'], "") self.buttLayout = QtGui.QHBoxLayout() self.buttLayout.addStretch(1) self.buttLayout.addWidget(self.imageWin.buttPrev) self.buttLayout.addWidget(self.imageCurrCont) self.buttLayout.addWidget(self.imageWin.buttNext) self.buttLayout.addStretch(1) self.imageWin.buttCont.setLayout(self.buttLayout) self.imageWin.buttPrev.setMinimumSize(self.buttMinimumSize) self.imageWin.buttNext.setMinimumSize(self.buttMinimumSize) self.imageWin.buttPrev.setIconSize(self.iconSize) self.imageWin.buttNext.setIconSize(self.iconSize) self.buttLayout.setContentsMargins(0, 5, 0, 5) self.imageWin.buttPrev.clicked.connect(self.prevImage) self.imageWin.buttNext.clicked.connect(self.nextImage) # Create slider self.imageWin.sliderCon = QtGui.QWidget() self.imageWin.slider = QtGui.QSlider(self) self.imageWin.slider.setOrientation(QtCore.Qt.Horizontal) self.imageWin.slider.setMinimum(1) self.imageWin.slider.setMaximum(100) self.imageWin.slider.setMinimumWidth(100) self.imageWin.slider.valueChanged.connect(self.sliderValueChanged) self.imageWin.sliderLabel = QtGui.QLabel('1') self.imageWin.sliderLabel.setMinimumWidth(120) self.sliderLayout = QtGui.QHBoxLayout() self.sliderLayout.addStretch(1) self.sliderLayout.addWidget(self.imageWin.sliderLabel) self.sliderLayout.addWidget(self.imageWin.slider) self.sliderLayout.addStretch(1) self.imageWin.sliderCon.setLayout(self.sliderLayout) self.sliderLayout.setContentsMargins(0, 0, 0, 5) # Format image window self.imageWinLayout = QtGui.QVBoxLayout() self.imageWinLayout.addWidget(self.imageWin.glw) self.imageWinLayout.addWidget(self.imageWin.buttCont) self.imageWinLayout.addWidget(self.imageWin.sliderCon) self.imageWin.setLayout(self.imageWinLayout) self.imageWin.imagesRGB = None # Show self.imageWin.show() self.imageWin.slider.setValue(10) self.sliderValueChanged(10) self.imageWinIndex = 0 def prevImage(self): #numImages = len(self.imageFiles) minIndex = 0 currIndex = self.imageWinIndex prevIndex = currIndex - 1 self.imageWinIndex = max(prevIndex, minIndex) self.updateImageWin() def nextImage(self): numImages = len(self.imageFiles) maxIndex = numImages - 1 currIndex = self.imageWinIndex nextIndex = currIndex + 1 self.imageWinIndex = min(nextIndex, maxIndex) self.updateImageWin() def updateImageWin(self): imageFilenames = self.sidePanel.getListOfImages() imageName = imageFilenames[self.imageWinIndex] self.imageWin.vb.img1.setImage(self.imageFiles[str(imageName.text())], autoLevels=False) self.imageWin.vb.img2.setImage( self.imageWin.imagesRGB[self.imageWinIndex], autoLevels=False) self.imageWin.currLabel.setText( "%i / %i" % (self.imageWinIndex + 1, len(imageFilenames))) def showImageWin(self): self.createImageWin() #if self.imageWin.imagesRGB == None: self.imagesBMDpercentChange() self.imagesBMDpercentChange() self.updateImageWin() def imagesBMDpercentChange(self): # Get image arrays and convert to an array of floats imageFilenames = self.sidePanel.getListOfImages() images = [self.imageFiles[str(name.text())] for name in imageFilenames] imagesConv = [] for img in images: image = img.copy() image[np.where(image == 0)] = 1 image = image.astype(np.float) imagesConv.append(image) # Calculate percentage change and set with limits -100% to +100% imagesPercCh = [] imageInitial = imagesConv[0] for image in imagesConv: imagePercCh = (image - imageInitial) / imageInitial * 100. imagePercCh[np.where(imagePercCh > 100.)] = 100. imagePercCh[np.where(imagePercCh < -100.)] = -100. imagesPercCh.append(imagePercCh) numImages = len(imagesPercCh) self.imageWin.imagesRGB = [] for i in xrange(numImages): image = imagesPercCh[i] sx, sy = image.shape #imageCh = np.zeros((sx,sy),dtype=np.float) imageRGB = image * (255 / 200.) + (255 / 2.) self.imageWin.imagesRGB.append(imageRGB) def BMDtoCSVfile(self): """ Write BMD change to csv file """ fileName = QtGui.QFileDialog.getSaveFileName(None, self.tr("Export to CSV"), QtCore.QDir.currentPath(), self.tr("CSV (*.csv)")) # 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 not fileName.isEmpty(): textFile = open(fileName, 'w') numFrames, numROIs = self.BMDchange.shape roiNames = self.roiNames header = "%10s," % 'Time' header += ((numROIs - 1) * '%10s,' + '%10s\n') % tuple(roiNames) textFile.write(header) for i in xrange(numFrames): textFile.write('%10.1f,' % self.timeData[i]) for j in xrange(numROIs): if j < numROIs - 1: fmt = '%10.3f,' else: fmt = '%10.3f\n' textFile.write(fmt % self.BMDchange[i, j]) textFile.close() def showResults(self, ): """ Plots BMD change using matplotlib """ # Create plot window if self.plotWin == None: self.plotWin = QtGui.QDialog(self, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint | \ QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint) self.plotWin.setWindowTitle('BMDanalyse') self.plotWin.setWindowIcon(self.icons['BMDanalyseIcon']) self.plotWin.setMinimumSize(600, 500) self.plotWin.resize(self.minimumSize()) # Create Matplotlib widget self.mplw = MatplotlibWidget(size=(5, 6)) self.fig = self.mplw.getFigure() self.editDataButton = QtGui.QPushButton('Edit plot') self.exportCSVButton = QtGui.QPushButton('Export data') self.mplw.toolbar.addWidget(self.editDataButton) self.mplw.toolbar.addWidget(self.exportCSVButton) self.editDataButton.clicked.connect(self.showEditBox) self.exportCSVButton.clicked.connect(self.BMDtoCSVfile) # Format plot window self.plotWinLayout = QtGui.QVBoxLayout() self.plotWinLayout.addWidget(self.mplw) self.plotWin.setLayout(self.plotWinLayout) self.createFigure() self.plotWin.show() self.mplw.draw() def createFigure(self): """ Creates plot of results """ self.ax1 = self.fig.add_subplot(111) self.ax1.clear() self.fig.subplots_adjust(bottom=0.15, top=0.85, left=0.15, right=0.925) numFrames, numROIs = self.BMDchange.shape t = self.timeData # Plot data for i in xrange(numROIs): roiname = self.roiNames[i] self.ax1.plot(t, self.BMDchange[:, i], '-o', label=roiname, linewidth=2.0) kwargs = dict(y=1.05) # Or kwargs = {'y':1.05} self.ax1.set_title('Change in Bone Mineral Density over time', fontsize=14, fontweight='roman', **kwargs) self.ax1.set_xlabel('Time', fontsize=10) self.ax1.set_ylabel('Change in BMD (%)', fontsize=10) self.ax1.legend(loc=0) matplotlib.pyplot.setp(self.ax1.get_xmajorticklabels(), fontsize=10) matplotlib.pyplot.setp(self.ax1.get_ymajorticklabels(), fontsize=10) matplotlib.pyplot.setp(self.ax1.get_legend().get_texts(), fontsize=10) self.ax1.grid() def fillEditBox(self): rows, cols = self.BMDchange.shape for i in xrange(rows): itmValue = '%.2f' % self.timeData[i] itm = QtGui.QTableWidgetItem(itmValue) self.tableResults.setItem(i, 0, itm) for j in xrange(cols): itmValue = '%.2f' % self.BMDchange[i, j] itm = QtGui.QTableWidgetItem(itmValue) self.tableResults.setItem(i, j + 1, itm) def showEditBox(self): self.plotWin.editBox = QtGui.QDialog( self.plotWin, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) self.plotWin.editBox.setWindowIcon(self.icons['BMDanalyseIcon']) self.plotWin.editBox.setWindowTitle('BMDanalyse') self.plotWin.editBox.setModal(True) # Add table layout = QtGui.QVBoxLayout() layout.setContentsMargins(10, 10, 10, 10) layout.setSpacing(20) rows, cols = self.BMDchange.shape self.tableResults = MyTableWidget(rows, cols + 1, self.plotWin.editBox) self.tableResults.verticalHeader().setVisible(True) # Set headers self.tableResults.setHorizontalHeaderItem( 0, QtGui.QTableWidgetItem('Time')) for i in xrange(cols): header = QtGui.QTableWidgetItem(self.roiNames[i]) self.tableResults.setHorizontalHeaderItem(i + 1, header) # Add values to table self.fillEditBox() # Set layout layout.addWidget(self.tableResults) self.buttonsFrame = QtGui.QFrame() self.buttonsLayout = QtGui.QHBoxLayout() self.buttonReset = QtGui.QPushButton('Reset') self.buttonSave = QtGui.QPushButton('Save') self.buttonClose = QtGui.QPushButton('Cancel') self.buttonReset.setFixedWidth(50) self.buttonSave.setFixedWidth(50) self.buttonClose.setFixedWidth(50) self.buttonClose.clicked.connect(self.plotWin.editBox.close) self.buttonSave.clicked.connect(self.updateTableValues) self.buttonReset.clicked.connect(self.fillEditBox) self.buttonsLayout.addStretch(1) self.buttonsLayout.addWidget(self.buttonReset) self.buttonsLayout.addWidget(self.buttonSave) self.buttonsLayout.addWidget(self.buttonClose) self.buttonsLayout.setContentsMargins(0, 0, 0, 0) self.buttonsFrame.setLayout(self.buttonsLayout) layout.addWidget(self.buttonsFrame) self.plotWin.editBox.setLayout(layout) self.plotWin.editBox.setMaximumSize(layout.sizeHint()) self.plotWin.editBox.show() def updateTableValues(self): # Create temporary arrays timeData = self.timeData.copy() BMDchange = self.BMDchange.copy() # Put the values from the tables into the temporary arrays rows = self.tableResults.rowCount() cols = self.tableResults.columnCount() for r in xrange(rows): for c in xrange(cols): item = self.tableResults.item(r, c) itemValue = float(item.text()) if c == 0: timeData[r] = itemValue else: BMDchange[r, c - 1] = itemValue # Check that time values are in increasing order. If so, then update arrays if any(np.diff(timeData) <= 0): self.errorMessage = QtGui.QMessageBox() self.errorMessage.setWindowIcon(self.icons['BMDanalyseIcon']) self.errorMessage.setWindowTitle('BMDanalyse') self.errorMessage.setText( 'Input error: Time values should be in order of increasing value' ) self.errorMessage.setIcon(QtGui.QMessageBox.Warning) self.errorMessage.open() else: self.timeData = timeData self.BMDchange = BMDchange self.createFigure() self.mplw.draw() self.plotWin.editBox.close()