auto-finalize constant properties and parameters in Utils
[trakem2.git] / TrakEM2_ / src / main / java / ini / trakem2 / utils / Utils.java
blob549e7264ab726eb63e3ddbe403c21576a38b5ee9
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005-2009 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 ij.IJ;
27 import ij.ImagePlus;
28 import ij.Menus;
29 import ij.WindowManager;
30 import ij.gui.GenericDialog;
31 import ij.gui.OvalRoi;
32 import ij.gui.YesNoCancelDialog;
33 import ij.io.OpenDialog;
34 import ij.io.SaveDialog;
35 import ij.measure.ResultsTable;
36 import ij.process.ByteProcessor;
37 import ij.process.ColorProcessor;
38 import ij.process.FloatProcessor;
39 import ij.process.ImageConverter;
40 import ij.process.ImageProcessor;
41 import ij.process.ShortProcessor;
42 import ij.text.TextWindow;
43 import ini.trakem2.ControlWindow;
44 import ini.trakem2.Project;
45 import ini.trakem2.display.Displayable;
46 import ini.trakem2.display.Layer;
47 import ini.trakem2.display.Pipe;
48 import ini.trakem2.display.YesNoDialog;
49 import ini.trakem2.imaging.FloatProcessorT2;
50 import ini.trakem2.persistence.Loader;
51 import ini.trakem2.plugin.TPlugIn;
52 import ini.trakem2.tree.ProjectThing.Profile_List;
53 import ini.trakem2.vector.VectorString3D;
55 import java.awt.Checkbox;
56 import java.awt.Choice;
57 import java.awt.Color;
58 import java.awt.Component;
59 import java.awt.Container;
60 import java.awt.Dimension;
61 import java.awt.Event;
62 import java.awt.EventQueue;
63 import java.awt.Font;
64 import java.awt.FontMetrics;
65 import java.awt.Image;
66 import java.awt.Menu;
67 import java.awt.MenuBar;
68 import java.awt.MenuItem;
69 import java.awt.event.ActionEvent;
70 import java.awt.event.ActionListener;
71 import java.awt.event.InputEvent;
72 import java.awt.event.ItemEvent;
73 import java.awt.event.ItemListener;
74 import java.awt.event.KeyEvent;
75 import java.awt.event.MouseEvent;
76 import java.awt.image.BufferedImage;
77 import java.awt.image.IndexColorModel;
78 import java.io.BufferedOutputStream;
79 import java.io.BufferedReader;
80 import java.io.File;
81 import java.io.FileOutputStream;
82 import java.io.FileReader;
83 import java.io.FilenameFilter;
84 import java.io.IOException;
85 import java.io.OutputStreamWriter;
86 import java.io.RandomAccessFile;
87 import java.lang.reflect.Field;
88 import java.util.ArrayList;
89 import java.util.Calendar;
90 import java.util.Collection;
91 import java.util.Date;
92 import java.util.HashMap;
93 import java.util.Iterator;
94 import java.util.LinkedList;
95 import java.util.List;
96 import java.util.Map;
97 import java.util.TreeMap;
98 import java.util.Vector;
99 import java.util.concurrent.Callable;
100 import java.util.concurrent.Executors;
101 import java.util.concurrent.Future;
102 import java.util.concurrent.ThreadFactory;
103 import java.util.concurrent.ThreadPoolExecutor;
104 import java.util.concurrent.atomic.AtomicInteger;
105 import java.util.regex.Matcher;
106 import java.util.regex.Pattern;
108 import javax.swing.JMenu;
109 import javax.swing.JMenuItem;
110 import javax.swing.JPopupMenu;
111 import javax.swing.KeyStroke;
112 import javax.swing.SwingUtilities;
114 /** Utils class: stores generic widely used methods. In particular, those for logging text messages (for debugging) and also some math and memory utilities.
118 public class Utils implements ij.plugin.PlugIn {
120 static public String version = "1.0a 2012-07-04";
122 static public boolean debug = false;
123 static public boolean debug_mouse = false;
124 static public boolean debug_sql = false;
125 static public boolean debug_event = false;
126 static public boolean debug_clip = false; //clip for repainting
127 static public boolean debug_thing = false;
129 /** The error to use in floating-point or double floating point literal comparisons. */
130 static public final double FL_ERROR = 0.0000001;
132 static public void debug(final String msg) {
133 if (debug) IJ.log(msg);
136 static public void debugMouse(final String msg) {
137 if (debug_mouse) IJ.log(msg);
140 /** Avoid waiting on the AWT thread repainting ImageJ's log window. */
141 static private final class LogDispatcher extends Thread {
142 private final StringBuilder cache = new StringBuilder();
143 public LogDispatcher(final ThreadGroup tg) {
144 super(tg, "T2-Log-Dispatcher");
145 setPriority(Thread.NORM_PRIORITY);
146 try { setDaemon(true); } catch (final Exception e) { e.printStackTrace(); }
147 start();
149 public final void quit() {
150 interrupt();
151 synchronized (cache) { cache.notify(); }
153 public final void log(final String msg) {
154 try {
155 synchronized (cache) {
156 cache.append(msg).append('\n');
157 cache.notify();
159 } catch (final Exception e) {
160 e.printStackTrace();
163 @Override
164 public void run() {
165 final StringBuilder sb = new StringBuilder();
166 while (!isInterrupted()) {
167 try {
168 final long start = System.currentTimeMillis();
169 // Accumulate messages for about one second
170 do {
171 synchronized (cache) {
172 if (cache.length() > 0) {
173 sb.append(cache);
174 cache.setLength(0);
177 try { Thread.sleep(100); } catch (final InterruptedException ie) { return; }
178 } while (System.currentTimeMillis() - start < 1000);
180 // ... then, if any, update the log window:
181 if (sb.length() > 0) {
182 IJ.log(sb.toString());
183 sb.setLength(0);
185 synchronized (cache) {
186 if (0 == cache.length()) {
187 try { cache.wait(); } catch (final InterruptedException ie) {}
190 } catch (final Exception e) {
191 e.printStackTrace();
197 /** Avoid waiting on the AWT thread repainting ImageJ's status bar.
198 Waits 100 ms before printing the status message; if too many status messages are being sent, the last one overrides all. */
199 static private final class StatusDispatcher extends Thread {
200 private volatile String msg = null;
201 private volatile double progress = -1;
202 public StatusDispatcher(final ThreadGroup tg) {
203 super(tg, "T2-Status-Dispatcher");
204 setPriority(Thread.NORM_PRIORITY);
205 try { setDaemon(true); } catch (final Exception e) { e.printStackTrace(); }
206 start();
208 public final void quit() {
209 interrupt();
210 synchronized (this) { notify(); }
212 public final void showStatus(final String msg) {
213 try {
214 synchronized (this) {
215 this.msg = msg;
216 notify();
218 } catch (final Exception e) {
219 e.printStackTrace();
222 public final void showProgress(final double progress) {
223 try {
224 synchronized (this) {
225 this.progress = progress;
226 notify();
228 } catch (final Exception e) {
229 e.printStackTrace();
232 @Override
233 public void run() {
234 while (!isInterrupted()) {
235 try {
236 String msg = null;
237 double progress = -1;
238 synchronized (this) {
239 // Acquire and reset
240 if (null != this.msg) {
241 msg = this.msg;
242 this.msg = null;
244 if (-1 != this.progress) {
245 progress = this.progress;
246 this.progress = -1;
250 // Execute within the context of this Thread
251 if (null != msg) {
252 IJ.showStatus(msg);
253 msg = null;
255 if (-1 != progress) IJ.showProgress(progress);
257 // allow some time for overwriting of messages
258 Thread.sleep(100);
259 synchronized (this) {
260 if (null == this.msg && -1 == this.progress) {
261 try { wait(); } catch (final InterruptedException ie) {}
264 } catch (final InterruptedException ie) {
265 // pass
266 } catch (final Exception e) {
267 e.printStackTrace();
272 static private LogDispatcher logger = null;
273 static private StatusDispatcher status = null;
275 /** Initialize house keeping threads. */
276 static public final void setup(final ControlWindow master) { // the ControlWindow acts as a switch: nobody can controls this because the CW constructor is private
277 SwingUtilities.invokeLater(new Runnable() { @Override
278 public void run() {
279 if (null != status) status.quit();
280 if (null != logger) logger.quit();
281 logger = new LogDispatcher(Thread.currentThread().getThreadGroup());
282 status = new StatusDispatcher(Thread.currentThread().getThreadGroup());
283 }});
286 /** Destroy house keeping threads. */
287 static public final void destroy(final ControlWindow master) {
288 if (null != status) { status.quit(); status = null; }
289 if (null != logger) { logger.quit(); logger = null; }
292 /** Intended for the user to see. */
293 static public final void log(final String msg) {
294 if (ControlWindow.isGUIEnabled() && null != logger) {
295 logger.log(msg);
296 } else {
297 System.out.println(msg);
301 /** Intended for the user to see; time-stamps every logging line. */
302 static public final void logStamped(final String msg) {
303 if (ControlWindow.isGUIEnabled() && null != logger) {
304 logger.log(new Date().toString() + " : " + msg);
305 } else {
306 System.out.println(new Date().toString() + " : " + msg);
310 /** Print in all printable places: log window, System.out.println, and status bar.*/
311 static public final void logAll(final String msg) {
312 if (!ControlWindow.isGUIEnabled()) {
313 System.out.println(msg);
314 return;
316 System.out.println(msg);
317 if (null != IJ.getInstance() && null != logger) logger.log(msg);
318 if (null != status) status.showStatus(msg);
321 /** Intended for developers: prints to terminal. */
322 static public final void log2(final String msg) {
323 System.out.println(msg);
326 /** Pretty-print the object, for example arrays as [0, 1, 2]. */
327 static public final void log2(final Object ob) {
328 Utils.log2(null, ob);
330 /** Pretty-print the object, for example arrays as [0, 1, 2]. */
331 static public final void log2(final String msg, final Object ob) {
332 Utils.log2((null != msg ? msg : "") + ob + "\n\t" + Utils.toString(ob) + "\n");
334 static public final void log2(final String msg, final Object ob1, final Object... ob) {
335 final StringBuilder sb = new StringBuilder(null == msg ? "" : msg + "\n");
336 sb.append(ob1.toString()).append(" : ").append(Utils.toString(ob1)).append('\n');
337 for (int i=0; i<ob.length; i++) sb.append(ob.toString()).append(" : ").append(Utils.toString(ob[i])).append('\n');
338 sb.setLength(sb.length()-1);
339 Utils.log2(sb.toString());
342 static public final void log(final Object ob) {
343 Utils.log(Utils.toString(ob));
346 /** Pretty-print the object, for example arrays as [0, 1, 2]. */
347 static public final void log(final String msg, final Object ob) {
348 Utils.log((null != msg ? msg : "") + "\n\t" + Utils.toString(ob));
351 static public final void log2(final Object... ob){
352 Utils.log2(Utils.toString(ob));
354 static public final void logMany2(final Object... ob){
355 Utils.log2(Utils.toString(ob));
358 /** 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. */
359 static public final String toString(final Object ob) {
360 if (null == ob) return "null";
361 // Clojure could do this so much easier with a macro
362 final StringBuilder sb = new StringBuilder();
363 sb.append('[');
364 char closing = ']';
365 if (ob instanceof String[]) { // could be done with Object[] and recursive calls, but whatever
366 final String[] s = (String[])ob;
367 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
368 } else if (ob instanceof int[]) {
369 final int[] s = (int[])ob;
370 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
371 } else if (ob instanceof double[]) {
372 final double[] s = (double[])ob;
373 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
374 } else if (ob instanceof float[]) {
375 final float[] s = (float[])ob;
376 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
377 } else if (ob instanceof char[]) {
378 final char[] s = (char[])ob;
379 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
380 } else if (ob instanceof Object[]) {
381 final Object[] s = (Object[])ob;
382 for (int i=0; i<s.length; i++) sb.append(Utils.toString(s[i])).append(", ");
383 } else if (ob instanceof Iterable<?>) {
384 final Iterable<?> s = (Iterable<?>)ob;
385 for (final Iterator<?> it = s.iterator(); it.hasNext(); ) sb.append(Utils.toString(it.next())).append(", ");
386 } else if (ob instanceof Map<?,?>) {
387 sb.setCharAt(0, '{');
388 closing = '}';
389 final Map<?,?> s = (Map<?,?>)ob;
390 for (final Map.Entry<?,?> e : s.entrySet()) {
391 sb.append(Utils.toString(e.getKey())).append(" => ").append(Utils.toString(e.getValue())).append(", ");
393 } else if (ob instanceof long[]) {
394 final long[] s = (long[])ob;
395 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
396 } else if (ob instanceof short[]) {
397 final short[] s = (short[])ob;
398 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
399 } else if (ob instanceof boolean[]) {
400 final boolean[] s = (boolean[])ob;
401 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
402 } else {
403 return ob.toString();
405 final int len = sb.length();
406 if (len > 2) sb.setLength(len-2); // remove the last ", "
407 sb.append(closing);
408 sb.append('\n');
409 return sb.toString();
412 static public void setDebug(final boolean debug) {
413 Utils.debug = debug;
416 static public void setDebugMouse(final boolean debug_mouse) {
417 Utils.debug_mouse = debug_mouse;
420 static public void setDebugSQL(final boolean debug_sql) {
421 Utils.debug_sql = debug_sql;
424 /** Find out which method from which class called the method where the printCaller is used; for debugging purposes.*/
425 static public final void printCaller(final Object called_object) {
426 final StackTraceElement[] elems = new Exception().getStackTrace();
427 if (elems.length < 3) {
428 log2("Stack trace too short! No useful info");
429 } else {
430 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() + "()");
431 log2("==== END ====");
435 static public final void printCaller(final Object called_object, final int lines) {
436 final StackTraceElement[] elems = new Exception().getStackTrace();
437 if (elems.length < 3) {
438 log2("Stack trace too short! No useful info");
439 } else {
440 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() + "()");
441 for (int i=3; i<lines+2 && i<elems.length; i++) {
442 log2("\tby: " + elems[i].getClassName() + " " + elems[i].getLineNumber() + ": " + elems[i].getMethodName() + "()");
444 log2("==== END ====");
448 /** Returns a String representation of the class of the object one step before in the stack trace. */
449 static public final String caller(final Object called) {
450 final StackTraceElement[] elems = new Exception().getStackTrace();
451 if (elems.length < 3) {
452 log2("Stack trace too short! No useful info");
453 return null;
454 } else {
455 return elems[2].getClassName();
459 /**Restore ImageJ's MenuBar*/
460 static public final void restoreMenuBar() {
461 final MenuBar menu_bar = Menus.getMenuBar();
462 final int n_menus = menu_bar.getMenuCount();
463 for (int i=0; i<n_menus;i++) {
464 final Menu menu = menu_bar.getMenu(i);
465 restoreMenu(menu);
467 //make sure there isn't a null menu bar
468 //WindowManager.getCurrentWindow().setMenuBar(menu_bar);
471 static private void restoreMenu(final Menu menu) {
472 final int n_menuitems = menu.getItemCount();
473 for (int i=0; i<n_menuitems; i++) {
474 final MenuItem menu_item = menu.getItem(i);
475 if (menu_item instanceof Menu) {
476 restoreMenu((Menu)menu_item);
478 menu_item.setEnabled(true);
482 static public final void showMessage(final String msg) {
483 if (!ControlWindow.isGUIEnabled()) System.out.println(msg);
484 else IJ.showMessage(msg);
486 static public final void showMessage(final String title, final String msg) {
487 if (!ControlWindow.isGUIEnabled()) System.out.println(title + "\n" + msg);
488 else IJ.showMessage(title, msg);
491 /** Runs the showMessage in a separate Thread. */
492 static public final void showMessageT(final String msg) {
493 new Thread() {
494 @Override
495 public void run() {
496 setPriority(Thread.NORM_PRIORITY);
497 Utils.showMessage(msg);
499 }.start();
502 static public final void showStatus(final String msg, final boolean focus) {
503 if (null == IJ.getInstance() || !ControlWindow.isGUIEnabled() || null == status) {
504 System.out.println(msg);
505 return;
507 if (focus) IJ.getInstance().toFront();
509 status.showStatus(msg);
512 static public final void showStatus(final String msg) {
513 showStatus(msg, false);
516 static private double last_progress = 0;
517 static private int last_percent = 0;
519 static public final void showProgress(final double p) {
520 //IJ.showProgress(p); // never happens, can't repaint even though they are different threads
521 if (null == IJ.getInstance() || !ControlWindow.isGUIEnabled() || null == status) {
522 if (0 == p) {
523 last_progress = 0; // reset
524 last_percent = 0;
525 return;
527 // don't show intervals smaller than 1%:
528 if (last_progress + 0.01 > p ) {
529 final int percent = (int)(p * 100);
530 if (last_percent != percent) {
531 System.out.println(percent + " %");
532 last_percent = percent;
535 last_progress = p;
536 return;
539 status.showProgress(p);
542 static public void debugDialog() {
543 // note: all this could nicely be done using reflection, so adding another boolean variable would be automatically added here (filtering by the prefix "debug").
544 final GenericDialog gd = new GenericDialog("Debug:");
545 gd.addCheckbox("debug", debug);
546 gd.addCheckbox("debug mouse", debug_mouse);
547 gd.addCheckbox("debug sql", debug_sql);
548 gd.addCheckbox("debug event", debug_event);
549 gd.addCheckbox("debug clip", debug_clip);
550 gd.addCheckbox("debug thing", debug_thing);
551 gd.showDialog();
552 if (gd.wasCanceled()) {
553 return;
555 debug = gd.getNextBoolean();
556 debug_mouse = gd.getNextBoolean();
557 debug_sql = gd.getNextBoolean();
558 debug_event = gd.getNextBoolean();
559 debug_clip = gd.getNextBoolean();
560 debug_thing = gd.getNextBoolean();
564 /** Scan the WindowManager for open stacks.*/
565 static public ImagePlus[] findOpenStacks() {
566 final ImagePlus[] imp = scanWindowManager("stacks");
567 if (null == imp) return null;
568 return imp;
571 /** Scan the WindowManager for non-stack images*/
572 static public ImagePlus[] findOpenImages() {
573 final ImagePlus[] imp = scanWindowManager("images");
574 if (null == imp) return null;
575 return imp;
578 /** Scan the WindowManager for all open images, including stacks.*/
579 static public ImagePlus[] findAllOpenImages() {
580 return scanWindowManager("all");
583 static private ImagePlus[] scanWindowManager(final String type) {
584 // check if any stacks are opened within ImageJ
585 final int[] all_ids = WindowManager.getIDList();
586 if (null == all_ids) return null;
587 ImagePlus[] imp = new ImagePlus[all_ids.length];
588 int next = 0;
589 for (int i=0; i < all_ids.length; i++) {
590 final ImagePlus image = WindowManager.getImage(all_ids[i]);
591 if (type.equals("stacks")) {
592 if (image.getStackSize() <= 1) {
593 continue;
595 } else if (type.equals("images")) {
596 if (image.getStackSize() > 1) {
597 continue;
600 // add:
601 imp[next] = image;
602 next++;
604 // resize the array if necessary
605 if (next != all_ids.length) {
606 final ImagePlus[] imp2 = new ImagePlus[next];
607 System.arraycopy(imp, 0, imp2, 0, next);
608 imp = imp2;
610 // return what has been found:
611 if (0 == next) return null;
612 return imp;
615 /**The path of the directory from which images have been recently loaded.*/
616 static public String last_dir = ij.Prefs.getString(ij.Prefs.DIR_IMAGE);
617 /**The path of the last opened file.*/
618 static public String last_file = null;
620 static public String cutNumber(final double d, final int n_decimals) {
621 return cutNumber(d, n_decimals, false);
624 /** remove_trailing_zeros will leave at least one zero after the comma if appropriate. */
625 static public final String cutNumber(final double d, final int n_decimals, final boolean remove_trailing_zeros) {
626 final String num = new Double(d).toString();
627 final int i_e = num.indexOf("E-");
628 if (-1 != i_e) {
629 final int exp = Integer.parseInt(num.substring(i_e+2));
630 if (n_decimals < exp) {
631 final StringBuilder sb = new StringBuilder("0.");
632 int count = n_decimals;
633 while (count > 0) {
634 sb.append('0');
635 count--;
637 return sb.toString(); // returns 0.000... as many zeros as desired n_decimals
639 // else move comma
640 final StringBuilder sb = new StringBuilder("0.");
641 int count = exp -1;
642 while (count > 0) {
643 sb.append('0');
644 count--;
646 sb.append(num.charAt(0)); // the single number before the comma
647 // up to here there are 'exp' number of decimals appended
648 int i_end = 2 + n_decimals - exp;
649 if (i_end > i_e) i_end = i_e; // there arent' that ,any, so cut
650 sb.append(num.substring(2, i_end)); // all numbers after the comma
651 return sb.toString();
653 // else, there is no scientific notation to worry about
654 final int i_dot = num.indexOf('.');
655 final StringBuilder sb = new StringBuilder(num.substring(0, i_dot+1));
656 for (int i=i_dot+1; i < (n_decimals + i_dot + 1) && i < num.length(); i++) {
657 sb.append(num.charAt(i));
659 // remove zeros from the end
660 if (remove_trailing_zeros) {
661 for (int i=sb.length()-1; i>i_dot+1; i--) { // leave at least one zero after the comma
662 if ('0' == sb.charAt(i)) {
663 sb.setLength(i);
664 } else {
665 break;
669 return sb.toString();
672 /** Zero-pad a number, so that '1' becomes '001' if n_digits is 3. */
673 static public final String zeroPad(final int i, final int n_digits) {
674 final StringBuilder sb = new StringBuilder();
675 sb.append(i);
676 int len = sb.length();
677 while (len < n_digits) {
678 sb.insert(0, '0');
679 len++;
681 return sb.toString();
684 static public final boolean check(final String msg) {
685 try { return new TaskOnEDT<Boolean>(new Callable<Boolean>() { @Override
686 public Boolean call() {
687 final YesNoCancelDialog dialog = new YesNoCancelDialog(IJ.getInstance(), "Execute?", msg);
688 if (dialog.yesPressed()) {
689 return true;
691 return false;
692 }}).get(); } catch (final Throwable t) { IJError.print(t); return false; }
695 static public final boolean checkYN(final String msg) {
696 try { return new TaskOnEDT<Boolean>(new Callable<Boolean>() { @Override
697 public Boolean call() {
698 final YesNoDialog yn = new YesNoDialog(IJ.getInstance(), "Execute?", msg);
699 if (yn.yesPressed()) return true;
700 return false;
701 }}).get(); } catch (final Throwable t) { IJError.print(t); return false; }
704 static public final String d2s(final double d, final int n_decimals) {
705 return IJ.d2s(d, n_decimals);
708 static public final String[] getHexRGBColor(final Color color) {
709 final int c = color.getRGB();
710 String r = Integer.toHexString(((c&0x00FF0000)>>16));
711 if (1 == r.length()) r = "0" + r;
712 String g = Integer.toHexString(((c&0x0000FF00)>>8));
713 if (1 == g.length()) g = "0" + g;
714 String b = Integer.toHexString((c&0x000000FF));
715 if (1 == b.length()) b = "0" + b;
716 return new String[]{r, g, b};
719 static public final String asHexRGBColor(final Color color) {
720 final StringBuilder sb = new StringBuilder(6);
721 Utils.asHexRGBColor(sb, color);
722 return sb.toString();
724 static public final void asHexRGBColor(final StringBuilder sb, final Color color) {
725 final int c = color.getRGB();
726 final String r = Integer.toHexString(((c&0x00FF0000)>>16));
727 if (1 == r.length()) sb.append('0');
728 sb.append(r);
729 final String g = Integer.toHexString(((c&0x0000FF00)>>8));
730 if (1 == g.length()) sb.append('0');
731 sb.append(g);
732 final String b = Integer.toHexString((c&0x000000FF));
733 if (1 == b.length()) sb.append('0');
734 sb.append(b);
737 static public final Color getRGBColorFromHex(final String hex) {
738 if (hex.length() < 6) return null;
739 return new Color(Integer.parseInt(hex.substring(0, 2), 16), // parse in hexadecimal radix
740 Integer.parseInt(hex.substring(2, 4), 16),
741 Integer.parseInt(hex.substring(4, 6), 16));
744 static public final int[] get4Ints(final int hex) {
745 return new int[]{((hex&0xFF000000)>>24),
746 ((hex&0x00FF0000)>>16),
747 ((hex&0x0000FF00)>> 8),
748 hex&0x000000FF };
751 @Override
752 public void run(final String arg) {
753 try { new TaskOnEDT<Boolean>(new Callable<Boolean>() { @Override
754 public Boolean call() {
755 IJ.showMessage("TrakEM2",
756 new StringBuilder("TrakEM2 ").append(Utils.version)
757 .append("\nCopyright Albert Cardona & Rodney Douglas\n")
758 .append("Institute for Neuroinformatics, Univ/ETH Zurich.\n")
759 .append("\nRegistration library copyright Stephan Saalfeld, MPI-CBG.\n")
760 .append("\nLens correction copyright Verena Kaynig, ETH Zurich.\n")
761 .append("\nSome parts copyright Ignacio Arganda, INI Univ/ETH Zurich.")
762 .toString());
763 return true;
764 }}).get(); } catch (final Throwable t) { IJError.print(t); }
767 static public final File chooseFile(final String name, final String extension) {
768 return Utils.chooseFile(null, name, extension);
771 /** 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). */
772 static public final File chooseFile(final String default_dir, final String name, final String extension) {
773 try { return new TaskOnEDT<File>(new Callable<File>() { @Override
774 public File call() {
775 // using ImageJ's JFileChooser or internal FileDialog, according to user preferences.
776 String name2 = null;
777 if (null != name && null != extension) name2 = name + extension;
778 else if (null != name) name2 = name;
779 else if (null != extension) name2 = "untitled" + extension;
780 if (null != default_dir) {
781 OpenDialog.setDefaultDirectory(default_dir);
783 final SaveDialog sd = new SaveDialog("Save",
784 OpenDialog.getDefaultDirectory(),
785 name2,
786 extension);
788 final String filename = sd.getFileName();
789 if (null == filename || filename.toLowerCase().startsWith("null")) return null;
790 String dir = sd.getDirectory();
791 if (IJ.isWindows()) dir = dir.replace('\\', '/');
792 if (!dir.endsWith("/")) dir += "/";
793 final File f = new File(dir + filename);
794 if (f.exists() && ControlWindow.isGUIEnabled()) {
795 final YesNoCancelDialog d = new YesNoCancelDialog(IJ.getInstance(), "Overwrite?", "File " + filename + " exists! Overwrite?");
796 if (d.cancelPressed()) {
797 return null;
798 } else if (!d.yesPressed()) {
799 return chooseFile(name, extension);
801 // else if yes pressed, overwrite.
803 return f;
804 }}).get(); } catch (final Throwable t) { IJError.print(t); return null; }
807 /** Returns null or the selected directory and file. */
808 static public final String[] selectFile(final String title_msg) {
809 try { return new TaskOnEDT<String[]>(new Callable<String[]>() { @Override
810 public String[] call() {
811 final OpenDialog od = new OpenDialog("Select file", OpenDialog.getDefaultDirectory(), null);
812 final String file = od.getFileName();
813 if (null == file || file.toLowerCase().startsWith("null")) return null;
814 String dir = od.getDirectory();
815 File f = null;
816 if (null != dir) {
817 if (IJ.isWindows()) dir = dir.replace('\\', '/');
818 if (!dir.endsWith("/")) dir += "/";
819 f = new File(dir + file); // I'd use File.separator, but in Windows it fails
821 if (null == dir || !f.exists()) {
822 Utils.log2("No proper file selected.");
823 return null;
825 return new String[]{dir, file};
826 }}).get(); } catch (final Throwable t) { IJError.print(t); return null; }
829 static public final boolean saveToFile(final File f, final String contents) {
830 if (null == f) return false;
831 try { return new TaskOnEDT<Boolean>(new Callable<Boolean>() { @Override
832 public Boolean call() {
833 OutputStreamWriter dos = null;
834 try {
835 dos = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(f), contents.length()), "8859_1"); // encoding in Latin 1 (for macosx not to mess around
836 //dos.writeBytes(contents);
837 dos.write(contents, 0, contents.length());
838 dos.flush();
839 dos.close();
840 } catch (final Exception e) {
841 IJError.print(e);
842 Utils.showMessage("ERROR: Most likely did NOT save your file.");
843 try {
844 if (null != dos) dos.close();
845 } catch (final Exception ee) { IJError.print(ee); }
846 return false;
848 return true;
849 }}).get(); } catch (final Throwable t) { IJError.print(t); return false; }
852 static public final boolean saveToFile(final String name, final String extension, final String contents) {
853 if (null == contents) {
854 Utils.log2("Utils.saveToFile: contents is null");
855 return false;
857 // save the file
858 final File f = Utils.chooseFile(name, extension);
859 return Utils.saveToFile(f, contents);
862 /** Converts sequences of spaces into single space, and trims the ends. */
863 static public final String cleanString(String s) {
864 s = s.trim();
865 while (-1 != s.indexOf("\u0020\u0020")) { // \u0020 equals a single space
866 s = s.replaceAll("\u0020\u0020", "\u0020");
868 return s;
871 static public final String openTextFile(final String path) {
872 if (null == path || !new File(path).exists()) return null;
873 final StringBuilder sb = new StringBuilder();
874 BufferedReader r = null;
875 try {
876 r = new BufferedReader(new FileReader(path));
877 while (true) {
878 final String s = r.readLine();
879 if (null == s) break;
880 sb.append(s).append('\n'); // I am sure the reading can be done better
882 } catch (final Exception e) {
883 IJError.print(e);
884 if (null != r) try { r.close(); } catch (final IOException ioe) { ioe.printStackTrace(); }
885 return null;
887 return sb.toString();
890 /** Returns the file found at path as an array of lines, or null if not found. */
891 static public final String[] openTextFileLines(final String path) {
892 if (null == path || !new File(path).exists()) return null;
893 final ArrayList<String> al = new ArrayList<String>();
894 try {
895 final BufferedReader r = new BufferedReader(new FileReader(path));
896 while (true) {
897 final String s = r.readLine();
898 if (null == s) break;
899 al.add(s);
901 r.close();
902 } catch (final Exception e) {
903 IJError.print(e);
904 return null;
906 final String[] sal = new String[al.size()];
907 al.toArray(sal);
908 return sal;
911 static public final char[] openTextFileChars(final String path) {
912 File f = null;
913 if (null == path || !(f = new File(path)).exists()) {
914 Utils.log("File not found: " + path);
915 return null;
917 final char[] src = new char[(int)f.length()]; // assumes file is small enough to fit in integer range!
918 try {
919 final BufferedReader r = new BufferedReader(new FileReader(path));
920 r.read(src, 0, src.length);
921 r.close();
922 } catch (final Exception e) {
923 IJError.print(e);
924 return null;
926 return src;
929 /** The cosinus between two vectors (in polar coordinates), by means of the dot product. */
930 static public final double getCos(final double x1, final double y1, final double x2, final double y2) {
931 return (x1 * x2 + y1 * y2) / (Math.sqrt(x1*x1 + y1*y1) * Math.sqrt(x2*x2 + y2*y2));
934 static public final String removeExtension(final String path) {
935 final int i_dot = path.lastIndexOf('.');
936 if (-1 == i_dot || i_dot + 4 != path.length()) return path;
937 else return path.substring(0, i_dot);
940 /** A helper for GenericDialog checkboxes to control other the enabled state of other GUI elements in the same dialog. */
941 static public final void addEnablerListener(final Checkbox master, final Component[] enable, final Component[] disable) {
942 master.addItemListener(new ItemListener() {
943 @Override
944 public void itemStateChanged(final ItemEvent ie) {
945 if (ie.getStateChange() == ItemEvent.SELECTED) {
946 process(enable, true);
947 process(disable, false);
948 } else {
949 process(enable, false);
950 process(disable, true);
953 private void process(final Component[] c, final boolean state) {
954 if (null == c) return;
955 for (int i=0; i<c.length; i++) c[i].setEnabled(state);
960 static public final boolean wrongImageJVersion() {
961 final boolean b = IJ.versionLessThan("1.37g");
962 if (b) Utils.showMessage("TrakEM2 requires ImageJ 1.37g or above.");
963 return b;
966 static public final boolean java3d = isJava3DInstalled();
968 static private final boolean isJava3DInstalled() {
969 try {
970 Class.forName("javax.vecmath.Point3f");
971 } catch (final ClassNotFoundException cnfe) {
972 return false;
974 return true;
977 static public final void addLayerRangeChoices(final Layer selected, final GenericDialog gd) {
978 Utils.addLayerRangeChoices(selected, selected, gd);
981 static public final void addLayerRangeChoices(final Layer first, final Layer last, final GenericDialog gd) {
982 final String[] layers = new String[first.getParent().size()];
983 final ArrayList<String> al_layer_titles = new ArrayList<String>();
984 int i = 0;
985 for (final Layer layer : first.getParent().getLayers()) {
986 layers[i] = first.getProject().findLayerThing(layer).toString();
987 al_layer_titles.add(layers[i]);
988 i++;
990 final int i_first = first.getParent().indexOf(first);
991 final int i_last = last.getParent().indexOf(last);
992 gd.addChoice("Start: ", layers, layers[i_first]);
993 final Vector<?> v = gd.getChoices();
994 final Choice cstart = (Choice)v.get(v.size()-1);
995 gd.addChoice("End: ", layers, layers[i_last]);
996 final Choice cend = (Choice)v.get(v.size()-1);
997 cstart.addItemListener(new ItemListener() {
998 @Override
999 public void itemStateChanged(final ItemEvent ie) {
1000 final int index = al_layer_titles.indexOf(ie.getItem());
1001 if (index > cend.getSelectedIndex()) cend.select(index);
1004 cend.addItemListener(new ItemListener() {
1005 @Override
1006 public void itemStateChanged(final ItemEvent ie) {
1007 final int index = al_layer_titles.indexOf(ie.getItem());
1008 if (index < cstart.getSelectedIndex()) cstart.select(index);
1013 static public final void addLayerChoice(final String label, final Layer selected, final GenericDialog gd) {
1014 final String[] layers = new String[selected.getParent().size()];
1015 final ArrayList<String> al_layer_titles = new ArrayList<String>();
1016 int i = 0;
1017 for (final Layer layer : selected.getParent().getLayers()) {
1018 layers[i] = selected.getProject().findLayerThing(layer).toString();
1019 al_layer_titles.add(layers[i]);
1020 i++;
1022 final int i_layer = selected.getParent().indexOf(selected);
1023 gd.addChoice(label, layers, layers[i_layer]);
1027 static public final void addRGBColorSliders(final GenericDialog gd, final Color color) {
1028 gd.addSlider("Red: ", 0, 255, color.getRed());
1029 gd.addSlider("Green: ", 0, 255, color.getGreen());
1030 gd.addSlider("Blue: ", 0, 255, color.getBlue());
1033 /** Converts the ImageProcessor to an ImageProcessor of the given type, or the same if of equal type. */
1034 static final public ImageProcessor convertTo(final ImageProcessor ip, final int type, final boolean scaling) {
1035 switch (type) {
1036 case ImagePlus.GRAY8:
1037 return ip.convertToByte(scaling);
1038 case ImagePlus.GRAY16:
1039 return ip.convertToShort(scaling);
1040 case ImagePlus.GRAY32:
1041 return ip.convertToFloat();
1042 case ImagePlus.COLOR_RGB:
1043 return ip.convertToRGB();
1044 case ImagePlus.COLOR_256:
1045 final ImagePlus imp = new ImagePlus("", ip.convertToRGB());
1046 new ImageConverter(imp).convertRGBtoIndexedColor(256);
1047 return imp.getProcessor();
1048 default:
1049 return null;
1053 /** 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. */
1054 static public final double[] copy(final double[] a, final int new_length) {
1055 final double[] b = new double[new_length];
1056 final int len = a.length > new_length ? new_length : a.length;
1057 System.arraycopy(a, 0, b, 0, len);
1058 return b;
1061 static public final long[] copy(final long[] a, final int new_length) {
1062 final long[] b = new long[new_length];
1063 final int len = a.length > new_length ? new_length : a.length;
1064 System.arraycopy(a, 0, b, 0, len);
1065 return b;
1068 static public final double[] copy(final double[] a, final int first, final int new_length) {
1069 final double[] b = new double[new_length];
1070 final int len = new_length < a.length - first ? new_length : a.length - first;
1071 System.arraycopy(a, first, b, 0, len);
1072 return b;
1075 static public final long[] copy(final long[] a, final int first, final int new_length) {
1076 final long[] b = new long[new_length];
1077 final int len = new_length < a.length - first ? new_length : a.length - first;
1078 System.arraycopy(a, first, b, 0, len);
1079 return b;
1082 /** Reverse in place an array of doubles. */
1083 static public final void reverse(final double[] a) {
1084 for (int left=0, right=a.length-1; left<right; left++, right--) {
1085 final double tmp = a[left];
1086 a[left] = a[right];
1087 a[right] = tmp;
1090 /** Reverse in place an array of longs. */
1091 static public final void reverse(final long[] a) {
1092 for (int left=0, right=a.length-1; left<right; left++, right--) {
1093 final long tmp = a[left];
1094 a[left] = a[right];
1095 a[right] = tmp;
1099 static public final double[] toDouble(final int[] a, final int len) {
1100 final double[] b = new double[len];
1101 for (int i=0; i<len; i++) b[i] = a[i];
1102 return b;
1105 static public final int[] toInt(final double[] a, final int len) {
1106 final int[] b = new int[len];
1107 for (int i=0; i<len; i++) b[i] = (int) a[i];
1108 return b;
1111 /** OS-agnostic diagnosis of whether the click was for the contextual popup menu. */
1112 static public final boolean isPopupTrigger(final MouseEvent me) {
1113 // ImageJ way, in ij.gui.ImageCanvas class, plus an is-windows switch to prevent meta key from poping up for MacOSX
1114 return (me.isPopupTrigger() && me.getButton() != 0) || (IJ.isWindows() && 0 != (me.getModifiers() & Event.META_MASK) );
1117 /** Repaint the given Component on the swing repaint thread (aka "SwingUtilities.invokeLater"). */
1118 static public final void updateComponent(final Component c) {
1119 //c.invalidate();
1120 //c.validate();
1121 // ALL that was needed: to put it into the swing repaint queue ... couldn't they JUST SAY SO
1122 Utils.invokeLater(new Runnable() {
1123 @Override
1124 public void run() {
1125 c.repaint();
1129 /** Like calling pack() on a Frame but on a Component. */
1130 static public final void revalidateComponent(final Component c) {
1131 Utils.invokeLater(new Runnable() {
1132 @Override
1133 public void run() {
1134 c.invalidate();
1135 c.validate();
1136 c.repaint();
1141 /** Returns the time as HH:MM:SS */
1142 static public final String now() {
1143 /* Java time management is retarded. */
1144 final Calendar c = Calendar.getInstance();
1145 final int hour = c.get(Calendar.HOUR_OF_DAY);
1146 final int min = c.get(Calendar.MINUTE);
1147 final int sec = c.get(Calendar.SECOND);
1148 final StringBuilder sb = new StringBuilder();
1149 if (hour < 10) sb.append('0');
1150 sb.append(hour).append(':');
1151 if (min < 10) sb.append('0');
1152 sb.append(min).append(':');
1153 if (sec < 10) sb.append(0);
1154 return sb.append(sec).toString();
1157 static public final void sleep(final long miliseconds) {
1158 try { Thread.sleep(miliseconds); } catch (final Exception e) { e.printStackTrace(); }
1161 /** Mix colors visually: red + green = yellow, etc.*/
1162 static public final Color mix(final Color c1, final Color c2) {
1163 final float[] b = Color.RGBtoHSB(c1.getRed(), c1.getGreen(), c1.getBlue(), new float[3]);
1164 final float[] c = Color.RGBtoHSB(c2.getRed(), c2.getGreen(), c2.getBlue(), new float[3]);
1165 final float[] a = new float[3];
1166 // find to which side the hue values are closer, since hue space is a a circle
1167 // hue values all between 0 and 1
1168 float h1 = b[0];
1169 float h2 = c[0];
1170 if (h1 < h2) {
1171 final float tmp = h1;
1172 h1 = h2;
1173 h2 = tmp;
1175 final float d1 = h2 - h1;
1176 final float d2 = 1 + h1 - h2;
1177 if (d1 < d2) {
1178 a[0] = h1 + d1 / 2;
1179 } else {
1180 a[0] = h2 + d2 / 2;
1181 if (a[0] > 1) a[0] -= 1;
1184 for (int i=1; i<3; i++) a[i] = (b[i] + c[i]) / 2; // only Saturation and Brightness can be averaged
1185 return Color.getHSBColor(a[0], a[1], a[2]);
1188 /** 1 A, 2 B, 3 C --- 26 - z, 27 AA, 28 AB, 29 AC --- 26*27 AAA */
1189 static public final String getCharacter(int i) {
1190 i--;
1191 final int k = i / 26;
1192 final char c = (char)((i % 26) + 65); // 65 is 'A'
1193 if (0 == k) return Character.toString(c);
1194 return new StringBuilder().append(getCharacter(k)).append(c).toString();
1197 static public final Object getField(final Object ob, final String field_name) {
1198 if (null == ob || null == field_name) return null;
1199 return getField(ob, ob.getClass(), field_name);
1202 /** Get by reflection a private or protected field in the given object. */
1203 static public final Object getField(final Object ob, final Class<?> c, final String field_name) {
1204 try {
1205 final Field f = c.getDeclaredField(field_name);
1206 f.setAccessible(true);
1207 return f.get(ob);
1208 } catch (final Exception e) {
1209 IJError.print(e);
1211 return null;
1213 static public final void setField(final Object ob, final Class<?> c, final String field_name, final Object value) {
1214 try {
1215 final Field f = c.getDeclaredField(field_name);
1216 f.setAccessible(true);
1217 f.set(ob, value);
1218 } catch (final Exception e) {
1219 IJError.print(e);
1223 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1224 static public final FloatProcessor fastConvertToFloat(final ByteProcessor ip) {
1225 final byte[] pix = (byte[])ip.getPixels();
1226 final float[] data = new float[pix.length];
1227 for (int i=0; i<pix.length; i++) data[i] = pix[i]&0xff;
1228 final FloatProcessor fp = new FloatProcessorT2(ip.getWidth(), ip.getHeight(), data, ip.getColorModel(), ip.getMin(), ip.getMax());
1229 return fp;
1231 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1232 static public final FloatProcessor fastConvertToFloat(final ShortProcessor ip) {
1233 final short[] pix = (short[])ip.getPixels();
1234 final float[] data = new float[pix.length];
1235 for (int i=0; i<pix.length; i++) data[i] = pix[i]&0xffff;
1236 final FloatProcessor fp = new FloatProcessorT2(ip.getWidth(), ip.getHeight(), data, ip.getColorModel(), ip.getMin(), ip.getMax());
1237 return fp;
1239 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1240 static public final FloatProcessor fastConvertToFloat(final ImageProcessor ip, final int type) {
1241 switch (type) {
1242 case ImagePlus.GRAY16: return fastConvertToFloat((ShortProcessor)ip);
1243 case ImagePlus.GRAY32: return (FloatProcessor)ip;
1244 case ImagePlus.GRAY8:
1245 case ImagePlus.COLOR_256: return fastConvertToFloat((ByteProcessor)ip);
1246 case ImagePlus.COLOR_RGB: return (FloatProcessor)ip.convertToFloat(); // SLOW
1248 return null;
1250 static public final FloatProcessor fastConvertToFloat(final ImageProcessor ip) {
1251 if (ip instanceof ByteProcessor) return fastConvertToFloat((ByteProcessor)ip);
1252 if (ip instanceof ShortProcessor) return fastConvertToFloat((ShortProcessor)ip);
1253 return (FloatProcessor)ip.convertToFloat();
1256 /** 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. */
1257 static public final ResultsTable createResultsTable(final String title, final String[] columns) {
1258 try { return new TaskOnEDT<ResultsTable>(new Callable<ResultsTable>() { @Override
1259 public ResultsTable call() {
1260 final TextWindow tw = (TextWindow)WindowManager.getFrame(title);
1261 if (null != tw) {
1262 // hacking again ... missing a getResultsTable() method in TextWindow
1263 final ResultsTable rt = (ResultsTable)Utils.getField(tw.getTextPanel(), "rt");
1264 if (null != rt) return rt; // assumes columns will be identical
1266 // else create a new one
1267 final ResultsTable rt = new ResultsTable();
1268 rt.setPrecision(2);
1269 for (int i=0; i<columns.length; i++) rt.setHeading(i, columns[i]);
1271 return rt;
1272 }}).get(); } catch (final Throwable t) { IJError.print(t); return null; }
1275 static public final ImageProcessor createProcessor(final int type, final int width, final int height) {
1276 switch (type) {
1277 case ImagePlus.GRAY8: return new ByteProcessor(width, height);
1278 case ImagePlus.GRAY16: return new ShortProcessor(width, height);
1279 case ImagePlus.GRAY32: return new FloatProcessor(width, height);
1280 case ImagePlus.COLOR_RGB: return new ColorProcessor(width, height);
1282 return null;
1285 /** Paints an approximation of the pipe into the set of slices. */
1286 static public void paint(final Pipe pipe, final Map<Layer,ImageProcessor> slices, final int value, final float scale) {
1287 final VectorString3D vs = pipe.asVectorString3D();
1288 vs.resample(1); // one pixel
1289 final double[] px = vs.getPoints(0);
1290 final double[] py = vs.getPoints(1);
1291 final double[] pz = vs.getPoints(2);
1292 final double[] pr = vs.getDependent(0);
1293 // For each point
1294 for (int i=0; i<px.length-1; i++) {
1295 final ImageProcessor ip = slices.get(pipe.getLayerSet().getNearestLayer(pz[i]));
1296 if (null == ip) continue;
1297 final OvalRoi ov = new OvalRoi((int)((px[i] - pr[i]) * scale),
1298 (int)((py[i] - pr[i]) * scale),
1299 (int)(pr[i]*2*scale), (int)(pr[i]*2*scale));
1300 ip.setRoi(ov);
1301 ip.setValue(value);
1302 ip.fill(ip.getMask());
1306 static final public boolean matches(final String pattern, final String s) {
1307 return Pattern.compile(pattern).matcher(s).matches();
1310 static final public boolean isValidIdentifier(final String s) {
1311 if (null == s) return false;
1312 if (!Utils.matches("^[a-zA-Z]+[a-zA-Z1-9_]*$", s)) {
1313 Utils.log("Invalid identifier " + s);
1314 return false;
1316 return true;
1320 user=> (def pat #"\b[a-zA-Z]+[\w]*\b")
1321 #'user/pat
1322 user=>(re-seq pat "abc def 1a334")
1323 ("abc" "def")
1324 user=> (re-seq pat "abc def a334")
1325 ("abc" "def" "a334")
1327 Then concatenate all good words with underscores.
1328 Returns null when nothing valid is found in 's'.
1330 static final public String makeValidIdentifier(final String s) {
1331 if (null == s) return null;
1332 // Concatenate all good groups with underscores:
1333 final Pattern pat = Pattern.compile("\\b[a-zA-Z]+[\\w]*\\b");
1334 final Matcher m = pat.matcher(s);
1335 final StringBuilder sb = new StringBuilder();
1336 while (m.find()) {
1337 sb.append(m.group()).append('_');
1339 if (0 == sb.length()) return null;
1340 // Remove last underscore
1341 sb.setLength(sb.length()-1);
1342 return sb.toString();
1345 static final public int indexOf(final Object needle, final Object[] haystack) {
1346 for (int i=0; i<haystack.length; i++) {
1347 if (haystack[i].equals(needle)) return i;
1349 return -1;
1352 /** 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. */
1353 static public final boolean removeFile(final File f) {
1354 return Utils.removeFile(f, true, null);
1357 // Accumulates removed files (not directories) into removed_paths, if not null.
1358 static private final boolean removeFile(final File f, final boolean stop_if_dir_not_empty, final ArrayList<String> removed_paths) {
1359 if (null == f || !f.exists()) return false;
1360 try {
1361 if (!Utils.isTrakEM2Subfile(f)) {
1362 Utils.log2("REFUSING to remove file " + f + "\n-->REASON: not in a '/trakem2.' file path");
1363 return false;
1366 // If it's not a directory, just delete it
1367 if (!f.isDirectory()) {
1368 return f.delete();
1370 // Else delete all directories:
1371 final ArrayList<File> dirs = new ArrayList<File>();
1372 dirs.add(f);
1373 // Non-recursive version ... I hate java
1374 do {
1375 final int i = dirs.size() -1;
1376 final File fdir = dirs.get(i);
1377 Utils.log2("Examining folder for deletion: " + fdir.getName());
1378 boolean remove = true;
1379 final File[] files = fdir.listFiles();
1380 if (null != files) { // can be null if the directory doesn't contain any files. Why not just return an empty array!?
1381 for (final File file : files) {
1382 final String name = file.getName();
1383 if (name.equals(".") || name.equals("..")) continue;
1384 if (file.isDirectory()) {
1385 remove = false;
1386 dirs.add(file);
1387 } else if (file.isHidden()) {
1388 if (!file.delete()) {
1389 Utils.log("Failed to delete hidden file " + file.getAbsolutePath());
1390 return false;
1392 if (null != removed_paths) removed_paths.add(file.getAbsolutePath());
1393 } else if (stop_if_dir_not_empty) {
1394 //Utils.log("Not empty: cannot remove dir " + fdir.getAbsolutePath());
1395 return false;
1396 } else {
1397 if (!file.delete()) {
1398 Utils.log("Failed to delete visible file " + file.getAbsolutePath());
1399 return false;
1401 if (null != removed_paths) removed_paths.add(file.getAbsolutePath());
1405 if (remove) {
1406 dirs.remove(i);
1407 if (!fdir.delete()) {
1408 return false;
1409 } else {
1410 Utils.log2("Removed folder " + fdir.getAbsolutePath());
1413 } while (dirs.size() > 0);
1415 return true;
1417 } catch (final Exception e) {
1418 IJError.print(e);
1420 return false;
1423 /** Returns true if the file cannonical path contains "/trakem2." (adjusted for Windows as well). */
1424 static public boolean isTrakEM2Subfile(final File f) throws Exception {
1425 return isTrakEM2Subpath(f.getCanonicalPath());
1428 /** Returns true if the path contains "/trakem2." (adjusted for Windows as well). */
1429 static public boolean isTrakEM2Subpath(String path) {
1430 if (IJ.isWindows()) path = path.replace('\\', '/');
1431 return -1 != path.toLowerCase().indexOf("/trakem2.");
1434 /** Returns true if all files and their subdirectories, recursively, under parent folder have been removed.
1435 * For safety reasons, this function will return false immediately if the parent file path does not include a
1436 * lowercase "trakem2." in it.
1437 * If removed_paths is not null, all removed full paths are added to it.
1438 * Returns false if some files could not be removed.
1440 static public final boolean removePrefixedFiles(final File parent, final String prefix, final ArrayList<String> removed_paths) {
1441 if (null == parent || !parent.isDirectory()) return false;
1443 try {
1444 if (!Utils.isTrakEM2Subfile(parent)) {
1445 Utils.log2("REFUSING to remove files recursively under folder " + parent + "\n-->REASON: not in a '/trakem2.' file path");
1446 return false;
1449 final File[] list = parent.listFiles(new FilenameFilter() {
1450 @Override
1451 public boolean accept(final File dir, final String name) {
1452 if (name.startsWith(prefix)) return true;
1453 return false;
1457 boolean success = true;
1458 ArrayList<String> a = null;
1459 if (null != removed_paths) a = new ArrayList<String>();
1461 if (null != list && list.length > 0) {
1462 for (final File f : list) {
1463 if (!Utils.removeFile(f, false, a)) success = false;
1464 if (null != removed_paths) {
1465 removed_paths.addAll(a);
1466 a.clear();
1471 return success;
1473 } catch (final Exception e) {
1474 IJError.print(e);
1476 return false;
1479 /** The CTRL key functionality is passed over to the COMMAND key (aka META key) in a MacOSX. */
1480 static public final int getControlModifier() {
1481 return IJ.isMacOSX() ? InputEvent.META_MASK
1482 : InputEvent.CTRL_MASK;
1485 /** The CTRL key functionality is passed over to the COMMAND key (aka META key) in a MacOSX. */
1486 static public final boolean isControlDown(final InputEvent e) {
1487 return IJ.isMacOSX() ? e.isMetaDown()
1488 : e.isControlDown();
1491 static public final void drawPoint(final java.awt.Graphics g, final int x, final int y) {
1492 g.setColor(Color.white);
1493 g.drawLine(x-4, y+2, x+8, y+2);
1494 g.drawLine(x+2, y-4, x+2, y+8);
1495 g.setColor(Color.yellow);
1496 g.fillRect(x+1,y+1,3,3);
1497 g.setColor(Color.black);
1498 g.drawRect(x, y, 4, 4);
1501 static public final String trim(final CharSequence sb) {
1502 char c;
1503 int start = 0;
1504 do {
1505 c = sb.charAt(start);
1506 start++;
1507 } while ('\t' == c || ' ' == c || '\n' == c);
1508 int end = sb.length() -1;
1509 do {
1510 c = sb.charAt(end);
1511 end--;
1512 } while ('\n' == c || ' ' == c || '\t' == c);
1514 return sb.subSequence(start-1, end+2).toString();
1517 static public final void wait(final Collection<Future<?>> fus) {
1518 for (final Future<?> fu : fus) {
1519 if (null != fu) try {
1520 fu.get(); // wait until done
1521 } catch (final Exception e) {
1522 IJError.print(e);
1523 if (Thread.currentThread().isInterrupted()) return;
1528 static public final void waitIfAlive(final Collection<Future<?>> fus, final boolean throwException) {
1529 for (final Future<?> fu : fus) {
1530 if (Thread.currentThread().isInterrupted()) return;
1531 if (null != fu) try {
1532 fu.get(); // wait until done
1533 } catch (final Exception e) {
1534 if (throwException) IJError.print(e);
1539 /** Convert a D:\\this\that\there to D://this/that/there/
1540 * Notice it adds an ending backslash. */
1541 static public final String fixDir(String path) {
1542 if (IJ.isWindows()) path = path.replace('\\', '/');
1543 return '/' == path.charAt(path.length() -1) ?
1544 path
1545 : new StringBuilder(path.length() +1).append(path).append('/').toString();
1548 /** Creates a new fixed thread pool whose threads are in the same ThreadGroup as the Thread that calls this method.
1549 * This allows for the threads to be interrupted when the caller thread's group is interrupted. */
1550 static public final ThreadPoolExecutor newFixedThreadPool(final int n_proc) {
1551 return newFixedThreadPool(n_proc, null);
1553 /** Creates a new fixed thread pool with as many threads as CPUs, and whose threads are in the same ThreadGroup as the Thread that calls this method. */
1554 static public final ThreadPoolExecutor newFixedThreadPool(final String namePrefix) {
1555 return newFixedThreadPool(Runtime.getRuntime().availableProcessors(), namePrefix);
1557 static public final ThreadPoolExecutor newFixedThreadPool(final int n_proc, final String namePrefix) {
1558 final ThreadPoolExecutor exec = (ThreadPoolExecutor) Executors.newFixedThreadPool(n_proc);
1559 final AtomicInteger ai = new AtomicInteger(0);
1560 exec.setThreadFactory(new ThreadFactory() {
1561 @Override
1562 public Thread newThread(final Runnable r) {
1563 final ThreadGroup tg = Thread.currentThread().getThreadGroup();
1564 final Thread t = new CachingThread(tg, r, new StringBuilder(null == namePrefix ? tg.getName() : namePrefix).append('-').append(ai.incrementAndGet()).toString());
1565 t.setDaemon(true);
1566 t.setPriority(Thread.NORM_PRIORITY);
1567 return t;
1570 return exec;
1572 /** If both are null will throw an error. */
1573 static public final boolean equalContent(final Collection<?> a, final Collection<?> b) {
1574 if ((null == a && null != b)
1575 || (null != b && null == b)) return false;
1576 if (a.size() != b.size()) return false;
1577 for (Iterator<?> ia = a.iterator(), ib = b.iterator(); ia.hasNext(); ) {
1578 if (!ia.next().equals(ib.next())) return false;
1580 return true;
1582 /** If both are null will throw an error. */
1583 static public final boolean equalContent(final Map<?,?> a, final Map<?,?> b) {
1584 if ((null == a && null != b)
1585 || (null != b && null == b)) return false;
1586 if (a.size() != b.size()) return false;
1587 for (final Map.Entry<?,?> e : a.entrySet()) {
1588 final Object ob = b.get(e.getKey());
1589 if (null != ob && !ob.equals(e.getValue())) return false;
1590 if (null != e.getValue() && !e.getValue().equals(ob)) return false;
1591 // if both are null that's ok
1593 return true;
1596 /** Returns false if none to add. */
1597 static public final boolean addPlugIns(final JPopupMenu popup, final String menu, final Project project, final Callable<Displayable> active) {
1598 final JMenu m = addPlugIns(menu, project, active);
1599 if (null == m) return false;
1600 popup.add(m);
1601 return true;
1604 /** Returns null if none to add. */
1605 static public final JMenu addPlugIns(final String menu, final Project project, final Callable<Displayable> active) {
1606 final Map<String,TPlugIn> plugins = project.getPlugins(menu);
1607 if (0 == plugins.size()) return null;
1608 Displayable d = null;
1609 try {
1610 d = active.call();
1611 } catch (final Exception e) {
1612 IJError.print(e);
1614 final JMenu m = new JMenu("Plugins");
1615 JMenuItem item;
1616 int count = 0;
1617 for (final Map.Entry<String,TPlugIn> e : plugins.entrySet()) {
1618 item = new JMenuItem(e.getKey());
1619 item.addActionListener(new ActionListener() {
1620 @Override
1621 public void actionPerformed(final ActionEvent ae) {
1622 Bureaucrat.createAndStart(new Worker.Task(e.getKey()) {
1623 @Override
1624 public void exec() {
1625 try {
1626 e.getValue().invoke(active.call());
1627 } catch (final Exception e) {
1628 IJError.print(e);
1631 }, project);
1634 item.setEnabled(e.getValue().applies(d));
1635 if (count < 9) {
1636 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1 + count, Utils.getControlModifier(), true));
1638 m.add(item);
1639 count++;
1641 if (0 == m.getItemCount()) return null;
1642 m.addSeparator();
1643 // Now all the "Setup " + name
1644 for (final Map.Entry<String,TPlugIn> e : plugins.entrySet()) {
1645 item = new JMenuItem("Setup " + e.getKey());
1646 item.addActionListener(new ActionListener() {
1647 @Override
1648 public void actionPerformed(final ActionEvent ae) {
1649 Bureaucrat.createAndStart(new Worker.Task(e.getKey()) {
1650 @Override
1651 public void exec() {
1652 try {
1653 e.getValue().setup(active.call());
1654 } catch (final Exception e) {
1655 IJError.print(e);
1658 }, project);
1661 m.add(item);
1663 return m;
1666 /** Returns null if no plugin was launched.
1667 * To launch a plugin, it needs a Utils.getControlModifier + 1,2,3,4... up to 9.
1668 * @param active may be null. */
1669 static final public Bureaucrat launchTPlugIn(final KeyEvent ke, final String menu, final Project project, final Displayable active) {
1670 try {
1671 if (0 == (ke.getModifiers() ^ Utils.getControlModifier())) {
1672 final TreeMap<String,TPlugIn> plugins = project.getPlugins("Display");
1673 if (plugins.size() > 0) {
1674 final int index = ke.getKeyCode() - KeyEvent.VK_1;
1675 if (index < plugins.size()) {
1676 int count = 0;
1677 for (final Map.Entry<String,TPlugIn> e : plugins.entrySet()) {
1678 if (index != count) {
1679 count++;
1680 continue;
1682 return Bureaucrat.createAndStart(new Worker.Task(e.getKey()) {
1683 @Override
1684 public void exec() {
1685 e.getValue().invoke(active);
1687 }, project);
1692 } catch (final Throwable t) {
1693 IJError.print(t);
1695 return null;
1698 static private java.awt.Frame frame = null;
1700 /** Get the width and height of single-line text. */
1701 static public final Dimension getDimensions(final String text, final Font font) {
1702 if (null == frame) { frame = new java.awt.Frame(); frame.pack(); frame.setBackground(Color.white); } // emulating the ImageProcessor class
1703 final FontMetrics fm = frame.getFontMetrics(font);
1704 final int[] w = fm.getWidths(); // the advance widths of the first 256 chars
1705 int width = 0;
1706 for (int i = text.length() -1; i>-1; i--) {
1707 final int c = (int)text.charAt(i);
1708 if (c < 256) width += w[c];
1710 return new Dimension(width, fm.getHeight());
1713 static public final java.io.InputStream createStream(final String path_or_url) throws Exception {
1714 return 0 == path_or_url.indexOf("http://") ?
1715 new java.net.URL(path_or_url).openStream()
1716 : new java.io.BufferedInputStream(new java.io.FileInputStream(path_or_url));
1719 static public final List<Long> asList(final long[] ids) {
1720 return asList(ids, 0, ids.length);
1722 static public final List<Long> asList(final long[] ids, final int first, final int length) {
1723 final ArrayList<Long> l = new ArrayList<Long>();
1724 if (null == ids) return l;
1725 for (int i=first; i<length; i++) l.add(ids[i]);
1726 return l;
1728 /** Recursively enable/disable all components of the @param root Container. */
1729 static public void setEnabled(final Container root, final boolean b) {
1730 final LinkedList<Container> cs = new LinkedList<Container>();
1731 cs.add(root);
1732 while (cs.size() > 0) {
1733 for (final Component c : cs.removeLast().getComponents()) {
1734 if (c instanceof Container) cs.add((Container)c);
1735 c.setEnabled(b);
1740 static public final boolean ensure(final String filepath) {
1741 return ensure(new File(filepath));
1744 /** Ensure the file can be written to. Will create parent directories if necessary. */
1745 static public final synchronized boolean ensure(final File f) {
1746 final File parent = f.getParentFile();
1747 if (!parent.exists()) {
1748 if (!parent.mkdirs()) {
1749 Utils.log("FAILED to create directories " + parent.getAbsolutePath());
1750 return false;
1752 } else if (!parent.canWrite()) {
1753 Utils.log("Cannot write to parent directory " + parent.getAbsolutePath());
1754 return false;
1756 return true;
1759 /** 0.3 * R + 0.6 * G + 0.1 * B */
1760 public static final int luminance(final Color c) {
1761 return (int)(c.getRed() * 0.3f + c.getGreen() * 0.6f + c.getBlue() * 0.1f + 0.5f);
1764 /** Invoke in the context of the event dispatch thread. */
1765 public static final void invokeLater(final Runnable r) {
1766 if (EventQueue.isDispatchThread()) r.run();
1767 else SwingUtilities.invokeLater(r);
1770 public static final void showAllTables(final HashMap<Class<?>, ResultsTable> ht) {
1771 for (final Map.Entry<Class<?>,ResultsTable> entry : ht.entrySet()) {
1772 final Class<?> c = entry.getKey();
1773 String title;
1774 if (Profile_List.class == c) title = "Profile List";
1775 else {
1776 title = c.getName();
1777 final int idot = title.lastIndexOf('.');
1778 if (-1 != idot) title = title.substring(idot+1);
1780 entry.getValue().show(title + " results");
1784 /** Returns a byte[3][256] containing the colors of the fire LUT. */
1785 public static final IndexColorModel fireLUT() {
1786 final ImagePlus imp = new ImagePlus("fire", new ByteProcessor(1, 1));
1787 IJ.run(imp, "Fire", "");
1788 return (IndexColorModel) imp.getProcessor().getColorModel();
1792 static public final BufferedImage convertToBufferedImage(final ByteProcessor bp) {
1793 bp.setMinAndMax(0, 255); // TODO what is this doing here? The ByteProcessor.setMinAndMax is destructive, it expands the pixel values to the desired range.
1794 final Image img = bp.createImage();
1795 if (img instanceof BufferedImage) return (BufferedImage)img;
1796 //else:
1797 final BufferedImage bi = new BufferedImage(bp.getWidth(), bp.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, Loader.GRAY_LUT);
1798 bi.createGraphics().drawImage(img, 0, 0, null);
1799 return bi;
1804 * @param source The file to copy.
1805 * @param target The new file to create.
1806 * @return Whether the file could be copied; also returns false if the target file exists.
1807 * @throws IOException
1809 static public final boolean safeCopy(final String source, final String target) throws IOException {
1810 final File f2 = new File(target);
1811 if (f2.exists()) return false;
1812 RandomAccessFile sra = null,
1813 tra = null;
1814 try {
1815 Utils.ensure(f2);
1816 sra = new RandomAccessFile(new File(source), "r");
1817 tra = new RandomAccessFile(f2, "rw");
1818 sra.getChannel().transferTo(0, sra.length(), tra.getChannel());
1819 } finally {
1820 if (null != tra) tra.close();
1821 if (null != sra) sra.close();
1823 return true;