De-skew distorted image

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

De-skew distorted image

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.

Thanks,

Lloyd Carroll
Reply | Threaded
Open this post in threaded view
|

Re: De-skew distorted image

Michael Schmid
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];
         }
     }

}
Reply | Threaded
Open this post in threaded view
|

Re: De-skew distorted image

Rainer M. Engel
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
Reply | Threaded
Open this post in threaded view
|

Re: De-skew distorted image

nan0guy
In reply to this post by Michael Schmid
Michael Schmid-3 wrote
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
________________________________________________________________

{Michael's plugin trimmed}
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);