/******************************************************************************\ * 2D Anisotropic Diffusion Tschumperle-Deriche Filtering Plug-in for ImageJ * * version 0.3, 2006/02/02 * * written by Vladimir Pilny (vladimir@pilny.com) and Jiri Janacek * * based on CImg Library by David Tschumperle (http://cimg.sourceforge.net/) * * GNU GPL2 license * * This plug-in is designed to perform Filtering on an 8-bit, 16-bit * * and RGB images with support for ROI and Stacks. Long processing can be * * stopped with Esc. * \******************************************************************************/ /* importing standard Java API Files and ImageJ packages */ import ij.*; import ij.plugin.filter.PlugInFilter; import ij.process.*; import java.awt.*; import java.io.*; import java.lang.*; import ij.gui.*; import java.util.*; // for time measuring public class Anisotropic_Diffusion_2D implements PlugInFilter { static final String ADTDVersion = "0.3"; static final String ADTDDate = "2006/02/02"; // the following are the input parameters, with default values assigned to them static int nb_iter = 20; // Number of iterations static int nb_smoothings = 1; // Number of smoothings per iteration static double dt = 20.0; // Adapting time step static float a1 = 0.5f; // Diffusion limiter along minimal variations static float a2 = 0.9f; // Diffusion limiter along maximal variations static int save = 20; // Iteration saving step static boolean sstats = false; // display xdt value in each iteration static boolean tstats = false; // measure needed runtime static boolean add_labels = false; // add labels to output stack static float edgeheight = 5; // edge threshold Color label_color; Font font; int font_size = 18; int scount; // number of stacks protected ImagePlus imp, imp2; protected ImageStack stack, stack2; //----------------------------------------------------------------------------------- public int setup(String arg, ImagePlus imp) { if (arg.equals("about")) { showAbout(); return DONE; } this.imp = imp; IJ.resetEscape(); return DOES_16+DOES_8G+DOES_RGB+NO_UNDO; } // end of 'setup' method //----------------------------------------------------------------------------------- void showAbout() { IJ.showMessage("About 2D Anisotropic Diffusion Tschumperle-Deriche", "version "+ADTDVersion+" ("+ADTDDate+")\n"+ "Vladimir Pilny, Jiri Janacek GPL2\n"+ "Based on CImg Library - http://cimg.sourceforge.net/\n\n"+ "This plugin is designed to filter images\n"+ "using 2D Tschumperle-Deriche's anisotropic diffusion."); } // end of 'showAbout' method //----------------------------------------------------------------------------------- private boolean GUI() { GenericDialog gd = new GenericDialog("2D Anisotropic Diffusion Tschumperle-Deriche v"+ADTDVersion); gd.addNumericField("Number of iterations", nb_iter, 0); gd.addNumericField("Smoothings per iteration", nb_smoothings, 0); if (scount==1) { if (save>nb_iter) save = nb_iter; gd.addNumericField("Keep each ", save, 0, 2, "iteration"); } gd.addNumericField("a1 (Diffusion limiter along minimal variations)", a1, 2); gd.addNumericField("a2 (Diffusion limiter along maximal variations)", a2, 2); gd.addNumericField("dt (Time step)", dt, 1); gd.addNumericField("edge threshold height", edgeheight, 1); String[] labels = {"Show_filter stats", "Show_time stats", "Add labels"}; boolean[] values = {sstats, tstats, add_labels}; gd.addCheckboxGroup(2, 2, labels, values); gd.addMessage("Incorrect values will be replaced by defaults.\nLabels are drawn in the foreground color.\nPress Esc to stop processing."); return getUserParams(gd); } //----------------------------------------------------------------------------------- private boolean getUserParams(GenericDialog gd) { gd.showDialog(); // the user presses the Cancel button if (gd.wasCanceled()) return false; nb_iter = (int) gd.getNextNumber(); if (nb_iter<1) nb_iter=1; nb_smoothings = (int) gd.getNextNumber(); if (nb_smoothings<1) nb_smoothings=1; if (scount==1) { save = (int) gd.getNextNumber(); if (save<1 || save>nb_iter) save=nb_iter; } else save = 0; a1 = (float) gd.getNextNumber(); a2 = (float) gd.getNextNumber(); dt = (double) gd.getNextNumber(); edgeheight = (float) gd.getNextNumber(); sstats = (boolean) gd.getNextBoolean(); tstats = (boolean) gd.getNextBoolean(); add_labels = (boolean) gd.getNextBoolean(); return true; } // end of 'getUserParams' method //----------------------------------------------------------------------------------- public void run(ImageProcessor ip) { stack = imp.getStack(); scount = stack.getSize(); if (GUI()) runTD(ip); } // end of 'run' method //----------------------------------------------------------------------------------- public void runTD(ImageProcessor ip) { Date d1, d2; d1 = new Date(); IJ.showStatus("Initializing..."); IJ.showProgress(0.0); if (sstats) IJ.log("\n--- "+imp.getShortTitle()+" --- a1="+a1+", a2 = "+a2+", dt = "+dt); // variables and arrays int channels = ip instanceof ColorProcessor ? 3 : 1; Rectangle r = ip.getRoi(); int width = r.width; int height = r.height; int totalwidth = ip.getWidth(); int totalheight = ip.getHeight(); ImageProcessor ip2; ImageStack stack2 = imp.createEmptyStack(); float[][][][] grad = new float[2][width][height][channels]; float[][][] G = new float[width][height][3]; // must be clean for each slice float[][][] T = new float[width][height][3]; float[][][] veloc = new float[width][height][3]; float val1, val2; float vec1, vec2; double xdt; float[][][] ipf = new float[width][height][channels]; float[][][] ipfo = new float[width][height][channels]; int[] pixel = new int[channels]; float fx, fy; double ipfnew; long[] time = new long[6]; boolean breaked = false; int iter = 0; double average, stddev, drange, drange2, drange0; // consts final float c1 = (float)(0.25*(2-Math.sqrt(2.0))), c2 = (float)(0.5f*(Math.sqrt(2.0)-1)); if (tstats) { d2 = new Date(); time[0]=d2.getTime()-d1.getTime(); } // slices loop for (int s=0; s++ < scount && !breaked; ) { if (tstats) d1 = new Date(); if (scount>1) { ip = stack.getProcessor(s); IJ.showStatus("Slice "+s+"/"+scount+"..."); } // convert image into the float channels for (int x=width; x-->0;) for (int y=height; y-->0;) { pixel = ip.getPixel(x+r.x, y+r.y, pixel); for (int k=channels; k-->0;) { ipf[x][y][k] = pixel[k]; } } // get initial stats for later normalizing average = 0; float initial_max=ipf[0][0][0], initial_min=ipf[0][0][0], pix; for (int x=width; x-->0;) for (int y=height; y-->0;) for (int k=channels; k-->0;) { pix = ipf[x][y][k]; if (pix>initial_max) initial_max=pix; if (pix0;) for (int y=height; y-->0;) for (int k=channels; k-->0;) { pix = ipf[x][y][k]; stddev += (pix-average)*(pix-average); } stddev = Math.sqrt(stddev/(width*height*channels)); //version 0.3 normalization drange= (edgeheight*stddev)/256.0; drange0= (6*stddev)/256.0; drange2= drange * drange; if (tstats) { d2 = new Date(); time[0]+=d2.getTime()-d1.getTime(); } // PDE main iteration loop for (iter=0; (iter < nb_iter) && (!breaked); iter++) { if (scount == 1) IJ.showStatus("Iteration "+(iter+1)+"/"+nb_iter+"..."); // compute gradients if (tstats) d1 = new Date(); float Ipp,Icp,Inp=0,Ipc,Icc,Inc=0,Ipn,Icn,Inn=0; /*/ for (int k=0; k0;) { int px=x-1; if (px<0) px=0; int nx=x+1; if (nx==width) nx--; for (int y=height; y-->0;) { int py=y-1; if (py<0) py=0; int ny=y+1; if (ny==height) ny--; for (int k=channels; k-->0;) { Ipp=ipf[px][py][k]; Ipc=ipf[px][y] [k]; Ipn=ipf[px][ny][k]; Icp=ipf[x] [py][k]; Icn=ipf[x] [ny][k]; Inp=ipf[nx][py][k]; Inc=ipf[nx][y] [k]; Inn=ipf[nx][ny][k]; float IppInn = c1*(Inn-Ipp); float IpnInp = c1*(Ipn-Inp); grad[0][x][y][k] = (float)(IppInn-IpnInp-c2*Ipc+c2*Inc); grad[1][x][y][k] = (float)(IppInn+IpnInp-c2*Icp+c2*Icn); } } } //*/ if (tstats) { d2 = new Date(); time[1]+=d2.getTime()-d1.getTime(); d1 = new Date(); } // compute structure tensor field G // G = new float[width][height][3]; // must be clean for each slice for (int x=width; x-->0;) for (int y=height; y-->0;) { G[x][y][0]= 0.0f; G[x][y][1]= 0.0f; G[x][y][2]= 0.0f; for (int k=channels; k-->0;) { //version 0.2 normalization fx = grad[0][x][y][k]; fy = grad[1][x][y][k]; G[x][y][0] += fx*fx; G[x][y][1] += fx*fy; G[x][y][2] += fy*fy; } } if (tstats) { d2 = new Date(); time[2]+=d2.getTime()-d1.getTime(); d1 = new Date(); } // compute the tensor field T, used to drive the diffusion for (int x=width; x-->0;) for (int y=height; y-->0;) { // eigenvalues: double a = G[x][y][0], b = G[x][y][1], c = G[x][y][1], d = G[x][y][2], e = a+d; double f = Math.sqrt(e*e-4*(a*d-b*c)); double l1 = 0.5*(e-f), l2 = 0.5*(e+f); // more precise computing of quadratic equation if (e>0) { if (l1!=0) l2 = (a*d - b*c)/l1; } else { if (l2!=0) l1 = (a*d - b*c)/l2; } val1=(float)(l2 / drange2); val2=(float)(l1 / drange2); // slight cheat speedup for default a1 value float f1 = (a1==.5) ? (float)(1/Math.sqrt(1.0f+val1+val2)) : (float)(Math.pow(1.0f+val1+val2,-a1)); float f2 = (float)(Math.pow(1.0f+val1+val2,-a2)); // eigenvectors: double u, v, n; if (Math.abs(b)>Math.abs(a-l1)) { u = 1; v = (l1-a)/b; } else { if (a-l1!=0) { u = -b/(a-l1); v = 1; } else { u = 1; v = 0; } } n = Math.sqrt(u*u+v*v); u/=n; v/=n; vec1 = (float)u; vec2 = (float)v; float vec11 = vec1*vec1, vec12 = vec1*vec2, vec22 = vec2*vec2; T[x][y][0] = f1*vec11 + f2*vec22; T[x][y][1] = (f1-f2)*vec12; T[x][y][2] = f1*vec22 + f2*vec11; } if (tstats) { d2 = new Date(); time[3]+=d2.getTime()-d1.getTime(); d1 = new Date(); } xdt= 0.0; // multiple smoothings per iteration for(int sit=0; sit < nb_smoothings && !breaked; sit++) { IJ.showProgress((scount==1) ? (iter+sit/(float)nb_smoothings)/(float)nb_iter : s/(float)scount); // compute the PDE velocity and update the iterated image Inp=Inc=Inn=0; /*/ for (int k=0; k0;) { int px=x-1; if (px<0) px=0; int nx=x+1; if (nx==width) nx--; for (int y=height; y-->0;) { int py=y-1; if (py<0) py=0; int ny=y+1; if (ny==height) ny--; for (int k=channels; k-->0;) { Ipp=ipf[px][py][k]; Ipc=ipf[px][y] [k]; Ipn=ipf[px][ny][k]; Icp=ipf[x] [py][k]; Icc=ipf[x] [y] [k]; Icn=ipf[x] [ny][k]; Inp=ipf[nx][py][k]; Inc=ipf[nx][y] [k]; Inn=ipf[nx][ny][k]; float ixx = Inc+Ipc-2*Icc, iyy = Icn+Icp-2*Icc, ixy = 0.5f*(Ipp+Inn-Ipn-Inp); veloc[x][y][k] = T[x][y][0]*ixx + T[x][y][1]*ixy + T[x][y][2]*iyy; } } } //*/ // find xdt coefficient if (dt>0) { float max=veloc[0][0][0], min=veloc[0][0][0]; for (int x=width; x-->0;) for (int y=height; y-->0;) for (int k=channels; k-->0;) { if (veloc[x][y][k]>max) max=veloc[x][y][k]; if (veloc[x][y][k]0;) for (int y=height; y-->0;) for (int k=channels; k-->0;) { ipfnew = ipf[x][y][k] + veloc[x][y][k]*xdt; ipf[x][y][k] = (float)ipfnew; // normalize image to the original range if (ipf[x][y][k] < initial_min) ipf[x][y][k] = initial_min; if (ipf[x][y][k] > initial_max) ipf[x][y][k] = initial_max; } if (breaked=IJ.escapePressed()) IJ.beep(); } // smoothings per iteration // save result if ((scount==1 && ((save>0 && (iter+1)%save==0) || breaked)) || (iter==nb_iter-1)) // create new image if you break the cycle with Esc, if it is saving step, or if we get below stopping condition { ip2 = ip.createProcessor(totalwidth, totalheight); for (int x=totalwidth; x-->0;) for (int y=totalheight; y-->0;) for (int k=channels; k-->0;) { if ((x=r.x+width) || (y=r.y+height)) ip2.putPixel(x,y,ip.getPixel(x,y)); else { pixel[k] = (int)ipf[x-r.x][y-r.y][k]; ip2.putPixel(x,y,pixel); } } if (scount == 1) { if (add_labels) { if (label_color==null) { label_color = Toolbar.getForegroundColor(); font = new Font("SansSerif", Font.PLAIN, font_size); } ip2.setColor(label_color); ip2.setFont(font); ip2.drawString("iterations: "+(iter+1), 5, (int)(font_size*1.3)); } if (imp2==null) { stack2 = new ImageStack(ip2.getWidth(), ip2.getHeight()); stack2.addSlice("iter"+(iter+1), ip2); imp2 = new ImagePlus(imp.getShortTitle()+"-iter"+(iter+1)+((breaked)?"-interrupted":""), stack2); imp2.show(); } else { stack2.addSlice("iter"+(iter+1), ip2); imp2.setStack(null, stack2); imp2.setSlice(imp2.getStackSize()); imp2.setTitle(imp.getShortTitle()+"-iter"+(iter+1)); } } else stack2.addSlice(stack.getSliceLabel(s), ip2); } if (sstats) IJ.log("\riter "+(iter+1)+", xdt = "+xdt); if (tstats) { d2 = new Date(); time[5]+=d2.getTime()-d1.getTime(); } } // iterations } // slices if (imp2!=null && imp2.getStackSize()>1) { ip2 = imp.getProcessor().duplicate(); if (add_labels) { ip2.setColor(label_color); ip2.setFont(font); ip2.drawString("original", 5, (int)(font_size*1.3)); } stack2.addSlice("orig", ip2, 0); imp2.setStack(null, stack2); imp2.setSlice(imp2.getStackSize()); } IJ.showProgress(1.0); if (scount>1) new ImagePlus(imp.getShortTitle()+"-iter"+nb_iter, stack2).show(); if (tstats) { IJ.log("\r--- time statistics ---"); IJ.log("\rinitialization = "+time[0]+" ms"); IJ.log("\rgradients = "+time[1]+" ms"); IJ.log("\rG tensor field = "+time[2]+" ms"); IJ.log("\rT tensor field = "+time[3]+" ms"); IJ.log("\rvelocity = "+time[4]+" ms"); IJ.log("\rupdate image = "+time[5]+" ms"); } } // end of 'runTD' method } // end of filter Class