I have topographic data (converted to greyscale) from force microscopy that is distorted by drift in a fairly uniform way. The rate of drift is not know, but since the structure has a well-defined geometric structure, I can establish an a priori scan distortion (assuming it is linear).
My question is, how can I apply such a distortion to the data in ImageJ? What I am looking for is the equivalent of the skew transformation in Photoshop. This could probably be done with a line by line shift of the data, but I do not know how to do this. If anyone knows of a plugin which can do this, or could provide me with some direction of how to modify an image line by line, I would appreciate it. Thanks, Lloyd Carroll |
Hi Lloyd,
I have an experimental plugin for shearing; I'll paste it below. It may still have bugs, but usually it works well. You will also need to adjust the y/x scale ratio by Image>Adjust>Size. Remember to set the pixel aspect ratio to 1 after this step. Michael ________________________________________________________________ On 20 Apr 2011, at 06:54, nan0guy wrote: > I have topographic data (converted to greyscale) from force > microscopy that > is distorted by drift in a fairly uniform way. The rate of drift is > not > know, but since the structure has a well-defined geometric > structure, I can > establish an a priori scan distortion (assuming it is linear). > > My question is, how can I apply such a distortion to the data in > ImageJ? > What I am looking for is the equivalent of the skew transformation in > Photoshop. This could probably be done with a line by line shift > of the > data, but I do not know how to do this. > > If anyone knows of a plugin which can do this, or could provide me > with some > direction of how to modify an image line by line, I would > appreciate it. > > Thanks, > > Lloyd Carroll import ij.plugin.filter.ExtendedPlugInFilter; import ij.plugin.filter.PlugInFilterRunner; import ij.*; import ij.gui.*; import ij.process.*; import java.awt.*; import java.awt.geom.*; /** This plugin shears an image in x, i.e., horizontal lines remain horizontal * * Limitaitons: Interpolation between image and background at the edges is bilinear * also at bicubic setting (same for interpolation in a 1-pixel wide edge zone inside * the image; this is a property of ImageJ) * * 2010-05-20 Michael Schmid, first version using ideas from ij.plugin.filter.Rotator * 2010-11-07 Michael Schmid, bugfixes (enlarge of non-float images, etc). * Add interpolation at borders. Enlarge also with partial y range (but full x range). * */ public class Shear_x implements ExtendedPlugInFilter, DialogListener { private int flags = DOES_ALL|SUPPORTS_MASKING|PARALLELIZE_STACKS; private static double angle = 0; private static boolean fillWithBackground; private static boolean enlarge; private static double gridSpacing = 50; private static double gridAngle = 90; private ImagePlus imp; private Roi originalRoi; //private int bitDepth; private boolean canEnlarge; private boolean isEnlarged; private GenericDialog gd; private PlugInFilterRunner pfr; private String[] methods = ImageProcessor.getInterpolationMethods (); private static int interpolationMethod = ImageProcessor.BILINEAR; private final static String[] aligns = new String[] {"top", "bottom"}; private final static int TOP=0, BOTTOM=1; private static int align = TOP; public int setup(String arg, ImagePlus imp) { this.imp = imp; if (imp!=null) { originalRoi = imp.getRoi(); if (originalRoi!=null && !originalRoi.isArea()) originalRoi = null; Rectangle r = originalRoi!=null ? originalRoi.getBounds () : null; canEnlarge = r==null || (originalRoi.getType()==Roi.RECTANGLE && r.x==0 && r.width==imp.getWidth()); } return flags; } public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) { this.pfr = pfr; String macroOptions = Macro.getOptions(); if (macroOptions!=null) { if (macroOptions.indexOf(" interpolate")!=-1) macroOptions.replaceAll(" interpolate", " interpolation=Bilinear"); else if (macroOptions.indexOf(" interpolation=")==-1) macroOptions = macroOptions+" interpolation=None"; Macro.setOptions(macroOptions); } gd = new GenericDialog("command", IJ.getInstance()); gd.addNumericField("Angle (degrees):", angle, (int) angle==angle?1:2); gd.addChoice("Interpolation:", methods, methods [interpolationMethod]); gd.addChoice("Align at:", aligns, aligns[align]); gd.addNumericField("Grid Spacing:", gridSpacing, 0); gd.addNumericField("Grid Angle:", gridAngle, 2); gd.addCheckbox("Fill with Background Color", fillWithBackground); if (canEnlarge) gd.addCheckbox("Enlarge Image to Fit Result", enlarge); gd.addPreviewCheckbox(pfr); gd.addDialogListener(this); gd.showDialog(); drawGridLines(0,0); if (gd.wasCanceled()) { return DONE; } if (canEnlarge && enlarge) { if (imp.getStackSize()==1) flags |= NO_CHANGES; // undoable as a "compound filter" } else flags |= KEEP_PREVIEW; // standard filter without enlarge return IJ.setupDialog(imp, flags); } public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) { angle = gd.getNextNumber(); //only check for invalid input to "angle", don't care about gridSpacing if (gd.invalidNumber() || angle<-89 || angle>89) { if (gd.wasOKed()) IJ.error("Angle is invalid."); return false; } interpolationMethod = gd.getNextChoiceIndex(); align = gd.getNextChoiceIndex(); gridSpacing = gd.getNextNumber(); gridAngle = gd.getNextNumber(); fillWithBackground = gd.getNextBoolean(); if (canEnlarge) enlarge = gd.getNextBoolean(); return true; } public void setNPasses(int nPasses) { } public void run(ImageProcessor ip) { synchronized(this) { if (gd.wasOKed() && canEnlarge && enlarge && !isEnlarged) { enlargeCanvas(); // enlarge canvas before possibly converting to float isEnlarged = true; //IJ.log("enlarged; w="+imp.getWidth()); } } int slice = pfr.getSliceNumber(); if (imp.getStackSize()==1) ip = imp.getProcessor(); else ip = imp.getStack().getProcessor(slice); Color bgc = Toolbar.getBackgroundColor(); float[] rgbF = bgc.getRGBColorComponents(null); int bitDepth = imp.getBitDepth(); ip.setRoi(originalRoi); FloatProcessor fp = null; for (int c=0; c<ip.getNChannels(); c++) { fp = ip.toFloat(c, fp); fp.setInterpolationMethod(interpolationMethod); float backgroundValue = 0; if (fillWithBackground) { if (bitDepth==24) backgroundValue = rgbF[c]*255; else if (bitDepth==8) backgroundValue = ip.getBestIndex(bgc); else {//short, float int bestIndex = ip.getBestIndex(bgc); backgroundValue = (float)(ip.getMin() + (ip.getMax()-ip.getMin())*(bestIndex/255.0)); } } fp.setBackgroundValue(backgroundValue); if (Thread.currentThread().isInterrupted()) return; shear(fp, angle, align, backgroundValue); if (Thread.currentThread().isInterrupted()) return; ip.setPixels(c, fp); } if (!gd.wasOKed()) drawGridLines(gridSpacing, gridAngle); if (isEnlarged && imp.getStackSize()==1) { imp.changes = true; //prepare for undo of enlarged imp.updateAndDraw(); Undo.setup(Undo.COMPOUND_FILTER_DONE, imp); } } void enlargeCanvas() { imp.unlock(); if (imp.getStackSize()==1) Undo.setup(Undo.COMPOUND_FILTER, imp); int width = imp.getWidth(); int height = imp.getHeight(); int roiHeight = originalRoi!=null ? originalRoi.getBounds ().height : height; int newWidth = width + (int)Math.round(roiHeight*Math.abs (Math.tan(angle*Math.PI/180))); IJ.showStatus("Shear: Enlarging..."); IJ.run("Canvas Size...", "width="+newWidth+" height="+height+ " position=Center-Left "+ (fillWithBackground?"":"zero")); IJ.showStatus("Shearing..."); } //overlay grid. No grid lines if spacing<2 void drawGridLines(double spacing, double angle) { ImageCanvas ic = imp.getCanvas(); if (ic==null) return; if (spacing < 2) {ic.setDisplayList(null); return;} GeneralPath path = new GeneralPath(); angle *= Math.PI/180.0; double cosinus = (float)Math.cos(angle); double sinus = (float)Math.sin(angle); int width = imp.getWidth(); int height = imp.getHeight(); double halfLen = 0.5*Math.sqrt(width*width + height*height); int nLines = (int)Math.ceil(halfLen/spacing); int halfLineX = (int)Math.round(halfLen*cosinus); int halfLineY = -(int)Math.round(halfLen*sinus); for (int i=-nLines; i<=nLines; i++) { int x0 = (int)Math.round(0.5*width - i*spacing*sinus); int y0 = (int)Math.round(0.5*height - i*spacing*cosinus); path.moveTo(x0-halfLineX, y0-halfLineY); path.lineTo(x0+halfLineX, y0+halfLineY); } ic.setDisplayList(path, null, null); } //shear a float image void shear(FloatProcessor ip, double angle, int align, float backgroundValue) { int width = ip.getWidth(); int interpolationMethod = ip.getInterpolationMethod(); float[] pixels = (float[])ip.getPixels(); float[] temp = new float[width]; //for transformed line Rectangle roi = ip.getRoi(); int oldWidth = width; if (isEnlarged) roi.width = width; //IJ.log("shear wid="+width+" r.x="+roi.x+" rWid="+roi.width); for (int y=roi.y; y<roi.y+roi.height; y++) { int deltaY = y-roi.y; // 0 for the neutral (unshifted) line if ((isEnlarged && Math.tan(angle*Math.PI/180)<0) || (!isEnlarged && align==BOTTOM)) deltaY -= roi.height-1; double xOffset = -deltaY*Math.tan(angle*Math.PI/180); //if(y%50==0)IJ.log("y="+y+" xOffs="+xOffset); for (int i=0,x=roi.x; i<roi.width; i++,x++) { double xSource = x+xOffset; double xTooLarge = xSource - (oldWidth-1); if (interpolationMethod == ImageProcessor.NEAREST_NEIGHBOR) xSource = Math.round(xSource); if (xSource < 0) // outside image at left? temp[i] = xSource <= -1 ? backgroundValue : (float)(-xSource*backgroundValue + (xSource+1)*pixels[y*width]); else if (xTooLarge > 0) //outside image at right? temp[i] = xTooLarge >= 1 ? backgroundValue : (float)(xTooLarge*backgroundValue + (1- xTooLarge)*pixels[y*width+width-1]); else temp[i] = (float)ip.getInterpolatedPixel (xSource, y); } for (int i=0, p=y*width+roi.x; i<roi.width; i++,p++) pixels[p] = temp[i]; } } } |
In reply to this post by nan0guy
Am 20.04.2011 06:54, schrieb nan0guy:
> I have topographic data (converted to greyscale) from force microscopy that > is distorted by drift in a fairly uniform way. The rate of drift is not > know, but since the structure has a well-defined geometric structure, I can > establish an a priori scan distortion (assuming it is linear). > > My question is, how can I apply such a distortion to the data in ImageJ? > What I am looking for is the equivalent of the skew transformation in > Photoshop. This could probably be done with a line by line shift of the > data, but I do not know how to do this. > > If anyone knows of a plugin which can do this, or could provide me with some > direction of how to modify an image line by line, I would appreciate it. > You may want to do it in Photoshop once and if it's uniform you can use bUnwarpJ (included in fiji i.e.) to calculate the transform between source and your de-skewed image. Then you can batch whole directories of skewed images with this data set. good luck, Rainer |
In reply to this post by Michael Schmid
Michael, This did exactly what I needed it to do, and worked perfectly (once I accounted for cutting and pasting artifacts at end-of-line). I am still receiving two warnings related to ic.setDisplayList (pasted below), but they do not stop the plugin from functioning. Is there anything I can do/should do to remove them? Thanks so much for the help, and the well-written and commented plugin. Lloyd Warnings: C:\Program Files (x86)\ImageJ\plugins\Image Processing\Shear_x.java:172: warning: [deprecation] setDisplayList(java.util.Vector) in ij.gui.ImageCanvas has been deprecated if (spacing < 2) {ic.setDisplayList(null); return;} ^ C:\Program Files (x86)\ImageJ\plugins\Image Processing\Shear_x.java:189: warning: [deprecation] setDisplayList(java.awt.Shape,java.awt.Color,java.awt.BasicStroke) in ij.gui.ImageCanvas has been deprecated ic.setDisplayList(path, null, null); |
Free forum by Nabble | Edit this page |