''' This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. Copyright 2015 Michael Woodfin, Shuai Li ''' from ij import IJ, ImageListener, WindowManager, ImagePlus from ij.gui import GenericDialog, ProfilePlot, Plot, Roi, PlotWindow from ij.io import DirectoryChooser from ij.macro import Interpreter from ij.plugin import PlugIn, ChannelSplitter from ij.plugin.filter import Analyzer from java import lang from java.awt import Dimension, BorderLayout, Color, Panel from java.awt.event import ItemListener, ActionListener, WindowAdapter from java.lang import * from javax.swing import DefaultCellEditor, JCheckBox, JLabel, JRadioButton,JTextField from javax.swing import JPanel, JFrame, JTable, JScrollPane, JButton, JComboBox from javax.swing.table import DefaultTableModel, TableCellRenderer from sys import platform as _platform import cPickle as pickle import time class addRegionOKListener(ActionListener): def actionPerformed(self,e): # Get the point the user selected and set it to plotMin in expCont xBoundTitle = mainWindow.dataModel.getXBoundaryImgTitle() xBoundImp = getImpByTitle(xBoundTitle) pixelWidth = xBoundImp.getCalibration().pixelWidth userROI = expCont.userROI roiBound = userROI.getBounds() xVal = getResultTableValue(xBoundTitle,"X") if xVal is None: IJ.error("Point was not found, please try adding the point again.") getAddRegion() else: if xVal/pixelWidth <= (roiBound.getX() + roiBound.getWidth()): IJ.error("Point was not to the right of the ROI, please place the point again.") getAddRegion() else: expCont.xBounds.append(getResultTableValue(xBoundTitle,"X") - roiBound.getX()*pixelWidth) #print "xBounds ", expCont.xBounds # Need to add this region to userROI so that it will be added in the analysis profiles # ROI values are in pixels, converting xBounds[-1] to pixels here newROIWidth = expCont.xBounds[-1]/pixelWidth# - roiBound.getX() newUserROI = Roi(roiBound.getX(),roiBound.getY(),newROIWidth,roiBound.getHeight()) expCont.userROI = newUserROI # Get analysis profiles using the new roi expCont.plotProfList = getAnalysisProfiles(expCont.userROI) getBackground() class backgroundDialogOKListener(ActionListener): def actionPerformed(self,e): # Get the point the user selected and set it to plotMin in expCont xBoundProfTitle = mainWindow.dataModel.getXBoundaryImgTitle() xBoundProfImp = getImpByTitle(xBoundProfTitle) # Get the coordinates of the selected point in the current unit of measure xInUOM = getResultTableValue(xBoundProfTitle,"X") yInUOM = getResultTableValue(xBoundProfTitle,"Y") if xInUOM == 0 and yInUOM == 0: # Indicates no point was selected IJ.error("Either no point was selected or a point with a 0 intensity value was selected,\nplease select a point with a non-zero intensity or click cancel.") getBackgroundInput(xBoundProfImp) else: # Get the coordinates in pixels pixelWidth = xBoundProfImp.getCalibration().pixelWidth xPixel = int(round(xInUOM/pixelWidth)) yPixel = int(round(yInUOM/pixelWidth)) backgroundROI = Roi(xPixel - 1, yPixel - 1,3,3) # 3x3 ROI with user selected point at center xBoundProfImp.setRoi(backgroundROI) expCont.plotMin = getResultTableValue(xBoundProfTitle,"Mean") # average intensity in ROI #print "plotmin ", expCont.plotMin xBoundProfImp.setRoi(expCont.userROI) analysisReady() class displayAveragedResultsOKListener(ActionListener): def actionPerformed(self,e): results = averageResults(expCont.resultList) if results != None: saveAndDisplayResults(expCont.plotNames,expCont.plotColors,results) # By default gets the first value from the results table with the given column name # for the relevant image title. Optional parameter getAll to get all values for that column def getResultTableValue(imgTitle,colName,getAll = None): imgImp = getImpByTitle(imgTitle) imgAnalyzer = Analyzer(imgImp) resultTable = imgAnalyzer.getResultsTable() imgAnalyzer.measure() imgAnalyzer.displayResults() # get column by name in case there are more/less columns than we expect colIndex = resultTable.getColumnIndex(colName) col = resultTable.getColumn(colIndex) # The column wasn't in the table if col == None: closeWindow("Results") return None if getAll is not None: result = col else: result = col[0] # there should only be one point selected closeWindow("Results") return result # This class is a container for various values used by the analysis portion of the program class ExperimentContainer(): def __init__(self): self.xBounds = [] # The xBounds selected by the user, from left to right self.plotLMax = 0 # The x val corresponding to the left max from the analysis plot self.plotRMax = 0 # The x val corresponding to the right max from the analysis plot self.midVal = 0 # The min or max between the left and right neurite stripe (depends on stain type) self.plotMin = 0 # The minimum value between the user selected xBounds # List of all plot profiles selected for analysis + one used for boundary calculations self.plotProfList = [] self.userROI = None # A user defined ROI self.userBounds = [] # Boundaries selected by the user instead of being calculated by the program self.numEqualLayers = 0 # If dividing IPL into equal layers this is the number of those layers self.resultList = [] # list of results used to calculate an average result self.plotHeights = [] # list of results used to calculate a weighted average result self.plotNames = [] self.plotColors = [] # Clear all values set before except for stored results from previous runs def reset(self): self.xBounds = [] self.plotLMax = 0 self.plotRMax = 0 self.midVal = 0 self.plotMin = 0 self.plotProfList = [] self.userROI = None self.userBounds = [] self.numEqualLayers = 0 self.plotNames = [] self.plotColors = [] def clearSavedResults(self): self.resultList = [] self.plotHeights = [] # Return the plot profile with the given title if it exists, if not return None def getProfByTitle(self,title): for profile in self.plotProfList: if profile.imgTitle == title: return profile return None class ImgListener(ImageListener,PlugIn): def imageOpened(self,imp): #IJ.log(imp.getTitle() + " opened") mainWindow.updateTable() # add recently opened image to our table def imageClosed(self,imp): #IJ.log(imp.getTitle() + " closed") mainWindow.updateTable() # remove recently closed images from our table # If there are no longer any open images remove this image listener if WindowManager.getImageCount() == 0: ImagePlus.removeImageListener(self) hasImage() # Throws no image error and exits program class mainWindowListener(WindowAdapter): def windowClosing(self,event): # dispose of image listener when the window closes ImagePlus.removeImageListener(impImgListener) # Call method in superclass WindowAdapter.windowClosing(self,event) # Class used for the image table model, overides methods in DefaultTableModel class OurTblModel(DefaultTableModel): # Override of parent method, allows us to make cells uneditable def isCellEditable(self,row,column): if column>0: return 1 # same as return True, java prefers 1 else: return 0 # Override of parent method, gets the object type in a specific column def getColumnClass(self,column): if column == 3: return lang.Boolean # java language boolean type, gives us a checkbox in col 3 else: return type(self.getValueAt(0,column)) # Returns an array containing the titles of the images that will be analyzed def getAnalysisImgTitles(self): titleArray = [] for i in range(0,self.getRowCount()): if self.getValueAt(i, 3) == True: titleArray.append(self.getValueAt(i,0)) return titleArray # For lack of a better term BoundProf is the plot profile used for layer boundary calculations. def getBoundProfTitle(self): for i in range(0,self.getRowCount()): if self.getValueAt(i, 2) == True: return self.getValueAt(i,0) # Returns title of image used for x Boundary calculations (IPL bounds, additional analysis region point) def getXBoundaryImgTitle(self): for i in range(0,self.getRowCount()): if self.getValueAt(i, 1) == True: return self.getValueAt(i,0) # Checks to see if at least one image has the Analyze box checked def hasAnalysisImage(self): for i in range(0,self.getRowCount()): if self.getValueAt(i, 3) == True: return True return False def hasBoundProfImage(self): for i in range(0,self.getRowCount()): if self.getValueAt(i, 2) == True: return True return False # Checks to see if an image has been selected for xBoundary calculations def hasXBoundaryImage(self): for i in range(0,self.getRowCount()): if self.getValueAt(i, 1) == True: return True return False # Override, allows us to edit radioboxes in table def setValueAt(self, value, row, column): # if setting a radio box if (column == 1 or column == 2) and value == True: # unset all radio boxes in this column for i in range(0,self.getRowCount()): self.setValueAt(False,i,column) # then set this radio box DefaultTableModel.setValueAt(self,value,row,column) else: DefaultTableModel.setValueAt(self,value,row,column) # Handles code relating to our settings pickle class pickleHandler(): def __init__(self): self.pickleFileName = 'IPLaminator_Options.pickle' # Try to open the pickle if it exists try: handle = open(self.pickleFileName,'rb') try: self.pickleDict = pickle.load(handle) finally: handle.close() # If not, create it and set default values in it except: self.pickleDict = {"defDir": "Unset", "subtractBackground": False, "displayResults": False, "addRegion": False, "stripeStainType": "0", "boundCalcMethod": "0", "ResultsMode": "0"} handle = open(self.pickleFileName,'wb') try: pickle.dump(self.pickleDict,handle) finally: handle.close() self.defDir = self.pickleDict.get("defDir") # default output directory # Bool, subtract background value to reduce noise in output self.subtractBackground = self.pickleDict.get("subtractBackground") # Bool, display output histograms self.displayResults = self.pickleDict.get("displayResults") # Bool, add an additional user selected region to be analyzed outside IPL boundaries self.addRegion = self.pickleDict.get("addRegion") # int (stored as string), represents stain type for s2/s4 plot profile # 0 = ChAT (2 max, 1 min), 1 = Calbindin/Calretinin (3 max) self.stripeStainType = int(self.pickleDict.get("stripeStainType")) # int (stored as string), represents method for calculating boundaries # 0 = Use bio makers (ChAT Stripes), 1 = use percentile values, 2 = n equal user defined layers # 3 = user defined layers self.boundCalcMethod = int(self.pickleDict.get("boundCalcMethod")) # int (stored as string), method for outputting results # 0 = default, 1 = average results from separate selections, 2 = weighted average self.ResultsMode = int(self.pickleDict.get("ResultsMode")) def savePickle(self): handle = open(self.pickleFileName,'wb') try: pickle.dump(self.pickleDict,handle) finally: handle.close() def setDefDir(self,directory): self.pickleDict["defDir"] = directory self.defDir = directory self.savePickle() def setSubtractBackground(self,sBBool): self.pickleDict["subtractBackground"] = sBBool self.subtractBackground = sBBool self.savePickle() def setDisplayResults(self,dRBool): self.pickleDict["displayResults"] = dRBool self.displayResults = dRBool self.savePickle() def setAddRegion(self,aRBool): self.pickleDict["addRegion"] = aRBool self.addRegion = aRBool self.savePickle() def setStripeStainType(self,stainType): self.pickleDict["stripeStainType"] = str(stainType) self.stripeStainType = stainType self.savePickle() def setBoundCalcMethod(self,boundType): self.pickleDict["boundCalcMethod"] = str(boundType) self.boundCalcMethod = boundType self.savePickle() def setResultsMode(self,resultMode): self.pickleDict["ResultsMode"] = str(resultMode) self.ResultsMode = resultMode self.savePickle() # A simple class to store a plot profile along with some associated image data class plotProfile(): def __init__(self,Title): self.imgTitle = Title self.imgPath = "" # Path to folder containing image self.xVals = [] self.yVals = [] # A list of lists containing paired x and y values from the plot profile # i.e. [[x1,y1],[x2,y2],....,[xn,yn]] self.plotList = [] def __init__(self,Title,xValList,yValList): self.imgTitle = Title self.imgPath = "" self.xVals = xValList self.yVals = yValList self.plotList = [] self.buildPlotList() def __init__(self,Title,xValList,yValList,imgFPath): self.imgTitle = Title self.imgPath = imgFPath self.xVals = xValList self.yVals = yValList self.plotList = [] self.buildPlotList() # builds the plotList using self.xVals and self.yVals def buildPlotList(self): self.plotList = [] for i in range(0,len(self.xVals)): self.plotList.append([self.xVals[i],self.yVals[i]]) # Returns a pair of values in a list, the first point being the x value of the min or max and # the second point being the y value (intensity) of that min or max. # As paramaters it takes two x values to search between, order doesn't matter, and a string to # indicate whether we are searching for a min or max def getMinMaxBetweenPoints(self,someX,notherX,minOrMax): # Get the index of the x values, someX and notherX may not be in the list of xVals, hence # we need to find the indexes of the x values closest to them # for what this does see http://stackoverflow.com/questions/9706041/finding-index-of-an-item-closest-to-the-value-in-a-list-thats-not-entirely-sort someXIndex = min(range(len(self.xVals)), key=lambda i: abs(self.xVals[i]-someX)) notherXIndex = min(range(len(self.xVals)), key=lambda i: abs(self.xVals[i]-notherX)) leftIndex = min(someXIndex,notherXIndex) rightIndex = max(someXIndex,notherXIndex) if minOrMax == "min": minMaxY = min(self.yVals[leftIndex+1:rightIndex]) else: minMaxY = max(self.yVals[leftIndex+1:rightIndex]) yIndex = self.yVals[leftIndex+1:rightIndex].index(minMaxY) thisX = self.xVals[yIndex+leftIndex+1] return [thisX,minMaxY] # Returns a list containing the x and y values (in that order) of the top n local maximums # from the plot. n=1 gets only the absolute max, n=2 is the two largest maximums, etc. # maxList is a list of these value pairs in order of greatest to least # i.e. n=2 generates maxList = [[someX,maxY],[anotherx,secondLargestY]] # # It is important to remember that the values here are in units of whatever the image scale is, # hence we are working with cm and not pixels if the image units are in cm. def getNLocalMaximums(self,n,maxList = None): if maxList == None: maxList = [] if n==1: maxY = max(self.yVals) # The largest local maximum is also the absolute maximum maxYIndex = self.yVals.index(maxY) thisX = self.xVals[maxYIndex] thisMaxPair = [thisX,maxY] maxList.append(thisMaxPair) return maxList else: maxList = self.getNLocalMaximums(n-1,maxList) #get all maximums larger than the nth one # The maximum Ys we already have, assumes no two local maximums have the same value. maxYVals = [] for pair in maxList: maxYVals.append(pair[1]) # We use self.plotList here so make sure it's built if len(self.plotList)==0: self.buildPlotList() # Begin the process of finding the next largest local maximum thisMax = 0 # The largest local max have found so far (that we don't already have) thisMaxX = 0 # x corresponding to this maximum # To simplify things we don't check the first and last values on the plot profile for i in range (1,len(self.plotList)-1): yVal = self.plotList[i][1] # Check if value is a maximum if yVal > self.plotList[i-1][1] and yVal > self.plotList[i+1][1]: # do we not already have it? if yVal not in maxYVals: # is it the largest one we don't already have? if yVal > thisMax: thisMax = yVal thisMaxX = self.plotList[i][0] #found local max we were looking for, add it to our list maxList.append([thisMaxX,thisMax]) return maxList # Editor class for radio buttons class RadioBtnEditor(DefaultCellEditor,ItemListener): def __init__(self,checkbox): self.radioBtn = JRadioButton() # Initialize superclass DefaultCellEditor.__init__(self,checkbox) # Allows us to render radio buttons in our table class RadioBtnRenderer(TableCellRenderer): def __init__(self): self.radioBtn = JRadioButton() # Overrides method in parent class def getTableCellRendererComponent(self,table,value,isSelected,hasFocus,row,col): if value == False: self.radioBtn.setSelected(False) return self.radioBtn self.radioBtn.setSelected(True) return self.radioBtn # Very similar to addRegionOKListener, this is a dialog listener on the prompt for user defined boundary points class userBoundsOKListener(ActionListener): def actionPerformed(self,e): # Get the points the user selected and save them in expCont pickleHandle = pickleHandler() xBoundTitle = mainWindow.dataModel.getXBoundaryImgTitle() xBoundImp = getImpByTitle(xBoundTitle) pixelWidth = xBoundImp.getCalibration().pixelWidth userROI = expCont.userROI roiBound = userROI.getBounds() # Set to false if an error occurs, keeping us going to the next step with an error boundsFinished = True xValArray = getResultTableValue(xBoundTitle,"X","yes") xValList = [] for element in xValArray: # making python list from java array xValList.append(element) boundList = [] if xValList is None: IJ.error("A point was not found, please try adding the points again.") getUserBounds() boundsFinished = False else: xValList.sort() for xVal in xValList: # points should be inside the IPL if xVal/pixelWidth <= roiBound.getX() or xVal/pixelWidth >= (roiBound.getX() + roiBound.getWidth()): IJ.error("Point found outside the IPL, please try adding the points again.") getUserBounds() boundsFinished = False break else: boundList.append(xVal - roiBound.getX()*pixelWidth) if boundsFinished: #print "boundList ", boundList expCont.userBounds = boundList if pickleHandle.addRegion: getAddRegion() else: getBackground() class MainWindow(JFrame): def __init__(self): super(MainWindow, self).__init__() self.initUI() def initUI(self): self.panel = JPanel() self.getContentPane().add(self.panel) windowXSize = 600 windowYSize = 300 # Image list table self.tableData = [['No Image',False,False,True]] colNames = ('Image','IPL Boundary','S2/S4 Plot Profile','Analyze') self.dataModel = OurTblModel(self.tableData, colNames) self.imgTable = JTable(self.dataModel) # Rendering column 1 and 2 as radio buttons self.imgTable.getColumnModel().getColumn(1).setCellRenderer(RadioBtnRenderer()) self.imgTable.getColumnModel().getColumn(2).setCellRenderer(RadioBtnRenderer()) # Need to set cell editor for radio button column self.imgTable.getColumnModel().getColumn(1).setCellEditor(RadioBtnEditor(JCheckBox())) self.imgTable.getColumnModel().getColumn(2).setCellEditor(RadioBtnEditor(JCheckBox())) # Contains imgTable, allows for scrolling scrollPane = JScrollPane() scrollPane.setPreferredSize(Dimension(windowXSize-20,windowYSize-70)) scrollPane.getViewport().setView((self.imgTable)) self.panel.add(scrollPane) # Analyze button self.analzyeBtn = JButton('Analyze',actionPerformed=analyzeClick) self.analzyeBtn.setPreferredSize(Dimension(windowXSize/3 - 5, 26)); # Settings button self.settingsBtn = JButton('Settings',actionPerformed=settingsClick) self.settingsBtn.setPreferredSize(Dimension(windowXSize/3 - 5, 26)); # Split channel button self.splitChannelBtn = JButton('Split Color Channels',actionPerformed=splitChannelClick) self.splitChannelBtn.setPreferredSize(Dimension(windowXSize/3 - 5, 26)); # Container in bottom of window that holds buttons southContainer = JPanel() southContainer.setLayout(BorderLayout()) southContainer.add(self.settingsBtn,BorderLayout.LINE_START) southContainer.add(self.splitChannelBtn,BorderLayout.CENTER) southContainer.add(self.analzyeBtn,BorderLayout.LINE_END) self.getContentPane().add(southContainer,BorderLayout.PAGE_END) # Sets window properties and displays window self.setTitle("IPLaminator") self.setSize(windowXSize, windowYSize) self.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE) self.setLocationRelativeTo(None) self.setVisible(True) # refreshes table rows and columns def updateTable(self): # Set the correct number of table rows while self.imgTable.getRowCount() < WindowManager.getImageCount(): # add row self.dataModel.addRow(["An error has occured",False,False,False]) while self.imgTable.getRowCount() > WindowManager.getImageCount(): # remove a row self.dataModel.removeRow(0) # populate first column with image names imgList = WindowManager.getImageTitles() i = 0 for imgName in imgList: self.dataModel.setValueAt(imgName,i,0) i+=1 #Update table self.dataModel.fireTableDataChanged() # exits the program, saves no data def exit(self): self.dispose() # The dialog listener for the get image xBoundary dialog class xBoundDialogOKListener(ActionListener): def actionPerformed(self,e): pickleHandle = pickleHandler() expCont.userROI = getXBoundROI() expCont.xBounds = processXBounds(expCont.userROI) expCont.plotProfList = getAnalysisProfiles(expCont.userROI) expCont.plotHeights.append(expCont.userROI.getFloatHeight()) # used when calculating weighted average if pickleHandle.boundCalcMethod == 0: # use biological markers to calculate boundaries getBoundProfVals(expCont.userROI) #expCont values set in this function elif pickleHandle.boundCalcMethod == 2: getNEqualBounds() elif pickleHandle.boundCalcMethod == 3: getUserBounds() elif pickleHandle.addRegion: getAddRegion() else: getBackground() def analyzeClick(event): # Delete values from previous runs expCont.reset() pickleHandle = pickleHandler() if mainWindow.dataModel.hasAnalysisImage(): if mainWindow.dataModel.hasXBoundaryImage(): if mainWindow.dataModel.hasBoundProfImage() or pickleHandle.boundCalcMethod > 0: if pickleHandle.defDir == "Unset": IJ.error("Please open the settings dialog and set a default output directory.") else: getXBoundsInput() else: IJ.error("Please select an image to use for S2/S4 boundary calculations.") else: IJ.error("Please select an image to use for IPL boundary calculations.") else: IJ.error("Please select at least one image to analyze.") # This function is called when we have all the values we need for analysis. It essentially # loops over the doAnalysis function for all images we want to analyze, then shows and saves output. def analysisReady(): pickleHandle = pickleHandler() numPlots = len(expCont.plotProfList) IJ.showStatus("Analyzing images...") i = 0 IJ.showProgress(0, 100) # Analyze all plots marked for analysis, save results in array results = [] # The intensity values corresponding to each expNum for each plot plotColors = [] # Colors for the plot lines plotNames = ["Layer_Depth"] # Names to be shown in the output text file expNums = getExpNums() for plot in expCont.plotProfList: results.append(doAnalysis(expNums,plot)) if "blue" in plot.imgTitle: plotColors.append(Color.blue) elif "red" in plot.imgTitle: plotColors.append(Color.red) elif "green" in plot.imgTitle: plotColors.append(Color.green) else: plotColors.append(Color.black) plotNames.append(plot.imgTitle) i += 1 IJ.showProgress(100*i/numPlots, 100) #save results generated here for average if pickleHandle.ResultsMode > 0: expCont.resultList.append(results) expCont.plotNames = plotNames # Used in the dialog OK listener below expCont.plotColors = plotColors dialogMessage = "Press OK to output averaged results, otherwise click cancel to perform an additional analysis." showDialog("Output averaged results?",dialogMessage,displayAveragedResultsOKListener()) else: saveAndDisplayResults(plotNames,plotColors,results) IJ.showStatus("Analysis complete") # Separate function for when we are averaging results from many runs def saveAndDisplayResults(plotNames,plotColors,results): pickleHandle = pickleHandler() outputPath = pickleHandle.defDir dTstring = time.strftime("%Y_%m_%d_%H_%M") expNums = getExpNums() outputFile("Results_" + dTstring,outputPath,plotNames,results) # display the results histogram if desired if pickleHandle.displayResults: # Show a plot profile containing the data we will output plotUnit = getImpByTitle(expCont.plotProfList[0].imgTitle).getCalibration().getUnits() # Contains plot lines outPlotArray = [] for i,result in enumerate(results): # Not showing the last point in the plot outPlotArray.append([expNums[:-1],result[:-1],plotColors[i]]) if len(results) > 1: # only show combined summary plot for > 1 image showOutputPlot("Results",outPlotArray,plotUnit) # Average the results from separate runs # resultList = [result1,result2,...resultN] # result1 = [result1_Img1,result1_Img2,...result1_ImgN] def averageResults(resultList): pickleHandle = pickleHandler() # Make sure that an equal number of images were analyzed on each run numImagesAnalyzed = len(resultList[0]) for result in resultList: if len(result) != numImagesAnalyzed: IJ.error("It looks like you have analyzed a different number of images on separate runs\n Unfortunately results cannot be saved and you will have to start over.") expCont.clearSavedResults() return None # Make sure that each image analyzed has an equal number of points numPointsPerImage = len(resultList[0][0]) for result in resultList: for imageResult in result: if len(imageResult) != numPointsPerImage: IJ.error("Not all images analyzed have the same number of regions. Results from this run\n cannot be saved.") expCont.clearSavedResults() return None # calculate the average averageResult = [] # The number we divide the sums by when calculating averages if (pickleHandle.ResultsMode == 2):# weighted average plotHeights = expCont.plotHeights totalHeight = sum(plotHeights) divisor = 1.0*totalHeight else: divisor = len(resultList)*1.0 for y in range(0,numImagesAnalyzed): r = [] for z in range(0,numPointsPerImage): value = 0 for x in range(0,len(resultList)): if (pickleHandle.ResultsMode == 2):# weighted average value += resultList[x][y][z]*plotHeights[x] else: value += resultList[x][y][z] r.append(value/divisor) averageResult.append(r) expCont.clearSavedResults() return averageResult def closeWindow(windowTitle): if WindowManager.getFrame(windowTitle)!=None: # Check if window is open IJ.selectWindow(windowTitle) IJ.run("Close") def directoryBtnClick(event): pickleHandle = pickleHandler() dc = DirectoryChooser("Choose the default output directory") dc.setDefaultDirectory(pickleHandle.defDir) defDir = dc.getDirectory() if defDir is None: #User canceled the dialog pass else: pickleHandle.setDefDir(defDir) # Averages intensities at given points along the x axis given by expNumList # Returns an array containing average intensity at each expNum # e.g. [[avgIntensityAtExp1],...,[avgIntensityAtExp12]] def doAnalysis(expNumList,analysisProf): pickleHandle = pickleHandler() Collection = [[0 for x in range(len(analysisProf.plotList))] for x in range(len(expNumList))] for i in range(len(expNumList)): for j in range(len(analysisProf.xVals)): for k in range(1,len(expNumList)): if (expNumList[k-1] <= analysisProf.xVals[j] and analysisProf.xVals[j] <= expNumList[k]): Collection[k-1][j] = analysisProf.yVals[j] Vector = [0 for x in range(len(expNumList))] Ave = [0 for x in range(len(expNumList))] noise = expCont.plotMin for h in range(len(expNumList)): Vector = Collection[h] sumVector = sum(Vector) VecPosLength = sum(m > 0 for m in Vector) # The number of elements greater than zero in the vector if (VecPosLength > 0):# prevents divide by zero error Ave[h] = (sumVector/VecPosLength) - noise # Remove noise from sumVector to make output values more significant else: Ave[h] = 0 if pickleHandle.displayResults and pickleHandle.ResultsMode == 0: # Show a plot profile contianing the data we will output # Not showing the final point plotUnit = getImpByTitle(analysisProf.imgTitle).getCalibration().getUnits() plotArray = [[expNumList[:-1],Ave[:-1]]] showOutputPlot("Output_" + analysisProf.imgTitle,plotArray,plotUnit) return Ave # Get an additional user defined region outside the IPL boundaries for analysis # Must be run after getXBoundsInput because it appends to expCont.xBounds def getAddRegion(): # Get the image that contains the IPL dataModel = mainWindow.dataModel xBoundImgTitle = dataModel.getXBoundaryImgTitle() dialogMessage = ("Please select a point corresponding to the end of the additional analysis region on " + xBoundImgTitle + "\nand click OK when you are done. This region will be added to the already selected IPL region." + "\nIf you are unhappy with the results or want to go back click cancel.") IJ.selectWindow(xBoundImgTitle) IJ.setTool("point") showDialog("User input required - additional analysis region",dialogMessage,addRegionOKListener()) # This function is done for now, we wait for the user to press the OK btn in the # dialogue before and continue program flow in addRegionOKListener def getAnalysisProfiles(userROI): analysisTitles = mainWindow.dataModel.getAnalysisImgTitles() profList = [] for analysisTitle in analysisTitles: analysisImp = getImpByTitle(analysisTitle) profList.append(getPlotProfile(analysisImp,userROI)) return profList # Get minimum intensity in ROI ("the background") so that we may subtract it from the output # as a way to reduce noise. def getBackground(): pickleHandle = pickleHandler() xBoundProfTitle = mainWindow.dataModel.getXBoundaryImgTitle() xBoundProfImp = getImpByTitle(xBoundProfTitle) # Check to see if we want to remove background noise if not pickleHandle.subtractBackground: expCont.plotMin = 0 # Will subtract zero from output in doAnalysis function analysisReady() else: getBackgroundInput(xBoundProfImp) # Prompts the user to choose a background point for reducing noise in the output # backImp is the imageplus where the background will be chosen def getBackgroundInput(backImp): backImgTitle = backImp.getTitle() dialogMessage = ("Please select a point to use for background intensity levels on " + backImgTitle + " and click OK\n" + "when you are done. The background level will be subtracted from all output values to make\n" + "the results more significant. If you do not wish to reduce background noise click cancel.") IJ.selectWindow(backImgTitle) IJ.setTool("point") IJ.run(backImp,"Select None","") #remove any selections showDialog("User input required",dialogMessage,backgroundDialogOKListener()) # This function is done for now, we wait for the user to press the OK btn in the # dialoge before and continue program flow in backgroundDialogOKListener # Set expCont variables that come from the plot profile with the neurite stripes def getBoundProfVals(userROI): boundProfTitle = mainWindow.dataModel.getBoundProfTitle() boundProfImp = getImpByTitle(boundProfTitle) boundProf = getPlotProfile(boundProfImp,userROI) pickleHandle = pickleHandler() pixelWidth = boundProfImp.getCalibration().pixelWidth # Get local max in the left and right halves of the image respectively, this ensures # that we get the left and right stripe and not multiple maxes on a single stripe leftROI = Roi(userROI.getXBase(),userROI.getYBase(),userROI.getFloatWidth()*0.5,userROI.getFloatHeight()) rightROI = Roi(userROI.getXBase() + userROI.getFloatWidth()*0.5,userROI.getYBase(),userROI.getFloatWidth()*0.5,userROI.getFloatHeight()) leftBoundProf = getPlotProfile(boundProfImp,leftROI) rightBoundProf = getPlotProfile(boundProfImp,rightROI) expCont.plotLMax = leftBoundProf.getNLocalMaximums(1)[0][0] # The max from the boundprof is relative to the beginning of the ROI, # rightROI.getXBase() is relative to the image edge expCont.plotRMax = rightROI.getXBase()*pixelWidth + rightBoundProf.getNLocalMaximums(1)[0][0] - userROI.getXBase()*pixelWidth #restore original ROI boundProfImp.setRoi(userROI) # Correctly set the plot midval depending on the stain type if pickleHandle.stripeStainType == 0: # ChAT staining (2 max 1 min), midVal is min between maxes expCont.midVal = boundProf.getMinMaxBetweenPoints(expCont.plotLMax,expCont.plotRMax,"min")[0] else: #Calbindin or calretinin staining (3 max) # Find the stripe between plotLMax and plotRMax regionWidth = expCont.plotRMax - expCont.plotLMax # We look between the 25th and 75th percentile of the region between the two outer stripes expCont.midVal = boundProf.getMinMaxBetweenPoints(expCont.plotLMax + regionWidth*0.25,expCont.plotRMax - regionWidth*0.25,"max")[0] if pickleHandle.addRegion: getAddRegion() else: getBackground() # Returns the widths of the intensity channels for output in the results file def getLayerWidths(expNums): widthList = [] j = 0 for i in range(0,len(expNums) - 1): j += 1 width = expNums[j] - expNums[i] widthList.append(width) widthList.append("")# Last row has no layer width as there is not another layer after it return widthList # Calculate the layer boundaries def getExpNums(): pickleHandle = pickleHandler() expList = [] if pickleHandle.boundCalcMethod == 1: expList = getExpNumsByPercentile() elif pickleHandle.boundCalcMethod == 2: numLayers = expCont.numEqualLayers layerWidth = (expCont.xBounds[1]/numLayers) expList.append(expCont.xBounds[0]) while numLayers > 0: nextLayer = expList[-1] + layerWidth expList.append(nextLayer) numLayers = numLayers - 1 elif pickleHandle.boundCalcMethod == 3: expList.append(expCont.xBounds[0]) for expNum in expCont.userBounds: expList.append(expNum) expList.append(expCont.xBounds[1]) else: leftBound = expCont.xBounds[0] rightBound = expCont.xBounds[1] plotLMax = expCont.plotLMax plotRMax = expCont.plotRMax midVal = expCont.midVal #print "plotLMax, midVal, plotRMax ", plotLMax, " ", midVal, " ", plotRMax expOne = leftBound #expTwo = leftBound + E7 =leftBound + 2(E2-E1)/7 = leftBound + 2(plotLMax-leftBound)/7 expTwo = leftBound + 2*(plotLMax-leftBound)/7 expThree = expTwo + 2*(plotLMax-leftBound)/7 expFour = expThree + 2*(plotLMax-leftBound)/7 #expFive = E2 + E8 = plotLMax + (E3-E2) / 4 = plotLMax + (midVal - plotLMax)/4 expFive = plotLMax + (midVal - plotLMax)/4 expSix = expFive + (midVal - plotLMax)/2 #expSeven = E3 + E9 = midVal + (E4-midVal)/4 = midVal + (plotRMax-midVal)/4 expSeven = midVal + (plotRMax-midVal)/4 expEight = expSeven + 2*(plotRMax-midVal)/4 #expNine = plotRMax + E10 = plotRMax + (E5-E4)/5 = plotRMax + (rightBound - plotRMax)/5 expNine = plotRMax + (rightBound - plotRMax)/5 expTen = expNine + 2*(rightBound - plotRMax)/5 expEleven = rightBound expList = [expOne,expTwo,expThree,expFour,expFive,expSix,expSeven,expEight,expNine,expTen,expEleven] # Add the extra point outside the IPL if it exists if pickleHandle.addRegion: expList.append(expCont.xBounds[-1]) return expList # Gets expNums using a percentile distance across the userROI # e.g. x1 = 0, X2=X1+0.119*userRoiWidth def getExpNumsByPercentile(): percentileList = [0,0.119,0.119,0.119,0.105,0.08,0.081,0.084,0.093,0.1,0.1] pickleHandle = pickleHandler() width = expCont.xBounds[1] - expCont.xBounds[0] expList = [] percent = 0.0 for i in range(0,len(percentileList)): percent += percentileList[i] expNum = width*percent expList.append(expNum) return expList def getImpByTitle(imgTitle): IJ.selectWindow(imgTitle) return IJ.getImage() def getIntensityPercentages(intensityList): sumInt = sum(intensityList) return [100*intensity/sumInt for intensity in intensityList] # Returns a "normalized" list of intensities, here normalized means subtracting 0.99*minIntensity # from all values in intensityList, minIntensity is the smallest non-zero intensity # intensityList is a list of numeric intensities def getNormalizedIntensities(intensityList): minIntensity = min(x for x in intensityList if x > 0.1) normIL = [] for i in range(0,len(intensityList)): if intensityList[i] > 0: normVal = intensityList[i] - minIntensity*0.99 normIL.append(normVal) else: normIL.append(intensityList[i]) return normIL # Takes in an imageplus reference, a user defined ROI, and returns a plotProfile class def getPlotProfile(imageImp,userROI = None): if userROI == None: # No ROI given by user, select entire image imageImp.setRoi(0,0,imageImp.getWidth(),imageImp.getHeight()) else: imageImp.setRoi(userROI.getBounds()) fInfo = imageImp.getOriginalFileInfo() if fInfo == None:# The image is a new image fPath = "" else: fPath = fInfo.directory if (fPath == None): #files not opened locally will not have a local filepath fPath = "" profPlot = ProfilePlot(imageImp) plot = profPlot.getPlot() plotWindow = plot.show() xValsStr = IJ.runMacro("Plot.getValues(x,y);\n"+"xList = \"[\" + toString(x[0]);\n"+"for (i=1; i 0: expCont.numEqualLayers = numLayers if pickleHandle.addRegion: getAddRegion() else: getBackground() else: IJ.error("Error getting input for number of equal layers, please enter an integer greater than zero") getNEqualBounds() # Prompts the user to place points using the multipoint tool indicating boundaries in the IPL def getUserBounds(): # Get the image that contains the IPL dataModel = mainWindow.dataModel xBoundImgTitle = dataModel.getXBoundaryImgTitle() xBoundImp = getImpByTitle(xBoundImgTitle) dialogMessage = ("Please select points corresponding to layer boundaries within the IPL region on " + xBoundImgTitle + "\nand click OK when you are done. If you are unhappy with the results or want to go back click cancel.") IJ.selectWindow(xBoundImgTitle) IJ.setTool("multipoint") IJ.run(xBoundImp,"Select None","") #remove any selections showDialog("User input required - layer boundaries",dialogMessage,userBoundsOKListener()) # This function is done for now, we wait for the user to press the OK btn in the # dialogue before and continue program flow in userBoundsOKListener # gets the user defined region(s) of interest on the x bound image def getXBoundROI(): dataModel = mainWindow.dataModel xBoundImgTitle = dataModel.getXBoundaryImgTitle() xBoundImgPlus = getImpByTitle(xBoundImgTitle) xBoundROI = xBoundImgPlus.getRoi() if xBoundROI == None: # something went wrong, alert the user and stop processing IJ.error("No ROI found, results from this run are invalid, exiting the program.") mainWindow.exit() return xBoundROI # Asks user to input xBoundaries on image selected for xBoundary calculation def getXBoundsInput(): # Get the image used for analysis dataModel = mainWindow.dataModel xBoundImgTitle = dataModel.getXBoundaryImgTitle() dialogMessage = ("Please select a rectangular region of interest on " + xBoundImgTitle + " and click OK when you\nare done. If you are unhappy with the results or want to go back click cancel.") IJ.selectWindow(xBoundImgTitle) IJ.setTool("rectangle") showDialog("User input required - ROI",dialogMessage,xBoundDialogOKListener()) # This function is done for now, we wait for the user to press the OK btn in # xBoundDialogOKListener before we continue in processXBounds() #TODO: handle no ipl roi nicely # Checks to see if there images open in FIJI def hasImage(): openImgCount = WindowManager.getImageCount() if openImgCount == 0: # Displays a no open image message IJ.noImage() return False return True # Output the given data table to the specified path with the specified filename # dataTable = [[intensityForPlotA],...,[intensityForPlotZ]] # elements in dataTable have had background intensity subtracted already # File output should look like this ''' Layer_Width imagename imagename_normalized imageName_minusBackground moreImageColumns Layer_Widths (int, beginning of layer boundary) (intensity) (normalizedIntensity) (intensity - backgroundpoint) (only if reduce background noise is selected) ''' def outputFile(fName,fPath,colNames,dataTable): pickleHandle = pickleHandler() fName += ".txt" fileOutput = open(fPath + fName, 'w' ) expNums = getExpNums() layerWidths = getLayerWidths(expNums) outputData = "" outputDataTable = [] outputColNames = [] # If background subtraction has occured we need to get the raw unmodified intensity # values as well. We call these the raw intensities. This is a workaround that come from changing # requirements and it would be a good candidate for refactoring. rawDataTable = [] for intensityList in dataTable: rawDataTable.append(list(intensityList)) # list() ensures a copy is made if pickleHandle.subtractBackground: # add the background noise value to each value in each intensity list for intensityList in rawDataTable: for i in xrange(0,len(intensityList)): if intensityList[i] != 0: intensityList[i] = intensityList[i] + expCont.plotMin normIntensities = [] for intensityList in rawDataTable: normIntensities.append(getNormalizedIntensities(list(intensityList))) # Get intensity percentages for raw values (no background subtraction, no normalization) rawIntensityPercentages = [] for intensityList in rawDataTable: rawIntensityPercentages.append(getIntensityPercentages(intensityList)) # Get user subtracted background intensity percentages intensityPercentages = [] for intensityList in dataTable: intensityPercentages.append(getIntensityPercentages(intensityList)) # Get normalized intensity percentages normIntensityPercentages = [] for intensityList in normIntensities: normIntensityPercentages.append(getIntensityPercentages(intensityList)) # get layer widths column layerWidths = getLayerWidths(expNums) # build outputDataTable and outputColNames outputColNames.append("Layer_Number") outputColNames.append(colNames[0]) colNames.pop(0) for i in range(0,len(dataTable)): # Add raw intensity value outputColNames.append(colNames[i]) outputDataTable.append(rawDataTable[i]) #Add raw Intensity Percentages outputColNames.append("intensity_%") outputDataTable.append(rawIntensityPercentages[i]) # Add normalized intensity values outputColNames.append(colNames[i] + "_Normalized") outputDataTable.append(normIntensities[i]) #Add normIntensityPercentages outputColNames.append("intensity_%") outputDataTable.append(normIntensityPercentages[i]) if pickleHandle.subtractBackground: # Add intensity minus user selected background noise outputColNames.append(colNames[i] + "_minus_background") outputDataTable.append(dataTable[i]) # Add percentages for above values outputColNames.append("intensity_%") outputDataTable.append(intensityPercentages[i]) outputColNames.append("Layer_Width") outputDataTable.append(layerWidths) for colName in outputColNames: # replace any spaces in the colName with _ as the text file is space delimited outputData += colName.replace(" ","_") outputData += " " outputData += "\n" for i in range(len(outputDataTable[0])): outputData += str(i+1) # Layer number outputData += " " outputData += str(expNums[i]) # layer depth values for j in range(len(outputDataTable)): outputData += " " outputData += str(outputDataTable[j][i]) outputData += "\n" print >>fileOutput, outputData # redirect print fileOutput to outputFile fileOutput.close() print "fin" # Collects xBoundaries that user entered on xBoundary image, returns them as a list def processXBounds(xBoundROI): dataModel = mainWindow.dataModel xBoundImgTitle = dataModel.getXBoundaryImgTitle() xBoundImgPlus = getImpByTitle(xBoundImgTitle) pixelWidth = xBoundImgPlus.getCalibration().pixelWidth # Get left and right xbound from ROI # can get ROI boundaries from it's bounding rectangle boundRect = xBoundROI.getBounds() leftXBound = 0 # Because we are using positions relative to the ROI #boundRect.getX() rightXBoundPixel = boundRect.getWidth() # Convert the pixel values to measurement values rightXBound = rightXBoundPixel*pixelWidth return [leftXBound,rightXBound] def settingsClick(event): result = getSettingsDialog() if result is not None: removeNoise = result[0] displayHistogram = result[1] addRegion = result[2] stripeStainType = result[3] boundCalcMethod = result[4] ResultsMode = result[5] pickleHandle = pickleHandler() if removeNoise is not None: pickleHandle.setSubtractBackground(removeNoise) if displayHistogram is not None: pickleHandle.setDisplayResults(displayHistogram) if addRegion is not None: pickleHandle.setAddRegion(addRegion) if stripeStainType is not None: pickleHandle.setStripeStainType(stripeStainType) if boundCalcMethod is not None: pickleHandle.setBoundCalcMethod(boundCalcMethod) if ResultsMode is not None: pickleHandle.setResultsMode(ResultsMode) # Gets a non-modal dialogue with the specified title, message, and OK button listener def showDialog(title,message,OKListener): dialog = GenericDialog(title) dialog.addMessage(message) dialog.setModal(False)# Allows user to interact with images while dialog is open dialog.showDialog() okBtn = dialog.getButtons()[0] okBtn.addActionListener(OKListener) # Generates a Plot instance using the given data # plotArray = [[xVals,yVals,plotColor],....,[moreXVals,moreYVals,difColor]] def showOutputPlot(plotTitle,plotArray,xAxisLabel): outputPlot = Plot(plotTitle,xAxisLabel,"Intensity") for i, plotObj in enumerate(plotArray): xVals = plotObj[0] yVals = plotObj[1] if i == 0: ymin = min(yVals) ymax = max(yVals) xmin = min(xVals) xmax = max(xVals) if len(plotObj) > 2: plotColor = plotObj[2] else: plotColor = Color.black # update y min and maxes, all x values should be the same if ymin > min(yVals): ymin = min(yVals) if ymax < max(yVals): ymax = max(yVals) outputPlot.setColor(plotColor) outputPlot.addPoints(xVals,yVals,PlotWindow.X) outputPlot.addPoints(xVals,yVals,PlotWindow.LINE) try: outputPlot.setLimits(xmin, xmax, ymin, ymax) outputPlot.show() except: IJ.error("There was a problem generating a histogram, output may have still been saved.\nTo prevent this error run analysis without showing the histogram or adjust\nyour ROI rectangle.") # Splits the xBound image into 3 color channel images def splitChannelClick(event): if mainWindow.dataModel.hasXBoundaryImage(): xBoundTitle = mainWindow.dataModel.getXBoundaryImgTitle() IJ.selectWindow(xBoundTitle) xBoundImp = getImpByTitle(xBoundTitle) colorChannelArray = ChannelSplitter().split(xBoundImp) for imp in colorChannelArray: imp.setTitle(xBoundTitle + " ("+imp.getShortTitle()+")") imp.show() else: IJ.error("An error occured while trying to split the image into separate color channels.\nEnsure you have selected the image you wish to split using the IPL Boundary\nradio button.") def main(): if hasImage(): mainWindow.updateTable()# Builds image table else: mainWindow.exit() # Show main window, visible to all functions mainWindow = MainWindow() mainWindow.addWindowListener(mainWindowListener()) # Listens for window closing expCont = ExperimentContainer() # Listens for opening and closing of image windows impImgListener = ImgListener() ImagePlus().addImageListener(impImgListener) batch = Interpreter()# Speeds up processing with batch mode but may supress errors main()