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
;
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
;
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
;
55 import java
.awt
.MenuItem
;
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
;
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(); }
119 public final void quit() {
121 synchronized (this) { notify(); }
123 public final void log(final String msg
) {
125 synchronized (cache
) {
126 loading
= true; // no need to synch, variable setting is atomic
127 if (0 != cache
.length()) cache
.append('\n');
133 } catch (Exception e
) {
137 synchronized (this) { notify(); }
138 } catch (Exception e
) {
145 synchronized (this) { wait(); }
147 synchronized (cache
) {
148 if (0 != cache
.length()) msg
= cache
.toString();
152 } catch (Exception e
) {
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(); }
173 public final void quit() {
175 synchronized (this) { notify(); }
177 public final void showStatus(final String msg
) {
179 synchronized (this) {
183 } catch (Exception e
) {
187 public final void showProgress(final double progress
) {
189 synchronized (this) {
190 this.progress
= progress
;
193 } catch (Exception e
) {
200 // first part ensures it gets printed even if the notify was issued while not waiting
201 synchronized (this) {
206 if (-1 != progress
) {
207 IJ
.showProgress(progress
);
212 // allow some time for overwriting of messages
214 /* // should not be needed
215 // print the msg if necessary
216 synchronized (this) {
223 } catch (Exception e
) {
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
) {
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
);
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();
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, '{');
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(", ");
340 return ob
.toString();
342 sb
.setLength(sb
.length()-2);
345 return sb
.toString();
348 static public void setDebug(boolean 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");
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");
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");
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
);
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
) {
427 setPriority(Thread
.NORM_PRIORITY
);
428 Utils
.showMessage(msg
);
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
);
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
) {
454 last_progress
= 0; // reset
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
;
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
);
483 if (gd
.wasCanceled()) {
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;
502 /** Scan the WindowManager for non-stack images*/
503 static public ImagePlus
[] findOpenImages() {
504 ImagePlus
[] imp
= scanWindowManager("images");
505 if (null == imp
) return null;
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
];
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) {
526 } else if (type
.equals("images")) {
527 if (image
.getStackSize() > 1) {
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
);
541 // return what has been found:
542 if (0 == next
) return null;
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-");
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
;
568 return sb
.toString(); // returns 0.000... as many zeros as desired n_decimals
571 StringBuffer sb
= new StringBuffer("0.");
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
)) {
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()) {
611 static public final boolean checkYN(String msg
) {
612 YesNoDialog yn
= new YesNoDialog(IJ
.getInstance(), "Execute?", msg
);
613 if (yn
.yesPressed()) return true;
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),
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");
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(),
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()) {
680 } else if (!d
.yesPressed()) {
681 return chooseFile(name
, extension
);
683 // else if yes pressed, overwrite.
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();
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.");
704 return new String
[]{dir
, file
};
707 static public final boolean saveToFile(File f
, String contents
) {
708 if (null == f
) return false;
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());
717 } catch (Exception e
) {
719 Utils
.showMessage("ERROR: Most likely did NOT save your file.");
725 static public final boolean saveToFile(String name
, String extension
, String contents
) {
726 if (null == contents
) {
727 Utils
.log2("Utils.saveToFile: contents is null");
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
) {
738 while (-1 != s
.indexOf("\u0020\u0020")) { // \u0020 equals a single space
739 s
= s
.replaceAll("\u0020\u0020", "\u0020");
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;
749 r
= new BufferedReader(new FileReader(path
));
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
) {
757 if (null != r
) try { r
.close(); } catch (IOException ioe
) { ioe
.printStackTrace(); }
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();
768 BufferedReader r
= new BufferedReader(new FileReader(path
));
770 String s
= r
.readLine();
771 if (null == s
) break;
775 } catch (Exception e
) {
779 final String
[] sal
= new String
[al
.size()];
784 static public final char[] openTextFileChars(final String path
) {
786 if (null == path
|| !(f
= new File(path
)).exists()) {
787 Utils
.log("File not found: " + path
);
790 final char[] src
= new char[(int)f
.length()]; // assumes file is small enough to fit in integer range!
792 BufferedReader r
= new BufferedReader(new FileReader(path
));
793 r
.read(src
, 0, src
.length
);
795 } catch (Exception e
) {
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);
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.");
838 static public final boolean java3d
= isJava3DInstalled();
840 static private final boolean isJava3DInstalled() {
842 Class p3f
= Class
.forName("javax.vecmath.Point3f");
843 } catch (ClassNotFoundException cnfe
) {
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();
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
]);
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();
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
]);
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
) {
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();
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
);
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
);
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
);
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
);
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
];
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
--) {
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
];
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
];
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
) {
991 // ALL that was needed: to put it into the swing repaint queue ... couldn't they JUST SAY SO
992 SwingUtilities
.invokeLater(new Runnable() {
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() {
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
1044 float d2
= 1 + h1
- h2
;
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
) {
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;
1069 Field f
= ob
.getClass().getDeclaredField(field_name
);
1070 f
.setAccessible(true);
1072 } catch (Exception e
) {
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());
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());
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
) {
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
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
);
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();
1122 for (int i
=0; i
<columns
.length
; i
++) rt
.setHeading(i
, columns
[i
]);
1127 static public final ImageProcessor
createProcessor(final int type
, final int width
, final int height
) {
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
);
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);
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
));
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
);
1172 user=> (def pat #"\b[a-zA-Z]+[\w]*\b")
1174 user=>(re-seq pat "abc def 1a334")
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();
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
;
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;
1213 if (!Utils
.isTrakEM2Subfile(f
)) {
1214 Utils
.log2("REFUSING to remove file " + f
+ "\n-->REASON: not in a '/trakem2.' file path");
1218 // If it's not a directory, just delete it
1219 if (!f
.isDirectory()) {
1222 // Else delete all directories:
1223 final ArrayList
<File
> dirs
= new ArrayList
<File
>();
1225 // Non-recursive version ... I hate java
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()) {
1239 } else if (file
.isHidden()) {
1240 if (!file
.delete()) {
1241 Utils
.log("Failed to delete hidden file " + file
.getAbsolutePath());
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());
1249 if (!file
.delete()) {
1250 Utils
.log("Failed to delete visible file " + file
.getAbsolutePath());
1253 if (null != removed_paths
) removed_paths
.add(file
.getAbsolutePath());
1258 if (!fdir
.delete()) {
1261 Utils
.log2("Removed folder " + fdir
.getAbsolutePath());
1264 } while (dirs
.size() > 0);
1268 } catch (Exception e
) {
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;
1294 if (!Utils
.isTrakEM2Subfile(parent
)) {
1295 Utils
.log2("REFUSING to remove files recursively under folder " + parent
+ "\n-->REASON: not in a '/trakem2.' file path");
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;
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
);
1323 } catch (Exception e
) {
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
) {
1355 c
= sb
.charAt(start
);
1357 } while ('\t' == c
|| ' ' == c
|| '\n' == c
);
1358 int end
= sb
.length() -1;
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
) {
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) ?
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");
1394 t
.setPriority(Thread
.NORM_PRIORITY
);