package ij.plugin.filter;
import ij.*;
import ij.gui.*;
import ij.process.*;
import ij.macro.*;
import java.awt.*;
/** This plugin implements ImageJ's Process/Math submenu. */
public class ImageMath implements ExtendedPlugInFilter, DialogListener {
public static final String MACRO_KEY = "math.macro";
private int flags = DOES_ALL|SUPPORTS_MASKING|KEEP_PREVIEW;
private String arg;
private ImagePlus imp;
private boolean canceled;
private double lower=-1.0, upper=-1.0;
private static double addValue = 25;
private static double mulValue = 1.25;
private static double minValue = 0;
private static double maxValue = 255;
private static final String defaultAndValue = "11110000";
private static String andValue = defaultAndValue;
private static final double defaultGammaValue = 0.5;
private static double gammaValue = defaultGammaValue;
private static String macro = Prefs.get(MACRO_KEY, "v=v+50*sin(d/10)");
private int w, h, w2, h2;
private boolean hasX, hasA, hasD, hasGetPixel;
private String macro2;
private PlugInFilterRunner pfr;
private GenericDialog gd;
public int setup(String arg, ImagePlus imp) {
this.arg = arg;
this.imp = imp;
IJ.register(ImageMath.class);
if (imp!=null && imp.getStackSize()==1 && arg.equals("macro"))
flags |= PARALLELIZE_IMAGES;
else
flags |= PARALLELIZE_STACKS;
return flags;
}
public void run(ImageProcessor ip) {
if (canceled)
return;
if (arg.equals("add")) {
ip.add(addValue);
return;
}
if (arg.equals("sub")) {
ip.subtract(addValue);
return;
}
if (arg.equals("mul")) {
ip.multiply(mulValue);
return;
}
if (arg.equals("div")) {
if (mulValue==0.0&&imp.getBitDepth()!=32)
return;
ip.multiply(1.0/mulValue);
return;
}
if (arg.equals("and")) {
try {
ip.and(Integer.parseInt(andValue,2));
} catch (NumberFormatException e) {
andValue = defaultAndValue;
IJ.error("Binary number required");
}
return;
}
if (arg.equals("or")) {
try {
ip.or(Integer.parseInt(andValue,2));
} catch (NumberFormatException e) {
andValue = defaultAndValue;
IJ.error("Binary number required");
}
return;
}
if (arg.equals("xor")) {
try {
ip.xor(Integer.parseInt(andValue,2));
} catch (NumberFormatException e) {
andValue = defaultAndValue;
IJ.error("Binary number required");
}
return;
}
if (arg.equals("min")) {
ip.min(minValue);
if (!(ip instanceof ByteProcessor))
ip.resetMinAndMax();
return;
}
if (arg.equals("max")) {
ip.max(maxValue);
if (!(ip instanceof ByteProcessor))
ip.resetMinAndMax();
return;
}
if (arg.equals("gamma")) {
if ((gammaValue<0.1 || gammaValue>5.0) && !previewing()) {
IJ.error("Gamma must be between 0.1 and 5.0");
gammaValue = defaultGammaValue;
return;
}
ip.gamma(gammaValue);
return;
}
if (arg.equals("set")) {
boolean rgb = ip instanceof ColorProcessor;
if (rgb) {
if (addValue>255.0) addValue=255.0;
if (addValue<0.0) addValue=0.0;
int ival = (int)addValue;
ip.setValue(ival + (ival<<8) + (ival<<16));
} else
ip.setValue(addValue);
ip.fill();
return;
}
if (arg.equals("log")) {
ip.log();
return;
}
if (arg.equals("exp")) {
ip.exp();
return;
}
if (arg.equals("sqr")) {
ip.sqr();
return;
}
if (arg.equals("sqrt")) {
ip.sqrt();
return;
}
if (arg.equals("reciprocal")) {
if (!isFloat(ip)) return;
float[] pixels = (float[])ip.getPixels();
for (int i=0; i<ip.getWidth()*ip.getHeight(); i++) {
if (pixels[i]==0f)
pixels[i] = Float.NaN;
else
pixels[i] = 1f/pixels[i];
}
ip.resetMinAndMax();
return;
}
if (arg.equals("nan")) {
setBackgroundToNaN(ip);
return;
}
if (arg.equals("abs")) {
if (!((ip instanceof FloatProcessor)||imp.getCalibration().isSigned16Bit())) {
IJ.error("32-bit or signed 16-bit image required");
canceled = true;
} else {
ip.abs();
ip.resetMinAndMax();
}
return;
}
if (arg.equals("macro")) {
applyMacro(ip);
return;
}
}
boolean previewing() {
return gd!=null && gd.getPreviewCheckbox().getState();
}
boolean isFloat(ImageProcessor ip) {
if (!(ip instanceof FloatProcessor)) {
IJ.error("32-bit float image required");
canceled = true;
return false;
} else
return true;
}
void getValue (String title, String prompt, double defaultValue, int digits) {
int places = Analyzer.getPrecision();
if (digits>0 || (int)defaultValue!=defaultValue)
digits = Math.max(places, 1);
gd = new GenericDialog(title);
gd.addNumericField(prompt, defaultValue, digits, 8, null);
gd.addPreviewCheckbox(pfr);
gd.addDialogListener(this);
gd.showDialog();
}
void getBinaryValue (String title, String prompt, String defaultValue) {
gd = new GenericDialog(title);
gd.addStringField(prompt, defaultValue);
gd.addPreviewCheckbox(pfr);
gd.addDialogListener(this);
gd.showDialog();
}
void getGammaValue (double defaultValue) {
gd = new GenericDialog("Gamma");
gd.addSlider("Value:", 0.05, 5.0, defaultValue);
gd.addPreviewCheckbox(pfr);
gd.addDialogListener(this);
gd.showDialog();
}
/** Set non-thresholded pixels in a float image to NaN. */
void setBackgroundToNaN(ImageProcessor ip) {
if (lower==-1.0 && upper==-1.0) {
lower = ip.getMinThreshold();
upper = ip.getMaxThreshold();
if (lower==ImageProcessor.NO_THRESHOLD || !(ip instanceof FloatProcessor)) {
IJ.error("Thresholded 32-bit float image required");
canceled = true;
return;
}
}
float[] pixels = (float[])ip.getPixels();
int width = ip.getWidth();
int height = ip.getHeight();
double v;
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
v = pixels[y*width+x];
if (v<lower || v>upper)
pixels[y*width+x] = Float.NaN;
}
}
ip.resetMinAndMax();
return;
}
// first default: v = v+(sin(x/(w/25))+sin(y/(h/25)))*40
// a=round(a/10); if (a%2==0) v=0;
// cone: v=d
// translate: v=getPixel(x+10,y+10)
// flip vertically: v=getPixel(x,h-y-1)
// spiral: v=(sin(d/10+a*PI/180)+1)*128
// spiral on image: v=v+50*sin(a*PI/180+d/5)
// spiral rotation: a+=PI+d*PI/360; v=getPixel(d*cos(a)+w/2,d*sin(a)+h/2);
// v=sin(log(d)*8 + a) * sin(a*8)
// v=(a * 40.74 + d) % 32
// v=floor((a * 40.75 + 1) % 2)
// v=sin(x) * sin(y)
// v=cos(0.2*x) + sin(0.2*y)
void applyMacro(ImageProcessor ip) {
int PCStart = 23;
if (macro2==null) return;
if (macro2.indexOf("=")==-1) {
IJ.error("The variable 'v' must be assigned a value (e.g., \"v=255-v\")");
canceled = true;
return;
}
macro = macro2;
Program pgm = (new Tokenizer()).tokenize(macro);
hasX = pgm.hasWord("x");
hasA = pgm.hasWord("a");
hasD = pgm.hasWord("d");
hasGetPixel = pgm.hasWord("getPixel");
w = imp.getWidth();
h = imp.getHeight();
w2 = w/2;
h2 = h/2;
String code =
"var v,x,y,z,w,h,d,a;\n"+
"function dummy() {}\n"+
macro2+";\n"; // code starts at program counter location 'PCStart'
Interpreter interp = new Interpreter();
interp.run(code, null);
if (interp.wasError())
return;
Prefs.set(MACRO_KEY, macro);
interp.setVariable("w", w);
interp.setVariable("h", h);
boolean showProgress = pfr.getSliceNumber()==1 && !Interpreter.isBatchMode();
interp.setVariable("z", pfr.getSliceNumber()-1);
int bitDepth = imp.getBitDepth();
Rectangle r = ip.getRoi();
int inc = r.height/50;
if (inc<1) inc = 1;
double v;
int index, v2;
if (bitDepth==8) {
byte[] pixels1 = (byte[])ip.getPixels();
byte[] pixels2 = pixels1;
if (hasGetPixel)
pixels2 = new byte[w*h];
for (int y=r.y; y<(r.y+r.height); y++) {
if (showProgress && y%inc==0)
IJ.showProgress(y-r.y, r.height);
interp.setVariable("y", y);
for (int x=r.x; x<(r.x+r.width); x++) {
index = y*w+x;
v = pixels1[index]&255;
interp.setVariable("v", v);
if (hasX) interp.setVariable("x", x);
if (hasA) interp.setVariable("a", getA(x,y));
if (hasD) interp.setVariable("d", getD(x,y));
interp.run(PCStart);
v2 = (int)interp.getVariable("v");
if (v2<0) v2 = 0;
if (v2>255) v2 = 255;
pixels2[index] = (byte)v2;
}
}
if (hasGetPixel) System.arraycopy(pixels2, 0, pixels1, 0, w*h);
} else if (bitDepth==24) {
int rgb, red, green, blue;
int[] pixels1 = (int[])ip.getPixels();
int[] pixels2 = pixels1;
if (hasGetPixel)
pixels2 = new int[w*h];
for (int y=r.y; y<(r.y+r.height); y++) {
if (showProgress && y%inc==0)
IJ.showProgress(y-r.y, r.height);
interp.setVariable("y", y);
for (int x=r.x; x<(r.x+r.width); x++) {
if (hasX) interp.setVariable("x", x);
if (hasA) interp.setVariable("a", getA(x,y));
if (hasD) interp.setVariable("d", getD(x,y));
index = y*w+x;
rgb = pixels1[index];
if (hasGetPixel) {
interp.setVariable("v", rgb);
interp.run(PCStart);
rgb = (int)interp.getVariable("v");
} else {
red = (rgb&0xff0000)>>16;
green = (rgb&0xff00)>>8;
blue = rgb&0xff;
interp.setVariable("v", red);
interp.run(PCStart);
red = (int)interp.getVariable("v");
if (red<0) red=0; if (red>255) red=255;
interp.setVariable("v", green);
interp.run(PCStart);
green= (int)interp.getVariable("v");
if (green<0) green=0; if (green>255) green=255;
interp.setVariable("v", blue);
interp.run(PCStart);
blue = (int)interp.getVariable("v");
if (blue<0) blue=0; if (blue>255) blue=255;
rgb = 0xff000000 | ((red&0xff)<<16) | ((green&0xff)<<8) | blue&0xff;
}
pixels2[index] = rgb;
}
}
if (hasGetPixel) System.arraycopy(pixels2, 0, pixels1, 0, w*h);
} else {
for (int y=r.y; y<(r.y+r.height); y++) {
if (showProgress && y%inc==0)
IJ.showProgress(y-r.y, r.height);
interp.setVariable("y", y);
for (int x=r.x; x<(r.x+r.width); x++) {
v = ip.getPixelValue(x, y);
interp.setVariable("v", v);
if (hasX) interp.setVariable("x", x);
if (hasA) interp.setVariable("a", getA(x,y));
if (hasD) interp.setVariable("d", getD(x,y));
interp.run(PCStart);
ip.putPixelValue(x, y, interp.getVariable("v"));
}
}
}
if (showProgress)
IJ.showProgress(1.0);
if (pfr.getSliceNumber()==1)
ip.resetMinAndMax();
}
final double getD(int x, int y) {
double dx = x - w2;
double dy = y - h2;
return Math.sqrt(dx*dx + dy*dy);
}
final double getA(int x, int y) {
double angle = Math.atan2((h-y-1)-h2, x-w2);
if (angle<0) angle += 2*Math.PI;
return angle;
}
void getMacro(String macro) {
gd = new GenericDialog("Expression Evaluator");
gd.addStringField("Code:", macro, 42);
gd.setInsets(0,40,0);
gd.addMessage("v=pixel value, x,y&z=pixel coordinates, w=image width,\nh=image height, a=angle, d=distance from center\n");
gd.setInsets(5,40,0);
gd.addPreviewCheckbox(pfr);
gd.addDialogListener(this);
gd.addHelp(IJ.URL+"/docs/menus/process.html#math-macro");
gd.showDialog();
}
public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) {
this.pfr = pfr;
if (arg.equals("macro"))
getMacro(macro);
else if (arg.equals("add"))
getValue("Add", "Value: ", addValue, 0);
else if (arg.equals("sub"))
getValue("Subtract", "Value: ", addValue, 0);
else if (arg.equals("mul"))
getValue("Multiply", "Value: ", mulValue, 2);
else if (arg.equals("div"))
getValue("Divide", "Value: ", mulValue, 2);
else if (arg.equals("and"))
getBinaryValue("AND", "Value (binary): ", andValue);
else if (arg.equals("or"))
getBinaryValue("OR", "Value (binary): ", andValue);
else if (arg.equals("xor"))
getBinaryValue("XOR", "Value (binary): ", andValue);
else if (arg.equals("min"))
getValue("Min", "Value: ", minValue, 0);
else if (arg.equals("max"))
getValue("Max", "Value: ", maxValue, 0);
else if (arg.equals("gamma"))
getGammaValue(gammaValue);
else if (arg.equals("set")) {
boolean rgb = imp.getBitDepth()==24;
String prompt = rgb?"Value (0-255): ":"Value: ";
getValue("Set", prompt, addValue, 0);
}
if (gd!=null && gd.wasCanceled())
return DONE;
else
return IJ.setupDialog(imp, flags);
}
public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
if (arg.equals("macro")) {
String str = gd.getNextString();
if (previewing() && macro2!=null && !str.equals(macro2))
gd.getPreviewCheckbox().setState(false);
macro2 = str;
} else if (arg.equals("add")||arg.equals("sub")||arg.equals("set"))
addValue = gd.getNextNumber();
else if (arg.equals("mul")||arg.equals("div"))
mulValue = gd.getNextNumber();
else if (arg.equals("and")||arg.equals("or")||arg.equals("xor"))
andValue = gd.getNextString();
else if (arg.equals("min"))
minValue = gd.getNextNumber();
else if (arg.equals("max"))
maxValue = gd.getNextNumber();
else if (arg.equals("gamma"))
gammaValue = gd.getNextNumber();
canceled = gd.invalidNumber();
if (gd.wasOKed() && canceled) {
IJ.error("Value is invalid.");
return false;
}
return true;
}
public void setNPasses(int nPasses) {
}
}