0.7m
[trakem2.git] / ini / trakem2 / utils / Utils.java
blobbb7214e3f083dc32f3924868a0ea3d78aa324083
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 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.OvalRoi;
42 import ij.text.TextWindow;
43 import ij.measure.ResultsTable;
44 import ij.process.*;
45 import ij.io.*;
46 import ij.process.ImageProcessor;
47 import ij.process.ImageConverter;
49 import java.awt.Checkbox;
50 import java.awt.Choice;
51 import java.awt.Color;
52 import java.awt.Component;
53 import java.awt.MenuBar;
54 import java.awt.Menu;
55 import java.awt.MenuItem;
56 import java.io.*;
57 import java.awt.event.ItemListener;
58 import java.awt.event.ItemEvent;
59 import java.awt.event.MouseEvent;
60 import java.awt.event.InputEvent;
61 import java.awt.Event;
62 import javax.swing.SwingUtilities;
64 import java.lang.reflect.Field;
66 import java.util.ArrayList;
67 import java.util.Iterator;
68 import java.util.Vector;
69 import java.util.Calendar;
70 import java.lang.Iterable;
71 import java.util.Iterator;
72 import java.util.Collection;
73 import java.util.Map;
74 import java.util.regex.Pattern;
75 import java.util.regex.Matcher;
76 import java.util.concurrent.Future;
77 import java.util.concurrent.ExecutorService;
78 import java.util.concurrent.Executors;
79 import java.util.concurrent.ThreadFactory;
80 import java.util.concurrent.ThreadPoolExecutor;
82 /** Utils class: stores generic widely used methods. In particular, those for logging text messages (for debugging) and also some math and memory utilities.
86 public class Utils implements ij.plugin.PlugIn {
88 static public String version = "0.7m 2009-12-04";
90 static public boolean debug = false;
91 static public boolean debug_mouse = false;
92 static public boolean debug_sql = false;
93 static public boolean debug_event = false;
94 static public boolean debug_clip = false; //clip for repainting
95 static public boolean debug_thing = false;
97 /** The error to use in floating-point or double floating point literal comparisons. */
98 static public final double FL_ERROR = 0.0000001;
100 static public void debug(String msg) {
101 if (debug) IJ.log(msg);
104 static public void debugMouse(String msg) {
105 if (debug_mouse) IJ.log(msg);
108 /** Avoid waiting on the AWT thread repainting ImageJ's log window. */
109 static private final class LogDispatcher extends Thread {
110 private final StringBuffer cache = new StringBuffer();
111 private boolean loading = false;
112 private boolean go = true;
113 public LogDispatcher() {
114 super("T2-Log-Dispatcher");
115 setPriority(Thread.NORM_PRIORITY);
116 try { setDaemon(true); } catch (Exception e) { e.printStackTrace(); }
117 start();
119 public final void quit() {
120 go = false;
121 synchronized (this) { notify(); }
123 public final void log(final String msg) {
124 try {
125 synchronized (cache) {
126 loading = true; // no need to synch, variable setting is atomic
127 if (0 != cache.length()) cache.append('\n');
128 cache.append(msg);
129 loading = false;
131 Thread.yield();
132 if (loading) return;
133 } catch (Exception e) {
134 e.printStackTrace();
136 try {
137 synchronized (this) { notify(); }
138 } catch (Exception e) {
139 e.printStackTrace();
142 public void run() {
143 while (go) {
144 try {
145 synchronized (this) { wait(); }
146 String msg = null;
147 synchronized (cache) {
148 if (0 != cache.length()) msg = cache.toString();
149 cache.setLength(0);
151 IJ.log(msg);
152 } catch (Exception e) {
153 e.printStackTrace();
158 static private LogDispatcher logger = new LogDispatcher();
160 /** Avoid waiting on the AWT thread repainting ImageJ's status bar.
161 Waits 100 ms before printing the status message; if too many status messages are being sent, the last one overrides all. */
162 static private final class StatusDispatcher extends Thread {
163 private String msg = null;
164 private boolean loading = false;
165 private boolean go = true;
166 private double progress = -1;
167 public StatusDispatcher() {
168 super("T2-Status-Dispatcher");
169 setPriority(Thread.NORM_PRIORITY);
170 try { setDaemon(true); } catch (Exception e) { e.printStackTrace(); }
171 start();
173 public final void quit() {
174 go = false;
175 synchronized (this) { notify(); }
177 public final void showStatus(final String msg) {
178 try {
179 synchronized (this) {
180 this.msg = msg;
181 notify();
183 } catch (Exception e) {
184 e.printStackTrace();
187 public final void showProgress(final double progress) {
188 try {
189 synchronized (this) {
190 this.progress = progress;
191 notify();
193 } catch (Exception e) {
194 e.printStackTrace();
197 public void run() {
198 while (go) {
199 try {
200 // first part ensures it gets printed even if the notify was issued while not waiting
201 synchronized (this) {
202 if (null != msg) {
203 IJ.showStatus(msg);
204 msg = null;
206 if (-1 != progress) {
207 IJ.showProgress(progress);
208 progress = -1;
210 wait();
212 // allow some time for overwriting of messages
213 Thread.sleep(100);
214 /* // should not be needed
215 // print the msg if necessary
216 synchronized (this) {
217 if (null != msg) {
218 IJ.showStatus(msg);
219 msg = null;
223 } catch (Exception e) {
224 e.printStackTrace();
229 static private StatusDispatcher status = new StatusDispatcher();
231 /** Initialize house keeping threads. */
232 static public final void setup(final ControlWindow master) { // the ControlWindow acts as a switch: nobody can controls this because the CW constructor is private
233 if (null != status) status.quit();
234 status = new StatusDispatcher();
235 if (null != logger) logger.quit();
236 logger = new LogDispatcher();
239 /** Destroy house keeping threads. */
240 static public final void destroy(final ControlWindow master) {
241 if (null != status) { status.quit(); status = null; }
242 if (null != logger) { logger.quit(); logger = null; }
245 /** Intended for the user to see. */
246 static public final void log(final String msg) {
247 if (ControlWindow.isGUIEnabled() && null != logger) {
248 logger.log(msg);
249 } else {
250 System.out.println(msg);
254 /** Print in all printable places: log window, System.out.println, and status bar.*/
255 static public final void logAll(final String msg) {
256 if (!ControlWindow.isGUIEnabled()) {
257 System.out.println(msg);
258 return;
260 System.out.println(msg);
261 if (null != logger) logger.log(msg);
262 if (null != status) status.showStatus(msg);
265 /** Intended for developers: prints to terminal. */
266 static public final void log2(final String msg) {
267 System.out.println(msg);
270 /** Pretty-print the object, for example arrays as [0, 1, 2]. */
271 static public final void log2(final Object ob) {
272 Utils.log2(null, ob);
274 /** Pretty-print the object, for example arrays as [0, 1, 2]. */
275 static public final void log2(final String msg, final Object ob) {
276 Utils.log2((null != msg ? msg : "") + ob + "\n\t" + Utils.toString(ob));
278 static public final void log2(final String msg, final Object ob1, final Object... ob) {
279 final StringBuffer sb = new StringBuffer(null == msg ? "" : msg + "\n");
280 sb.append(ob1.toString()).append(" : ").append(Utils.toString(ob1)).append('\n');
281 for (int i=0; i<ob.length; i++) sb.append(ob.toString()).append(" : ").append(Utils.toString(ob[i])).append('\n');
282 sb.setLength(sb.length()-1);
283 Utils.log2(sb.toString());
286 static public final void log(final Object ob) {
287 Utils.log(Utils.toString(ob));
290 static public final void log2(final Object... ob){
291 Utils.log2(Utils.toString(ob));
294 /** 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. */
295 static public final String toString(final Object ob) {
296 if (null == ob) return "null";
297 // Clojure could do this so much easier with a macro
298 final StringBuilder sb = new StringBuilder();
299 sb.append('[');
300 char closing = ']';
301 if (ob instanceof String[]) { // could be done with Object[] and recursive calls, but whatever
302 final String[] s = (String[])ob;
303 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
304 } else if (ob instanceof int[]) {
305 final int[] s = (int[])ob;
306 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
307 } else if (ob instanceof double[]) {
308 final double[] s = (double[])ob;
309 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
310 } else if (ob instanceof float[]) {
311 final float[] s = (float[])ob;
312 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
313 } else if (ob instanceof char[]) {
314 final char[] s = (char[])ob;
315 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
316 } else if (ob instanceof Object[]) {
317 final Object[] s = (Object[])ob;
318 for (int i=0; i<s.length; i++) sb.append(Utils.toString(s[i])).append(", ");
319 } else if (ob instanceof Iterable) {
320 final Iterable s = (Iterable)ob;
321 for (Iterator it = s.iterator(); it.hasNext(); ) sb.append(Utils.toString(it.next())).append(", ");
322 } else if (ob instanceof Map) {
323 sb.setCharAt(0, '{');
324 closing = '}';
325 final Map s = (Map)ob;
326 for (Iterator it = s.entrySet().iterator(); it.hasNext(); ) {
327 Map.Entry e = (Map.Entry)it.next();
328 sb.append(Utils.toString(e.getKey())).append(" => ").append(Utils.toString(e.getValue())).append(", ");
330 } else if (ob instanceof long[]) {
331 final long[] s = (long[])ob;
332 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
333 } else if (ob instanceof short[]) {
334 final short[] s = (short[])ob;
335 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
336 } else if (ob instanceof boolean[]) {
337 final boolean[] s = (boolean[])ob;
338 for (int i=0; i<s.length; i++) sb.append(s[i]).append(", ");
339 } else {
340 return ob.toString();
342 sb.setLength(sb.length()-2);
343 sb.append(closing);
344 sb.append('\n');
345 return sb.toString();
348 static public void setDebug(boolean debug) {
349 Utils.debug = debug;
352 static public void setDebugMouse(boolean debug_mouse) {
353 Utils.debug_mouse = debug_mouse;
356 static public void setDebugSQL(boolean debug_sql) {
357 Utils.debug_sql = debug_sql;
360 /** Find out which method from which class called the method where the printCaller is used; for debugging purposes.*/
361 static public final void printCaller(Object called_object) {
362 StackTraceElement[] elems = new Exception().getStackTrace();
363 if (elems.length < 3) {
364 log2("Stack trace too short! No useful info");
365 } else {
366 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() + "()");
367 log2("==== END ====");
371 static public final void printCaller(Object called_object, int lines) {
372 StackTraceElement[] elems = new Exception().getStackTrace();
373 if (elems.length < 3) {
374 log2("Stack trace too short! No useful info");
375 } else {
376 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() + "()");
377 for (int i=3; i<lines+2 && i<elems.length; i++) {
378 log2("\tby: " + elems[i].getClassName() + " " + elems[i].getLineNumber() + ": " + elems[i].getMethodName() + "()");
380 log2("==== END ====");
384 /** Returns a String representation of the class of the object one step before in the stack trace. */
385 static public final String caller(Object called) {
386 StackTraceElement[] elems = new Exception().getStackTrace();
387 if (elems.length < 3) {
388 log2("Stack trace too short! No useful info");
389 return null;
390 } else {
391 return elems[2].getClassName();
395 /**Restore ImageJ's MenuBar*/
396 static public final void restoreMenuBar() {
397 MenuBar menu_bar = Menus.getMenuBar();
398 final int n_menus = menu_bar.getMenuCount();
399 for (int i=0; i<n_menus;i++) {
400 Menu menu = menu_bar.getMenu(i);
401 restoreMenu(menu);
403 //make sure there isn't a null menu bar
404 //WindowManager.getCurrentWindow().setMenuBar(menu_bar);
407 static private void restoreMenu(final Menu menu) {
408 final int n_menuitems = menu.getItemCount();
409 for (int i=0; i<n_menuitems; i++) {
410 MenuItem menu_item = menu.getItem(i);
411 if (menu_item instanceof Menu) {
412 restoreMenu((Menu)menu_item);
414 menu_item.setEnabled(true);
418 static public final void showMessage(String msg) {
419 if (!ControlWindow.isGUIEnabled()) System.out.println(msg);
420 else IJ.showMessage(msg);
423 /** Runs the showMessage in a separate Thread. */
424 static public final void showMessageT(final String msg) {
425 new Thread() {
426 public void run() {
427 setPriority(Thread.NORM_PRIORITY);
428 Utils.showMessage(msg);
430 }.start();
433 static public final void showStatus(final String msg, final boolean focus) {
434 if (null == IJ.getInstance() || !ControlWindow.isGUIEnabled() || null == status) {
435 System.out.println(msg);
436 return;
438 if (focus) IJ.getInstance().toFront();
440 status.showStatus(msg);
443 static public final void showStatus(final String msg) {
444 showStatus(msg, false);
447 static private double last_progress = 0;
448 static private int last_percent = 0;
450 static public final void showProgress(final double p) {
451 //IJ.showProgress(p); // never happens, can't repaint even though they are different threads
452 if (null == IJ.getInstance() || !ControlWindow.isGUIEnabled() || null == status) {
453 if (0 == p) {
454 last_progress = 0; // reset
455 last_percent = 0;
456 return;
458 // don't show intervals smaller than 1%:
459 if (last_progress + 0.01 > p ) {
460 int percent = (int)(p * 100);
461 if (last_percent != percent) {
462 System.out.println(percent + " %");
463 last_percent = percent;
466 last_progress = p;
467 return;
470 status.showProgress(p);
473 static public void debugDialog() {
474 // note: all this could nicely be done using reflection, so adding another boolean variable would be automatically added here (filtering by the prefix "debug").
475 GenericDialog gd = new GenericDialog("Debug:");
476 gd.addCheckbox("debug", debug);
477 gd.addCheckbox("debug mouse", debug_mouse);
478 gd.addCheckbox("debug sql", debug_sql);
479 gd.addCheckbox("debug event", debug_event);
480 gd.addCheckbox("debug clip", debug_clip);
481 gd.addCheckbox("debug thing", debug_thing);
482 gd.showDialog();
483 if (gd.wasCanceled()) {
484 return;
486 debug = gd.getNextBoolean();
487 debug_mouse = gd.getNextBoolean();
488 debug_sql = gd.getNextBoolean();
489 debug_event = gd.getNextBoolean();
490 debug_clip = gd.getNextBoolean();
491 debug_thing = gd.getNextBoolean();
495 /** Scan the WindowManager for open stacks.*/
496 static public ImagePlus[] findOpenStacks() {
497 ImagePlus[] imp = scanWindowManager("stacks");
498 if (null == imp) return null;
499 return imp;
502 /** Scan the WindowManager for non-stack images*/
503 static public ImagePlus[] findOpenImages() {
504 ImagePlus[] imp = scanWindowManager("images");
505 if (null == imp) return null;
506 return imp;
509 /** Scan the WindowManager for all open images, including stacks.*/
510 static public ImagePlus[] findAllOpenImages() {
511 return scanWindowManager("all");
514 static private ImagePlus[] scanWindowManager(String type) {
515 // check if any stacks are opened within ImageJ
516 int[] all_ids = WindowManager.getIDList();
517 if (null == all_ids) return null;
518 ImagePlus[] imp = new ImagePlus[all_ids.length];
519 int next = 0;
520 for (int i=0; i < all_ids.length; i++) {
521 ImagePlus image = WindowManager.getImage(all_ids[i]);
522 if (type.equals("stacks")) {
523 if (image.getStackSize() <= 1) {
524 continue;
526 } else if (type.equals("images")) {
527 if (image.getStackSize() > 1) {
528 continue;
531 // add:
532 imp[next] = image;
533 next++;
535 // resize the array if necessary
536 if (next != all_ids.length) {
537 ImagePlus[] imp2 = new ImagePlus[next];
538 System.arraycopy(imp, 0, imp2, 0, next);
539 imp = imp2;
541 // return what has been found:
542 if (0 == next) return null;
543 return imp;
546 /**The path of the directory from which images have been recently loaded.*/
547 static public String last_dir = ij.Prefs.getString(ij.Prefs.DIR_IMAGE);
548 /**The path of the last opened file.*/
549 static public String last_file = null;
551 static public String cutNumber(double d, int n_decimals) {
552 return cutNumber(d, n_decimals, false);
555 /** remove_trailing_zeros will leave at least one zero after the comma if appropriate. */
556 static public final String cutNumber(final double d, final int n_decimals, final boolean remove_trailing_zeros) {
557 final String num = new Double(d).toString();
558 int i_e = num.indexOf("E-");
559 if (-1 != i_e) {
560 final int exp = Integer.parseInt(num.substring(i_e+2));
561 if (n_decimals < exp) {
562 final StringBuffer sb = new StringBuffer("0.");
563 int count = n_decimals;
564 while (count > 0) {
565 sb.append('0');
566 count--;
568 return sb.toString(); // returns 0.000... as many zeros as desired n_decimals
570 // else move comma
571 StringBuffer sb = new StringBuffer("0.");
572 int count = exp -1;
573 while (count > 0) {
574 sb.append('0');
575 count--;
577 sb.append(num.charAt(0)); // the single number before the comma
578 // up to here there are 'exp' number of decimals appended
579 int i_end = 2 + n_decimals - exp;
580 if (i_end > i_e) i_end = i_e; // there arent' that ,any, so cut
581 sb.append(num.substring(2, i_end)); // all numbers after the comma
582 return sb.toString();
584 // else, there is no scientific notation to worry about
585 final int i_dot = num.indexOf('.');
586 final StringBuffer sb = new StringBuffer(num.substring(0, i_dot+1));
587 for (int i=i_dot+1; i < (n_decimals + i_dot + 1) && i < num.length(); i++) {
588 sb.append(num.charAt(i));
590 // remove zeros from the end
591 if (remove_trailing_zeros) {
592 for (int i=sb.length()-1; i>i_dot+1; i--) { // leave at least one zero after the comma
593 if ('0' == sb.charAt(i)) {
594 sb.setLength(i);
595 } else {
596 break;
600 return sb.toString();
603 static public final boolean check(final String msg) {
604 YesNoCancelDialog dialog = new YesNoCancelDialog(IJ.getInstance(), "Execute?", msg);
605 if (dialog.yesPressed()) {
606 return true;
608 return false;
611 static public final boolean checkYN(String msg) {
612 YesNoDialog yn = new YesNoDialog(IJ.getInstance(), "Execute?", msg);
613 if (yn.yesPressed()) return true;
614 return false;
617 static public final String d2s(double d, int n_decimals) {
618 return IJ.d2s(d, n_decimals);
621 static public final String[] getHexRGBColor(Color color) {
622 int c = color.getRGB();
623 String r = Integer.toHexString(((c&0x00FF0000)>>16));
624 if (1 == r.length()) r = "0" + r;
625 String g = Integer.toHexString(((c&0x0000FF00)>>8));
626 if (1 == g.length()) g = "0" + g;
627 String b = Integer.toHexString((c&0x000000FF));
628 if (1 == b.length()) b = "0" + b;
629 return new String[]{r, g, b};
632 static public final Color getRGBColorFromHex(String hex) {
633 if (hex.length() < 6) return null;
634 return new Color(Integer.parseInt(hex.substring(0, 2), 16), // parse in hexadecimal radix
635 Integer.parseInt(hex.substring(2, 4), 16),
636 Integer.parseInt(hex.substring(4, 6), 16));
639 static public final int[] get4Ints(final int hex) {
640 return new int[]{((hex&0xFF000000)>>24),
641 ((hex&0x00FF0000)>>16),
642 ((hex&0x0000FF00)>> 8),
643 hex&0x000000FF };
646 public void run(String arg) {
647 IJ.showMessage("TrakEM2", "TrakEM2 " + Utils.version + "\nCopyright Albert Cardona & Rodney Douglas\nInstitute for Neuroinformatics, Univ/ETH Zurich.\n \nRegistration library copyright Stephan Saalfeld, MPI-CBG.\nLens correction copyright Verena Kaynig, ETH Zurich.\nSome parts copyright Ignacio Arganda, INI Univ/ETH Zurich.");
650 static public final File chooseFile(String name, String extension) {
651 return Utils.chooseFile(null, name, extension);
654 /** 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). */
655 static public final File chooseFile(String default_dir, String name, String extension) {
656 // using ImageJ's JFileChooser or internal FileDialog, according to user preferences.
657 String user = System.getProperty("user.name");
658 String name2 = null;
659 if (null != name && null != extension) name2 = name + extension;
660 else if (null != name) name2 = name;
661 else if (null != extension) name2 = "untitled" + extension;
662 if (null != default_dir) {
663 OpenDialog.setDefaultDirectory(default_dir);
665 SaveDialog sd = new SaveDialog("Save",
666 OpenDialog.getDefaultDirectory(),
667 name2,
668 extension);
670 String filename = sd.getFileName();
671 if (null == filename || filename.toLowerCase().startsWith("null")) return null;
672 String dir = sd.getDirectory();
673 if (IJ.isWindows()) dir = dir.replace('\\', '/');
674 if (!dir.endsWith("/")) dir += "/";
675 File f = new File(dir + filename);
676 if (f.exists() && ControlWindow.isGUIEnabled()) {
677 YesNoCancelDialog d = new YesNoCancelDialog(IJ.getInstance(), "Overwrite?", "File " + filename + " exists! Overwrite?");
678 if (d.cancelPressed()) {
679 return null;
680 } else if (!d.yesPressed()) {
681 return chooseFile(name, extension);
683 // else if yes pressed, overwrite.
685 return f;
688 /** Returns null or the selected directory and file. */
689 static public final String[] selectFile(String title_msg) {
690 OpenDialog od = new OpenDialog("Select file", OpenDialog.getDefaultDirectory(), null);
691 String file = od.getFileName();
692 if (null == file || file.toLowerCase().startsWith("null")) return null;
693 String dir = od.getDirectory();
694 File f = null;
695 if (null != dir) {
696 if (IJ.isWindows()) dir = dir.replace('\\', '/');
697 if (!dir.endsWith("/")) dir += "/";
698 f = new File(dir + file); // I'd use File.separator, but in Windows it fails
700 if (null == dir || !f.exists()) {
701 Utils.log2("No proper file selected.");
702 return null;
704 return new String[]{dir, file};
707 static public final boolean saveToFile(File f, String contents) {
708 if (null == f) return false;
709 try {
711 DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f), contents.length()));
713 OutputStreamWriter dos = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(f), contents.length()), "8859_1"); // encoding in Latin 1 (for macosx not to mess around
714 //dos.writeBytes(contents);
715 dos.write(contents, 0, contents.length());
716 dos.flush();
717 } catch (Exception e) {
718 IJError.print(e);
719 Utils.showMessage("ERROR: Most likely did NOT save your file.");
720 return false;
722 return true;
725 static public final boolean saveToFile(String name, String extension, String contents) {
726 if (null == contents) {
727 Utils.log2("Utils.saveToFile: contents is null");
728 return false;
730 // save the file
731 File f = Utils.chooseFile(name, extension);
732 return Utils.saveToFile(f, contents);
735 /** Converts sequences of spaces into single space, and trims the ends. */
736 static public final String cleanString(String s) {
737 s = s.trim();
738 while (-1 != s.indexOf("\u0020\u0020")) { // \u0020 equals a single space
739 s = s.replaceAll("\u0020\u0020", "\u0020");
741 return s;
744 static public final String openTextFile(final String path) {
745 if (null == path || !new File(path).exists()) return null;
746 final StringBuffer sb = new StringBuffer();
747 BufferedReader r = null;
748 try {
749 r = new BufferedReader(new FileReader(path));
750 while (true) {
751 String s = r.readLine();
752 if (null == s) break;
753 sb.append(s).append('\n'); // I am sure the reading can be done better
755 } catch (Exception e) {
756 IJError.print(e);
757 if (null != r) try { r.close(); } catch (IOException ioe) { ioe.printStackTrace(); }
758 return null;
760 return sb.toString();
763 /** Returns the file found at path as an array of lines, or null if not found. */
764 static public final String[] openTextFileLines(final String path) {
765 if (null == path || !new File(path).exists()) return null;
766 final ArrayList al = new ArrayList();
767 try {
768 BufferedReader r = new BufferedReader(new FileReader(path));
769 while (true) {
770 String s = r.readLine();
771 if (null == s) break;
772 al.add(s);
774 r.close();
775 } catch (Exception e) {
776 IJError.print(e);
777 return null;
779 final String[] sal = new String[al.size()];
780 al.toArray(sal);
781 return sal;
784 static public final char[] openTextFileChars(final String path) {
785 File f = null;
786 if (null == path || !(f = new File(path)).exists()) {
787 Utils.log("File not found: " + path);
788 return null;
790 final char[] src = new char[(int)f.length()]; // assumes file is small enough to fit in integer range!
791 try {
792 BufferedReader r = new BufferedReader(new FileReader(path));
793 r.read(src, 0, src.length);
794 r.close();
795 } catch (Exception e) {
796 IJError.print(e);
797 return null;
799 return src;
802 /** The cosinus between two vectors (in polar coordinates), by means of the dot product. */
803 static public final double getCos(final double x1, final double y1, final double x2, final double y2) {
804 return (x1 * x2 + y1 * y2) / (Math.sqrt(x1*x1 + y1*y1) * Math.sqrt(x2*x2 + y2*y2));
807 static public final String removeExtension(final String path) {
808 final int i_dot = path.lastIndexOf('.');
809 if (-1 == i_dot || i_dot + 4 != path.length()) return path;
810 else return path.substring(0, i_dot);
813 /** A helper for GenericDialog checkboxes to control other the enabled state of other GUI elements in the same dialog. */
814 static public final void addEnablerListener(final Checkbox master, final Component[] enable, final Component[] disable) {
815 master.addItemListener(new ItemListener() {
816 public void itemStateChanged(ItemEvent ie) {
817 if (ie.getStateChange() == ItemEvent.SELECTED) {
818 process(enable, true);
819 process(disable, false);
820 } else {
821 process(enable, false);
822 process(disable, true);
825 private void process(final Component[] c, final boolean state) {
826 if (null == c) return;
827 for (int i=0; i<c.length; i++) c[i].setEnabled(state);
832 static public final boolean wrongImageJVersion() {
833 boolean b = IJ.versionLessThan("1.37g");
834 if (b) Utils.showMessage("TrakEM2 requires ImageJ 1.37g or above.");
835 return b;
838 static public final boolean java3d = isJava3DInstalled();
840 static private final boolean isJava3DInstalled() {
841 try {
842 Class p3f = Class.forName("javax.vecmath.Point3f");
843 } catch (ClassNotFoundException cnfe) {
844 return false;
846 return true;
849 static public final void addLayerRangeChoices(final Layer selected, final GenericDialog gd) {
850 Utils.addLayerRangeChoices(selected, selected, gd);
853 static public final void addLayerRangeChoices(final Layer first, final Layer last, final GenericDialog gd) {
854 final String[] layers = new String[first.getParent().size()];
855 final ArrayList al_layer_titles = new ArrayList();
856 int i = 0;
857 for (Iterator it = first.getParent().getLayers().iterator(); it.hasNext(); ) {
858 layers[i] = first.getProject().findLayerThing((Layer)it.next()).toString();
859 al_layer_titles.add(layers[i]);
860 i++;
862 final int i_first = first.getParent().indexOf(first);
863 final int i_last = last.getParent().indexOf(last);
864 gd.addChoice("Start: ", layers, layers[i_first]);
865 final Vector v = gd.getChoices();
866 final Choice cstart = (Choice)v.get(v.size()-1);
867 gd.addChoice("End: ", layers, layers[i_last]);
868 final Choice cend = (Choice)v.get(v.size()-1);
869 cstart.addItemListener(new ItemListener() {
870 public void itemStateChanged(ItemEvent ie) {
871 int index = al_layer_titles.indexOf(ie.getItem());
872 if (index > cend.getSelectedIndex()) cend.select(index);
875 cend.addItemListener(new ItemListener() {
876 public void itemStateChanged(ItemEvent ie) {
877 int index = al_layer_titles.indexOf(ie.getItem());
878 if (index < cstart.getSelectedIndex()) cstart.select(index);
883 static public final void addLayerChoice(final String label, final Layer selected, final GenericDialog gd) {
884 final String[] layers = new String[selected.getParent().size()];
885 final ArrayList al_layer_titles = new ArrayList();
886 int i = 0;
887 for (Iterator it = selected.getParent().getLayers().iterator(); it.hasNext(); ) {
888 layers[i] = selected.getProject().findLayerThing((Layer)it.next()).toString();
889 al_layer_titles.add(layers[i]);
890 i++;
892 final int i_layer = selected.getParent().indexOf(selected);
893 gd.addChoice(label, layers, layers[i_layer]);
897 static public final void addRGBColorSliders(final GenericDialog gd, final Color color) {
898 gd.addSlider("Red: ", 0, 255, color.getRed());
899 gd.addSlider("Green: ", 0, 255, color.getGreen());
900 gd.addSlider("Blue: ", 0, 255, color.getBlue());
903 /** Converts the ImageProcessor to an ImageProcessor of the given type, or the same if of equal type. */
904 static final public ImageProcessor convertTo(final ImageProcessor ip, final int type, final boolean scaling) {
905 switch (type) {
906 case ImagePlus.GRAY8:
907 return ip.convertToByte(scaling);
908 case ImagePlus.GRAY16:
909 return ip.convertToShort(scaling);
910 case ImagePlus.GRAY32:
911 return ip.convertToFloat();
912 case ImagePlus.COLOR_RGB:
913 return ip.convertToRGB();
914 case ImagePlus.COLOR_256:
915 ImagePlus imp = new ImagePlus("", ip.convertToRGB());
916 new ImageConverter(imp).convertRGBtoIndexedColor(256);
917 return imp.getProcessor();
918 default:
919 return null;
923 /** 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. */
924 static public final double[] copy(final double[] a, final int new_length) {
925 final double[] b = new double[new_length];
926 final int len = a.length > new_length ? new_length : a.length;
927 System.arraycopy(a, 0, b, 0, len);
928 return b;
931 static public final long[] copy(final long[] a, final int new_length) {
932 final long[] b = new long[new_length];
933 final int len = a.length > new_length ? new_length : a.length;
934 System.arraycopy(a, 0, b, 0, len);
935 return b;
938 static public final double[] copy(final double[] a, final int first, final int new_length) {
939 final double[] b = new double[new_length];
940 final int len = new_length < a.length - first ? new_length : a.length - first;
941 System.arraycopy(a, first, b, 0, len);
942 return b;
945 static public final long[] copy(final long[] a, final int first, final int new_length) {
946 final long[] b = new long[new_length];
947 final int len = new_length < a.length - first ? new_length : a.length - first;
948 System.arraycopy(a, first, b, 0, len);
949 return b;
952 /** Reverse in place an array of doubles. */
953 static public final void reverse(final double[] a) {
954 for (int left=0, right=a.length-1; left<right; left++, right--) {
955 double tmp = a[left];
956 a[left] = a[right];
957 a[right] = tmp;
960 /** Reverse in place an array of longs. */
961 static public final void reverse(final long[] a) {
962 for (int left=0, right=a.length-1; left<right; left++, right--) {
963 long tmp = a[left];
964 a[left] = a[right];
965 a[right] = tmp;
969 static public final double[] toDouble(final int[] a, final int len) {
970 final double[] b = new double[len];
971 for (int i=0; i<len; i++) b[i] = a[i];
972 return b;
975 static public final int[] toInt(final double[] a, final int len) {
976 final int[] b = new int[len];
977 for (int i=0; i<len; i++) b[i] = (int) a[i];
978 return b;
981 /** OS-agnostic diagnosis of whether the click was for the contextual popup menu. */
982 static public final boolean isPopupTrigger(final MouseEvent me) {
983 // ImageJ way, in ij.gui.ImageCanvas class, plus an is-windows switch to prevent meta key from poping up for MacOSX
984 return (me.isPopupTrigger() && me.getButton() != 0) || (IJ.isWindows() && 0 != (me.getModifiers() & Event.META_MASK) );
987 /** Repaint the given Component on the swing repaint thread (aka "SwingUtilities.invokeLater"). */
988 static public final void updateComponent(final Component c) {
989 //c.invalidate();
990 //c.validate();
991 // ALL that was needed: to put it into the swing repaint queue ... couldn't they JUST SAY SO
992 SwingUtilities.invokeLater(new Runnable() {
993 public void run() {
994 c.repaint();
998 /** Like calling pack() on a Frame but on a Component. */
999 static public final void revalidateComponent(final Component c) {
1000 SwingUtilities.invokeLater(new Runnable() {
1001 public void run() {
1002 c.invalidate();
1003 c.validate();
1004 c.repaint();
1009 /** Returns the time as HH:MM:SS */
1010 static public final String now() {
1011 /* Java time management is retarded. */
1012 final Calendar c = Calendar.getInstance();
1013 final int hour = c.get(Calendar.HOUR_OF_DAY);
1014 final int min = c.get(Calendar.MINUTE);
1015 final int sec = c.get(Calendar.SECOND);
1016 final StringBuffer sb = new StringBuffer();
1017 if (hour < 10) sb.append('0');
1018 sb.append(hour).append(':');
1019 if (min < 10) sb.append('0');
1020 sb.append(min).append(':');
1021 if (sec < 10) sb.append(0);
1022 return sb.append(sec).toString();
1025 static public final void sleep(final long miliseconds) {
1026 try { Thread.currentThread().sleep(miliseconds); } catch (Exception e) { e.printStackTrace(); }
1029 /** Mix colors visually: red + green = yellow, etc.*/
1030 static public final Color mix(Color c1, Color c2) {
1031 final float[] b = Color.RGBtoHSB(c1.getRed(), c1.getGreen(), c1.getBlue(), new float[3]);
1032 final float[] c = Color.RGBtoHSB(c2.getRed(), c2.getGreen(), c2.getBlue(), new float[3]);
1033 final float[] a = new float[3];
1034 // find to which side the hue values are closer, since hue space is a a circle
1035 // hue values all between 0 and 1
1036 float h1 = b[0];
1037 float h2 = c[0];
1038 if (h1 < h2) {
1039 float tmp = h1;
1040 h1 = h2;
1041 h2 = tmp;
1043 float d1 = h2 - h1;
1044 float d2 = 1 + h1 - h2;
1045 if (d1 < d2) {
1046 a[0] = h1 + d1 / 2;
1047 } else {
1048 a[0] = h2 + d2 / 2;
1049 if (a[0] > 1) a[0] -= 1;
1052 for (int i=1; i<3; i++) a[i] = (b[i] + c[i]) / 2; // only Saturation and Brightness can be averaged
1053 return Color.getHSBColor(a[0], a[1], a[2]);
1056 /** 1 A, 2 B, 3 C --- 26 - z, 27 AA, 28 AB, 29 AC --- 26*27 AAA */
1057 static public final String getCharacter(int i) {
1058 i--;
1059 int k = i / 26;
1060 char c = (char)((i % 26) + 65); // 65 is 'A'
1061 if (0 == k) return Character.toString(c);
1062 return new StringBuffer().append(getCharacter(k)).append(c).toString();
1065 /** Get by reflection a private or protected field in the given object. */
1066 static public final Object getField(final Object ob, final String field_name) {
1067 if (null == ob || null == field_name) return null;
1068 try {
1069 Field f = ob.getClass().getDeclaredField(field_name);
1070 f.setAccessible(true);
1071 return f.get(ob);
1072 } catch (Exception e) {
1073 IJError.print(e);
1075 return null;
1078 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1079 static public final FloatProcessor fastConvertToFloat(final ByteProcessor ip) {
1080 final byte[] pix = (byte[])ip.getPixels();
1081 final float[] data = new float[pix.length];
1082 for (int i=0; i<pix.length; i++) data[i] = pix[i]&0xff;
1083 final FloatProcessor fp = new FloatProcessorT2(ip.getWidth(), ip.getHeight(), data, ip.getColorModel(), ip.getMin(), ip.getMax());
1084 return fp;
1086 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1087 static public final FloatProcessor fastConvertToFloat(final ShortProcessor ip) {
1088 final short[] pix = (short[])ip.getPixels();
1089 final float[] data = new float[pix.length];
1090 for (int i=0; i<pix.length; i++) data[i] = pix[i]&0xffff;
1091 final FloatProcessor fp = new FloatProcessorT2(ip.getWidth(), ip.getHeight(), data, ip.getColorModel(), ip.getMin(), ip.getMax());
1092 return fp;
1094 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1095 static public final FloatProcessor fastConvertToFloat(final ImageProcessor ip, final int type) {
1096 switch (type) {
1097 case ImagePlus.GRAY16: return fastConvertToFloat((ShortProcessor)ip);
1098 case ImagePlus.GRAY32: return (FloatProcessor)ip;
1099 case ImagePlus.GRAY8:
1100 case ImagePlus.COLOR_256: return fastConvertToFloat((ByteProcessor)ip);
1101 case ImagePlus.COLOR_RGB: return (FloatProcessor)ip.convertToFloat(); // SLOW
1103 return null;
1105 static public final FloatProcessor fastConvertToFloat(final ImageProcessor ip) {
1106 if (ip instanceof ByteProcessor) return fastConvertToFloat((ByteProcessor)ip);
1107 if (ip instanceof ShortProcessor) return fastConvertToFloat((ShortProcessor)ip);
1108 return (FloatProcessor)ip.convertToFloat();
1111 /** 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. */
1112 static public final ResultsTable createResultsTable(final String title, final String[] columns) {
1113 TextWindow tw = (TextWindow)WindowManager.getFrame(title);
1114 if (null != tw) {
1115 // hacking again ... missing a getResultsTable() method in TextWindow
1116 ResultsTable rt = (ResultsTable)Utils.getField(tw.getTextPanel(), "rt");
1117 if (null != rt) return rt; // assumes columns will be identical
1119 // else create a new one
1120 ResultsTable rt = new ResultsTable();
1121 rt.setPrecision(2);
1122 for (int i=0; i<columns.length; i++) rt.setHeading(i, columns[i]);
1124 return rt;
1127 static public final ImageProcessor createProcessor(final int type, final int width, final int height) {
1128 switch (type) {
1129 case ImagePlus.GRAY8: return new ByteProcessor(width, height);
1130 case ImagePlus.GRAY16: return new ShortProcessor(width, height);
1131 case ImagePlus.GRAY32: return new FloatProcessor(width, height);
1132 case ImagePlus.COLOR_RGB: return new ColorProcessor(width, height);
1134 return null;
1137 /** Paints an approximation of the pipe into the set of slices. */
1138 public void paint(final Pipe pipe, final Map<Layer,ImageProcessor> slices, final int value, final float scale) {
1139 VectorString3D vs = pipe.asVectorString3D();
1140 vs.resample(1); // one pixel
1141 double[] px = vs.getPoints(0);
1142 double[] py = vs.getPoints(1);
1143 double[] pz = vs.getPoints(2);
1144 double[] pr = vs.getDependent(0);
1145 // For each point
1146 for (int i=0; i<px.length-1; i++) {
1147 ImageProcessor ip = slices.get(pipe.getLayerSet().getNearestLayer(pz[i]));
1148 if (null == ip) continue;
1149 OvalRoi ov = new OvalRoi((int)((px[i] - pr[i]) * scale),
1150 (int)((py[i] - pr[i]) * scale),
1151 (int)(pr[i]*2*scale), (int)(pr[i]*2*scale));
1152 ip.setRoi(ov);
1153 ip.setValue(value);
1154 ip.fill(ip.getMask());
1158 static final public boolean matches(final String pattern, final String s) {
1159 return Pattern.compile(pattern).matcher(s).matches();
1162 static final public boolean isValidIdentifier(final String s) {
1163 if (null == s) return false;
1164 if (!Utils.matches("^[a-zA-Z]+[a-zA-Z1-9_]*$", s)) {
1165 Utils.log("Invalid identifier " + s);
1166 return false;
1168 return true;
1172 user=> (def pat #"\b[a-zA-Z]+[\w]*\b")
1173 #'user/pat
1174 user=>(re-seq pat "abc def 1a334")
1175 ("abc" "def")
1176 user=> (re-seq pat "abc def a334")
1177 ("abc" "def" "a334")
1179 Then concatenate all good words with underscores.
1180 Returns null when nothing valid is found in 's'.
1182 static final public String makeValidIdentifier(final String s) {
1183 if (null == s) return null;
1184 // Concatenate all good groups with underscores:
1185 final Pattern pat = Pattern.compile("\\b[a-zA-Z]+[\\w]*\\b");
1186 final Matcher m = pat.matcher(s);
1187 final StringBuffer sb = new StringBuffer();
1188 while (m.find()) {
1189 sb.append(m.group()).append('_');
1191 if (0 == sb.length()) return null;
1192 // Remove last underscore
1193 sb.setLength(sb.length()-1);
1194 return sb.toString();
1197 static final public int indexOf(final Object needle, final Object[] haystack) {
1198 for (int i=0; i<haystack.length; i++) {
1199 if (haystack[i].equals(needle)) return i;
1201 return -1;
1204 /** 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. */
1205 static public final boolean removeFile(final File f) {
1206 return Utils.removeFile(f, true, null);
1209 // Accumulates removed files (not directories) into removed_paths, if not null.
1210 static private final boolean removeFile(final File f, final boolean stop_if_dir_not_empty, final ArrayList<String> removed_paths) {
1211 if (null == f || !f.exists()) return false;
1212 try {
1213 if (!Utils.isTrakEM2Subfile(f)) {
1214 Utils.log2("REFUSING to remove file " + f + "\n-->REASON: not in a '/trakem2.' file path");
1215 return false;
1218 // If it's not a directory, just delete it
1219 if (!f.isDirectory()) {
1220 return f.delete();
1222 // Else delete all directories:
1223 final ArrayList<File> dirs = new ArrayList<File>();
1224 dirs.add(f);
1225 // Non-recursive version ... I hate java
1226 do {
1227 int i = dirs.size() -1;
1228 final File fdir = dirs.get(i);
1229 Utils.log2("Examining folder for deletion: " + fdir.getName());
1230 boolean remove = true;
1231 File[] files = fdir.listFiles();
1232 if (null == files) continue; // can be null if the directory doesn't contain any files. Why not just return an empty array!?
1233 for (final File file : files) {
1234 String name = file.getName();
1235 if (name.equals(".") || name.equals("..")) continue;
1236 if (file.isDirectory()) {
1237 remove = false;
1238 dirs.add(file);
1239 } else if (file.isHidden()) {
1240 if (!file.delete()) {
1241 Utils.log("Failed to delete hidden file " + file.getAbsolutePath());
1242 return false;
1244 if (null != removed_paths) removed_paths.add(file.getAbsolutePath());
1245 } else if (stop_if_dir_not_empty) {
1246 //Utils.log("Not empty: cannot remove dir " + fdir.getAbsolutePath());
1247 return false;
1248 } else {
1249 if (!file.delete()) {
1250 Utils.log("Failed to delete visible file " + file.getAbsolutePath());
1251 return false;
1253 if (null != removed_paths) removed_paths.add(file.getAbsolutePath());
1256 if (remove) {
1257 dirs.remove(i);
1258 if (!fdir.delete()) {
1259 return false;
1260 } else {
1261 Utils.log2("Removed folder " + fdir.getAbsolutePath());
1264 } while (dirs.size() > 0);
1266 return true;
1268 } catch (Exception e) {
1269 IJError.print(e);
1271 return false;
1274 /** Returns true if the file cannonical path contains "/trakem2." (adjusted for Windows as well). */
1275 static public boolean isTrakEM2Subfile(final File f) throws Exception {
1276 return isTrakEM2Subpath(f.getCanonicalPath());
1279 /** Returns true if the path contains "/trakem2." (adjusted for Windows as well). */
1280 static public boolean isTrakEM2Subpath(String path) {
1281 if (IJ.isWindows()) path = path.replace('\\', '/');
1282 return -1 != path.toLowerCase().indexOf("/trakem2.");
1285 /** Returns true if all files and their subdirectories, recursively, under parent folder have been removed.
1286 * For safety reasons, this function will return false immediately if the parent file path does not include a
1287 * lowercase "trakem2." in it.
1288 * If removed_paths is not null, all removed full paths are added to it.
1290 static public final boolean removePrefixedFiles(final File parent, final String prefix, final ArrayList<String> removed_paths) {
1291 if (null == parent || !parent.isDirectory()) return false;
1293 try {
1294 if (!Utils.isTrakEM2Subfile(parent)) {
1295 Utils.log2("REFUSING to remove files recursively under folder " + parent + "\n-->REASON: not in a '/trakem2.' file path");
1296 return false;
1299 boolean success = true;
1301 final File[] list = parent.listFiles(new FilenameFilter() {
1302 public boolean accept(File dir, String name) {
1303 if (name.startsWith(prefix)) return true;
1304 return false;
1308 ArrayList<String> a = null;
1309 if (null != removed_paths) a = new ArrayList<String>();
1311 if (null != list && list.length > 0) {
1312 for (final File f : list) {
1313 if (!Utils.removeFile(f, false, a)) success = false;
1314 if (null != removed_paths) {
1315 removed_paths.addAll(a);
1316 a.clear();
1321 return true;
1323 } catch (Exception e) {
1324 IJError.print(e);
1326 return false;
1329 /** The CTRL key functionality is passed over to the COMMAND key (aka META key) in a MacOSX. */
1330 static public final int getControlModifier() {
1331 return IJ.isMacOSX() ? InputEvent.META_MASK
1332 : InputEvent.CTRL_MASK;
1335 /** The CTRL key functionality is passed over to the COMMAND key (aka META key) in a MacOSX. */
1336 static public final boolean isControlDown(final InputEvent e) {
1337 return IJ.isMacOSX() ? e.isMetaDown()
1338 : e.isControlDown();
1341 static public final void drawPoint(final java.awt.Graphics g, final int x, final int y) {
1342 g.setColor(Color.white);
1343 g.drawLine(x-4, y+2, x+8, y+2);
1344 g.drawLine(x+2, y-4, x+2, y+8);
1345 g.setColor(Color.yellow);
1346 g.fillRect(x+1,y+1,3,3);
1347 g.setColor(Color.black);
1348 g.drawRect(x, y, 4, 4);
1351 static public final String trim(CharSequence sb) {
1352 char c;
1353 int start = 0;
1354 do {
1355 c = sb.charAt(start);
1356 start++;
1357 } while ('\t' == c || ' ' == c || '\n' == c);
1358 int end = sb.length() -1;
1359 do {
1360 c = sb.charAt(end);
1361 end--;
1362 } while ('\n' == c || ' ' == c || '\t' == c);
1364 return sb.subSequence(start-1, end+2).toString();
1367 static public final void wait(final Collection<Future> fus) {
1368 for (final Future fu : fus) {
1369 if (null != fu) try {
1370 fu.get(); // wait until done
1371 } catch (Exception e) {
1372 IJError.print(e);
1377 /** Convert a D:\\this\that\there to D://this/that/there/
1378 * Notice it adds an ending backslash. */
1379 static public final String fixDir(String path) {
1380 if (IJ.isWindows()) path = path.replace('\\', '/');
1381 return '/' == path.charAt(path.length() -1) ?
1382 path
1383 : new StringBuilder(path.length() +1).append(path).append('/').toString();
1386 /** Creates a new fixed thread pool whose threads are in the same ThreadGroup as the Thread that calls this method.
1387 * This allows for the threads to be interrupted when the caller thread's group is interrupted. */
1388 static public final ThreadPoolExecutor newFixedThreadPool(final int n_proc) {
1389 final ThreadPoolExecutor exec = (ThreadPoolExecutor) Executors.newFixedThreadPool(n_proc);
1390 exec.setThreadFactory(new ThreadFactory() {
1391 public Thread newThread(final Runnable r) {
1392 final Thread t = new Thread(Thread.currentThread().getThreadGroup(), r, "AlignLayersTask executor");
1393 t.setDaemon(true);
1394 t.setPriority(Thread.NORM_PRIORITY);
1395 return t;
1398 return exec;