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
;
77 /** Utils class: stores generic widely used methods. In particular, those for logging text messages (for debugging) and also some math and memory utilities.
81 public class Utils
implements ij
.plugin
.PlugIn
{
83 static public String version
= "0.7g 2009-05-27";
85 static public boolean debug
= false;
86 static public boolean debug_mouse
= false;
87 static public boolean debug_sql
= false;
88 static public boolean debug_event
= false;
89 static public boolean debug_clip
= false; //clip for repainting
90 static public boolean debug_thing
= false;
92 /** The error to use in floating-point or double floating point literal comparisons. */
93 static public final double FL_ERROR
= 0.0000001;
95 static public void debug(String msg
) {
96 if (debug
) IJ
.log(msg
);
99 static public void debugMouse(String msg
) {
100 if (debug_mouse
) IJ
.log(msg
);
103 /** Avoid waiting on the AWT thread repainting ImageJ's log window. */
104 static private final class LogDispatcher
extends Thread
{
105 private final StringBuffer cache
= new StringBuffer();
106 private boolean loading
= false;
107 private boolean go
= true;
108 public LogDispatcher() {
109 super("T2-Log-Dispatcher");
110 setPriority(Thread
.NORM_PRIORITY
);
111 try { setDaemon(true); } catch (Exception e
) { e
.printStackTrace(); }
114 public final void quit() {
116 synchronized (this) { notify(); }
118 public final void log(final String msg
) {
120 synchronized (cache
) {
121 loading
= true; // no need to synch, variable setting is atomic
122 if (0 != cache
.length()) cache
.append('\n');
128 } catch (Exception e
) {
132 synchronized (this) { notify(); }
133 } catch (Exception e
) {
140 synchronized (this) { wait(); }
142 synchronized (cache
) {
143 if (0 != cache
.length()) msg
= cache
.toString();
147 } catch (Exception e
) {
153 static private LogDispatcher logger
= new LogDispatcher();
155 /** Avoid waiting on the AWT thread repainting ImageJ's status bar.
156 Waits 100 ms before printing the status message; if too many status messages are being sent, the last one overrides all. */
157 static private final class StatusDispatcher
extends Thread
{
158 private String msg
= null;
159 private boolean loading
= false;
160 private boolean go
= true;
161 private double progress
= -1;
162 public StatusDispatcher() {
163 super("T2-Status-Dispatcher");
164 setPriority(Thread
.NORM_PRIORITY
);
165 try { setDaemon(true); } catch (Exception e
) { e
.printStackTrace(); }
168 public final void quit() {
170 synchronized (this) { notify(); }
172 public final void showStatus(final String msg
) {
174 synchronized (this) {
178 } catch (Exception e
) {
182 public final void showProgress(final double progress
) {
184 synchronized (this) {
185 this.progress
= progress
;
188 } catch (Exception e
) {
195 // first part ensures it gets printed even if the notify was issued while not waiting
196 synchronized (this) {
201 if (-1 != progress
) {
202 IJ
.showProgress(progress
);
207 // allow some time for overwriting of messages
209 /* // should not be needed
210 // print the msg if necessary
211 synchronized (this) {
218 } catch (Exception e
) {
224 static private StatusDispatcher status
= new StatusDispatcher();
226 /** Initialize house keeping threads. */
227 static public final void setup(final ControlWindow master
) { // the ControlWindow acts as a switch: nobody can controls this because the CW constructor is private
228 if (null != status
) status
.quit();
229 status
= new StatusDispatcher();
230 if (null != logger
) logger
.quit();
231 logger
= new LogDispatcher();
234 /** Destroy house keeping threads. */
235 static public final void destroy(final ControlWindow master
) {
236 if (null != status
) { status
.quit(); status
= null; }
237 if (null != logger
) { logger
.quit(); logger
= null; }
240 /** Intended for the user to see. */
241 static public final void log(final String msg
) {
242 if (ControlWindow
.isGUIEnabled() && null != logger
) {
245 System
.out
.println(msg
);
249 /** Print in all printable places: log window, System.out.println, and status bar.*/
250 static public final void logAll(final String msg
) {
251 if (!ControlWindow
.isGUIEnabled()) {
252 System
.out
.println(msg
);
255 System
.out
.println(msg
);
256 if (null != logger
) logger
.log(msg
);
257 if (null != status
) status
.showStatus(msg
);
260 /** Intended for developers: prints to terminal. */
261 static public final void log2(final String msg
) {
262 System
.out
.println(msg
);
265 /** Pretty-print the object, for example arrays as [0, 1, 2]. */
266 static public final void log2(final Object ob
) {
267 Utils
.log2(null, ob
);
269 /** Pretty-print the object, for example arrays as [0, 1, 2]. */
270 static public final void log2(final String msg
, final Object ob
) {
271 Utils
.log2((null != msg ? msg
: "") + ob
+ "\n\t" + Utils
.toString(ob
));
273 static public final void log2(final String msg
, final Object ob1
, final Object
... ob
) {
274 final StringBuffer sb
= new StringBuffer(null == msg ?
"" : msg
+ "\n");
275 sb
.append(ob1
.toString()).append(" : ").append(Utils
.toString(ob1
)).append('\n');
276 for (int i
=0; i
<ob
.length
; i
++) sb
.append(ob
.toString()).append(" : ").append(Utils
.toString(ob
[i
])).append('\n');
277 sb
.setLength(sb
.length()-1);
278 Utils
.log2(sb
.toString());
281 /** 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. */
282 static public final String
toString(final Object ob
) {
283 if (null == ob
) return "null";
284 // Clojure could do this so much easier with a macro
285 final StringBuffer sb
= new StringBuffer();
288 if (ob
instanceof String
[]) { // could be done with Object[] and recursive calls, but whatever
289 final String
[] s
= (String
[])ob
;
290 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
291 } else if (ob
instanceof int[]) {
292 final int[] s
= (int[])ob
;
293 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
294 } else if (ob
instanceof double[]) {
295 final double[] s
= (double[])ob
;
296 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
297 } else if (ob
instanceof float[]) {
298 final float[] s
= (float[])ob
;
299 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
300 } else if (ob
instanceof char[]) {
301 final char[] s
= (char[])ob
;
302 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
303 } else if (ob
instanceof Object
[]) {
304 final Object
[] s
= (Object
[])ob
;
305 for (int i
=0; i
<s
.length
; i
++) sb
.append(Utils
.toString(s
[i
])).append(", ");
306 } else if (ob
instanceof Iterable
) {
307 final Iterable s
= (Iterable
)ob
;
308 for (Iterator it
= s
.iterator(); it
.hasNext(); ) sb
.append(Utils
.toString(it
.next())).append(", ");
309 } else if (ob
instanceof Map
) {
310 sb
.setCharAt(0, '{');
312 final Map s
= (Map
)ob
;
313 for (Iterator it
= s
.entrySet().iterator(); it
.hasNext(); ) {
314 Map
.Entry e
= (Map
.Entry
)it
.next();
315 sb
.append(Utils
.toString(e
.getKey())).append(" => ").append(Utils
.toString(e
.getValue())).append(", ");
317 } else if (ob
instanceof long[]) {
318 final long[] s
= (long[])ob
;
319 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
320 } else if (ob
instanceof short[]) {
321 final short[] s
= (short[])ob
;
322 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
323 } else if (ob
instanceof boolean[]) {
324 final boolean[] s
= (boolean[])ob
;
325 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
327 return ob
.toString();
329 sb
.setLength(sb
.length()-2);
332 return sb
.toString();
335 static public void setDebug(boolean debug
) {
339 static public void setDebugMouse(boolean debug_mouse
) {
340 Utils
.debug_mouse
= debug_mouse
;
343 static public void setDebugSQL(boolean debug_sql
) {
344 Utils
.debug_sql
= debug_sql
;
347 /** Find out which method from which class called the method where the printCaller is used; for debugging purposes.*/
348 static public final void printCaller(Object called_object
) {
349 StackTraceElement
[] elems
= new Exception().getStackTrace();
350 if (elems
.length
< 3) {
351 log2("Stack trace too short! No useful info");
353 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() + "()");
354 log2("==== END ====");
358 static public final void printCaller(Object called_object
, int lines
) {
359 StackTraceElement
[] elems
= new Exception().getStackTrace();
360 if (elems
.length
< 3) {
361 log2("Stack trace too short! No useful info");
363 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() + "()");
364 for (int i
=3; i
<lines
+2 && i
<elems
.length
; i
++) {
365 log2("\tby: " + elems
[i
].getClassName() + " " + elems
[i
].getLineNumber() + ": " + elems
[i
].getMethodName() + "()");
367 log2("==== END ====");
371 /** Returns a String representation of the class of the object one step before in the stack trace. */
372 static public final String
caller(Object called
) {
373 StackTraceElement
[] elems
= new Exception().getStackTrace();
374 if (elems
.length
< 3) {
375 log2("Stack trace too short! No useful info");
378 return elems
[2].getClassName();
382 /**Restore ImageJ's MenuBar*/
383 static public final void restoreMenuBar() {
384 MenuBar menu_bar
= Menus
.getMenuBar();
385 final int n_menus
= menu_bar
.getMenuCount();
386 for (int i
=0; i
<n_menus
;i
++) {
387 Menu menu
= menu_bar
.getMenu(i
);
390 //make sure there isn't a null menu bar
391 //WindowManager.getCurrentWindow().setMenuBar(menu_bar);
394 static private void restoreMenu(final Menu menu
) {
395 final int n_menuitems
= menu
.getItemCount();
396 for (int i
=0; i
<n_menuitems
; i
++) {
397 MenuItem menu_item
= menu
.getItem(i
);
398 if (menu_item
instanceof Menu
) {
399 restoreMenu((Menu
)menu_item
);
401 menu_item
.setEnabled(true);
405 static public final void showMessage(String msg
) {
406 if (!ControlWindow
.isGUIEnabled()) System
.out
.println(msg
);
407 else IJ
.showMessage(msg
);
410 /** Runs the showMessage in a separate Thread. */
411 static public final void showMessageT(final String msg
) {
414 setPriority(Thread
.NORM_PRIORITY
);
415 Utils
.showMessage(msg
);
420 static public final void showStatus(final String msg
, final boolean focus
) {
421 if (null == IJ
.getInstance() || !ControlWindow
.isGUIEnabled() || null == status
) {
422 System
.out
.println(msg
);
425 if (focus
) IJ
.getInstance().toFront();
427 status
.showStatus(msg
);
430 static public final void showStatus(final String msg
) {
431 showStatus(msg
, false);
434 static private double last_progress
= 0;
435 static private int last_percent
= 0;
437 static public final void showProgress(final double p
) {
438 //IJ.showProgress(p); // never happens, can't repaint even though they are different threads
439 if (null == IJ
.getInstance() || !ControlWindow
.isGUIEnabled() || null == status
) {
441 last_progress
= 0; // reset
445 // don't show intervals smaller than 1%:
446 if (last_progress
+ 0.01 > p
) {
447 int percent
= (int)(p
* 100);
448 if (last_percent
!= percent
) {
449 System
.out
.println(percent
+ " %");
450 last_percent
= percent
;
457 status
.showProgress(p
);
460 static public void debugDialog() {
461 // note: all this could nicely be done using reflection, so adding another boolean variable would be automatically added here (filtering by the prefix "debug").
462 GenericDialog gd
= new GenericDialog("Debug:");
463 gd
.addCheckbox("debug", debug
);
464 gd
.addCheckbox("debug mouse", debug_mouse
);
465 gd
.addCheckbox("debug sql", debug_sql
);
466 gd
.addCheckbox("debug event", debug_event
);
467 gd
.addCheckbox("debug clip", debug_clip
);
468 gd
.addCheckbox("debug thing", debug_thing
);
470 if (gd
.wasCanceled()) {
473 debug
= gd
.getNextBoolean();
474 debug_mouse
= gd
.getNextBoolean();
475 debug_sql
= gd
.getNextBoolean();
476 debug_event
= gd
.getNextBoolean();
477 debug_clip
= gd
.getNextBoolean();
478 debug_thing
= gd
.getNextBoolean();
482 /** Scan the WindowManager for open stacks.*/
483 static public ImagePlus
[] findOpenStacks() {
484 ImagePlus
[] imp
= scanWindowManager("stacks");
485 if (null == imp
) return null;
489 /** Scan the WindowManager for non-stack images*/
490 static public ImagePlus
[] findOpenImages() {
491 ImagePlus
[] imp
= scanWindowManager("images");
492 if (null == imp
) return null;
496 /** Scan the WindowManager for all open images, including stacks.*/
497 static public ImagePlus
[] findAllOpenImages() {
498 return scanWindowManager("all");
501 static private ImagePlus
[] scanWindowManager(String type
) {
502 // check if any stacks are opened within ImageJ
503 int[] all_ids
= WindowManager
.getIDList();
504 if (null == all_ids
) return null;
505 ImagePlus
[] imp
= new ImagePlus
[all_ids
.length
];
507 for (int i
=0; i
< all_ids
.length
; i
++) {
508 ImagePlus image
= WindowManager
.getImage(all_ids
[i
]);
509 if (type
.equals("stacks")) {
510 if (image
.getStackSize() <= 1) {
513 } else if (type
.equals("images")) {
514 if (image
.getStackSize() > 1) {
522 // resize the array if necessary
523 if (next
!= all_ids
.length
) {
524 ImagePlus
[] imp2
= new ImagePlus
[next
];
525 System
.arraycopy(imp
, 0, imp2
, 0, next
);
528 // return what has been found:
529 if (0 == next
) return null;
533 /**The path of the directory from which images have been recently loaded.*/
534 static public String last_dir
= ij
.Prefs
.getString(ij
.Prefs
.DIR_IMAGE
);
535 /**The path of the last opened file.*/
536 static public String last_file
= null;
538 static public String
cutNumber(double d
, int n_decimals
) {
539 return cutNumber(d
, n_decimals
, false);
542 /** remove_trailing_zeros will leave at least one zero after the comma if appropriate. */
543 static public final String
cutNumber(final double d
, final int n_decimals
, final boolean remove_trailing_zeros
) {
544 final String num
= new Double(d
).toString();
545 int i_e
= num
.indexOf("E-");
547 final int exp
= Integer
.parseInt(num
.substring(i_e
+2));
548 if (n_decimals
< exp
) {
549 final StringBuffer sb
= new StringBuffer("0.");
550 int count
= n_decimals
;
555 return sb
.toString(); // returns 0.000... as many zeros as desired n_decimals
558 StringBuffer sb
= new StringBuffer("0.");
564 sb
.append(num
.charAt(0)); // the single number before the comma
565 // up to here there are 'exp' number of decimals appended
566 int i_end
= 2 + n_decimals
- exp
;
567 if (i_end
> i_e
) i_end
= i_e
; // there arent' that ,any, so cut
568 sb
.append(num
.substring(2, i_end
)); // all numbers after the comma
569 return sb
.toString();
571 // else, there is no scientific notation to worry about
572 final int i_dot
= num
.indexOf('.');
573 final StringBuffer sb
= new StringBuffer(num
.substring(0, i_dot
+1));
574 for (int i
=i_dot
+1; i
< (n_decimals
+ i_dot
+ 1) && i
< num
.length(); i
++) {
575 sb
.append(num
.charAt(i
));
577 // remove zeros from the end
578 if (remove_trailing_zeros
) {
579 for (int i
=sb
.length()-1; i
>i_dot
+1; i
--) { // leave at least one zero after the comma
580 if ('0' == sb
.charAt(i
)) {
587 return sb
.toString();
590 static public final boolean check(final String msg
) {
591 YesNoCancelDialog dialog
= new YesNoCancelDialog(IJ
.getInstance(), "Execute?", msg
);
592 if (dialog
.yesPressed()) {
598 static public final boolean checkYN(String msg
) {
599 YesNoDialog yn
= new YesNoDialog(IJ
.getInstance(), "Execute?", msg
);
600 if (yn
.yesPressed()) return true;
604 static public final String
d2s(double d
, int n_decimals
) {
605 return IJ
.d2s(d
, n_decimals
);
608 static public final String
[] getHexRGBColor(Color color
) {
609 int c
= color
.getRGB();
610 String r
= Integer
.toHexString(((c
&0x00FF0000)>>16));
611 if (1 == r
.length()) r
= "0" + r
;
612 String g
= Integer
.toHexString(((c
&0x0000FF00)>>8));
613 if (1 == g
.length()) g
= "0" + g
;
614 String b
= Integer
.toHexString((c
&0x000000FF));
615 if (1 == b
.length()) b
= "0" + b
;
616 return new String
[]{r
, g
, b
};
619 static public final Color
getRGBColorFromHex(String hex
) {
620 if (hex
.length() < 6) return null;
621 return new Color(Integer
.parseInt(hex
.substring(0, 2), 16), // parse in hexadecimal radix
622 Integer
.parseInt(hex
.substring(2, 4), 16),
623 Integer
.parseInt(hex
.substring(4, 6), 16));
626 static public final int[] get4Ints(final int hex
) {
627 return new int[]{((hex
&0xFF000000)>>24),
628 ((hex
&0x00FF0000)>>16),
629 ((hex
&0x0000FF00)>> 8),
633 public void run(String arg
) {
634 IJ
.showMessage("TrakEM2", "TrakEM2 " + Utils
.version
+ "\nCopyright Albert Cardona & Rodney Douglas\nInstitute for Neuroinformatics, Univ. Zurich / ETH");
637 static public final File
chooseFile(String name
, String extension
) {
638 return Utils
.chooseFile(null, name
, extension
);
641 /** 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). */
642 static public final File
chooseFile(String default_dir
, String name
, String extension
) {
643 // using ImageJ's JFileChooser or internal FileDialog, according to user preferences.
644 String user
= System
.getProperty("user.name");
646 if (null != name
&& null != extension
) name2
= name
+ extension
;
647 else if (null != name
) name2
= name
;
648 else if (null != extension
) name2
= "untitled" + extension
;
649 if (null != default_dir
) {
650 OpenDialog
.setDefaultDirectory(default_dir
);
652 SaveDialog sd
= new SaveDialog("Save",
653 OpenDialog
.getDefaultDirectory(),
657 String filename
= sd
.getFileName();
658 if (null == filename
|| filename
.toLowerCase().startsWith("null")) return null;
659 File f
= new File(sd
.getDirectory() + "/" + filename
);
660 if (f
.exists() && ControlWindow
.isGUIEnabled()) {
661 YesNoCancelDialog d
= new YesNoCancelDialog(IJ
.getInstance(), "Overwrite?", "File " + filename
+ " exists! Overwrite?");
662 if (d
.cancelPressed()) {
664 } else if (!d
.yesPressed()) {
665 return chooseFile(name
, extension
);
667 // else if yes pressed, overwrite.
672 /** Returns null or the selected directory and file. */
673 static public final String
[] selectFile(String title_msg
) {
674 OpenDialog od
= new OpenDialog("Select file", OpenDialog
.getDefaultDirectory(), null);
675 String file
= od
.getFileName();
676 if (null == file
|| file
.toLowerCase().startsWith("null")) return null;
677 String dir
= od
.getDirectory();
680 dir
= dir
.replace('\\', '/');
681 if (!dir
.endsWith("/")) dir
+= "/";
682 f
= new File(dir
+ "/" + file
); // I'd use File.separator, but in Windows it fails
684 if (null == dir
|| !f
.exists()) {
685 Utils
.log2("No proper file selected.");
688 return new String
[]{dir
, file
};
691 static public final boolean saveToFile(File f
, String contents
) {
692 if (null == f
) return false;
695 DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f), contents.length()));
697 OutputStreamWriter dos
= new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(f
), contents
.length()), "8859_1"); // encoding in Latin 1 (for macosx not to mess around
698 //dos.writeBytes(contents);
699 dos
.write(contents
, 0, contents
.length());
701 } catch (Exception e
) {
703 Utils
.showMessage("ERROR: Most likely did NOT save your file.");
709 static public final boolean saveToFile(String name
, String extension
, String contents
) {
710 if (null == contents
) {
711 Utils
.log2("Utils.saveToFile: contents is null");
715 File f
= Utils
.chooseFile(name
, extension
);
716 return Utils
.saveToFile(f
, contents
);
719 /** Converts sequences of spaces into single space, and trims the ends. */
720 static public final String
cleanString(String s
) {
722 while (-1 != s
.indexOf("\u0020\u0020")) { // \u0020 equals a single space
723 s
= s
.replaceAll("\u0020\u0020", "\u0020");
728 static public final String
openTextFile(final String path
) {
729 if (null == path
|| !new File(path
).exists()) return null;
730 final StringBuffer sb
= new StringBuffer();
731 BufferedReader r
= null;
733 r
= new BufferedReader(new FileReader(path
));
735 String s
= r
.readLine();
736 if (null == s
) break;
737 sb
.append(s
).append('\n'); // I am sure the reading can be done better
739 } catch (Exception e
) {
741 if (null != r
) try { r
.close(); } catch (IOException ioe
) { ioe
.printStackTrace(); }
744 return sb
.toString();
747 /** Returns the file found at path as an array of lines, or null if not found. */
748 static public final String
[] openTextFileLines(final String path
) {
749 if (null == path
|| !new File(path
).exists()) return null;
750 final ArrayList al
= new ArrayList();
752 BufferedReader r
= new BufferedReader(new FileReader(path
));
754 String s
= r
.readLine();
755 if (null == s
) break;
759 } catch (Exception e
) {
763 final String
[] sal
= new String
[al
.size()];
768 static public final char[] openTextFileChars(final String path
) {
770 if (null == path
|| !(f
= new File(path
)).exists()) {
771 Utils
.log("File not found: " + path
);
774 final char[] src
= new char[(int)f
.length()]; // assumes file is small enough to fit in integer range!
776 BufferedReader r
= new BufferedReader(new FileReader(path
));
777 r
.read(src
, 0, src
.length
);
779 } catch (Exception e
) {
786 /** The cosinus between two vectors (in polar coordinates), by means of the dot product. */
787 static public final double getCos(final double x1
, final double y1
, final double x2
, final double y2
) {
788 return (x1
* x2
+ y1
* y2
) / (Math
.sqrt(x1
*x1
+ y1
*y1
) * Math
.sqrt(x2
*x2
+ y2
*y2
));
791 static public final String
removeExtension(final String path
) {
792 final int i_dot
= path
.lastIndexOf('.');
793 if (-1 == i_dot
|| i_dot
+ 4 != path
.length()) return path
;
794 else return path
.substring(0, i_dot
);
797 /** A helper for GenericDialog checkboxes to control other the enabled state of other GUI elements in the same dialog. */
798 static public final void addEnablerListener(final Checkbox master
, final Component
[] enable
, final Component
[] disable
) {
799 master
.addItemListener(new ItemListener() {
800 public void itemStateChanged(ItemEvent ie
) {
801 if (ie
.getStateChange() == ItemEvent
.SELECTED
) {
802 process(enable
, true);
803 process(disable
, false);
805 process(enable
, false);
806 process(disable
, true);
809 private void process(final Component
[] c
, final boolean state
) {
810 if (null == c
) return;
811 for (int i
=0; i
<c
.length
; i
++) c
[i
].setEnabled(state
);
816 static public final boolean wrongImageJVersion() {
817 boolean b
= IJ
.versionLessThan("1.37g");
818 if (b
) Utils
.showMessage("TrakEM2 requires ImageJ 1.37g or above.");
822 static public final boolean java3d
= isJava3DInstalled();
824 static private final boolean isJava3DInstalled() {
826 Class p3f
= Class
.forName("javax.vecmath.Point3f");
827 } catch (ClassNotFoundException cnfe
) {
833 static public final void addLayerRangeChoices(final Layer selected
, final GenericDialog gd
) {
834 Utils
.addLayerRangeChoices(selected
, selected
, gd
);
837 static public final void addLayerRangeChoices(final Layer first
, final Layer last
, final GenericDialog gd
) {
838 final String
[] layers
= new String
[first
.getParent().size()];
839 final ArrayList al_layer_titles
= new ArrayList();
841 for (Iterator it
= first
.getParent().getLayers().iterator(); it
.hasNext(); ) {
842 layers
[i
] = first
.getProject().findLayerThing((Layer
)it
.next()).toString();
843 al_layer_titles
.add(layers
[i
]);
846 final int i_first
= first
.getParent().indexOf(first
);
847 final int i_last
= last
.getParent().indexOf(last
);
848 gd
.addChoice("Start: ", layers
, layers
[i_first
]);
849 final Vector v
= gd
.getChoices();
850 final Choice cstart
= (Choice
)v
.get(v
.size()-1);
851 gd
.addChoice("End: ", layers
, layers
[i_last
]);
852 final Choice cend
= (Choice
)v
.get(v
.size()-1);
853 cstart
.addItemListener(new ItemListener() {
854 public void itemStateChanged(ItemEvent ie
) {
855 int index
= al_layer_titles
.indexOf(ie
.getItem());
856 if (index
> cend
.getSelectedIndex()) cend
.select(index
);
859 cend
.addItemListener(new ItemListener() {
860 public void itemStateChanged(ItemEvent ie
) {
861 int index
= al_layer_titles
.indexOf(ie
.getItem());
862 if (index
< cstart
.getSelectedIndex()) cstart
.select(index
);
867 static public final void addLayerChoice(final String label
, final Layer selected
, final GenericDialog gd
) {
868 final String
[] layers
= new String
[selected
.getParent().size()];
869 final ArrayList al_layer_titles
= new ArrayList();
871 for (Iterator it
= selected
.getParent().getLayers().iterator(); it
.hasNext(); ) {
872 layers
[i
] = selected
.getProject().findLayerThing((Layer
)it
.next()).toString();
873 al_layer_titles
.add(layers
[i
]);
876 final int i_layer
= selected
.getParent().indexOf(selected
);
877 gd
.addChoice(label
, layers
, layers
[i_layer
]);
881 static public final void addRGBColorSliders(final GenericDialog gd
, final Color color
) {
882 gd
.addSlider("Red: ", 0, 255, color
.getRed());
883 gd
.addSlider("Green: ", 0, 255, color
.getGreen());
884 gd
.addSlider("Blue: ", 0, 255, color
.getBlue());
887 /** Converts the ImageProcessor to an ImageProcessor of the given type, or the same if of equal type. */
888 static final public ImageProcessor
convertTo(final ImageProcessor ip
, final int type
, final boolean scaling
) {
890 case ImagePlus
.GRAY8
:
891 return ip
.convertToByte(scaling
);
892 case ImagePlus
.GRAY16
:
893 return ip
.convertToShort(scaling
);
894 case ImagePlus
.GRAY32
:
895 return ip
.convertToFloat();
896 case ImagePlus
.COLOR_RGB
:
897 return ip
.convertToRGB();
898 case ImagePlus
.COLOR_256
:
899 ImagePlus imp
= new ImagePlus("", ip
.convertToRGB());
900 new ImageConverter(imp
).convertRGBtoIndexedColor(256);
901 return imp
.getProcessor();
907 /** 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. */
908 static public final double[] copy(final double[] a
, final int new_length
) {
909 final double[] b
= new double[new_length
];
910 final int len
= a
.length
> new_length ? new_length
: a
.length
;
911 System
.arraycopy(a
, 0, b
, 0, len
);
915 static public final double[] copy(final double[] a
, final int first
, final int new_length
) {
916 final double[] b
= new double[new_length
];
917 final int len
= new_length
< a
.length
- first ? new_length
: a
.length
- first
;
918 System
.arraycopy(a
, first
, b
, 0, len
);
922 /** Reverse in place an array of doubles. */
923 static public final void reverse(final double[] a
) {
924 for (int left
=0, right
=a
.length
-1; left
<right
; left
++, right
--) {
925 double tmp
= a
[left
];
931 static public final double[] toDouble(final int[] a
, final int len
) {
932 final double[] b
= new double[len
];
933 for (int i
=0; i
<len
; i
++) b
[i
] = a
[i
];
937 static public final int[] toInt(final double[] a
, final int len
) {
938 final int[] b
= new int[len
];
939 for (int i
=0; i
<len
; i
++) b
[i
] = (int) a
[i
];
943 /** OS-agnostic diagnosis of whether the click was for the contextual popup menu. */
944 static public final boolean isPopupTrigger(final MouseEvent me
) {
945 // ImageJ way, in ij.gui.ImageCanvas class, plus an is-windows switch to prevent meta key from poping up for MacOSX
946 return (me
.isPopupTrigger() && me
.getButton() != 0) || (IJ
.isWindows() && 0 != (me
.getModifiers() & Event
.META_MASK
) );
949 /** Repaint the given Component on the swing repaint thread (aka "SwingUtilities.invokeLater"). */
950 static public final void updateComponent(final Component c
) {
953 // ALL that was needed: to put it into the swing repaint queue ... couldn't they JUST SAY SO
954 SwingUtilities
.invokeLater(new Runnable() {
960 /** Like calling pack() on a Frame but on a Component. */
961 static public final void revalidateComponent(final Component c
) {
962 SwingUtilities
.invokeLater(new Runnable() {
971 /** Returns the time as HH:MM:SS */
972 static public final String
now() {
973 /* Java time management is retarded. */
974 final Calendar c
= Calendar
.getInstance();
975 final int hour
= c
.get(Calendar
.HOUR_OF_DAY
);
976 final int min
= c
.get(Calendar
.MINUTE
);
977 final int sec
= c
.get(Calendar
.SECOND
);
978 final StringBuffer sb
= new StringBuffer();
979 if (hour
< 10) sb
.append('0');
980 sb
.append(hour
).append(':');
981 if (min
< 10) sb
.append('0');
982 sb
.append(min
).append(':');
983 if (sec
< 10) sb
.append(0);
984 return sb
.append(sec
).toString();
987 static public final void sleep(final long miliseconds
) {
988 try { Thread
.currentThread().sleep(miliseconds
); } catch (Exception e
) { e
.printStackTrace(); }
991 /** Mix colors visually: red + green = yellow, etc.*/
992 static public final Color
mix(Color c1
, Color c2
) {
993 final float[] b
= Color
.RGBtoHSB(c1
.getRed(), c1
.getGreen(), c1
.getBlue(), new float[3]);
994 final float[] c
= Color
.RGBtoHSB(c2
.getRed(), c2
.getGreen(), c2
.getBlue(), new float[3]);
995 final float[] a
= new float[3];
996 // find to which side the hue values are closer, since hue space is a a circle
997 // hue values all between 0 and 1
1006 float d2
= 1 + h1
- h2
;
1011 if (a
[0] > 1) a
[0] -= 1;
1014 for (int i
=1; i
<3; i
++) a
[i
] = (b
[i
] + c
[i
]) / 2; // only Saturation and Brightness can be averaged
1015 return Color
.getHSBColor(a
[0], a
[1], a
[2]);
1018 /** 1 A, 2 B, 3 C --- 26 - z, 27 AA, 28 AB, 29 AC --- 26*27 AAA */
1019 static public final String
getCharacter(int i
) {
1022 char c
= (char)((i
% 26) + 65); // 65 is 'A'
1023 if (0 == k
) return Character
.toString(c
);
1024 return new StringBuffer().append(getCharacter(k
)).append(c
).toString();
1027 /** Get by reflection a private or protected field in the given object. */
1028 static public final Object
getField(final Object ob
, final String field_name
) {
1029 if (null == ob
|| null == field_name
) return null;
1031 Field f
= ob
.getClass().getDeclaredField(field_name
);
1032 f
.setAccessible(true);
1034 } catch (Exception e
) {
1040 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1041 static public final FloatProcessor
fastConvertToFloat(final ByteProcessor ip
) {
1042 final byte[] pix
= (byte[])ip
.getPixels();
1043 final float[] data
= new float[pix
.length
];
1044 for (int i
=0; i
<pix
.length
; i
++) data
[i
] = pix
[i
]&0xff;
1045 final FloatProcessor fp
= new FloatProcessorT2(ip
.getWidth(), ip
.getHeight(), data
, ip
.getColorModel(), ip
.getMin(), ip
.getMax());
1048 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1049 static public final FloatProcessor
fastConvertToFloat(final ShortProcessor ip
) {
1050 final short[] pix
= (short[])ip
.getPixels();
1051 final float[] data
= new float[pix
.length
];
1052 for (int i
=0; i
<pix
.length
; i
++) data
[i
] = pix
[i
]&0xffff;
1053 final FloatProcessor fp
= new FloatProcessorT2(ip
.getWidth(), ip
.getHeight(), data
, ip
.getColorModel(), ip
.getMin(), ip
.getMax());
1056 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1057 static public final FloatProcessor
fastConvertToFloat(final ImageProcessor ip
, final int type
) {
1059 case ImagePlus
.GRAY16
: return fastConvertToFloat((ShortProcessor
)ip
);
1060 case ImagePlus
.GRAY32
: return (FloatProcessor
)ip
;
1061 case ImagePlus
.GRAY8
:
1062 case ImagePlus
.COLOR_256
: return fastConvertToFloat((ByteProcessor
)ip
);
1063 case ImagePlus
.COLOR_RGB
: return (FloatProcessor
)ip
.convertToFloat(); // SLOW
1067 static public final FloatProcessor
fastConvertToFloat(final ImageProcessor ip
) {
1068 if (ip
instanceof ByteProcessor
) return fastConvertToFloat((ByteProcessor
)ip
);
1069 if (ip
instanceof ShortProcessor
) return fastConvertToFloat((ShortProcessor
)ip
);
1070 return (FloatProcessor
)ip
.convertToFloat();
1073 /** 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. */
1074 static public final ResultsTable
createResultsTable(final String title
, final String
[] columns
) {
1075 TextWindow tw
= (TextWindow
)WindowManager
.getFrame(title
);
1077 // hacking again ... missing a getResultsTable() method in TextWindow
1078 ResultsTable rt
= (ResultsTable
)Utils
.getField(tw
.getTextPanel(), "rt");
1079 if (null != rt
) return rt
; // assumes columns will be identical
1081 // else create a new one
1082 ResultsTable rt
= new ResultsTable();
1084 for (int i
=0; i
<columns
.length
; i
++) rt
.setHeading(i
, columns
[i
]);
1089 static public final ImageProcessor
createProcessor(final int type
, final int width
, final int height
) {
1091 case ImagePlus
.GRAY8
: return new ByteProcessor(width
, height
);
1092 case ImagePlus
.GRAY16
: return new ShortProcessor(width
, height
);
1093 case ImagePlus
.GRAY32
: return new FloatProcessor(width
, height
);
1094 case ImagePlus
.COLOR_RGB
: return new ColorProcessor(width
, height
);
1099 /** Paints an approximation of the pipe into the set of slices. */
1100 public void paint(final Pipe pipe
, final Map
<Layer
,ImageProcessor
> slices
, final int value
, final float scale
) {
1101 VectorString3D vs
= pipe
.asVectorString3D();
1102 vs
.resample(1); // one pixel
1103 double[] px
= vs
.getPoints(0);
1104 double[] py
= vs
.getPoints(1);
1105 double[] pz
= vs
.getPoints(2);
1106 double[] pr
= vs
.getDependent(0);
1108 for (int i
=0; i
<px
.length
-1; i
++) {
1109 ImageProcessor ip
= slices
.get(pipe
.getLayerSet().getNearestLayer(pz
[i
]));
1110 if (null == ip
) continue;
1111 OvalRoi ov
= new OvalRoi((int)((px
[i
] - pr
[i
]) * scale
),
1112 (int)((py
[i
] - pr
[i
]) * scale
),
1113 (int)(pr
[i
]*2*scale
), (int)(pr
[i
]*2*scale
));
1116 ip
.fill(ip
.getMask());
1120 static final public boolean matches(final String pattern
, final String s
) {
1121 return Pattern
.compile(pattern
).matcher(s
).matches();
1124 static final public boolean isValidIdentifier(final String s
) {
1125 if (null == s
) return false;
1126 if (!Utils
.matches("^[a-zA-Z]+[a-zA-Z1-9_]*$", s
)) {
1127 Utils
.log("Invalid identifier " + s
);
1134 user=> (def pat #"\b[a-zA-Z]+[\w]*\b")
1136 user=>(re-seq pat "abc def 1a334")
1138 user=> (re-seq pat "abc def a334")
1139 ("abc" "def" "a334")
1141 Then concatenate all good words with underscores.
1142 Returns null when nothing valid is found in 's'.
1144 static final public String
makeValidIdentifier(final String s
) {
1145 if (null == s
) return null;
1146 // Concatenate all good groups with underscores:
1147 final Pattern pat
= Pattern
.compile("\\b[a-zA-Z]+[\\w]*\\b");
1148 final Matcher m
= pat
.matcher(s
);
1149 final StringBuffer sb
= new StringBuffer();
1151 sb
.append(m
.group()).append('_');
1153 if (0 == sb
.length()) return null;
1154 // Remove last underscore
1155 sb
.setLength(sb
.length()-1);
1156 return sb
.toString();
1159 static final public int indexOf(final Object needle
, final Object
[] haystack
) {
1160 for (int i
=0; i
<haystack
.length
; i
++) {
1161 if (haystack
[i
].equals(needle
)) return i
;
1166 /** 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. */
1167 static public final boolean removeFile(final File f
) {
1168 return Utils
.removeFile(f
, true, null);
1171 // Accumulates removed files (not directories) into removed_paths, if not null.
1172 static private final boolean removeFile(final File f
, final boolean stop_if_dir_not_empty
, final ArrayList
<String
> removed_paths
) {
1173 if (null == f
|| !f
.exists()) return false;
1175 if (!Utils
.isTrakEM2Subfile(f
)) {
1176 Utils
.log2("REFUSING to remove file " + f
+ "\n-->REASON: not in a '/trakem2.' file path");
1180 // If it's not a directory, just delete it
1181 if (!f
.isDirectory()) {
1184 // Else delete all directories:
1185 final ArrayList
<File
> dirs
= new ArrayList
<File
>();
1187 // Non-recursive version ... I hate java
1189 int i
= dirs
.size() -1;
1190 final File fdir
= dirs
.get(i
);
1191 Utils
.log2("Examining folder for deletion: " + fdir
.getName());
1192 boolean remove
= true;
1193 for (final File file
: fdir
.listFiles()) {
1194 String name
= file
.getName();
1195 if (name
.equals(".") || name
.equals("..")) continue;
1196 if (file
.isDirectory()) {
1199 } else if (file
.isHidden()) {
1200 if (!file
.delete()) {
1201 Utils
.log("Failed to delete hidden file " + file
.getAbsolutePath());
1204 if (null != removed_paths
) removed_paths
.add(file
.getAbsolutePath());
1205 } else if (stop_if_dir_not_empty
) {
1206 //Utils.log("Not empty: cannot remove dir " + fdir.getAbsolutePath());
1209 if (!file
.delete()) {
1210 Utils
.log("Failed to delete visible file " + file
.getAbsolutePath());
1213 if (null != removed_paths
) removed_paths
.add(file
.getAbsolutePath());
1218 if (!fdir
.delete()) {
1221 Utils
.log2("Removed folder " + fdir
.getAbsolutePath());
1224 } while (dirs
.size() > 0);
1228 } catch (Exception e
) {
1234 /** Returns true if the file cannonical path contains "/trakem2." (adjusted for Windows as well). */
1235 static public boolean isTrakEM2Subfile(final File f
) throws Exception
{
1236 return isTrakEM2Subpath(f
.getCanonicalPath());
1239 /** Returns true if the path contains "/trakem2." (adjusted for Windows as well). */
1240 static public boolean isTrakEM2Subpath(String path
) {
1241 if (IJ
.isWindows()) path
= path
.replace('\\', '/');
1242 return -1 != path
.toLowerCase().indexOf("/trakem2.");
1245 /** Returns true if all files and their subdirectories, recursively, under parent folder have been removed.
1246 * For safety reasons, this function will return false immediately if the parent file path does not include a
1247 * lowercase "trakem2." in it.
1248 * If removed_paths is not null, all removed full paths are added to it.
1250 static public final boolean removePrefixedFiles(final File parent
, final String prefix
, final ArrayList
<String
> removed_paths
) {
1251 if (null == parent
|| !parent
.isDirectory()) return false;
1254 if (!Utils
.isTrakEM2Subfile(parent
)) {
1255 Utils
.log2("REFUSING to remove files recursively under folder " + parent
+ "\n-->REASON: not in a '/trakem2.' file path");
1259 boolean success
= true;
1261 final File
[] list
= parent
.listFiles(new FilenameFilter() {
1262 public boolean accept(File dir
, String name
) {
1263 if (name
.startsWith(prefix
)) return true;
1268 ArrayList
<String
> a
= null;
1269 if (null != removed_paths
) a
= new ArrayList
<String
>();
1271 if (null != list
&& list
.length
> 0) {
1272 for (final File f
: list
) {
1273 if (!Utils
.removeFile(f
, false, a
)) success
= false;
1274 if (null != removed_paths
) {
1275 removed_paths
.addAll(a
);
1283 } catch (Exception e
) {
1289 /** The CTRL key functionality is passed over to the COMMAND key (aka META key) in a MacOSX. */
1290 static public final int getControlModifier() {
1291 return IJ
.isMacOSX() ? InputEvent
.META_MASK
1292 : InputEvent
.CTRL_MASK
;
1295 /** The CTRL key functionality is passed over to the COMMAND key (aka META key) in a MacOSX. */
1296 static public final boolean isControlDown(final InputEvent e
) {
1297 return IJ
.isMacOSX() ? e
.isMetaDown()
1298 : e
.isControlDown();