package ij.plugin.filter; import ij.*; import ij.process.*; import ij.gui.*; import ij.io.*; import ij.plugin.TextReader; import ij.plugin.frame.Recorder; import ij.util.Tools; import java.awt.*; import java.util.*; import java.awt.event.*; import java.io.*; /** This plugin convolves images using user user defined kernels. */ public class Convolver implements ExtendedPlugInFilter, DialogListener, ActionListener { ImagePlus imp; int kw, kh; boolean canceled; float[] kernel; boolean isLineRoi; Button open, save; GenericDialog gd; boolean normalize = true; int nSlices; int flags = DOES_ALL+CONVERT_TO_FLOAT|SUPPORTS_MASKING|PARALLELIZE_STACKS|KEEP_PREVIEW|FINAL_PROCESSING; int nPasses = 1; int pass; boolean kernelError; static String kernelText = "-1 -1 -1 -1 -1\n-1 -1 -1 -1 -1\n-1 -1 24 -1 -1\n-1 -1 -1 -1 -1\n-1 -1 -1 -1 -1\n"; static boolean normalizeFlag = true; public int setup(String arg, ImagePlus imp) { this.imp = imp; if (imp==null) {IJ.noImage(); return DONE;} if (arg.equals("final")&&imp.getRoi()==null) { imp.getProcessor().resetMinAndMax(); imp.updateAndDraw(); return DONE; } IJ.resetEscape(); Roi roi = imp.getRoi(); isLineRoi= roi!=null && roi.isLine(); nSlices = imp.getStackSize(); imp.startTiming(); return flags; } public void run(ImageProcessor ip) { if (canceled) return; if (isLineRoi) ip.resetRoi(); if (!kernelError) convolve(ip, kernel, kw, kh); if (canceled) Undo.undo(); } public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) { gd = new GenericDialog("Convolver...", IJ.getInstance()); gd.addTextAreas(kernelText, null, 10, 30); gd.addPanel(makeButtonPanel(gd)); gd.addCheckbox("Normalize Kernel", normalizeFlag); gd.addPreviewCheckbox(pfr); gd.addDialogListener(this); gd.showDialog(); if (gd.wasCanceled()) return DONE; return IJ.setupDialog(imp, flags); } public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) { kernelText = gd.getNextText(); normalizeFlag = gd.getNextBoolean(); normalize = normalizeFlag; kernelError = !decodeKernel(kernelText); if (!kernelError) { IJ.showStatus("Convolve: "+kw+"x"+kh+" kernel"); return true; } else return !gd.getPreviewCheckbox().getState(); } boolean decodeKernel(String text) { if (Macro.getOptions()!=null && !hasNewLine(text)) return decodeSquareKernel(text); String[] rows = Tools.split(text, "\n"); kh = rows.length; if (kh==0) return false; String[] values = Tools.split(rows[0]); kw = values.length; kernel = new float[kw*kh]; boolean done = gd.wasOKed(); int i = 0; for (int y=0; y=3 && (kw&1)==1) { StringBuffer sb = new StringBuffer(); int i = 0; for (int y=0; yip with a kernel of width kw and height kh. Returns false if the user cancels the operation. */ public boolean convolve(ImageProcessor ip, float[] kernel, int kw, int kh) { if (canceled || kw*kh!=kernel.length) return false; if ((kw&1)!=1 || (kh&1)!=1) throw new IllegalArgumentException("Kernel width or height not odd ("+kw+"x"+kh+")"); boolean notFloat = !(ip instanceof FloatProcessor); ImageProcessor ip2 = ip; if (notFloat) { if (ip2 instanceof ColorProcessor) throw new IllegalArgumentException("RGB images not supported"); ip2 = ip2.convertToFloat(); } if (kw==1 || kh==1) convolveFloat1D(ip2, kernel, kw, kh); else convolveFloat(ip2, kernel, kw, kh); if (notFloat) { if (ip instanceof ByteProcessor) ip2 = ip2.convertToByte(false); else ip2 = ip2.convertToShort(false); ip.setPixels(ip2.getPixels()); } return !canceled; } public void setNormalize(boolean normalizeKernel) { normalize = normalizeKernel; } /** Convolves the float image ip with a kernel of width kw and height kh. Returns false if the user cancels the operation by pressing 'Esc'. */ public boolean convolveFloat(ImageProcessor ip, float[] kernel, int kw, int kh) { if (canceled) return false; int width = ip.getWidth(); int height = ip.getHeight(); Rectangle r = ip.getRoi(); boolean nonRectRoi = ip.getMask()!=null; if (nonRectRoi) ip.snapshot(); int x1 = r.x; int y1 = r.y; int x2 = x1 + r.width; int y2 = y1 + r.height; int uc = kw/2; int vc = kh/2; float[] pixels = (float[])ip.getPixels(); float[] pixels2 = (float[])ip.getPixelsCopy(); double scale = getScale(kernel); pass++; Thread thread = Thread.currentThread(); if (pass>nPasses) pass =1; double sum; int offset, i; boolean edgePixel; int xedge = width-uc; int yedge = height-vc; long lastTime = System.currentTimeMillis(); for(int y=y1; y100) { lastTime = time; if (thread.isInterrupted()) return false; if (IJ.escapePressed()) { IJ.beep(); canceled = true; ip.reset(); return false; } showProgress((y-y1)/(double)(y2-y1)); } for(int x=x1; x=yedge || x=xedge; for(int v=-vc; v <= vc; v++) { offset = x+(y+v)*width; for(int u = -uc; u <= uc; u++) { if (edgePixel) { if (i>=kernel.length) // work around for JIT compiler bug on Linux IJ.log("kernel index error: "+i); sum += getPixel(x+u, y+v, pixels2, width, height)*kernel[i++]; } else sum += pixels2[offset+u]*kernel[i++]; } } pixels[x+y*width] = (float)(sum*scale); } } if (nonRectRoi) ip.reset(ip.getMask()); return true; } /** Convolves the image ip with a kernel of width kw and height kh. */ void convolveFloat1D(ImageProcessor ip, float[] kernel, int kw, int kh) { int width = ip.getWidth(); int height = ip.getHeight(); Rectangle r = ip.getRoi(); int x1 = r.x; int y1 = r.y; int x2 = x1 + r.width; int y2 = y1 + r.height; int uc = kw/2; int vc = kh/2; float[] pixels = (float[])ip.getPixels(); float[] pixels2 = (float[])ip.getPixelsCopy(); double scale = getScale(kernel); boolean vertical = kw==1; double sum; int offset, i; boolean edgePixel; int xedge = width-uc; int yedge = height-vc; for(int y=y1; y=yedge; offset = x+(y-vc)*width; for(int v=-vc; v<=vc; v++) { if (edgePixel) sum += getPixel(x+uc, y+v, pixels2, width, height)*kernel[i++]; else sum += pixels2[offset+uc]*kernel[i++]; offset += width; } } else { edgePixel = x=xedge; offset = x+(y-vc)*width; for(int u = -uc; u<=uc; u++) { if (edgePixel) sum += getPixel(x+u, y+vc, pixels2, width, height)*kernel[i++]; else sum += pixels2[offset+u]*kernel[i++]; } } pixels[x+y*width] = (float)(sum*scale); } } } double getScale(float[] kernel) { double scale = 1.0; if (normalize) { double sum = 0.0; for (int i=0; i=width) x = width-1; if (y<=0) y = 0; if (y>=height) y = height-1; return pixels[x+y*width]; } void save() { TextArea ta1 = gd.getTextArea1(); ta1.selectAll(); String text = ta1.getText(); ta1.select(0, 0); if (text==null || text.length()==0) return; text += "\n"; SaveDialog sd = new SaveDialog("Save as Text...", "kernel", ".txt"); String name = sd.getFileName(); if (name == null) return; String directory = sd.getDirectory(); PrintWriter pw = null; try { FileOutputStream fos = new FileOutputStream(directory+name); BufferedOutputStream bos = new BufferedOutputStream(fos); pw = new PrintWriter(bos); } catch (IOException e) { IJ.error("" + e); return; } IJ.wait(250); // give system time to redraw ImageJ window pw.print(text); pw.close(); } void open() { OpenDialog od = new OpenDialog("Open Kernel...", ""); String directory = od.getDirectory(); String name = od.getFileName(); if (name==null) return; String path = directory + name; TextReader tr = new TextReader(); ImageProcessor ip = tr.open(path); if (ip==null) return; int width = ip.getWidth(); int height = ip.getHeight(); if ((width&1)!=1 || (height&1)!=1) { IJ.error("Convolver", "Kernel must be have odd width and height"); return; } StringBuffer sb = new StringBuffer(); boolean integers = true; for (int y=0; y