Utils.showStatus(String) defaults to NOT stealing focus for the ImageJ status bar.
[trakem2.git] / ini / trakem2 / utils / Utils.java
blob99410c5765a52e2b71266468b6ccaca2a9b90858
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005,2006,2007,2008 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
24 package ini.trakem2.utils;
26 import ini.trakem2.ControlWindow;
27 import ini.trakem2.display.YesNoDialog;
28 import ini.trakem2.display.Layer;
29 import ini.trakem2.display.LayerSet;
30 import ini.trakem2.display.Pipe;
31 import ini.trakem2.persistence.Loader;
32 import ini.trakem2.imaging.FloatProcessorT2;
33 import ini.trakem2.vector.VectorString3D;
35 import ij.IJ;
36 import ij.ImagePlus;
37 import ij.Menus;
38 import ij.WindowManager;
39 import ij.gui.GenericDialog;
40 import ij.gui.YesNoCancelDialog;
41 import ij.gui.Roi;
42 import ij.gui.ShapeRoi;
43 import ij.gui.OvalRoi;
44 import ij.text.TextWindow;
45 import ij.measure.ResultsTable;
46 import ij.process.*;
47 import ij.io.*;
48 import ij.process.ImageProcessor;
49 import ij.process.ImageConverter;
51 import java.awt.Checkbox;
52 import java.awt.Choice;
53 import java.awt.Color;
54 import java.awt.Component;
55 import java.awt.MenuBar;
56 import java.awt.Menu;
57 import java.awt.MenuItem;
58 import java.awt.Shape;
59 import java.awt.geom.AffineTransform;
60 import java.awt.geom.Point2D;
61 import java.awt.geom.NoninvertibleTransformException;
62 import java.awt.geom.Area;
63 import java.awt.Rectangle;
64 import java.awt.Polygon;
65 import java.awt.geom.PathIterator;
66 import java.awt.Graphics2D;
67 import java.awt.image.BufferedImage;
68 import java.awt.image.DataBufferByte;
69 import java.io.*;
70 import java.awt.event.ItemListener;
71 import java.awt.event.ItemEvent;
72 import java.awt.event.MouseEvent;
73 import java.awt.event.InputEvent;
74 import java.awt.Event;
75 import javax.swing.SwingUtilities;
77 import java.lang.reflect.Field;
79 import java.util.ArrayList;
80 import java.util.Iterator;
81 import java.util.Vector;
82 import java.util.Calendar;
83 import java.lang.Iterable;
84 import java.util.Iterator;
85 import java.util.Collection;
86 import java.util.Map;
87 import java.util.regex.Pattern;
88 import java.util.regex.Matcher;
90 import javax.vecmath.Point3f;
91 import javax.vecmath.Vector3f;
93 /** Utils class: stores generic widely used methods. In particular, those for logging text messages (for debugging) and also some math and memory utilities.
97 public class Utils implements ij.plugin.PlugIn {
99 static public String version = "0.6a 2008-11-13";
101 static public boolean debug = false;
102 static public boolean debug_mouse = false;
103 static public boolean debug_sql = false;
104 static public boolean debug_event = false;
105 static public boolean debug_clip = false; //clip for repainting
106 static public boolean debug_thing = false;
108 /** The error to use in floating-point or double floating point literal comparisons. */
109 static public final double FL_ERROR = 0.0000001;
111 static public void debug(String msg) {
112 if (debug) IJ.log(msg);
115 static public void debugMouse(String msg) {
116 if (debug_mouse) IJ.log(msg);
119 /** Avoid waiting on the AWT thread repainting ImageJ's log window. */
120 static private final class LogDispatcher extends Thread {
121 private final StringBuffer cache = new StringBuffer();
122 private boolean loading = false;
123 private boolean go = true;
124 public LogDispatcher() {
125 super("T2-Log-Dispatcher");
126 setPriority(Thread.NORM_PRIORITY);
127 try { setDaemon(true); } catch (Exception e) { e.printStackTrace(); }
128 start();
130 public final void quit() {
131 go = false;
132 synchronized (this) { notify(); }
134 public final void log(final String msg) {
135 try {
136 synchronized (cache) {
137 loading = true; // no need to synch, variable setting is atomic
138 if (0 != cache.length()) cache.append('\n');
139 cache.append(msg);
140 loading = false;
142 Thread.yield();
143 if (loading) return;
144 } catch (Exception e) {
145 e.printStackTrace();
147 try {
148 synchronized (this) { notify(); }
149 } catch (Exception e) {
150 e.printStackTrace();
153 public void run() {
154 while (go) {
155 try {
156 synchronized (this) { wait(); }
157 String msg = null;
158 synchronized (cache) {
159 if (0 != cache.length()) msg = cache.toString();
160 cache.setLength(0);
162 IJ.log(msg);
163 } catch (Exception e) {
164 e.printStackTrace();
169 static private LogDispatcher logger = new LogDispatcher();
171 /** Avoid waiting on the AWT thread repainting ImageJ's status bar.
172 Waits 100 ms before printing the status message; if too many status messages are being sent, the last one overrides all. */
173 static private final class StatusDispatcher extends Thread {
174 private String msg = null;
175 private boolean loading = false;
176 private boolean go = true;
177 private double progress = -1;
178 public StatusDispatcher() {
179 super("T2-Status-Dispatcher");
180 setPriority(Thread.NORM_PRIORITY);
181 try { setDaemon(true); } catch (Exception e) { e.printStackTrace(); }
182 start();
184 public final void quit() {
185 go = false;
186 synchronized (this) { notify(); }
188 public final void showStatus(final String msg) {
189 try {
190 synchronized (this) {
191 this.msg = msg;
192 notify();
194 } catch (Exception e) {
195 e.printStackTrace();
198 public final void showProgress(final double progress) {
199 try {
200 synchronized (this) {
201 this.progress = progress;
202 notify();
204 } catch (Exception e) {
205 e.printStackTrace();
208 public void run() {
209 while (go) {
210 try {
211 // first part ensures it gets printed even if the notify was issued while not waiting
212 synchronized (this) {
213 if (null != msg) {
214 IJ.showStatus(msg);
215 msg = null;
217 if (-1 != progress) {
218 IJ.showProgress(progress);
219 progress = -1;
221 wait();
223 // allow some time for overwriting of messages
224 Thread.sleep(100);
225 /* // should not be needed
226 // print the msg if necessary
227 synchronized (this) {
228 if (null != msg) {
229 IJ.showStatus(msg);
230 msg = null;
234 } catch (Exception e) {
235 e.printStackTrace();
240 static private StatusDispatcher status = new StatusDispatcher();
242 /** Initialize house keeping threads. */
243 static public final void setup(final ControlWindow master) { // the ControlWindow acts as a switch: nobody can controls this because the CW constructor is private
244 if (null == status) status = new StatusDispatcher();
245 if (null == logger) logger = new LogDispatcher();
248 /** Destroy house keeping threads. */
249 static public final void destroy(final ControlWindow master) {
250 if (null != status) { status.quit(); status = null; }
251 if (null != logger) { logger.quit(); logger = null; }
254 /** Intended for the user to see. */
255 static public final void log(final String msg) {
256 if (ControlWindow.isGUIEnabled() && null != logger) {
257 logger.log(msg);
258 } else {
259 System.out.println(msg);
263 /** Print in all printable places: log window, System.out.println, and status bar.*/
264 static public final void logAll(final String msg) {
265 if (!ControlWindow.isGUIEnabled()) {
266 System.out.println(msg);
267 return;
269 System.out.println(msg);
270 if (null != logger) logger.log(msg);
271 if (null != status) status.showStatus(msg);
274 /** Intended for developers: prints to terminal. */
275 static public final void log2(final String msg) {
276 System.out.println(msg);
279 /** Pretty-print the object, for example arrays as [0, 1, 2]. */
280 static public final void log2(final Object ob) {
281 Utils.log2(null, ob);
283 /** Pretty-print the object, for example arrays as [0, 1, 2]. */
284 static public final void log2(final String msg, final Object ob) {
285 Utils.log2((null != msg ? msg : "") + ob + "\n\t" + Utils.toString(ob));
287 static public final void log2(final String msg, final Object ob1, final Object... ob) {
288 final StringBuffer sb = new StringBuffer(null == msg ? "" : msg + "\n");
289 sb.append(ob1.toString()).append(" : ").append(Utils.toString(ob1)).append('\n');
290 for (int i=0; i<ob.length; i++) sb.append(ob.toString()).append(" : ").append(Utils.toString(ob[i])).append('\n');
291 sb.setLength(sb.length()-1);
292 Utils.log2(sb.toString());
295 /** Print an object; if it's an array, print each element, recursively, as [0, 1, 2] or [[0, 1, 2], [3, 4, 5]], etc, same for Iterable and Map objects. */
296 static public final String toString(final Object ob) {
297 if (null == ob) return "null";
298 // Clojure could do this so much easier with a macro
299 final StringBuffer sb = new StringBuffer();
300 sb.append('[');
301 char closing = ']';
302 if (ob instanceof String[]) { // could be done with Object[] and recursive calls, but whatever
303 final String[] s = (String[])ob;
304 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
305 } else if (ob instanceof int[]) {
306 final int[] s = (int[])ob;
307 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
308 } else if (ob instanceof double[]) {
309 final double[] s = (double[])ob;
310 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
311 } else if (ob instanceof float[]) {
312 final float[] s = (float[])ob;
313 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
314 } else if (ob instanceof char[]) {
315 final char[] s = (char[])ob;
316 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
317 } else if (ob instanceof Object[]) {
318 final Object[] s = (Object[])ob;
319 for (int i=0; i<s.length; i++) sb.append(Utils.toString(s[i])).append(", ");
320 } else if (ob instanceof Iterable) {
321 final Iterable s = (Iterable)ob;
322 for (Iterator it = s.iterator(); it.hasNext(); ) sb.append(Utils.toString(it.next())).append(", ");
323 } else if (ob instanceof Map) {
324 sb.setCharAt(0, '{');
325 closing = '}';
326 final Map s = (Map)ob;
327 for (Iterator it = s.entrySet().iterator(); it.hasNext(); ) {
328 Map.Entry e = (Map.Entry)it.next();
329 sb.append(Utils.toString(e.getKey())).append(" => ").append(Utils.toString(e.getValue())).append(", ");
331 } else if (ob instanceof long[]) {
332 final long[] s = (long[])ob;
333 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
334 } else if (ob instanceof short[]) {
335 final short[] s = (short[])ob;
336 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
337 } else if (ob instanceof boolean[]) {
338 final boolean[] s = (boolean[])ob;
339 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
340 } else {
341 return ob.toString();
343 sb.setLength(sb.length()-2);
344 sb.append(closing);
345 sb.append('\n');
346 return sb.toString();
349 static public void setDebug(boolean debug) {
350 Utils.debug = debug;
353 static public void setDebugMouse(boolean debug_mouse) {
354 Utils.debug_mouse = debug_mouse;
357 static public void setDebugSQL(boolean debug_sql) {
358 Utils.debug_sql = debug_sql;
361 /** Adjusting so that 0 is 3 o'clock, PI+PI/2 is 12 o'clock, PI is 9 o'clock, and PI/2 is 6 o'clock (why atan2 doesn't output angles this way? I remember I had the same problem for Pipe.java in the A_3D_editing plugin)
362 Using schemata in JavaAngles.ai as reference */
363 static public final double fixAtan2Angle(double angle) {
365 double a = angle;
366 //fix too large angles
367 if (angle > 2*Math.PI) {
368 a = angle - 2*Math.PI;
370 //fix signs and shity oriented angle values given by atan2
371 if (a > 0.0 && a <= Math.PI/2) {
372 a = Math.PI/2 - a;
373 } else if (a <= 0.0 && a >= -Math.PI) {
374 a = Math.PI/2 - a; // minus because angle is negative
375 } else if (a > Math.PI/2 && a <= Math.PI ) {
376 a = Math.PI + Math.PI + (Math.PI/2 - a);
379 return a;
382 static public int count = 0;
384 /** Find out which method from which class called the method where the printCaller is used; for debugging purposes.*/
385 static public final void printCaller(Object called_object) {
386 StackTraceElement[] elems = new Exception().getStackTrace();
387 if (elems.length < 3) {
388 log2("Stack trace too short! No useful info");
389 } else {
390 log2( "#### START TRACE ####\nObject " + called_object.getClass().getName() + " called at: " + elems[1].getFileName() + " " + elems[1].getLineNumber() + ": " + elems[1].getMethodName() + "()\n by: " + elems[2].getClassName() + " " + elems[2].getLineNumber() + ": " + elems[2].getMethodName() + "()");
391 log2("==== END ====");
395 static public final void printCaller(Object called_object, int lines) {
396 StackTraceElement[] elems = new Exception().getStackTrace();
397 if (elems.length < 3) {
398 log2("Stack trace too short! No useful info");
399 } else {
400 log2( "#### START TRACE ####\nObject " + called_object.getClass().getName() + " called at: " + elems[1].getFileName() + " " + elems[1].getLineNumber() + ": " + elems[1].getMethodName() + "()\n by: " + elems[2].getClassName() + " " + elems[2].getLineNumber() + ": " + elems[2].getMethodName() + "()");
401 for (int i=3; i<lines+2 && i<elems.length; i++) {
402 log2("\tby: " + elems[i].getClassName() + " " + elems[i].getLineNumber() + ": " + elems[i].getMethodName() + "()");
404 log2("==== END ====");
408 static public final String caller(Object called) {
409 StackTraceElement[] elems = new Exception().getStackTrace();
410 if (elems.length < 3) {
411 log2("Stack trace too short! No useful info");
412 return null;
413 } else {
414 return elems[2].getClassName();
418 /**Restore ImageJ's MenuBar*/
419 static public final void restoreMenuBar() {
420 MenuBar menu_bar = Menus.getMenuBar();
421 final int n_menus = menu_bar.getMenuCount();
422 for (int i=0; i<n_menus;i++) {
423 Menu menu = menu_bar.getMenu(i);
424 restoreMenu(menu);
426 //make sure there isn't a null menu bar
427 //WindowManager.getCurrentWindow().setMenuBar(menu_bar);
430 static private void restoreMenu(final Menu menu) {
431 final int n_menuitems = menu.getItemCount();
432 for (int i=0; i<n_menuitems; i++) {
433 MenuItem menu_item = menu.getItem(i);
434 if (menu_item instanceof Menu) {
435 restoreMenu((Menu)menu_item);
437 menu_item.setEnabled(true);
441 static public final void showMessage(String msg) {
442 if (!ControlWindow.isGUIEnabled()) System.out.println(msg);
443 else IJ.showMessage(msg);
446 /** Runs the showMessage in a separate Thread. */
447 static public final void showMessageT(final String msg) {
448 new Thread() {
449 public void run() {
450 setPriority(Thread.NORM_PRIORITY);
451 Utils.showMessage(msg);
453 }.start();
456 static public final void showStatus(final String msg, final boolean focus) {
457 if (null == IJ.getInstance() || !ControlWindow.isGUIEnabled() || null == status) {
458 System.out.println(msg);
459 return;
461 if (focus) IJ.getInstance().toFront();
463 status.showStatus(msg);
466 static public final void showStatus(final String msg) {
467 showStatus(msg, false);
470 static private double last_progress = 0;
471 static private int last_percent = 0;
473 static public final void showProgress(final double p) {
474 //IJ.showProgress(p); // never happens, can't repaint even though they are different threads
475 if (null == IJ.getInstance() || !ControlWindow.isGUIEnabled() || null == status) {
476 if (0 == p) {
477 last_progress = 0; // reset
478 last_percent = 0;
479 return;
481 // don't show intervals smaller than 1%:
482 if (last_progress + 0.01 > p ) {
483 int percent = (int)(p * 100);
484 if (last_percent != percent) {
485 System.out.println(percent + " %");
486 last_percent = percent;
489 last_progress = p;
490 return;
493 status.showProgress(p);
496 static public void debugDialog() {
497 // note: all this could nicely be done using reflection, so adding another boolean variable would be automatically added here (filtering by the prefix "debug").
498 GenericDialog gd = new GenericDialog("Debug:");
499 gd.addCheckbox("debug", debug);
500 gd.addCheckbox("debug mouse", debug_mouse);
501 gd.addCheckbox("debug sql", debug_sql);
502 gd.addCheckbox("debug event", debug_event);
503 gd.addCheckbox("debug clip", debug_clip);
504 gd.addCheckbox("debug thing", debug_thing);
505 gd.showDialog();
506 if (gd.wasCanceled()) {
507 return;
509 debug = gd.getNextBoolean();
510 debug_mouse = gd.getNextBoolean();
511 debug_sql = gd.getNextBoolean();
512 debug_event = gd.getNextBoolean();
513 debug_clip = gd.getNextBoolean();
514 debug_thing = gd.getNextBoolean();
518 /** Scan the WindowManager for open stacks.*/
519 static public ImagePlus[] findOpenStacks() {
520 ImagePlus[] imp = scanWindowManager("stacks");
521 if (null == imp) return null;
522 return imp;
525 /** Scan the WindowManager for non-stack images*/
526 static public ImagePlus[] findOpenImages() {
527 ImagePlus[] imp = scanWindowManager("images");
528 if (null == imp) return null;
529 return imp;
532 /** Scan the WindowManager for all open images, including stacks.*/
533 static public ImagePlus[] findAllOpenImages() {
534 return scanWindowManager("all");
537 static private ImagePlus[] scanWindowManager(String type) {
538 // check if any stacks are opened within ImageJ
539 int[] all_ids = WindowManager.getIDList();
540 if (null == all_ids) return null;
541 ImagePlus[] imp = new ImagePlus[all_ids.length];
542 int next = 0;
543 for (int i=0; i < all_ids.length; i++) {
544 ImagePlus image = WindowManager.getImage(all_ids[i]);
545 if (type.equals("stacks")) {
546 if (image.getStackSize() <= 1) {
547 continue;
549 } else if (type.equals("images")) {
550 if (image.getStackSize() > 1) {
551 continue;
554 // add:
555 imp[next] = image;
556 next++;
558 // resize the array if necessary
559 if (next != all_ids.length) {
560 ImagePlus[] imp2 = new ImagePlus[next];
561 System.arraycopy(imp, 0, imp2, 0, next);
562 imp = imp2;
564 // return what has been found:
565 if (0 == next) return null;
566 return imp;
569 /**The path of the directory from which images have been recently loaded.*/
570 static public String last_dir = ij.Prefs.getString(ij.Prefs.DIR_IMAGE);
571 /**The path of the last opened file.*/
572 static public String last_file = null;
574 static public String cutNumber(double d, int n_decimals) {
575 return cutNumber(d, n_decimals, false);
578 /** remove_trailing_zeros will leave at least one zero after the comma if appropriate. */
579 static public final String cutNumber(final double d, final int n_decimals, final boolean remove_trailing_zeros) {
580 final String num = new Double(d).toString();
581 int i_e = num.indexOf("E-");
582 if (-1 != i_e) {
583 final int exp = Integer.parseInt(num.substring(i_e+2));
584 if (n_decimals < exp) {
585 final StringBuffer sb = new StringBuffer("0.");
586 int count = n_decimals;
587 while (count > 0) {
588 sb.append('0');
589 count--;
591 return sb.toString(); // returns 0.000... as many zeros as desired n_decimals
593 // else move comma
594 StringBuffer sb = new StringBuffer("0.");
595 int count = exp -1;
596 while (count > 0) {
597 sb.append('0');
598 count--;
600 sb.append(num.charAt(0)); // the single number before the comma
601 // up to here there are 'exp' number of decimals appended
602 int i_end = 2 + n_decimals - exp;
603 if (i_end > i_e) i_end = i_e; // there arent' that ,any, so cut
604 sb.append(num.substring(2, i_end)); // all numbers after the comma
605 return sb.toString();
607 // else, there is no scientific notation to worry about
608 final int i_dot = num.indexOf('.');
609 final StringBuffer sb = new StringBuffer(num.substring(0, i_dot+1));
610 for (int i=i_dot+1; i < (n_decimals + i_dot + 1) && i < num.length(); i++) {
611 sb.append(num.charAt(i));
613 // remove zeros from the end
614 if (remove_trailing_zeros) {
615 for (int i=sb.length()-1; i>i_dot+1; i--) { // leave at least one zero after the comma
616 if ('0' == sb.charAt(i)) {
617 sb.setLength(i);
618 } else {
619 break;
623 return sb.toString();
626 static public final boolean check(final String msg) {
627 YesNoCancelDialog dialog = new YesNoCancelDialog(IJ.getInstance(), "Execute?", msg);
628 if (dialog.yesPressed()) {
629 return true;
631 return false;
634 static public final boolean checkYN(String msg) {
635 YesNoDialog yn = new YesNoDialog(IJ.getInstance(), "Execute?", msg);
636 if (yn.yesPressed()) return true;
637 return false;
640 static public final String d2s(double d, int n_decimals) {
641 return IJ.d2s(d, n_decimals);
644 // from utilities.c in my CurveMorphing C module ... from C! Java is a low level language with the disadvantages of the high level languages ...
645 /** Returns the angle in radians of the given polar coordinates, correcting the Math.atan2 output. */
646 static public final double getAngle(double x, double y) {
647 // calculate angle
648 double a = Math.atan2(x, y);
649 // fix too large angles (beats me why are they ever generated)
650 if (a > 2 * Math.PI) {
651 a = a - 2 * Math.PI;
653 // fix atan2 output scheme to match my mental scheme
654 if (a >= 0.0 && a <= Math.PI/2) {
655 a = Math.PI/2 - a;
656 } else if (a < 0 && a >= -Math.PI) {
657 a = Math.PI/2 -a;
658 } else if (a > Math.PI/2 && a <= Math.PI) {
659 a = Math.PI + Math.PI + Math.PI/2 - a;
661 // return
662 return a;
665 static public final String[] getHexRGBColor(Color color) {
666 int c = color.getRGB();
667 String r = Integer.toHexString(((c&0x00FF0000)>>16));
668 if (1 == r.length()) r = "0" + r;
669 String g = Integer.toHexString(((c&0x0000FF00)>>8));
670 if (1 == g.length()) g = "0" + g;
671 String b = Integer.toHexString((c&0x000000FF));
672 if (1 == b.length()) b = "0" + b;
673 return new String[]{r, g, b};
676 static public final Color getRGBColorFromHex(String hex) {
677 if (hex.length() < 6) return null;
678 return new Color(Integer.parseInt(hex.substring(0, 2), 16), // parse in hexadecimal radix
679 Integer.parseInt(hex.substring(2, 4), 16),
680 Integer.parseInt(hex.substring(4, 6), 16));
683 static public final int[] get4Ints(final int hex) {
684 return new int[]{((hex&0xFF000000)>>24),
685 ((hex&0x00FF0000)>>16),
686 ((hex&0x0000FF00)>> 8),
687 hex&0x000000FF };
690 public void run(String arg) {
691 IJ.showMessage("TrakEM2", "TrakEM2 " + Utils.version + "\nCopyright Albert Cardona & Rodney Douglas\nInstitute for Neuroinformatics, Univ. Zurich / ETH\nUniversity of California Los Angeles");
694 static public final File chooseFile(String name, String extension) {
695 return Utils.chooseFile(null, name, extension);
698 /** Select a file from the file system, for saving purposes. Prompts for overwritting if the file exists, unless the ControlWindow.isGUIEnabled() returns false (i.e. there is no GUI). */
699 static public final File chooseFile(String default_dir, String name, String extension) {
700 // using ImageJ's JFileChooser or internal FileDialog, according to user preferences.
701 String user = System.getProperty("user.name");
702 String name2 = null;
703 if (null != name && null != extension) name2 = name + extension;
704 else if (null != name) name2 = name;
705 else if (null != extension) name2 = "untitled" + extension;
706 if (null != default_dir) {
707 OpenDialog.setDefaultDirectory(default_dir);
709 SaveDialog sd = new SaveDialog("Save",
710 OpenDialog.getDefaultDirectory(),
711 name2,
712 extension);
714 String filename = sd.getFileName();
715 if (null == filename || filename.toLowerCase().startsWith("null")) return null;
716 File f = new File(sd.getDirectory() + "/" + filename);
717 if (f.exists() && ControlWindow.isGUIEnabled()) {
718 YesNoCancelDialog d = new YesNoCancelDialog(IJ.getInstance(), "Overwrite?", "File " + filename + " exists! Overwrite?");
719 if (d.cancelPressed()) {
720 return null;
721 } else if (!d.yesPressed()) {
722 return chooseFile(name, extension);
724 // else if yes pressed, overwrite.
726 return f;
729 /** Returns null or the selected directory and file. */
730 static public final String[] selectFile(String title_msg) {
731 OpenDialog od = new OpenDialog("Select file", OpenDialog.getDefaultDirectory(), null);
732 String file = od.getFileName();
733 if (null == file || file.toLowerCase().startsWith("null")) return null;
734 String dir = od.getDirectory();
735 File f = null;
736 if (null != dir) {
737 dir = dir.replace('\\', '/');
738 if (!dir.endsWith("/")) dir += "/";
739 f = new File(dir + "/" + file); // I'd use File.separator, but in Windows it fails
741 if (null == dir || !f.exists()) {
742 Utils.log2("No proper file selected.");
743 return null;
745 return new String[]{dir, file};
748 static public final boolean saveToFile(File f, String contents) {
749 if (null == f) return false;
750 try {
752 DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f), contents.length()));
754 OutputStreamWriter dos = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(f), contents.length()), "8859_1"); // encoding in Latin 1 (for macosx not to mess around
755 //dos.writeBytes(contents);
756 dos.write(contents, 0, contents.length());
757 dos.flush();
758 } catch (Exception e) {
759 IJError.print(e);
760 Utils.showMessage("ERROR: Most likely did NOT save your file.");
761 return false;
763 return true;
766 static public final boolean saveToFile(String name, String extension, String contents) {
767 if (null == contents) {
768 Utils.log2("Utils.saveToFile: contents is null");
769 return false;
771 // save the file
772 File f = Utils.chooseFile(name, extension);
773 return Utils.saveToFile(f, contents);
776 /** Converts sequences of spaces into single space, and trims the ends. */
777 static public final String cleanString(String s) {
778 s = s.trim();
779 while (-1 != s.indexOf("\u0020\u0020")) { // \u0020 equals a single space
780 s = s.replaceAll("\u0020\u0020", "\u0020");
782 return s;
785 static public final String openTextFile(final String path) {
786 if (null == path || !new File(path).exists()) return null;
787 final StringBuffer sb = new StringBuffer();
788 BufferedReader r = null;
789 try {
790 r = new BufferedReader(new FileReader(path));
791 while (true) {
792 String s = r.readLine();
793 if (null == s) break;
794 sb.append(s).append('\n'); // I am sure the reading can be done better
796 } catch (Exception e) {
797 IJError.print(e);
798 if (null != r) try { r.close(); } catch (IOException ioe) { ioe.printStackTrace(); }
799 return null;
801 return sb.toString();
804 /** Returns the file found at path as an array of lines, or null if not found. */
805 static public final String[] openTextFileLines(final String path) {
806 if (null == path || !new File(path).exists()) return null;
807 final ArrayList al = new ArrayList();
808 try {
809 BufferedReader r = new BufferedReader(new FileReader(path));
810 while (true) {
811 String s = r.readLine();
812 if (null == s) break;
813 al.add(s);
815 r.close();
816 } catch (Exception e) {
817 IJError.print(e);
818 return null;
820 final String[] sal = new String[al.size()];
821 al.toArray(sal);
822 return sal;
825 static public final char[] openTextFileChars(final String path) {
826 File f = null;
827 if (null == path || !(f = new File(path)).exists()) {
828 Utils.log("File not found: " + path);
829 return null;
831 final char[] src = new char[(int)f.length()]; // assumes file is small enough to fit in integer range!
832 try {
833 BufferedReader r = new BufferedReader(new FileReader(path));
834 r.read(src, 0, src.length);
835 r.close();
836 } catch (Exception e) {
837 IJError.print(e);
838 return null;
840 return src;
843 /** The cosinus between two vectors (in polar coordinates), by means of the dot product. */
844 static public final double getCos(final double x1, final double y1, final double x2, final double y2) {
845 return (x1 * x2 + y1 * y2) / (Math.sqrt(x1*x1 + y1*y1) * Math.sqrt(x2*x2 + y2*y2));
848 static public final String removeExtension(final String path) {
849 final int i_dot = path.lastIndexOf('.');
850 if (-1 == i_dot || i_dot + 4 != path.length()) return path;
851 else return path.substring(0, i_dot);
854 /** A helper for GenericDialog checkboxes to control other the enabled state of other GUI elements in the same dialog. */
855 static public final void addEnablerListener(final Checkbox master, final Component[] enable, final Component[] disable) {
856 master.addItemListener(new ItemListener() {
857 public void itemStateChanged(ItemEvent ie) {
858 if (ie.getStateChange() == ItemEvent.SELECTED) {
859 process(enable, true);
860 process(disable, false);
861 } else {
862 process(enable, false);
863 process(disable, true);
866 private void process(final Component[] c, final boolean state) {
867 if (null == c) return;
868 for (int i=0; i<c.length; i++) c[i].setEnabled(state);
873 static private int n_CPUs = 0;
875 /** This method is obsolete: there's Runtime.getRuntime().availableProcessors() */
877 static public final int getCPUCount() {
878 if (0 != n_CPUs) return n_CPUs;
879 if (IJ.isWindows()) return 1; // no clue
880 // POSIX systems, attempt to count CPUs from /proc/stat
881 try {
882 Runtime runtime = Runtime.getRuntime();
883 Process process = runtime.exec("cat /proc/stat");
884 InputStream is = process.getInputStream();
885 InputStreamReader isr = new InputStreamReader(is);
886 BufferedReader br = new BufferedReader(isr);
887 String line;
888 n_CPUs = 0;
889 // valid cores will print as cpu0, cpu1. cpu2 ...
890 while ((line = br.readLine()) != null) {
891 if (0 == line.indexOf("cpu") && line.length() > 3 && Character.isDigit(line.charAt(3))) {
892 n_CPUs++;
895 // fix possible errors
896 if (0 == n_CPUs) n_CPUs = 1;
897 return n_CPUs;
898 } catch (Exception e) {
899 Utils.log(e.toString()); // just one line
900 return 1;
905 static public final boolean wrongImageJVersion() {
906 boolean b = IJ.versionLessThan("1.37g");
907 if (b) Utils.showMessage("TrakEM2 requires ImageJ 1.37g or above.");
908 return b;
911 static public final boolean java3d = isJava3DInstalled();
913 static private final boolean isJava3DInstalled() {
914 try {
915 Class p3f = Class.forName("javax.vecmath.Point3f");
916 } catch (ClassNotFoundException cnfe) {
917 return false;
919 return true;
922 static public final void addLayerRangeChoices(final Layer selected, final GenericDialog gd) {
923 Utils.addLayerRangeChoices(selected, selected, gd);
926 static public final void addLayerRangeChoices(final Layer first, final Layer last, final GenericDialog gd) {
927 final String[] layers = new String[first.getParent().size()];
928 final ArrayList al_layer_titles = new ArrayList();
929 int i = 0;
930 for (Iterator it = first.getParent().getLayers().iterator(); it.hasNext(); ) {
931 layers[i] = first.getProject().findLayerThing((Layer)it.next()).toString();
932 al_layer_titles.add(layers[i]);
933 i++;
935 final int i_first = first.getParent().indexOf(first);
936 final int i_last = last.getParent().indexOf(last);
937 gd.addChoice("Start: ", layers, layers[i_first]);
938 final Vector v = gd.getChoices();
939 final Choice cstart = (Choice)v.get(v.size()-1);
940 gd.addChoice("End: ", layers, layers[i_last]);
941 final Choice cend = (Choice)v.get(v.size()-1);
942 cstart.addItemListener(new ItemListener() {
943 public void itemStateChanged(ItemEvent ie) {
944 int index = al_layer_titles.indexOf(ie.getItem());
945 if (index > cend.getSelectedIndex()) cend.select(index);
948 cend.addItemListener(new ItemListener() {
949 public void itemStateChanged(ItemEvent ie) {
950 int index = al_layer_titles.indexOf(ie.getItem());
951 if (index < cstart.getSelectedIndex()) cstart.select(index);
956 static public final void addLayerChoice(final String label, final Layer selected, final GenericDialog gd) {
957 final String[] layers = new String[selected.getParent().size()];
958 final ArrayList al_layer_titles = new ArrayList();
959 int i = 0;
960 for (Iterator it = selected.getParent().getLayers().iterator(); it.hasNext(); ) {
961 layers[i] = selected.getProject().findLayerThing((Layer)it.next()).toString();
962 al_layer_titles.add(layers[i]);
963 i++;
965 final int i_layer = selected.getParent().indexOf(selected);
966 gd.addChoice(label, layers, layers[i_layer]);
970 static public final void addRGBColorSliders(final GenericDialog gd, final Color color) {
971 gd.addSlider("Red: ", 0, 255, color.getRed());
972 gd.addSlider("Green: ", 0, 255, color.getGreen());
973 gd.addSlider("Blue: ", 0, 255, color.getBlue());
976 /** Converts the ImageProcessor to an ImageProcessor of the given type, or the same if of equal type. */
977 static final public ImageProcessor convertTo(final ImageProcessor ip, final int type, final boolean scaling) {
978 switch (type) {
979 case ImagePlus.GRAY8:
980 return ip.convertToByte(scaling);
981 case ImagePlus.GRAY16:
982 return ip.convertToShort(scaling);
983 case ImagePlus.GRAY32:
984 return ip.convertToFloat();
985 case ImagePlus.COLOR_RGB:
986 return ip.convertToRGB();
987 case ImagePlus.COLOR_256:
988 ImagePlus imp = new ImagePlus("", ip.convertToRGB());
989 new ImageConverter(imp).convertRGBtoIndexedColor(256);
990 return imp.getProcessor();
991 default:
992 return null;
996 /** Will make a new double[] array, then fit in it as many points from the given array as possible according to the desired new length. If the new length is shorter that a.length, it will shrink and crop from the end; if larger, the extra spaces will be set with zeros. */
997 static public final double[] copy(final double[] a, final int new_length) {
998 final double[] b = new double[new_length];
999 final int len = a.length > new_length ? new_length : a.length;
1000 System.arraycopy(a, 0, b, 0, len);
1001 return b;
1004 static public final double[] copy(final double[] a, final int first, final int new_length) {
1005 final double[] b = new double[new_length];
1006 final int len = new_length < a.length - first ? new_length : a.length - first;
1007 System.arraycopy(a, first, b, 0, len);
1008 return b;
1011 /** Reverse in place an array of doubles. */
1012 static public final void reverse(final double[] a) {
1013 for (int left=0, right=a.length-1; left<right; left++, right--) {
1014 double tmp = a[left];
1015 a[left] = a[right];
1016 a[right] = tmp;
1020 static public final double[] toDouble(final int[] a, final int len) {
1021 final double[] b = new double[len];
1022 for (int i=0; i<len; i++) b[i] = a[i];
1023 return b;
1026 static public final int[] toInt(final double[] a, final int len) {
1027 final int[] b = new int[len];
1028 for (int i=0; i<len; i++) b[i] = (int) a[i];
1029 return b;
1032 /** OS-agnostic diagnosis of whether the click was for the contextual popup menu. */
1033 static public final boolean isPopupTrigger(final MouseEvent me) {
1034 return me.isPopupTrigger() || 0 != (me.getModifiers() & Event.META_MASK);
1037 /** Repaint the given Component on the swing repaint thread (aka "SwingUtilities.invokeLater"). */
1038 static public final void updateComponent(final Component c) {
1039 //c.invalidate();
1040 //c.validate();
1041 // ALL that was needed: to put it into the swing repaint queue ... couldn't they JUST SAY SO
1042 SwingUtilities.invokeLater(new Runnable() {
1043 public void run() {
1044 c.repaint();
1048 /** Like calling pack() on a Frame but on a Component. */
1049 static public final void revalidateComponent(final Component c) {
1050 SwingUtilities.invokeLater(new Runnable() {
1051 public void run() {
1052 c.invalidate();
1053 c.validate();
1054 c.repaint();
1059 static public final Point2D.Double transform(final AffineTransform affine, final double x, final double y) {
1060 final Point2D.Double pSrc = new Point2D.Double(x, y);
1061 if (affine.isIdentity()) return pSrc;
1062 final Point2D.Double pDst = new Point2D.Double();
1063 affine.transform(pSrc, pDst);
1064 return pDst;
1066 static public final Point2D.Double inverseTransform(final AffineTransform affine, final double x, final double y) {
1067 final Point2D.Double pSrc = new Point2D.Double(x, y);
1068 if (affine.isIdentity()) return pSrc;
1069 final Point2D.Double pDst = new Point2D.Double();
1070 try {
1071 affine.createInverse().transform(pSrc, pDst);
1072 } catch (NoninvertibleTransformException nite) {
1073 IJError.print(nite);
1075 return pDst;
1078 /** Returns the time as HH:MM:SS */
1079 static public final String now() {
1080 /* Java time management is retarded. */
1081 final Calendar c = Calendar.getInstance();
1082 final int hour = c.get(Calendar.HOUR_OF_DAY);
1083 final int min = c.get(Calendar.MINUTE);
1084 final int sec = c.get(Calendar.SECOND);
1085 final StringBuffer sb = new StringBuffer();
1086 if (hour < 10) sb.append('0');
1087 sb.append(hour).append(':');
1088 if (min < 10) sb.append('0');
1089 sb.append(min).append(':');
1090 if (sec < 10) sb.append(0);
1091 return sb.append(sec).toString();
1094 static public final void sleep(final long miliseconds) {
1095 try { Thread.currentThread().sleep(miliseconds); } catch (Exception e) { e.printStackTrace(); }
1098 /** Mix colors visually: red + green = yellow, etc.*/
1099 static public final Color mix(Color c1, Color c2) {
1100 final float[] b = Color.RGBtoHSB(c1.getRed(), c1.getGreen(), c1.getBlue(), new float[3]);
1101 final float[] c = Color.RGBtoHSB(c2.getRed(), c2.getGreen(), c2.getBlue(), new float[3]);
1102 final float[] a = new float[3];
1103 // find to which side the hue values are closer, since hue space is a a circle
1104 // hue values all between 0 and 1
1105 float h1 = b[0];
1106 float h2 = c[0];
1107 if (h1 < h2) {
1108 float tmp = h1;
1109 h1 = h2;
1110 h2 = tmp;
1112 float d1 = h2 - h1;
1113 float d2 = 1 + h1 - h2;
1114 if (d1 < d2) {
1115 a[0] = h1 + d1 / 2;
1116 } else {
1117 a[0] = h2 + d2 / 2;
1118 if (a[0] > 1) a[0] -= 1;
1121 for (int i=1; i<3; i++) a[i] = (b[i] + c[i]) / 2; // only Saturation and Brightness can be averaged
1122 return Color.getHSBColor(a[0], a[1], a[2]);
1125 /** Test whether the areas intersect each other. */
1126 static public final boolean intersects(final Area a1, final Area a2) {
1127 final Area b = new Area(a1);
1128 b.intersect(a2);
1129 final java.awt.Rectangle r = b.getBounds();
1130 return 0 != r.width && 0 != r.height;
1133 /** 1 A, 2 B, 3 C --- 26 - z, 27 AA, 28 AB, 29 AC --- 26*27 AAA */
1134 static public final String getCharacter(int i) {
1135 i--;
1136 int k = i / 26;
1137 char c = (char)((i % 26) + 65); // 65 is 'A'
1138 if (0 == k) return Character.toString(c);
1139 return new StringBuffer().append(getCharacter(k)).append(c).toString();
1142 static private Field shape_field = null;
1144 static public final Shape getShape(final ShapeRoi roi) {
1145 try {
1146 if (null == shape_field) {
1147 shape_field = ShapeRoi.class.getDeclaredField("shape");
1148 shape_field.setAccessible(true);
1150 return (Shape)shape_field.get(roi);
1151 } catch (Exception e) {
1152 IJError.print(e);
1154 return null;
1157 /** Get by reflection a private or protected field in the given object. */
1158 static public final Object getField(final Object ob, final String field_name) {
1159 if (null == ob || null == field_name) return null;
1160 try {
1161 Field f = ob.getClass().getDeclaredField(field_name);
1162 f.setAccessible(true);
1163 return f.get(ob);
1164 } catch (Exception e) {
1165 IJError.print(e);
1167 return null;
1170 static public final Area getArea(final Roi roi) {
1171 if (null == roi) return null;
1172 ShapeRoi sroi = new ShapeRoi(roi);
1173 AffineTransform at = new AffineTransform();
1174 Rectangle bounds = sroi.getBounds();
1175 at.translate(bounds.x, bounds.y);
1176 Area area = new Area(getShape(sroi));
1177 return area.createTransformedArea(at);
1180 static public final boolean isEmpty(final Area area) {
1181 final Rectangle b = area.getBounds();
1182 return 0 == b.width || 0 == b.height;
1185 /** Returns the approximated area of the given Area object. */
1186 static public final double measureArea(Area area, final Loader loader) {
1187 double sum = 0;
1188 try {
1189 Rectangle bounds = area.getBounds();
1190 double scale = 1;
1191 if (bounds.width > 2048 || bounds.height > 2048) {
1192 scale = 2048.0 / bounds.width;
1194 if (0 == scale) {
1195 Utils.log("Can't measure: area too large, out of scale range for approximation.");
1196 return sum;
1198 AffineTransform at = new AffineTransform();
1199 at.translate(-bounds.x, -bounds.y);
1200 at.scale(scale, scale);
1201 area = area.createTransformedArea(at);
1202 bounds = area.getBounds();
1203 if (0 == bounds.width || 0 == bounds.height) {
1204 Utils.log("Can't measure: area too large, approximates zero.");
1205 return sum;
1207 if (null != loader) loader.releaseToFit(bounds.width * bounds.height * 3);
1208 BufferedImage bi = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_BYTE_INDEXED);
1209 Graphics2D g = bi.createGraphics();
1210 g.setColor(Color.white);
1211 g.fill(area);
1212 final byte[] pixels = ((DataBufferByte)bi.getRaster().getDataBuffer()).getData(); // buffer.getData();
1213 for (int i=pixels.length-1; i>-1; i--) {
1214 //if (255 == (pixels[i]&0xff)) sum++;
1215 if (0 != pixels[i]) sum++;
1217 bi.flush();
1218 g.dispose();
1219 if (1 != scale) sum = sum / (scale * scale);
1220 } catch (Throwable e) {
1221 IJError.print(e);
1223 return sum;
1226 /** Compute the area of the triangle defined by 3 points in 3D space, returning half of the length of the vector resulting from the cross product of vectors p1p2 and p1p3. */
1227 static public final double measureArea(final Point3f p1, final Point3f p2, final Point3f p3) {
1228 final Vector3f v = new Vector3f();
1229 v.cross(new Vector3f(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z),
1230 new Vector3f(p3.x - p1.x, p3.y - p1.y, p3.z - p1.z));
1231 return 0.5 * Math.abs(v.x * v.x + v.y * v.y + v.z * v.z);
1234 /** Returns true if the roi is of closed shape type like an OvalRoi, ShapeRoi, a Roi rectangle, etc. */
1235 static public final boolean isAreaROI(final Roi roi) {
1236 switch (roi.getType()) {
1237 case Roi.POLYLINE:
1238 case Roi.FREELINE:
1239 case Roi.LINE:
1240 case Roi.POINT:
1241 return false;
1243 return true;
1246 static public final Collection<Polygon> getPolygons(Area area) {
1247 final ArrayList<Polygon> pols = new ArrayList<Polygon>();
1248 Polygon pol = new Polygon();
1250 final float[] coords = new float[6];
1251 for (PathIterator pit = area.getPathIterator(null); !pit.isDone(); ) {
1252 int seg_type = pit.currentSegment(coords);
1253 switch (seg_type) {
1254 case PathIterator.SEG_MOVETO:
1255 case PathIterator.SEG_LINETO:
1256 pol.addPoint((int)coords[0], (int)coords[1]);
1257 break;
1258 case PathIterator.SEG_CLOSE:
1259 pols.add(pol);
1260 pol = new Polygon();
1261 break;
1262 default:
1263 Utils.log2("WARNING: unhandled seg type.");
1264 break;
1266 pit.next();
1267 if (pit.isDone()) {
1268 break;
1271 return pols;
1274 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1275 static public final FloatProcessor fastConvertToFloat(final ByteProcessor ip) {
1276 final byte[] pix = (byte[])ip.getPixels();
1277 final float[] data = new float[pix.length];
1278 for (int i=0; i<pix.length; i++) data[i] = pix[i]&0xff;
1279 final FloatProcessor fp = new FloatProcessorT2(ip.getWidth(), ip.getHeight(), data, ip.getColorModel(), ip.getMin(), ip.getMax());
1280 return fp;
1282 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1283 static public final FloatProcessor fastConvertToFloat(final ShortProcessor ip) {
1284 final short[] pix = (short[])ip.getPixels();
1285 final float[] data = new float[pix.length];
1286 for (int i=0; i<pix.length; i++) data[i] = pix[i]&0xffff;
1287 final FloatProcessor fp = new FloatProcessorT2(ip.getWidth(), ip.getHeight(), data, ip.getColorModel(), ip.getMin(), ip.getMax());
1288 return fp;
1290 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1291 static public final FloatProcessor fastConvertToFloat(final ImageProcessor ip, final int type) {
1292 switch (type) {
1293 case ImagePlus.GRAY16: return fastConvertToFloat((ShortProcessor)ip);
1294 case ImagePlus.GRAY32: return (FloatProcessor)ip;
1295 case ImagePlus.GRAY8:
1296 case ImagePlus.COLOR_256: return fastConvertToFloat((ByteProcessor)ip);
1297 case ImagePlus.COLOR_RGB: return (FloatProcessor)ip.convertToFloat(); // SLOW
1299 return null;
1301 static public final FloatProcessor fastConvertToFloat(final ImageProcessor ip) {
1302 if (ip instanceof ByteProcessor) return fastConvertToFloat((ByteProcessor)ip);
1303 if (ip instanceof ShortProcessor) return fastConvertToFloat((ShortProcessor)ip);
1304 return (FloatProcessor)ip.convertToFloat();
1307 /** Creates a new ResultsTable with the given window title and column titles, and 2 decimals of precision, or if one exists for the given window title, returns it. */
1308 static public final ResultsTable createResultsTable(final String title, final String[] columns) {
1309 TextWindow tw = (TextWindow)WindowManager.getFrame(title);
1310 if (null != tw) {
1311 // hacking again ... missing a getResultsTable() method in TextWindow
1312 ResultsTable rt = (ResultsTable)Utils.getField(tw.getTextPanel(), "rt");
1313 if (null != rt) return rt; // assumes columns will be identical
1315 // else create a new one
1316 ResultsTable rt = new ResultsTable();
1317 rt.setPrecision(2);
1318 for (int i=0; i<columns.length; i++) rt.setHeading(i, columns[i]);
1320 return rt;
1323 static public final ImageProcessor createProcessor(final int type, final int width, final int height) {
1324 switch (type) {
1325 case ImagePlus.GRAY8: return new ByteProcessor(width, height);
1326 case ImagePlus.GRAY16: return new ShortProcessor(width, height);
1327 case ImagePlus.GRAY32: return new FloatProcessor(width, height);
1328 case ImagePlus.COLOR_RGB: return new ColorProcessor(width, height);
1330 return null;
1333 /** Paints an approximation of the pipe into the set of slices. */
1334 public void paint(final Pipe pipe, final Map<Layer,ImageProcessor> slices, final int value, final float scale) {
1335 VectorString3D vs = pipe.asVectorString3D();
1336 vs.resample(1); // one pixel
1337 double[] px = vs.getPoints(0);
1338 double[] py = vs.getPoints(1);
1339 double[] pz = vs.getPoints(2);
1340 double[] pr = vs.getDependent(0);
1341 // For each point
1342 for (int i=0; i<px.length-1; i++) {
1343 ImageProcessor ip = slices.get(pipe.getLayerSet().getNearestLayer(pz[i]));
1344 if (null == ip) continue;
1345 OvalRoi ov = new OvalRoi((int)((px[i] - pr[i]) * scale),
1346 (int)((py[i] - pr[i]) * scale),
1347 (int)(pr[i]*2*scale), (int)(pr[i]*2*scale));
1348 ip.setRoi(ov);
1349 ip.setValue(value);
1350 ip.fill(ip.getMask());
1354 static final public boolean matches(final String pattern, final String s) {
1355 return Pattern.compile(pattern).matcher(s).matches();
1358 static final public boolean isValidIdentifier(final String s) {
1359 if (null == s) return false;
1360 if (!Utils.matches("^[a-zA-Z]+[a-zA-Z1-9_]*$", s)) {
1361 Utils.log("Invalid identifier " + s);
1362 return false;
1364 return true;
1368 user=> (def pat #"\b[a-zA-Z]+[\w]*\b")
1369 #'user/pat
1370 user=>(re-seq pat "abc def 1a334")
1371 ("abc" "def")
1372 user=> (re-seq pat "abc def a334")
1373 ("abc" "def" "a334")
1375 Then concatenate all good words with underscores.
1376 Returns null when nothing valid is found in 's'.
1378 static final public String makeValidIdentifier(final String s) {
1379 if (null == s) return null;
1380 // Concatenate all good groups with underscores:
1381 final Pattern pat = Pattern.compile("\\b[a-zA-Z]+[\\w]*\\b");
1382 final Matcher m = pat.matcher(s);
1383 final StringBuffer sb = new StringBuffer();
1384 while (m.find()) {
1385 sb.append(m.group()).append('_');
1387 if (0 == sb.length()) return null;
1388 // Remove last underscore
1389 sb.setLength(sb.length()-1);
1390 return sb.toString();
1393 static final public int indexOf(final Object needle, final Object[] haystack) {
1394 for (int i=0; i<haystack.length; i++) {
1395 if (haystack[i].equals(needle)) return i;
1397 return -1;
1400 /** Remove the file, or if it's a directory, recursively go down subdirs and remove all contents, but will stop on encountering a non-hidden file that is not an empty dir. */
1401 static public final boolean removeFile(final File f) {
1402 return Utils.removeFile(f, true, null);
1405 // Accumulates removed files (not directories) into removed_paths, if not null.
1406 static private final boolean removeFile(final File f, final boolean stop_if_dir_not_empty, final ArrayList<String> removed_paths) {
1407 if (null == f || !f.exists()) return false;
1408 try {
1409 if (!Utils.isTrakEM2Subfile(f)) {
1410 Utils.log2("REFUSING to remove file " + f + "\n-->REASON: not in a '/trakem2.' file path");
1411 return false;
1414 // If it's not a directory, just delete it
1415 if (!f.isDirectory()) {
1416 return f.delete();
1418 // Else delete all directories:
1419 final ArrayList<File> dirs = new ArrayList<File>();
1420 dirs.add(f);
1421 // Non-recursive version ... I hate java
1422 do {
1423 int i = dirs.size() -1;
1424 final File fdir = dirs.get(i);
1425 Utils.log2("Examining folder for deletion: " + fdir.getName());
1426 boolean remove = true;
1427 for (final File file : fdir.listFiles()) {
1428 String name = file.getName();
1429 if (name.equals(".") || name.equals("..")) continue;
1430 if (file.isDirectory()) {
1431 remove = false;
1432 dirs.add(file);
1433 } else if (file.isHidden()) {
1434 if (!file.delete()) {
1435 Utils.log("Failed to delete hidden file " + file.getAbsolutePath());
1436 return false;
1438 if (null != removed_paths) removed_paths.add(file.getAbsolutePath());
1439 } else if (stop_if_dir_not_empty) {
1440 //Utils.log("Not empty: cannot remove dir " + fdir.getAbsolutePath());
1441 return false;
1442 } else {
1443 if (!file.delete()) {
1444 Utils.log("Failed to delete visible file " + file.getAbsolutePath());
1445 return false;
1447 if (null != removed_paths) removed_paths.add(file.getAbsolutePath());
1450 if (remove) {
1451 dirs.remove(i);
1452 if (!fdir.delete()) {
1453 return false;
1454 } else {
1455 Utils.log2("Removed folder " + fdir.getAbsolutePath());
1458 } while (dirs.size() > 0);
1460 return true;
1462 } catch (Exception e) {
1463 IJError.print(e);
1465 return false;
1468 /** Returns true if the file cannonical path contains "/trakem2." (adjusted for Windows as well). */
1469 static public boolean isTrakEM2Subfile(final File f) throws Exception {
1470 return isTrakEM2Subpath(f.getCanonicalPath());
1473 /** Returns true if the path contains "/trakem2." (adjusted for Windows as well). */
1474 static public boolean isTrakEM2Subpath(String path) {
1475 if (IJ.isWindows()) path = path.replace('\\', '/');
1476 return -1 != path.toLowerCase().indexOf("/trakem2.");
1479 /** Returns true if all files and their subdirectories, recursively, under parent folder have been removed.
1480 * For safety reasons, this function will return false immediately if the parent file path does not include a
1481 * lowercase "trakem2." in it.
1482 * If removed_paths is not null, all removed full paths are added to it.
1484 static public final boolean removePrefixedFiles(final File parent, final String prefix, final ArrayList<String> removed_paths) {
1485 if (null == parent || !parent.isDirectory()) return false;
1487 try {
1488 if (!Utils.isTrakEM2Subfile(parent)) {
1489 Utils.log2("REFUSING to remove files recursively under folder " + parent + "\n-->REASON: not in a '/trakem2.' file path");
1490 return false;
1493 boolean success = true;
1495 final File[] list = parent.listFiles(new FilenameFilter() {
1496 public boolean accept(File dir, String name) {
1497 if (name.startsWith(prefix)) return true;
1498 return false;
1502 ArrayList<String> a = null;
1503 if (null != removed_paths) a = new ArrayList<String>();
1505 if (null != list && list.length > 0) {
1506 for (final File f : list) {
1507 if (!Utils.removeFile(f, false, a)) success = false;
1508 if (null != removed_paths) {
1509 removed_paths.addAll(a);
1510 a.clear();
1515 return true;
1517 } catch (Exception e) {
1518 IJError.print(e);
1520 return false;
1523 /** The CTRL key functionality is passed over to the COMMAND key (aka META key) in a MacOSX. */
1524 static public final int getControlModifier() {
1525 return IJ.isMacOSX() ? InputEvent.META_MASK
1526 : InputEvent.CTRL_MASK;
1529 /** The CTRL key functionality is passed over to the COMMAND key (aka META key) in a MacOSX. */
1530 static public final boolean isControlDown(final InputEvent e) {
1531 return IJ.isMacOSX() ? e.isMetaDown()
1532 : e.isControlDown();