3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005,2006,2007,2008 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
;
42 import ij
.gui
.ShapeRoi
;
43 import ij
.gui
.OvalRoi
;
44 import ij
.text
.TextWindow
;
45 import ij
.measure
.ResultsTable
;
48 import ij
.process
.ImageProcessor
;
49 import ij
.process
.ImageConverter
;
51 import java
.awt
.Checkbox
;
52 import java
.awt
.Choice
;
53 import java
.awt
.Color
;
54 import java
.awt
.Component
;
55 import java
.awt
.MenuBar
;
57 import java
.awt
.MenuItem
;
58 import java
.awt
.Shape
;
59 import java
.awt
.geom
.AffineTransform
;
60 import java
.awt
.geom
.Point2D
;
61 import java
.awt
.geom
.NoninvertibleTransformException
;
62 import java
.awt
.geom
.Area
;
63 import java
.awt
.Rectangle
;
64 import java
.awt
.Polygon
;
65 import java
.awt
.geom
.PathIterator
;
66 import java
.awt
.Graphics2D
;
67 import java
.awt
.image
.BufferedImage
;
68 import java
.awt
.image
.DataBufferByte
;
70 import java
.awt
.event
.ItemListener
;
71 import java
.awt
.event
.ItemEvent
;
72 import java
.awt
.event
.MouseEvent
;
73 import java
.awt
.event
.InputEvent
;
74 import java
.awt
.Event
;
75 import javax
.swing
.SwingUtilities
;
77 import java
.lang
.reflect
.Field
;
79 import java
.util
.ArrayList
;
80 import java
.util
.Iterator
;
81 import java
.util
.Vector
;
82 import java
.util
.Calendar
;
83 import java
.lang
.Iterable
;
84 import java
.util
.Iterator
;
85 import java
.util
.Collection
;
87 import java
.util
.regex
.Pattern
;
88 import java
.util
.regex
.Matcher
;
90 import javax
.vecmath
.Point3f
;
91 import javax
.vecmath
.Vector3f
;
93 /** Utils class: stores generic widely used methods. In particular, those for logging text messages (for debugging) and also some math and memory utilities.
97 public class Utils
implements ij
.plugin
.PlugIn
{
99 static public String version
= "0.6a 2008-11-13";
101 static public boolean debug
= false;
102 static public boolean debug_mouse
= false;
103 static public boolean debug_sql
= false;
104 static public boolean debug_event
= false;
105 static public boolean debug_clip
= false; //clip for repainting
106 static public boolean debug_thing
= false;
108 /** The error to use in floating-point or double floating point literal comparisons. */
109 static public final double FL_ERROR
= 0.0000001;
111 static public void debug(String msg
) {
112 if (debug
) IJ
.log(msg
);
115 static public void debugMouse(String msg
) {
116 if (debug_mouse
) IJ
.log(msg
);
119 /** Avoid waiting on the AWT thread repainting ImageJ's log window. */
120 static private final class LogDispatcher
extends Thread
{
121 private final StringBuffer cache
= new StringBuffer();
122 private boolean loading
= false;
123 private boolean go
= true;
124 public LogDispatcher() {
125 super("T2-Log-Dispatcher");
126 setPriority(Thread
.NORM_PRIORITY
);
127 try { setDaemon(true); } catch (Exception e
) { e
.printStackTrace(); }
130 public final void quit() {
132 synchronized (this) { notify(); }
134 public final void log(final String msg
) {
136 synchronized (cache
) {
137 loading
= true; // no need to synch, variable setting is atomic
138 if (0 != cache
.length()) cache
.append('\n');
144 } catch (Exception e
) {
148 synchronized (this) { notify(); }
149 } catch (Exception e
) {
156 synchronized (this) { wait(); }
158 synchronized (cache
) {
159 if (0 != cache
.length()) msg
= cache
.toString();
163 } catch (Exception e
) {
169 static private LogDispatcher logger
= new LogDispatcher();
171 /** Avoid waiting on the AWT thread repainting ImageJ's status bar.
172 Waits 100 ms before printing the status message; if too many status messages are being sent, the last one overrides all. */
173 static private final class StatusDispatcher
extends Thread
{
174 private String msg
= null;
175 private boolean loading
= false;
176 private boolean go
= true;
177 private double progress
= -1;
178 public StatusDispatcher() {
179 super("T2-Status-Dispatcher");
180 setPriority(Thread
.NORM_PRIORITY
);
181 try { setDaemon(true); } catch (Exception e
) { e
.printStackTrace(); }
184 public final void quit() {
186 synchronized (this) { notify(); }
188 public final void showStatus(final String msg
) {
190 synchronized (this) {
194 } catch (Exception e
) {
198 public final void showProgress(final double progress
) {
200 synchronized (this) {
201 this.progress
= progress
;
204 } catch (Exception e
) {
211 // first part ensures it gets printed even if the notify was issued while not waiting
212 synchronized (this) {
217 if (-1 != progress
) {
218 IJ
.showProgress(progress
);
223 // allow some time for overwriting of messages
225 /* // should not be needed
226 // print the msg if necessary
227 synchronized (this) {
234 } catch (Exception e
) {
240 static private StatusDispatcher status
= new StatusDispatcher();
242 /** Initialize house keeping threads. */
243 static public final void setup(final ControlWindow master
) { // the ControlWindow acts as a switch: nobody can controls this because the CW constructor is private
244 if (null == status
) status
= new StatusDispatcher();
245 if (null == logger
) logger
= new LogDispatcher();
248 /** Destroy house keeping threads. */
249 static public final void destroy(final ControlWindow master
) {
250 if (null != status
) { status
.quit(); status
= null; }
251 if (null != logger
) { logger
.quit(); logger
= null; }
254 /** Intended for the user to see. */
255 static public final void log(final String msg
) {
256 if (ControlWindow
.isGUIEnabled() && null != logger
) {
259 System
.out
.println(msg
);
263 /** Print in all printable places: log window, System.out.println, and status bar.*/
264 static public final void logAll(final String msg
) {
265 if (!ControlWindow
.isGUIEnabled()) {
266 System
.out
.println(msg
);
269 System
.out
.println(msg
);
270 if (null != logger
) logger
.log(msg
);
271 if (null != status
) status
.showStatus(msg
);
274 /** Intended for developers: prints to terminal. */
275 static public final void log2(final String msg
) {
276 System
.out
.println(msg
);
279 /** Pretty-print the object, for example arrays as [0, 1, 2]. */
280 static public final void log2(final Object ob
) {
281 Utils
.log2(null, ob
);
283 /** Pretty-print the object, for example arrays as [0, 1, 2]. */
284 static public final void log2(final String msg
, final Object ob
) {
285 Utils
.log2((null != msg ? msg
: "") + ob
+ "\n\t" + Utils
.toString(ob
));
287 static public final void log2(final String msg
, final Object ob1
, final Object
... ob
) {
288 final StringBuffer sb
= new StringBuffer(null == msg ?
"" : msg
+ "\n");
289 sb
.append(ob1
.toString()).append(" : ").append(Utils
.toString(ob1
)).append('\n');
290 for (int i
=0; i
<ob
.length
; i
++) sb
.append(ob
.toString()).append(" : ").append(Utils
.toString(ob
[i
])).append('\n');
291 sb
.setLength(sb
.length()-1);
292 Utils
.log2(sb
.toString());
295 /** 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. */
296 static public final String
toString(final Object ob
) {
297 if (null == ob
) return "null";
298 // Clojure could do this so much easier with a macro
299 final StringBuffer sb
= new StringBuffer();
302 if (ob
instanceof String
[]) { // could be done with Object[] and recursive calls, but whatever
303 final String
[] s
= (String
[])ob
;
304 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
305 } else if (ob
instanceof int[]) {
306 final int[] s
= (int[])ob
;
307 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
308 } else if (ob
instanceof double[]) {
309 final double[] s
= (double[])ob
;
310 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
311 } else if (ob
instanceof float[]) {
312 final float[] s
= (float[])ob
;
313 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
314 } else if (ob
instanceof char[]) {
315 final char[] s
= (char[])ob
;
316 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
317 } else if (ob
instanceof Object
[]) {
318 final Object
[] s
= (Object
[])ob
;
319 for (int i
=0; i
<s
.length
; i
++) sb
.append(Utils
.toString(s
[i
])).append(", ");
320 } else if (ob
instanceof Iterable
) {
321 final Iterable s
= (Iterable
)ob
;
322 for (Iterator it
= s
.iterator(); it
.hasNext(); ) sb
.append(Utils
.toString(it
.next())).append(", ");
323 } else if (ob
instanceof Map
) {
324 sb
.setCharAt(0, '{');
326 final Map s
= (Map
)ob
;
327 for (Iterator it
= s
.entrySet().iterator(); it
.hasNext(); ) {
328 Map
.Entry e
= (Map
.Entry
)it
.next();
329 sb
.append(Utils
.toString(e
.getKey())).append(" => ").append(Utils
.toString(e
.getValue())).append(", ");
331 } else if (ob
instanceof long[]) {
332 final long[] s
= (long[])ob
;
333 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
334 } else if (ob
instanceof short[]) {
335 final short[] s
= (short[])ob
;
336 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
337 } else if (ob
instanceof boolean[]) {
338 final boolean[] s
= (boolean[])ob
;
339 for (int i
=0; i
<s
.length
; i
++) sb
.append(s
[i
]).append(", ");
341 return ob
.toString();
343 sb
.setLength(sb
.length()-2);
346 return sb
.toString();
349 static public void setDebug(boolean debug
) {
353 static public void setDebugMouse(boolean debug_mouse
) {
354 Utils
.debug_mouse
= debug_mouse
;
357 static public void setDebugSQL(boolean debug_sql
) {
358 Utils
.debug_sql
= debug_sql
;
361 /** Adjusting so that 0 is 3 o'clock, PI+PI/2 is 12 o'clock, PI is 9 o'clock, and PI/2 is 6 o'clock (why atan2 doesn't output angles this way? I remember I had the same problem for Pipe.java in the A_3D_editing plugin)
362 Using schemata in JavaAngles.ai as reference */
363 static public final double fixAtan2Angle(double angle
) {
366 //fix too large angles
367 if (angle
> 2*Math
.PI
) {
368 a
= angle
- 2*Math
.PI
;
370 //fix signs and shity oriented angle values given by atan2
371 if (a
> 0.0 && a
<= Math
.PI
/2) {
373 } else if (a
<= 0.0 && a
>= -Math
.PI
) {
374 a
= Math
.PI
/2 - a
; // minus because angle is negative
375 } else if (a
> Math
.PI
/2 && a
<= Math
.PI
) {
376 a
= Math
.PI
+ Math
.PI
+ (Math
.PI
/2 - a
);
382 static public int count
= 0;
384 /** Find out which method from which class called the method where the printCaller is used; for debugging purposes.*/
385 static public final void printCaller(Object called_object
) {
386 StackTraceElement
[] elems
= new Exception().getStackTrace();
387 if (elems
.length
< 3) {
388 log2("Stack trace too short! No useful info");
390 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() + "()");
391 log2("==== END ====");
395 static public final void printCaller(Object called_object
, int lines
) {
396 StackTraceElement
[] elems
= new Exception().getStackTrace();
397 if (elems
.length
< 3) {
398 log2("Stack trace too short! No useful info");
400 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() + "()");
401 for (int i
=3; i
<lines
+2 && i
<elems
.length
; i
++) {
402 log2("\tby: " + elems
[i
].getClassName() + " " + elems
[i
].getLineNumber() + ": " + elems
[i
].getMethodName() + "()");
404 log2("==== END ====");
408 static public final String
caller(Object called
) {
409 StackTraceElement
[] elems
= new Exception().getStackTrace();
410 if (elems
.length
< 3) {
411 log2("Stack trace too short! No useful info");
414 return elems
[2].getClassName();
418 /**Restore ImageJ's MenuBar*/
419 static public final void restoreMenuBar() {
420 MenuBar menu_bar
= Menus
.getMenuBar();
421 final int n_menus
= menu_bar
.getMenuCount();
422 for (int i
=0; i
<n_menus
;i
++) {
423 Menu menu
= menu_bar
.getMenu(i
);
426 //make sure there isn't a null menu bar
427 //WindowManager.getCurrentWindow().setMenuBar(menu_bar);
430 static private void restoreMenu(final Menu menu
) {
431 final int n_menuitems
= menu
.getItemCount();
432 for (int i
=0; i
<n_menuitems
; i
++) {
433 MenuItem menu_item
= menu
.getItem(i
);
434 if (menu_item
instanceof Menu
) {
435 restoreMenu((Menu
)menu_item
);
437 menu_item
.setEnabled(true);
441 static public final void showMessage(String msg
) {
442 if (!ControlWindow
.isGUIEnabled()) System
.out
.println(msg
);
443 else IJ
.showMessage(msg
);
446 /** Runs the showMessage in a separate Thread. */
447 static public final void showMessageT(final String msg
) {
450 setPriority(Thread
.NORM_PRIORITY
);
451 Utils
.showMessage(msg
);
456 static public final void showStatus(final String msg
, final boolean focus
) {
457 if (null == IJ
.getInstance() || !ControlWindow
.isGUIEnabled() || null == status
) {
458 System
.out
.println(msg
);
461 if (focus
) IJ
.getInstance().toFront();
463 status
.showStatus(msg
);
466 static public final void showStatus(final String msg
) {
467 showStatus(msg
, false);
470 static private double last_progress
= 0;
471 static private int last_percent
= 0;
473 static public final void showProgress(final double p
) {
474 //IJ.showProgress(p); // never happens, can't repaint even though they are different threads
475 if (null == IJ
.getInstance() || !ControlWindow
.isGUIEnabled() || null == status
) {
477 last_progress
= 0; // reset
481 // don't show intervals smaller than 1%:
482 if (last_progress
+ 0.01 > p
) {
483 int percent
= (int)(p
* 100);
484 if (last_percent
!= percent
) {
485 System
.out
.println(percent
+ " %");
486 last_percent
= percent
;
493 status
.showProgress(p
);
496 static public void debugDialog() {
497 // note: all this could nicely be done using reflection, so adding another boolean variable would be automatically added here (filtering by the prefix "debug").
498 GenericDialog gd
= new GenericDialog("Debug:");
499 gd
.addCheckbox("debug", debug
);
500 gd
.addCheckbox("debug mouse", debug_mouse
);
501 gd
.addCheckbox("debug sql", debug_sql
);
502 gd
.addCheckbox("debug event", debug_event
);
503 gd
.addCheckbox("debug clip", debug_clip
);
504 gd
.addCheckbox("debug thing", debug_thing
);
506 if (gd
.wasCanceled()) {
509 debug
= gd
.getNextBoolean();
510 debug_mouse
= gd
.getNextBoolean();
511 debug_sql
= gd
.getNextBoolean();
512 debug_event
= gd
.getNextBoolean();
513 debug_clip
= gd
.getNextBoolean();
514 debug_thing
= gd
.getNextBoolean();
518 /** Scan the WindowManager for open stacks.*/
519 static public ImagePlus
[] findOpenStacks() {
520 ImagePlus
[] imp
= scanWindowManager("stacks");
521 if (null == imp
) return null;
525 /** Scan the WindowManager for non-stack images*/
526 static public ImagePlus
[] findOpenImages() {
527 ImagePlus
[] imp
= scanWindowManager("images");
528 if (null == imp
) return null;
532 /** Scan the WindowManager for all open images, including stacks.*/
533 static public ImagePlus
[] findAllOpenImages() {
534 return scanWindowManager("all");
537 static private ImagePlus
[] scanWindowManager(String type
) {
538 // check if any stacks are opened within ImageJ
539 int[] all_ids
= WindowManager
.getIDList();
540 if (null == all_ids
) return null;
541 ImagePlus
[] imp
= new ImagePlus
[all_ids
.length
];
543 for (int i
=0; i
< all_ids
.length
; i
++) {
544 ImagePlus image
= WindowManager
.getImage(all_ids
[i
]);
545 if (type
.equals("stacks")) {
546 if (image
.getStackSize() <= 1) {
549 } else if (type
.equals("images")) {
550 if (image
.getStackSize() > 1) {
558 // resize the array if necessary
559 if (next
!= all_ids
.length
) {
560 ImagePlus
[] imp2
= new ImagePlus
[next
];
561 System
.arraycopy(imp
, 0, imp2
, 0, next
);
564 // return what has been found:
565 if (0 == next
) return null;
569 /**The path of the directory from which images have been recently loaded.*/
570 static public String last_dir
= ij
.Prefs
.getString(ij
.Prefs
.DIR_IMAGE
);
571 /**The path of the last opened file.*/
572 static public String last_file
= null;
574 static public String
cutNumber(double d
, int n_decimals
) {
575 return cutNumber(d
, n_decimals
, false);
578 /** remove_trailing_zeros will leave at least one zero after the comma if appropriate. */
579 static public final String
cutNumber(final double d
, final int n_decimals
, final boolean remove_trailing_zeros
) {
580 final String num
= new Double(d
).toString();
581 int i_e
= num
.indexOf("E-");
583 final int exp
= Integer
.parseInt(num
.substring(i_e
+2));
584 if (n_decimals
< exp
) {
585 final StringBuffer sb
= new StringBuffer("0.");
586 int count
= n_decimals
;
591 return sb
.toString(); // returns 0.000... as many zeros as desired n_decimals
594 StringBuffer sb
= new StringBuffer("0.");
600 sb
.append(num
.charAt(0)); // the single number before the comma
601 // up to here there are 'exp' number of decimals appended
602 int i_end
= 2 + n_decimals
- exp
;
603 if (i_end
> i_e
) i_end
= i_e
; // there arent' that ,any, so cut
604 sb
.append(num
.substring(2, i_end
)); // all numbers after the comma
605 return sb
.toString();
607 // else, there is no scientific notation to worry about
608 final int i_dot
= num
.indexOf('.');
609 final StringBuffer sb
= new StringBuffer(num
.substring(0, i_dot
+1));
610 for (int i
=i_dot
+1; i
< (n_decimals
+ i_dot
+ 1) && i
< num
.length(); i
++) {
611 sb
.append(num
.charAt(i
));
613 // remove zeros from the end
614 if (remove_trailing_zeros
) {
615 for (int i
=sb
.length()-1; i
>i_dot
+1; i
--) { // leave at least one zero after the comma
616 if ('0' == sb
.charAt(i
)) {
623 return sb
.toString();
626 static public final boolean check(final String msg
) {
627 YesNoCancelDialog dialog
= new YesNoCancelDialog(IJ
.getInstance(), "Execute?", msg
);
628 if (dialog
.yesPressed()) {
634 static public final boolean checkYN(String msg
) {
635 YesNoDialog yn
= new YesNoDialog(IJ
.getInstance(), "Execute?", msg
);
636 if (yn
.yesPressed()) return true;
640 static public final String
d2s(double d
, int n_decimals
) {
641 return IJ
.d2s(d
, n_decimals
);
644 // from utilities.c in my CurveMorphing C module ... from C! Java is a low level language with the disadvantages of the high level languages ...
645 /** Returns the angle in radians of the given polar coordinates, correcting the Math.atan2 output. */
646 static public final double getAngle(double x
, double y
) {
648 double a
= Math
.atan2(x
, y
);
649 // fix too large angles (beats me why are they ever generated)
650 if (a
> 2 * Math
.PI
) {
653 // fix atan2 output scheme to match my mental scheme
654 if (a
>= 0.0 && a
<= Math
.PI
/2) {
656 } else if (a
< 0 && a
>= -Math
.PI
) {
658 } else if (a
> Math
.PI
/2 && a
<= Math
.PI
) {
659 a
= Math
.PI
+ Math
.PI
+ Math
.PI
/2 - a
;
665 static public final String
[] getHexRGBColor(Color color
) {
666 int c
= color
.getRGB();
667 String r
= Integer
.toHexString(((c
&0x00FF0000)>>16));
668 if (1 == r
.length()) r
= "0" + r
;
669 String g
= Integer
.toHexString(((c
&0x0000FF00)>>8));
670 if (1 == g
.length()) g
= "0" + g
;
671 String b
= Integer
.toHexString((c
&0x000000FF));
672 if (1 == b
.length()) b
= "0" + b
;
673 return new String
[]{r
, g
, b
};
676 static public final Color
getRGBColorFromHex(String hex
) {
677 if (hex
.length() < 6) return null;
678 return new Color(Integer
.parseInt(hex
.substring(0, 2), 16), // parse in hexadecimal radix
679 Integer
.parseInt(hex
.substring(2, 4), 16),
680 Integer
.parseInt(hex
.substring(4, 6), 16));
683 static public final int[] get4Ints(final int hex
) {
684 return new int[]{((hex
&0xFF000000)>>24),
685 ((hex
&0x00FF0000)>>16),
686 ((hex
&0x0000FF00)>> 8),
690 public void run(String arg
) {
691 IJ
.showMessage("TrakEM2", "TrakEM2 " + Utils
.version
+ "\nCopyright Albert Cardona & Rodney Douglas\nInstitute for Neuroinformatics, Univ. Zurich / ETH\nUniversity of California Los Angeles");
694 static public final File
chooseFile(String name
, String extension
) {
695 return Utils
.chooseFile(null, name
, extension
);
698 /** 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). */
699 static public final File
chooseFile(String default_dir
, String name
, String extension
) {
700 // using ImageJ's JFileChooser or internal FileDialog, according to user preferences.
701 String user
= System
.getProperty("user.name");
703 if (null != name
&& null != extension
) name2
= name
+ extension
;
704 else if (null != name
) name2
= name
;
705 else if (null != extension
) name2
= "untitled" + extension
;
706 if (null != default_dir
) {
707 OpenDialog
.setDefaultDirectory(default_dir
);
709 SaveDialog sd
= new SaveDialog("Save",
710 OpenDialog
.getDefaultDirectory(),
714 String filename
= sd
.getFileName();
715 if (null == filename
|| filename
.toLowerCase().startsWith("null")) return null;
716 File f
= new File(sd
.getDirectory() + "/" + filename
);
717 if (f
.exists() && ControlWindow
.isGUIEnabled()) {
718 YesNoCancelDialog d
= new YesNoCancelDialog(IJ
.getInstance(), "Overwrite?", "File " + filename
+ " exists! Overwrite?");
719 if (d
.cancelPressed()) {
721 } else if (!d
.yesPressed()) {
722 return chooseFile(name
, extension
);
724 // else if yes pressed, overwrite.
729 /** Returns null or the selected directory and file. */
730 static public final String
[] selectFile(String title_msg
) {
731 OpenDialog od
= new OpenDialog("Select file", OpenDialog
.getDefaultDirectory(), null);
732 String file
= od
.getFileName();
733 if (null == file
|| file
.toLowerCase().startsWith("null")) return null;
734 String dir
= od
.getDirectory();
737 dir
= dir
.replace('\\', '/');
738 if (!dir
.endsWith("/")) dir
+= "/";
739 f
= new File(dir
+ "/" + file
); // I'd use File.separator, but in Windows it fails
741 if (null == dir
|| !f
.exists()) {
742 Utils
.log2("No proper file selected.");
745 return new String
[]{dir
, file
};
748 static public final boolean saveToFile(File f
, String contents
) {
749 if (null == f
) return false;
752 DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f), contents.length()));
754 OutputStreamWriter dos
= new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(f
), contents
.length()), "8859_1"); // encoding in Latin 1 (for macosx not to mess around
755 //dos.writeBytes(contents);
756 dos
.write(contents
, 0, contents
.length());
758 } catch (Exception e
) {
760 Utils
.showMessage("ERROR: Most likely did NOT save your file.");
766 static public final boolean saveToFile(String name
, String extension
, String contents
) {
767 if (null == contents
) {
768 Utils
.log2("Utils.saveToFile: contents is null");
772 File f
= Utils
.chooseFile(name
, extension
);
773 return Utils
.saveToFile(f
, contents
);
776 /** Converts sequences of spaces into single space, and trims the ends. */
777 static public final String
cleanString(String s
) {
779 while (-1 != s
.indexOf("\u0020\u0020")) { // \u0020 equals a single space
780 s
= s
.replaceAll("\u0020\u0020", "\u0020");
785 static public final String
openTextFile(final String path
) {
786 if (null == path
|| !new File(path
).exists()) return null;
787 final StringBuffer sb
= new StringBuffer();
788 BufferedReader r
= null;
790 r
= new BufferedReader(new FileReader(path
));
792 String s
= r
.readLine();
793 if (null == s
) break;
794 sb
.append(s
).append('\n'); // I am sure the reading can be done better
796 } catch (Exception e
) {
798 if (null != r
) try { r
.close(); } catch (IOException ioe
) { ioe
.printStackTrace(); }
801 return sb
.toString();
804 /** Returns the file found at path as an array of lines, or null if not found. */
805 static public final String
[] openTextFileLines(final String path
) {
806 if (null == path
|| !new File(path
).exists()) return null;
807 final ArrayList al
= new ArrayList();
809 BufferedReader r
= new BufferedReader(new FileReader(path
));
811 String s
= r
.readLine();
812 if (null == s
) break;
816 } catch (Exception e
) {
820 final String
[] sal
= new String
[al
.size()];
825 static public final char[] openTextFileChars(final String path
) {
827 if (null == path
|| !(f
= new File(path
)).exists()) {
828 Utils
.log("File not found: " + path
);
831 final char[] src
= new char[(int)f
.length()]; // assumes file is small enough to fit in integer range!
833 BufferedReader r
= new BufferedReader(new FileReader(path
));
834 r
.read(src
, 0, src
.length
);
836 } catch (Exception e
) {
843 /** The cosinus between two vectors (in polar coordinates), by means of the dot product. */
844 static public final double getCos(final double x1
, final double y1
, final double x2
, final double y2
) {
845 return (x1
* x2
+ y1
* y2
) / (Math
.sqrt(x1
*x1
+ y1
*y1
) * Math
.sqrt(x2
*x2
+ y2
*y2
));
848 static public final String
removeExtension(final String path
) {
849 final int i_dot
= path
.lastIndexOf('.');
850 if (-1 == i_dot
|| i_dot
+ 4 != path
.length()) return path
;
851 else return path
.substring(0, i_dot
);
854 /** A helper for GenericDialog checkboxes to control other the enabled state of other GUI elements in the same dialog. */
855 static public final void addEnablerListener(final Checkbox master
, final Component
[] enable
, final Component
[] disable
) {
856 master
.addItemListener(new ItemListener() {
857 public void itemStateChanged(ItemEvent ie
) {
858 if (ie
.getStateChange() == ItemEvent
.SELECTED
) {
859 process(enable
, true);
860 process(disable
, false);
862 process(enable
, false);
863 process(disable
, true);
866 private void process(final Component
[] c
, final boolean state
) {
867 if (null == c
) return;
868 for (int i
=0; i
<c
.length
; i
++) c
[i
].setEnabled(state
);
873 static private int n_CPUs
= 0;
875 /** This method is obsolete: there's Runtime.getRuntime().availableProcessors() */
877 static public final int getCPUCount() {
878 if (0 != n_CPUs) return n_CPUs;
879 if (IJ.isWindows()) return 1; // no clue
880 // POSIX systems, attempt to count CPUs from /proc/stat
882 Runtime runtime = Runtime.getRuntime();
883 Process process = runtime.exec("cat /proc/stat");
884 InputStream is = process.getInputStream();
885 InputStreamReader isr = new InputStreamReader(is);
886 BufferedReader br = new BufferedReader(isr);
889 // valid cores will print as cpu0, cpu1. cpu2 ...
890 while ((line = br.readLine()) != null) {
891 if (0 == line.indexOf("cpu") && line.length() > 3 && Character.isDigit(line.charAt(3))) {
895 // fix possible errors
896 if (0 == n_CPUs) n_CPUs = 1;
898 } catch (Exception e) {
899 Utils.log(e.toString()); // just one line
905 static public final boolean wrongImageJVersion() {
906 boolean b
= IJ
.versionLessThan("1.37g");
907 if (b
) Utils
.showMessage("TrakEM2 requires ImageJ 1.37g or above.");
911 static public final boolean java3d
= isJava3DInstalled();
913 static private final boolean isJava3DInstalled() {
915 Class p3f
= Class
.forName("javax.vecmath.Point3f");
916 } catch (ClassNotFoundException cnfe
) {
922 static public final void addLayerRangeChoices(final Layer selected
, final GenericDialog gd
) {
923 Utils
.addLayerRangeChoices(selected
, selected
, gd
);
926 static public final void addLayerRangeChoices(final Layer first
, final Layer last
, final GenericDialog gd
) {
927 final String
[] layers
= new String
[first
.getParent().size()];
928 final ArrayList al_layer_titles
= new ArrayList();
930 for (Iterator it
= first
.getParent().getLayers().iterator(); it
.hasNext(); ) {
931 layers
[i
] = first
.getProject().findLayerThing((Layer
)it
.next()).toString();
932 al_layer_titles
.add(layers
[i
]);
935 final int i_first
= first
.getParent().indexOf(first
);
936 final int i_last
= last
.getParent().indexOf(last
);
937 gd
.addChoice("Start: ", layers
, layers
[i_first
]);
938 final Vector v
= gd
.getChoices();
939 final Choice cstart
= (Choice
)v
.get(v
.size()-1);
940 gd
.addChoice("End: ", layers
, layers
[i_last
]);
941 final Choice cend
= (Choice
)v
.get(v
.size()-1);
942 cstart
.addItemListener(new ItemListener() {
943 public void itemStateChanged(ItemEvent ie
) {
944 int index
= al_layer_titles
.indexOf(ie
.getItem());
945 if (index
> cend
.getSelectedIndex()) cend
.select(index
);
948 cend
.addItemListener(new ItemListener() {
949 public void itemStateChanged(ItemEvent ie
) {
950 int index
= al_layer_titles
.indexOf(ie
.getItem());
951 if (index
< cstart
.getSelectedIndex()) cstart
.select(index
);
956 static public final void addLayerChoice(final String label
, final Layer selected
, final GenericDialog gd
) {
957 final String
[] layers
= new String
[selected
.getParent().size()];
958 final ArrayList al_layer_titles
= new ArrayList();
960 for (Iterator it
= selected
.getParent().getLayers().iterator(); it
.hasNext(); ) {
961 layers
[i
] = selected
.getProject().findLayerThing((Layer
)it
.next()).toString();
962 al_layer_titles
.add(layers
[i
]);
965 final int i_layer
= selected
.getParent().indexOf(selected
);
966 gd
.addChoice(label
, layers
, layers
[i_layer
]);
970 static public final void addRGBColorSliders(final GenericDialog gd
, final Color color
) {
971 gd
.addSlider("Red: ", 0, 255, color
.getRed());
972 gd
.addSlider("Green: ", 0, 255, color
.getGreen());
973 gd
.addSlider("Blue: ", 0, 255, color
.getBlue());
976 /** Converts the ImageProcessor to an ImageProcessor of the given type, or the same if of equal type. */
977 static final public ImageProcessor
convertTo(final ImageProcessor ip
, final int type
, final boolean scaling
) {
979 case ImagePlus
.GRAY8
:
980 return ip
.convertToByte(scaling
);
981 case ImagePlus
.GRAY16
:
982 return ip
.convertToShort(scaling
);
983 case ImagePlus
.GRAY32
:
984 return ip
.convertToFloat();
985 case ImagePlus
.COLOR_RGB
:
986 return ip
.convertToRGB();
987 case ImagePlus
.COLOR_256
:
988 ImagePlus imp
= new ImagePlus("", ip
.convertToRGB());
989 new ImageConverter(imp
).convertRGBtoIndexedColor(256);
990 return imp
.getProcessor();
996 /** 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. */
997 static public final double[] copy(final double[] a
, final int new_length
) {
998 final double[] b
= new double[new_length
];
999 final int len
= a
.length
> new_length ? new_length
: a
.length
;
1000 System
.arraycopy(a
, 0, b
, 0, len
);
1004 static public final double[] copy(final double[] a
, final int first
, final int new_length
) {
1005 final double[] b
= new double[new_length
];
1006 final int len
= new_length
< a
.length
- first ? new_length
: a
.length
- first
;
1007 System
.arraycopy(a
, first
, b
, 0, len
);
1011 /** Reverse in place an array of doubles. */
1012 static public final void reverse(final double[] a
) {
1013 for (int left
=0, right
=a
.length
-1; left
<right
; left
++, right
--) {
1014 double tmp
= a
[left
];
1020 static public final double[] toDouble(final int[] a
, final int len
) {
1021 final double[] b
= new double[len
];
1022 for (int i
=0; i
<len
; i
++) b
[i
] = a
[i
];
1026 static public final int[] toInt(final double[] a
, final int len
) {
1027 final int[] b
= new int[len
];
1028 for (int i
=0; i
<len
; i
++) b
[i
] = (int) a
[i
];
1032 /** OS-agnostic diagnosis of whether the click was for the contextual popup menu. */
1033 static public final boolean isPopupTrigger(final MouseEvent me
) {
1034 return me
.isPopupTrigger() || 0 != (me
.getModifiers() & Event
.META_MASK
);
1037 /** Repaint the given Component on the swing repaint thread (aka "SwingUtilities.invokeLater"). */
1038 static public final void updateComponent(final Component c
) {
1041 // ALL that was needed: to put it into the swing repaint queue ... couldn't they JUST SAY SO
1042 SwingUtilities
.invokeLater(new Runnable() {
1048 /** Like calling pack() on a Frame but on a Component. */
1049 static public final void revalidateComponent(final Component c
) {
1050 SwingUtilities
.invokeLater(new Runnable() {
1059 static public final Point2D
.Double
transform(final AffineTransform affine
, final double x
, final double y
) {
1060 final Point2D
.Double pSrc
= new Point2D
.Double(x
, y
);
1061 if (affine
.isIdentity()) return pSrc
;
1062 final Point2D
.Double pDst
= new Point2D
.Double();
1063 affine
.transform(pSrc
, pDst
);
1066 static public final Point2D
.Double
inverseTransform(final AffineTransform affine
, final double x
, final double y
) {
1067 final Point2D
.Double pSrc
= new Point2D
.Double(x
, y
);
1068 if (affine
.isIdentity()) return pSrc
;
1069 final Point2D
.Double pDst
= new Point2D
.Double();
1071 affine
.createInverse().transform(pSrc
, pDst
);
1072 } catch (NoninvertibleTransformException nite
) {
1073 IJError
.print(nite
);
1078 /** Returns the time as HH:MM:SS */
1079 static public final String
now() {
1080 /* Java time management is retarded. */
1081 final Calendar c
= Calendar
.getInstance();
1082 final int hour
= c
.get(Calendar
.HOUR_OF_DAY
);
1083 final int min
= c
.get(Calendar
.MINUTE
);
1084 final int sec
= c
.get(Calendar
.SECOND
);
1085 final StringBuffer sb
= new StringBuffer();
1086 if (hour
< 10) sb
.append('0');
1087 sb
.append(hour
).append(':');
1088 if (min
< 10) sb
.append('0');
1089 sb
.append(min
).append(':');
1090 if (sec
< 10) sb
.append(0);
1091 return sb
.append(sec
).toString();
1094 static public final void sleep(final long miliseconds
) {
1095 try { Thread
.currentThread().sleep(miliseconds
); } catch (Exception e
) { e
.printStackTrace(); }
1098 /** Mix colors visually: red + green = yellow, etc.*/
1099 static public final Color
mix(Color c1
, Color c2
) {
1100 final float[] b
= Color
.RGBtoHSB(c1
.getRed(), c1
.getGreen(), c1
.getBlue(), new float[3]);
1101 final float[] c
= Color
.RGBtoHSB(c2
.getRed(), c2
.getGreen(), c2
.getBlue(), new float[3]);
1102 final float[] a
= new float[3];
1103 // find to which side the hue values are closer, since hue space is a a circle
1104 // hue values all between 0 and 1
1113 float d2
= 1 + h1
- h2
;
1118 if (a
[0] > 1) a
[0] -= 1;
1121 for (int i
=1; i
<3; i
++) a
[i
] = (b
[i
] + c
[i
]) / 2; // only Saturation and Brightness can be averaged
1122 return Color
.getHSBColor(a
[0], a
[1], a
[2]);
1125 /** Test whether the areas intersect each other. */
1126 static public final boolean intersects(final Area a1
, final Area a2
) {
1127 final Area b
= new Area(a1
);
1129 final java
.awt
.Rectangle r
= b
.getBounds();
1130 return 0 != r
.width
&& 0 != r
.height
;
1133 /** 1 A, 2 B, 3 C --- 26 - z, 27 AA, 28 AB, 29 AC --- 26*27 AAA */
1134 static public final String
getCharacter(int i
) {
1137 char c
= (char)((i
% 26) + 65); // 65 is 'A'
1138 if (0 == k
) return Character
.toString(c
);
1139 return new StringBuffer().append(getCharacter(k
)).append(c
).toString();
1142 static private Field shape_field
= null;
1144 static public final Shape
getShape(final ShapeRoi roi
) {
1146 if (null == shape_field
) {
1147 shape_field
= ShapeRoi
.class.getDeclaredField("shape");
1148 shape_field
.setAccessible(true);
1150 return (Shape
)shape_field
.get(roi
);
1151 } catch (Exception e
) {
1157 /** Get by reflection a private or protected field in the given object. */
1158 static public final Object
getField(final Object ob
, final String field_name
) {
1159 if (null == ob
|| null == field_name
) return null;
1161 Field f
= ob
.getClass().getDeclaredField(field_name
);
1162 f
.setAccessible(true);
1164 } catch (Exception e
) {
1170 static public final Area
getArea(final Roi roi
) {
1171 if (null == roi
) return null;
1172 ShapeRoi sroi
= new ShapeRoi(roi
);
1173 AffineTransform at
= new AffineTransform();
1174 Rectangle bounds
= sroi
.getBounds();
1175 at
.translate(bounds
.x
, bounds
.y
);
1176 Area area
= new Area(getShape(sroi
));
1177 return area
.createTransformedArea(at
);
1180 static public final boolean isEmpty(final Area area
) {
1181 final Rectangle b
= area
.getBounds();
1182 return 0 == b
.width
|| 0 == b
.height
;
1185 /** Returns the approximated area of the given Area object. */
1186 static public final double measureArea(Area area
, final Loader loader
) {
1189 Rectangle bounds
= area
.getBounds();
1191 if (bounds
.width
> 2048 || bounds
.height
> 2048) {
1192 scale
= 2048.0 / bounds
.width
;
1195 Utils
.log("Can't measure: area too large, out of scale range for approximation.");
1198 AffineTransform at
= new AffineTransform();
1199 at
.translate(-bounds
.x
, -bounds
.y
);
1200 at
.scale(scale
, scale
);
1201 area
= area
.createTransformedArea(at
);
1202 bounds
= area
.getBounds();
1203 if (0 == bounds
.width
|| 0 == bounds
.height
) {
1204 Utils
.log("Can't measure: area too large, approximates zero.");
1207 if (null != loader
) loader
.releaseToFit(bounds
.width
* bounds
.height
* 3);
1208 BufferedImage bi
= new BufferedImage(bounds
.width
, bounds
.height
, BufferedImage
.TYPE_BYTE_INDEXED
);
1209 Graphics2D g
= bi
.createGraphics();
1210 g
.setColor(Color
.white
);
1212 final byte[] pixels
= ((DataBufferByte
)bi
.getRaster().getDataBuffer()).getData(); // buffer.getData();
1213 for (int i
=pixels
.length
-1; i
>-1; i
--) {
1214 //if (255 == (pixels[i]&0xff)) sum++;
1215 if (0 != pixels
[i
]) sum
++;
1219 if (1 != scale
) sum
= sum
/ (scale
* scale
);
1220 } catch (Throwable e
) {
1226 /** Compute the area of the triangle defined by 3 points in 3D space, returning half of the length of the vector resulting from the cross product of vectors p1p2 and p1p3. */
1227 static public final double measureArea(final Point3f p1
, final Point3f p2
, final Point3f p3
) {
1228 final Vector3f v
= new Vector3f();
1229 v
.cross(new Vector3f(p2
.x
- p1
.x
, p2
.y
- p1
.y
, p2
.z
- p1
.z
),
1230 new Vector3f(p3
.x
- p1
.x
, p3
.y
- p1
.y
, p3
.z
- p1
.z
));
1231 return 0.5 * Math
.abs(v
.x
* v
.x
+ v
.y
* v
.y
+ v
.z
* v
.z
);
1234 /** Returns true if the roi is of closed shape type like an OvalRoi, ShapeRoi, a Roi rectangle, etc. */
1235 static public final boolean isAreaROI(final Roi roi
) {
1236 switch (roi
.getType()) {
1246 static public final Collection
<Polygon
> getPolygons(Area area
) {
1247 final ArrayList
<Polygon
> pols
= new ArrayList
<Polygon
>();
1248 Polygon pol
= new Polygon();
1250 final float[] coords
= new float[6];
1251 for (PathIterator pit
= area
.getPathIterator(null); !pit
.isDone(); ) {
1252 int seg_type
= pit
.currentSegment(coords
);
1254 case PathIterator
.SEG_MOVETO
:
1255 case PathIterator
.SEG_LINETO
:
1256 pol
.addPoint((int)coords
[0], (int)coords
[1]);
1258 case PathIterator
.SEG_CLOSE
:
1260 pol
= new Polygon();
1263 Utils
.log2("WARNING: unhandled seg type.");
1274 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1275 static public final FloatProcessor
fastConvertToFloat(final ByteProcessor ip
) {
1276 final byte[] pix
= (byte[])ip
.getPixels();
1277 final float[] data
= new float[pix
.length
];
1278 for (int i
=0; i
<pix
.length
; i
++) data
[i
] = pix
[i
]&0xff;
1279 final FloatProcessor fp
= new FloatProcessorT2(ip
.getWidth(), ip
.getHeight(), data
, ip
.getColorModel(), ip
.getMin(), ip
.getMax());
1282 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1283 static public final FloatProcessor
fastConvertToFloat(final ShortProcessor ip
) {
1284 final short[] pix
= (short[])ip
.getPixels();
1285 final float[] data
= new float[pix
.length
];
1286 for (int i
=0; i
<pix
.length
; i
++) data
[i
] = pix
[i
]&0xffff;
1287 final FloatProcessor fp
= new FloatProcessorT2(ip
.getWidth(), ip
.getHeight(), data
, ip
.getColorModel(), ip
.getMin(), ip
.getMax());
1290 /** A method that circumvents the findMinAndMax when creating a float processor from an existing processor. Ignores color calibrations and does no scaling at all. */
1291 static public final FloatProcessor
fastConvertToFloat(final ImageProcessor ip
, final int type
) {
1293 case ImagePlus
.GRAY16
: return fastConvertToFloat((ShortProcessor
)ip
);
1294 case ImagePlus
.GRAY32
: return (FloatProcessor
)ip
;
1295 case ImagePlus
.GRAY8
:
1296 case ImagePlus
.COLOR_256
: return fastConvertToFloat((ByteProcessor
)ip
);
1297 case ImagePlus
.COLOR_RGB
: return (FloatProcessor
)ip
.convertToFloat(); // SLOW
1301 static public final FloatProcessor
fastConvertToFloat(final ImageProcessor ip
) {
1302 if (ip
instanceof ByteProcessor
) return fastConvertToFloat((ByteProcessor
)ip
);
1303 if (ip
instanceof ShortProcessor
) return fastConvertToFloat((ShortProcessor
)ip
);
1304 return (FloatProcessor
)ip
.convertToFloat();
1307 /** 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. */
1308 static public final ResultsTable
createResultsTable(final String title
, final String
[] columns
) {
1309 TextWindow tw
= (TextWindow
)WindowManager
.getFrame(title
);
1311 // hacking again ... missing a getResultsTable() method in TextWindow
1312 ResultsTable rt
= (ResultsTable
)Utils
.getField(tw
.getTextPanel(), "rt");
1313 if (null != rt
) return rt
; // assumes columns will be identical
1315 // else create a new one
1316 ResultsTable rt
= new ResultsTable();
1318 for (int i
=0; i
<columns
.length
; i
++) rt
.setHeading(i
, columns
[i
]);
1323 static public final ImageProcessor
createProcessor(final int type
, final int width
, final int height
) {
1325 case ImagePlus
.GRAY8
: return new ByteProcessor(width
, height
);
1326 case ImagePlus
.GRAY16
: return new ShortProcessor(width
, height
);
1327 case ImagePlus
.GRAY32
: return new FloatProcessor(width
, height
);
1328 case ImagePlus
.COLOR_RGB
: return new ColorProcessor(width
, height
);
1333 /** Paints an approximation of the pipe into the set of slices. */
1334 public void paint(final Pipe pipe
, final Map
<Layer
,ImageProcessor
> slices
, final int value
, final float scale
) {
1335 VectorString3D vs
= pipe
.asVectorString3D();
1336 vs
.resample(1); // one pixel
1337 double[] px
= vs
.getPoints(0);
1338 double[] py
= vs
.getPoints(1);
1339 double[] pz
= vs
.getPoints(2);
1340 double[] pr
= vs
.getDependent(0);
1342 for (int i
=0; i
<px
.length
-1; i
++) {
1343 ImageProcessor ip
= slices
.get(pipe
.getLayerSet().getNearestLayer(pz
[i
]));
1344 if (null == ip
) continue;
1345 OvalRoi ov
= new OvalRoi((int)((px
[i
] - pr
[i
]) * scale
),
1346 (int)((py
[i
] - pr
[i
]) * scale
),
1347 (int)(pr
[i
]*2*scale
), (int)(pr
[i
]*2*scale
));
1350 ip
.fill(ip
.getMask());
1354 static final public boolean matches(final String pattern
, final String s
) {
1355 return Pattern
.compile(pattern
).matcher(s
).matches();
1358 static final public boolean isValidIdentifier(final String s
) {
1359 if (null == s
) return false;
1360 if (!Utils
.matches("^[a-zA-Z]+[a-zA-Z1-9_]*$", s
)) {
1361 Utils
.log("Invalid identifier " + s
);
1368 user=> (def pat #"\b[a-zA-Z]+[\w]*\b")
1370 user=>(re-seq pat "abc def 1a334")
1372 user=> (re-seq pat "abc def a334")
1373 ("abc" "def" "a334")
1375 Then concatenate all good words with underscores.
1376 Returns null when nothing valid is found in 's'.
1378 static final public String
makeValidIdentifier(final String s
) {
1379 if (null == s
) return null;
1380 // Concatenate all good groups with underscores:
1381 final Pattern pat
= Pattern
.compile("\\b[a-zA-Z]+[\\w]*\\b");
1382 final Matcher m
= pat
.matcher(s
);
1383 final StringBuffer sb
= new StringBuffer();
1385 sb
.append(m
.group()).append('_');
1387 if (0 == sb
.length()) return null;
1388 // Remove last underscore
1389 sb
.setLength(sb
.length()-1);
1390 return sb
.toString();
1393 static final public int indexOf(final Object needle
, final Object
[] haystack
) {
1394 for (int i
=0; i
<haystack
.length
; i
++) {
1395 if (haystack
[i
].equals(needle
)) return i
;
1400 /** 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. */
1401 static public final boolean removeFile(final File f
) {
1402 return Utils
.removeFile(f
, true, null);
1405 // Accumulates removed files (not directories) into removed_paths, if not null.
1406 static private final boolean removeFile(final File f
, final boolean stop_if_dir_not_empty
, final ArrayList
<String
> removed_paths
) {
1407 if (null == f
|| !f
.exists()) return false;
1409 if (!Utils
.isTrakEM2Subfile(f
)) {
1410 Utils
.log2("REFUSING to remove file " + f
+ "\n-->REASON: not in a '/trakem2.' file path");
1414 // If it's not a directory, just delete it
1415 if (!f
.isDirectory()) {
1418 // Else delete all directories:
1419 final ArrayList
<File
> dirs
= new ArrayList
<File
>();
1421 // Non-recursive version ... I hate java
1423 int i
= dirs
.size() -1;
1424 final File fdir
= dirs
.get(i
);
1425 Utils
.log2("Examining folder for deletion: " + fdir
.getName());
1426 boolean remove
= true;
1427 for (final File file
: fdir
.listFiles()) {
1428 String name
= file
.getName();
1429 if (name
.equals(".") || name
.equals("..")) continue;
1430 if (file
.isDirectory()) {
1433 } else if (file
.isHidden()) {
1434 if (!file
.delete()) {
1435 Utils
.log("Failed to delete hidden file " + file
.getAbsolutePath());
1438 if (null != removed_paths
) removed_paths
.add(file
.getAbsolutePath());
1439 } else if (stop_if_dir_not_empty
) {
1440 //Utils.log("Not empty: cannot remove dir " + fdir.getAbsolutePath());
1443 if (!file
.delete()) {
1444 Utils
.log("Failed to delete visible file " + file
.getAbsolutePath());
1447 if (null != removed_paths
) removed_paths
.add(file
.getAbsolutePath());
1452 if (!fdir
.delete()) {
1455 Utils
.log2("Removed folder " + fdir
.getAbsolutePath());
1458 } while (dirs
.size() > 0);
1462 } catch (Exception e
) {
1468 /** Returns true if the file cannonical path contains "/trakem2." (adjusted for Windows as well). */
1469 static public boolean isTrakEM2Subfile(final File f
) throws Exception
{
1470 return isTrakEM2Subpath(f
.getCanonicalPath());
1473 /** Returns true if the path contains "/trakem2." (adjusted for Windows as well). */
1474 static public boolean isTrakEM2Subpath(String path
) {
1475 if (IJ
.isWindows()) path
= path
.replace('\\', '/');
1476 return -1 != path
.toLowerCase().indexOf("/trakem2.");
1479 /** Returns true if all files and their subdirectories, recursively, under parent folder have been removed.
1480 * For safety reasons, this function will return false immediately if the parent file path does not include a
1481 * lowercase "trakem2." in it.
1482 * If removed_paths is not null, all removed full paths are added to it.
1484 static public final boolean removePrefixedFiles(final File parent
, final String prefix
, final ArrayList
<String
> removed_paths
) {
1485 if (null == parent
|| !parent
.isDirectory()) return false;
1488 if (!Utils
.isTrakEM2Subfile(parent
)) {
1489 Utils
.log2("REFUSING to remove files recursively under folder " + parent
+ "\n-->REASON: not in a '/trakem2.' file path");
1493 boolean success
= true;
1495 final File
[] list
= parent
.listFiles(new FilenameFilter() {
1496 public boolean accept(File dir
, String name
) {
1497 if (name
.startsWith(prefix
)) return true;
1502 ArrayList
<String
> a
= null;
1503 if (null != removed_paths
) a
= new ArrayList
<String
>();
1505 if (null != list
&& list
.length
> 0) {
1506 for (final File f
: list
) {
1507 if (!Utils
.removeFile(f
, false, a
)) success
= false;
1508 if (null != removed_paths
) {
1509 removed_paths
.addAll(a
);
1517 } catch (Exception e
) {
1523 /** The CTRL key functionality is passed over to the COMMAND key (aka META key) in a MacOSX. */
1524 static public final int getControlModifier() {
1525 return IJ
.isMacOSX() ? InputEvent
.META_MASK
1526 : InputEvent
.CTRL_MASK
;
1529 /** The CTRL key functionality is passed over to the COMMAND key (aka META key) in a MacOSX. */
1530 static public final boolean isControlDown(final InputEvent e
) {
1531 return IJ
.isMacOSX() ? e
.isMetaDown()
1532 : e
.isControlDown();