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