import java.awt.*; import java.awt.event.*; import java.io.*; import ij.*; import ij.plugin.*; import ij.process.*; import ij.gui.*; /** * Expression is a plugin filter for ImageJ that applies * a user-specified mathematical expression to each pixel. * * Available variables are: * w (roi width), h (roi height), x (position in row), y (position in column), * i (value of this pixel), fw (image width), fh (image height), * pi, e, d (distance from center of image), * a (angle, measured from center of image; between 0 and 2*pi) * ? (random number 0 <= r < 1) * * Available operators and functions are: * + - * / % ^ sin() cos() tan() asin() acos() atan() * exp() ln() cond(a,b,c) min(a,b) max(a,b) sqrt(a) * * Expression is inspired by Jim Bumgardners Expression plugin for NIH Image, * but doesn't have anywhere near the functionality, in particular no fractal calculations. * The image processing framework was ripped off ImageJ's IP_Demo example. * The expression parsing class is based on C code written by Guy Laden. * * This code was written by Ulf Dittmer (dittmer@capital.net), * and is hereby released under the same terms as ImageJ. */ public class Expression_ extends ij.plugin.frame.PlugInFrame implements PlugIn, ActionListener { TextField tf1, tf2, tf3; int previousId; public Expression_() { super("Expression"); setLayout(new BorderLayout(10, 10)); MultiLineLabel mll = new MultiLineLabel("i - value of this pixel, w - roi width, h - roi height\n" + "x - position in row, y - position in column, fw - image width\nfh - image height, pi, e, " + "r - red value, g - green value, b - blue value\n? - random number 0<=r<1, " + "d - distance from center of image\na - angle, measured from center of image; 0<=d<2*pi\n" + "functions: min, max, cond, sqrt, sin, cos, tan, asin, acos, atan, exp, ln"); add("North", mll); Panel centerPanel = new Panel(); Panel row1 = new Panel(); Font myFont = new Font("Helvetica", Font.PLAIN, 14); tf1 = new TextField(35); tf1.setFont(myFont); row1.add(new Label(" R / I ")); row1.add(tf1); centerPanel.add(row1); Panel row2 = new Panel(); tf2 = new TextField(35); tf2.setFont(myFont); row2.add(new Label(" G ")); row2.add(tf2); centerPanel.add(row2); Panel row3 = new Panel(); tf3 = new TextField(35); tf3.setFont(myFont); row3.add(new Label(" B ")); row3.add(tf3); centerPanel.add(row3); add("Center", centerPanel); Button apply = new Button("Apply"); apply.setFont(new Font("Helvetica", Font.BOLD, 14)); apply.addActionListener(this); add("South", apply); pack(); setSize(400, 260); GUI.center(this); show(); } public void actionPerformed (ActionEvent e) { ImagePlus imp = WindowManager.getCurrentImage(); if (imp==null) { IJ.showStatus("No image"); previousId = 0; return; } if (!imp.lock()) {previousId = 0; return;} ImageProcessor ip = imp.getProcessor(); if (imp.getID()!=previousId) ip.snapshot(); previousId = imp.getID(); new Executer(tf1.getText(), tf2.getText(), tf3.getText(), imp, ip); } /** Runs expression evaluation in a separate thread. */ class Executer extends Thread { private String cmd1, cmd2, cmd3; private ImagePlus imp; private ImageProcessor ip; Executer(String cmd1, String cmd2, String cmd3, ImagePlus imp, ImageProcessor ip) { super(cmd1); this.cmd1 = cmd1; this.cmd2 = cmd2; this.cmd3 = cmd3; this.imp = imp; this.ip = ip; // setPriority(Math.max(getPriority()-2, MIN_PRIORITY)); start(); } public void run() { try { runCommand(cmd1, cmd2, cmd3, imp, ip); } catch (OutOfMemoryError e) { IJ.outOfMemory(cmd1); if (imp!=null) imp.unlock(); } catch (Exception ex) { CharArrayWriter caw = new CharArrayWriter(); PrintWriter pw = new PrintWriter(caw); ex.printStackTrace(pw); IJ.write(caw.toString()); IJ.showStatus(""); if (imp!=null) imp.unlock(); } } public void runCommand (String exp1, String exp2, String exp3, ImagePlus imp, ImageProcessor ip) { IJ.showStatus(exp1 + "..."); ImageProcessor newImp = null; int width = ip.getWidth(); int height = ip.getHeight(); Rectangle rect = ip.getRoi(); int rWidth = rect.width; int rHeight = rect.height; int type = imp.getType(); switch (type) { case ImagePlus.GRAY8: // 8-bit grayscale (unsigned) case ImagePlus.COLOR_256: // 8-bit indexed color newImp = new ByteProcessor(rWidth, rHeight); break; case ImagePlus.GRAY16: // 16-bit grayscale (unsigned) newImp = new ShortProcessor(rWidth, rHeight); break; case ImagePlus.GRAY32: // 32-bit floating-point grayscale newImp = new FloatProcessor(rWidth, rHeight); break; case ImagePlus.COLOR_RGB: // 32-bit RGB color newImp = new ColorProcessor(rWidth, rHeight); break; default: IJ.error("Expression does not support this image type."); return; } Expr expr1 = new Expr(exp1, rWidth, rHeight, width, height); Expr expr2 = new Expr(exp2, rWidth, rHeight, width, height); Expr expr3 = new Expr(exp3, rWidth, rHeight, width, height); boolean expr2empty=exp2.length() == 0; boolean expr3empty=exp3.length() == 0; long start = System.currentTimeMillis(); int val; double result=0.0; float fval; for (int y=rect.y; y<(rect.y+rHeight); y++) { IJ.showProgress((y-rect.y) / (float) rHeight); for (int x=rect.x; x<(rect.x+rWidth); x++) { val = ip.getPixel(x, y); switch (type) { case ImagePlus.GRAY8: case ImagePlus.COLOR_256: result = Math.floor(expr1.eval(x-rect.x, y-rect.y, val<0 ? 256+val : val) + 0.5); newImp.putPixel(x-rect.x, y-rect.y, (byte) result); break; case ImagePlus.GRAY16: result = Math.floor(expr1.eval(x-rect.x, y-rect.y, val<0 ? 65536+val : val) + 0.5); newImp.putPixel(x-rect.x, y-rect.y, (short) result); break; case ImagePlus.GRAY32: fval = ip.getPixelValue(x, y); result = expr1.eval(x-rect.x, y-rect.y, fval); ((FloatProcessor) newImp).putPixelValue(x-rect.x, y-rect.y, result); break; case ImagePlus.COLOR_RGB: int r = (val & 0xff0000) >> 16; if (r < 0) r += 256; int g = (val & 0x00ff00) >> 8; if (g < 0) g += 256; int b = (val & 0x0000ff); if (b < 0) b += 256; r = (int) Math.floor(expr1.eval(x-rect.x, y-rect.y, r, r, g, b) + 0.5); if (expr2empty) g = (int) Math.floor(expr1.eval(x-rect.x, y-rect.y, g, r, g, b) + 0.5); else g = (int) Math.floor(expr2.eval(x-rect.x, y-rect.y, g, r, g, b) + 0.5); if (expr3empty) b = (int) Math.floor(expr1.eval(x-rect.x, y-rect.y, b, r, g, b) + 0.5); else b = (int) Math.floor(expr3.eval(x-rect.x, y-rect.y, b, r, g, b) + 0.5); result = ((r & 0xff) << 16) + ((g & 0xff) << 8) + (b & 0xff); newImp.putPixel(x-rect.x, y-rect.y, (int) result); break; } // if (x == y) System.out.println(x + "::" + val + "::" + result); } } float secs = (System.currentTimeMillis() - start) / 1000.0f; IJ.showStatus("Expression: " + secs + " seconds, " + (int) (rWidth*rHeight/secs) + " pixels/second"); ip.copyBits(newImp, rect.x, rect.y, Blitter.COPY); Roi roi = imp.getRoi(); if (roi!=null) { int[] mask = roi.getMask(); if (mask!=null) ip.reset(mask); } imp.updateAndDraw(); imp.unlock(); } } class Expr { /* Simple recursive descent expression evaluator. written in C by Guy Laden Changes by Ulf Dittmer: May 1997: ported to Oberon and small enhancements November 1997: ported to Java May 1999: enhanced to accept an arbitrary number of variables May 1999: added cond(,,) and % functions June 2001: added min(,) and max(,) This is a special version for ImageJ. Functions: + - * / ^ % sqrt() sin() cos() arctan() exp() ln() cond(,,) min(,) max(,) cond works somewhat like an if/then/else, or rather like the "a ? b : c" construct in C and Java. The 1st argument should evaluate to true or false (the numerical comparison operators can be used); if it's true, cond evaluates to the 2nd argument, otherwise to the 3rd. Variables can be upper- or lowercase; they override functions of the same name. "PI" and "E" are predefined to be the corresponding mathematical constants. Here's an EBNF of the expressions this class understands: EBNF: expr: ['-' | '+' ] term {'+' term | '-' term}. term: factor {'*' factor | '/' factor | '%' factor}. factor: primary {'^' factor}. compExpr: ['-' | '+' ] term compOper term compOper: '=' | '<' | '>' | '<>' | '<=' | '>=' primary: number | '(' expr ')' | 'sin' '(' expr ')' | 'cos' '(' expr ')' | 'tan' '(' expr ')' | 'exp' '(' expr ')' | 'ln' '(' expr ')' | 'atan' '(' expr ')'| 'acos' '(' expr ')' | 'sqrt' '(' expr ')' | 'cond' '(' compExpr ',' expr ',' expr ')' | 'asin' '(' expr ')' 'min' '(' expr ',' expr ')' | 'max' '(' expr ',' expr ')' | variable | '?' variable: [a-zA-Z]+ number: intnumber | realnumber. realnumber: [ intnumber ] . [ intnumber ]. intnumber: digit {digit}. digit: 0 | 1 | 2 | 3 | ... | 9 */ final int MULT = 1, DIVIDED = 2, PLUS = 3, MINUS = 4, LBRAK = 5, RBRAK = 6, POW = 7, NUMBER = 8, SIN = 9, COS = 10, EXP = 11, LN = 12, ATAN = 13, SQRT = 14, LAST = 15, COND = 16, COMMA = 17, LT = 18, GT = 19, EQ = 20, NE = 21, LE = 22, GE = 23, MOD = 24, MIN = 25, MAX = 26, ASIN = 27, ACOS = 28, TAN = 29; char c; /* last character read from input */ String str, ident; /* scanned identifiers are stored here */ double num, /* scanned numbers are stored here */ value, r, g, b; int x, y, w, h, fw, fh; int i, token; /* invariant throughout: token is the last token scanned */ java.util.Random rnd = new java.util.Random(System.currentTimeMillis()); /* Scanner */ void getIdent () { ident = ""; do { ident += String.valueOf(c); if (++i < str.length()) c = str.charAt(i); } while ((i < str.length()) && Character.isLetter(c)); } int getCompOp () { char c1 = c, c2 = ' '; if (++i < str.length()) c = str.charAt(i); c2 = c; if ((c=='=') || (c=='<') || (c=='>')) if (++i < str.length()) c = str.charAt(i); if ((c1 == '<') && (c2 == '=')) return LE; else if ((c1 == '>') && (c2 == '=')) return GE; else if (c1 == '=') return EQ; else if ((c1 == '<') && (c2 == '>')) return NE; else if (c1 == '<') return LT; else if (c1 == '>') return GT; else throw new RuntimeException("unknown operator: " + c1 + c2); } double getNum () throws NumberFormatException { StringBuffer s = new StringBuffer(""); do { s.append(String.valueOf(c)); if (++i < str.length()) c = str.charAt(i); } while ((i< str.length()) && (Character.isDigit(c) || (c == '.'))); return Double.valueOf(s.toString()).doubleValue(); } int getTok () { /* pre: c is the last character read */ int t; while ((i < str.length()) && (c == ' ')) { if (++i < str.length()) c = str.charAt(i); } if (i == str.length()) t = LAST; else if (Character.isDigit(c) || (c == '.')) { num = getNum(); t = NUMBER; } else if ((c == '<') || (c == '=') || (c == '>')) { t = getCompOp(); } else if (Character.isLetter(Character.toUpperCase(c))) { getIdent(); if (ident.equalsIgnoreCase("SIN")) t = SIN; else if (ident.equalsIgnoreCase("COS")) t = COS; else if (ident.equalsIgnoreCase("TAN")) t = TAN; else if (ident.equalsIgnoreCase("EXP")) t = EXP; else if (ident.equalsIgnoreCase("LN")) t = LN; else if (ident.equalsIgnoreCase("ASIN")) t = ASIN; else if (ident.equalsIgnoreCase("ACOS")) t = ACOS; else if (ident.equalsIgnoreCase("ATAN")) t = ATAN; else if (ident.equalsIgnoreCase("SQRT")) t = SQRT; else if (ident.equalsIgnoreCase("COND")) t = COND; else if (ident.equalsIgnoreCase("MIN")) t = MIN; else if (ident.equalsIgnoreCase("MAX")) t = MAX; else if (ident.equalsIgnoreCase("X")) { num = x; t = NUMBER; } else if (ident.equalsIgnoreCase("Y")) { num = y; t = NUMBER; } else if (ident.equalsIgnoreCase("I")) { num = value; t = NUMBER; } else if (ident.equalsIgnoreCase("R")) { num = r; t = NUMBER; } else if (ident.equalsIgnoreCase("G")) { num = g; t = NUMBER; } else if (ident.equalsIgnoreCase("B")) { num = b; t = NUMBER; } else if (ident.equalsIgnoreCase("W")) { num = w; t = NUMBER; } else if (ident.equalsIgnoreCase("H")) { num = h; t = NUMBER; } else if (ident.equalsIgnoreCase("FW")) { num = fw; t = NUMBER; } else if (ident.equalsIgnoreCase("FH")) { num = fh; t = NUMBER; } else if (ident.equalsIgnoreCase("PI")) { num = Math.PI; t = NUMBER; } else if (ident.equalsIgnoreCase("E")) { num = Math.E; t = NUMBER; } else if (ident.equalsIgnoreCase("D")) { int dx = x - (fw>>1); int dy = y - (fh>>1); num = Math.sqrt(dx*dx + dy*dy); t = NUMBER; } else if (ident.equalsIgnoreCase("A")) { num = Math.atan2(y-(fh>>1), x-(fw>>1)); if (num < 0) num += 2*Math.PI; t = NUMBER; } else { throw new RuntimeException("unknown ident: " + ident); /* this exception can be ignored; simply replace it by the following lines if (++i < str.length()) c = str.charAt(i); t = getTok(); */ } } else switch (c) { case '*': t = MULT; if (++i < str.length()) c = str.charAt(i); break; case '/': t = DIVIDED; if (++i < str.length()) c = str.charAt(i); break; case '%': t = MOD; if (++i < str.length()) c = str.charAt(i); break; case '+': t = PLUS; if (++i < str.length()) c = str.charAt(i); break; case '-': t = MINUS; if (++i < str.length()) c = str.charAt(i); break; case '(': t = LBRAK; if (++i < str.length()) c = str.charAt(i); break; case ')': t = RBRAK; if (++i < str.length()) c = str.charAt(i); break; case ',': t = COMMA; if (++i < str.length()) c = str.charAt(i); break; case '^': t = POW; if (++i < str.length()) c = str.charAt(i); break; case '?': t = NUMBER; num = rnd.nextDouble(); if (++i < str.length()) c = str.charAt(i); break; default: throw new RuntimeException("unknown char: " + c); /* this exception can be ignored; simply replace it by the following line if (++i < str.length()) c = str.charAt(i); t = getTok(); break; */ } return t; } /* Parser */ void eat (int expectedToken) { if (token == expectedToken) token = getTok(); else throw new RuntimeException("expected: " + expectedToken + " but got: " + token); } double primary () { double p=0.0; boolean bool; double first=0.0, second=0.0; switch (token) { case NUMBER: p = num; token = getTok(); break; case SIN: token = getTok(); eat(LBRAK); p = Math.sin(expr()); eat(RBRAK); break; case COS: token = getTok(); eat(LBRAK); p = Math.cos(expr()); eat(RBRAK); break; case TAN: token = getTok(); eat(LBRAK); p = Math.tan(expr()); eat(RBRAK); break; case EXP: token = getTok(); eat(LBRAK); p = Math.exp(expr()); eat(RBRAK); break; case LN: token = getTok(); eat(LBRAK); p = Math.log(expr()); eat(RBRAK); break; case ASIN: token = getTok(); eat(LBRAK); p = Math.asin(expr()); eat(RBRAK); break; case ACOS: token = getTok(); eat(LBRAK); p = Math.acos(expr()); eat(RBRAK); break; case ATAN: token = getTok(); eat(LBRAK); p = Math.atan(expr()); eat(RBRAK); break; case SQRT: token = getTok(); eat(LBRAK); p = Math.sqrt(expr()); eat(RBRAK); break; case COND: token = getTok(); eat(LBRAK); bool=compExpr(); eat(COMMA); first=expr(); eat(COMMA); second=expr(); eat(RBRAK); p = (bool ? first : second); break; case MIN: token = getTok(); eat(LBRAK); first=expr(); eat(COMMA); second=expr(); eat(RBRAK); p = (first < second ? first : second); break; case MAX: token = getTok(); eat(LBRAK); first=expr(); eat(COMMA); second=expr(); eat(RBRAK); p = (first >= second ? first : second); break; case LBRAK: token = getTok(); p = expr(); eat(RBRAK); break; default: throw new RuntimeException("unexpected token: " + token); } return p; } double factor () { double f = primary(); while (token == POW) { token = getTok(); f = Math.exp(factor() * Math.log(f)); } return f; } double term () { double f = factor(); while ((token == MULT) || (token == DIVIDED) || (token == MOD)) { if (token == MULT) { token = getTok(); f *= factor(); } else if (token == DIVIDED) { token = getTok(); f /= factor(); } else { token = getTok(); f %= factor(); } } return f; } double expr () { double t; if (token == PLUS) { token = getTok(); t = term(); } else if (token == MINUS) { token = getTok(); t = -term(); } else t = term(); while ((token == PLUS) || (token == MINUS)) if (token == PLUS) { token = getTok(); t += term(); } else { token = getTok(); t -= term(); } return t; } boolean compExpr () { double t1, t2; int compToken; if (token == PLUS) { token = getTok(); t1 = term(); } else if (token == MINUS) { token = getTok(); t1 = -term(); } else t1 = term(); compToken = token; // remember the comparison operator token = getTok(); t2 = term(); switch (compToken) { case LE: return (t1 <= t2); case LT: return (t1 < t2); case EQ: return (t1 == t2); case NE: return (t1 != t2); case GE: return (t1 >= t2); case GT: return (t1 > t2); default: throw new RuntimeException("unknow comparison operator: " + compToken); // default should not happen } } Expr (String expression, int w, int h, int fw, int fh) { this.str = expression; this.w = w; this.h = h; this.fw = fw; this.fh = fh; } public double eval (int x, int y, double val) { this.ident = ""; this.x = x; this.y = y; this.value = val; this.i = -1; this.c = ' '; this.token = getTok(); if (this.token != LAST) try { return this.expr(); } catch (Exception ex) { IJ.write("Expression could not be evaluated: " + ex.getMessage()); } return 0.0; } public double eval (int x, int y, double val, double r, double g, double b) { this.r = r; this.g = g; this.b = b; return eval(x, y, val); } } } -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html