/*==================================================================== | Version: May 13, 2015 \===================================================================*/ /*==================================================================== Minimum spanning tree based alignment of 2D image sequence Author: Nilanjan Ray Computing Science University of Alberta Canada \===================================================================*/ /*==================================================================== Description: This program aligns a sequences of 2D images by minimum spanning tree-based image alignment algorithm MISTICA; If you use our program in your work, please cite our relevant papers. To use this program you need to have a sequence of open as a stack in ImageJ. The program will modify the images in the stack. User input parameters: Transformation: Choose "Translation" or "Rigid Body" to get best coarse alignment results. Graph width: If "Exact" option is used for the Method, then use a smaller value here (say, between 1 to 5) Anchor selection: Default "Automatic" option obtains the best result for most cases; For the "Manual" option, the current slice in the stack will be treated as the anchor image to which other images are registered. Method: "Approximate" option is fast. "Exact" option is much slower. Exact method might lead to more accurate results. Add borders: Whether the user wants to increase the image size after transformations. Dependencies: This plugin requires these two packages: (1) turboreg (http://bigwww.epfl.ch/thevenaz/turboreg/) and (2) jgrapht (http://jgrapht.org/) Acknowledgements: The plugin uses some methods taken from the StackReg plugin (http://bigwww.epfl.ch/thevenaz/stackreg/) \===================================================================*/ // ImageJ import ij.IJ; import ij.ImagePlus; import ij.ImageStack; import ij.WindowManager; import ij.gui.GUI; import ij.gui.GenericDialog; import ij.io.FileSaver; import ij.plugin.PlugIn; import ij.process.*; // Java 1.1 import java.awt.BorderLayout; import java.awt.Button; import java.awt.Dialog; import java.awt.FlowLayout; import java.awt.Frame; import java.awt.Insets; import java.awt.Label; import java.awt.Panel; import java.awt.TextArea; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.IndexColorModel; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; // jGraphT import java.net.*; import java.util.*; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.alg.*; import org.jgrapht.traverse.*; // For visualization import javax.swing.JFrame; import org.jgraph.JGraph; import org.jgraph.graph.DefaultGraphCell; import org.jgraph.graph.GraphConstants; import org.jgrapht.ext.JGraphModelAdapter; /*==================================================================== | MSTStackReg_ \===================================================================*/ public class MSTStackReg2_ implements PlugIn { /* class MSTStackReg2_ */ /*.................................................................... private variables ....................................................................*/ private static final double TINY = (double)Float.intBitsToFloat((int)0x33FFFFFF); private int graphWidth = 50; // width of the graph private double[][][][] transformationMatrix = null; SimpleWeightedGraph graph = null; /*.................................................................... PlugIn methods ....................................................................*/ /*------------------------------------------------------------------*/ public void run ( final String arg ) { Runtime.getRuntime().gc(); final ImagePlus imp = WindowManager.getCurrentImage(); if (imp == null) { IJ.error("No image available"); return; } if (imp.getStack().isRGB() || imp.getStack().isHSB()) { IJ.error("Unable to process either RGB or HSB stacks"); return; } GenericDialog gd = new GenericDialog("MSTStackReg"); final String[] transformationItem = { "Translation", "Rigid Body", "Scaled Rotation", "Affine" }; final String[] anchor = { "Automatic", "Manual" }; final String[] method = { "Approximate", "Exact" }; final String[] border = { "No", "Yes" }; gd.addChoice("Transformation:", transformationItem, "Rigid Body"); gd.addNumericField("Graph width:", graphWidth, 0); gd.addChoice("Anchor selection", anchor, "Automatic"); gd.addChoice("Method", method, "Approximate"); gd.addChoice("Add border?", border, "No"); gd.showDialog(); if (gd.wasCanceled()) { return; } final int transformation = gd.getNextChoiceIndex(); graphWidth = (int)gd.getNextNumber(); final int anchorChoice = gd.getNextChoiceIndex(); final int methodChoice = gd.getNextChoiceIndex(); final int borderChoice = gd.getNextChoiceIndex(); int width = imp.getWidth(); int height = imp.getHeight(); int targetSlice = imp.getCurrentSlice(); double[][] anchorPoints = computeAnchorPoints(transformation,width,height); // create a graph with vertices as the images in the stack graph = new SimpleWeightedGraph(DefaultWeightedEdge.class); for(int i=1; i<=imp.getStackSize();i++){ graph.addVertex(i); } // create transformation matrix to hold registration information transformationMatrix = new double[imp.getStackSize()][2*graphWidth][3][3]; // compute transformation, registration error between image pairs and set graph edge weights ImageStack stack = imp.getStack(); double[] colorWeights = null; switch (imp.getType()) { case ImagePlus.COLOR_256: case ImagePlus.COLOR_RGB: { colorWeights = getColorWeightsFromPrincipalComponents(stack,targetSlice); } } ImageStack newStack = null; if(methodChoice==0){ // preprocess stack: most expensive component when blurring is applied newStack = preprocess(imp,stack); } int imageCount=0; for (int i=1; i<=imp.getStackSize();i++){ ImagePlus target = computeTarget(imp, stack, i, colorWeights, width, height); //imp.setSliceWithoutUpdate(i); for (int j=1; j<=graphWidth; j++){ if((i+j)<=imp.getStackSize()){ if(methodChoice==0){ // approximate graph creation; fast and may be inaccurate appoximateGraphCreate(imp, newStack, i, i+j, colorWeights, newStack.getWidth(), newStack.getHeight()); }else{ // exact graph creation; slow, but accurate registerSlice(target, imp, stack, width, height,transformation, anchorPoints, colorWeights, i, i+j, true); } } } //IJ.showProgress(i+1, imp.getStackSize()); imageCount += 1; IJ.showStatus("Building graph: " + IJ.d2s(imageCount,0) + " out of " + IJ.d2s(imp.getStackSize(),0) + " images processed."); } IJ.showStatus("Computing minimum spanning tree..."); // Compute minimum spanning tree KruskalMinimumSpanningTree kmsTree = new KruskalMinimumSpanningTree(graph); //double cost = kmsTree.getMinimumSpanningTreeTotalWeight(); //IJ.showMessage("MSTStackReg_",IJ.d2s(cost,2)); // construct the minimum weighted spanning tree returned by KruskalMinimumSpanningTree UndirectedSubgraph tree = new UndirectedSubgraph(graph, graph.vertexSet(), kmsTree.getMinimumSpanningTreeEdgeSet()); IJ.showStatus("Computing minimum spanning tree...done."); // for visualization of the tree /* JFrame frame = new JFrame(); frame.setSize(400, 400); JGraph jgraph = new JGraph(new JGraphModelAdapter(tree)); frame.getContentPane().add(jgraph); frame.setVisible(true); */ // automatic anchor image computation - it should be half way on the longest path // approximately, the longest path is between the first and the last node if(anchorChoice==0){ IJ.showStatus("Finding out anchor image..."); int len = imp.getStackSize(); DijkstraShortestPath spath = new DijkstraShortestPath(tree,1,len); List list = Graphs.getPathVertexList(spath.getPath()); int mid = list.size(); mid = mid/2; mid = (Integer)list.get(mid); IJ.showStatus("Finding out anchor image...done."); targetSlice = mid; imp.setSlice(targetSlice); } // IJ.showMessage("MSTStackReg_",IJ.d2s(mid,0)); // breadth first tree traversal and some book keeping BreadthFirstIterator bfs = new BreadthFirstIterator(tree, targetSlice); List visited = new ArrayList(); List parents = new ArrayList(); List transformMat = new ArrayList(); int next = bfs.next(); visited.add(next); parents.add(0); double[][] tmtx = {{1.0, 0.0, 0.0},{0.0, 1.0, 0.0},{0.0, 0.0, 1.0}}; transformMat.add(tmtx); imageCount=0; //int leafcnt=0; while(bfs.hasNext()){ next = bfs.next(); visited.add(next); List neighbors = Graphs.neighborListOf(tree, next); Object[] neighborsObj = neighbors.toArray(); int parent=0; for(int i=0; i=101 && next <=200){ if(parent>=101 && parent <=200){ leafcnt +=1; IJ.showMessage("MSTStackReg2_",IJ.d2s(next,0)+' '+IJ.d2s(parent,0)); } } } */ //DefaultWeightedEdge e = tree.getEdge(next,parent); //totcost += tree.getEdgeWeight(e); int k = parent; int l = next; if(methodChoice==0){ // approximate graph ImagePlus target = computeTarget(imp, stack, k, colorWeights, width, height); registerSlice(target, imp, stack, width, height,transformation, anchorPoints, colorWeights, k, l, false); imageCount += 1; IJ.showStatus("Registering stack: " + IJ.d2s(imageCount,0) + " out of " + IJ.d2s(imp.getStackSize(),0) + " images processed."); } int m=-1; if(kwidth) width = maxr; if(maxb>height) height = maxb; IJ.run("Canvas Size...", "width="+width+" height="+height+" position=Top-Left zero"); width += maxl; height += maxt; IJ.run("Canvas Size...", "width="+width+" height="+height+" position=Bottom-Right zero"); // recompute anchor points anchorPoints = computeAnchorPoints(transformation,width,height); } // transform images for(int i=1; imaxr){ maxr = (int)x; }else{ if(-x>maxl) maxl = -(int)x; } if(y>maxb){ maxb = (int)y; }else{ if(-y>maxt) maxt = -(int)y; } // corner 2 x = (width-1)*tmatrix[0][0] - 0*tmatrix[0][1] + tmatrix[0][2]; y = (width-1)*tmatrix[1][0] - 0*tmatrix[1][1] + tmatrix[1][2]; y = -y; if(x>maxr){ maxr = (int)x; }else{ if(-x>maxl) maxl = -(int)x; } if(y>maxb){ maxb = (int)y; }else{ if(-y>maxt) maxt = -(int)y; } // corner 3 x = (width-1)*tmatrix[0][0] - (height-1)*tmatrix[0][1] + tmatrix[0][2]; y = (width-1)*tmatrix[1][0] - (height-1)*tmatrix[1][1] + tmatrix[1][2]; y = -y; if(x>maxr){ maxr = (int)x; }else{ if(-x>maxl) maxl = -(int)x; } if(y>maxb){ maxb = (int)y; }else{ if(-y>maxt) maxt = -(int)y; } // corner 4 x = (0)*tmatrix[0][0] - (height-1)*tmatrix[0][1] + tmatrix[0][2]; y = (0)*tmatrix[1][0] - (height-1)*tmatrix[1][1] + tmatrix[1][2]; y = -y; if(x>maxr){ maxr = (int)x; }else{ if(-x>maxl) maxl = -(int)x; } if(y>maxb){ maxb = (int)y; }else{ if(-y>maxt) maxt = -(int)y; } bbox[0] = maxt; bbox[1] = maxb; bbox[2] = maxr; bbox[3] = maxl; return bbox; } /*-------------------------------------------------------------------*/ private ImagePlus computeTarget(ImagePlus imp, ImageStack stack, int i, double[] colorWeights, int width, int height){ ImagePlus target = null; //imp.setSliceWithoutUpdate(i); switch (imp.getType()) { case ImagePlus.COLOR_256: case ImagePlus.COLOR_RGB: { target = getGray32("StackRegTarget", stack, i, colorWeights); break; } case ImagePlus.GRAY8: { target = new ImagePlus("StackRegTarget", new ByteProcessor(width, height, new byte[width * height], stack.getProcessor(i).getColorModel())); target.getProcessor().copyBits( stack.getProcessor(i), 0, 0, Blitter.COPY); break; } case ImagePlus.GRAY16: { target = new ImagePlus("StackRegTarget", new ShortProcessor(width, height, new short[width * height], stack.getProcessor(i).getColorModel())); target.getProcessor().copyBits( stack.getProcessor(i), 0, 0, Blitter.COPY); break; } case ImagePlus.GRAY32: { target = new ImagePlus("StackRegTarget", new FloatProcessor(width, height, new float[width * height], stack.getProcessor(i).getColorModel())); target.getProcessor().copyBits( stack.getProcessor(i), 0, 0, Blitter.COPY); break; } default: { IJ.error("Unexpected image type"); return null; } } return target; } /*-------------------------------------------------------------------*/ private double[][] computeAnchorPoints(final int transformation, final int width, final int height){ double[][] anchorPoints = null; switch (transformation) { case 0: { anchorPoints = new double[1][3]; anchorPoints[0][0] = (double)(width / 2); anchorPoints[0][1] = (double)(height / 2); anchorPoints[0][2] = 1.0; break; } case 1: { anchorPoints = new double[3][3]; anchorPoints[0][0] = (double)(width / 2); anchorPoints[0][1] = (double)(height / 2); anchorPoints[0][2] = 1.0; anchorPoints[1][0] = (double)(width / 2); anchorPoints[1][1] = (double)(height / 4); anchorPoints[1][2] = 1.0; anchorPoints[2][0] = (double)(width / 2); anchorPoints[2][1] = (double)((3 * height) / 4); anchorPoints[2][2] = 1.0; break; } case 2: { anchorPoints = new double[2][3]; anchorPoints[0][0] = (double)(width / 4); anchorPoints[0][1] = (double)(height / 2); anchorPoints[0][2] = 1.0; anchorPoints[1][0] = (double)((3 * width) / 4); anchorPoints[1][1] = (double)(height / 2); anchorPoints[1][2] = 1.0; break; } case 3: { anchorPoints = new double[3][3]; anchorPoints[0][0] = (double)(width / 2); anchorPoints[0][1] = (double)(height / 4); anchorPoints[0][2] = 1.0; anchorPoints[1][0] = (double)(width / 4); anchorPoints[1][1] = (double)((3 * height) / 4); anchorPoints[1][2] = 1.0; anchorPoints[2][0] = (double)((3 * width) / 4); anchorPoints[2][1] = (double)((3 * height) / 4); anchorPoints[2][2] = 1.0; break; } default: { IJ.error("Unexpected transformation"); return null; } } return anchorPoints; } /*-------------------------------------------------------------------*/ //return C = A * B private double[][] matrixMultiply(double[][] A, double[][] B) { int mA = A.length; int nA = A[0].length; int mB = B.length; int nB = A[0].length; if (nA != mB) throw new RuntimeException("Illegal matrix dimensions."); double[][] C = new double[mA][nB]; for (int i = 0; i < mA; i++) for (int j = 0; j < nB; j++) for (int k = 0; k < nA; k++) C[i][j] += (A[i][k] * B[k][j]); return C; } /*------------------------------------------------------------------*/ private void computeStatistics ( final ImageStack imp, final double[] average, final double[][] scatterMatrix ) { int length = imp.getWidth() * imp.getHeight(); double r; double g; double b; if (imp.getProcessor(1).getPixels() instanceof byte[]) { final IndexColorModel icm = (IndexColorModel)imp.getProcessor(1).getColorModel(); final int mapSize = icm.getMapSize(); final byte[] reds = new byte[mapSize]; final byte[] greens = new byte[mapSize]; final byte[] blues = new byte[mapSize]; icm.getReds(reds); icm.getGreens(greens); icm.getBlues(blues); final double[] histogram = new double[mapSize]; for (int k = 0; (k < mapSize); k++) { histogram[k] = 0.0; } for (int s = 1; (s <= imp.getSize()); s++) { //imp.setSliceWithoutUpdate(s); final byte[] pixels = (byte[])imp.getProcessor(s).getPixels(); for (int k = 0; (k < length); k++) { histogram[pixels[k] & 0xFF]++; } } for (int k = 0; (k < mapSize); k++) { r = (double)(reds[k] & 0xFF); g = (double)(greens[k] & 0xFF); b = (double)(blues[k] & 0xFF); average[0] += histogram[k] * r; average[1] += histogram[k] * g; average[2] += histogram[k] * b; scatterMatrix[0][0] += histogram[k] * r * r; scatterMatrix[0][1] += histogram[k] * r * g; scatterMatrix[0][2] += histogram[k] * r * b; scatterMatrix[1][1] += histogram[k] * g * g; scatterMatrix[1][2] += histogram[k] * g * b; scatterMatrix[2][2] += histogram[k] * b * b; } } else if (imp.getProcessor(1).getPixels() instanceof int[]) { for (int s = 1; (s <= imp.getSize()); s++) { //imp.setSliceWithoutUpdate(s); final int[] pixels = (int[])imp.getProcessor(s).getPixels(); for (int k = 0; (k < length); k++) { r = (double)((pixels[k] & 0x00FF0000) >>> 16); g = (double)((pixels[k] & 0x0000FF00) >>> 8); b = (double)(pixels[k] & 0x000000FF); average[0] += r; average[1] += g; average[2] += b; scatterMatrix[0][0] += r * r; scatterMatrix[0][1] += r * g; scatterMatrix[0][2] += r * b; scatterMatrix[1][1] += g * g; scatterMatrix[1][2] += g * b; scatterMatrix[2][2] += b * b; } } } else { IJ.error("Internal type mismatch"); } length *= imp.getSize(); average[0] /= (double)length; average[1] /= (double)length; average[2] /= (double)length; scatterMatrix[0][0] /= (double)length; scatterMatrix[0][1] /= (double)length; scatterMatrix[0][2] /= (double)length; scatterMatrix[1][1] /= (double)length; scatterMatrix[1][2] /= (double)length; scatterMatrix[2][2] /= (double)length; scatterMatrix[0][0] -= average[0] * average[0]; scatterMatrix[0][1] -= average[0] * average[1]; scatterMatrix[0][2] -= average[0] * average[2]; scatterMatrix[1][1] -= average[1] * average[1]; scatterMatrix[1][2] -= average[1] * average[2]; scatterMatrix[2][2] -= average[2] * average[2]; scatterMatrix[2][1] = scatterMatrix[1][2]; scatterMatrix[2][0] = scatterMatrix[0][2]; scatterMatrix[1][0] = scatterMatrix[0][1]; } /* computeStatistics */ /*------------------------------------------------------------------*/ private double[] getColorWeightsFromPrincipalComponents ( final ImageStack imp, final int slice ) { final double[] average = {0.0, 0.0, 0.0}; final double[][] scatterMatrix = {{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}}; computeStatistics(imp, average, scatterMatrix); double[] eigenvalue = getEigenvalues(scatterMatrix); if ((eigenvalue[0] * eigenvalue[0] + eigenvalue[1] * eigenvalue[1] + eigenvalue[2] * eigenvalue[2]) <= TINY) { return(getLuminanceFromCCIR601()); } double bestEigenvalue = getLargestAbsoluteEigenvalue(eigenvalue); double eigenvector[] = getEigenvector(scatterMatrix, bestEigenvalue); final double weight = eigenvector[0] + eigenvector[1] + eigenvector[2]; if (TINY < Math.abs(weight)) { eigenvector[0] /= weight; eigenvector[1] /= weight; eigenvector[2] /= weight; } return(eigenvector); } /* getColorWeightsFromPrincipalComponents */ /*------------------------------------------------------------------*/ private double[] getEigenvalues ( final double[][] scatterMatrix ) { final double[] a = { scatterMatrix[0][0] * scatterMatrix[1][1] * scatterMatrix[2][2] + 2.0 * scatterMatrix[0][1] * scatterMatrix[1][2] * scatterMatrix[2][0] - scatterMatrix[0][1] * scatterMatrix[0][1] * scatterMatrix[2][2] - scatterMatrix[1][2] * scatterMatrix[1][2] * scatterMatrix[0][0] - scatterMatrix[2][0] * scatterMatrix[2][0] * scatterMatrix[1][1], scatterMatrix[0][1] * scatterMatrix[0][1] + scatterMatrix[1][2] * scatterMatrix[1][2] + scatterMatrix[2][0] * scatterMatrix[2][0] - scatterMatrix[0][0] * scatterMatrix[1][1] - scatterMatrix[1][1] * scatterMatrix[2][2] - scatterMatrix[2][2] * scatterMatrix[0][0], scatterMatrix[0][0] + scatterMatrix[1][1] + scatterMatrix[2][2], -1.0 }; double[] RealRoot = new double[3]; double Q = (3.0 * a[1] - a[2] * a[2] / a[3]) / (9.0 * a[3]); double R = (a[1] * a[2] - 3.0 * a[0] * a[3] - (2.0 / 9.0) * a[2] * a[2] * a[2] / a[3]) / (6.0 * a[3] * a[3]); double Det = Q * Q * Q + R * R; if (Det < 0.0) { Det = 2.0 * Math.sqrt(-Q); R /= Math.sqrt(-Q * Q * Q); R = (1.0 / 3.0) * Math.acos(R); Q = (1.0 / 3.0) * a[2] / a[3]; RealRoot[0] = Det * Math.cos(R) - Q; RealRoot[1] = Det * Math.cos(R + (2.0 / 3.0) * Math.PI) - Q; RealRoot[2] = Det * Math.cos(R + (4.0 / 3.0) * Math.PI) - Q; if (RealRoot[0] < RealRoot[1]) { if (RealRoot[2] < RealRoot[1]) { double Swap = RealRoot[1]; RealRoot[1] = RealRoot[2]; RealRoot[2] = Swap; if (RealRoot[1] < RealRoot[0]) { Swap = RealRoot[0]; RealRoot[0] = RealRoot[1]; RealRoot[1] = Swap; } } } else { double Swap = RealRoot[0]; RealRoot[0] = RealRoot[1]; RealRoot[1] = Swap; if (RealRoot[2] < RealRoot[1]) { Swap = RealRoot[1]; RealRoot[1] = RealRoot[2]; RealRoot[2] = Swap; if (RealRoot[1] < RealRoot[0]) { Swap = RealRoot[0]; RealRoot[0] = RealRoot[1]; RealRoot[1] = Swap; } } } } else if (Det == 0.0) { final double P = 2.0 * ((R < 0.0) ? (Math.pow(-R, 1.0 / 3.0)) : (Math.pow(R, 1.0 / 3.0))); Q = (1.0 / 3.0) * a[2] / a[3]; if (P < 0) { RealRoot[0] = P - Q; RealRoot[1] = -0.5 * P - Q; RealRoot[2] = RealRoot[1]; } else { RealRoot[0] = -0.5 * P - Q; RealRoot[1] = RealRoot[0]; RealRoot[2] = P - Q; } } else { IJ.error("Warning: complex eigenvalue found; ignoring imaginary part."); Det = Math.sqrt(Det); Q = ((R + Det) < 0.0) ? (-Math.exp((1.0 / 3.0) * Math.log(-R - Det))) : (Math.exp((1.0 / 3.0) * Math.log(R + Det))); R = Q + ((R < Det) ? (-Math.exp((1.0 / 3.0) * Math.log(Det - R))) : (Math.exp((1.0 / 3.0) * Math.log(R - Det)))); Q = (-1.0 / 3.0) * a[2] / a[3]; Det = Q + R; RealRoot[0] = Q - R / 2.0; RealRoot[1] = RealRoot[0]; RealRoot[2] = RealRoot[1]; if (Det < RealRoot[0]) { RealRoot[0] = Det; } else { RealRoot[2] = Det; } } return(RealRoot); } /* end getEigenvalues */ /*------------------------------------------------------------------*/ private double[] getEigenvector ( final double[][] scatterMatrix, final double eigenvalue ) { final int n = scatterMatrix.length; final double[][] matrix = new double[n][n]; for (int i = 0; (i < n); i++) { System.arraycopy(scatterMatrix[i], 0, matrix[i], 0, n); matrix[i][i] -= eigenvalue; } final double[] eigenvector = new double[n]; double absMax; double max; double norm; for (int i = 0; (i < n); i++) { norm = 0.0; for (int j = 0; (j < n); j++) { norm += matrix[i][j] * matrix[i][j]; } norm = Math.sqrt(norm); if (TINY < norm) { for (int j = 0; (j < n); j++) { matrix[i][j] /= norm; } } } for (int j = 0; (j < n); j++) { max = matrix[j][j]; absMax = Math.abs(max); int k = j; for (int i = j + 1; (i < n); i++) { if (absMax < Math.abs(matrix[i][j])) { max = matrix[i][j]; absMax = Math.abs(max); k = i; } } if (k != j) { final double[] partialLine = new double[n - j]; System.arraycopy(matrix[j], j, partialLine, 0, n - j); System.arraycopy(matrix[k], j, matrix[j], j, n - j); System.arraycopy(partialLine, 0, matrix[k], j, n - j); } if (TINY < absMax) { for (k = 0; (k < n); k++) { matrix[j][k] /= max; } } for (int i = j + 1; (i < n); i++) { max = matrix[i][j]; for (k = 0; (k < n); k++) { matrix[i][k] -= max * matrix[j][k]; } } } final boolean[] ignore = new boolean[n]; int valid = n; for (int i = 0; (i < n); i++) { ignore[i] = false; if (Math.abs(matrix[i][i]) < TINY) { ignore[i] = true; valid--; eigenvector[i] = 1.0; continue; } if (TINY < Math.abs(matrix[i][i] - 1.0)) { IJ.error("Insufficient accuracy."); eigenvector[0] = 0.212671; eigenvector[1] = 0.71516; eigenvector[2] = 0.072169; return(eigenvector); } norm = 0.0; for (int j = 0; (j < i); j++) { norm += matrix[i][j] * matrix[i][j]; } for (int j = i + 1; (j < n); j++) { norm += matrix[i][j] * matrix[i][j]; } if (Math.sqrt(norm) < TINY) { ignore[i] = true; valid--; eigenvector[i] = 0.0; continue; } } if (0 < valid) { double[][] reducedMatrix = new double[valid][valid]; for (int i = 0, u = 0; (i < n); i++) { if (!ignore[i]) { for (int j = 0, v = 0; (j < n); j++) { if (!ignore[j]) { reducedMatrix[u][v] = matrix[i][j]; v++; } } u++; } } double[] reducedEigenvector = new double[valid]; for (int i = 0, u = 0; (i < n); i++) { if (!ignore[i]) { for (int j = 0; (j < n); j++) { if (ignore[j]) { reducedEigenvector[u] -= matrix[i][j] * eigenvector[j]; } } u++; } } reducedEigenvector = linearLeastSquares(reducedMatrix, reducedEigenvector); for (int i = 0, u = 0; (i < n); i++) { if (!ignore[i]) { eigenvector[i] = reducedEigenvector[u]; u++; } } } norm = 0.0; for (int i = 0; (i < n); i++) { norm += eigenvector[i] * eigenvector[i]; } norm = Math.sqrt(norm); if (Math.sqrt(norm) < TINY) { IJ.error("Insufficient accuracy."); eigenvector[0] = 0.212671; eigenvector[1] = 0.71516; eigenvector[2] = 0.072169; return(eigenvector); } absMax = Math.abs(eigenvector[0]); valid = 0; for (int i = 1; (i < n); i++) { max = Math.abs(eigenvector[i]); if (absMax < max) { absMax = max; valid = i; } } norm = (eigenvector[valid] < 0.0) ? (-norm) : (norm); for (int i = 0; (i < n); i++) { eigenvector[i] /= norm; } return(eigenvector); } /* getEigenvector */ /*------------------------------------------------------------------*/ private ImagePlus getGray32 ( final String title, final ImageStack imp, final int slice, final double[] colorWeights ) { final int length = imp.getWidth() * imp.getHeight(); final ImagePlus gray32 = new ImagePlus(title, new FloatProcessor(imp.getWidth(), imp.getHeight())); final float[] gray = (float[])gray32.getProcessor().getPixels(); double r; double g; double b; if (imp.getProcessor(slice).getPixels() instanceof byte[]) { final byte[] pixels = (byte[])imp.getProcessor(slice).getPixels(); final IndexColorModel icm = (IndexColorModel)imp.getProcessor(slice).getColorModel(); final int mapSize = icm.getMapSize(); final byte[] reds = new byte[mapSize]; final byte[] greens = new byte[mapSize]; final byte[] blues = new byte[mapSize]; icm.getReds(reds); icm.getGreens(greens); icm.getBlues(blues); int index; for (int k = 0; (k < length); k++) { index = (int)(pixels[k] & 0xFF); r = (double)(reds[index] & 0xFF); g = (double)(greens[index] & 0xFF); b = (double)(blues[index] & 0xFF); gray[k] = (float)(colorWeights[0] * r + colorWeights[1] * g + colorWeights[2] * b); } } else if (imp.getProcessor(slice).getPixels() instanceof int[]) { final int[] pixels = (int[])imp.getProcessor(slice).getPixels(); for (int k = 0; (k < length); k++) { r = (double)((pixels[k] & 0x00FF0000) >>> 16); g = (double)((pixels[k] & 0x0000FF00) >>> 8); b = (double)(pixels[k] & 0x000000FF); gray[k] = (float)(colorWeights[0] * r + colorWeights[1] * g + colorWeights[2] * b); } } return(gray32); } /* getGray32 */ /*------------------------------------------------------------------*/ private double getLargestAbsoluteEigenvalue ( final double[] eigenvalue ) { double best = eigenvalue[0]; for (int k = 1; (k < eigenvalue.length); k++) { if (Math.abs(best) < Math.abs(eigenvalue[k])) { best = eigenvalue[k]; } if (Math.abs(best) == Math.abs(eigenvalue[k])) { if (best < eigenvalue[k]) { best = eigenvalue[k]; } } } return(best); } /* getLargestAbsoluteEigenvalue */ /*------------------------------------------------------------------*/ private double[] getLuminanceFromCCIR601 ( ) { double[] weights = {0.299, 0.587, 0.114}; return(weights); } /* getLuminanceFromCCIR601 */ /*------------------------------------------------------------------*/ private double[][] getTransformationMatrix ( final double[][] fromCoord, final double[][] toCoord, final int transformation ) { double[][] matrix = new double[3][3]; switch (transformation) { case 0: { matrix[0][0] = 1.0; matrix[0][1] = 0.0; matrix[0][2] = toCoord[0][0] - fromCoord[0][0]; matrix[1][0] = 0.0; matrix[1][1] = 1.0; matrix[1][2] = toCoord[0][1] - fromCoord[0][1]; break; } case 1: { final double angle = Math.atan2(fromCoord[2][0] - fromCoord[1][0], fromCoord[2][1] - fromCoord[1][1]) - Math.atan2(toCoord[2][0] - toCoord[1][0], toCoord[2][1] - toCoord[1][1]); final double c = Math.cos(angle); final double s = Math.sin(angle); matrix[0][0] = c; matrix[0][1] = -s; matrix[0][2] = toCoord[0][0] - c * fromCoord[0][0] + s * fromCoord[0][1]; matrix[1][0] = s; matrix[1][1] = c; matrix[1][2] = toCoord[0][1] - s * fromCoord[0][0] - c * fromCoord[0][1]; break; } case 2: { double[][] a = new double[3][3]; double[] v = new double[3]; a[0][0] = fromCoord[0][0]; a[0][1] = fromCoord[0][1]; a[0][2] = 1.0; a[1][0] = fromCoord[1][0]; a[1][1] = fromCoord[1][1]; a[1][2] = 1.0; a[2][0] = fromCoord[0][1] - fromCoord[1][1] + fromCoord[1][0]; a[2][1] = fromCoord[1][0] + fromCoord[1][1] - fromCoord[0][0]; a[2][2] = 1.0; invertGauss(a); v[0] = toCoord[0][0]; v[1] = toCoord[1][0]; v[2] = toCoord[0][1] - toCoord[1][1] + toCoord[1][0]; for (int i = 0; (i < 3); i++) { matrix[0][i] = 0.0; for (int j = 0; (j < 3); j++) { matrix[0][i] += a[i][j] * v[j]; } } v[0] = toCoord[0][1]; v[1] = toCoord[1][1]; v[2] = toCoord[1][0] + toCoord[1][1] - toCoord[0][0]; for (int i = 0; (i < 3); i++) { matrix[1][i] = 0.0; for (int j = 0; (j < 3); j++) { matrix[1][i] += a[i][j] * v[j]; } } break; } case 3: { double[][] a = new double[3][3]; double[] v = new double[3]; a[0][0] = fromCoord[0][0]; a[0][1] = fromCoord[0][1]; a[0][2] = 1.0; a[1][0] = fromCoord[1][0]; a[1][1] = fromCoord[1][1]; a[1][2] = 1.0; a[2][0] = fromCoord[2][0]; a[2][1] = fromCoord[2][1]; a[2][2] = 1.0; invertGauss(a); v[0] = toCoord[0][0]; v[1] = toCoord[1][0]; v[2] = toCoord[2][0]; for (int i = 0; (i < 3); i++) { matrix[0][i] = 0.0; for (int j = 0; (j < 3); j++) { matrix[0][i] += a[i][j] * v[j]; } } v[0] = toCoord[0][1]; v[1] = toCoord[1][1]; v[2] = toCoord[2][1]; for (int i = 0; (i < 3); i++) { matrix[1][i] = 0.0; for (int j = 0; (j < 3); j++) { matrix[1][i] += a[i][j] * v[j]; } } break; } default: { IJ.error("Unexpected transformation"); } } matrix[2][0] = 0.0; matrix[2][1] = 0.0; matrix[2][2] = 1.0; return(matrix); } /* end getTransformationMatrix */ /*------------------------------------------------------------------*/ private void invertGauss ( final double[][] matrix ) { final int n = matrix.length; final double[][] inverse = new double[n][n]; for (int i = 0; (i < n); i++) { double max = matrix[i][0]; double absMax = Math.abs(max); for (int j = 0; (j < n); j++) { inverse[i][j] = 0.0; if (absMax < Math.abs(matrix[i][j])) { max = matrix[i][j]; absMax = Math.abs(max); } } inverse[i][i] = 1.0 / max; for (int j = 0; (j < n); j++) { matrix[i][j] /= max; } } for (int j = 0; (j < n); j++) { double max = matrix[j][j]; double absMax = Math.abs(max); int k = j; for (int i = j + 1; (i < n); i++) { if (absMax < Math.abs(matrix[i][j])) { max = matrix[i][j]; absMax = Math.abs(max); k = i; } } if (k != j) { final double[] partialLine = new double[n - j]; final double[] fullLine = new double[n]; System.arraycopy(matrix[j], j, partialLine, 0, n - j); System.arraycopy(matrix[k], j, matrix[j], j, n - j); System.arraycopy(partialLine, 0, matrix[k], j, n - j); System.arraycopy(inverse[j], 0, fullLine, 0, n); System.arraycopy(inverse[k], 0, inverse[j], 0, n); System.arraycopy(fullLine, 0, inverse[k], 0, n); } for (k = 0; (k <= j); k++) { inverse[j][k] /= max; } for (k = j + 1; (k < n); k++) { matrix[j][k] /= max; inverse[j][k] /= max; } for (int i = j + 1; (i < n); i++) { for (k = 0; (k <= j); k++) { inverse[i][k] -= matrix[i][j] * inverse[j][k]; } for (k = j + 1; (k < n); k++) { matrix[i][k] -= matrix[i][j] * matrix[j][k]; inverse[i][k] -= matrix[i][j] * inverse[j][k]; } } } for (int j = n - 1; (1 <= j); j--) { for (int i = j - 1; (0 <= i); i--) { for (int k = 0; (k <= j); k++) { inverse[i][k] -= matrix[i][j] * inverse[j][k]; } for (int k = j + 1; (k < n); k++) { matrix[i][k] -= matrix[i][j] * matrix[j][k]; inverse[i][k] -= matrix[i][j] * inverse[j][k]; } } } for (int i = 0; (i < n); i++) { System.arraycopy(inverse[i], 0, matrix[i], 0, n); } } /* end invertGauss */ /*------------------------------------------------------------------*/ private double[] linearLeastSquares ( final double[][] A, final double[] b ) { final int lines = A.length; final int columns = A[0].length; final double[][] Q = new double[lines][columns]; final double[][] R = new double[columns][columns]; final double[] x = new double[columns]; double s; for (int i = 0; (i < lines); i++) { for (int j = 0; (j < columns); j++) { Q[i][j] = A[i][j]; } } QRdecomposition(Q, R); for (int i = 0; (i < columns); i++) { s = 0.0; for (int j = 0; (j < lines); j++) { s += Q[j][i] * b[j]; } x[i] = s; } for (int i = columns - 1; (0 <= i); i--) { s = R[i][i]; if ((s * s) == 0.0) { x[i] = 0.0; } else { x[i] /= s; } for (int j = i - 1; (0 <= j); j--) { x[j] -= R[j][i] * x[i]; } } return(x); } /* end linearLeastSquares */ /*------------------------------------------------------------------*/ private void QRdecomposition ( final double[][] Q, final double[][] R ) { final int lines = Q.length; final int columns = Q[0].length; final double[][] A = new double[lines][columns]; double s; for (int j = 0; (j < columns); j++) { for (int i = 0; (i < lines); i++) { A[i][j] = Q[i][j]; } for (int k = 0; (k < j); k++) { s = 0.0; for (int i = 0; (i < lines); i++) { s += A[i][j] * Q[i][k]; } for (int i = 0; (i < lines); i++) { Q[i][j] -= s * Q[i][k]; } } s = 0.0; for (int i = 0; (i < lines); i++) { s += Q[i][j] * Q[i][j]; } if ((s * s) == 0.0) { s = 0.0; } else { s = 1.0 / Math.sqrt(s); } for (int i = 0; (i < lines); i++) { Q[i][j] *= s; } } for (int i = 0; (i < columns); i++) { for (int j = 0; (j < i); j++) { R[i][j] = 0.0; } for (int j = i; (j < columns); j++) { R[i][j] = 0.0; for (int k = 0; (k < lines); k++) { R[i][j] += Q[k][i] * A[k][j]; } } } } /* end QRdecomposition */ /*------------------------------------------------------------------*/ private ImageStack preprocess(final ImagePlus imp, ImageStack stack){ // preprocess stack: create a new stack of 1/resizeFactor the width and the height of the input stack final int resizeFactor = 4; final double sigmaGauss = 2; ImageStack stackOut = new ImageStack((int)(stack.getWidth()/resizeFactor),(int)(stack.getHeight()/resizeFactor)); for(int n=1; n<=imp.getStackSize(); n++){ ImageProcessor im = stack.getProcessor(n); ImageProcessor imDuplicate = im.duplicate(); if(sigmaGauss>=0.5) imDuplicate.blurGaussian(sigmaGauss); // Gaussian smoothing filter to suppress noise imDuplicate.setInterpolationMethod(ImageProcessor.BILINEAR); ImageProcessor im1 = imDuplicate.resize((int)(stack.getWidth()/resizeFactor),(int)(stack.getHeight()/resizeFactor)); stackOut.addSlice(im1); IJ.showProgress(n, imp.getStackSize()); IJ.showStatus("Preprocessing: " + IJ.d2s(n,0) + " out of " + IJ.d2s(imp.getStackSize(),0) + " images processed."); } return stackOut; } /*------------------------------------------------------------------*/ // Approximating graph creation by resized image difference private void appoximateGraphCreate(ImagePlus imp, ImageStack stack, int t, int s, double[] colorWeights, int width, int height){ // create edge and add cost to the graph ImagePlus im1 = computeTarget(imp, stack, t, colorWeights, width, height); ImagePlus im2 = computeTarget(imp, stack, s, colorWeights, width, height); double cost = mad(im1, im2); if(t