Posted by
Russell Kincaid on
Nov 08, 2007; 8:43am
URL: http://imagej.273.s1.nabble.com/Loosing-Particles-when-Rotating-Image-tp3696797p3696798.html
Dear imageJ users and Brendan H,
I have a plugin that might make this job easy. I sent earlier email to
Brendan but now I have the support documentation on my website and I'm
sharing my crazy masterpiece with you all.
I have a plugin to map a circle to a vertical line. my goal was to look
at the characteristics of the "edge", along the circle, and I wanted to
check each radial direction easily. Like Brendan, I would have needed to
rotate the image over and over again incrementally and this has been a
good solution to avoid that. One good thing about my plugin is that you
can adjust where you think the center of your circle is with the goal of
making (in the case of Brendan's task) all the particles line up in a
straight line.
Even if you don't get the particles in a straight line, the new image
coordinates can be easily converted to radius and angle (polar
coordinates), using the spot you picked as the center as the polar
origin. In the new image, the "x" distance is the radius and the "y"
distance is the angle. If this isn't obvious, let me know and I'll
explain completely.
I have made good instructions with nice pictures. I think this is all
pretty clear if you look at them. I've posted them at:
http://www.phy.syr.edu/~rkincaid/PluginDescription_RadialDisk_Mapper080606.docThe plugin is attached to this email. I apologize in advance, to the
great programmers who contribute to this listserv, for my hacked messy code.
My plugin can be tried out very easily. All you need is an image with a
circle on it and (essentially) you just click in the center and it draws
a new image! (The fourth option on the "alternatives" menu is to "Quit
the program".) The instructions say you need a 32 bit real image but it
worked just now with a *.jpg.
Please let me know if there are any questions. And, please let me know
what you think.
Sincerely,
Russell Kincaid
Brendan H. wrote:
> I have a series of images where a few thousand tiny particles form a circular ring, and I need a
> way to count the number of particles in a discrete fashion; i.e. form a 5 degree selection wedge
> (using the center of the ring as the vertex, and half the image width, which is slightly larger than
> the ring diameter, as the radii) that includes 5 degree's worth of particles, count them, then rotate
> the wedge by 5 degrees, and iterate.
>
> I'm having some troubles getting this to work however. My first idea was to make the wedge,
> count the particles, rotate the image, and reiterate. This keeps the wedge stationary, but "sweeps"
> the image past the selection, eventually counting all the particles in discrete steps. I got the
> macro to work fine, but I noticed a few things:
>
> 1) When I rotate the image arbitrarily without iterating, I loose a bunch of particles each time. E.g.
> when I do a total particle count before rotation, I get 1669 particles, but after one arbitrary
> rotation (43degrees in this case) the particle count drops to 1557. My ring is centered in the
> square window, so the particles are not leaving the field of view; also 90 degree rotations do not
> yield a flux in the number of particles. I noticed this problem because as I ran the macro, the
> particles got more "clumpy" with each small rotation; it's clearly an additive issue.
>
> 2) If I iterate when rotating, I have to re-threshold every rotation. This has the same clumping
> effect (although much more severe) as the simple rotation.
>
> Anybody have any ideas? I'm pretty stuck on this!!
>
> Thanks for your help,
> Brendan Hermalyn
>
import ij.*; /* Russell Kincaid */
import ij.process.*; /*
[hidden email] */
import ij.gui.*; /* start August 4, 2006 */
import java.awt.*; /* hacked from */
import ij.plugin.*; /* GridColValues_AquireN07.java */
/* added start at any angle (not just straight up) and appends to image name, Sept. 2, 2006 */
import ij.plugin.filter.PlugInFilter;
import java.awt.event.*;
import ij.plugin.PlugIn;
import ij.plugin.filter.Duplicater;
import ij.ImagePlus;
import ij.ImagePlus.*;
import ij.text.TextPanel.*;
import ij.text.*;
import java.lang.*;
import java.math.*;
import java.math.BigDecimal;
/*
This plugin implements the MouseListener interface and listens
for mouse events generated by the current image.
*/
public class RadialDisk_MapperN06 implements PlugInFilter, MouseListener {
ImagePlus img;
ImageWindow win;
ImageCanvas canvas;
boolean done;
ImageProcessor IprocMINE, IprocNEW;
ImagePlus IplusSHOW;
int xCOL, yROW;
double thePixValue, theIncrement, theLimit, moveSinPhase, RemoveSinAmplitude;
int theBestX, theBestY, centerXguess, centerYguess, numIncrements, newSizeXX;
int numPixelsForRadius, numArcIncrements, startAngle;
double arcIncrement, ddCOL, ddROW, INTERPcenterX, INTERPcenterY, Theta, incrementsPerRadialPixel;
String startAngleString;
int CH = 0;
String[] ChoiceN01 = {"I want to use the location I clicked on", "I want to input the center coordinates myself", "I want to modify the center coordinates using a sin function", "I want to QUIT the Program"};
public int setup(String arg, ImagePlus img) {
this.img = img;
if (img!=null) {
win = img.getWindow();
win.running = true;
}
return DOES_ALL+NO_CHANGES;
}
public void run(ImageProcessor ip) {
IprocMINE = img.getProcessor(); /* img is the ImagePlus */
ImageWindow win = img.getWindow();
canvas = win.getCanvas();
canvas.addMouseListener(this);
if (!win.running)
{IJ.beep(); done=true; return;}
IJ.showMessage(" \n click on your estimate for the very center of the disk \n \n this need not be exact, just click the image \n \n a mouse click is needed to drive the whole plugin ");
numPixelsForRadius = 300; // hard wired to start
arcIncrement = 0.25; // this is hard wired to start, IN DEGREES
incrementsPerRadialPixel = 2; // to start I want to see better
startAngle = 0;
moveSinPhase = 0.0;
RemoveSinAmplitude = 0.0;
}
public void mousePressed(MouseEvent e) {
int x = e.getX();
int y = e.getY();
int offscreenX = canvas.offScreenX(x);
int offscreenY = canvas.offScreenY(y);
IJ.write(" your estimate for the center was "+offscreenX+" , "+offscreenY);
centerXguess = offscreenX;
centerYguess = offscreenY; // initial guess
GenericDialog gd24 = new GenericDialog("Alternatives");
gd24.addChoice("What Now?: ", ChoiceN01, ChoiceN01[CH]);
gd24.showDialog();
CH = gd24.getNextChoiceIndex();
if(CH == 0) { // start of use the clicked coordinates
INTERPcenterX = centerXguess;
INTERPcenterY = centerYguess;
} // end of use the clicked coordinates
if(CH == 1) { // start of input center coordinates myself
GenericDialog gd28 = new GenericDialog("Center Coordinates");
gd28.addNumericField("X coordinate, col: ", INTERPcenterX, 2);
gd28.addNumericField("Y coordinate, row: ", INTERPcenterY, 2);
gd28.showDialog();
INTERPcenterX = gd28.getNextNumber();
INTERPcenterY = gd28.getNextNumber();
} // end of input center coordinates myself
if(CH == 2) { // start of modify old coordinates by using sin function
IJ.showMessage(" \n you'll have two inputs, look at the previous output image \n look for a sin curve increasing at first to the right as it moves down the screen \n \n you need the phase (in degrees) that you want \n to shift the sin function DOWN the screen to ''correct'' it \n and the amplitude of the sin curve you see \n \n the plugin DOES take into account the radial scaling being used so don't worry about that");
GenericDialog gd22 = new GenericDialog("Center Coordinates");
gd22.addNumericField("phase shift sin (down, in degrees): ", moveSinPhase, 2);
gd22.addNumericField("amplitude of the function (in pixels): ", RemoveSinAmplitude, 2);
gd22.showDialog();
moveSinPhase = gd22.getNextNumber();
RemoveSinAmplitude = gd22.getNextNumber();
Theta = ((2.0 * Math.PI) / 360) * (moveSinPhase); /* now in radians */
INTERPcenterX = INTERPcenterX + ((RemoveSinAmplitude/incrementsPerRadialPixel) * Math.cos(Theta)) ;
INTERPcenterY = INTERPcenterY - ((RemoveSinAmplitude/incrementsPerRadialPixel) * Math.sin(Theta)) ;
IJ.write(" the new image was created with phase shift equals (down): "+moveSinPhase+" and amplitude equals, "+RemoveSinAmplitude);
} // end of modify old coordinates by using sin function
if(CH == 3) { // start of end the program
canvas.removeMouseListener(this);
return; // this is the end of the program
} // end of end the program
YesNoCancelDialog YesNoEND = new YesNoCancelDialog(win, "New Parameters?", " Do you want to input parameters? \n \n (click NO to use previous or defaults) ");
if(YesNoEND.yesPressed()) { // start of ifYN
GenericDialog gd25 = new GenericDialog("Radial Mapping");
gd25.addNumericField("Start angle, reads integer value ONLY, zero is straight up: ", startAngle, 0);
gd25.addNumericField("Angular increment in degrees: ", arcIncrement, 3);
gd25.addNumericField("Acquire how many radial pixels from disk: ", numPixelsForRadius, 0);
gd25.addNumericField("How many new pixels per old radial pixel: ", incrementsPerRadialPixel, 1);
gd25.showDialog();
startAngle = (int) gd25.getNextNumber();
arcIncrement = gd25.getNextNumber();
numPixelsForRadius = (int) gd25.getNextNumber();
incrementsPerRadialPixel = gd25.getNextNumber();
} // end of ifYN
else { // start of elseYN
} // end of elseYN
numArcIncrements = (int) Math.rint(Math.ceil(360 / arcIncrement));
newSizeXX = (int) Math.rint(Math.ceil(numPixelsForRadius * incrementsPerRadialPixel));
ImageProcessor IprocNEW;
IprocNEW = IprocMINE.createProcessor(newSizeXX, numArcIncrements);
for (int iROW=0; iROW <= (numArcIncrements - 1);iROW++) { // this becomes the angle, clockwise from the startAngle
Theta = ((2.0 * Math.PI) / 360) * (startAngle + (iROW * arcIncrement)); /* in radians */
for (int iCOL=0; iCOL <= (newSizeXX - 1); iCOL++) { // this is scaled distance from center
ddCOL = INTERPcenterX + (Math.sin(Theta) * (iCOL / incrementsPerRadialPixel)) ; // this real valued position on old image
ddROW = INTERPcenterY - (Math.cos(Theta) * (iCOL / incrementsPerRadialPixel)) ; // remember starts at top so need neg sign
IprocNEW.putPixelValue(iCOL, iROW, findXYinterpPixel(IprocMINE, ddCOL, ddROW));
}
}
startAngleString = Integer.toString(startAngle);
IplusSHOW = new ImagePlus(" Radial Profiles Start Angle "+startAngleString+" Degrees ", IprocNEW);
IplusSHOW.show();
IJ.write(" the new image was created using center equals (x,y): "+INTERPcenterX+" , "+INTERPcenterY);
IJ.write(" and StartAngle of : "+startAngle+" , arcIncrement : "+arcIncrement+" , PixelRadius : "+numPixelsForRadius+" , IncPerOldRadialPixel : "+incrementsPerRadialPixel);
IJ.showMessage(" \n after clicking this message to remove it, \n look at the output image and decide how much you want to adjust the sin function \n then click the mouse on the original image \n \n a mouse click is needed to drive the whole plugin ");
} // end of mousePressed
public void mouseReleased(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public double findXYinterpPixel(ImageProcessor XYinterpIP, double XXCol, double YYRow) {
/* inputs correct data into cubic spline routine
to get new pixel value, where x and y are real
and will be fit using four consecutive "x's" and "y's" in a row */
double XYvaluePIX, XYvalue1, XYvalue2, XYvalue3, XYvalue4; // I need two sets of four variables and a pixel value
double XYpixValInterp, Pv1InterpXY, Pv2InterpXY, Pv3InterpXY, Pv4InterpXY;
XYpixValInterp = 0.0; // for now I assume I have two pixels on all sides
int xTruncXY = (int) Math.rint(XXCol - 0.5); // rint converts to a double that = an int
int yTruncXY = (int) Math.rint(YYRow - 0.5); // rint converts to a double that = an int
Pv1InterpXY = XYinterpIP.getPixelValue((xTruncXY - 1), yTruncXY - 1);
Pv2InterpXY = XYinterpIP.getPixelValue((xTruncXY), yTruncXY - 1);
Pv3InterpXY = XYinterpIP.getPixelValue((xTruncXY + 1), yTruncXY - 1);
Pv4InterpXY = XYinterpIP.getPixelValue((xTruncXY + 2), yTruncXY - 1); /* in the middle, expect 1 < x < 2 */
XYvalue1 = findWithCubicSplines(Pv1InterpXY, Pv2InterpXY, Pv3InterpXY, Pv4InterpXY, (XXCol - xTruncXY + 1));
Pv1InterpXY = XYinterpIP.getPixelValue((xTruncXY - 1), yTruncXY);
Pv2InterpXY = XYinterpIP.getPixelValue((xTruncXY), yTruncXY);
Pv3InterpXY = XYinterpIP.getPixelValue((xTruncXY + 1), yTruncXY);
Pv4InterpXY = XYinterpIP.getPixelValue((xTruncXY + 2), yTruncXY); /* in the middle, expect 1 < x < 2 */
XYvalue2 = findWithCubicSplines(Pv1InterpXY, Pv2InterpXY, Pv3InterpXY, Pv4InterpXY, (XXCol - xTruncXY + 1));
Pv1InterpXY = XYinterpIP.getPixelValue((xTruncXY - 1), yTruncXY + 1);
Pv2InterpXY = XYinterpIP.getPixelValue((xTruncXY), yTruncXY + 1);
Pv3InterpXY = XYinterpIP.getPixelValue((xTruncXY + 1), yTruncXY + 1);
Pv4InterpXY = XYinterpIP.getPixelValue((xTruncXY + 2), yTruncXY + 1); /* in the middle, expect 1 < x < 2 */
XYvalue3 = findWithCubicSplines(Pv1InterpXY, Pv2InterpXY, Pv3InterpXY, Pv4InterpXY, (XXCol - xTruncXY + 1));
Pv1InterpXY = XYinterpIP.getPixelValue((xTruncXY - 1), yTruncXY + 2);
Pv2InterpXY = XYinterpIP.getPixelValue((xTruncXY), yTruncXY + 2);
Pv3InterpXY = XYinterpIP.getPixelValue((xTruncXY + 1), yTruncXY + 2);
Pv4InterpXY = XYinterpIP.getPixelValue((xTruncXY + 2), yTruncXY + 2); /* in the middle, expect 1 < x < 2 */
XYvalue4 = findWithCubicSplines(Pv1InterpXY, Pv2InterpXY, Pv3InterpXY, Pv4InterpXY, (XXCol -xTruncXY + 1));
XYpixValInterp = findWithCubicSplines(XYvalue1, XYvalue2, XYvalue3, XYvalue4, (YYRow - yTruncXY +1));
Pv1InterpXY = XYinterpIP.getPixelValue((xTruncXY - 1), yTruncXY - 1);
Pv2InterpXY = XYinterpIP.getPixelValue((xTruncXY - 1), yTruncXY);
Pv3InterpXY = XYinterpIP.getPixelValue((xTruncXY - 1), yTruncXY + 1);
Pv4InterpXY = XYinterpIP.getPixelValue((xTruncXY - 1), yTruncXY + 2); /* in the middle, expect 1 < x < 2 */
XYvalue1 = findWithCubicSplines(Pv1InterpXY, Pv2InterpXY, Pv3InterpXY, Pv4InterpXY, (YYRow - yTruncXY + 1));
Pv1InterpXY = XYinterpIP.getPixelValue((xTruncXY), yTruncXY - 1);
Pv2InterpXY = XYinterpIP.getPixelValue((xTruncXY), yTruncXY);
Pv3InterpXY = XYinterpIP.getPixelValue((xTruncXY), yTruncXY + 1);
Pv4InterpXY = XYinterpIP.getPixelValue((xTruncXY), yTruncXY + 2); /* in the middle, expect 1 < x < 2 */
XYvalue2 = findWithCubicSplines(Pv1InterpXY, Pv2InterpXY, Pv3InterpXY, Pv4InterpXY, (YYRow - yTruncXY + 1));
Pv1InterpXY = XYinterpIP.getPixelValue((xTruncXY + 1), yTruncXY - 1);
Pv2InterpXY = XYinterpIP.getPixelValue((xTruncXY + 1), yTruncXY);
Pv3InterpXY = XYinterpIP.getPixelValue((xTruncXY + 1), yTruncXY + 1);
Pv4InterpXY = XYinterpIP.getPixelValue((xTruncXY + 1), yTruncXY + 2); /* in the middle, expect 1 < x < 2 */
XYvalue3 = findWithCubicSplines(Pv1InterpXY, Pv2InterpXY, Pv3InterpXY, Pv4InterpXY, (YYRow - yTruncXY + 1));
Pv1InterpXY = XYinterpIP.getPixelValue((xTruncXY + 2), yTruncXY - 1);
Pv2InterpXY = XYinterpIP.getPixelValue((xTruncXY + 2), yTruncXY);
Pv3InterpXY = XYinterpIP.getPixelValue((xTruncXY + 2), yTruncXY + 1);
Pv4InterpXY = XYinterpIP.getPixelValue((xTruncXY + 2), yTruncXY + 2); /* in the middle, expect 1 < x < 2 */
XYvalue4 = findWithCubicSplines(Pv1InterpXY, Pv2InterpXY, Pv3InterpXY, Pv4InterpXY, (YYRow - yTruncXY + 1));
XYpixValInterp = XYpixValInterp + findWithCubicSplines(XYvalue1, XYvalue2, XYvalue3, XYvalue4, (XXCol - xTruncXY +1));
XYpixValInterp = (XYpixValInterp / 2.0);
/* makes a pretty picture to use instead of using the 47 lines of code above...
XYpixValInterp = XXCol * YYRow; */
return XYpixValInterp;
}
public double findWithCubicSplines(double I_y0, double I_y1, double I_y2, double I_y3, double I_x) {
/* This finds the missing value in a cubic polynomial if four neighbors in a row
are known. It follows from Newton's interpolation formula, MAT581 10/15/02,
and from my lab notes 6/23/05. Modified 7/9/05 for input of I_x */
double x1 = 0.0; /* the "y" inputs come from four "x's" in a row */
double x2 = 1.0;
double x3 = 2.0;
double x4 = 3.0;
double x = I_x; /* this must be indexed as one should imagine from the "x's" */
double y1 = I_y0;
double y2 = I_y1;
double y3 = I_y2;
double y4 = I_y3; /* from here on down the code is generic */
double C1 = y1;
double C2 = (y2 - C1)/(x2 - x1);
double C3 = ( (y3 - C1 - (C2 * (x3 - x1))) / ((x3 - x1)*(x3 - x2)) );
double C4 = ((y4-C1-(C2*(x4-x1))-(C3*((x4-x1)*(x4-x2))))/((x4-x1)*(x4-x2)*(x4-x3)));
double y5 = (C1+ (C2*(x-x1)) +(C3*((x-x1)*(x-x2))) +(C4*((x-x1)*(x-x2)*(x-x3))) );
return y5;
}
}