Posted by
Michael Schmid on
Apr 20, 2011; 12:56pm
URL: http://imagej.273.s1.nabble.com/De-skew-distorted-image-tp3684884p3684885.html
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];
}
}
}