diff -pruN 1.51q-1/applet.html 1.52g-1/applet.html --- 1.51q-1/applet.html 2006-01-26 12:35:32.000000000 +0000 +++ 1.52g-1/applet.html 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ - - - - -ImageJ Applet - - - - - - - - diff -pruN 1.51q-1/aREADME.txt 1.52g-1/aREADME.txt --- 1.51q-1/aREADME.txt 2010-08-26 22:59:02.000000000 +0000 +++ 1.52g-1/aREADME.txt 2018-07-04 16:14:14.000000000 +0000 @@ -1,7 +1,2 @@ The ant utility (http://ant.apache.org/) will compile and run ImageJ using -the build file (build.xml) in this directory. There is a version of ant at - - http://imagej.nih.gov/ij/download/tools/ant/ant.zip - -set up to use the JVM distributed with the Windows version of ImageJ. -The README included in the ZIP archive has more information. +the build file (build.xml) in this directory. \ No newline at end of file diff -pruN 1.51q-1/build.xml 1.52g-1/build.xml --- 1.51q-1/build.xml 2017-09-03 23:43:40.000000000 +0000 +++ 1.52g-1/build.xml 2018-06-22 22:04:06.000000000 +0000 @@ -6,7 +6,7 @@ - + @@ -17,7 +17,6 @@ - Tue, 09 Oct 2018 18:18:15 +0200 + imagej (1.51q-1) unstable; urgency=medium * New upstream release. diff -pruN 1.51q-1/debian/compat 1.52g-1/debian/compat --- 1.51q-1/debian/compat 2017-09-26 14:31:23.000000000 +0000 +++ 1.52g-1/debian/compat 2018-10-09 16:18:15.000000000 +0000 @@ -1 +1 @@ -10 +11 diff -pruN 1.51q-1/debian/control 1.52g-1/debian/control --- 1.51q-1/debian/control 2017-09-26 14:31:23.000000000 +0000 +++ 1.52g-1/debian/control 2018-10-09 16:18:15.000000000 +0000 @@ -1,24 +1,25 @@ Source: imagej Maintainer: Debian Med Packaging Team -Uploaders: Carnë Draug +Uploaders: David Miguel Susano Pinto Section: science Priority: optional -Build-Depends: debhelper (>= 10) +Build-Depends: debhelper (>= 11~) Build-Depends-Indep: ant, - default-jdk-doc, - default-jdk-headless, - javahelper, - maven-repo-helper -Standards-Version: 4.1.0 -Vcs-Browser: https://anonscm.debian.org/cgit/debian-med/imagej.git -Vcs-Git: https://anonscm.debian.org/git/debian-med/imagej.git + default-jdk-doc, + default-jdk-headless, + javahelper, + maven-repo-helper +Standards-Version: 4.2.1 +Vcs-Browser: https://salsa.debian.org/med-team/imagej +Vcs-Git: https://salsa.debian.org/med-team/imagej.git Homepage: https://imagej.nih.gov/ij Package: imagej Architecture: all -Depends: ${java:Depends}, ${misc:Depends}, - default-jre | java5-runtime, - libij-java (= ${source:Version}) +Depends: ${java:Depends}, + ${misc:Depends}, + default-jre | java6-runtime, + libij-java Recommends: ${java:Recommends} Description: Image processing program with a focus on microscopy images It can display, edit, analyze, process, save and print 8-bit, 16-bit and @@ -43,10 +44,11 @@ Description: Image processing program wi Package: libij-java Architecture: all Section: java -Depends: ${misc:Depends}, ${java:Depends} +Depends: ${misc:Depends}, + ${java:Depends} Recommends: ${java:Recommends} -Replaces: imagej (<< 1.51p+dfsg-1) Breaks: imagej (<< 1.51p+dfsg-1) +Replaces: imagej (<< 1.51p+dfsg-1) Description: Java library for ImageJ ImageJ is a program for image analysis and processing, with a focus on microscopy images. @@ -56,7 +58,8 @@ Description: Java library for ImageJ Package: libij-java-doc Architecture: all Section: doc -Depends: ${misc:Depends}, ${java:Depends} +Depends: ${misc:Depends}, + ${java:Depends} Recommends: ${java:Recommends} Suggests: libij-java Description: documentation of libij-java diff -pruN 1.51q-1/debian/patches/drop-mac-plugins.patch 1.52g-1/debian/patches/drop-mac-plugins.patch --- 1.51q-1/debian/patches/drop-mac-plugins.patch 2017-09-26 14:31:23.000000000 +0000 +++ 1.52g-1/debian/patches/drop-mac-plugins.patch 2018-10-09 16:18:15.000000000 +0000 @@ -12,12 +12,11 @@ Author: Carnë Draug - -- -Last-Update: 2017-09-26 ---- a/ij/gui/Plot.java -+++ b/ij/gui/Plot.java -@@ -246,7 +246,7 @@ - - /** Constructs a new plot from an InputStream and closes the stream. If the ImagePlus is - * non-null, its title and ImageProcessor are used, but the image displayed is not modified. -- * @see toStream() */ -+ */ - public Plot(ImagePlus imp, InputStream is) throws IOException, ClassNotFoundException { - ObjectInputStream in = new ObjectInputStream(is); - pp = (PlotProperties)in.readObject(); diff -pruN 1.51q-1/debian/patches/series 1.52g-1/debian/patches/series --- 1.51q-1/debian/patches/series 2017-09-26 14:31:23.000000000 +0000 +++ 1.52g-1/debian/patches/series 2018-10-09 16:18:15.000000000 +0000 @@ -1,3 +1,2 @@ drop-mac-plugins.patch link-javadocs.patch -fix-javadocs-syntax.patch diff -pruN 1.51q-1/debian/rules 1.52g-1/debian/rules --- 1.51q-1/debian/rules 2017-09-26 14:31:23.000000000 +0000 +++ 1.52g-1/debian/rules 2018-10-09 16:18:15.000000000 +0000 @@ -13,6 +13,3 @@ override_dh_auto_build: override_dh_installchangelogs: dh_installchangelogs release-notes.html - -get-orig-source: - uscan --verbose --force-download --repack --compression xz diff -pruN 1.51q-1/debian/upstream/metadata 1.52g-1/debian/upstream/metadata --- 1.51q-1/debian/upstream/metadata 2017-09-26 14:31:23.000000000 +0000 +++ 1.52g-1/debian/upstream/metadata 2018-10-09 16:18:15.000000000 +0000 @@ -1,6 +1,8 @@ +--- +Changelog: https://imagej.nih.gov/ij/notes.html Reference: - Author: Caroline A Schneider and Wayne S Rasband and Kevin W Eliceiri - Title: NIH Image to ImageJ: 25 years of image analysis + Title: "NIH Image to ImageJ: 25 years of image analysis" Journal: Nature methods Year: 2012 Volume: 9 @@ -10,3 +12,10 @@ Reference: PMID: 22930834 URL: https://www.nature.com/nmeth/journal/v9/n7/abs/nmeth.2089.html Eprint: https://www.nature.com/nmeth/journal/v9/n7/pdf/nmeth.2089.pdf +Registry: + - Name: biii + Entry: imagej + - Name: SciCrunch + Entry: SCR_003070 + - Name: OMICtools + Entry: OMICS_13947 diff -pruN 1.51q-1/ij/CompositeImage.java 1.52g-1/ij/CompositeImage.java --- 1.51q-1/ij/CompositeImage.java 2017-04-13 21:06:42.000000000 +0000 +++ 1.52g-1/ij/CompositeImage.java 2018-07-03 08:21:10.000000000 +0000 @@ -294,10 +294,7 @@ public class CompositeImage extends Imag for (int i=1; i=0; i--) - sb.copyBits(cip[i], 0, 0, Blitter. OR); - } - img = ip.createImage(); - singleChannel = false; - } - */ - ImageStack getRGBStack(ImagePlus imp) { ImageProcessor ip = imp.getProcessor(); int w = ip.getWidth(); diff -pruN 1.51q-1/ij/Executer.java 1.52g-1/ij/Executer.java --- 1.51q-1/ij/Executer.java 2017-08-18 22:33:34.000000000 +0000 +++ 1.52g-1/ij/Executer.java 2018-09-02 07:49:34.000000000 +0000 @@ -146,59 +146,20 @@ public class Executer implements Runnabl if (openRecent(cmd)) return; // is it an example in Help>Examples menu? - if (openExample(cmd)) + if (Editor.openExample(cmd)) return; if ("Auto Threshold".equals(cmd)&&(String)table.get("Auto Threshold...")!=null) runCommand("Auto Threshold..."); else if ("Enhance Local Contrast (CLAHE)".equals(cmd)&&(String)table.get("CLAHE ")!=null) runCommand("CLAHE "); - else - IJ.error("Unrecognized command: \"" + cmd+"\""); + else { + if ("Table...".equals(cmd)) + IJ.runPlugIn("ij.plugin.NewPlugin", "table"); + else + IJ.error("Unrecognized command: \"" + cmd+"\""); + } } - } - - private boolean openExample(String name) { - boolean isMacro = name.endsWith(".ijm"); - boolean isJava = name.endsWith(".java"); - boolean isJavaScript = name.endsWith(".js"); - boolean isBeanShell = name.endsWith(".bsh"); - boolean isPython = name.endsWith(".py"); - if (!(isMacro||isJava||isJavaScript||isBeanShell||isPython)) - return false; - boolean run = !isJava && (Prefs.autoRunExamples||IJ.shiftKeyDown()||IJ.controlKeyDown()||IJ.altKeyDown()); - int rows = 24; - int columns = 70; - int options = Editor.MENU_BAR; - String text = null; - Editor ed = new Editor(rows, columns, 0, options); - String dir = "Macro/"; - if (isJava) - dir = "Java/"; - else if (isJavaScript) - dir = "JavaScript/"; - else if (isBeanShell) - dir = "BeanShell/"; - else if (isPython) - dir = "Python/"; - String url = "http://wsr.imagej.net/download/Examples/"+dir+name; - text = IJ.openUrlAsString(url); - if (text.startsWith(" -* To work with macros, the first word of each component label must be -* unique. If this is not the case, add underscores, which will be converted +* To work with macros, the first word of each component label must be +* unique. If this is not the case, add underscores, which will be converted * to spaces when the dialog is displayed. For example, change the checkbox labels * "Show Quality" and "Show Residue" to "Show_Quality" and "Show_Residue". */ -public class GenericDialog extends Dialog implements ActionListener, TextListener, +public class GenericDialog extends Dialog implements ActionListener, TextListener, FocusListener, ItemListener, KeyListener, AdjustmentListener, WindowListener { protected Vector numberField, stringField, checkbox, choice, slider, radioButtonGroups; @@ -48,10 +48,8 @@ FocusListener, ItemListener, KeyListener private String okLabel = " OK "; private String cancelLabel = "Cancel"; private String helpLabel = "Help"; - private boolean wasCanceled, wasOKed; - private int y; - private int nfIndex, sfIndex, cbIndex, choiceIndex, textAreaIndex, radioButtonIndex; - private GridBagLayout grid; + private boolean wasCanceled, wasOKed; + private int nfIndex, sfIndex, cbIndex, choiceIndex, textAreaIndex, radioButtonIndex; private GridBagConstraints c; private boolean firstNumericField=true; private boolean firstSlider=true; @@ -60,26 +58,27 @@ FocusListener, ItemListener, KeyListener private Hashtable labels; private boolean macro; private String macroOptions; + private boolean addToSameRow; + private boolean addToSameRowCalled; private int topInset, leftInset, bottomInset; - private boolean customInsets; - private Vector sliderIndexes; - private Vector sliderScales; - private Checkbox previewCheckbox; // the "Preview" Checkbox, if any - private Vector dialogListeners; // the Objects to notify on user input - private PlugInFilterRunner pfr; // the PlugInFilterRunner for automatic preview - private String previewLabel = " Preview"; - private final static String previewRunning = "wait..."; - private boolean recorderOn; // whether recording is allowed - private boolean yesNoCancel; - private char echoChar; - private boolean hideCancelButton; - private boolean centerDialog = true; - private String helpURL; - private String yesLabel, noLabel; - private boolean smartRecording; - private Vector imagePanels; - private static GenericDialog instance; - private boolean firstPaint = true; + private boolean customInsets; + private Vector sliderIndexes, sliderScales, sliderDigits; + private Checkbox previewCheckbox; // the "Preview" Checkbox, if any + private Vector dialogListeners; // the Objects to notify on user input + private PlugInFilterRunner pfr; // the PlugInFilterRunner for automatic preview + private String previewLabel = " Preview"; + private final static String previewRunning = "wait..."; + private boolean recorderOn; // whether recording is allowed + private boolean yesNoCancel; + private char echoChar; + private boolean hideCancelButton; + private boolean centerDialog = true; + private String helpURL; + private String yesLabel, noLabel; + private boolean smartRecording; + private Vector imagePanels; + private static GenericDialog instance; + private boolean firstPaint = true; /** Creates a new GenericDialog with the specified title. Uses the current image image window as the parent frame or the ImageJ frame if no image windows @@ -88,7 +87,7 @@ FocusListener, ItemListener, KeyListener public GenericDialog(String title) { this(title, getParentFrame()); } - + private static Frame getParentFrame() { Frame parent = WindowManager.getCurrentImage()!=null? (Frame)WindowManager.getCurrentImage().getWindow():IJ.getInstance()!=null?IJ.getInstance():new Frame(); @@ -109,7 +108,7 @@ FocusListener, ItemListener, KeyListener setForeground(SystemColor.controlText); setBackground(SystemColor.control); } - grid = new GridBagLayout(); + GridBagLayout grid = new GridBagLayout(); c = new GridBagConstraints(); setLayout(grid); macroOptions = Macro.getOptions(); @@ -117,7 +116,7 @@ FocusListener, ItemListener, KeyListener addKeyListener(this); addWindowListener(this); } - + /** Adds a numeric field. The first word of the label must be unique or command recording will not work. * @param label the label @@ -141,15 +140,24 @@ FocusListener, ItemListener, KeyListener if (label2.indexOf('_')!=-1) label2 = label2.replace('_', ' '); Label theLabel = makeLabel(label2); - c.gridx = 0; c.gridy = y; + if (addToSameRow) { + c.gridx = GridBagConstraints.RELATIVE; + c.insets.left = 10; + } else { + c.gridx = 0; c.gridy++; + if (firstNumericField) + c.insets = getInsets(5, 0, 3, 0); // top, left, bottom, right + else + c.insets = getInsets(0, 0, 3, 0); + } c.anchor = GridBagConstraints.EAST; c.gridwidth = 1; - if (firstNumericField) - c.insets = getInsets(5, 0, 3, 0); - else - c.insets = getInsets(0, 0, 3, 0); - grid.setConstraints(theLabel, c); - add(theLabel); + //IJ.log("x="+c.gridx+", y= "+c.gridy+", width="+c.gridwidth+", ancher= "+c.anchor+" "+c.insets); + add(theLabel, c); + if (addToSameRow) { + c.insets.left = 0; + addToSameRow = false; + } if (numberField==null) { numberField = new Vector(5); defaultValues = new Vector(5); @@ -169,43 +177,53 @@ FocusListener, ItemListener, KeyListener numberField.addElement(tf); defaultValues.addElement(new Double(defaultValue)); defaultText.addElement(tf.getText()); - c.gridx = 1; c.gridy = y; + c.gridx = GridBagConstraints.RELATIVE; c.anchor = GridBagConstraints.WEST; tf.setEditable(true); //if (firstNumericField) tf.selectAll(); firstNumericField = false; if (units==null||units.equals("")) { - grid.setConstraints(tf, c); - add(tf); + add(tf, c); } else { Panel panel = new Panel(); panel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); panel.add(tf); panel.add(new Label(" "+units)); - grid.setConstraints(panel, c); - add(panel); + add(panel, c); } if (Recorder.record || macro) saveLabel(tf, label); - y++; } - + private Label makeLabel(String label) { if (IJ.isMacintosh()) label += " "; return new Label(label); } - + + /** Saves the label for given component, for macro recording and for accessing the component in macros. */ private void saveLabel(Object component, String label) { if (labels==null) labels = new Hashtable(); - if (label.length()>0) { - if (label.charAt(0)==' ') - label = label.trim(); - labels.put(component, label); - } + if (label.length()>0) + label = Macro.trimKey(label.trim()); + if (hasLabel(label)) { // not a unique label? + label += "_0"; + for (int n=1; hasLabel(label); n++) { // while still not a unique label + label = label.substring(0, label.lastIndexOf('_')); //remove counter + label += "_"+n; + } + } + labels.put(component, label); + } + + /** Returns whether the list of labels for macro recording or macro creation contains a given label. */ + private boolean hasLabel(String label) { + for (Object o : labels.keySet()) + if (labels.get(o).equals(label)) return true; + return false; } - + /** Adds an 8 column text field. * @param label the label * @param defaultText the text initially displayed @@ -217,31 +235,32 @@ FocusListener, ItemListener, KeyListener /** Adds a text field. * @param label the label * @param defaultText text initially displayed - * @param columns width of the text field + * @param columns width of the text field. If columns is 8 or more, additional items may be added to this line with addToSameRow() */ public void addStringField(String label, String defaultText, int columns) { String label2 = label; if (label2.indexOf('_')!=-1) label2 = label2.replace('_', ' '); Label theLabel = makeLabel(label2); - c.gridx = 0; c.gridy = y; + boolean custom = customInsets; + if (addToSameRow) { + c.gridx = GridBagConstraints.RELATIVE; + addToSameRow = false; + } else { + c.gridx = 0; c.gridy++; + if (stringField==null) + c.insets = getInsets(5, 0, 5, 0); // top, left, bottom, right + else + c.insets = getInsets(0, 0, 5, 0); + } c.anchor = GridBagConstraints.EAST; c.gridwidth = 1; - boolean custom = customInsets; + add(theLabel, c); if (stringField==null) { stringField = new Vector(4); defaultStrings = new Vector(4); - c.insets = getInsets(5, 0, 5, 0); - } else - c.insets = getInsets(0, 0, 5, 0); - grid.setConstraints(theLabel, c); - add(theLabel); - if (custom) { - if (stringField.size()==0) - c.insets = getInsets(5, 0, 5, 0); - else - c.insets = getInsets(0, 0, 5, 0); } + TextField tf = new TextField(defaultText, columns); if (IJ.isLinux()) tf.setBackground(Color.white); tf.setEchoChar(echoChar); @@ -250,23 +269,23 @@ FocusListener, ItemListener, KeyListener tf.addTextListener(this); tf.addFocusListener(this); tf.addKeyListener(this); - c.gridx = 1; c.gridy = y; + c.gridx = GridBagConstraints.RELATIVE; c.anchor = GridBagConstraints.WEST; - grid.setConstraints(tf, c); + c.gridwidth = columns <= 8 ? 1 : GridBagConstraints.REMAINDER; + c.insets.left = 0; tf.setEditable(true); - add(tf); + add(tf, c); stringField.addElement(tf); defaultStrings.addElement(defaultText); if (Recorder.record || macro) saveLabel(tf, label); - y++; } - + /** Sets the echo character for the next string field. */ public void setEchoChar(char echoChar) { this.echoChar = echoChar; } - + /** Adds a checkbox. * @param label the label * @param defaultValue the initial state @@ -283,25 +302,31 @@ FocusListener, ItemListener, KeyListener String label2 = label; if (label2.indexOf('_')!=-1) label2 = label2.replace('_', ' '); - if (checkbox==null) { - checkbox = new Vector(4); - c.insets = getInsets(15, 20, 0, 0); - } else - c.insets = getInsets(0, 20, 0, 0); - c.gridx = 0; c.gridy = y; - c.gridwidth = 2; + if (addToSameRow) { + c.gridx = GridBagConstraints.RELATIVE; + c.insets.left = 10; + addToSameRow = false; + } else { + c.gridx = 0; c.gridy++; + if (checkbox==null) + c.insets = getInsets(15, 20, 0, 0); // top, left, bottom, right + else + c.insets = getInsets(0, 20, 0, 0); + } c.anchor = GridBagConstraints.WEST; + c.gridwidth = 2; + if (checkbox==null) + checkbox = new Vector(4); Checkbox cb = new Checkbox(label2); - grid.setConstraints(cb, c); cb.setState(defaultValue); cb.addItemListener(this); cb.addKeyListener(this); - add(cb); + add(cb, c); + c.insets.left = 0; checkbox.addElement(cb); if (!isPreview &&(Recorder.record || macro)) //preview checkbox is not recordable saveLabel(cb, label); if (isPreview) previewCheckbox = cb; - y++; } /** Adds a checkbox labelled "Preview" for "automatic" preview. @@ -414,13 +439,12 @@ FocusListener, ItemListener, KeyListener i1++; } } - c.gridx = 0; c.gridy = y; - c.gridwidth = 2; + c.gridx = 0; c.gridy++; + c.gridwidth = GridBagConstraints.REMAINDER; c.anchor = GridBagConstraints.WEST; c.insets = getInsets(10, 0, 0, 0); - grid.setConstraints(panel, c); - add(panel); - y++; + addToSameRow = false; + add(panel, c); } /** Adds a radio button group. @@ -431,6 +455,7 @@ FocusListener, ItemListener, KeyListener * @param defaultItem button initially selected */ public void addRadioButtonGroup(String label, String[] items, int rows, int columns, String defaultItem) { + addToSameRow = false; Panel panel = new Panel(); int n = items.length; panel.setLayout(new GridLayout(rows, columns, 0, 0)); @@ -453,15 +478,13 @@ FocusListener, ItemListener, KeyListener insets.top = 2; insets.left += 10; } - c.gridx = 0; c.gridy = y; - c.gridwidth = 2; + c.gridx = 0; c.gridy++; + c.gridwidth = GridBagConstraints.REMAINDER; c.anchor = GridBagConstraints.WEST; c.insets = new Insets(insets.top, insets.left, 0, 0); - grid.setConstraints(panel, c); - add(panel); + add(panel, c); if (Recorder.record || macro) saveLabel(cg, label); - y++; } /** Adds a popup menu. @@ -474,17 +497,23 @@ FocusListener, ItemListener, KeyListener if (label2.indexOf('_')!=-1) label2 = label2.replace('_', ' '); Label theLabel = makeLabel(label2); - c.gridx = 0; c.gridy = y; + if (addToSameRow) { + c.gridx = GridBagConstraints.RELATIVE; + addToSameRow = false; + } else { + c.gridx = 0; c.gridy++; + if (choice==null) + c.insets = getInsets(5, 0, 5, 0); + else + c.insets = getInsets(0, 0, 5, 0); + } c.anchor = GridBagConstraints.EAST; c.gridwidth = 1; if (choice==null) { choice = new Vector(4); defaultChoiceIndexes = new Vector(4); - c.insets = getInsets(5, 0, 5, 0); - } else - c.insets = getInsets(0, 0, 5, 0); - grid.setConstraints(theLabel, c); - add(theLabel); + } + add(theLabel, c); Choice thisChoice = new Choice(); thisChoice.addKeyListener(this); thisChoice.addItemListener(this); @@ -494,18 +523,16 @@ FocusListener, ItemListener, KeyListener thisChoice.select(defaultItem); else thisChoice.select(0); - c.gridx = 1; c.gridy = y; + c.gridx = GridBagConstraints.RELATIVE; c.anchor = GridBagConstraints.WEST; - grid.setConstraints(thisChoice, c); - add(thisChoice); + add(thisChoice, c); choice.addElement(thisChoice); int index = thisChoice.getSelectedIndex(); defaultChoiceIndexes.addElement(new Integer(index)); if (Recorder.record || macro) saveLabel(thisChoice, label); - y++; } - + /** Adds a message consisting of one or more lines of text. */ public void addMessage(String text) { addMessage(text, null, null); @@ -516,7 +543,7 @@ FocusListener, ItemListener, KeyListener public void addMessage(String text, Font font) { addMessage(text, font, null); } - + /** Adds a message consisting of one or more lines of text, which will be displayed using the specified font and color. */ public void addMessage(String text, Font font, Color color) { @@ -525,22 +552,24 @@ FocusListener, ItemListener, KeyListener theLabel = new MultiLineLabel(text); else theLabel = new Label(text); - //theLabel.addKeyListener(this); - c.gridx = 0; c.gridy = y; - c.gridwidth = 2; + if (addToSameRow) { + c.gridx = GridBagConstraints.RELATIVE; + addToSameRow = false; + } else { + c.gridx = 0; c.gridy++; + c.insets = getInsets("".equals(text)?0:10, 20, 0, 0); // top, left, bottom, right + } + c.gridwidth = GridBagConstraints.REMAINDER; c.anchor = GridBagConstraints.WEST; - c.insets = getInsets(text.equals("")?0:10, 20, 0, 0); c.fill = GridBagConstraints.HORIZONTAL; - grid.setConstraints(theLabel, c); if (font!=null) theLabel.setFont(font); if (color!=null) theLabel.setForeground(color); - add(theLabel); + add(theLabel, c); c.fill = GridBagConstraints.NONE; - y++; } - + /** Adds one or two (side by side) text areas. * @param text1 initial contents of the first text area * @param text2 initial contents of the second text area or null @@ -562,19 +591,18 @@ FocusListener, ItemListener, KeyListener textArea2.setFont(font); panel.add(textArea2); } - c.gridx = 0; c.gridy = y; - c.gridwidth = 2; + c.gridx = 0; c.gridy++; + c.gridwidth = GridBagConstraints.REMAINDER; c.anchor = GridBagConstraints.WEST; c.insets = getInsets(15, 20, 0, 0); - grid.setConstraints(panel, c); - add(panel); - y++; + addToSameRow = false; + add(panel, c); } - + /** * Adds a slider (scroll bar) to the dialog box. - * Floating point values will be used if (maxValue-minValue)<=5.0 - * and either minValue or maxValue are non-integer. + * Floating point values are used if (maxValue-minValue)<=5.0 + * and either defaultValue or minValue are non-integer. * @param label the label * @param minValue the minimum value of the slider * @param maxValue the maximum value of the slider @@ -583,31 +611,66 @@ FocusListener, ItemListener, KeyListener public void addSlider(String label, double minValue, double maxValue, double defaultValue) { if (defaultValuemaxValue) defaultValue=maxValue; - int columns = 4; int digits = 0; double scale = 1.0; if ((maxValue-minValue)<=5.0 && (minValue!=(int)minValue||maxValue!=(int)maxValue||defaultValue!=(int)defaultValue)) { - scale = 20.0; + scale = 50.0; minValue *= scale; maxValue *= scale; defaultValue *= scale; digits = 2; } + addSlider( label, minValue, maxValue, defaultValue, scale, digits); + } + + /** This vesion of addSlider() adds a 'stepSize' argument.
+ * Example: http://wsr.imagej.net/macros/SliderDemo.txt + */ + public void addSlider(String label, double minValue, double maxValue, double defaultValue, double stepSize) { + if ( stepSize <= 0 ) stepSize = 1; + int digits = digits(stepSize); + double scale = 1.0 / Math.abs( stepSize ); + if ( scale <= 0 ) scale = 1; + if ( defaultValue < minValue ) defaultValue = minValue; + if ( defaultValue > maxValue ) defaultValue = maxValue; + minValue *= scale; + maxValue *= scale; + defaultValue *= scale; + addSlider(label, minValue, maxValue, defaultValue, scale, digits); + } + + private int digits( double d ) { + if ( d == (int) d ) return 0; + String s = Double.toString(d); + s = s.substring(s.indexOf(".") + 1); + return s.length(); + } + + private void addSlider(String label, double minValue, double maxValue, double defaultValue, double scale, int digits) { + int columns = 4 + digits - 2; + if ( columns < 4 ) columns = 4; + if (minValue<0.0) columns++; String label2 = label; if (label2.indexOf('_')!=-1) label2 = label2.replace('_', ' '); Label theLabel = makeLabel(label2); - c.gridx = 0; c.gridy = y; + if (addToSameRow) { + c.gridx = GridBagConstraints.RELATIVE; + c.insets.bottom += 3; + addToSameRow = false; + } else { + c.gridx = 0; c.gridy++; + c.insets = getInsets(0, 0, 3, 0); // top, left, bottom, right + } c.anchor = GridBagConstraints.EAST; c.gridwidth = 1; - c.insets = new Insets(0, 0, 3, 0); - grid.setConstraints(theLabel, c); - add(theLabel); - + add(theLabel, c); + if (slider==null) { slider = new Vector(5); sliderIndexes = new Vector(5); sliderScales = new Vector(5); + sliderDigits = new Vector(5); } Scrollbar s = new Scrollbar(Scrollbar.HORIZONTAL, (int)defaultValue, 1, (int)minValue, (int)maxValue+1); slider.addElement(s); @@ -621,6 +684,7 @@ FocusListener, ItemListener, KeyListener } if (IJ.isWindows()) columns -= 2; if (columns<1) columns = 1; + //IJ.log("scale=" + scale + ", columns=" + columns + ", digits=" + digits); TextField tf = new TextField(IJ.d2s(defaultValue/scale, digits), columns); if (IJ.isLinux()) tf.setBackground(Color.white); tf.addActionListener(this); @@ -630,11 +694,12 @@ FocusListener, ItemListener, KeyListener numberField.addElement(tf); sliderIndexes.add(new Integer(numberField.size()-1)); sliderScales.add(new Double(scale)); + sliderDigits.add(new Integer(digits)); defaultValues.addElement(new Double(defaultValue/scale)); defaultText.addElement(tf.getText()); tf.setEditable(true); firstSlider = false; - + Panel panel = new Panel(); GridBagLayout pgrid = new GridBagLayout(); GridBagConstraints pc = new GridBagConstraints(); @@ -643,46 +708,45 @@ FocusListener, ItemListener, KeyListener pc.gridwidth = 1; pc.ipadx = 85; pc.anchor = GridBagConstraints.WEST; - pgrid.setConstraints(s, pc); - panel.add(s); + panel.add(s, pc); pc.ipadx = 0; // reset // text field pc.gridx = 1; pc.insets = new Insets(5, 5, 0, 0); pc.anchor = GridBagConstraints.EAST; - pgrid.setConstraints(tf, pc); - panel.add(tf); - - grid.setConstraints(panel, c); - c.gridx = 1; c.gridy = y; + panel.add(tf, pc); + + c.gridx = GridBagConstraints.RELATIVE; c.gridwidth = 1; c.anchor = GridBagConstraints.WEST; - c.insets = new Insets(0, 0, 0, 0); - grid.setConstraints(panel, c); - add(panel); - y++; + c.insets.left = 0; + c.insets.bottom -= 3; + add(panel, c); if (Recorder.record || macro) saveLabel(tf, label); } /** Adds a Panel to the dialog. */ public void addPanel(Panel panel) { - addPanel(panel , GridBagConstraints.WEST, getInsets(5,0,0,0)); + addPanel(panel, GridBagConstraints.WEST, addToSameRow ? c.insets : getInsets(5,0,0,0)); } /** Adds a Panel to the dialog with custom contraint and insets. The - defaults are GridBagConstraints.WEST (left justified) and + defaults are GridBagConstraints.WEST (left justified) and "new Insets(5, 0, 0, 0)" (5 pixels of padding at the top). */ public void addPanel(Panel panel, int constraints, Insets insets) { - c.gridx = 0; c.gridy = y; + if (addToSameRow) { + c.gridx = GridBagConstraints.RELATIVE; + addToSameRow = false; + } else { + c.gridx = 0; c.gridy++; + } c.gridwidth = 2; c.anchor = constraints; c.insets = insets; - grid.setConstraints(panel, c); - add(panel); - y++; + add(panel, c); } - + /** Adds an image to the dialog. */ public void addImage(ImagePlus image) { ImagePanel imagePanel = new ImagePanel(image); @@ -692,15 +756,16 @@ FocusListener, ItemListener, KeyListener imagePanels.add(imagePanel); } - - /** Set the insets (margins), in pixels, that will be - used for the next component added to the dialog. + + /** Set the insets (margins), in pixels, that will be + used for the next component added to the dialog + (except components added to the same row with addToSameRow)
     Default insets:
         addMessage: 0,20,0 (empty string) or 10,20,0
         addCheckbox: 15,20,0 (first checkbox) or 0,20,0
-        addCheckboxGroup: 10,0,0 
-        addRadioButtonGroup: 5,10,0 
+        addCheckboxGroup: 10,0,0
+        addRadioButtonGroup: 5,10,0
         addNumericField: 5,0,3 (first field) or 0,0,3
         addStringField: 5,0,5 (first field) or 0,0,5
         addChoice: 5,0,5 (first field) or 0,0,5
@@ -712,7 +777,19 @@ FocusListener, ItemListener, KeyListener
     	bottomInset = bottom;
     	customInsets = true;
     }
-    
+
+    /** Makes the next item appear in the same row as the previous.
+     *  May be used for addNumericField, addSlider, addChoice, addCheckbox, addStringField,
+     *  addMessage, addPanel, and before the showDialog() method
+     *  (in the latter case, the buttons appear to the right of the previous item).
+     *  Note that addMessage (and addStringField, if its column width is more than 8) use
+     *  the remaining width, so it must be the last item of a row. 
+     */
+    public void addToSameRow() {
+        addToSameRow = true;
+        addToSameRowCalled = true;
+    }
+
     /** Sets a replacement label for the "OK" button. */
     public void setOKLabel(String label) {
     	okLabel = label;
@@ -737,7 +814,7 @@ FocusListener, ItemListener, KeyListener
     public void enableYesNoCancel() {
     	enableYesNoCancel(" Yes ", " No ");
     }
-    
+
     /** Make this a "Yes No Cancel" dialog with custom labels. Here is an example:
     	
         GenericDialog gd = new GenericDialog("YesNoCancel Demo");
@@ -779,7 +856,7 @@ FocusListener, ItemListener, KeyListener
      * For other listeners, the OK button will not cause a call to dialogItemChanged;
      * the CANCEL button will never cause such a call.
      * @param dl the Object that wants to listen.
-     */    
+     */
     public void addDialogListener(DialogListener dl) {
         if (dialogListeners == null)
             dialogListeners = new Vector();
@@ -789,11 +866,11 @@ FocusListener, ItemListener, KeyListener
 
 	/** Returns true if the user clicked on "Cancel". */
     public boolean wasCanceled() {
-    	if (wasCanceled)
+    	if (wasCanceled && !Thread.currentThread().getName().endsWith("Script_Macro$"))
     		Macro.abort();
     	return wasCanceled;
     }
-    
+
 	/** Returns true if the user has clicked on "OK" or a macro is running. */
     public boolean wasOKed() {
     	return wasOKed || macro;
@@ -810,7 +887,7 @@ FocusListener, ItemListener, KeyListener
 		if (macro) {
 			label = (String)labels.get((Object)tf);
 			theText = Macro.getValue(macroOptions, label, theText);
-		}	
+		}
 		String originalText = (String)defaultText.elementAt(nfIndex);
 		double defaultValue = ((Double)(defaultValues.elementAt(nfIndex))).doubleValue();
 		double value;
@@ -846,7 +923,7 @@ FocusListener, ItemListener, KeyListener
 		nfIndex++;
 		return value;
     }
-    
+
 	private String trim(String value) {
 		if (value.endsWith(".0"))
 			value = value.substring(0, value.length()-2);
@@ -890,16 +967,16 @@ FocusListener, ItemListener, KeyListener
 		}
 		return value;
 	}
-	
-	/** Returns true if one or more of the numeric fields contained an  
+
+	/** Returns true if one or more of the numeric fields contained an
 		invalid number. Must be called after one or more calls to getNextNumber(). */
    public boolean invalidNumber() {
     	boolean wasInvalid = invalidNumber;
     	invalidNumber = false;
     	return wasInvalid;
     }
-    
-	/** Returns an error message if getNextNumber was unable to convert a 
+
+	/** Returns an error message if getNextNumber was unable to convert a
 		string into a number, otherwise, returns null. */
 	public String getErrorMessage() {
 		return errorMessage;
@@ -922,11 +999,12 @@ FocusListener, ItemListener, KeyListener
 				String s = interp!=null?interp.getVariableAsString(theText):null;
 				if (s!=null) theText = s;
 			}
-		}	
+		}
 		if (recorderOn) {
 			String s = theText;
 			if (s!=null&&s.length()>=3&&Character.isLetter(s.charAt(0))&&s.charAt(1)==':'&&s.charAt(2)=='\\')
-				s = s.replaceAll("\\\\", "\\\\\\\\");  // replace "\" with "\\" in Windows file paths
+				s = s.replaceAll("\\\\", "/");  // replace "\" with "/" in Windows file paths
+			s = Recorder.fixString(s);
 			if (!smartRecording || !s.equals((String)defaultStrings.elementAt(sfIndex)))
 				recordOption(tf, s);
 			else if (Recorder.getCommandOptions()==null)
@@ -935,7 +1013,7 @@ FocusListener, ItemListener, KeyListener
 		sfIndex++;
 		return theText;
     }
-    
+
   	/** Returns the state of the next checkbox. */
     public boolean getNextBoolean() {
 		if (checkbox==null)
@@ -952,7 +1030,7 @@ FocusListener, ItemListener, KeyListener
 		cbIndex++;
 		return state;
     }
-    
+
     // Returns true if s2 is in s1 and not in a bracketed literal (e.g., "[literal]")
     boolean isMatch(String s1, String s2) {
     	if (s1.startsWith(s2))
@@ -979,7 +1057,7 @@ FocusListener, ItemListener, KeyListener
     	}
     	return false;
     }
-    
+
   	/** Returns the selected item in the next popup menu. */
     public String getNextChoice() {
 		if (choice==null)
@@ -991,13 +1069,13 @@ FocusListener, ItemListener, KeyListener
 			item = Macro.getValue(macroOptions, label, item);
 			if (item!=null && item.startsWith("&")) // value is macro variable
 				item = getChoiceVariable(item);
-		}	
+		}
 		if (recorderOn)
 			recordOption(thisChoice, item);
 		choiceIndex++;
 		return item;
     }
-    
+
   	/** Returns the index of the selected item in the next popup menu. */
     public int getNextChoiceIndex() {
 		if (choice==null)
@@ -1022,7 +1100,7 @@ FocusListener, ItemListener, KeyListener
 				else
 					item = s;
 			}
-		}	
+		}
 		if (recorderOn) {
 			int defaultIndex = ((Integer)(defaultChoiceIndexes.elementAt(choiceIndex))).intValue();
 			if (!(smartRecording&&index==defaultIndex)) {
@@ -1034,7 +1112,7 @@ FocusListener, ItemListener, KeyListener
 		choiceIndex++;
 		return index;
     }
-    
+
   	/** Returns the selected item in the next radio button group. */
     public String getNextRadioButton() {
 		if (radioButtonGroups==null)
@@ -1048,7 +1126,7 @@ FocusListener, ItemListener, KeyListener
 		if (macro) {
 			String label = (String)labels.get((Object)cg);
 			item = Macro.getValue(macroOptions, label, item);
-		}	
+		}
 		if (recorderOn)
 			recordOption(cg, item);
 		return item;
@@ -1071,36 +1149,30 @@ FocusListener, ItemListener, KeyListener
 			item = s;
 		return item;
 	}
-    
-  	/** Returns the contents of the next textarea. */
+
+  	/** Returns the contents of the next text area. */
 	public String getNextText() {
-		String text;
+		String text = null;
+		String key = "text1";
 		if (textAreaIndex==0 && textArea1!=null) {
-			//textArea1.selectAll();
 			text = textArea1.getText();
-			textAreaIndex++;
 			if (macro)
 				text = Macro.getValue(macroOptions, "text1", text);
-			if (recorderOn) {
-				String text2 = text;
-				String cmd = Recorder.getCommand();
-				if (cmd!=null && cmd.equals("Convolve...")) {
-					text2 = text.replaceAll("\n","\\\\n");
-					if (!text.endsWith("\n")) text2 = text2 + "\\n";
-				} else
-					text2 = text.replace('\n',' ');
-				Recorder.recordOption("text1", text2);
-			}
 		} else if (textAreaIndex==1 && textArea2!=null) {
-			textArea2.selectAll();
 			text = textArea2.getText();
-			textAreaIndex++;
 			if (macro)
 				text = Macro.getValue(macroOptions, "text2", text);
-			if (recorderOn)
-				Recorder.recordOption("text2", text.replace('\n',' '));
-		} else
-			text = null;
+			key = "text2";
+		}
+		textAreaIndex++;
+		if (recorderOn && text!=null) {
+			String text2 = text;
+			String cmd = Recorder.getCommand();
+			if (cmd!=null && cmd.equals("Calibrate..."))
+				text2 = text2.replace('\n',' ');
+			text2 = Recorder.fixString(text2);
+			Recorder.recordOption(key, text2);
+		}
 		return text;
 	}
 
@@ -1132,24 +1204,27 @@ FocusListener, ItemListener, KeyListener
 				help.addActionListener(this);
 				help.addKeyListener(this);
 			}
-			if (IJ.isMacintosh()) {
-				if (addHelp) buttons.add(help);
-				if (yesNoCancel) buttons.add(no);
-				if (!hideCancelButton) buttons.add(cancel);
-				buttons.add(okay);
-			} else {
+			if (IJ.isWindows() || Prefs.dialogCancelButtonOnRight) {
 				buttons.add(okay);
 				if (yesNoCancel) buttons.add(no);;
 				if (!hideCancelButton)
 					buttons.add(cancel);
 				if (addHelp) buttons.add(help);
+			} else {
+				if (addHelp) buttons.add(help);
+				if (yesNoCancel) buttons.add(no);
+				if (!hideCancelButton) buttons.add(cancel);
+				buttons.add(okay);
+			}
+			if (addToSameRow) {
+				c.gridx = GridBagConstraints.RELATIVE;
+			} else {
+				c.gridx = 0; c.gridy++;
 			}
-			c.gridx = 0; c.gridy = y;
 			c.anchor = GridBagConstraints.EAST;
-			c.gridwidth = 2;
+			c.gridwidth = addToSameRowCalled?GridBagConstraints.REMAINDER:2;
 			c.insets = new Insets(15, 0, 0, 0);
-			grid.setConstraints(buttons, c);
-			add(buttons);
+			add(buttons, c);
 			if (IJ.isMacOSX()&&IJ.isJava18())
 				instance = this;
 			pack();
@@ -1159,7 +1234,7 @@ FocusListener, ItemListener, KeyListener
 			recorderOn = Recorder.record;
 			IJ.wait(25);
 		}
-		
+
 		/* For plugins that read their input only via dialogItemChanged, call it at least once */
 		if (!wasCanceled && dialogListeners!=null && dialogListeners.size()>0) {
 			resetCounters();
@@ -1168,7 +1243,7 @@ FocusListener, ItemListener, KeyListener
 		}
 		resetCounters();
 	}
-	
+
     /** Reset the counters before reading the dialog parameters */
 	private void resetCounters() {
 		nfIndex = 0;        // prepare for readout
@@ -1184,7 +1259,7 @@ FocusListener, ItemListener, KeyListener
   	public Vector getNumericFields() {
   		return numberField;
   	}
-    
+
   	/** Returns the Vector containing the string TextFields. */
   	public Vector getStringFields() {
   		return stringField;
@@ -1219,7 +1294,7 @@ FocusListener, ItemListener, KeyListener
   	public TextArea getTextArea2() {
   		return textArea2;
   	}
-  	
+
   	/** Returns a reference to the Label or MultiLineLabel created by the
   		last addMessage() call, or null if addMessage() was not called. */
   	public Component getMessage() {
@@ -1230,13 +1305,13 @@ FocusListener, ItemListener, KeyListener
     public Checkbox getPreviewCheckbox() {
         return previewCheckbox;
     }
-    
+
     /** Returns 'true' if this dialog has a "Preview" checkbox and it is enabled. */
     public boolean isPreviewActive() {
         return previewCheckbox!=null && previewCheckbox.getState();
     }
 
-	/** Returns references to the "OK" ("Yes"), "Cancel", 
+	/** Returns references to the "OK" ("Yes"), "Cancel",
 		and if present, "No" buttons as an array. */
 	public Button[] getButtons() {
   		Button[] buttons = new Button[3];
@@ -1255,7 +1330,7 @@ FocusListener, ItemListener, KeyListener
             if (IJ.isMacOSX()) repaint();   //workaround OSX 10.4 refresh bug
         }
     }
-    
+
     /** Display dialog centered on the primary screen. */
     public void centerDialog(boolean b) {
     	centerDialog = b;
@@ -1266,7 +1341,7 @@ FocusListener, ItemListener, KeyListener
     	super.setLocation(x, y);
     	centerDialog = false;
     }
-    
+
     public void setDefaultString(int index, String str) {
     	if (defaultStrings!=null && index>=0 && index". There is an example at
@@ -1464,11 +1539,11 @@ FocusListener, ItemListener, KeyListener
 			new MacroRunner(macro);
 		}
 	}
-	
+
 	protected boolean isMacro() {
 		return macro;
 	}
-    
+
 	public static GenericDialog getInstance() {
 		return instance;
 	}
diff -pruN 1.51q-1/ij/gui/HistogramWindow.java 1.52g-1/ij/gui/HistogramWindow.java
--- 1.51q-1/ij/gui/HistogramWindow.java	2017-05-30 17:33:08.000000000 +0000
+++ 1.52g-1/ij/gui/HistogramWindow.java	2018-07-27 13:17:56.000000000 +0000
@@ -448,7 +448,6 @@ public class HistogramWindow extends Ima
 	/** Returns the histogram values as a ResultsTable. */
 	public ResultsTable getResultsTable() {
 		ResultsTable rt = new ResultsTable();
-		rt.showRowNumbers(false);
 		rt.setPrecision(digits);
 		String vheading = stats.binSize==1.0?"value":"bin start";
 		if (cal.calibrated() && !cal.isSigned16Bit()) {
@@ -459,6 +458,8 @@ public class HistogramWindow extends Ima
 			}
 		} else {
 			for (int i=0; i=0; i--) {
 			if (labelRects!=null&&labelRects[i]!=null&&labelRects[i].contains(sx,sy)) {
@@ -932,8 +941,6 @@ public class ImageCanvas extends Canvas
 			setMagnification(newMag);
 			imp.getWindow().pack();
 		}
-		//IJ.write(newMag + " " + srcRect.x+" "+srcRect.y+" "+srcRect.width+" "+srcRect.height+" "+dstWidth + " " + dstHeight);
-		//IJ.write(srcRect.x + " " + srcRect.width + " " + dstWidth);
 		setMaxBounds();
 		repaint();
 	}
@@ -1004,7 +1011,6 @@ public class ImageCanvas extends Canvas
 	
 	Color getColor(int index){
 		IndexColorModel cm = (IndexColorModel)imp.getProcessor().getColorModel();
-		//IJ.write(""+index+" "+(new Color(cm.getRGB(index))));
 		return new Color(cm.getRGB(index));
 	}
 	
@@ -1077,11 +1083,10 @@ public class ImageCanvas extends Canvas
 				win.running2 = false;
 			return;
 		}
-		
+				
 		int x = e.getX();
 		int y = e.getY();
-		flags = e.getModifiers();
-		
+		flags = e.getModifiers();		
 		if (toolID!=Toolbar.MAGNIFIER && (e.isPopupTrigger()||(!IJ.isMacintosh()&&(flags&Event.META_MASK)!=0))) {
 			handlePopupMenu(e);
 			return;
@@ -1142,8 +1147,8 @@ public class ImageCanvas extends Canvas
 				setDrawingColor(ox, oy, IJ.altKeyDown());
 				break;
 			case Toolbar.WAND:
-				Roi roi = imp.getRoi();
 				double tolerance = WandToolOptions.getTolerance();
+				Roi roi = imp.getRoi();
 				if (roi!=null && (tolerance==0.0||imp.isThreshold()) && roi.contains(ox, oy)) {
 					Rectangle r = roi.getBounds();
 					if (r.width==imageWidth && r.height==imageHeight)
@@ -1277,14 +1282,16 @@ public class ImageCanvas extends Canvas
 		int sy = e.getY();
 		int ox = offScreenX(sx);
 		int oy = offScreenY(sy);
-		Roi roi = imp.getRoi();
+		Roi roi = imp.getRoi();	
+		int tool = Toolbar.getToolId();	
+
 		int handle = roi!=null?roi.isHandle(sx, sy):-1;
 		boolean multiPointMode = roi!=null && (roi instanceof PointRoi) && handle==-1
-			&& Toolbar.getToolId()==Toolbar.POINT && Toolbar.getMultiPointMode();
+			&& tool==Toolbar.POINT && Toolbar.getMultiPointMode();
 		if (multiPointMode) {
 			double oxd = offScreenXD(sx);
 			double oyd = offScreenYD(sy);
-			if (e.isShiftDown()) {
+			if (e.isShiftDown() && !IJ.isMacro()) {
 				FloatPolygon points = roi.getFloatPolygon();
 				if (points.npoints>0) {
 					double x0 = points.xpoints[0];
@@ -1296,10 +1303,21 @@ public class ImageCanvas extends Canvas
 						oxd = points.xpoints[0];
 				}
 			}
-			((PointRoi)roi).addPoint(imp, oxd, oyd);
+			((PointRoi)roi).addUserPoint(imp, oxd, oyd);
 			imp.setRoi(roi);
 			return;
 		}
+				
+		if (roi!=null && (roi instanceof PointRoi) && ((PointRoi)roi).promptBeforeDeleting()) {
+			int npoints = ((PolygonRoi)roi).getNCoordinates();
+			int counters = ((PointRoi)roi).getNCounters();
+			if (handle==-1 && !(tool==Toolbar.POINT && !Toolbar.getMultiPointMode()&&IJ.shiftKeyDown())) {
+				String msg = "Delete this multi-point selection ("+npoints+" points, "+counters+" counter"+(counters>1?"s":"")+")?";
+				if (!IJ.showMessageWithCancel("Delete Points?",msg+"\nRestore using Edit>Selection>Restore Selection."))
+					return;
+			}
+		}
+		
 		setRoiModState(e, roi, handle);
 		if (roi!=null) {
 			if (handle>=0) {
@@ -1325,7 +1343,6 @@ public class ImageCanvas extends Canvas
 			if ((type==Roi.POLYGON || type==Roi.POLYLINE || type==Roi.ANGLE)
 			&& roi.getState()==roi.CONSTRUCTING)
 				return;
-			int tool = Toolbar.getToolId();
 			if ((tool==Toolbar.POLYGON||tool==Toolbar.POLYLINE||tool==Toolbar.ANGLE)&& !(IJ.shiftKeyDown()||IJ.altKeyDown())) {
 				imp.deleteRoi();
 				return;
@@ -1541,7 +1558,7 @@ public class ImageCanvas extends Canvas
 		Overlay o = showAllOverlay;
 		if (o==null)
 			o = overlay;
-		if (o==null)
+		if (o==null || !o.isSelectable())
 			return false;
 		boolean roiManagerShowAllMode = o==showAllOverlay && !Prefs.showAllSliceOnly;
 		boolean labels = o.getDrawLabels();
@@ -1571,6 +1588,7 @@ public class ImageCanvas extends Canvas
 				imp.setRoi(roi);
 				roi.handleMouseDown(sx, sy);
 				roiManagerSelect(roi, false);
+				ResultsTable.selectRow(roi);
 				return true;
 			}
 		}
diff -pruN 1.51q-1/ij/gui/ImageWindow.java 1.52g-1/ij/gui/ImageWindow.java
--- 1.51q-1/ij/gui/ImageWindow.java	2017-08-14 22:28:50.000000000 +0000
+++ 1.52g-1/ij/gui/ImageWindow.java	2018-05-22 21:36:14.000000000 +0000
@@ -393,13 +393,17 @@ public class ImageWindow extends Frame i
 	public boolean close() {
 		boolean isRunning = running || running2;
 		running = running2 = false;
-		if (imp==null)
-			return true;
+		if (imp==null) return true;
 		boolean virtual = imp.getStackSize()>1 && imp.getStack().isVirtual();
 		if (isRunning) IJ.wait(500);
+		if (imp==null) return true;
+		boolean changes = imp.changes;
+		Roi roi = imp.getRoi();
+		if (roi!=null && (roi instanceof PointRoi) && ((PointRoi)roi).promptBeforeDeleting())
+			changes = true;
 		if (ij==null || ij.quittingViaMacro() || IJ.getApplet()!=null || Interpreter.isBatchMode() || IJ.macroRunning() || virtual)
-			imp.changes = false;
-		if (imp.changes) {
+			changes = false;
+		if (changes) {
 			String msg;
 			String name = imp.getTitle();
 			if (name.length()>22)
diff -pruN 1.51q-1/ij/gui/Line.java 1.52g-1/ij/gui/Line.java
--- 1.51q-1/ij/gui/Line.java	2017-04-23 10:11:30.000000000 +0000
+++ 1.52g-1/ij/gui/Line.java	2018-07-03 15:53:52.000000000 +0000
@@ -417,6 +417,8 @@ public class Line extends Roi {
 				if (ip2==null) return new double[0];
 				int width = ip2.getWidth();
 				int height = ip2.getHeight();
+				if (ip2 instanceof FloatProcessor)
+					return ProfilePlot.getColumnAverageProfile(new Rectangle(0,0,width,height),ip2);
 				profile = new double[width];
 				double[] aLine;
 				ip2.setInterpolate(false);
@@ -491,6 +493,11 @@ public class Line extends Roi {
 		}
 		return p;
 	}
+	
+	/** Returns the number of points in this selection; equivalent to getPolygon().npoints. */
+	public int size() {
+		return getStrokeWidth()<=1?2:4;
+	}
 
 	public void drawPixels(ImageProcessor ip) {
 		ip.setLineWidth(1);
diff -pruN 1.51q-1/ij/gui/NewImage.java 1.52g-1/ij/gui/NewImage.java
--- 1.51q-1/ij/gui/NewImage.java	2017-01-16 12:51:58.000000000 +0000
+++ 1.52g-1/ij/gui/NewImage.java	2018-05-23 15:18:02.000000000 +0000
@@ -12,7 +12,8 @@ import ij.process.*;
 public class NewImage {
 
 	public static final int GRAY8=0, GRAY16=1, GRAY32=2, RGB=3;
-	public static final int FILL_BLACK=1, FILL_RAMP=2, FILL_RANDOM=3, FILL_WHITE=4, CHECK_AVAILABLE_MEMORY=8;
+	public static final int FILL_BLACK=1, FILL_RAMP=2, FILL_NOISE=3, FILL_RANDOM=3,
+		FILL_WHITE=4, CHECK_AVAILABLE_MEMORY=8;
 	private static final int OLD_FILL_WHITE=0;
 	
     static final String TYPE = "new.type";
@@ -22,14 +23,14 @@ public class NewImage {
 	static final String SLICES = "new.slices";
 
     private static String name = "Untitled";
-    private static int width = Prefs.getInt(WIDTH, 512);
-    private static int height = Prefs.getInt(HEIGHT, 512);
-    private static int slices = Prefs.getInt(SLICES, 1);
-    private static int type = Prefs.getInt(TYPE, GRAY8);
-    private static int fillWith = Prefs.getInt(FILL, FILL_BLACK);
+    private static int staticWidth = Prefs.getInt(WIDTH, 512);
+    private static int staticHeight = Prefs.getInt(HEIGHT, 512);
+    private static int staticSlices = Prefs.getInt(SLICES, 1);
+    private static int staticType = Prefs.getInt(TYPE, GRAY8);
+    private static int staticFillWith = Prefs.getInt(FILL, FILL_BLACK);
     private static String[] types = {"8-bit", "16-bit", "32-bit", "RGB"};
-    private static String[] fill = {"White", "Black", "Ramp", "Random"};
-    
+    private static String[] fill = {"White", "Black", "Ramp", "Noise"}; 
+    private int gwidth, gheight, gslices, gtype, gfill;
 	
     public NewImage() {
     	openImage();
@@ -43,7 +44,7 @@ public class NewImage {
 		if (type==GRAY16) bytesPerPixel = 2;
 		else if (type==GRAY32||type==RGB) bytesPerPixel = 4;
 		long size = (long)width*height*nSlices*bytesPerPixel;
-		int sizeThreshold = fill==FILL_RANDOM?10:250;
+		int sizeThreshold = fill==FILL_NOISE?10:250;
 		boolean bigStack = size/(1024*1024)>=sizeThreshold;
 		String size2 = size/(1024*1024)+"MB ("+width+"x"+height+"x"+nSlices+")";
 		if ((options&CHECK_AVAILABLE_MEMORY)!=0) {
@@ -81,23 +82,23 @@ public class NewImage {
 				Object pixels2 = null;
 				switch (type) {
 					case GRAY8: pixels2 = new byte[width*height];
-						if (fill==FILL_RANDOM)
-							fillRandomByte(new ByteProcessor(width,height,(byte[])pixels2));
+						if (fill==FILL_NOISE)
+							fillNoiseByte(new ByteProcessor(width,height,(byte[])pixels2));
 						break;
 					case GRAY16: pixels2 = new short[width*height];
-						if (fill==FILL_RANDOM)
-							fillRandomShort(new ShortProcessor(width,height,(short[])pixels2,null));
+						if (fill==FILL_NOISE)
+							fillNoiseShort(new ShortProcessor(width,height,(short[])pixels2,null));
 						break;
 					case GRAY32: pixels2 = new float[width*height];
-						if (fill==FILL_RANDOM)
-							fillRandomFloat(new FloatProcessor(width,height,(float[])pixels2,null));
+						if (fill==FILL_NOISE)
+							fillNoiseFloat(new FloatProcessor(width,height,(float[])pixels2,null));
 						break;
 					case RGB: pixels2 = new int[width*height];
-						if (fill==FILL_RANDOM)
-							fillRandomRGB(new ColorProcessor(width,height,(int[])pixels2), false);
+						if (fill==FILL_NOISE)
+							fillNoiseRGB(new ColorProcessor(width,height,(int[])pixels2), false);
 						break;
 				}
-				if ((fill==FILL_WHITE||fill==FILL_RAMP) || ((type==RGB)&&(fill!=FILL_RANDOM)))
+				if ((fill==FILL_WHITE||fill==FILL_RAMP) || ((type==RGB)&&(fill!=FILL_NOISE)))
 					System.arraycopy(ip.getPixels(), 0, pixels2, 0, width*height);
 				stack.addSlice(null, pixels2);
 				if (IJ.escapePressed()) {IJ.beep(); break;};
@@ -148,8 +149,8 @@ public class NewImage {
 						pixels[offset++] = ramp[x];
 				}
 				break;
-			case FILL_RANDOM:
-				fillRandomByte(ip);
+			case FILL_NOISE:
+				fillNoiseByte(ip);
 				break;
 		}
 		ImagePlus imp = new ImagePlus(title, ip);
@@ -160,7 +161,7 @@ public class NewImage {
 		return imp;
 	}
 	
-	private static void fillRandomByte(ImageProcessor ip) {
+	private static void fillNoiseByte(ImageProcessor ip) {
 		ip.add(127);
 		ip.noise(31);
 	}
@@ -193,8 +194,8 @@ public class NewImage {
 						pixels[offset++] = ramp[x];
 				}
 				break;
-			case FILL_RANDOM:
-				fillRandomRGB(ip, true);
+			case FILL_NOISE:
+				fillNoiseRGB(ip, true);
 				break;
 		}
 		ImagePlus imp = new ImagePlus(title, ip);
@@ -205,7 +206,9 @@ public class NewImage {
 		return imp;
 	}
 	
-	private static void fillRandomRGB(ColorProcessor ip, boolean sp) {
+	private static void fillNoiseRGB(ColorProcessor ip, boolean sp) {
+		int width = ip.getWidth();
+		int height = ip.getHeight();
 		ByteProcessor rr = new ByteProcessor(width, height);
 		ByteProcessor gg = new ByteProcessor(width, height);
 		ByteProcessor bb = new ByteProcessor(width, height);
@@ -241,8 +244,8 @@ public class NewImage {
 						pixels[offset++] = ramp[x];
 				}
 				break;
-			case FILL_RANDOM:
-				fillRandomShort(ip);
+			case FILL_NOISE:
+				fillNoiseShort(ip);
 				break;
 		}
 	    if (fill==FILL_WHITE)
@@ -256,7 +259,7 @@ public class NewImage {
 		return imp;
 	}
 
-	private static void fillRandomShort(ImageProcessor ip) {
+	private static void fillNoiseShort(ImageProcessor ip) {
 		ip.add(32767);
 		ip.noise(7940);
 	}
@@ -289,8 +292,8 @@ public class NewImage {
 						pixels[offset++] = ramp[x];
 				}
 				break;
-			case FILL_RANDOM:
-				fillRandomFloat(ip);
+			case FILL_NOISE:
+				fillNoiseFloat(ip);
 				break;
 		}
 	    if (fill==FILL_WHITE)
@@ -300,12 +303,12 @@ public class NewImage {
 			boolean ok = createStack(imp, ip, slices, GRAY32, options);
 			if (!ok) imp = null;
 		}
-		if (fill!=FILL_RANDOM)
+		if (fill!=FILL_NOISE)
 			imp.getProcessor().setMinAndMax(0.0, 1.0); // default display range
 		return imp;
 	}
 
-	private static void fillRandomFloat(ImageProcessor ip) {
+	private static void fillNoiseFloat(ImageProcessor ip) {
 		ip.noise(1);
 	}
 
@@ -345,56 +348,67 @@ public class NewImage {
 	}
 	
 	boolean showDialog() {
-		if (typeRGB)
-			type = GRAY8;
-		if (fillWithFILL_RANDOM)
-			fillWith = FILL_WHITE;
+		if (staticTypeRGB)
+			staticType = GRAY8;
+		if (staticFillWithFILL_NOISE)
+			staticFillWith = FILL_WHITE;
 		GenericDialog gd = new GenericDialog("New Image...");
 		gd.addStringField("Name:", name, 12);
-		gd.addChoice("Type:", types, types[type]);
-		gd.addChoice("Fill with:", fill, fill[fillWith]);
-		gd.addNumericField("Width:", width, 0, 5, "pixels");
-		gd.addNumericField("Height:", height, 0, 5, "pixels");
-		gd.addNumericField("Slices:", slices, 0, 5, "");
+		gd.addChoice("Type:", types, types[staticType]);
+		gd.addChoice("Fill with:", fill, fill[staticFillWith]);
+		gd.addNumericField("Width:", staticWidth, 0, 5, "pixels");
+		gd.addNumericField("Height:", staticHeight, 0, 5, "pixels");
+		gd.addNumericField("Slices:", staticSlices, 0, 5, "");
 		gd.showDialog();
 		if (gd.wasCanceled())
 			return false;
 		name = gd.getNextString();
 		String s = gd.getNextChoice();
 		if (s.startsWith("8"))
-			type = GRAY8;
+			gtype = GRAY8;
 		else if (s.startsWith("16"))
-			type = GRAY16;
+			gtype = GRAY16;
 		else if (s.endsWith("RGB") || s.endsWith("rgb"))
-			type = RGB;
+			gtype = RGB;
 		else
-			type = GRAY32;
-		fillWith = gd.getNextChoiceIndex();
-		width = (int)gd.getNextNumber();
-		height = (int)gd.getNextNumber();
-		slices = (int)gd.getNextNumber();
-		if (slices<1) slices = 1;
-		if (width<1 || height<1) {
+			gtype = GRAY32;
+		gfill = gd.getNextChoiceIndex();
+		gwidth = (int)gd.getNextNumber();
+		gheight = (int)gd.getNextNumber();
+		gslices = (int)gd.getNextNumber();
+		if (gslices<1) gslices = 1;
+		if (gwidth<1 || gheight<1) {
 			IJ.error("New Image", "Width and height must be >0");
 			return false;
-		} else
+		} else {
+			if (!IJ.isMacro()) {
+				staticWidth = gwidth;
+				staticHeight = gheight;
+				staticSlices = gslices;
+				staticType = gtype;
+				staticFillWith = gfill;
+			}
 			return true;
+		}
 	}
 
 	void openImage() {
 		if (!showDialog())
 			return;
-		try {open(name, width, height, slices, type, fillWith);}
-		catch(OutOfMemoryError e) {IJ.outOfMemory("New Image...");}
+		try {
+			open(name, gwidth, gheight, gslices, gtype, gfill);
+		} catch(OutOfMemoryError e) {
+			IJ.outOfMemory("New Image...");
+		}
 	}
 	
 	/** Called when ImageJ quits. */
 	public static void savePreferences(Properties prefs) {
-		prefs.put(TYPE, Integer.toString(type));
-		prefs.put(FILL, Integer.toString(fillWith));
-		prefs.put(WIDTH, Integer.toString(width));
-		prefs.put(HEIGHT, Integer.toString(height));
-		prefs.put(SLICES, Integer.toString(slices));
+		prefs.put(TYPE, Integer.toString(staticType));
+		prefs.put(FILL, Integer.toString(staticFillWith));
+		prefs.put(WIDTH, Integer.toString(staticWidth));
+		prefs.put(HEIGHT, Integer.toString(staticHeight));
+		prefs.put(SLICES, Integer.toString(staticSlices));
 	}
 
 }
diff -pruN 1.51q-1/ij/gui/OvalRoi.java 1.52g-1/ij/gui/OvalRoi.java
--- 1.51q-1/ij/gui/OvalRoi.java	2014-01-21 21:01:08.000000000 +0000
+++ 1.52g-1/ij/gui/OvalRoi.java	2018-07-03 15:50:32.000000000 +0000
@@ -291,6 +291,12 @@ public class OvalRoi extends Roi {
 		Polygon p = getPolygon();
 		return new FloatPolygon(toFloat(p.xpoints), toFloat(p.ypoints), p.npoints);
 	}
+	
+	/** Returns the number of points in this selection; equivalent to getPolygon().npoints. */
+	public int size() {
+		return getPolygon().npoints;
+	}
+
 
 	/** Tests if the specified point is inside the boundary of this OvalRoi.
 	* Authors: Barry DeZonia and Michael Schmid
diff -pruN 1.51q-1/ij/gui/Overlay.java 1.52g-1/ij/gui/Overlay.java
--- 1.51q-1/ij/gui/Overlay.java	2017-01-30 09:37:56.000000000 +0000
+++ 1.52g-1/ij/gui/Overlay.java	2018-05-31 12:26:54.000000000 +0000
@@ -15,7 +15,9 @@ public class Overlay {
     private boolean drawBackgrounds;
     private Color labelColor;
     private Font labelFont;
+    private boolean scalableLabels;
     private boolean isCalibrationBar;
+    private boolean selectable = true;
     
     /** Constructs an empty Overlay. */
     public Overlay() {
@@ -47,6 +49,14 @@ public class Overlay {
     		list.add(roi);
     }
 
+    /** Replaces the ROI at the specified index. */
+    public void set(Roi roi, int index) {
+    	if (index<0 || index>=list.size())
+    		throw new IllegalArgumentException("set: index out of range");
+    	if (roi!=null)
+    		list.set(index, roi);
+    }
+
     /** Removes the ROI with the specified index from this Overlay. */
     public void remove(int index) {
     	list.remove(index);
@@ -159,10 +169,11 @@ public class Overlay {
 	*/
 	public ResultsTable measure(ImagePlus imp) {
 		ResultsTable rt = new ResultsTable();
+		rt.showRowNumbers(true);
+		Analyzer analyzer = new Analyzer(imp, rt);
 		for (int i=0; i 1)
 				super.zoomIn(sx, sy);
@@ -97,16 +97,9 @@ public class PlotCanvas extends ImageCan
 				super.zoomOut(sx, sy);
 			return;
 		}
-		boolean mouseMoved = sqr(sx-lastZoomSX) + sqr(sy-lastZoomSY) > MAX_MOUSEMOVE_ZOOM*MAX_MOUSEMOVE_ZOOM;
-		if (mouseMoved)
-			plot.zoom(sx, sy, zoomFactor);	  //zoom on cursor coordinates
-		else
-			plot.zoom(Plot.ZOOM_AS_PREVIOUS, -Plot.ZOOM_AS_PREVIOUS, zoomFactor);	 //zoom on center
-		lastZoomSX = sx;
-		lastZoomSY = sy;
+		plot.zoom(sx, sy, zoomFactor);
 	}
 
-
 	/** Implements the Image/Zoom/Original Scale command.
 	 *	Sets the original range of the x, y axes (unless the plot is frozen) */
 	public void unzoom() {
diff -pruN 1.51q-1/ij/gui/PlotContentsStyleDialog.java 1.52g-1/ij/gui/PlotContentsStyleDialog.java
--- 1.51q-1/ij/gui/PlotContentsStyleDialog.java	2016-04-28 22:15:00.000000000 +0000
+++ 1.52g-1/ij/gui/PlotContentsStyleDialog.java	2018-02-01 19:09:10.000000000 +0000
@@ -30,6 +30,8 @@ public class PlotContentsStyleDialog imp
 		gd.addStringField("Secondary (fill) color:", "#########");
 		gd.addNumericField("Line width: ", 1.0, 1);
 		gd.addChoice("Symbol:", Plot.SORTED_SHAPES, Plot.SORTED_SHAPES[2]);
+		gd.setInsets(10, 60, 0);
+		gd.addCheckbox("Hidden", false);
 		gd.addDialogListener(this);
 		IJ.wait(100);	//needed to avoid hanging
 		updateDialog(gd, 0);	//fill in style for index 0
@@ -49,11 +51,12 @@ public class PlotContentsStyleDialog imp
 		String color2 = gd.getNextString();
 		double width = gd.getNextNumber();
 		String symbol = gd.getNextChoice();
+		Boolean hidden = gd.getNextBoolean();
 		Choice designationsC = (Choice)(gd.getChoices().get(0));
 		if (e.getSource() == designationsC)
 			updateDialog(gd, index);
 		else
-			plot.setPlotObjectStyles(index, color.trim()+","+color2.trim()+","+(float)width+","+symbol);
+			plot.setPlotObjectStyles(index, color.trim()+","+color2.trim()+","+(float)width+","+symbol+(hidden?",hidden":""));
 		return true;
 	}
 
@@ -65,6 +68,7 @@ public class PlotContentsStyleDialog imp
 		TextField color2F = (TextField)(stringFields.get(1));
 		TextField widthF = (TextField)(gd.getNumericFields().get(0));
 		Choice symbolC = (Choice)(choices.get(1));
+		Checkbox hiddenC = (Checkbox)gd.getCheckboxes().get(0);
 		String styleString = plot.getPlotObjectStyles(index);
 		String[] items = styleString.split(",");
 		//IJ.log(items.length+" items from "+allStyles[index]);
@@ -73,14 +77,20 @@ public class PlotContentsStyleDialog imp
 		widthF.setText(items[2]);
 		if (items.length >= 4)
 			symbolC.select(items[3]);
-		String designation = designationsC.getSelectedItem();
-		boolean isData = designation.startsWith("Data");
-		boolean isText = designation.startsWith("Text");
-		color2F.setEnabled(isData);	//only (some) data symbols have secondary color
-		widthF.setEnabled(!isText); //all non-Text types have line width
+		String designation = designationsC.getSelectedItem().toLowerCase();
+		boolean isData = designation.startsWith("data");
+		boolean isText = designation.startsWith("text");
+		boolean isBox = designation.contains("boxes") || designation.contains("rectangles");
+		boolean isGrid = designation.toLowerCase().contains("redraw_grid");
+		
+		colorF.setEnabled(!isGrid);	//
+		color2F.setEnabled(isData || isBox);	//only (some) data symbols have secondary color
+		widthF.setEnabled(!isText  && !isGrid); //all non-Text types have line width
+		hiddenC.setEnabled(!isGrid);//dont't allow  to hide at several places
 		symbolC.setEnabled(isData); //only data have a symbol to choose
+		hiddenC.setState(styleString.contains("hidden"));
 	}
-
+	
 	public void setNPasses(int nPasses) {
 	}
 
diff -pruN 1.51q-1/ij/gui/PlotDialog.java 1.52g-1/ij/gui/PlotDialog.java
--- 1.51q-1/ij/gui/PlotDialog.java	2017-09-13 11:13:46.000000000 +0000
+++ 1.52g-1/ij/gui/PlotDialog.java	2018-08-23 07:50:10.000000000 +0000
@@ -79,13 +79,14 @@ public class PlotDialog {
 				linYMin = Double.NaN;
 			double linYMax = gd.getNextNumber();
 			if (gd.invalidNumber())
-				linYMax = Double.NaN;
-
+				linYMax = Double.NaN;		
+			if (linXMin == linXMax || linYMin == linYMax)
+				return;		
 			currentMinMax[0] = linXMin;
 			currentMinMax[1] = linXMax;
 			currentMinMax[2] = linYMin;
 			currentMinMax[3] = linYMax;
-
+			
 			if (livePlot) plot.templateFlags = setFlag(plot.templateFlags, Plot.X_RANGE, gd.getNextBoolean());
 			boolean xLog = gd.getNextBoolean();
 			if (livePlot) plot.templateFlags = setFlag(plot.templateFlags, Plot.Y_RANGE, gd.getNextBoolean());
@@ -108,7 +109,6 @@ public class PlotDialog {
 					Recorder.recordString("Plot.setLimits("+IJ.d2s(linXMin,xDigits)+","+IJ.d2s(linXMax,xDigits)+","+IJ.d2s(linYMin,yDigits)+","+IJ.d2s(linYMax,yDigits)+");\n");
 				}
 			}
-		//n__ end PlotDialog
 		} else if (dialogType == AXIS_OPTIONS) {
 			int flags = plot.getFlags();
 			int columns = 2;
@@ -154,8 +154,15 @@ public class PlotDialog {
                 yLabel = plotYLabel; // suggest last dialog entry if default profile label
 			gd.addNumericField("Number Font Size", numberFont.getSize2D(), 1);
 			gd.addNumericField("Label Font Size", labelFont.getSize2D(), 1);
-			gd.addStringField("X Axis Label", xLabel, 15);
-			gd.addStringField("Y Axis Label", yLabel, 15);
+			String xMultiLineLabel = xLabel.replace("\n", "|");
+			int nChars = 15;
+			if(xLabel.startsWith("{") ||yLabel.startsWith("{" )){
+					nChars = Math.max(nChars, xLabel.length());
+					nChars = Math.max(nChars, yLabel.length());
+			}
+			gd.addStringField("X Axis Label", xMultiLineLabel, nChars);
+			String yMultiLineLabel = yLabel.replace("\n", "|");
+			gd.addStringField("Y Axis Label", yMultiLineLabel, nChars);
 			gd.setInsets(0, 20, 0); // no extra space
 			gd.addCheckbox("Bold Labels", plotFontSize>0 ? axisLabelBold : (labelFont.isBold()));
 			gd.showDialog();
@@ -173,24 +180,30 @@ public class PlotDialog {
 			if (numberFontSize > 24) numberFontSize = 24f;
 			float labelFontSize = (float)gd.getNextNumber();
 			if (gd.invalidNumber()) labelFontSize = labelFont.getSize2D();
-			xLabel = gd.getNextString();
-			yLabel = gd.getNextString();
+			xMultiLineLabel = gd.getNextString();
+			xLabel = xMultiLineLabel.replace("|", "\n");
+			yMultiLineLabel = gd.getNextString();
+			yLabel = yMultiLineLabel.replace("|", "\n");
 			axisLabelBold = gd.getNextBoolean();
 			plot.setFont('f', numberFont.deriveFont(numberFont.getStyle(), numberFontSize));
 			plot.setAxisLabelFont(axisLabelBold ? Font.BOLD : Font.PLAIN, labelFontSize);
 			plot.setXYLabels(xLabel, yLabel);
 			plot.updateImage();
 			if (Recorder.record) {
+				String xLabel2 = xLabel.replace("\n", "\\n");
+				String yLabel2 = yLabel.replace("\n", "\\n");
 				if (Recorder.scriptMode()) {
 					Recorder.recordCall("//plot = IJ.getImage().getProperty(Plot.PROPERTY_KEY)");
 					Recorder.recordCall("plot.setFont(-1,"+IJ.d2s(plotFontSize,1)+");");
 					Recorder.recordCall("plot.setAxisLabelFont(Plot."+(axisLabelBold ? "BOLD" : "PLAIN")+","+IJ.d2s(labelFontSize,1)+");");
-					Recorder.recordCall("plot.setXYLabels(\""+xLabel+"\", \""+yLabel+"\");");
+					//Recorder.recordCall("plot.setXYLabels(\""+xLabel+"\", \""+yLabel+"\");");
+					Recorder.recordCall("plot.setXYLabels(\""+xLabel2+"\", \""+yLabel2+"\");");
 					Recorder.recordCall("plot.setFormatFlags(0x"+Integer.toHexString(flags)+");");
 				} else {
 					Recorder.recordString("Plot.setFontSize("+IJ.d2s(plotFontSize,1)+");\n");
 					Recorder.recordString("Plot.setAxisLabelSize("+IJ.d2s(labelFontSize,1)+", \""+(axisLabelBold ? "bold" : "plain")+"\");\n");
-					Recorder.recordString("Plot.setXYLabels(\""+xLabel+"\", \""+yLabel+"\");\n");
+					//Recorder.recordString("Plot.setXYLabels(\""+xLabel+"\", \""+yLabel+"\");\n");
+					Recorder.recordString("Plot.setXYLabels(\""+xLabel2+"\", \""+yLabel2+"\");\n");
 					Recorder.recordString("Plot.setFormatFlags(\""+Integer.toString(flags,2)+"\");\n");
 				}
 			}
diff -pruN 1.51q-1/ij/gui/Plot.java 1.52g-1/ij/gui/Plot.java
--- 1.51q-1/ij/gui/Plot.java	2017-09-13 11:13:46.000000000 +0000
+++ 1.52g-1/ij/gui/Plot.java	2018-09-14 09:30:30.000000000 +0000
@@ -20,7 +20,7 @@ import ij.measure.ResultsTable;
  *
  * @author Wayne Rasband
  * @author Philippe CARL, CNRS, philippe.carl (AT) unistra.fr (log axes, arrows, ArrayList data)
- * @author Norbert Vischer (overlay range arrows and 'R'eset range, ...)
+ * @author Norbert Vischer (overlay range arrows, 'R'eset range, filled plots, dynamic plots, boxes and whiskers)
  * @author Michael Schmid (axis grid/ticks, resizing/panning/changing range, high-resolution, serialization)
  */
 public class Plot implements Cloneable {
@@ -53,13 +53,23 @@ public class Plot implements Cloneable {
 	public static final int DOT = 6;
 	/** Draw black lines between the dots and a circle with the given color at each dot */
 	public static final int CONNECTED_CIRCLES = 7;
+	/** Display points using an diamond-shaped mark. */
+	public static final int DIAMOND = 8;
+	/** Draw shape using macro code */
+	public static final int CUSTOM = 9;
+	/** Fill area between line plot and x-axis at y=0. */
+	public static final int FILLED = 10;
+	/** Draw a bar for each point. */
+	public static final int BAR = 11;
+	
 	/** Names for the shapes as an array */
 	final static String[] SHAPE_NAMES = new String[] {
-			"Circle", "X", "Line", "Box", "Triangle", "+", "Dot", "Connected Circles"};
+			"Circle", "X", "Line", "Box", "Triangle", "+", "Dot", "Connected Circles", "Diamond", "Custom", "Filled", "Bar"};
 	/** Names in nicely sorting order for menus */
 	final static String[] SORTED_SHAPES = new String[] {
-			SHAPE_NAMES[LINE], SHAPE_NAMES[CONNECTED_CIRCLES], SHAPE_NAMES[CIRCLE], SHAPE_NAMES[BOX], SHAPE_NAMES[TRIANGLE],
-			SHAPE_NAMES[CROSS], SHAPE_NAMES[X], SHAPE_NAMES[DOT] };
+			SHAPE_NAMES[LINE], SHAPE_NAMES[CONNECTED_CIRCLES], SHAPE_NAMES[FILLED], SHAPE_NAMES[BAR],
+			SHAPE_NAMES[CIRCLE], SHAPE_NAMES[BOX], SHAPE_NAMES[TRIANGLE], SHAPE_NAMES[CROSS],
+			SHAPE_NAMES[DIAMOND], SHAPE_NAMES[X], SHAPE_NAMES[DOT]};
 	/** flag for numeric labels of x-axis ticks */
 	public static final int X_NUMBERS = 0x1;
 	/** flag for numeric labels of x-axis ticks */
@@ -143,11 +153,10 @@ public class Plot implements Cloneable {
 	private static final int MAX_ARROWHEAD_LENGTH = 20;
 	private static Font	 DEFAULT_FONT = FontUtil.getFont("Arial", Font.PLAIN, PlotWindow.fontSize);
 
-	static final int ZOOM_AS_PREVIOUS = -20202020;	//when zooming at this coordinate, it rather uses the previous zoom center
-
 	PlotProperties pp = new PlotProperties();		//size, range, formatting etc, for easy serialization
 	Vector allPlotObjects = new Vector();	//all curves, labels etc., also serialized for saving/reading
-
+	private PlotVirtualStack stack;
+	private boolean grayscaleStack;
 	/** For high-resolution plots, everything will be scaled with this number. Otherwise, must be 1.0.
 	 *  (creating margins, saving PlotProperties etc only supports scale=1.0) */
 	float scale = 1.0f;
@@ -173,8 +182,6 @@ public class Plot implements Cloneable {
 	Font currentFont = defaultFont;					//font as changed by setFont or setFontSize, must never be null
 	private double xScale, yScale;					//pixels per data unit
 	private int xBasePxl, yBasePxl;					//pixel coordinates corresponding to 0
-	private double previousXZoom = Double.NaN;
-	private double previousYZoom = Double.NaN;
 	private int maxIntervals = 12;					//maximum number of intervals between ticks or grid lines
 	private int tickLength = 7;						//length of major ticks
 	private int minorTickLength = 3;				//length of minor ticks
@@ -191,44 +198,46 @@ public class Plot implements Cloneable {
 	float currentLineWidth;
 	private int currentJustification = LEFT;
 	private boolean ignoreForce2Grid;				// after explicit setting of range (limits), ignore 'FORCE2GRID' flags
-	//private boolean snapToMinorGrid;				// snap to grid when zooming to selection
+	//private boolean snapToMinorGrid;			// snap to grid when zooming to selection
+	private double barWidth=0.5;						// 0.1-1.0
+	private double barWidthInPixels;
 
-	/** Constructs a new Plot.
-	 *	Note that the data xValues, yValues passed with the constructor are plotted last,
-	 *	with the settings (color, lineWidth) at the time when 'draw' or 'getProcessor' is called.
-	 *	These data are plotted as a LINE.
+	/** Constructs a new Plot with the default options.
+	 * Use add(shape,xvalues,yvalues) to add curves.
 	 * @param title the window title
 	 * @param xLabel	the x-axis label
 	 * @param yLabel	the y-axis label
-	 * @param xValues	the x-coodinates, or null. If null and yValues is not null, integers starting at 0 will be used for x.
-	 * @param yValues	the y-coodinates, or null for providing no data yet.
+	 * @see #add(String,double[],double[])
+	 * @see #add(String,double[])
 	 */
-	public Plot(String title, String xLabel, String yLabel, float[] xValues, float[] yValues) {
-		this(title, xLabel, yLabel, xValues, yValues, getDefaultFlags());
-	}
-
-	/** This version of the constructor accepts double arrays. */
-	public Plot(String title, String xLabel, String yLabel, double[] xValues, double[] yValues) {
-		this(title, xLabel, yLabel, xValues!=null?Tools.toFloat(xValues):null, yValues!=null?Tools.toFloat(yValues):null, getDefaultFlags());
+	public Plot(String title, String xLabel, String yLabel) {
+		this(title, xLabel, yLabel, (float[])null, (float[])null, getDefaultFlags());
 	}
 
-	/** This is a constructor that works with JavaScript. */
-	public Plot(String dummy, String title, String xLabel, String yLabel, float[] xValues, float[] yValues) {
-		this(title, xLabel, yLabel, xValues, yValues, getDefaultFlags());
+	/** Obsolete, replaced by "new Plot(title,xLabel,yLabel); add(shape,x,y);".
+	 * @deprecated
+	*/
+	public Plot(String title, String xLabel, String yLabel, float[] x, float[] y) {
+		this(title, xLabel, yLabel, x, y, getDefaultFlags());
 	}
 
-	/** This is a version of the constructor with no intial arrays. */
-	public Plot(String title, String xLabel, String yLabel) {
-		this(title, xLabel, yLabel, (float[])null, (float[])null, getDefaultFlags());
+	/** Obsolete, replaced by "new Plot(title,xLabel,yLabel); add(shape,x,y);".
+	 * @deprecated
+	*/
+	public Plot(String title, String xLabel, String yLabel, double[] x, double[] y) {
+		this(title, xLabel, yLabel, x!=null?Tools.toFloat(x):null, y!=null?Tools.toFloat(y):null, getDefaultFlags());
 	}
 
-	/** This is a version of the constructor with no intial arrays. */
+	/** This version of the constructor has a 'flags' argument for
+		controlling whether ticks, grid, etc. are present and whether
+		the axes are logarithmic */
 	public Plot(String title, String xLabel, String yLabel, int flags) {
 		this(title, xLabel, yLabel, (float[])null, (float[])null, flags);
 	}
 
-	/** This version of the constructor has a 'flags' argument for
-		controlling whether ticks, grid, etc. are present and whether the axes are logarithmic */
+	/** Obsolete, replaced by "new Plot(title,xLabel,yLabel,flags); add(shape,x,y);".
+	 * @deprecated
+	*/
 	public Plot(String title, String xLabel, String yLabel, float[] xValues, float[] yValues, int flags) {
 		this.title = title;
 		pp.axisFlags = flags;
@@ -236,17 +245,31 @@ public class Plot implements Cloneable {
 		if (yValues != null && yValues.length>0) {
 			addPoints(xValues, yValues, /*yErrorBars=*/null, LINE, /*label=*/null);
 			allPlotObjects.get(0).flags = PlotObject.CONSTRUCTOR_DATA;
+		}			
+		String[] xCats = labelsInBraces(xLabel);
+		String[] yCats = labelsInBraces(yLabel);
+		if (xCats.length > 0){
+		    xMin = -0.5;
+		    xMax = xCats.length - 0.5;
+			draw();
+		}
+		if (yCats.length > 0){
+		    yMin = -0.5;
+		    yMax = yCats.length - 0.5;
+			draw();
 		}
 	}
 
-	/** This version of the constructor accepts double arrays and has a 'flags' argument. */
-	public Plot(String title, String xLabel, String yLabel, double[] xValues, double[] yValues, int flags) {
-		this(title, xLabel, yLabel, xValues!=null?Tools.toFloat(xValues):null, yValues!=null?Tools.toFloat(yValues):null, flags);
+	/** Obsolete, replaced by "new Plot(title,xLabel,yLabel,flags); add(shape,x,y);".
+	 * @deprecated
+	*/
+	public Plot(String title, String xLabel, String yLabel, double[] x, double[] y, int flags) {
+		this(title, xLabel, yLabel, x!=null?Tools.toFloat(x):null, y!=null?Tools.toFloat(y):null, flags);
 	}
 
 	/** Constructs a new plot from an InputStream and closes the stream. If the ImagePlus is
 	 *  non-null, its title and ImageProcessor are used, but the image displayed is not modified.
-	 *	@see toStream() */
+	*/
 	public Plot(ImagePlus imp, InputStream is) throws IOException, ClassNotFoundException {
 		ObjectInputStream in = new ObjectInputStream(is);
 		pp = (PlotProperties)in.readObject();
@@ -265,6 +288,13 @@ public class Plot implements Cloneable {
 		}
 	}
 
+	/** Obsolete, replaced by "new Plot(title,xLabel,yLabel); add(shape,x,y);".
+	 * @deprecated
+	*/
+	public Plot(String dummy, String title, String xLabel, String yLabel, float[] x, float[] y) {
+		this(title, xLabel, yLabel, x, y, getDefaultFlags());
+	}
+
 	/** Writes this plot into an OutputStream containing (1) the serialized PlotProperties and
 	 *	(2) the serialized Vector of all 'added' PlotObjects. The stream is NOT closed.
 	 *	The plot should have been drawn already.
@@ -322,6 +352,9 @@ public class Plot implements Cloneable {
 	 *  Accepts NaN values to indicate auto-range.
 	 */
 	public void setLimits(double xMin, double xMax, double yMin, double yMax) {
+		
+		String[] xCats = labelsInBraces(this.getLabel('x'));
+		String[] yCats = labelsInBraces(this.getLabel('y'));	
 		boolean containsNaN = (Double.isNaN(xMin + xMax + yMin + yMax));
 		if (containsNaN && allPlotObjects.isEmpty())//can't apply auto-range without data
 			return;
@@ -334,7 +367,7 @@ public class Plot implements Cloneable {
 				if (Double.isNaN(range[jj])) {
 					range[jj] = extrema[jj];
 					auto[jj] = true;
-				}
+				}		
 			double left = range[0];
 			double right = range[1];
 			double bottom = range[2];
@@ -386,7 +419,6 @@ public class Plot implements Cloneable {
 		if (plotDrawn)
 			setLimitsToDefaults(true);
 	}
-	//n__ end setLimits
 
 	/** Returns the current limits as an array xMin, xMax, yMin, yMax.
 	 *	Note that future versions might return a longer array (e.g. for y2 axis limits) */
@@ -626,11 +658,32 @@ public class Plot implements Cloneable {
 		return defaultFlags;
 	}
 
+	/** Adds a curve or set of points to this plot, where 'shape' is
+		"line", "filled", "bars, "circles", "boxes", "triangles", "crosses",
+		 "dots", "diamonds", "x" or "connected". */
+	public void add(String shape, double[] xvalues, double[] yvalues) {
+		int iShape = toShape(shape);
+		addPoints(Tools.toFloat(xvalues), Tools.toFloat(yvalues), null, iShape, iShape==CUSTOM?shape.substring(5, shape.length()):null);
+	}
+
+	/** Adds a curve, set of points or error bars to this plot, where 'shape' is
+		"line", "filled", "bars, "circles", "boxes", "triangles", "crosses", "dots",
+		"diamonds", "x", "connected", "error bars" or "xerror bars". */
+	public void add(String shape, double[] yvalues) {
+		int iShape = toShape(shape);
+		if (iShape==-1)
+			addErrorBars(yvalues);
+		else if (iShape==-2)
+			addHorizontalErrorBars(yvalues);
+		else
+			addPoints(null, Tools.toFloat(yvalues), null, iShape, iShape==CUSTOM?shape.substring(5, shape.length()):null);
+	}
+
 	/** Adds a set of points to the plot or adds a curve if shape is set to LINE.
 	 * @param xValues	the x coordinates, or null. If null, integers starting at 0 will be used for x.
 	 * @param yValues	the y coordinates (must not be null)
 	 * @param yErrorBars error bars in y, may be null
-	 * @param shape		CIRCLE, X, BOX, TRIANGLE, CROSS, DOT, LINE, CONNECTED_CIRCLES
+	 * @param shape		CIRCLE, X, BOX, TRIANGLE, CROSS, DIAMOND, DOT, LINE, CONNECTED_CIRCLES
 	 * @param label		Label for this curve or set of points, used for a legend and for listing the plots
 	 */
 	public void addPoints(float[] xValues, float[] yValues, float[] yErrorBars, int shape, String label) {
@@ -646,7 +699,7 @@ public class Plot implements Cloneable {
 	/** Adds a set of points to the plot or adds a curve if shape is set to LINE.
 	 * @param x			the x coordinates
 	 * @param y			the y coordinates
-	 * @param shape		CIRCLE, X, BOX, TRIANGLE, CROSS, DOT, LINE, CONNECTED_CIRCLES
+	 * @param shape		CIRCLE, X, BOX, TRIANGLE, CROSS, DIAMOND, DOT, LINE, CONNECTED_CIRCLES
 	 */
 	public void addPoints(float[] x, float[] y, int shape) {
 		addPoints(x, y, null, shape, null);
@@ -657,15 +710,6 @@ public class Plot implements Cloneable {
 		addPoints(Tools.toFloat(x), Tools.toFloat(y), shape);
 	}
 
-	/** This a version of addPoints that works with JavaScript. */
-	public void addPoints(String dummy, float[] x, float[] y, int shape) {
-		addPoints(x, y, shape);
-	}
-
-	public void add(String shape, double[] x, double[] y) {
-		addPoints(Tools.toFloat(x), Tools.toFloat(y), null, toShape(shape), null);
-	}
-
 	/** Returns the number for a given plot symbol shape, -1 for xError and -2 for yError (all case-insensitive) */
 	public static int toShape(String str) {
 		str = str.toLowerCase(Locale.US);
@@ -674,12 +718,16 @@ public class Plot implements Cloneable {
 			shape = Plot.LINE;
 		if (str.contains("connected"))
 			shape = Plot.CONNECTED_CIRCLES;
+		else if (str.contains("filled"))
+			shape = Plot.FILLED;
 		else if (str.contains("box"))
 			shape = Plot.BOX;
 		else if (str.contains("triangle"))
 			shape = Plot.TRIANGLE;
 		else if (str.contains("cross") || str.contains("+"))
 			shape = Plot.CROSS;
+		else if (str.contains("diamond"))
+			shape = Plot.DIAMOND;
 		else if (str.contains("dot"))
 			shape = Plot.DOT;
 		else if (str.contains("xerror"))
@@ -688,6 +736,10 @@ public class Plot implements Cloneable {
 			shape = -1;
 		else if (str.contains("x"))
 			shape = Plot.X;
+		else if (str.contains("bar"))
+			shape = Plot.BAR;
+		if (str.startsWith("code:"))
+			shape = CUSTOM;
 		return shape;
 	}
 
@@ -701,7 +753,7 @@ public class Plot implements Cloneable {
 	 * @param x			the x-coodinates
 	 * @param y			the y-coodinates
 	 * @param errorBars			the vertical error bars, may be null
-	 * @param shape		CIRCLE, X, BOX, TRIANGLE, CROSS, DOT or LINE
+	 * @param shape		CIRCLE, X, BOX, TRIANGLE, CROSS, DIAMOND, DOT or LINE
 	 */
 	public void addPoints(double[] x, double[] y, double[] errorBars, int shape) {
 		addPoints(Tools.toFloat(x), Tools.toFloat(y), Tools.toFloat(errorBars), shape, null);
@@ -731,6 +783,18 @@ public class Plot implements Cloneable {
 				Tools.toFloat(x2), Tools.toFloat(y2), currentLineWidth, currentColor));
 	}
 
+
+	/**
+	 * Adds a set of 'shapes' such as boxes and whiskers
+	 *
+	 * @param shapeType e.g. "boxes width=20"
+	 * @param floatCoords eg[6][3] holding 1 Xval + 5 Yvals for 3 boxes
+	 */
+	public void drawShapes(String shapeType, ArrayList floatCoords) {
+			allPlotObjects.add(new PlotObject(shapeType, floatCoords, currentLineWidth, currentColor, currentColor2));
+
+	}
+
 	public static double calculateDistance(int x1, int y1, int x2, int y2) {
 		return java.lang.Math.sqrt((x2 - x1)*(double)(x2 - x1) + (y2 - y1)*(double)(y2 - y1));
 	}
@@ -755,11 +819,6 @@ public class Plot implements Cloneable {
 		addErrorBars(Tools.toFloat(errorBars));
 	}
 
-	/** This is a version of addErrorBars that works with JavaScript. */
-	public void addErrorBars(String dummy, float[] errorBars) {
-		addErrorBars(errorBars);
-	}
-
 	/** Adds horizontal error bars to the last data passed to the plot (via the constructor or addPoints). */
 	public void addHorizontalErrorBars(double[] xErrorBars) {
 		PlotObject mainObject = getLastCurveObject();
@@ -829,8 +888,11 @@ public class Plot implements Cloneable {
 			int iPart = 0;
 			for (PlotObject plotObject : allPlotObjects)
 				if (plotObject.type == PlotObject.XY_DATA && !plotObject.hasFlag(PlotObject.HIDDEN))
-					if (iPart < allLabels.length)
-						plotObject.label = allLabels[iPart++];
+					if (iPart < allLabels.length) {
+						String label = allLabels[iPart++];
+						if (label!=null && label.length()>0)
+							plotObject.label = label;
+					}
 		}
 		pp.legend = new PlotObject(currentLineWidth == 0 ? 1 : currentLineWidth,
 				currentFont, currentColor == null ? Color.black : currentColor, flags);
@@ -853,7 +915,7 @@ public class Plot implements Cloneable {
 	}
 
 	public void setColor(String color) {
-		setColor(Colors.getColor(color, Color.black));
+		setColor(Colors.decode(color, Color.black));
 	}
 
 	/** Changes the drawing color for the next objects that will be added to the plot.
@@ -972,11 +1034,6 @@ public class Plot implements Cloneable {
 		pp.antialiasedText = antialiasedText;
 	}
 
-	/** Obsolete; replaced by setFont(). */
-	public void changeFont(Font font) {
-		setFont(font);
-	}
-
 	/** Gets the font for xLabel ('x'), yLabel('y'), numbers ('f' for 'frame') or the legend ('l').
 	 *	Returns null if the given PlotObject does not exist or its font is null */
 	Font getFont(char c) {
@@ -996,7 +1053,7 @@ public class Plot implements Cloneable {
 
 	/** Gets the label String of the xLabel ('x'), yLabel('y') or the legend ('l').
 	 *	Returns null if the given PlotObject does not exist or its label is null */
-	String getLabel(char c) {
+	public String getLabel(char c) {
 		PlotObject plotObject = pp.getPlotObject(c);
 		if (plotObject != null)
 			return plotObject.label;
@@ -1018,8 +1075,8 @@ public class Plot implements Cloneable {
 		return p==null ? null : p.yValues;
 	}
 
-	/** Get an array with human-readable designations of the non-hidden PlotObjects (curves, labels, ...)
-	 *	in the sequence they are plotted (i.e., foreground last). **/
+	/** Get an array with human-readable designations of the PlotObjects (curves, labels, ...)
+	 *	in the sequence they are plotted (i.e., foreground last). Hidden PlotObjects are included. **/
 	public String[] getPlotObjectDesignations() {
 		int nObjects = allPlotObjects.size();
 		String[] names = new String[nObjects];
@@ -1027,13 +1084,12 @@ public class Plot implements Cloneable {
 		String[] legendLabels = null;
 		if (pp.legend != null && pp.legend.label != null)
 			legendLabels = pp.legend.label.split("[\t\n]");
-		int iData = 1, iArrow = 1, iLine = 1, iText = 1;                //Human readable counters of each object type
+		int iData = 1, iArrow = 1, iLine = 1, iText = 1,  iBox = 1, iShape = 1; //Human readable counters of each object type
 		int firstObject = allPlotObjects.get(0).hasFlag(PlotObject.CONSTRUCTOR_DATA) ? 1 : 0; //PlotObject passed with constructor is plotted last
 		for (int i=0, p=firstObject; i= allPlotObjects.size())                             //the PlotObject passed with Constructor comes last
 				p = 0;
 			PlotObject plotObject = allPlotObjects.get(p);
-			if (plotObject.hasFlag(PlotObject.HIDDEN)) continue;
 			int type = plotObject.type;
 			String label = plotObject.label;
 			switch (type) {
@@ -1061,6 +1117,12 @@ public class Plot implements Cloneable {
 					names[i] = "Text "+iText+": \""+text+'"';
 					iText++;
 					break;
+				case PlotObject.SHAPES:
+					String s = plotObject.shapeType;
+					String[] words = s.split(" ");
+					names[i] = "Shapes (" + words[0] +") " + iShape;
+					iShape++;
+					break;
 			}
 		}
 		return names;
@@ -1111,7 +1173,7 @@ public class Plot implements Cloneable {
 		if (items.length >= 3) try {
 			plotObject.lineWidth = Float.parseFloat(items[2].trim());
 		} catch (NumberFormatException e) {};
-		if (items.length >= 4)
+		if (items.length >= 4 && plotObject.shape!=CUSTOM)
 			plotObject.shape = toShape(items[3].trim());
 		updateImage();
 		return;
@@ -1209,9 +1271,17 @@ public class Plot implements Cloneable {
 	}
 
 	/** Displays the plot in a PlotWindow and returns a reference to the PlotWindow.
-	 *  Note that the PlotWindow might get closed immediately if its 'listValues' and 'autoClose'
-	 *  flags are set */
+	 *  Plot stacks are shown in a StackWindow, however; in this case the return value is null.
+	 *  Also returns null in BatchMode. Note that the PlotWindow might get closed
+	 *  immediately if its 'listValues' and 'autoClose' flags are set.
+	 */
 	public PlotWindow show() {
+		PlotVirtualStack stack = getStack();
+		if (stack!=null && stack.size()>1) {
+			stack.setBitDepth(grayscaleStack?8:24);
+			new ImagePlus("Plot Stack",stack).show();
+			return null;
+		}
 		if ((IJ.macroRunning() && IJ.getInstance()==null) || Interpreter.isBatchMode()) {
 			imp = getImagePlus();
 			WindowManager.setTempCurrentImage(imp);
@@ -1235,7 +1305,33 @@ public class Plot implements Cloneable {
 			IJ.selectWindow(imp.getID());
 		return pw;
 	}
-
+	
+	/**
+	 * Appends the current plot to a virtual stack and resets allPlotObjects
+	 * for next slice 
+	 * N. Vischer
+	 */
+	public void addToStack() {
+		if (stack==null) {
+			stack = new PlotVirtualStack(getSize().width,getSize().height);
+			grayscaleStack = true;
+		}
+		draw();
+		stack.addPlot(this);
+		if (isColored())
+			grayscaleStack = false;
+		IJ.showStatus("addToStack: "+stack.size());
+		allPlotObjects.clear();
+	}
+	
+	public void appendToStack() { addToStack(); }
+	
+	/** Returns the virtual stack created by addToStack(). */
+	public PlotVirtualStack getStack() {
+		IJ.showStatus("");
+		return stack;
+	}
+	
 	/** Draws the plot specified for the first time. Does nothing if the plot has been drawn already.
 	 *	Call getProcessor to retrieve the ImageProcessor with it.
 	 *	Does no action with respect to the ImagePlus (if any) */
@@ -1568,10 +1664,10 @@ public class Plot implements Cloneable {
 		bottomMargin = sc(BOTTOM_MARGIN*marginScale);
 		//IJ.log("marginScale="+marginScale+" left margin="+leftMargin);
 	}
-
+	double[] steps;  //for redrawing the grid
 	/** Calculate the actual range, major step interval and set variables for data <-> pixels scaling */
 	double[] makeRangeGetSteps() {
-		double[] steps = new double[2];
+		steps = new double[2];
 		logXAxis = hasFlag(X_LOG_NUMBERS);
 		logYAxis = hasFlag(Y_LOG_NUMBERS);
 
@@ -1675,6 +1771,13 @@ public class Plot implements Cloneable {
 		return steps;
 	}
 
+
+	public void redrawGrid(){
+		if(ip != null){
+			drawAxesTicksGridNumbers(steps);
+			ip.setColor(Color.black);
+		}
+	}
 	void getInitialMinAndMax() {
 		int axisRangeFlags = 0;
 		if (Double.isNaN(defaultMinMax[0])) axisRangeFlags |= X_RANGE;
@@ -1696,7 +1799,7 @@ public class Plot implements Cloneable {
 				allMinMax[i] = defaultMinMax[i];
 		enlargeRange = new int[allMinMax.length];
 		for (PlotObject plotObject : allPlotObjects) {
-			if (plotObject.type == PlotObject.XY_DATA || plotObject.type == PlotObject.ARROWS) {
+			if ((plotObject.type == PlotObject.XY_DATA || plotObject.type == PlotObject.ARROWS) && !plotObject.hasFlag(PlotObject.HIDDEN)) {
 				getMinAndMax(allMinMax, enlargeRange, plotObject, axisRangeFlags);
 				if (!allObjects) break;
 			}
@@ -1725,6 +1828,11 @@ public class Plot implements Cloneable {
 				else if (plotObject.shape != LINE)
 					suggestedEnlarge = USUALLY_ENLARGE;
 				getMinAndMax(allMinAndMax, enlargeRange, suggestedEnlarge, 0, plotObject.xValues, plotObject.xEValues);
+				if (plotObject.shape == BAR && plotObject.xValues.length > 1) {
+					int n = plotObject.xValues.length;
+					allMinAndMax[0] -= 0.5 * Math.abs(plotObject.xValues[1] - plotObject.xValues[0]);
+					allMinAndMax[1] += 0.5 * Math.abs(plotObject.xValues[n - 1] - plotObject.xValues[n - 2]);
+				}
 			}
 			if ((axisRangeFlags & Y_RANGE) != 0) {
 				int suggestedEnlarge = 0;
@@ -1853,55 +1961,57 @@ public class Plot implements Cloneable {
 		updateImage();
 	}
 
-	/** Zooms in or out on a point x, y in screen coordinates. If x>0, default in both directions,
-	 *	if the cursor is below the x axis, only in x direction, if the cursor is left of the y axis, only in y direction.
-	 *	If x < 0, zooms on center; if x == ZOOM_AS_PREVIOUS, zooms on the center of the previous zoom
-	 *	operation */
-	void zoom(int x, int y, double zoomFactor) {
-		boolean zoomIn = zoomFactor > 1.0;
-		boolean zoomAsPrevious = x==ZOOM_AS_PREVIOUS && (!Double.isNaN(previousXZoom) || !Double.isNaN(previousYZoom));
-		if (!zoomAsPrevious) {
-			previousXZoom = Double.NaN;
-			previousYZoom = Double.NaN;
-			saveMinMax();
-		}
-		boolean cursorLeft = x >= 0 && xtopMargin+frameHeight+1;
-		boolean zoomX = (!cursorLeft && !zoomAsPrevious) || (!Double.isNaN(previousXZoom) && zoomAsPrevious);
-		boolean zoomY = cursorLeft || !cursorBottom || (!Double.isNaN(previousYZoom) && zoomAsPrevious);
-		if (cursorLeft && cursorBottom) // if cursor is in bottom left corner, zoom in y as if cursor was outside
-			x = -1;
-		//IJ.log("x,y="+x+","+y+" zx="+zoomX+" zy="+zoomY+" zPrev="+zoomAsPrevious);
-		for (int axisIndex = 0; axisIndex= 0) { //cursor inside? zoom on cursor
-				mid = axisIndex==0 ? descaleX(x) : descaleY(y);
-				if (logAxis) mid = Math.log10(mid);
-			}
-			if (axisIndex==0)
-				previousXZoom = logAxis ? Math.pow(10, mid) : mid;
-			else
-				previousYZoom = logAxis ? Math.pow(10, mid) : mid;
-			//IJ.log("d="+(axisIndex==0 ? "X":"Y")+" x,y="+x+","+y+" mid="+(float)mid);
-			double newHalfSpan = 0.5 * span / zoomFactor;
-			currentMinMax[axisIndex] = mid - newHalfSpan;
-			currentMinMax[axisIndex+1] = mid + newHalfSpan;
-			if (logAxis) {
-				currentMinMax[axisIndex] = Math.pow(10, currentMinMax[axisIndex]);
-				currentMinMax[axisIndex+1] = Math.pow(10, currentMinMax[axisIndex+1]);
-			}
+	/** 
+	 * Zooms in or out  active plots while keeping focus on cursor position
+	 * Above or below frame: zoom x only
+	 * Left or right of frame: zoom y only
+	 * Corners: focus is in center 
+	 *  N. Vischer
+	*/
+	void zoom(int x, int y, double zoomFactor) {		
+		boolean wasLogX = logXAxis;
+		boolean wasLogY = logYAxis;			
+		double plotX = descaleX(x);
+		double plotY = descaleY(y);
+		IJ.showStatus ("" + plotX);
+		boolean insideX = x > frame.x && x < frame.x + frame.width;
+		boolean insideY = y > frame.y && y < frame.y + frame.height;
+		if (!insideX && !insideY) {
+			insideX = true;
+			insideY = true;
+			x = frame.x + frame.width / 2;
+			y = frame.y + frame.height / 2;
+		}
+		int leftPart = x - frame.x;
+		int rightPart = frame.x + frame.width - x;
+		int highPart = y - frame.y;
+		int lowPart = frame.y + frame.height - y;
+
+		if (insideX) {
+			currentMinMax[0] = descaleX((int) (x - leftPart / zoomFactor));
+			currentMinMax[1] = descaleX((int) (x + rightPart / zoomFactor));
+		}
+		if (insideY) {
+			currentMinMax[2] = descaleY((int) (y + lowPart / zoomFactor));
+			currentMinMax[3] = descaleY((int) (y - highPart / zoomFactor));
 		}
 		updateImage();
+		if (wasLogX != logXAxis ){//log-lin was automatically changed
+			int changedX = (int) scaleXtoPxl(plotX);
+			int left = changedX - leftPart;
+			int right = changedX + rightPart;
+			currentMinMax[0] = descaleX(left);
+			currentMinMax[1] = descaleX(right);
+			updateImage();
+		}
+		if (wasLogY != logYAxis){//log-lin was automatically changed
+			int changedY = (int) scaleYtoPxl(plotY);
+			int bottom = changedY + lowPart;
+			int top = changedY + highPart;
+			currentMinMax[2] = descaleY(bottom);
+			currentMinMax[3] = descaleY(top);
+			updateImage();
+		}
 	}
 
 	/** Moves the plot range by a given number of pixels and updates the image */
@@ -1935,6 +2045,9 @@ public class Plot implements Cloneable {
 	/** Draws ticks, grid and axis label for each tick/grid line.
 	 *	The grid or major tick spacing in each direction is given by steps */
 	void drawAxesTicksGridNumbers(double[] steps) {
+		
+		String[] xCats = labelsInBraces(this.getLabel('x'));
+		String[] yCats = labelsInBraces(this.getLabel('y'));	
 		Font scFont = scFont(pp.frame.getFont());
 		Font scFontMedium = scFont.deriveFont(scFont.getSize2D()*10f/12f); //for axis numbers if full size does not fit
 		Font scFontSmall = scFont.deriveFont(scFont.getSize2D()*9f/12f);   //for subscripts
@@ -1948,6 +2061,7 @@ public class Plot implements Cloneable {
 			Font baseFont = scFont;
 			boolean majorTicks = logXAxis ? hasFlag(X_LOG_TICKS) : hasFlag(X_TICKS);
 			boolean minorTicks = hasFlag(X_MINOR_TICKS);
+			minorTicks = minorTicks && (xCats.length == 0);
 			double step = steps[0];
 			int i1 = (int)Math.ceil (Math.min(xMin, xMax)/step-1.e-10);
 			int i2 = (int)Math.floor(Math.max(xMin, xMax)/step+1.e-10);
@@ -1974,6 +2088,23 @@ public class Plot implements Cloneable {
 				for (int i=0; i<=(i2-i1); i++) {
 					double v = (i+i1)*step;
 					int x = (int)Math.round((v - xMin)*xScale) + leftMargin;
+					
+					if (xCats.length > 0) {										
+						int index = (int) v;
+						double remainder =  Math.abs(v - Math.round(v));
+						if(index >= 0 && index < xCats.length  && remainder < 1e-9){
+							String s = xCats[index];
+							String[] parts = s.split("\n");
+							int w = 0;
+							for(int jj = 0; jj < parts.length; jj++)
+								w = Math.max(w, ip.getStringWidth(parts[jj]));
+							
+							ip.drawString(s, x-w/2, yOfXAxisNumbers);
+							//ip.drawString(s, x-ip.getStringWidth(s)/2, yOfXAxisNumbers);
+						}		
+						continue;
+					}
+																	
 					if (hasFlag(X_GRID)) {
 						ip.setColor(gridColor);
 						ip.drawLine(x, y1, x, y2);
@@ -2035,6 +2166,7 @@ public class Plot implements Cloneable {
 			Font baseFont = scFont;
 			boolean majorTicks = logYAxis ? hasFlag(Y_LOG_TICKS) : hasFlag(Y_TICKS);
 			boolean minorTicks = logYAxis ? hasFlag(Y_LOG_TICKS) : hasFlag(Y_MINOR_TICKS);
+			minorTicks = minorTicks && (yCats.length == 0);
 			double step = steps[1];
 			int i1 = (int)Math.ceil (Math.min(yMin, yMax)/step-1.e-10);
 			int i2 = (int)Math.floor(Math.max(yMin, yMax)/step+1.e-10);
@@ -2067,6 +2199,22 @@ public class Plot implements Cloneable {
 				for (int i=i1; i<=i2; i++) {
 					double v = step==0 ? yMin : i*step;
 					int y = topMargin + frameHeight - (int)Math.round((v - yMin)*yScale);
+		
+					if(yCats.length > 0){												
+						int index = (int) v;
+						double remainder =  Math.abs(v - Math.round(v));
+						if(index >= 0 && index < yCats.length  && remainder < 1e-9){
+							String s = yCats[index];
+							int multiLineOffset = 0; // multi-line cat labels
+							for(int jj = 0; jj < s.length(); jj++)
+								if(s.charAt(jj) == '\n')
+									multiLineOffset -= rect.height/2;
+										
+							ip.drawString(s, xNumberRight, y+yNumberOffset+ multiLineOffset);
+						}		
+						continue;
+					}
+					
 					if (hasFlag(Y_GRID)) {
 						ip.setColor(gridColor);
 						ip.drawLine(x1, y, x2, y);
@@ -2155,14 +2303,27 @@ public class Plot implements Cloneable {
 		} else
 			y += sc(1);
 		// --- Write x and y axis text labels
-		ip.setFont(pp.xLabel.getFont() == null ? scFont : scFont(pp.xLabel.getFont()));
-		ip.drawString(xLabelToDraw, leftMargin+(frame.width-ip.getStringWidth(xLabelToDraw))/2, y+ip.getFontMetrics().getHeight());
-		if (yLabelToDraw.length() > 0) {
+		if(xCats.length == 0){
+			ip.setFont(pp.xLabel.getFont() == null ? scFont : scFont(pp.xLabel.getFont()));
+			ip.drawString(xLabelToDraw, leftMargin+(frame.width-ip.getStringWidth(xLabelToDraw))/2, y+ip.getFontMetrics().getHeight());
+		}
+		if (yCats.length == 0 && yLabelToDraw.length() > 0) {
 			int xRightOfYLabel = xNumberRight - maxNumWidth - sc(2);
 			Font yLabelFont = pp.yLabel.getFont() == null ? scFont : scFont(pp.yLabel.getFont());
 			drawYLabel(yLabelToDraw, xRightOfYLabel, topMargin, frame.height, yLabelFont);
 		}
 	}
+	
+	//returns array of labels
+	String[] labelsInBraces(String s) {
+		if (s.startsWith("{") && s.endsWith("}")) {
+			String inBraces = s.substring(1, s.length() - 1);
+			String[] catLabels = inBraces.split(",");
+			return catLabels;
+		} else {
+			return new String[0];
+		}
+	}
 
 	/** Returns the smallest "nice" number >= v. "Nice" numbers are .. 0.5, 1, 2, 5, 10, 20 ... */
 	double niceNumber(double v) {
@@ -2253,12 +2414,13 @@ public class Plot implements Cloneable {
 
 	private void drawPlotObject(PlotObject plotObject, ImageProcessor ip) {
 		//IJ.log("DRAWING type="+plotObject.type+" lineWidth="+plotObject.lineWidth+" shape="+plotObject.shape);
+		if (plotObject.hasFlag(PlotObject.HIDDEN)) return;
 		ip.setColor(plotObject.color);
 		ip.setLineWidth(sc(plotObject.lineWidth));
 		int type = plotObject.type;
 		switch (type) {
 			case PlotObject.XY_DATA:
-				if (plotObject.hasFlag(PlotObject.HIDDEN)) break;
+				barWidthInPixels = 0.0;
 				ip.setClipRect(frame);
 				if (plotObject.yEValues != null)
 					drawVerticalErrorBars(plotObject.xValues, plotObject.yValues, plotObject.yEValues);
@@ -2268,10 +2430,26 @@ public class Plot implements Cloneable {
 				boolean drawLine = plotObject.hasCurve();
 				if (plotObject.shape == CONNECTED_CIRCLES)
 					ip.setColor(plotObject.color2 == null ? Color.black : plotObject.color2);
-				if (drawLine)
-					//draw line
-					drawFloatPolyline(ip, plotObject.xValues, plotObject.yValues,
-							Math.min(plotObject.xValues.length, plotObject.yValues.length));
+				if (drawLine) {
+					int shortLen = Math.min(plotObject.xValues.length, plotObject.yValues.length);
+					if (plotObject.shape == FILLED) {
+						//ip.setColor(plotObject.color);
+						boolean twoColors = plotObject.color2 != null;
+						if (twoColors) {
+							ip.setColor(plotObject.color2);
+							ip.setLineWidth(1);
+						} else
+							ip.setColor(plotObject.color);
+						drawFloatPolyLineFilled(ip, plotObject.xValues, plotObject.yValues, shortLen);
+						if (twoColors){
+							ip.setColor(plotObject.color);
+							ip.setClipRect(frame);
+							ip.setLineWidth(sc(plotObject.lineWidth));
+							drawFloatPolyline(ip, plotObject.xValues, plotObject.yValues, shortLen);
+						}
+					} else
+					    drawFloatPolyline(ip, plotObject.xValues, plotObject.yValues, shortLen);
+				}
 				if (drawMarker) {
 					int markSize = plotObject.getMarkerSize();
 					if (plotObject.hasFilledMarker()) {
@@ -2279,16 +2457,25 @@ public class Plot implements Cloneable {
 						ip.setColor(plotObject.color2);
 						ip.setLineWidth(1);
 						for (int i=0; i0) && (!logYAxis || plotObject.yValues[i]>0))
+							if ((!logXAxis || plotObject.xValues[i]>0) && (!logYAxis || plotObject.yValues[i]>0)
+							&& !Double.isNaN(plotObject.xValues[i]) && !Double.isNaN(plotObject.yValues[i]))
 								fillShape(plotObject.shape, scaleX(plotObject.xValues[i]), scaleY(plotObject.yValues[i]), markSize);
 						ip.setLineWidth(sc(plotObject.lineWidth));
 					}
 					// draw markers
 					ip.setColor(plotObject.color);
-					for (int i=0; i0) && (!logYAxis || plotObject.yValues[i]>0))
-							drawShape(plotObject.shape, scaleX(plotObject.xValues[i]), scaleY(plotObject.yValues[i]), markSize);
+					plotObject.pointIndex = 0;
+					Font saveFont = ip.getFont();
+					for (int i=0; i0) && (!logYAxis || plotObject.yValues[i]>0)
+						&& !Double.isNaN(plotObject.xValues[i]) && !Double.isNaN(plotObject.yValues[i]))
+							drawShape(plotObject, scaleX(plotObject.xValues[i]), scaleY(plotObject.yValues[i]), markSize);
+					}
+					if (plotObject.shape==CUSTOM)
+						ip.setFont(saveFont);
 				}
+				if (plotObject.shape==BAR)
+					drawBarChart(plotObject);
 				ip.setClipRect(null);
 				break;
 			case PlotObject.ARROWS:
@@ -2312,6 +2499,115 @@ public class Plot implements Cloneable {
 				}
 				ip.setClipRect(null);
 				break;
+
+			case PlotObject.SHAPES:
+				int iBoxWidth = 20;
+				ip.setClipRect(frame);
+				String shType = plotObject.shapeType.toLowerCase();						
+				if (shType.contains("rectangles")) {
+					int nShapes = plotObject.shapeData.size();
+				
+				
+						for (int i = 0; i < nShapes; i++) {
+							float[] corners = (float[])(plotObject.shapeData.get(i));							
+							int x1 = scaleX(corners[0]);
+							int y1 = scaleY(corners[1]);
+							int x2 = scaleX(corners[2]);
+							int y2 = scaleY(corners[3]);
+						
+						ip.setLineWidth(sc(plotObject.lineWidth));
+							int left = Math.min(x1, x2);
+							int right = Math.max(x1, x2);
+							int top = Math.min(y1, y2);
+							int bottom = Math.max(y1, y2);
+							
+							Rectangle r1 = new Rectangle(left, top, right-left, bottom - top);
+							Rectangle cBox = frame.intersection(r1);
+							if (plotObject.color2 != null) {
+								ip.setColor(plotObject.color2);
+								ip.fillRect(cBox.x, cBox.y, cBox.width, cBox.height);
+							}
+							ip.setColor(plotObject.color);
+							ip.drawRect(cBox.x, cBox.y, cBox.width, cBox.height);						
+						}
+					ip.setClipRect(null);
+					break;
+				}
+				if (shType.equals("redraw_grid")) {
+				ip.setLineWidth(sc(1));
+					redrawGrid();
+					ip.setClipRect(null);
+					break;
+				}
+				if (shType.contains("boxes")) {
+
+					String[] parts = Tools.split(shType);
+					for (int jj = 0; jj < parts.length; jj++) {
+						String[] pairs = parts[jj].split("=");
+						if ((pairs.length == 2) && pairs[0].equals("width")) {
+							iBoxWidth = Integer.parseInt(pairs[1]);
+						}
+					}
+					boolean horizontal = shType.contains("boxesx");
+					int nShapes = plotObject.shapeData.size();
+					int halfWidth = Math.round(sc(iBoxWidth / 2));
+					for (int i = 0; i < nShapes; i++) {
+						
+						float[] coords = (float[])(plotObject.shapeData.get(i));
+						
+					
+					if (!horizontal) {
+					
+							int x = scaleX(coords[0]);
+							int y1 = scaleY(coords[1]);
+							int y2 = scaleY(coords[2]);
+							int y3 = scaleY(coords[3]);
+							int y4 = scaleY(coords[4]);
+							int y5 = scaleY(coords[5]);
+							ip.setLineWidth(sc(plotObject.lineWidth));
+
+							Rectangle r1 = new Rectangle(x - halfWidth, y4, halfWidth * 2, y2 - y4);
+							Rectangle cBox = frame.intersection(r1);
+							if (y1 != y2 || y4 != y5)//otherwise omit whiskers
+							{
+								ip.drawLine(x, y1, x, y5);//whiskers
+							}
+							if (plotObject.color2 != null) {
+								ip.setColor(plotObject.color2);
+								ip.fillRect(cBox.x, cBox.y, cBox.width, cBox.height);
+							}
+							ip.setColor(plotObject.color);
+							ip.drawRect(cBox.x, cBox.y, cBox.width, cBox.height);
+							ip.setClipRect(frame);
+							ip.drawLine(x - halfWidth, y3, x + halfWidth - 1, y3);
+						}
+					
+					if (horizontal) {
+						
+							int y = scaleY(coords[0]);
+							int x1 = scaleX(coords[1]);
+							int x2 = scaleX(coords[2]);
+							int x3 = scaleX(coords[3]);
+							int x4 = scaleX(coords[4]);
+							int x5 = scaleX(coords[5]);
+							ip.setLineWidth(sc(plotObject.lineWidth));
+							if(x1 !=x2 || x4 != x5)//otherwise omit whiskers
+								ip.drawLine(x1, y, x5, y);//whiskers
+							Rectangle r1 = new Rectangle(x2, y - halfWidth, x4 - x2, halfWidth * 2);
+							Rectangle cBox = frame.intersection(r1);
+							if (plotObject.color2 != null) {
+								ip.setColor(plotObject.color2);
+								ip.fillRect(cBox.x, cBox.y, cBox.width, cBox.height);
+							}
+							ip.setColor(plotObject.color);
+							ip.drawRect(cBox.x, cBox.y, cBox.width, cBox.height);
+							ip.setClipRect(frame);
+							ip.drawLine(x3, y - halfWidth, x3, y + halfWidth - 1);
+						}
+					}
+					ip.setClipRect(null);
+					break;
+				}
 			case PlotObject.LINE:
 				ip.setClipRect(frame);
 				ip.drawLine(scaleX(plotObject.x), scaleY(plotObject.y), scaleX(plotObject.xEnd), scaleY(plotObject.yEnd));
@@ -2352,9 +2648,55 @@ public class Plot implements Cloneable {
 				break;
 		}
 	}
-
+	
+	/** Draw a bar at each point */
+	void drawBarChart(PlotObject plotObject) {
+		String[] xCats = labelsInBraces(this.getLabel('x'));
+		plotObject.pointIndex = 0;
+		int n = Math.min(plotObject.xValues.length, plotObject.yValues.length);
+		int frameWidth = (int) Math.round(getDrawingFrame().width * xScale);
+		barWidthInPixels = n > 1 ? (plotObject.xValues[1] - plotObject.xValues[0]) * xScale : frameWidth;
+		int theWidth = (int) Math.round(barWidthInPixels);
+		int y0 = scaleY(0);
+		int prevY = y0;
+		if (xCats.length > 0) {
+			theWidth = (int) Math.round(theWidth * barWidth);
+		}
+		for (int bar = 0; bar < n; bar++) {
+			int x = scaleX(plotObject.xValues[bar]);
+			int y = scaleY(plotObject.yValues[bar]);
+			int left = x - theWidth / 2;
+			int right = left + theWidth;
+			if (plotObject.color2 != null) {
+				ip.setColor(plotObject.color2);
+				ip.setLineWidth(1);
+				for (int x2 = left; x2 <= right; x2++) {
+					ip.drawLine(x2, y0, x2, y);
+				}
+			}
+			ip.setColor(plotObject.color);
+			ip.setLineWidth(sc(plotObject.lineWidth));
+			if (xCats.length > 0) {
+				ip.drawLine(left, y0, left, y);//up
+				ip.drawLine(left, y, right, y);//right
+				ip.drawLine(right, y, right, y0);//down
+			}
+			if (xCats.length == 0) {
+				ip.drawLine(left, prevY, left, y);//up or down
+				ip.drawLine(left, y, right, y);//right
+				if (bar == n - 1) {
+					ip.drawLine(right, y, right, y0);//last down
+				}
+				prevY = y;
+			}
+		}
+		barWidthInPixels = 0.0;
+	}	
+	
 	/** Draw the symbols for data points */
-	void drawShape(int shape, int x, int y, int size) {
+	void drawShape(PlotObject plotObject, int x, int y, int size) {
+		int shape = plotObject.shape;
+		if (shape == DIAMOND) size = (int)(size*1.21);
 		int xbase = x-sc(size/2);
 		int ybase = y-sc(size/2);
 		int xend = x+sc(size/2);
@@ -2379,9 +2721,49 @@ public class Plot implements Cloneable {
 				ip.drawLine(xbase,y,xend,y);
 				ip.drawLine(x,ybase,x,yend);
 				break;
+			case DIAMOND:
+				ip.drawLine(xbase,y,x,ybase);
+				ip.drawLine(x,ybase,xend,y);
+				ip.drawLine(xend,y,x,yend);
+				ip.drawLine(x,yend,xbase,y);
+				break;
 			case DOT:
 				ip.drawDot(x, y); //uses current line width
 				break;
+			case CUSTOM:
+				if (plotObject.macroCode==null || frame==null)
+				    break;				
+				if (xframe.x+frame.width || y>frame.y+frame.height){
+					plotObject.pointIndex++;
+					break;
+				}
+				ImagePlus imp = new ImagePlus("", ip);
+				WindowManager.setTempCurrentImage(imp);
+				int index = plotObject.pointIndex++;				
+				StringBuilder sb = new StringBuilder(140+plotObject.macroCode.length());
+				sb.append("x="); sb.append(x);
+				sb.append(";y="); sb.append(y);
+				sb.append(";setColor("); sb.append(plotObject.color.getRGB());
+				sb.append(");s="); sb.append(sc(1));
+				sb.append(";i="); sb.append(index);				
+				boolean inRange = index < plotObject.xValues.length && index < plotObject.yValues.length;
+				double xVal =0;//when the symbol is needed for the legend, index is beyond range
+				double yVal =0;
+				if (inRange) {
+				    xVal = plotObject.xValues[index];	
+				    yVal = plotObject.yValues[index];	
+				}
+				sb.append(";xval=" + xVal);
+				sb.append(";yval=" + yVal);
+				sb.append(";");				
+				sb.append(plotObject.macroCode);
+				if (inRange ||!sb.toString().contains("d2s") ){//a graphical symbol won't contain "d2s" ..
+				    String rtn = IJ.runMacro(sb.toString());//.. so it can go to the legend
+				    if ("[aborted]".equals(rtn))
+					plotObject.macroCode = null;
+				}				
+				WindowManager.setTempCurrentImage(null);
+				break;
 			default: // CIRCLE, CONNECTED_CIRCLES: 5x5 oval approximated by 5x5 square without corners
 				if (sc(size) < 5.01) {
 					ip.drawLine(x-1, y-2, x+1, y-2);
@@ -2395,14 +2777,14 @@ public class Plot implements Cloneable {
 				break;
 		}
 	}
-
+	
 	/** Fill the area of the symbols for data points (except for shape=DOT)
 	 *	Note that ip.fill, ip.fillOval etc. can't be used here: they do not care about the clip rectangle */
 	void fillShape(int shape, int x0, int y0, int size) {
+		if (shape == DIAMOND) size = (int)(size*1.21);
 		int r = sc(size/2)-1;
 		switch(shape) {
 			case BOX:
-				int widthOrHeight = 2*sc(size/2);
 				for (int dy=-r; dy<=r; dy++)
 					for (int dx=-r; dx<=r; dx++)
 						ip.drawDot(x0+dx, y0+dy);
@@ -2418,6 +2800,17 @@ public class Plot implements Cloneable {
 						ip.drawDot(x,y);
 				}
 				break;
+			case DIAMOND:
+				ybase = y0 - r - sc(1);
+				yend = y0 + r;
+				halfWidth = sc(size/2)+sc(1)-1;
+				hwStep = halfWidth/(yend-ybase+1);
+				for (int y=yend; y>=ybase; y--) {
+					int dx = (int)(Math.round(halfWidth-(hwStep+1)*Math.abs(y-y0)));
+					for (int x=x0-dx; x<=x0+dx; x++)
+						ip.drawDot(x,y);
+				}
+				break;
 			case CIRCLE:
 				int rsquare = (r+1)*(r+1);
 				for (int dy=-r; dy<=r; dy++)
@@ -2496,6 +2889,52 @@ public class Plot implements Cloneable {
 		}
 	}
 
+	/**
+	 * Fills space between polyline and y=0 with secondary color.
+	 * author: Norbert Vischer
+	 */
+	void drawFloatPolyLineFilled(ImageProcessor ip, float[] xF, float[] yF, int len) {
+		if (xF == null || len == 0) {
+			return;
+		}
+		double[] xD = new double[len];
+		double[] yD = new double[len];
+		double minX = Double.MAX_VALUE;
+		double maxX = -Double.MAX_VALUE;
+		for (int i = 0; i < len; i++) {
+			xD[i] = xF[i];
+			yD[i] = yF[i];
+			if (xF[i] < minX) {
+				minX = xF[i];
+			}
+			if (xF[i] > maxX) {
+				maxX = xF[i];
+			}
+		}
+		double dx = maxX - minX;
+		int stretchedLen = (int) Math.abs((dx * xScale * 10) + 1);
+		double[] stretchedArrX = Tools.resampleArray(xD, stretchedLen);
+		double[] stretchedArrY = Tools.resampleArray(yD, stretchedLen);
+		int yZero = scaleY(0);
+		int prevX = 0;
+		if (logYAxis) {
+			yZero = 999999;
+		}
+		for (int i = 0; i < stretchedLen; i++) {
+			if (Double.isNaN(stretchedArrY[i])) {
+				continue;
+			}
+			int intX = scaleX(stretchedArrX[i]);
+			int intY = scaleY(stretchedArrY[i]);
+			if (intX != prevX) {//don't paint twice
+				ip.drawLine(intX, intY, intX, yZero);
+			}
+			prevX = intX;
+		}
+		ip.setLineWidth((int) scale);
+		ip.setColor(Color.black);
+	}
+
 	/** Vertical text for y axis label */
 	void drawYLabel(String yLabel, int xRight, int yFrameTop, int frameHeight, Font scaledFont) {
 		if (yLabel.equals(""))
@@ -2561,7 +3000,7 @@ public class Plot implements Cloneable {
 			for (int y=y0; y0.0) {
+				int index = (int)((x-scaleXtoPxl(p.xValues[0]))/barWidthInPixels);
+				if (index<0) index=0;
+				if (index>=p.xValues.length) index=p.xValues.length-1;
+				xv = p.xValues[index];
+				yv = p.yValues[index];
 			}
 		}
 		if (!Double.isNaN(xv)) {
-			text =	"X=" + IJ.d2s(xv, getDigits(xv, 0.001*(xMax-xMin), 6))+", Y";
+			int significantDigits = logXAxis ? -2 : getDigits(xv, 0.001*(xMax-xMin), 6);
+			text =	"X=" + IJ.d2s(xv, significantDigits)+", Y";
 			if (yIsValue) text += "(X)";
-			text +="="+ IJ.d2s(yv, getDigits(yv, 0.001*(yMax-yMin), 6));
+			significantDigits = logYAxis ? -2 : getDigits(yv, 0.001*(yMax-yMin), 6);
+			text +="="+ IJ.d2s(yv, significantDigits);
 		}
 		return text;
 		//}catch(Exception e){IJ.handleException(e);return "ERR";}
 	}
 
+
 	/** Returns a reference to the PlotObject having the data passed with the constructor or (if that was null)
 	 *	the first x & y data added later. Otherwise returns null. */
 	private PlotObject getMainCurveObject() {
@@ -2717,8 +3167,8 @@ public class Plot implements Cloneable {
 		return plotMaker;
 	}
 
-	/** Returns the labels of the datasets as linefeed-delimited String.
-	 *	If the label is not set, a blank line is added */
+	/** Returns the labels of the (non-hidden) datasets as linefeed-delimited String.
+	 *	If the label is not set, a blank line is added. */
 	String getDataLabels() {
 		String labels = "";
 		boolean first = true;
@@ -2745,7 +3195,6 @@ public class Plot implements Cloneable {
 	 */
 	public ResultsTable getResultsTable(boolean writeFirstXColumn) {
 		ResultsTable rt = new ResultsTable();
-		rt.showRowNumbers(false);
 		// find the longest x-value data set and count the data sets
 		int nDataSets =	 0;
 		int tableLength = 0;
@@ -2817,13 +3266,23 @@ public class Plot implements Cloneable {
 			int dataSetNumber, boolean writeX, boolean writeY, boolean multipleSets) {
 		if (writeX) {
 			String label = plotObject.type == PlotObject.ARROWS ? "XStart" : "X";
-			if (multipleSets) label += dataSetNumber;
+			if (multipleSets) label += dataSetNumber;			
+			if (dataSetNumber==0 && plotObject.type!=PlotObject.ARROWS) {
+				String plotXLabel = getLabel('x');
+				if (plotXLabel!=null && plotXLabel.startsWith(" ") && plotXLabel.endsWith(" "))
+					label = plotXLabel.substring(1,plotXLabel.length()-1);
+			}
 			headings.add(label);
 			data.add(plotObject.xValues);
 		}
 		if (writeY) {
 			String label = plotObject.type == PlotObject.ARROWS ? "YStart" : "Y";
 			if (multipleSets) label += dataSetNumber;
+			if (dataSetNumber==0 && plotObject.type!=PlotObject.ARROWS) {
+				String plotYLabel = getLabel('y');
+				if (plotYLabel!=null && plotYLabel.startsWith(" ") && plotYLabel.endsWith(" "))
+					label = plotYLabel.substring(1,plotYLabel.length()-1);
+			}
 			headings.add(label);
 			data.add(plotObject.yValues);
 		}
@@ -2873,6 +3332,104 @@ public class Plot implements Cloneable {
 	boolean hasFlag(int what) {
 		return (pp.axisFlags&what) != 0;
 	}
+	
+	/* Obsolete, replaced by add(shape,x,y). */
+	public void addPoints(String dummy, float[] x, float[] y, int shape) {
+		addPoints(x, y, shape);
+	}
+	
+	/** Plots a histogram from an array using auto-binning.
+	 *  @param values	array containing the population
+	 *  N.Vischer
+	 */
+	public void addHistogram(double[] values) {
+		addHistogram(values, 0, 0);
+	}
+
+	/** Plots a histogram from an array using the specified bin width.
+	 *  @param values	array containing the population
+	 *  @param binWidth	set zero for auto-binning
+	 *  N.Vischer
+	 */
+	public void addHistogram(double[] values, double binWidth) {
+		addHistogram(values, binWidth, 0);
+	}
+
+	/** Plots a histogram from an array
+	 *  @param values	array containing the population
+	 *  @param binWidth	set zero for auto-binning
+	 *  @param binCenter any x value can be the center of a bin
+	 *  N.Vischer
+	 */
+	public void addHistogram(double[] values, double binWidth, double binCenter) {
+		int len = values.length;
+		double min = Double.POSITIVE_INFINITY;
+		double max = Double.NEGATIVE_INFINITY;
+		double[] cleanVals = new double[len];
+		int count = 0;
+		double sum = 0, sum2 = 0;
+		for (int i = 0; i < len; i++) {
+			double val = values[i];
+			if (!Double.isNaN(val)) {
+				cleanVals[count++] = val;
+				sum += val;
+				sum2 += val * val;
+				if (val < min)
+					min = val;
+				if (val > max)
+					max = val;
+			}
+		}
+		if (binWidth <= 0) {//autobinning
+			double stdDev = Math.sqrt(((count * sum2 - sum * sum) / count) / count);//not count - 1
+			// use Scott's method (1979 Biometrika, 66:605-610) for optimal binning: 3.49*sd*N^-1/3
+			binWidth = 3.49 * stdDev * (Math.pow(count, -1.0 / 3));
+
+		}		
+		double modCenter = binCenter % binWidth;
+		double modMin = min % binWidth;
+		double diff = modMin - modCenter;
+		double firstBin = min-diff;
+		while(firstBin  - binWidth * 0.499 > min)
+			firstBin -= binWidth;		
+		int nBins =  (int) ((max - firstBin)/binWidth);
+		double lastBin = firstBin + nBins * binWidth;		
+		while(lastBin  + binWidth * 0.499 < max)
+			lastBin += binWidth;
+		nBins = (int) Math.round((lastBin - firstBin)/binWidth) + 1;
+		if (nBins == 1)
+			nBins = 2;
+		if (nBins > 9999) {
+			IJ.error("max bins > 9999");
+			return;
+		}
+		double[] histo = new double[nBins];
+		double[] xValues = new double[nBins];
+		for (int i = 0; i < nBins; i++)
+			xValues[i] = firstBin + i * binWidth;
+		for (int i = 0; i < count; i++) {
+			double val = cleanVals[i];
+			double indexD = (val - firstBin) / binWidth;
+			int index = (int) Math.round(indexD);
+			if (index < 0 || index >= nBins) {
+			    IJ.error("index out of range");
+			    return;
+			} else
+			    histo[index]++;
+		}
+		add("bar", xValues, histo);
+	}
+		
+	/* Obsolete, replaced by add("error bars",errorBars). */
+	public void addErrorBars(String dummy, float[] errorBars) {
+		addErrorBars(errorBars);
+	}
+	
+	/* Obsolete; replaced by setFont(). */
+	public void changeFont(Font font) {
+		setFont(font);
+	}
+
 }
 
 /** This class contains the properties of the plot, such as size, format, range, etc, except for the data+format (plot contents) */
@@ -2916,7 +3473,7 @@ class PlotProperties implements Cloneabl
 		}
 	}
 
-}
+} // class PlotProperties
 
 /** This class contains the data and properties for displaying a curve, a set of arrows, a line or a label in a plot,
  *	as well as the legend, axis labels, and frame (including background and fonts of axis numbering).
@@ -2927,7 +3484,7 @@ class PlotObject implements Cloneable, S
 	static final long serialVersionUID = 1L;
 	/** constants for the type of objects */
 	public final static int XY_DATA = 0, ARROWS = 1, LINE = 2, NORMALIZED_LINE = 3, DOTTED_LINE = 4,
-			LABEL = 5, NORMALIZED_LABEL = 6, LEGEND = 7, AXIS_LABEL = 8, FRAME = 9;
+			LABEL = 5, NORMALIZED_LABEL = 6, LEGEND = 7, AXIS_LABEL = 8, FRAME = 9, SHAPES = 10;
 	/** mask for recovering font style from the flags */
 	final static int FONT_STYLE_MASK = 0x0f;
 	/** flag for the data set passed with the constructor. Note that 0 to 0x0f are reserved for fonts modifiers, 0x010-0x800 are reserved for legend modifiers */
@@ -2941,6 +3498,9 @@ class PlotObject implements Cloneable, S
 	/** The x and y data arrays and the error bars (if non-null). These arrays also serve as x0, y0, x1, y1
 	 *	arrays for plotting arrays of arrows */
 	public float[] xValues, yValues, xEValues, yEValues;
+	/** For Shapes such as boxplots */
+	public ArrayList shapeData;
+	public String shapeType;//e.g. "boxes width=20"	
 	/** Type of the points, such as Plot.LINE, Plot.CROSS etc. (for type = XY_DATA) */
 	public int shape;
 	/** The line width in pixels for 'small' plots */
@@ -2959,6 +3519,10 @@ class PlotObject implements Cloneable, S
 	public String label;
 	/** Labels only: Justification can be Plot.LEFT, Plot.CENTER or Plot.RIGHT */
 	public int justification;
+	/** Macro code for drawing symbols */
+	public String macroCode;
+	/** Index passed to macro code that draws symbols*/
+	public int pointIndex;
 	/** Text objects (labels, legend, axis labels) only: the font; maybe null for default. This is not serialized (transient) */
 	private transient Font font;
 	/** String for representation of the font family (for Serialization); may be null for default. Font style is in flags, font size in fontSize. */
@@ -2966,6 +3530,7 @@ class PlotObject implements Cloneable, S
 	/** Font size (for Serialization) */
 	private float fontSize;
 
+
 	/** Generic constructor */
 	PlotObject(int type) {
 		this.type = type;
@@ -2982,6 +3547,8 @@ class PlotObject implements Cloneable, S
 		this.color = color;
 		this.color2 = color2;
 		this.label = yLabel;
+		if (shape==Plot.CUSTOM)
+			this.macroCode = yLabel;
 	}
 
 	/** Constructor for a set of arrows */
@@ -2995,6 +3562,16 @@ class PlotObject implements Cloneable, S
 		this.color = color;
 	}
 
+	/** Constructor for a set of shapes */
+	PlotObject(String shapeType, ArrayList shapeData, float lineWidth,  Color color, Color color2) {
+		this.type = SHAPES;
+		this.shapeData = shapeData;
+		this.shapeType = shapeType;
+		this.lineWidth = lineWidth;
+		this.color = color;
+		this.color2 = color2;
+	}
+
 	/** Constructor for a line */
 	PlotObject(double x, double y, double xEnd, double yEnd, float lineWidth, int step, Color color, int type) {
 		this.type = type;
@@ -3052,18 +3629,19 @@ class PlotObject implements Cloneable, S
 
 	/** Whether an XY_DATA object has a curve to draw */
 	boolean hasCurve() {
-		return type == XY_DATA && (shape == Plot.LINE || shape == Plot.CONNECTED_CIRCLES);
+		return type == XY_DATA && (shape == Plot.LINE || shape == Plot.CONNECTED_CIRCLES || shape == Plot.FILLED);
 	}
 
 	/** Whether an XY_DATA object has markers to draw */
 	boolean hasMarker() {
-		return type == XY_DATA && (shape == Plot.CIRCLE || shape == Plot.X || shape == Plot.BOX || shape == Plot.TRIANGLE ||
-				shape == Plot.CROSS || shape == Plot.DOT || shape == Plot.CONNECTED_CIRCLES);
+		return type == XY_DATA && (shape == Plot.CIRCLE || shape == Plot.X || shape == Plot.BOX || shape == Plot.TRIANGLE
+				|| shape == Plot.CROSS || shape == Plot.DIAMOND || shape == Plot.DOT || shape == Plot.CONNECTED_CIRCLES
+				|| shape == Plot.CUSTOM);
 	}
 
 	/** Whether an XY_DATA object has markers that can be filled */
 	boolean hasFilledMarker() {
-		return type == XY_DATA && color2 != null && (shape == Plot.CIRCLE || shape == Plot.BOX || shape == Plot.TRIANGLE);
+		return type == XY_DATA && color2 != null && (shape == Plot.CIRCLE || shape == Plot.BOX || shape == Plot.TRIANGLE || shape == Plot.DIAMOND);
 	}
 
 	/** Size of the markers for an XY_DATA object with markers */
@@ -3099,4 +3677,5 @@ class PlotObject implements Cloneable, S
 			return null;
 		}
 	}
-}
+	
+} // class PlotObject 
\ No newline at end of file
diff -pruN 1.51q-1/ij/gui/PlotVirtualStack.java 1.52g-1/ij/gui/PlotVirtualStack.java
--- 1.51q-1/ij/gui/PlotVirtualStack.java	1970-01-01 00:00:00.000000000 +0000
+++ 1.52g-1/ij/gui/PlotVirtualStack.java	2018-01-26 19:00:20.000000000 +0000
@@ -0,0 +1,83 @@
+package ij.gui;
+import ij.*;
+import ij.process.*;
+import java.util.*;
+import java.io.*;
+
+/** This is a virtual stack of frozen plots. */
+public class PlotVirtualStack extends VirtualStack {
+	private Vector plots = new Vector(50);
+	private int bitDepth = 24;
+	
+	public PlotVirtualStack(int width, int height) {
+		super(width, height);
+		this.bitDepth = bitDepth;
+	}
+	
+	/** Adds a plot to the end of the stack. */
+	public void addPlot(Plot plot) {
+		plots.add(plot.toByteArray());
+	}
+	   
+   /** Returns the pixel array for the specified slice, were 1<=n<=nslices. */
+	public Object getPixels(int n) {
+		ImageProcessor ip = getProcessor(n);
+		if (ip!=null)
+			return ip.getPixels();
+		else
+			return null;
+	}		
+	
+	/** Returns an ImageProcessor for the specified slice,
+		were 1<=n<=nslices. Returns null if the stack is empty. */
+	public ImageProcessor getProcessor(int n) {
+		byte[] bytes = (byte[])plots.get(n-1);
+		if (bytes!=null) {
+			try {
+				Plot plot = new Plot(null, new ByteArrayInputStream(bytes));
+				ImageProcessor ip = plot.getProcessor();
+				if (bitDepth==24)
+					ip = ip.convertToRGB();
+				else if (bitDepth==8)
+					ip =  ip.convertToByte(false);
+				return ip;
+			} catch (Exception e) {
+				IJ.handleException(e);
+			}
+		}
+		return null;
+	}
+	 
+	 /** Returns the number of slices in this stack. */
+	public int getSize() {
+		return plots.size();
+	}
+		
+	/** Returns either 24 (RGB) or 8 (grayscale). */
+	public int getBitDepth() {
+		return bitDepth;
+	}
+		
+	public void setBitDepth(int bitDepth) {
+		this.bitDepth = bitDepth;
+	}
+
+	public String getSliceLabel(int n) {
+		return null;
+	}
+
+	public void setPixels(Object pixels, int n) {
+	}
+	
+	/** Deletes the specified slice, were 1<=n<=nslices. */
+	public void deleteSlice(int n) {
+		if (n<1 || n>plots.size())
+			throw new IllegalArgumentException("Argument out of range: "+n);
+		if (plots.size()<1)
+			return;			
+		plots.remove(n-1);
+	}
+
+
+} // PlotVirtualStack
+
diff -pruN 1.51q-1/ij/gui/PlotWindow.java 1.52g-1/ij/gui/PlotWindow.java
--- 1.51q-1/ij/gui/PlotWindow.java	2017-09-13 11:13:46.000000000 +0000
+++ 1.52g-1/ij/gui/PlotWindow.java	2018-02-21 11:58:12.000000000 +0000
@@ -42,8 +42,8 @@ public class PlotWindow extends ImageWin
 	/** Interpolate line profiles. To set, use Edit/Options/Plots. */
 	public static boolean interpolate;
 	// default values for new installations; values will be then saved in prefs
-	private static final int WIDTH = 450;
-	private static final int HEIGHT = 200;
+	private static final int WIDTH = 530;
+	private static final int HEIGHT = 300;
 	private static final int FONT_SIZE = 12;
 	/** The width of the plot (without frame) in pixels. */
 	public static int plotWidth = WIDTH;
@@ -550,6 +550,7 @@ public class PlotWindow extends ImageWin
 	/** Shows the data of the backing plot in a Textwindow with columns */
 	void showList(){
 		ResultsTable rt = plot.getResultsTable(saveXValues);
+		if (rt==null) return;
 		rt.show("Plot Values");
 		if (autoClose) {
 			imp.changes=false;
diff -pruN 1.51q-1/ij/gui/PointRoi.java 1.52g-1/ij/gui/PointRoi.java
--- 1.51q-1/ij/gui/PointRoi.java	2017-08-30 18:54:02.000000000 +0000
+++ 1.52g-1/ij/gui/PointRoi.java	2018-07-03 15:41:36.000000000 +0000
@@ -44,12 +44,20 @@ public class PointRoi extends PolygonRoi
 	private ResultsTable rt;
 	private long lastPointTime;
 	private int[] counterInfo;
+	private boolean promptBeforeDeleting;
+	private boolean promptBeforeDeletingCalled;
+	private int nMarkers;
 	
 	static {
 		setDefaultType((int)Prefs.get(TYPE_KEY, HYBRID));
 		setDefaultSize((int)Prefs.get(SIZE_KEY, 1));
 	}
 	
+	public PointRoi() {
+		this(0.0, 0.0);
+		deletePoint(0);
+	}
+	
 	/** Creates a new PointRoi using the specified int arrays of offscreen coordinates. */
 	public PointRoi(int[] ox, int[] oy, int points) {
 		super(itof(ox), itof(oy), points, POINT);
@@ -81,12 +89,14 @@ public class PointRoi extends PolygonRoi
 	public PointRoi(int ox, int oy) {
 		super(makeXArray(ox, null), makeYArray(oy, null), 1, POINT);
 		width=1; height=1;
+		incrementCounter(null);
 	}
 
 	/** Creates a new PointRoi using the specified offscreen double coordinates. */
 	public PointRoi(double ox, double oy) {
 		super(makeXArray(ox, null), makeYArray(oy, null), 1, POINT);
 		width=1; height=1;
+		incrementCounter(null);
 	}
 
 	/** Creates a new PointRoi using the specified screen coordinates. */
@@ -272,6 +282,12 @@ public class PointRoi extends PolygonRoi
 		width+=1; height+=1;
 	}
 	
+
+	public void addUserPoint(ImagePlus imp, double ox, double oy) {
+		addPoint(imp, ox, oy);
+		nMarkers++;
+	}
+
 	private void addPoint2(ImagePlus imp, double ox, double oy) {
 		double xbase = getXBase();
 		double ybase = getYBase();
@@ -496,6 +512,26 @@ public class PointRoi extends PolygonRoi
 		return counter;
 	}
 
+	public int getNCounters() {
+		int n = 0;
+		for (int counter=0; counter0) n++;
+		}
+		return n;
+	}
+	
+	public boolean promptBeforeDeleting() {
+	    if (promptBeforeDeletingCalled)
+	    	return promptBeforeDeleting;
+	    else
+			return nMarkers>10  && imp!=null && imp.getWindow()!=null;
+	} 
+
+	public void promptBeforeDeleting(Boolean prompt) {
+		promptBeforeDeleting = prompt;
+		promptBeforeDeletingCalled = true;
+	}
+
 	public static void setDefaultCounter(int counter) {
 		defaultCounter = counter;
 	}
@@ -516,11 +552,13 @@ public class PointRoi extends PolygonRoi
 	}
 
 	public int[] getCounters() {
-		if (counters==null)
+		if (nPoints>65535)
 			return null;
 		int[] temp = new int[nPoints];
-		for (int i=0; i1?imp.getCurrentSlice():0;
+		if (Prefs.showAllPoints)
+			slice = 0;
+		for (int i=0; i=0) {
+			deletePoint(pointToDelete);
+			if (splineFit) 
+				fitSpline(splinePoints);
+			imp.draw();
+		}
 	}
 	
 	protected void deletePoint(int index) {
@@ -824,6 +826,8 @@ public class PolygonRoi extends Roi {
 		modState = NO_MODS;
 		if (previousRoi!=null) previousRoi.modState = NO_MODS;
 		int pointToDuplicate = getClosestPoint(ox, oy, points);
+		if (pointToDuplicate<0)
+			return;
 		FloatPolygon points2 = new FloatPolygon();
 		for (int i2=0; i2=maxPoints)
+			enlargeArrays();
+		float xbase = (float)getXBase();
+		float ybase = (float)getYBase();
+		for (int i=0; i0) {
-			Polygon p = rois[0].getPolygon();
-			ImageProcessor ip = imp.getProcessor();
-			for (int i=0; ipixels)
 			rectWidth = pixels/3;
 		setDrawOffset(false);
@@ -38,8 +39,8 @@ public class RotatedRectRoi extends Poly
 		if (!overlay && ic!=null) {
 			double mag = ic.getMagnification();
 		    int size2 = HANDLE_SIZE/2;
-			for (int i=0; i<4; i++){
-			if (i==3)//mark starting point
+			for (int i=0; i<4; i++) {
+			if (i==3) //mark starting point
 				handleColor = strokeColor!=null?strokeColor:ROIColor;
 			else
 				handleColor=Color.white;
diff -pruN 1.51q-1/ij/gui/ShapeRoi.java 1.52g-1/ij/gui/ShapeRoi.java
--- 1.51q-1/ij/gui/ShapeRoi.java	2017-09-18 19:14:14.000000000 +0000
+++ 1.52g-1/ij/gui/ShapeRoi.java	2018-07-11 19:57:46.000000000 +0000
@@ -1171,4 +1171,9 @@ public class ShapeRoi extends Roi {
 			return super.getFloatPolygon();
 	}
 
+	/** If this ROI consists of a single polygon, retuns the number of vertices, otherwise returns 4. */
+	public int size() {
+		return getPolygon().npoints;
+	}
+
 }
diff -pruN 1.51q-1/ij/gui/TextRoi.java 1.52g-1/ij/gui/TextRoi.java
--- 1.51q-1/ij/gui/TextRoi.java	2016-10-25 19:53:56.000000000 +0000
+++ 1.52g-1/ij/gui/TextRoi.java	2018-08-19 12:47:08.000000000 +0000
@@ -94,12 +94,6 @@ public class TextRoi extends Roi {
 		init(text, font);
 	}
 
-	/** Creates a TextRoi using the specified location, size and Font.
-	public TextRoi(int x, int y, int width, int height, String text, Font font) {
-		super(x, y, width, height);
-		init(text, font);
-	}
-
 	/** Creates a TextRoi using the specified sub-pixel location, size and Font. */
 	public TextRoi(double x, double y, double width, double height, String text, Font font) {
 		super(x, y, width, height);
@@ -282,16 +276,17 @@ public class TextRoi extends Roi {
 		double heightd = bounds!=null?bounds.height:height;
 		int widthi = (int)Math.round(widthd);
 		int heighti = (int)Math.round(heightd);
-		int sx = nonScalable?xi:screenXD(getXBase());
-		int sy = nonScalable?yi:screenYD(getYBase());
-		int sw = nonScalable?widthi:(int)(getMagnification()*widthd);
-		int sh = nonScalable?heighti:(int)(getMagnification()*heightd);
 		Font font = getScaledFont();
 		FontMetrics metrics = g.getFontMetrics(font);
 		int fontHeight = metrics.getHeight();
 		int descent = metrics.getDescent();
 		g.setFont(font);
 		Graphics2D g2d = (Graphics2D)g;
+		updateBounds(g);
+		int sx = nonScalable?xi:screenXD(getXBase());
+		int sy = nonScalable?yi:screenYD(getYBase());
+		int sw = nonScalable?widthi:(int)(getMagnification()*widthd);
+		int sh = nonScalable?heighti:(int)(getMagnification()*heightd);
 		AffineTransform at = null;
 		if (angle!=0.0) {
 			at = g2d.getTransform();
@@ -305,7 +300,6 @@ public class TextRoi extends Roi {
 		}
 		int i = 0;
 		if (fillColor!=null) {
-			updateBounds(g);
 			Color c = g.getColor();
 			int alpha = fillColor.getAlpha();
  			g.setColor(fillColor);
@@ -411,6 +405,8 @@ public class TextRoi extends Roi {
 		if (justification<0 || justification>RIGHT)
 			justification = LEFT;
 		this.justification = justification;
+		if (imp!=null)
+			imp.draw();
 	}
 	
 	/** Returns the value of the 'justification' instance variable (LEFT, CENTER or RIGHT). */
diff -pruN 1.51q-1/ij/gui/Toolbar.java 1.52g-1/ij/gui/Toolbar.java
--- 1.51q-1/ij/gui/Toolbar.java	2017-07-06 10:56:00.000000000 +0000
+++ 1.52g-1/ij/gui/Toolbar.java	2017-11-24 12:11:50.000000000 +0000
@@ -778,8 +778,10 @@ public class Toolbar extends Canvas impl
 		}
 		if (legacyMode)
 			repaint();
-		if (!previousName.equals(getToolName()))
-			IJ.notifyEventListeners(IJEventListener.TOOL_CHANGED);
+		if (!previousName.equals(getToolName())) {
+			IJ.notifyEventListeners(IJEventListener.TOOL_CHANGED);;
+			repaint();
+		}
 	}
 	
 	boolean isValidTool(int tool) {
@@ -1102,7 +1104,7 @@ public class Toolbar extends Canvas impl
 	}
 	
 	void showSwitchPopupMenu(MouseEvent e) {
-		String path = IJ.getDirectory("macros")+"toolsets/";
+		String path = IJ.getDir("macros")+"toolsets/";
 		if (path==null)
 			return;
 		boolean applet = IJ.getApplet()!=null;
@@ -1114,8 +1116,12 @@ public class Toolbar extends Canvas impl
 		} else
 			list = new String[0];
 		switchPopup.removeAll();
-        path = IJ.getDirectory("macros") + "StartupMacros.txt";
+        path = IJ.getDir("macros") + "StartupMacros.txt";
 		f = new File(path);
+		if (!f.exists()) {
+			path = IJ.getDir("macros") + "StartupMacros.ijm";
+			f = new File(path);
+		}
 		if (!applet && f.exists())
             addItem("Startup Macros");
         else
@@ -1347,9 +1353,9 @@ public class Toolbar extends Canvas impl
                 	installStartupMacros();
                 	return;
                 } else if (label.endsWith(" "))
-                    path = IJ.getDirectory("macros")+"toolsets"+File.separator+label.substring(0, label.length()-1)+".ijm";
+                    path = IJ.getDir("macros")+"toolsets"+File.separator+label.substring(0, label.length()-1)+".ijm";
                 else
-                    path = IJ.getDirectory("macros")+"toolsets"+File.separator+label+".txt";
+                    path = IJ.getDir("macros")+"toolsets"+File.separator+label+".txt";
                 try {
                     if (IJ.shiftKeyDown()) {
                         IJ.open(path);
@@ -1396,16 +1402,19 @@ public class Toolbar extends Canvas impl
 
 	private 	void installStartupMacros() {
 		resetTools();
-		String path = IJ.getDirectory("macros")+"StartupMacros.txt";
+		String path = IJ.getDir("macros")+"StartupMacros.txt";
 		File f = new File(path);
 		if (!f.exists()) {
-			String path2 = IJ.getDirectory("macros")+"StartupMacros.fiji.ijm";
-			f = new File(path2);
-			if (!f.exists()) {
-				IJ.error("StartupMacros not found:\n \n"+path);
-				return;
-			} else
-				path = path2;
+			path = IJ.getDir("macros")+"StartupMacros.ijm";
+			f = new File(path);
+		}
+		if (!f.exists()) {
+			path = IJ.getDir("macros")+"StartupMacros.fiji.ijm";
+			f = new File(path);
+		}
+		if (!f.exists()) {
+			IJ.error("StartupMacros not found in\n \n"+IJ.getDir("macros"));
+			return;
 		}
 		if (IJ.shiftKeyDown()) {
 			IJ.open(path);
@@ -1723,10 +1732,10 @@ public class Toolbar extends Canvas impl
 	
 	// install tool from ImageJ/macros/toolsets
 	private boolean installToolsetTool(String name) {
-		String path = IJ.getDirectory("macros")+"toolsets"+File.separator+name+".ijm";
+		String path = IJ.getDir("macros")+"toolsets"+File.separator+name+".ijm";
 		if (!((new File(path)).exists())) {
 			name = name.replaceAll(" ", "_");
-			path = IJ.getDirectory("macros")+"toolsets"+File.separator+name+".ijm";
+			path = IJ.getDir("macros")+"toolsets"+File.separator+name+".ijm";
 		}
 		String text = IJ.openAsString(path);
 		if (text==null || text.startsWith("Error"))
diff -pruN 1.51q-1/ij/gui/WaitForUserDialog.java 1.52g-1/ij/gui/WaitForUserDialog.java
--- 1.51q-1/ij/gui/WaitForUserDialog.java	2015-10-10 23:01:08.000000000 +0000
+++ 1.52g-1/ij/gui/WaitForUserDialog.java	2018-06-22 21:35:04.000000000 +0000
@@ -46,8 +46,7 @@ public class WaitForUserDialog extends D
 			GUI.center(this);
 		else
 			setLocation(xloc, yloc);
-		if (IJ.isJava16())
-			setAlwaysOnTop(true);
+		setAlwaysOnTop(true);
 	}
 	
 	public WaitForUserDialog(String text) {
diff -pruN 1.51q-1/ij/gui/YesNoCancelDialog.java 1.52g-1/ij/gui/YesNoCancelDialog.java
--- 1.51q-1/ij/gui/YesNoCancelDialog.java	2017-01-28 11:17:58.000000000 +0000
+++ 1.52g-1/ij/gui/YesNoCancelDialog.java	2018-06-27 16:53:04.000000000 +0000
@@ -20,7 +20,7 @@ public class YesNoCancelDialog extends D
 		Panel panel = new Panel();
 		panel.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 10));
 		MultiLineLabel message = new MultiLineLabel(msg);
-		message.setFont(new Font("Dialog", Font.PLAIN, 12));
+		message.setFont(new Font("Dialog", Font.PLAIN, 14));
 		panel.add(message);
 		add("North", panel);
 		
diff -pruN 1.51q-1/ij/IJ.java 1.52g-1/ij/IJ.java
--- 1.51q-1/ij/IJ.java	2017-09-18 18:55:32.000000000 +0000
+++ 1.52g-1/ij/IJ.java	2018-09-05 19:14:04.000000000 +0000
@@ -49,7 +49,8 @@ public class IJ {
 	private static ProgressBar progressBar;
 	private static TextPanel textPanel;
 	private static String osname, osarch;
-	private static boolean isMac, isWin, isJava16, isJava17, isJava18, isJava19, isLinux, is64Bit;
+	private static boolean isMac, isWin, isLinux, is64Bit;
+	private static int javaVersion;
 	private static boolean controlDown, altDown, spaceDown, shiftDown;
 	private static boolean macroRunning;
 	private static Thread previousThread;
@@ -75,13 +76,23 @@ public class IJ {
 		isWin = osname.startsWith("Windows");
 		isMac = !isWin && osname.startsWith("Mac");
 		isLinux = osname.startsWith("Linux");
-		String version = System.getProperty("java.version").substring(0,3);
-		if (version.compareTo("2.9")<=0) {  // JVM on Sharp Zaurus PDA claims to be "3.1"!
-			isJava16 = version.compareTo("1.5")>0;
-			isJava17 = version.compareTo("1.6")>0;
-			isJava18 = version.compareTo("1.7")>0;
-			isJava19 = version.compareTo("1.8")>0;
-		}
+		String version = System.getProperty("java.version");
+		if (version.startsWith("1.8"))
+			javaVersion = 8;
+		else if (version.startsWith("1.6"))
+			javaVersion = 6;
+		else if (version.startsWith("1.9")||version.startsWith("9"))
+			javaVersion = 9;
+		else if (version.startsWith("10"))
+			javaVersion = 10;
+		else if (version.startsWith("11"))
+			javaVersion = 11;
+		else if (version.startsWith("12"))
+			javaVersion = 12;
+		else if (version.startsWith("1.7"))
+			javaVersion = 7;
+		else
+			javaVersion = 6;
 		dfs = new DecimalFormatSymbols(Locale.US);
 		df = new DecimalFormat[10];
 		df[0] = new DecimalFormat("0", dfs);
@@ -144,8 +155,6 @@ public class IJ {
 		Returns any string value returned by the macro, or null. Scripts always return null.
 		The equivalent macro function is runMacro(). */
 	public static String runMacroFile(String name, String arg) {
-		if (ij==null && Menus.getCommands()==null)
-			init();
 		Macro_Runner mr = new Macro_Runner();
 		return mr.runMacroFile(name, arg);
 	}
@@ -426,8 +435,17 @@ public class IJ {
 		if (logPanel!=null) {
 			if (s.startsWith("\\"))
 				handleLogCommand(s);
-			else
+			else {
+				if (s.endsWith("\n")) {
+					if (s.equals("\n\n"))
+						s= "\n \n ";
+					else if (s.endsWith("\n\n"))
+						s = s.substring(0, s.length()-2)+"\n \n ";
+					else
+						s = s+" ";
+				}
 				logPanel.append(s);
+			}
 		} else {
 			LogStream.redirectSystem(false);
 			System.out.println(s);
@@ -501,7 +519,7 @@ public class IJ {
 		Frame frame = WindowManager.getFrontWindow();
 		if (frame!=null && (frame instanceof TextWindow)) {
 			TextWindow tw = (TextWindow)frame;
-			if (tw.getTextPanel().getResultsTable()==null) {
+			if (tw.getResultsTable()==null) {
 				IJ.error("Rename", "\""+tw.getTitle()+"\" is not a results table");
 				return;
 			}
@@ -513,7 +531,7 @@ public class IJ {
 		}
 	}
 
-	/** Changes the name of a results window from 'oldTitle' to 'newTitle'. */
+	/** Changes the name of a table window from 'oldTitle' to 'newTitle'. */
 	public static void renameResults(String oldTitle, String newTitle) {
 		Frame frame = WindowManager.getFrame(oldTitle);
 		if (frame==null) {
@@ -521,22 +539,20 @@ public class IJ {
 			return;
 		} else if (frame instanceof TextWindow) {
 			TextWindow tw = (TextWindow)frame;
-			if (tw.getTextPanel().getResultsTable()==null) {
-				error("Rename", "\""+oldTitle+"\" is not a results table");
+			if (tw.getResultsTable()==null) {
+				error("Rename", "\""+oldTitle+"\" is not a table");
 				return;
 			}
 			tw.rename(newTitle);
 		} else
-			error("Rename", "\""+oldTitle+"\" is not a results table");
+			error("Rename", "\""+oldTitle+"\" is not a table");
 	}
 
-	/** Deletes 'row1' through 'row2' of the "Results" window. Arguments
-	     must be in the range 0-Analyzer.getCounter()-1. */
+	/** Deletes 'row1' through 'row2' of the "Results" window, where
+		'row1' and 'row2' must be in the range 0-Analyzer.getCounter()-1. */
 	public static void deleteRows(int row1, int row2) {
-		int n = row2 - row1 + 1;
 		ResultsTable rt = Analyzer.getResultsTable();
-		for (int i=row1; i= 6;
 	}
 
 	/** Returns true if ImageJ is running on a Java 1.7 or greater JVM. */
 	public static boolean isJava17() {
-		return isJava17;
+		return javaVersion >= 7;
 	}
 
 	/** Returns true if ImageJ is running on a Java 1.8 or greater JVM. */
 	public static boolean isJava18() {
-		return isJava18;
+		return javaVersion >= 8;
 	}
 
 	/** Returns true if ImageJ is running on a Java 1.9 or greater JVM. */
 	public static boolean isJava19() {
-		return isJava19;
+		return javaVersion >= 9;
 	}
 
 	/** Returns true if ImageJ is running on Linux. */
@@ -1151,6 +1172,16 @@ public class IJ {
 			img.setRoi(new PointRoi(x, y));
 	}
 
+	/** Creates an Roi. */
+	public static Roi Roi(double x, double y, double width, double height) {
+		return new Roi(x, y, width, height);
+	}
+
+	/** Creates an OvalRoi. */
+	public static OvalRoi OvalRoi(double x, double y, double width, double height) {
+		return new OvalRoi(x, y, width, height);
+	}
+
 	/** Sets the display range (minimum and maximum displayed pixel values) of the current image. */
 	public static void setMinAndMax(double min, double max) {
 		setMinAndMax(getImage(), min, max, 7);
@@ -1327,8 +1358,11 @@ public class IJ {
 			if (impC!=null && impC!=imp && impT!=null)
 				impC.saveRoi();
             WindowManager.setTempCurrentImage(imp);
+            Interpreter.activateImage(imp);
             WindowManager.setWindow(null);
 		} else {
+			if (imp==null)
+				return;
 			ImageWindow win = imp.getWindow();
 			if (win!=null) {
 				win.toFront();
@@ -1605,14 +1639,22 @@ public class IJ {
 		else if (title2.equals("temp")) {
 			String dir = System.getProperty("java.io.tmpdir");
 			if (isMacintosh()) dir = "/tmp/";
-			if (dir!=null && !dir.endsWith(File.separator)) dir += File.separator;
+			if (dir!=null && !dir.endsWith(File.separator))
+				dir += File.separator;
 			return dir;
 		} else if (title2.equals("image")) {
 			ImagePlus imp = WindowManager.getCurrentImage();
 	    	FileInfo fi = imp!=null?imp.getOriginalFileInfo():null;
-			if (fi!=null && fi.directory!=null)
-				return fi.directory;
-			else
+			if (fi!=null && fi.directory!=null) {
+				String dir = fi.directory;
+				if (dir!=null && !(dir.endsWith(File.separator)||dir.endsWith("/"))) {
+					if (IJ.isWindows()&&dir.contains(File.separator))
+						dir += File.separator;
+					else
+						dir += "/";
+				}
+				return dir;
+			} else
 				return null;
 		} else {
 			DirectoryChooser dc = new DirectoryChooser(title);
@@ -1621,7 +1663,7 @@ public class IJ {
 			return dir;
 		}
 	}
-	
+		
 	/** Alias for getDirectory(). */
 	public static String getDir(String title) {
 		return getDirectory(title);
@@ -1677,7 +1719,10 @@ public class IJ {
 		Use IJ.open() to display a file open dialog box.
 	*/
 	public static ImagePlus openImage(String path) {
-		return (new Opener()).openImage(path);
+		macroRunning = true;
+		ImagePlus imp = (new Opener()).openImage(path);
+		macroRunning = false;
+		return imp;
 	}
 
 	/** Opens the nth image of the specified tiff stack. */
@@ -1816,15 +1861,20 @@ public class IJ {
 		if (path!=null && path.length()==0)
 			path = null;
 		format = format.toLowerCase(Locale.US);
+		Roi roi2 = imp!=null?imp.getRoi():null;
+		if (roi2!=null)
+			roi2.endPaste();
 		if (format.indexOf("tif")!=-1) {
 			saveAsTiff(imp, path);
 			return;
-		} else if (format.indexOf("jpeg")!=-1  || format.indexOf("jpg")!=-1) {
+		} else if (format.indexOf("jpeg")!=-1 || format.indexOf("jpg")!=-1) {
 			path = updateExtension(path, ".jpg");
-			format = "Jpeg...";
+			JpegWriter.save(imp, path, FileSaver.getJpegQuality());
+			return;
 		} else if (format.indexOf("gif")!=-1) {
 			path = updateExtension(path, ".gif");
-			format = "Gif...";
+			GifWriter.save(imp, path);
+			return;
 		} else if (format.indexOf("text image")!=-1) {
 			path = updateExtension(path, ".txt");
 			format = "Text Image...";
@@ -1869,11 +1919,9 @@ public class IJ {
 		if (path==null)
 			run(format);
 		else {
-			if (path.contains(" ")) {
-				if (path.contains("]"))
-					error("ImageJ cannot save when file path contains both \" \" and \"]\"");
+			if (path.contains(" "))
 				run(imp, format, "save=["+path+"]");
-			} else
+			else
 				run(imp, format, "save="+path);
 		}
 	}
@@ -2010,8 +2058,8 @@ public class IJ {
 			options = NewImage.FILL_BLACK;
 		else if (type.contains("ramp"))
 			options = NewImage.FILL_RAMP;
-		else if (type.contains("random"))
-			options = NewImage.FILL_RANDOM;
+		else if (type.contains("noise") || type.contains("random"))
+			options = NewImage.FILL_NOISE;
 		options += NewImage.CHECK_AVAILABLE_MEMORY;
 		return NewImage.createImage(title, width, height, depth, bitDepth, options);
 	}
@@ -2165,7 +2213,7 @@ public class IJ {
 		ArrayList list = new ArrayList();
 		Hashtable commands = Menus.getCommands();
 		Menu lutsMenu = Menus.getImageJMenu("Image>Lookup Tables");
-		if (lutsMenu==null)
+		if (commands==null || lutsMenu==null)
 			return new String[0];
 		for (int i=0; i108) {
+				if (IJ.isWindows() && (size.height>108||IJ.javaVersion()>=10)) {
 					// workaround for IJ window layout and FileDialog freeze problems with Windows 10 Creators Update
 					IJ.wait(10);
 					pack();
@@ -216,24 +215,21 @@ public class ImageJ extends Frame implem
 			IJ.runPlugIn("ij.plugin.ClassChecker", "");
 		}
 		if (IJ.isMacintosh()&&applet==null) { 
-			Object qh = null; 
-			qh = IJ.runPlugIn("MacAdapter", ""); 
+			Object qh = null;
+			try {
+				qh = IJ.runPlugIn("MacAdapter", ""); 
+			} catch(Throwable e) {}
 			if (qh==null) 
 				IJ.runPlugIn("QuitHandler", ""); 
 		} 
 		if (applet==null)
 			IJ.runPlugIn("ij.plugin.DragAndDrop", "");
 		String str = m.getMacroCount()==1?" macro":" macros";
-		IJ.showStatus(version()+ m.getPluginCount() + " commands; " + m.getMacroCount() + str);
 		configureProxy();
 		if (applet==null)
 			loadCursors();
- 	}
- 	
- 	private void runStartupMacro() {
- 		String macro = (new Startup()).getStartupMacro();
- 		if (macro!=null && macro.length()>4)
- 			new MacroRunner(macro);
+		(new ij.macro.StartupRunner()).run(batchMode); // run RunAtStartup and AutoRun macros
+		IJ.showStatus(version()+ m.getPluginCount() + " commands; " + m.getMacroCount() + str);
  	}
  	
  	private void loadCursors() {
@@ -373,7 +369,7 @@ public class ImageJ extends Frame implem
 		MenuItem item = (MenuItem)e.getSource();
 		MenuComponent parent = (MenuComponent)item.getParent();
 		String cmd = e.getItem().toString();
-		if ("Autorun".equals(cmd)) // Examples>Autorun
+		if ("Autorun Examples".equals(cmd)) // Examples>Autorun Examples
 			Prefs.autoRunExamples = e.getStateChange()==1;
 		else if ((Menu)parent==Menus.window)
 			WindowManager.activateWindow(cmd, item);
@@ -422,10 +418,16 @@ public class ImageJ extends Frame implem
 		ImagePlus imp = WindowManager.getCurrentImage();
 		boolean isStack = (imp!=null) && (imp.getStackSize()>1);
 		
-		if (imp!=null && !control && ((keyChar>=32 && keyChar<=255) || keyChar=='\b' || keyChar=='\n')) {
+		if (imp!=null && ((keyChar>=32 && keyChar<=255) || keyChar=='\b' || keyChar=='\n')) {
 			Roi roi = imp.getRoi();
-			if (roi instanceof TextRoi) {
-				if ((flags & KeyEvent.META_MASK)!=0 && IJ.isMacOSX()) return;
+			if (roi!=null && roi instanceof TextRoi) {
+				if (imp.getOverlay()!=null && (control || alt || meta)
+				&& (keyCode==KeyEvent.VK_BACK_SPACE || keyCode==KeyEvent.VK_DELETE)) {
+					if (deleteOverlayRoi(imp))
+							return;
+				}
+				if ((flags & KeyEvent.META_MASK)!=0 && IJ.isMacOSX())
+					return;
 				if (alt) {
 					switch (keyChar) {
 						case 'u': case 'm': keyChar = IJ.micronSymbol; break;
@@ -447,7 +449,6 @@ public class ImageJ extends Frame implem
 				else
 					cmd = (String)macroShortcuts.get(new Integer(keyCode));
 				if (cmd!=null) {
-					//MacroInstaller.runMacroCommand(cmd);
 					commandName = cmd;
 					MacroInstaller.runMacroShortcut(cmd);
 					return;
@@ -455,7 +456,13 @@ public class ImageJ extends Frame implem
 			}
 		}
 
-		if ((!Prefs.requireControlKey || control || meta) && keyChar!='+') {
+		if (keyCode==KeyEvent.VK_SEPARATOR)
+			keyCode = KeyEvent.VK_DECIMAL;
+		boolean functionKey = keyCode>=KeyEvent.VK_F1 && keyCode<=KeyEvent.VK_F12;
+		boolean numPad = keyCode==KeyEvent.VK_DIVIDE || keyCode==KeyEvent.VK_MULTIPLY
+			|| keyCode==KeyEvent.VK_DECIMAL
+			|| (keyCode>=KeyEvent.VK_NUMPAD0 && keyCode<=KeyEvent.VK_NUMPAD9);			
+		if ((!Prefs.requireControlKey||control||meta||functionKey||numPad) && keyChar!='+') {
 			Hashtable shortcuts = Menus.getShortcuts();
 			if (shift)
 				cmd = (String)shortcuts.get(new Integer(keyCode+200));
@@ -476,15 +483,17 @@ public class ImageJ extends Frame implem
 
 		if (cmd==null) {
 			switch (keyCode) {
-				case KeyEvent.VK_TAB: WindowManager.putBehind(); return;
-				case KeyEvent.VK_BACK_SPACE: // delete
-					if (deleteOverlayRoi(imp))
-						return;
-					if (imp!=null&&imp.getOverlay()!=null&&imp==GelAnalyzer.getGelImage())
-						return;
-					cmd="Clear";
-					hotkey=true;
-					break; 
+				case KeyEvent.VK_TAB: WindowManager.putBehind(); return;				
+				case KeyEvent.VK_BACK_SPACE: case KeyEvent.VK_DELETE:
+					if (!(shift||control||alt||meta)) {
+						if (deleteOverlayRoi(imp))
+							return;
+						if (imp!=null&&imp.getOverlay()!=null&&imp==GelAnalyzer.getGelImage())
+							return;
+						cmd="Clear";
+						hotkey=true;
+					}
+					break;
 				//case KeyEvent.VK_BACK_SLASH: cmd=IJ.altKeyDown()?"Animation Options...":"Start Animation"; break;
 				case KeyEvent.VK_EQUALS: cmd="In [+]"; break;
 				case KeyEvent.VK_MINUS: cmd="Out [-]"; break;
@@ -678,37 +687,33 @@ public class ImageJ extends Frame implem
 	}
 
 	public static void main(String args[]) {
-		if (System.getProperty("java.version").substring(0,3).compareTo("1.5")<0) {
-			javax.swing.JOptionPane.showMessageDialog(null,"ImageJ "+VERSION+" requires Java 1.5 or later.");
-			System.exit(0);
-		}
 		boolean noGUI = false;
 		int mode = STANDALONE;
 		arguments = args;
-		//System.setProperty("file.encoding", "UTF-8");
 		int nArgs = args!=null?args.length:0;
 		boolean commandLine = false;
 		for (int i=0; i0 && DEFAULT_PORT+delta<65536)
-						port = DEFAULT_PORT+delta;
-				}
+			if (arg.startsWith("-batch")) {
+				noGUI = true;
+				batchMode = true;
+			} else if (arg.startsWith("-macro") || arg.endsWith(".ijm") || arg.endsWith(".txt"))
+				batchMode = true;
+			else if (arg.startsWith("-debug"))
+				IJ.setDebugMode(true);
+			else if (arg.startsWith("-ijpath") && i+10 && DEFAULT_PORT+delta<65536)
+					port = DEFAULT_PORT+delta;
 			} 
 		}
   		// If existing ImageJ instance, pass arguments to it and quit.
diff -pruN 1.51q-1/ij/ImagePlus.java 1.52g-1/ij/ImagePlus.java
--- 1.51q-1/ij/ImagePlus.java	2017-09-13 17:59:58.000000000 +0000
+++ 1.52g-1/ij/ImagePlus.java	2018-09-13 19:57:58.000000000 +0000
@@ -69,6 +69,7 @@ public class ImagePlus implements ImageO
 	private	String url;
 	private FileInfo fileInfo;
 	private int imageType = GRAY8;
+	private boolean typeSet;
 	private ImageStack stack;
 	private static int currentID = -1;
 	private int ID;
@@ -93,9 +94,9 @@ public class ImagePlus implements ImageO
 	private static int default16bitDisplayRange;
 	private boolean antialiasRendering = true;
 	private boolean ignoreGlobalCalibration;
+	private boolean oneSliceStack;
 	public boolean setIJMenuBar = Prefs.setIJMenuBar;
-	public boolean typeSet;
-	
+		
 
     /** Constructs an uninitialized ImagePlus. */
     public ImagePlus() {
@@ -106,10 +107,10 @@ public class ImagePlus implements ImageO
     /** Constructs an ImagePlus from an Image or BufferedImage. The first 
 		argument will be used as the title of the window that displays the image.
 		Throws an IllegalStateException if an error occurs while loading the image. */
-    public ImagePlus(String title, Image img) {
+    public ImagePlus(String title, Image image) {
 		this.title = title;
-		if (img!=null)
-			setImage(img);
+		if (image!=null)
+			setImage(image);
 		setID();
     }
     
@@ -194,14 +195,14 @@ public class ImagePlus implements ImageO
 		if (IJ.debugMode) IJ.log(title + ": unlock");
 	}
 		
-	private void waitForImage(Image img) {
+	private void waitForImage(Image image) {
 		if (comp==null) {
 			comp = IJ.getInstance();
 			if (comp==null)
 				comp = new Canvas();
 		}
 		imageLoaded = false;
-		if (!comp.prepareImage(img, this)) {
+		if (!comp.prepareImage(image, this)) {
 			double progress;
 			waitStart = System.currentTimeMillis();
 			while (!imageLoaded && !errorLoadingImage) {
@@ -407,11 +408,10 @@ public class ImagePlus implements ImageO
 		if (isVisible())
 			return;
 		win = null;
-		//if (ip!=null) throw new IllegalArgumentException();
 		if ((IJ.isMacro() && ij==null) || Interpreter.isBatchMode()) {
 			if (isComposite()) ((CompositeImage)this).reset();
-			ImagePlus img = WindowManager.getCurrentImage();
-			if (img!=null) img.saveRoi();
+			ImagePlus imp = WindowManager.getCurrentImage();
+			if (imp!=null) imp.saveRoi();
 			WindowManager.setTempCurrentImage(this);
 			Interpreter.addBatchModeImage(this);
 			return;
@@ -422,7 +422,6 @@ public class ImagePlus implements ImageO
 		if ((img!=null) && (width>=0) && (height>=0)) {
 			activated = false;
 			int stackSize = getStackSize();
-			//if (compositeImage) stackSize /= nChannels;
 			if (stackSize>1)
 				win = new StackWindow(this);
 			else if (getProperty(Plot.PROPERTY_KEY) != null)
@@ -504,9 +503,9 @@ public class ImagePlus implements ImageO
 	/** Replaces the image, if any, with the one specified. 
 		Throws an IllegalStateException if an error occurs 
 		while loading the image. */
-	public void setImage(Image img) {
-		if (img instanceof BufferedImage) {
-			BufferedImage bi = (BufferedImage)img;
+	public void setImage(Image image) {
+		if (image instanceof BufferedImage) {
+			BufferedImage bi = (BufferedImage)image;
 			if (bi.getType()==BufferedImage.TYPE_USHORT_GRAY) {
 				setProcessor(null, new ShortProcessor(bi));
 				return;
@@ -517,19 +516,17 @@ public class ImagePlus implements ImageO
 		}
 		roi = null;
 		errorLoadingImage = false;
-		waitForImage(img);
+		waitForImage(image);
 		if (errorLoadingImage)
 			throw new IllegalStateException ("Error loading image");
-		this.img = img;
-		int newWidth = img.getWidth(ij);
-		int newHeight = img.getHeight(ij);
+		int newWidth = image.getWidth(ij);
+		int newHeight = image.getHeight(ij);
 		boolean dimensionsChanged = newWidth!=width || newHeight!=height;
 		width = newWidth;
 		height = newHeight;
-		ip = null;
-		stack = null;
-		LookUpTable lut = new LookUpTable(img);
-		int type;
+		setStackNull();
+		LookUpTable lut = new LookUpTable(image);
+		int type = GRAY8;
 		if (lut.getMapSize() > 0) {
 			if (lut.isGrayscale())
 				type = GRAY8;
@@ -537,8 +534,11 @@ public class ImagePlus implements ImageO
 				type = COLOR_256;
 		} else
 			type = COLOR_RGB;
+		if (image!=null && type==COLOR_RGB)
+			ip = new ColorProcessor(image);
+		if (ip==null && image!=null)
+			ip = new ByteProcessor(image);
 		setType(type);
-		setupProcessor();
 		this.img = ip.createImage();
 		if (win!=null) {
 			if (dimensionsChanged)
@@ -590,21 +590,21 @@ public class ImagePlus implements ImageO
 	/** Replaces the ImageProcessor with the one specified and updates the display. With
 		stacks, the ImageProcessor must be the same type as other images in the stack and
 		it must be the same width and height.  Set 'title' to null to leave the title unchanged. */
-		public void setProcessor(String title, ImageProcessor ip) {
-			if (ip==null || ip.getPixels()==null)
-				throw new IllegalArgumentException("ip null or ip.getPixels() null");
-			if (getStackSize()>1) {
-				if (ip.getWidth()!=width || ip.getHeight()!=height)
-					throw new IllegalArgumentException("Wrong dimensions for this stack");
-				int stackBitDepth = stack!=null?stack.getBitDepth():0;
-				if (stackBitDepth>0 && getBitDepth()!=stackBitDepth)
-					throw new IllegalArgumentException("Wrong type for this stack");
-			} else {
-				stack = null;
-				setCurrentSlice(1);
-			}
-			setProcessor2(title, ip, null);
+	public void setProcessor(String title, ImageProcessor ip) {
+		if (ip==null || ip.getPixels()==null)
+			throw new IllegalArgumentException("ip null or ip.getPixels() null");
+		if (getStackSize()>1) {
+			if (ip.getWidth()!=width || ip.getHeight()!=height)
+				throw new IllegalArgumentException("Wrong dimensions for this stack");
+			int stackBitDepth = stack!=null?stack.getBitDepth():0;
+			if (stackBitDepth>0 && getBitDepth()!=stackBitDepth)
+				throw new IllegalArgumentException("Wrong type for this stack");
+		} else {
+			setStackNull();
+			setCurrentSlice(1);
 		}
+		setProcessor2(title, ip, null);
+	}
 	
 	void setProcessor2(String title, ImageProcessor ip, ImageStack newStack) {
 		//IJ.log("setProcessor2: "+ip+" "+this.ip+" "+newStack);
@@ -616,14 +616,16 @@ public class ImagePlus implements ImageO
 			notifyListeners(UPDATED);
 		if (ij!=null)
 			ip.setProgressBar(ij.getProgressBar());
-        int stackSize = 1;
+		int stackSize = 1;
+		boolean dimensionsChanged = width>0 && height>0 && (width!=ip.getWidth() || height!=ip.getHeight());
 		if (stack!=null) {
 			stackSize = stack.getSize();
 			if (currentSlice>stackSize)
 				setCurrentSlice(stackSize);
+			if (currentSlice>=1 && currentSlice<=stackSize && !dimensionsChanged)
+				stack.setPixels(ip.getPixels(),currentSlice);
 		}
 		img = null;
-		boolean dimensionsChanged = width>0 && height>0 && (width!=ip.getWidth() || height!=ip.getHeight());
 		if (dimensionsChanged) roi = null;
 		int type;
 		if (ip instanceof ByteProcessor)
@@ -657,6 +659,7 @@ public class ImagePlus implements ImageO
 	/** Replaces the image with the specified stack and updates 
 		the display. Set 'title' to null to leave the title unchanged. */
     public void setStack(String title, ImageStack newStack) {
+		int previousStackSize = getStackSize();
 		int newStackSize = newStack.getSize();
 		//IJ.log("setStack: "+newStackSize+" "+this);
 		if (newStackSize==0)
@@ -682,6 +685,7 @@ public class ImagePlus implements ImageO
     	if (this.stack==null)
     	    newStack.viewers(+1);
     	this.stack = newStack;
+    	oneSliceStack = false;
     	setProcessor2(title, ip, newStack);
 		if (win==null) {
 			if (resetCurrentSlice) setSlice(currentSlice);
@@ -691,10 +695,17 @@ public class ImagePlus implements ImageO
 		if (newStackSize>1 && !(win instanceof StackWindow)) {
 			if (isDisplayedHyperStack())
 				setOpenAsHyperStack(true);
+			activated = false;
 			win = new StackWindow(this, getCanvas());   // replaces this window
+			if (IJ.isMacro()) { // wait for stack window to be activated
+				long start = System.currentTimeMillis();
+				while (!activated) {
+					IJ.wait(5);
+					if ((System.currentTimeMillis()-start)>200)
+						break; // 0.2 second timeout
+				}
+			}
 			setPosition(1, 1, 1);
-			if (Interpreter.getInstance()!=null)
-				IJ.wait(25);
 		} else if (newStackSize>1 && invalidDimensions) {
 			if (isDisplayedHyperStack())
 				setOpenAsHyperStack(true);
@@ -711,7 +722,10 @@ public class ImagePlus implements ImageO
 			}
 			repaintWindow();
 		}
-		if (resetCurrentSlice) setSlice(currentSlice);
+		if (resetCurrentSlice)
+			setSlice(currentSlice);
+		if (isComposite() && previousStackSize!=newStackSize)
+			compositeImage = false;
     }
     
 	public void setStack(ImageStack newStack, int channels, int slices, int frames) {
@@ -731,6 +745,15 @@ public class ImagePlus implements ImageO
 		setStack(null, newStack);
 	}
 
+	private synchronized void setStackNull() {
+		if (oneSliceStack && stack!=null && stack.size()>0) {
+			String label = stack.getSliceLabel(1);
+			setProperty("Label", label);	
+		}
+		stack = null;
+		oneSliceStack = false;
+	}	
+
 	/**	Saves this image's FileInfo so it can be later
 		retieved using getOriginalFileInfo(). */
 	public void setFileInfo(FileInfo fi) {
@@ -771,15 +794,6 @@ public class ImagePlus implements ImageO
 	}
 	
 	void setupProcessor() {
-		if (imageType==COLOR_RGB) {
-			if (ip==null || ip instanceof ByteProcessor)
-				ip = new ColorProcessor(getImage());
-		} else if (ip==null || (ip instanceof ColorProcessor))
-			ip = new ByteProcessor(getImage());
-		if (roi!=null && roi.isArea())
-			ip.setRoi(roi.getBounds());
-		else
-			ip.resetRoi();
 	}
 	
 	public boolean isProcessor() {
@@ -792,9 +806,12 @@ public class ImagePlus implements ImageO
 		Sets the line width to the current line width and sets the
 		calibration table if the image is density calibrated. */
 	public ImageProcessor getProcessor() {
-		if (ip==null && img==null)
+		if (ip==null)
 			return null;
-		setupProcessor();
+		if (roi!=null && roi.isArea())
+			ip.setRoi(roi.getBounds());
+		else
+			ip.resetRoi();
 		if (!compositeImage)
 			ip.setLineWidth(Line.getWidth());
 		if (ij!=null)
@@ -825,7 +842,11 @@ public class ImagePlus implements ImageO
 	}
 	
 	/** For images with irregular ROIs, returns a byte mask, otherwise, returns
-		null. Mask pixels have a non-zero value. */
+	 * null. Mask pixels have a non-zero value.and the dimensions of the 
+	 * mask are equal to the width and height of the ROI.
+	 * @see ij.ImagePlus#createRoiMask
+	 * @see ij.ImagePlus#createThresholdMask
+	*/
 	public ImageProcessor getMask() {
 		if (roi==null) {
 			if (ip!=null) ip.resetRoi();
@@ -840,6 +861,34 @@ public class ImagePlus implements ImageO
 		}
 		return mask;
 	}
+	
+	/** Returns an 8-bit binary (0 and 255) ROI or overlay mask
+	 *  that has the same dimensions as this image.
+	 * @see ij.gui.Roi#getMask
+	*/
+	public ByteProcessor createRoiMask() {
+		Roi roi2 = getRoi();
+		Overlay overlay2 = getOverlay();
+		if (roi2==null && overlay2==null)
+			throw new IllegalArgumentException("ROI or overlay required");
+		ByteProcessor mask = new ByteProcessor(getWidth(),getHeight());
+		mask.setColor(255);
+		if (overlay2!=null) {
+			for (int i=0; i1) {
-			ImageStack stack = getStack();
-			String label = stack.getSliceLabel(getCurrentSlice());
+			ImageStack stack2 = getStack();
+			String label = stack2.getSliceLabel(getCurrentSlice());
 			if (label!=null && label.indexOf('\n')>0) {
 				String value = getStringProperty(key, label);
 				if (value!=null)
@@ -1287,12 +1345,7 @@ public class ImagePlus implements ImageO
 	/** Returns true is this image uses an inverting LUT that 
 		displays zero as white and 255 as black. */
 	public boolean isInvertedLut() {
-		if (ip==null) {
-			if (img==null)
-				return false;
-			setupProcessor();
-		}
-		return ip.isInvertedLut();
+		return ip!=null && ip.isInvertedLut();
 	}
     
 	private int[] pvalue = new int[4];
@@ -1375,10 +1428,16 @@ public class ImagePlus implements ImageO
 			ImageProcessor ip2 = getProcessor();
 			if (ip2==null)
 				return s;
-            String info = (String)getProperty("Info");
-            String label = info!=null?getTitle()+"\n"+info:null;
+			String label = (String)getProperty("Label");
+			if (label==null) {
+				String info = (String)getProperty("Info");
+				label = info!=null?getTitle()+"\n"+info:null; // DICOM metadata
+			}
 			s.addSlice(label, ip2);
 			s.update(ip2);
+			setStack(s);
+			ip = ip2;
+			oneSliceStack = true;
 		} else {
 			s = stack;
 			if (ip!=null) {
@@ -1436,7 +1495,7 @@ public class ImagePlus implements ImageO
 	}
 
 	public void killStack() {
-		stack = null;
+		setStackNull();
 		trimProcessor();
 	}
 	
@@ -1520,9 +1579,12 @@ public class ImagePlus implements ImageO
 			ColorModel cm = ip.getColorModel();
 			double min = ip.getMin();
 			double max = ip.getMax();
-			ip = stack.getProcessor(1);
-			ip.setColorModel(cm);
-			ip.setMinAndMax(min, max);
+			ImageProcessor ip2 = stack.getProcessor(1);
+			if (ip2!=null) {
+				ip = ip2;
+				ip.setColorModel(cm);
+				ip.setMinAndMax(min, max);
+			}
 		}
 	}
 	
@@ -1563,13 +1625,12 @@ public class ImagePlus implements ImageO
 				roi.endPaste();
 			if (isProcessor())
 				stack.setPixels(ip.getPixels(),currentSlice);
-			ip = getProcessor();
 			setCurrentSlice(n);
 			Object pixels = null;
 			Overlay overlay2 = null;
 			if (stack.isVirtual() && !((stack instanceof FileInfoVirtualStack)||(stack instanceof AVI_Reader))) {
 				ImageProcessor ip2 = stack.getProcessor(currentSlice);
-				overlay2 = ip2.getOverlay();
+				overlay2 = ip2!=null?ip2.getOverlay():null;
 				if (overlay2!=null)
 					setOverlay(overlay2);
 				if (stack instanceof VirtualStack) {
@@ -1577,7 +1638,7 @@ public class ImagePlus implements ImageO
 					if (props!=null)
 						setProperty("FHT", props.get("FHT"));
 				}
-				pixels = ip2.getPixels();
+				if (ip2!=null) pixels=ip2.getPixels();
 			} else
 				pixels = stack.getPixels(currentSlice);
 			if (ip!=null && pixels!=null) {
@@ -1585,8 +1646,10 @@ public class ImagePlus implements ImageO
 					ip.setPixels(pixels);
 					ip.setSnapshotPixels(null);
 				} catch(Exception e) {}
-			} else
-				ip = stack.getProcessor(n);
+			} else {
+				ImageProcessor ip2 = stack.getProcessor(n);
+				if (ip2!=null) ip = ip2;
+			}
 			if (compositeImage && getCompositeMode()==IJ.COMPOSITE && ip!=null) {
 				int channel = getC();
 				if (channel>0 && channel<=getNChannels())
@@ -1594,7 +1657,7 @@ public class ImagePlus implements ImageO
 			}
 			if (win!=null && win instanceof StackWindow)
 				((StackWindow)win).updateSliceSelector();
-			if ((Prefs.autoContrast||IJ.shiftKeyDown()) && nChannels==1 && imageType!=COLOR_RGB) {
+			if (Prefs.autoContrast && nChannels==1 && imageType!=COLOR_RGB) {
 				(new ContrastEnhancer()).stretchHistogram(ip,0.35,ip.getStats());
 				ContrastAdjuster.update();
 				//IJ.showStatus(n+": min="+ip.getMin()+", max="+ip.getMax());
@@ -1631,8 +1694,10 @@ public class ImagePlus implements ImageO
 	
 	/** Assigns 'newRoi'  to this image and displays it if 'updateDisplay' is true. */
 	public void setRoi(Roi newRoi, boolean updateDisplay) {
-		if (newRoi==null)
-			{deleteRoi(); return;}
+		if (newRoi==null) {
+			deleteRoi();
+			return;
+		}
 		if (Recorder.record) {
 			Recorder recorder = Recorder.getInstance();
 			if (recorder!=null) recorder.imageUpdated(this);
@@ -1752,7 +1817,7 @@ public class ImagePlus implements ImageO
 		if (roi!=null) {
 			saveRoi();
 			if (!(IJ.altKeyDown()||IJ.shiftKeyDown())) {
-				RoiManager rm = RoiManager.getInstance();
+				RoiManager rm = RoiManager.getRawInstance();
 				if (rm!=null)
 					rm.deselect(roi);
 			}
@@ -1773,13 +1838,14 @@ public class ImagePlus implements ImageO
 		deleteRoi();
 	}
 
-	public synchronized void saveRoi() {
-		if (roi!=null) {
-			roi.endPaste();
-			Rectangle r = roi.getBounds();
+	public void saveRoi() {
+		Roi roi2 = roi;
+		if (roi2!=null) {
+			roi2.endPaste();
+			Rectangle r = roi2.getBounds();
 			if ((r.width>0 || r.height>0)) {
-				Roi.previousRoi = (Roi)roi.clone();
-				if (IJ.debugMode) IJ.log("saveRoi: "+roi);
+				Roi.previousRoi = (Roi)roi2.clone();
+				if (IJ.debugMode) IJ.log("saveRoi: "+roi2);
 			}
 		}
 	}
@@ -1902,10 +1968,9 @@ public class ImagePlus implements ImageO
     		fi.nImages = getImageStackSize();
     	fi.whiteIsZero = isInvertedLut();
 		fi.intelByteOrder = false;
-    	setupProcessor();
-    	if (fi.nImages==1)
-    		fi.pixels = ip.getPixels();
-    	else
+		if (fi.nImages==1 && ip!=null)
+			fi.pixels = ip.getPixels();
+		else if (stack!=null)
 			fi.pixels = stack.getImageArray();
 		Calibration cal = getCalibration();
     	if (cal.scaled()) {
@@ -2014,7 +2079,7 @@ public class ImagePlus implements ImageO
 			if (isComposite())
 				((CompositeImage)this).setChannelsUpdated(); //flush
 		}
-		stack = null;
+		setStackNull();
 		img = null;
 		win = null;
 		if (roi!=null) roi.setImage(null);
@@ -2184,8 +2249,9 @@ public class ImagePlus implements ImageO
     	Called by ImageCanvas when the mouse moves. Can be overridden by
     	ImagePlus subclasses.
     */
-    public void mouseMoved(int x, int y) {
-    	if (ij!=null)
+	public void mouseMoved(int x, int y) {
+		Roi roi2 = getRoi();
+		if (ij!=null && (roi2==null || roi2.getState()==Roi.NORMAL))
 			ij.showStatus(getLocationAsString(x,y) + getValueAsString(x,y));
 		savex=x; savey=y;
 	}
@@ -2222,21 +2288,21 @@ public class ImagePlus implements ImageO
 		Calibration cal = getCalibration();
 		if (getProperty("FHT")!=null)
 			return getFFTLocation(x, height-y, cal);
-		if (!(IJ.altKeyDown()||IJ.shiftKeyDown())) {
-			String s = " x="+d2s(cal.getX(x)) + ", y=" + d2s(cal.getY(y,height));
-			if (getStackSize()>1) {
-				int z = isDisplayedHyperStack()?getSlice()-1:getCurrentSlice()-1;
-				s += ", z="+d2s(cal.getZ(z));
-			}
-			return s;
-		} else {
-			String s =  " x="+x+", y=" + y;
-			if (getStackSize()>1) {
+		String xx="", yy="";
+		if (cal.scaled()) {
+			xx = " ("+x+")";
+			yy = " ("+y+")";
+		}
+		String s = " x="+d2s(cal.getX(x)) + xx + ", y=" + d2s(cal.getY(y,height)) + yy;
+		if (getStackSize()>1) {
+			Roi roi2 = getRoi();
+			if (roi2==null || roi2.getState()==Roi.NORMAL) {
 				int z = isDisplayedHyperStack()?getSlice()-1:getCurrentSlice()-1;
-				s += ", z=" + z;
+				String zz = cal.scaled()&&cal.getZ(z)!=z?" ("+z+")":"";
+				s += ", z="+d2s(cal.getZ(z))+zz;
 			}
-			return s;
 		}
+		return s;
     }
     
     private String d2s(double n) {
@@ -2377,7 +2443,6 @@ public class ImagePlus implements ImageO
 				ip.reset(ip.getMask());
 			}
 			updateAndDraw();
-			//deleteRoi();
 		} else if (roi!=null) {
 			roi.startPaste(clipboard);
 			Undo.setup(Undo.PASTE, this);
@@ -2525,8 +2590,10 @@ public class ImagePlus implements ImageO
 			}
 		}
 		Overlay overlay2 = getOverlay();
-		if (overlay2!=null && imp2.getRoi()!=null)
+		if (overlay2!=null && imp2.getRoi()!=null) {
 			imp2.deleteRoi();
+			if (getWindow()!=null) IJ.wait(100);
+		}
 		setPointScale(imp2.getRoi(), overlay2);
 		ic2.setOverlay(overlay2);
 		ImageCanvas ic = getCanvas();
@@ -2554,8 +2621,8 @@ public class ImagePlus implements ImageO
 	 */
 	public void flattenStack() {
 		if (IJ.debugMode) IJ.log("flattenStack");
-		if (getStackSize()==1 || !IJ.isJava16())
-			throw new UnsupportedOperationException("Image stack and Java 1.6 required");
+		if (getStackSize()==1)
+			throw new UnsupportedOperationException("Image stack required");
 		boolean composite = isComposite();
 		if (getBitDepth()!=24)
 			new ImageConverter(this).convertToRGB();
@@ -2754,4 +2821,8 @@ public class ImagePlus implements ImageO
     	return setIJMenuBar && Prefs.setIJMenuBar;
     }
     
+    public boolean isStack() {
+    	return stack!=null;
+    }
+
 }
diff -pruN 1.51q-1/ij/io/DirectoryChooser.java 1.52g-1/ij/io/DirectoryChooser.java
--- 1.51q-1/ij/io/DirectoryChooser.java	2015-11-18 22:49:32.000000000 +0000
+++ 1.52g-1/ij/io/DirectoryChooser.java	2018-09-15 07:55:44.000000000 +0000
@@ -51,7 +51,7 @@ import javax.swing.filechooser.*;
 					if (chooser.showOpenDialog(null)==JFileChooser.APPROVE_OPTION) {
 						File file = chooser.getSelectedFile();
 						directory = file.getAbsolutePath();
-						if (!directory.endsWith(File.separator))
+						if (!(directory.endsWith(File.separator)||directory.endsWith("/")))
 							directory += File.separator;
 						OpenDialog.setDefaultDirectory(directory);
 					}
@@ -78,7 +78,7 @@ import javax.swing.filechooser.*;
 			if (chooser.showOpenDialog(null)==JFileChooser.APPROVE_OPTION) {
 				File file = chooser.getSelectedFile();
 				directory = file.getAbsolutePath();
-				if (!directory.endsWith(File.separator))
+				if (!(directory.endsWith(File.separator)||directory.endsWith("/")))
 					directory += File.separator;
 				OpenDialog.setDefaultDirectory(directory);
 			}
diff -pruN 1.51q-1/ij/io/FileInfo.java 1.52g-1/ij/io/FileInfo.java
--- 1.51q-1/ij/io/FileInfo.java	2017-09-13 11:13:46.000000000 +0000
+++ 1.52g-1/ij/io/FileInfo.java	2018-08-16 16:52:38.000000000 +0000
@@ -193,6 +193,31 @@ public class FileInfo implements Cloneab
 			+ ", samples=" + samplesPerPixel;
     }
     
+    /** Returns JavaScript code that can be used to recreate this FileInfo. */
+    public String getCode() {
+    	String code = "fi = new FileInfo();\n";
+    	String type = null;
+    	if (fileType==GRAY8)
+    		type = "GRAY8";
+    	else if (fileType==GRAY16_UNSIGNED)
+    		type = "GRAY16_UNSIGNED";
+    	else if (fileType==GRAY32_FLOAT)
+    		type = "GRAY32_FLOAT";
+    	else if (fileType==RGB)
+    		type = "RGB";
+    	if (type!=null)
+    		code += "fi.fileType = FileInfo."+type+";\n"; 
+    	code += "fi.width = "+width+";\n";
+    	code += "fi.height = "+height+";\n";
+    	if (nImages>1)
+			code += "fi.nImages = "+nImages+";\n";  	
+    	if (getOffset()>0)
+			code += "fi.longOffset = "+getOffset()+";\n";  	
+    	if (intelByteOrder)
+			code += "fi.intelByteOrder = true;\n";  	
+    	return code;
+    }
+
     private String getType() {
     	switch (fileType) {
 			case GRAY8: return "byte";
diff -pruN 1.51q-1/ij/io/FileOpener.java 1.52g-1/ij/io/FileOpener.java
--- 1.51q-1/ij/io/FileOpener.java	2017-09-13 11:13:46.000000000 +0000
+++ 1.52g-1/ij/io/FileOpener.java	2018-08-16 16:38:34.000000000 +0000
@@ -66,7 +66,7 @@ public class FileOpener {
 		
 		ColorModel cm = createColorModel(fi);
 		if (fi.nImages>1)
-			{return openStack(cm, show);}
+			return openStack(cm, show);
 		switch (fi.fileType) {
 			case FileInfo.GRAY8:
 			case FileInfo.COLOR8:
@@ -196,7 +196,8 @@ public class FileOpener {
 		try {
 			ImageReader reader = new ImageReader(fi);
 			InputStream is = createInputStream(fi);
-			if (is==null) return null;
+			if (is==null)
+				return null;
 			IJ.resetEscape();
 			for (int i=1; i<=fi.nImages; i++) {
 				if (!silentMode)
@@ -208,7 +209,8 @@ public class FileOpener {
 					return null;
 				}
 				pixels = reader.readPixels(is, skip);
-				if (pixels==null) break;
+				if (pixels==null)
+					break;
 				stack.addSlice(null, pixels);
 				skip = fi.gapBetweenImages;
 				if (!silentMode)
@@ -244,7 +246,6 @@ public class FileOpener {
 		if (ip.getMin()==ip.getMax())  // find stack min and max if first slice is blank
 			setStackDisplayRange(imp);
 		if (!silentMode) IJ.showProgress(1.0);
-		//silentMode = false;
 		return imp;
 	}
 
@@ -461,7 +462,7 @@ public class FileOpener {
 				is = new FileInputStream(f);
 		}
 		if (is!=null) {
-		    if (fi.compression>=FileInfo.LZW)
+			if (fi.compression>=FileInfo.LZW)
 				is = new RandomAccessStream(is);
 			else if (gzip)
 				is = new GZIPInputStream(is, 50000);
diff -pruN 1.51q-1/ij/io/FileSaver.java 1.52g-1/ij/io/FileSaver.java
--- 1.51q-1/ij/io/FileSaver.java	2017-09-13 11:13:46.000000000 +0000
+++ 1.52g-1/ij/io/FileSaver.java	2018-03-31 09:42:14.000000000 +0000
@@ -328,14 +328,9 @@ public class FileSaver {
 	}
 
 	public static boolean okForGif(ImagePlus imp) {
-		int type = imp.getType();
-		if (type==ImagePlus.COLOR_RGB) {
-			String msg = "To save as GIF, the image ";
-			if (imp.getStackSize()>1)
-				msg = "To save as Animated GIF, the stack ";
-			IJ.error(msg+"must be converted to 8-bit\nindexed color by the Image>Type>8-bit Color command.");
+		if (imp.getType()==ImagePlus.COLOR_RGB)
 			return false;
-		} else
+		else
 			return true;
 	}
 
@@ -343,8 +338,6 @@ public class FileSaver {
 		dialog. Returns false if the user selects cancel
 		or the image is not 8-bits. */
 	public boolean saveAsGif() {
-		if (!okForGif(imp))
-			return false;
 		String path = getPath("GIF", ".gif");
 		if (path==null)
 			return false;
@@ -355,7 +348,6 @@ public class FileSaver {
 	/** Save the image in Gif format using the specified path. Returns
 		false if the image is not 8-bits or there is an I/O error. */
 	public boolean saveAsGif(String path) {
-		if (!okForGif(imp)) return false;
 		IJ.runPlugIn(imp, "ij.plugin.GifWriter", path);
 		updateImp(fi, FileInfo.GIF_OR_JPG);
 		return true;
@@ -526,7 +518,7 @@ public class FileSaver {
 	/** Save the stack as raw data using the specified path. */
 	public boolean saveAsRawStack(String path) {
 		if (fi.nImages==1)
-			{IJ.write("This is not a stack"); return false;}
+			{IJ.log("This is not a stack"); return false;}
 		fi.intelByteOrder = Prefs.intelByteOrder;
 		boolean signed16Bit = false;
 		Object[] stack = null;
diff -pruN 1.51q-1/ij/io/ImageReader.java 1.52g-1/ij/io/ImageReader.java
--- 1.51q-1/ij/io/ImageReader.java	2017-02-13 13:42:24.000000000 +0000
+++ 1.52g-1/ij/io/ImageReader.java	2018-08-16 17:09:12.000000000 +0000
@@ -23,6 +23,7 @@ public class ImageReader {
     private long byteCount;
 	private boolean showProgressBar=true;
 	private int eofErrorCount;
+	private int imageCount;
 	private long startTime;
 	public double min, max; // readRGB48() calculates min/max pixel values
 
@@ -89,7 +90,8 @@ public class ImageReader {
 					last = b % fi.width == fi.width - 1 ? 0 : byteArray[b];
 				}
 			}
-			if (current+length>pixels.length) length = pixels.length-current;
+			if (current+length>pixels.length)
+				length = pixels.length-current;
 			System.arraycopy(byteArray, 0, pixels, current, length);
 			current += length;
 			showProgress(i+1, fi.stripOffsets.length);
@@ -116,8 +118,7 @@ public class ImageReader {
 			while (bufferCount0)
-						for (int i=bufferCount; i0)
-						for (int i=bufferCount; i0)
-						for (int i=bufferCount; i0)
-						for (int i=bufferCount; iFileInfo.COMPRESSION_NONE)
+		if (fi.compression>FileInfo.COMPRESSION_NONE || (fi.stripOffsets!=null&&fi.stripOffsets.length>1))
 			return readCompressedPlanarRGBImage(in);
 		DataInputStream dis = new DataInputStream(in);
 		int planeSize = nPixels; // 1/3 image size
@@ -697,7 +695,6 @@ public class ImageReader {
 		int b1, b2, b3;
 		DataInputStream dis = new DataInputStream(in);
 		for (int y=0; y5) break;
 				bytesRead += count;
-				//IJ.log("skip: "+skipCount+" "+count+" "+bytesRead+" "+skipAttempts);
 			}
 		}
 		byteCount = ((long)width)*height*bytesPerPixel;
@@ -807,6 +803,8 @@ public class ImageReader {
 					pixels = (Object)readChunkyRGB(in);
 					break;
 				case FileInfo.RGB_PLANAR:
+					if (!(in instanceof RandomAccessStream) && fi.stripOffsets!=null && fi.stripOffsets.length>1)
+						in = new RandomAccessStream(in);
 					bytesPerPixel = 3;
 					skip(in);
 					pixels = (Object)readPlanarRGB(in);
@@ -839,6 +837,7 @@ public class ImageReader {
 					pixels = null;
 			}
 			showProgress(1, 1);
+			imageCount++;
 			return pixels;
 		}
 		catch (IOException e) {
@@ -856,7 +855,7 @@ public class ImageReader {
 		this.skipCount = skipCount;
 		showProgressBar = false;
 		Object pixels = readPixels(in);
-		if (eofErrorCount>0)
+		if (eofErrorCount>(imageCount==1?1:0))
 			return null;
 		else
 			return pixels;
@@ -1046,7 +1045,6 @@ class ByteVector {
     }
 
 	void doubleCapacity() {
-		//IJ.log("double: "+data.length*2);
 		byte[] tmp = new byte[data.length*2 + 1];
 		System.arraycopy(data, 0, tmp, 0, data.length);
 		data = tmp;
@@ -1061,6 +1059,6 @@ class ByteVector {
 		System.arraycopy(data, 0, bytes, 0, size);
 		return bytes;
 	}
-	
+		
 }
 
diff -pruN 1.51q-1/ij/io/ImportDialog.java 1.52g-1/ij/io/ImportDialog.java
--- 1.51q-1/ij/io/ImportDialog.java	2016-07-15 15:13:58.000000000 +0000
+++ 1.52g-1/ij/io/ImportDialog.java	2018-08-18 17:47:18.000000000 +0000
@@ -9,8 +9,7 @@ import ij.gui.*;
 import ij.process.*;
 import ij.util.*;
 import ij.plugin.frame.Recorder;
-import ij.plugin.FolderOpener;
-import ij.plugin.FileInfoVirtualStack;
+import ij.plugin.*;
 import ij.measure.Calibration;
 
 
@@ -57,7 +56,7 @@ public class ImportDialog {
 		"24-bit RGB Planar", "24-bit BGR", "24-bit Integer", "32-bit ARGB", "32-bit ABGR", "1-bit Bitmap"};
     	
     static {
-    	options = Prefs.getInt(OPTIONS,0);
+    	options = Prefs.getInt(OPTIONS, 0);
     	sWhiteIsZero = (options&WHITE_IS_ZERO)!=0;
     	sIntelByteOrder = (options&INTEL_BYTE_ORDER)!=0;
     }
@@ -172,6 +171,7 @@ public class ImportDialog {
 				IJ.showStatus((stack.getSize()+1) + ": " + list[i]);
 			}
 		}
+		Recorder.recordCall(fi.getCode()+"imp = Raw.openAll(\""+ fi.directory+"\", fi);");
 		if (stack!=null) {
 			imp = new ImagePlus("Imported Stack", stack);
 			if (imp.getBitDepth()==16 || imp.getBitDepth()==32)
@@ -187,11 +187,17 @@ public class ImportDialog {
 		Does nothing if the dialog is canceled. */
 	public void openImage() {
 		FileInfo fi = getFileInfo();
-		if (fi==null) return;
+		if (fi==null)
+			return;
 		if (openAll) {
 			if (virtual) {
-				virtual = false;
-				IJ.error("Import Raw", "\"Open All\" does not currently support virtual stacks");
+				ImagePlus imp = Raw.openAllVirtual(directory, fi);
+				Recorder.recordCall(fi.getCode()+"imp = Raw.openAllVirtual(\""+directory+"\", fi);");
+				if (imp!=null) {
+					imp.setSlice(imp.getStackSize()/2);
+					imp.show();
+					imp.setSlice(1);
+				}
 				return;
 			}
 			String[] list = new File(directory).list();
@@ -202,6 +208,8 @@ public class ImportDialog {
 		else {
 			FileOpener fo = new FileOpener(fi);
 			ImagePlus imp = fo.openImage();
+			String filePath = fi.directory+fi.fileName;
+			Recorder.recordCall(fi.getCode()+"imp = Raw.open(\""+filePath+"\", fi);");
 			if (imp!=null) {
 				imp.show();
 				int n = imp.getStackSize();
@@ -211,7 +219,8 @@ public class ImportDialog {
 					ip.resetMinAndMax();
 					imp.setDisplayRange(ip.getMin(),ip.getMax());
 				}
-			}
+			} else
+				IJ.error("File>Import>Raw", "File not found: "+filePath);
 		}
 	}
 
@@ -225,6 +234,8 @@ public class ImportDialog {
 		FileInfo fi = new FileInfo();
 		fi.fileFormat = fi.RAW;
 		fi.fileName = fileName;
+		if (!(directory.endsWith(File.separator)||directory.endsWith("/")))
+			directory += "/";
 		fi.directory = directory;
 		fi.width = width;
 		fi.height = height;
@@ -292,7 +303,7 @@ public class ImportDialog {
 	public static FileInfo getLastFileInfo() {
 		return lastFileInfo;
 	}
-	
+		
 	private void getDimensionsFromName(String name) {
 		if (name==null) return;
 		int lastUnderscore = name.lastIndexOf("_");
@@ -305,39 +316,39 @@ public class ImportDialog {
 		name2 = new String(chars);
 		String[] numbers = Tools.split(name2);
 		int n = numbers.length;
-		if (n<2 || n>3) return;
+		if (n<2) return;
 		int w = (int)Tools.parseDouble(numbers[0],0);
-		if (w<10) return;
+		if (w<1) return;
 		int h = (int)Tools.parseDouble(numbers[1],0);
-		if (h<10) return;
+		if (h<1) return;
 		width = w;
 		height = h;
 		nImages = 1;
-		if (n==3) {
+		if (n>2) {
 			int d = (int)Tools.parseDouble(numbers[2],0);
 			if (d>0)
 				nImages = d;
 		}
 		guessFormat(directory, name);
 	}
-
+    
 	private void guessFormat(String dir, String name) {
 		if (dir==null) return;
 		File file = new File(dir+name);
 		long imageSize = (long)width*height*nImages;
 		long fileSize = file.length();
-		if (fileSize==4*imageSize) {
+		if (fileSize==4*imageSize)
 			choiceSelection = 5; // 32-bit real
-			intelByteOrder = true;
-		} else if (fileSize==2*imageSize) {
+		else if (fileSize==2*imageSize)
 			choiceSelection = 2;	// 16-bit unsigned
-			intelByteOrder = true;
-		} else if (fileSize==3*imageSize) {
+		else if (fileSize==3*imageSize)
 			choiceSelection = 7;	// 24-bit RGB
-		} else if (fileSize==imageSize)
+		else if (fileSize==imageSize)
 			choiceSelection = 0;	// 8-bit
-		if (name.endsWith("be.raw"))
+		if (name.endsWith("be.raw"))  // big-endian
 			intelByteOrder = false;
+		else if (name.endsWith("le.raw"))  // little-endian
+			intelByteOrder = true;
 	}
 	
 }
diff -pruN 1.51q-1/ij/io/OpenDialog.java 1.52g-1/ij/io/OpenDialog.java
--- 1.51q-1/ij/io/OpenDialog.java	2016-07-15 16:22:54.000000000 +0000
+++ 1.52g-1/ij/io/OpenDialog.java	2017-11-18 20:13:14.000000000 +0000
@@ -225,9 +225,8 @@ import javax.swing.filechooser.*;
 			defaultDirectory = defaultDirectory + File.separator;
 	}
 	
-	/** Returns the path to the last directory opened by the user
-		using a file open or file save dialog, or using drag and drop. 
-		Returns null if the users has not opened a file. */
+	/** Returns the path to the directory that contains the last file
+		 opened, or null if a file has not been opened. */
 	public static String getLastDirectory() {
 		return lastDir;
 	}
diff -pruN 1.51q-1/ij/io/Opener.java 1.52g-1/ij/io/Opener.java
--- 1.51q-1/ij/io/Opener.java	2017-05-22 16:27:30.000000000 +0000
+++ 1.52g-1/ij/io/Opener.java	2018-09-13 16:10:52.000000000 +0000
@@ -306,10 +306,11 @@ public class Opener {
 		FileOpener.setSilentMode(silentMode);
 		if (directory.length()>0 && !(directory.endsWith("/")||directory.endsWith("\\")))
 			directory += Prefs.separator;
+		OpenDialog.setLastDirectory(directory);
+		OpenDialog.setLastName(name);
 		String path = directory+name;
 		fileType = getFileType(path);
-		if (IJ.debugMode)
-			IJ.log("openImage: \""+types[fileType]+"\", "+path);
+		if (IJ.debugMode) IJ.log("openImage: \""+types[fileType]+"\", "+path);
 		switch (fileType) {
 			case TIFF:
 				imp = openTiff(directory, name);
@@ -354,6 +355,7 @@ public class Opener {
 				return openZip(path);
 			case AVI:
 				AVI_Reader reader = new AVI_Reader();
+				reader.setVirtual(true);
 				reader.displayDialog(!IJ.macroRunning());
 				reader.run(path);
 				return reader.getImagePlus();
@@ -1037,7 +1039,8 @@ public class Opener {
 		}
 		FileOpener fo = new FileOpener(info[0]);
 		imp = fo.openImage();
-		if (imp==null) return null;
+		if (imp==null)
+			return null;
 		int[] offsets = info[0].stripOffsets;
 		if (offsets!=null&&offsets.length>1 && offsets[offsets.length-1]=n)
 				countersSize = n*4;
@@ -168,6 +168,18 @@ public class RoiEncoder {
 				putShort(RoiDecoder.OPTIONS, options);
 			}
 		}
+		if (n>65535) {
+			if (type==polygon || type==freehand || type==traced) {
+				String name = roi.getName();
+				roi = new ShapeRoi(roi);
+				if (name!=null) roi.setName(name);
+				saveShapeRoi(roi, rect, f, options);
+				return;
+			}
+			ij.IJ.beep();
+			ij.IJ.log("Non-polygonal selections with more than 65k points cannot be saved.");
+			n = 65535;
+		}
 		putShort(RoiDecoder.N_COORDINATES, n);
 		putInt(RoiDecoder.POSITION, roi.getPosition());
 		
@@ -414,6 +426,7 @@ public class RoiEncoder {
 		putInt(hdr2Offset+RoiDecoder.COUNTERS_OFFSET, offset);
 		for (int i=0; i=darg1 && v<=darg2) {
+				boolean replace = v>=darg1 && v<=darg2;
+				if (Double.isNaN(darg1) && Double.isNaN(darg2) && Double.isNaN(v))
+					replace = true;
+				if (replace) {
 					if (isFloat)
 						ip.putPixelValue(x, y, darg3);
 					else
@@ -1086,6 +1101,7 @@ public class Functions implements MacroC
 					if (seed!=dseed)
 						interp.error("Seed not integer");
 					ran = new Random(seed);
+					ImageProcessor.setRandomSeed(seed);
 				} else if (arg.equals("gaussian"))
 					gaussian = true;
 				else
@@ -1094,6 +1110,7 @@ public class Functions implements MacroC
 			interp.getRightParen();
 			if (!Double.isNaN(dseed)) return Double.NaN;
 		}
+		ImageProcessor.setRandomSeed(Double.NaN);
 		interp.getParens();
  		if (ran==null)
 			ran = new Random();
@@ -1103,15 +1120,7 @@ public class Functions implements MacroC
 			return ran.nextDouble();
 	}
 
-	//void setSeed() {
-	//	long seed = (long)getArg();
-	//	if (ran==null)
-	//		ran = new Random(seed);
-	//	else
-	//		ran.setSeed(seed);
-	//}
-
-	double getResult() {
+	double getResult(ResultsTable rt) {
 		interp.getLeftParen();
 		String column = getString();
 		int row = -1;
@@ -1119,9 +1128,15 @@ public class Functions implements MacroC
 			interp.getComma();
 			row = (int)interp.getExpression();
 		}
+		if (interp.nextToken()==',') {
+			interp.getComma();
+			String title = getString();
+			rt = getResultsTable(title);
+		}
+		if (rt==null)
+			rt = getResultsTable(true);
 		interp.getRightParen();
-		ResultsTable rt = getResultsTable(true);
-		int counter = rt.getCounter();
+		int counter = rt.size();
 		if (row==-1) row = counter-1;
 		if (row<0 || row>=counter)
 			interp.error("Row ("+row+") out of range");
@@ -1139,7 +1154,7 @@ public class Functions implements MacroC
 		}
 	}
 
-	String getResultString() {
+	String getResultString(ResultsTable rt) {
 		interp.getLeftParen();
 		String column = getString();
 		int row = -1;
@@ -1147,9 +1162,15 @@ public class Functions implements MacroC
 			interp.getComma();
 			row = (int)interp.getExpression();
 		}
+		if (interp.nextToken()==',') {
+			interp.getComma();
+			String title = getString();
+			rt = getResultsTable(title);
+		}
 		interp.getRightParen();
-		ResultsTable rt = getResultsTable(true);
-		int counter = rt.getCounter();
+		if (rt==null)
+			rt = getResultsTable(true);
+		int counter = rt.size();
 		if (row==-1) row = counter-1;
 		if (row<0 || row>=counter)
 			interp.error("Row ("+row+") out of range");
@@ -1167,7 +1188,7 @@ public class Functions implements MacroC
 	String getResultLabel() {
 		int row = (int)getArg();
 		ResultsTable rt = getResultsTable(true);
-		int counter = rt.getCounter();
+		int counter = rt.size();
 		if (row<0 || row>=counter)
 			interp.error("Row ("+row+") out of range");
 		String label = rt.getLabel(row);
@@ -1195,7 +1216,7 @@ public class Functions implements MacroC
 		return rt;
 	}
 
-	void setResult() {
+	void setResult(ResultsTable rt) {
 		interp.getLeftParen();
 		String column = getString();
 		interp.getComma();
@@ -1208,11 +1229,19 @@ public class Functions implements MacroC
 			stringValue = getString();
 		else
 			value = interp.getExpression();
+		if (interp.nextToken()==',') {
+			interp.getComma();
+			String title = getString();
+			rt = getResultsTable(title);
+		}
 		interp.getRightParen();
-		ResultsTable rt = Analyzer.getResultsTable();
-		if (row<0 || row>rt.getCounter())
+		if (rt==null) {
+			rt = Analyzer.getResultsTable();
+			resultsPending = true;
+		}
+		if (row<0 || row>rt.size())
 			interp.error("Row ("+row+") out of range");
-		if (row==rt.getCounter())
+		if (row==rt.size())
 			rt.incrementCounter();
 		try {
 			if (stringValue!=null) {
@@ -1222,7 +1251,6 @@ public class Functions implements MacroC
 					rt.setValue(column, row, stringValue);
 			} else
 				rt.setValue(column, row, value);
-			resultsPending = true;
 		} catch (Exception e) {
 			interp.error(""+e.getMessage());
 		}
@@ -1379,26 +1407,6 @@ public class Functions implements MacroC
 			return new Variable(array).getArray();
 	}
 
-	Variable[] newArray() {
-		if (interp.nextToken()!='(' || interp.nextNextToken()==')') {
-			interp.getParens();
-			return new Variable[0];
-		}
-		interp.getLeftParen();
-		int next = interp.nextToken();
-		int nextNext = interp.nextNextToken();
-		if (next==STRING_CONSTANT || nextNext==','
-		|| nextNext=='[' || next=='-' || next==PI)
-			return initNewArray();
-		int size = (int)interp.getExpression();
-		if (size<0) interp.error("Negative array size");
-		interp.getRightParen();
-    	Variable[] array = new Variable[size];
-    	for (int i=0; i
@@ -570,7 +583,7 @@ public class CurveFitter implements User public String getStatusString() { return errorString != null ? errorString : minimizer.STATUS_STRING[getStatus()]; } - + /** Get a string with detailed description of the curve fitting results (several lines, * including the fit parameters). */ @@ -622,17 +635,17 @@ public class CurveFitter implements User public int getIterations() { return linearRegressionUsed ? 1 : minimizer.getIterations(); } - + /** Get maximum number of iterations allowed (sum of iteration count for all restarts) */ public int getMaxIterations() { return minimizer.getMaxIterations(); } - + /** Set maximum number of iterations allowed (sum of iteration count for all restarts) */ public void setMaxIterations(int maxIter) { minimizer.setMaxIterations(maxIter); } - + /** Get maximum number of simplex restarts to do. See Minimizer.setMaxRestarts for details. */ public int getRestarts() { return minimizer.getMaxRestarts(); @@ -682,10 +695,10 @@ public class CurveFitter implements User /** Returns an array of fit names with nicer sorting */ public static String[] getSortedFitList() { if (sortedFitList == null) { - String[] l = new String[fitList.length]; + String[] tmpList = new String[fitList.length]; for (int i=0; i 0) // convert to shorter arrays with only the parameters used by the minimizer for (int i=0, iNew=0; i0, change by factor of e between xMin & xMax case EXP_RECOVERY: // a*(1-exp(-bx)) + c initialParams[1] = 1./(xMax-xMin+1e-100); - initialParams[0] = (yMax-yMin)/Math.exp(initialParams[1]*xMean) * Math.signum(slope) * + initialParams[0] = (yMax-yMin+1e-100)/Math.exp(initialParams[1]*xMean) * Math.signum(slope) * fitType==EXP_RECOVERY ? 1 : -1; // don't care, we will do this via regression initialParams[2] = 0.5*yMean; // don't care, we will do this via regression break; @@ -949,7 +967,7 @@ public class CurveFitter implements User break; case LOG2: // y = a+b*ln(x-c) initialParams[0] = yMean; // don't care, we will do this via regression - initialParams[1] = (yMax-yMin)/(xMax-xMin+1e-100); // don't care, we will do this via regression + initialParams[1] = (yMax-yMin+1e-100)/(xMax-xMin+1e-100); // don't care, we will do this via regression initialParams[2] = Math.min(0., -xMin-0.1*(xMax-xMin)-1e-100); break; case RODBARD: // d+(a-d)/(1+(x/c)^b) @@ -987,7 +1005,7 @@ public class CurveFitter implements User initialParams[0] = yMax; //actually don't care, we will do this via regression initialParams[1] = xOfMax; //actually don't care, we will do this via regression initialParams[2] = 0.39894 * (xMax-xMin) * yMean/(yMax+1e-100); - break; + break; case CHAPMAN: // a*(1-exp(-b*x))^c initialParams[0] = yMax; initialParams[2] = 1.5; // just assuming any reasonable value @@ -999,6 +1017,12 @@ public class CurveFitter implements User if(Double.isNaN(initialParams[1]) || initialParams[1]>1000./xMax) //in case an outlier at the beginning has fooled us initialParams[1] = 10./xMax; break; + case ERF: // a+b*erf((x-c)/d) + initialParams[0] = 0.5*(yMax+yMin); //actually don't care, we will do this via regression + initialParams[1] = 0.5*(yMax-yMin+1e-100) * (lasty>firsty ? 1 : -1); //actually don't care, we will do this via regression + initialParams[2] = xMin + (xMax-xMin)*(lasty>firsty ? yMax - yMean : yMean - yMin)/(yMax-yMin+1e-100); + initialParams[3] = 0.1 * (xMax-xMin+1e-100); + break; //no case CUSTOM: here, was done above } } @@ -1037,7 +1061,7 @@ public class CurveFitter implements User // therefore an estimate for a and B is sqrt(tm-t0) // K [a] can now be calculated from these estimates initialParamVariations[0] = 0.1*Math.max(yMax-yMin, Math.abs(yMax)); - double ab = xOfMax - firstx + 0.1*(xMax-xMin); + double ab = xOfMax - firstx + 0.1*(xMax-xMin+1e-100); initialParamVariations[2] = 0.1*Math.sqrt(ab); initialParamVariations[3] = 0.1*Math.sqrt(ab); break; @@ -1047,6 +1071,10 @@ public class CurveFitter implements User case GAUSSIAN_NOOFFSET: // a*exp(-(x-b)^2/(2c^2)) initialParamVariations[1] = 0.2*initialParams[2]; //(and default for c) break; + case ERF: // a+b*erf((x-c)/d) + initialParamVariations[2] = 0.1 * (xMax-xMin+1e-100); + initialParamVariations[3] = 0.5 * initialParams[3]; + break; } } return true; @@ -1106,7 +1134,11 @@ public class CurveFitter implements User break; case GAUSSIAN_NOOFFSET: // a*exp(-(x-b)^2/(2c^2)) factorParam = 0; - break; + break; + case ERF: // a + b*erf((x-c)/d) + offsetParam = 0; + factorParam = 1; + break; } numRegressionParams = 0; if (offsetParam >= 0) numRegressionParams++; @@ -1186,7 +1218,7 @@ public class CurveFitter implements User /** Get correct params and revert xData, yData if fit has been done via another function */ private void postProcessModifiedFitType(int fitType) { if (fitType == POWER_REGRESSION || fitType == EXP_REGRESSION) // ln y = ln (a*x^b) = ln a + b ln x - finalParams[0] = ySign * Math.exp(finalParams[0]); //or: ln (+,-)y = ln ((+,-)a*exp(bx)) = ln (+,-)a + bx + finalParams[0] = ySign * Math.exp(finalParams[0]); //or: ln (+,-)y = ln ((+,-)a*exp(bx)) = ln (+,-)a + bx if (fitType == GAUSSIAN) // a + b exp(-...) to a + (b-a)*exp(-...) finalParams[1] += finalParams[0]; else if (fitType == RODBARD || fitType == RODBARD2) //d+a/(1+(x/c)^b) to d+(a-d)/(1+(x/c)^b) @@ -1217,7 +1249,7 @@ public class CurveFitter implements User gd.addNumericField("Number of restarts:", minimizer.getMaxRestarts(), 0); gd.addNumericField("Error tolerance [1*10^(-x)]:", -(Math.log(maxRelError)/Math.log(10)), 0); gd.showDialog(); - if (gd.wasCanceled()) + if (gd.wasCanceled()) return; // read initial parameters: for (int i = 0; i < numParams; i++) { @@ -1236,10 +1268,10 @@ public class CurveFitter implements User n = gd.getNextNumber(); setMaxError(Math.pow(10.0, -n)); } - + /** * Gets index of highest value in an array. - * + * * @param array the array. * @return Index of highest value. */ @@ -1255,4 +1287,4 @@ public class CurveFitter implements User return index; } -} \ No newline at end of file +} diff -pruN 1.51q-1/ij/measure/ResultsTable.java 1.52g-1/ij/measure/ResultsTable.java --- 1.51q-1/ij/measure/ResultsTable.java 2017-07-07 16:45:06.000000000 +0000 +++ 1.52g-1/ij/measure/ResultsTable.java 2018-09-14 17:34:40.000000000 +0000 @@ -6,6 +6,7 @@ import ij.process.*; import ij.gui.Roi; import ij.util.Tools; import ij.io.*; +import ij.macro.*; import java.awt.*; import java.text.*; import java.util.*; @@ -55,23 +56,36 @@ public class ResultsTable implements Clo private String rowLabelHeading = ""; private char delimiter = '\t'; private boolean headingSet; - private boolean showRowNumbers = true; + private boolean showRowNumbers; private Hashtable stringColumns; private boolean NaNEmptyCells; private boolean quoteCommas; + private String title; + private boolean columnDeleted; /** Constructs an empty ResultsTable with the counter=0, no columns and the precision set to 3 or the "Decimal places" value in Analyze/Set Measurements if that value is higher than 3. */ public ResultsTable() { + init(); + } + + /** Constructs a ResultsTable with 'nRows' rows. */ + public ResultsTable(Integer nRows) { + init(); + for (int i=0; iprecision) precision = (short)p; for (int i=0; i0.*/ + /** Adds a value to the end of the given column. */ public void addValue(int column, double value) { if (column>=maxColumns) addColumns(); @@ -154,7 +168,7 @@ public class ResultsTable implements Clo if (NaNEmptyCells) Arrays.fill(columns[column], Double.NaN); if (headings[column]==null) - headings[column] = "---"; + headings[column] = "C"+(column+1); if (column>lastColumn) lastColumn = column; } columns[column][counter-1] = value; @@ -165,10 +179,10 @@ public class ResultsTable implements Clo } /** Adds a value to the end of the given column. If the column - does not exist, it is created. Counter must be >0. + does not exist, it is created. There is an example at:
http://imagej.nih.gov/ij/plugins/sine-cosine.html - */ + */ public void addValue(String column, double value) { if (column==null) throw new IllegalArgumentException("Column is null"); @@ -180,7 +194,7 @@ public class ResultsTable implements Clo } /** Adds a string value to the end of the given column. If the column - does not exist, it is created. Counter must be >0. */ + does not exist, it is created. */ public void addValue(String column, String value) { if (column==null) throw new IllegalArgumentException("Column is null"); @@ -192,7 +206,7 @@ public class ResultsTable implements Clo keep[index] = true; } - /** Adds a label to the beginning of the current row. Counter must be >0. */ + /** Adds a label to the beginning of the current row. */ public void addLabel(String label) { if (rowLabelHeading.equals("")) rowLabelHeading = "Label"; @@ -214,7 +228,7 @@ public class ResultsTable implements Clo } /** Adds a label to the beginning of the specified row, - or updates an existing lable, where 0<=rowshow() to update the window displaying the table. */ public void setLabel(String label, int row) { @@ -429,7 +443,7 @@ public class ResultsTable implements Clo } /** Sets the value of the given column and row, where - where 0<=row<counter. If the specified column does + where 0<=row<size(). If the specified column does not exist, it is created. When adding columns, show() must be called to update the window that displays the table.*/ @@ -443,7 +457,7 @@ public class ResultsTable implements Clo } /** Sets the value of the given column and row, where - where 0<=column<=(lastRow+1 and 0<=row<=counter. */ + where 0<=column<=(lastRow+1 and 0<=row<=size(). */ public void setValue(int column, int row, double value) { if (column>=maxColumns) addColumns(); @@ -462,14 +476,14 @@ public class ResultsTable implements Clo if (column>lastColumn) lastColumn = column; } columns[column][row] = value; - if (counter<25) { - if ((int)value!=value && !Double.isNaN(value)) - decimalPlaces[column] = (short)precision; - } + if (headings[column]==null) + headings[column] = "C"+(column+1); + if ((int)value!=value && !Double.isNaN(value)) + decimalPlaces[column] = (short)precision; } /** Sets the string value of the given column and row, where - where 0<=row<counter. If the specified column does + where 0<=row<size(). If the specified column does not exist, it is created. When adding columns, show() must be called to update the window that displays the table.*/ @@ -483,7 +497,7 @@ public class ResultsTable implements Clo } /** Sets the string value of the given column and row, where - where 0<=column<=(lastRow+1 and 0<=row<=counter. */ + where 0<=column<=(lastRow+1 and 0<=row<=size(). */ public void setValue(int column, int row, String value) { setValue(column, row, Double.NaN); if (stringColumns==null) @@ -522,7 +536,7 @@ public class ResultsTable implements Clo for (int i=0; i<=lastColumn; i++) { if (columns[i]!=null) { heading = headings[i]; - if (heading==null) heading ="---"; + if (heading==null) heading ="C"+(i+1); sb.append(heading); if (i!=lastColumn) sb.append(delimiter); } @@ -545,7 +559,7 @@ public class ResultsTable implements Clo for (int i=0; i<=lastColumn; i++) { if (columns[i]!=null) { heading = headings[i]; - if (heading==null) heading ="---"; + if (heading==null) heading ="C"+(i+1); temp[index++] = heading; } } @@ -560,7 +574,7 @@ public class ResultsTable implements Clo } /** Returns a tab or comma delimited string representing the - given row, where 0<=row<=counter-1. */ + given row, where 0<=row<=size()-1. */ public String getRowAsString(int row) { if ((row<0) || (row>=counter)) throw new IllegalArgumentException("Row out of range: "+row); @@ -596,6 +610,74 @@ public class ResultsTable implements Clo return new String(sb); } + /** Implements the Table.getColumn() macro function. */ + public Variable[] getColumnAsVariables(String column) { + int col = getColumnIndex(column); + if (col==COLUMN_NOT_FOUND) + throw new IllegalArgumentException("\""+column+"\" column not found"); + boolean firstValueNumeric = true; + int nValues = size(); + Variable[] values = new Variable[nValues]; + for (int row=0; row=0 && row0 && size()>initialSize) { + for (int c=0; c<=lastColumn; c++) { + if (c!=col && columns[c]!=null) { + String heading = headings[c]; + if (heading!=null) { + for (int i=initialSize; i0?getRowAsString(0).length():15, getColumnHeadings().length()); + int width = 100 + chars*10; + if (width<180) width=180; + if (width>700) width=700; if (showRowNumbers) width += 50; - win = new TextWindow(windowTitle, "", width, 300); + int height = 300; + if (size()>15) height = 400; + if (size()>30 && width>300) height = 500; + win = new TextWindow(windowTitle, "", width, height); cloneNeeded = true; } tp = win.getTextPanel(); @@ -980,6 +1105,7 @@ public class ResultsTable implements Clo firstColumn = 1; } ResultsTable rt = new ResultsTable(); + rt.showRowNumbers(true); for (int i=firstRow; i newColumnList = new ArrayList(); + String[] variables = interp.getVariableNames(); + for (String variable:variables) { // check for variables that make a new Column + int columnNumber = indexOf(columnNames, variable); + if (columnNumber >= 0) // variable is a know column + columnInUse[columnNumber] = macro.indexOf(variable) >=0; + else if (Character.isUpperCase(variable.charAt(0))) { + getFreeColumn(variable); // create new column + newColumnList.add(variable); + } + } + String[] newColumns = newColumnList.toArray(new String[0]); + int[] newColumnIndices = new int[newColumns.length]; + for (int i=0; i='0' && names[i].charAt(0)<='9') // variable must not start with digit + names[i] = "_"+names[i]; + names[i] = names[i].replaceAll("[^A-Za-z0-9_]","_"); // replace unsuitable characters with underscores + for (int postfix=0; ; postfix++) { + boolean isDuplicate = false; + for (int j=0; j 0) // remove trailing underscore+postfix + names[i] = names[i].substring(0, names[i].lastIndexOf('_')); + names[i] += "_"+postfix; // add underscore+postfix number + } + } + return names; + } + + public String getTitle() { + if (title==null && this==Analyzer.getResultsTable()) + title = "Results"; + return title; + } + + public boolean columnDeleted() { + return columnDeleted; + } + + /** Selects the row in the "Results" table assocuiated with the specified Roi. + The row number is obtained from the roi name.. + */ + public static boolean selectRow(Roi roi) { + if (roi==null) + return false; + String name = roi.getName(); + if (name==null || name.length()>8) + return false ; + Frame frame = WindowManager.getFrame("Results"); + if (frame==null) + return false; + if (!(frame instanceof TextWindow)) + return false ; + ResultsTable rt = ((TextWindow)frame).getResultsTable(); + if (rt==null || rt!=Analyzer.getResultsTable()) + return false ; + double n = Tools.parseDouble(name); + if (Double.isNaN(n)) + return false; + int index = (int)n - 1; + if (index<0 || index>=rt.size()) + return false; + ((TextWindow)frame).getTextPanel().setSelection(index, index); + return true; + } } diff -pruN 1.51q-1/ij/measure/ResultsTableMacros.java 1.52g-1/ij/measure/ResultsTableMacros.java --- 1.51q-1/ij/measure/ResultsTableMacros.java 1970-01-01 00:00:00.000000000 +0000 +++ 1.52g-1/ij/measure/ResultsTableMacros.java 2018-04-05 16:15:26.000000000 +0000 @@ -0,0 +1,214 @@ +package ij.measure; +import ij.plugin.filter.Analyzer; +import ij.plugin.frame.Recorder; +import ij.plugin.*; +import ij.*; +import ij.gui.*; +import ij.text.*; +import java.awt.*; +import java.awt.event.*; + + +/** This class implements the Apply Macro command in tables. +* @author Michael Schmid +*/ +public class ResultsTableMacros implements Runnable, DialogListener, ActionListener, KeyListener { + private static String NAME = "TableMacro.ijm"; + private String defaultMacro = "Sin=sin(row*0.1);\nCos=cos(row*0.1);\nSqr=Sin*Sin+Cos*Cos;"; + private GenericDialog gd; + private ResultsTable rt, rtBackup; + private Button runButton, resetButton, openButton, saveButton; + private String title; + private int runCount; + private TextArea ta; + + public ResultsTableMacros(ResultsTable rt) { + this.rt = rt; + title = rt!=null?rt.getTitle():null; + Thread thread = new Thread(this, "ResultTableMacros"); + thread.start(); + } + + private void showDialog() { + if (rt==null) + rt = Analyzer.getResultsTable(); + if (rt==null || rt.size()==0) { + IJ.error("Results Table required"); + return; + } + String[] temp = rt.getHeadingsAsVariableNames(); + String[] variableNames = new String[temp.length+2]; + variableNames[0] = "Insert..."; + variableNames[1] = "row"; + for (int i=2; i

Macro Equations for Results Tables

    "+ + "
  • The macro, or a selection, is applied to each row of the table."+ + "
  • A new variable starting with an Uppercase character creates
    a new column."+ + "
  • A new variable starting with a lowercase character is temporary."+ + "
  • The variable row (row index) is pre-defined.\n"+ + "
  • String operations are supported for the 'Label' column only (if
    enabled"+ + "with Analyze>Set Measurements>Display Label)."+ + "
  • Click \"Run\", or press "+(IJ.isMacOSX()?"cmd":"ctrl") + "-r, to apply the macro code to the table."+ + "
  • Select a line and press "+(IJ.isMacOSX()?"cmd":"ctrl") + "-r to apply a line of macro code."+ + "
  • Click \"Reset\" to revert to the original version of the table."+ + "
  • The code is saved at macros/TableMacro.ijm, and the
    \"Apply Macro\" command is recorded, when you click \"OK\"."+ + "
  • All Table. macro functions (such as Table.size) refer to the
    current (frontmost) table unless the table name is given."+ + "
"); + + gd.addDialogListener(this); + gd.showDialog(); + if (gd.wasCanceled()) { // dialog cancelled? + rt = rtBackup; + updateDisplay(); + return; + } + if (runCount==0) + applyMacro(); + if (Recorder.record) { + String macro = getMacroCode(); + macro = macro.replaceAll("\n", " "); + if (Recorder.scriptMode()) { + Recorder.recordCall("title = \""+title+"\";"); + Recorder.recordCall("frame = WindowManager.getFrame(title);"); + Recorder.recordCall("rt = frame.getResultsTable();"); + Recorder.recordCall("rt.applyMacro(\""+macro+"\");"); + Recorder.recordCall("rt.show(title);"); + } else { + if (title.equals("Results")) + Recorder.record("Table.applyMacro", macro); + else + Recorder.record("Table.applyMacro", macro, title); + } + } + IJ.saveString(ta.getText(), IJ.getDir("macros")+NAME); + } + + private void applyMacro() { + String code = getMacroCode(); + rt.applyMacro(code); + updateDisplay(); + runCount++; + } + + private String getMacroCode() { + int start = ta.getSelectionStart(); + int end = ta.getSelectionEnd(); + return start==end?ta.getText():ta.getSelectedText(); + } + + public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) { + final String variableName = gd.getNextChoice(); + if (e!=null && (e.getSource() instanceof Choice) && !variableName.equals("Insert...")) { + final int pos = ta.getCaretPosition(); + ((Choice)e.getSource()).select(0); + final TextArea textArea = ta; + new Thread(new Runnable() { + public void run() { + IJ.wait(100); + textArea.insert(variableName, pos); + textArea.setCaretPosition(pos+variableName.length()); + textArea.requestFocus(); + }}).start(); + } + return true; + } + + public void actionPerformed(ActionEvent e) { + Object source = e.getSource(); + if (source==runButton) { + applyMacro(); + } else if (source==resetButton) { + rt = (ResultsTable)rtBackup.clone(); + updateDisplay(); + } else if (source==openButton) { + String macro = IJ.openAsString(null); + if (macro==null) + return; + if (macro.startsWith("Error: ")) { + IJ.error(macro); + return; + } else + ta.setText(macro); + } else if (source==saveButton) { + ta.selectAll(); + String macro = ta.getText(); + ta.select(0, 0); + IJ.saveString(macro, null); + } + + } + + public void keyPressed(KeyEvent e) { + int flags = e.getModifiers(); + boolean control = (flags & KeyEvent.CTRL_MASK) != 0; + boolean meta = (flags & KeyEvent.META_MASK) != 0; + int keyCode = e.getKeyCode(); + if (keyCode==KeyEvent.VK_R && (control||meta)) + applyMacro(); + if (keyCode==KeyEvent.VK_Z && (control||meta)) { + rt = (ResultsTable)rtBackup.clone(); + updateDisplay(); + } + } + + private void updateDisplay() { + if (title!=null) + rt.show(title); + } + + public void keyReleased(KeyEvent e) { + } + + public void keyTyped(KeyEvent e) { + } + + private String getMacro() { + String macro = IJ.openAsString(IJ.getDir("macros")+NAME); + if (macro==null || macro.startsWith("Error:")) + return defaultMacro; + else { + macro = macro.replaceAll("rowNumber", "row"); + macro = macro.replaceAll("rowIndex", "row"); + return macro; + } + } + + public void run() { + rtBackup = (ResultsTable)rt.clone(); + showDialog(); + } + +} + diff -pruN 1.51q-1/ij/Menus.java 1.52g-1/ij/Menus.java --- 1.51q-1/ij/Menus.java 2017-09-20 17:51:50.000000000 +0000 +++ 1.52g-1/ij/Menus.java 2018-07-03 22:30:40.000000000 +0000 @@ -231,6 +231,7 @@ public class Menus { addPlugInItem(help, "Macros...", "ij.plugin.BrowserLauncher(\""+IJ.URL+"/macros/\")", 0, false); addPlugInItem(help, "Macro Functions...", "ij.plugin.BrowserLauncher(\""+IJ.URL+"/developer/macro/functions.html\")", 0, false); Menu examplesMenu = getExamplesMenu(ij); + addPlugInItem(examplesMenu, "Open as Panel", "ij.plugin.SimpleCommands(\"opencp\")", 0, false); help.add(examplesMenu); help.addSeparator(); addPlugInItem(help, "Update ImageJ...", "ij.plugin.ImageJ_Updater", 0, false); @@ -264,12 +265,23 @@ public class Menus { public static Menu getExamplesMenu(ActionListener listener) { Menu menu = new Menu("Examples"); - Menu submenu = new Menu("Macro"); + Menu submenu = new Menu("Plots"); + addExample(submenu, "Example Plot", "Example_Plot_.ijm"); + addExample(submenu, "Semi-log Plot", "Semi-log_Plot_.ijm"); + addExample(submenu, "Arrow Plot", "Arrow_Plot_.ijm"); + addExample(submenu, "Damped Wave Plot", "Damped_Wave_Plot_.ijm"); + addExample(submenu, "Dynamic Plot", "Dynamic_Plot_.ijm"); + addExample(submenu, "Dynamic Plot 2D", "Dynamic_Plot_2D_.ijm"); + addExample(submenu, "Custom Plot Symbols", "Custom_Plot_Symbols_.ijm"); + addExample(submenu, "Histograms", "Histograms_.ijm"); + addExample(submenu, "Bar Charts", "Bar_Charts_.ijm"); + addExample(submenu, "Shapes", "Plot_Shapes_.ijm"); + submenu.addActionListener(listener); + menu.add(submenu); + + submenu = new Menu("Macro"); addExample(submenu, "Sphere", "Sphere.ijm"); addExample(submenu, "Dialog Box", "Dialog_Box.ijm"); - addExample(submenu, "Example Plot", "Example_Plot.ijm"); - addExample(submenu, "Semi-log Plot", "Semi-log_Plot.ijm"); - addExample(submenu, "Arrow Plot", "Arrow_Plot.ijm"); addExample(submenu, "Process Folder", "Batch_Process_Folder.ijm"); addExample(submenu, "OpenDialog Demo", "OpenDialog_Demo.ijm"); addExample(submenu, "Sine/Cosine Table", "Sine_Cosine_Table.ijm"); @@ -280,18 +292,25 @@ public class Menus { addExample(submenu, "Dual Progress Bars", "Dual_Progress_Bars.ijm"); addExample(submenu, "Grab Viridis Colormap", "Grab_Viridis_Colormap.ijm"); addExample(submenu, "Custom Measurement", "Custom_Measurement.ijm"); + addExample(submenu, "Synthetic Images", "Synthetic_Images.ijm"); + addExample(submenu, "Spiral Rotation", "Spiral_Rotation.ijm"); submenu.addSeparator(); addExample(submenu, "Circle Tool", "Circle_Tool.ijm"); addExample(submenu, "Star Tool", "Star_Tool.ijm"); submenu.addActionListener(listener); menu.add(submenu); + submenu = new Menu("JavaScript"); addExample(submenu, "Sphere", "Sphere.js"); addExample(submenu, "Plasma Cloud", "Plasma_Cloud.js"); addExample(submenu, "Cloud Debugger", "Cloud_Debugger.js"); + addExample(submenu, "Synthetic Images", "Synthetic_Images.js"); + addExample(submenu, "Points", "Points.js"); + addExample(submenu, "Spiral Rotation", "Spiral_Rotation.js"); addExample(submenu, "Example Plot", "Example_Plot.js"); addExample(submenu, "Semi-log Plot", "Semi-log_Plot.js"); addExample(submenu, "Arrow Plot", "Arrow_Plot.js"); + addExample(submenu, "Dynamic Plot", "Dynamic_Plot.js"); addExample(submenu, "Process Folder", "Batch_Process_Folder.js"); addExample(submenu, "Sine/Cosine Table", "Sine_Cosine_Table.js"); addExample(submenu, "Non-numeric Table", "Non-numeric_Table.js"); @@ -301,6 +320,8 @@ public class Menus { addExample(submenu, "Gamma Adjuster", "Gamma_Adjuster.js"); addExample(submenu, "Custom Measurement", "Custom_Measurement.js"); addExample(submenu, "Terabyte VirtualStack", "Terabyte_VirtualStack.js"); + addExample(submenu, "Event Listener", "Event_Listener.js"); + addExample(submenu, "FFT Filter", "FFT_Filter.js"); submenu.addActionListener(listener); menu.add(submenu); submenu = new Menu("BeanShell"); @@ -314,7 +335,7 @@ public class Menus { submenu = new Menu("Python"); addExample(submenu, "Sphere", "Sphere.py"); addExample(submenu, "Animated Gaussian Blur", "Animated_Gaussian_Blur.py"); - addExample(submenu, "Rotational Animation", "Rotational_Animation.py"); + addExample(submenu, "Spiral Rotation", "Spiral_Rotation.py"); addExample(submenu, "Overlay", "Overlay.py"); submenu.addActionListener(listener); menu.add(submenu); @@ -329,7 +350,7 @@ public class Menus { submenu.addActionListener(listener); menu.add(submenu); menu.addSeparator(); - CheckboxMenuItem item = new CheckboxMenuItem("Autorun"); + CheckboxMenuItem item = new CheckboxMenuItem("Autorun Examples"); menu.add(item); item.addItemListener(ij); item.setState(Prefs.autoRunExamples); @@ -427,7 +448,8 @@ public class Menus { if (applet==null && f.exists() && f.isDirectory()) list = f.list(); if (list==null) return; - if (IJ.isLinux()) StringSorter.sort(list); + if (IJ.isLinux() || IJ.isMacOSX()) + Arrays.sort(list); submenu.addSeparator(); for (int i=0; i=KeyEvent.VK_F1 && keyCode<=KeyEvent.VK_F12; if (keyCode>0 && !functionKey) command = command.substring(0,openBracket); - //IJ.write(command+": "+shortcut); } } if (keyCode>=KeyEvent.VK_F1 && keyCode<=KeyEvent.VK_F12) { @@ -763,6 +784,8 @@ public class Menus { /** Returns the specified ImageJ menu (e.g., "File>New") or null if it is not found. */ public static Menu getImageJMenu(String menuPath) { + if (menus==null) + return null; if (menus.get(menuPath)!=null) return getMenu(menuPath, false); else @@ -1044,7 +1067,6 @@ public class Menus { v.addElement(dir+name); classCount++; className = name; - //IJ.write("File: "+f+"/"+name); } else if (hasUnderscore && (name.endsWith(".jar") || name.endsWith(".zip"))) { if (jarFiles==null) jarFiles = new Vector(); jarFiles.addElement(f.getPath() + File.separator + name); @@ -1305,7 +1327,6 @@ public class Menus { window.insertSeparator(WINDOW_MENU_ITEMS+windowMenuItems2); windowMenuItems2++; } - //IJ.write("insertWindowMenuItem: "+windowMenuItems2); } /** Adds one image to the end of the Window menu. */ @@ -1521,17 +1542,6 @@ public class Menus { } void installStartupMacroSet() { - if (applet!=null) { - String docBase = ""+applet.getDocumentBase(); - if (!docBase.endsWith("/")) { - int index = docBase.lastIndexOf("/"); - if (index!=-1) - docBase = docBase.substring(0, index+1); - } - IJ.runPlugIn("ij.plugin.URLOpener", docBase+"StartupMacros.txt"); - return; - } - if (macrosPath==null) { try { (new MacroInstaller()).installFromIJJar("/macros/StartupMacros.txt"); @@ -1547,6 +1557,9 @@ public class Menus { (new MacroInstaller()).installFromIJJar("/macros/StartupMacros.txt"); return; } + } else { + if ("StartupMacros.fiji.ijm".equals(f.getName())) + path = f.getPath(); } String libraryPath = macrosPath + "Library.txt"; f = new File(libraryPath); @@ -1554,7 +1567,7 @@ public class Menus { try { MacroInstaller mi = new MacroInstaller(); if (isLibrary) mi.installLibrary(libraryPath); - mi.installFile(path); + mi.installStartupMacros(path); nMacros += mi.getMacroCount(); } catch (Exception e) {} } diff -pruN 1.51q-1/ij/OtherInstance.java 1.52g-1/ij/OtherInstance.java --- 1.51q-1/ij/OtherInstance.java 2016-09-08 20:52:26.000000000 +0000 +++ 1.52g-1/ij/OtherInstance.java 2018-03-31 09:24:48.000000000 +0000 @@ -217,7 +217,7 @@ public class OtherInstance { if (s!=null) { try { return Integer.decode(s).intValue(); - } catch (NumberFormatException e) {IJ.write(""+e);} + } catch (NumberFormatException e) {IJ.log(""+e);} } return -1; } diff -pruN 1.51q-1/ij/plugin/Animator.java 1.52g-1/ij/plugin/Animator.java --- 1.51q-1/ij/plugin/Animator.java 2016-07-27 11:50:34.000000000 +0000 +++ 1.52g-1/ij/plugin/Animator.java 2018-02-13 10:42:56.000000000 +0000 @@ -19,14 +19,12 @@ public class Animator implements PlugIn to stop any animation and display the next or previous frame. */ public void run(String arg) { - imp = WindowManager.getCurrentImage(); - if (imp==null) - {IJ.noImage(); return;} - nSlices = imp.getStackSize(); + imp = IJ.getImage(); + nSlices = imp.getStackSize(); if (nSlices<2) {IJ.error("Stack required."); return;} ImageWindow win = imp.getWindow(); - if (win==null || !(win instanceof StackWindow)) { + if ((win==null || !(win instanceof StackWindow)) && !arg.equals("options")) { if (arg.equals("next")) imp.setSlice(imp.getCurrentSlice()+1); else if (arg.equals("previous")) @@ -215,7 +213,7 @@ public class Animator implements PlugIn else if (slices>1) lastFrame=slices; } - boolean start = !swin.getAnimate(); + boolean start = swin!=null && !swin.getAnimate(); Calibration cal = imp.getCalibration(); if (cal.fps!=0.0) animationRate = cal.fps; @@ -251,7 +249,7 @@ public class Animator implements PlugIn animationRate = speed; if (animationRate!=0.0) cal.fps = animationRate; - if (start && !swin.getAnimate()) + if (start && swin!=null && !swin.getAnimate()) startAnimation(); } diff -pruN 1.51q-1/ij/plugin/AppearanceOptions.java 1.52g-1/ij/plugin/AppearanceOptions.java --- 1.51q-1/ij/plugin/AppearanceOptions.java 2017-07-04 11:04:12.000000000 +0000 +++ 1.52g-1/ij/plugin/AppearanceOptions.java 2018-06-04 09:31:58.000000000 +0000 @@ -32,8 +32,10 @@ public class AppearanceOptions implement gd.addCheckbox("Black canvas", Prefs.blackCanvas); gd.addCheckbox("No image border", Prefs.noBorder); gd.addCheckbox("Use inverting lookup table", Prefs.useInvertingLut); - gd.addCheckbox("Auto contrast stacks (or use shift key)", Prefs.autoContrast); + gd.addCheckbox("Auto contrast stacks", Prefs.autoContrast); gd.addCheckbox("IJ window always on top", Prefs.alwaysOnTop); + if (IJ.isLinux()) + gd.addCheckbox("Cancel button on right", Prefs.dialogCancelButtonOnRight); gd.addChoice("16-bit range:", ranges, ranges[rangeIndex]); gd.addNumericField("Menu font size:", Menus.getFontSize(), 0, 3, "points"); gd.addHelp(IJ.URL+"/docs/menus/edit.html#appearance"); @@ -90,6 +92,8 @@ public class AppearanceOptions implement boolean alwaysOnTop = Prefs.alwaysOnTop; Prefs.autoContrast = gd.getNextBoolean(); Prefs.alwaysOnTop = gd.getNextBoolean(); + if (IJ.isLinux()) + Prefs.dialogCancelButtonOnRight = gd.getNextBoolean(); setMenuSize = (int)gd.getNextNumber(); if (interpolate!=Prefs.interpolateScaledImages) { Prefs.interpolateScaledImages = interpolate; diff -pruN 1.51q-1/ij/plugin/AVI_Reader.java 1.52g-1/ij/plugin/AVI_Reader.java --- 1.51q-1/ij/plugin/AVI_Reader.java 2017-04-22 19:53:02.000000000 +0000 +++ 1.52g-1/ij/plugin/AVI_Reader.java 2018-06-20 13:35:32.000000000 +0000 @@ -134,7 +134,7 @@ public class AVI_Reader extends VirtualS private final static int FOURCC_RIFF = 0x46464952; //'RIFF' private final static int FOURCC_AVI = 0x20495641; //'AVI ' private final static int FOURCC_AVIX = 0x58495641; //'AVIX' // extended AVI - private final static int FOURCC_ix00 = 0x30307869; //'ix00' // index within + private final static int FOURCC_ix00 = 0x30307869; //'ix00' // index within private final static int FOURCC_indx = 0x78646e69; //'indx' // main index private final static int FOURCC_idx1 = 0x31786469; //'idx1' // index of single 'movi' block private final static int FOURCC_LIST = 0x5453494c; //'LIST' @@ -287,7 +287,7 @@ public class AVI_Reader extends VirtualS /** The plugin is invoked by ImageJ using this method. * @param arg String 'arg' may be used to select the path. If it is an empty string, * a file open dialog is shown, and the resulting ImagePlus is displayed thereafter. - * The ImagePlus is not displayed only if 'arg' is a non-empty String; it can be + * The ImagePlus is not displayed if 'arg' is a non-empty String; it can be * retrieved with getImagePlus(). */ public void run (String arg) { @@ -610,7 +610,7 @@ public class AVI_Reader extends VirtualS * Returns next position * If not found, throws exception or returns -1 */ private long findFourccAndRead(int fourcc, boolean isList, long endPosition, - boolean throwNotFoundException) throws Exception, IOException { + boolean throwNotFoundException) throws Exception, IOException { long nextPos; boolean contentOk = false; do { @@ -623,7 +623,6 @@ public class AVI_Reader extends VirtualS } long size = readInt() & SIZE_MASK; nextPos = raFile.getFilePointer() + size; - if (nextPos>endPosition || nextPos>fileSize) { errorText = "AVI File Error: '"+fourccString(type)+"' @ 0x"+Long.toHexString(raFile.getFilePointer()-8)+" has invalid length. File damaged/truncated?"; IJ.log(errorText); // this text is also remembered as error message for showing in message box @@ -816,8 +815,8 @@ public class AVI_Reader extends VirtualS if (verbose) IJ.log(" indx entry: '" +fourccString(dwChunkId)+"' incl header "+posSizeString(qwOffset,dwSize)+timeString()); long nextIndxEntryPointer = raFile.getFilePointer(); - raFile.seek(qwOffset); //qwOffset & dwSize here include chunk header of ix00 - findFourccAndRead(FOURCC_ix00, false, qwOffset+dwSize, true); + raFile.seek(qwOffset); //qwOffset & dwSize here include chunk header of ix00 + findFourccAndRead(FOURCC_ix00, false, qwOffset+dwSize, false); raFile.seek(nextIndxEntryPointer); if (frameNumber>lastFrameToRead) break; } @@ -1140,8 +1139,8 @@ public class AVI_Reader extends VirtualS * return the pixels array of the resulting image */ private Object readFixedLengthFrame (RandomAccessFile rFile, int size) throws Exception, IOException { - if (size < scanLineSize*biHeight) //check minimum size (fixed frame length format) - throw new Exception("Data chunk size "+size+" too short ("+(scanLineSize*biHeight)+" required)"); + if (size < scanLineSize*biHeight) + size = scanLineSize*biHeight; // bugfix for RGB odd-width files byte[] rawData = new byte[size]; int n = rFile.read(rawData, 0, size); if (n < rawData.length) @@ -1571,4 +1570,9 @@ public class AVI_Reader extends VirtualS this.displayDialog = displayDialog; } + /** Open as virtual stack? */ + public void setVirtual(boolean virtual) { + isVirtual = virtual; + } + } diff -pruN 1.51q-1/ij/plugin/BatchConverter.java 1.52g-1/ij/plugin/BatchConverter.java --- 1.51q-1/ij/plugin/BatchConverter.java 2016-06-06 14:00:28.000000000 +0000 +++ 1.52g-1/ij/plugin/BatchConverter.java 2018-08-08 09:30:52.000000000 +0000 @@ -149,8 +149,6 @@ import java.io.*; inputDir.setText(path); else outputDir.setText(path); - if (IJ.isMacOSX()) - {gd.setVisible(false); gd.setVisible(true);} } } diff -pruN 1.51q-1/ij/plugin/BatchProcessor.java 1.52g-1/ij/plugin/BatchProcessor.java --- 1.51q-1/ij/plugin/BatchProcessor.java 2017-03-17 20:01:54.000000000 +0000 +++ 1.52g-1/ij/plugin/BatchProcessor.java 2018-08-08 09:50:58.000000000 +0000 @@ -43,7 +43,7 @@ import java.util.Vector; +"virtual stack. Make sure the Output folder is empty
" +"before clicking on Process.
" +"
" - +"In the macro code, the 'i' (index) and 'n' (stack size) variables
" + +"In the macro code, the 'i' (slice index) and 'n' (stack size) variables
" +"are predefined. Call setOption('SaveBatchOutput',false) to
" +"prevent the image currently being processed from being saved,
" +"effectively removing it from the output virtual stack.

" @@ -123,7 +123,7 @@ import java.util.Vector; gd = new NonBlockingGenericDialog("Batch Process"); addPanels(gd); gd.setInsets(15, 0, 5); - gd.addChoice("Output format:", formats, format); + gd.addChoice("Output_format:", formats, format); gd.setInsets(0, 0, 5); gd.addChoice("Add macro code:", code, code[0]); if (virtualStack==null) @@ -370,14 +370,10 @@ import java.util.Vector; String path = IJ.getDirectory("Input Folder"); if (path==null) return; inputDir.setText(path); - if (IJ.isMacOSX()) - {gd.setVisible(false); gd.setVisible(true);} } else if (source==output) { String path = IJ.getDirectory("Output Folder"); if (path==null) return; outputDir.setText(path); - if (IJ.isMacOSX()) - {gd.setVisible(false); gd.setVisible(true);} } else if (source==test) { thread = new Thread(this, "BatchTest"); thread.setPriority(Math.max(thread.getPriority()-2, Thread.MIN_PRIORITY)); diff -pruN 1.51q-1/ij/plugin/BMP_Reader.java 1.52g-1/ij/plugin/BMP_Reader.java --- 1.51q-1/ij/plugin/BMP_Reader.java 2014-12-30 10:24:02.000000000 +0000 +++ 1.52g-1/ij/plugin/BMP_Reader.java 2018-03-31 09:25:38.000000000 +0000 @@ -44,7 +44,7 @@ public class BMP_Reader extends ImagePlu } MemoryImageSource mis = bmp.makeImageSource(); - if (mis==null) IJ.write("mis=null"); + if (mis==null) IJ.log("BMP_Reader: mis=null"); Image img = Toolkit.getDefaultToolkit().createImage(mis); FileInfo fi = new FileInfo(); fi.fileFormat = FileInfo.BMP; @@ -182,7 +182,6 @@ class BMPDecoder { void getPalette() throws IOException { noOfEntries = actualColorsUsed; - //IJ.write("noOfEntries: " + noOfEntries); if (noOfEntries>0) { r = new byte[noOfEntries]; g = new byte[noOfEntries]; diff -pruN 1.51q-1/ij/plugin/BMP_Writer.java 1.52g-1/ij/plugin/BMP_Writer.java --- 1.51q-1/ij/plugin/BMP_Writer.java 2011-04-04 11:11:40.000000000 +0000 +++ 1.52g-1/ij/plugin/BMP_Writer.java 2018-03-31 09:26:08.000000000 +0000 @@ -180,7 +180,6 @@ public class BMP_Writer implements PlugI bfo.write (0x00); counter += pad; } - // IJ.write("counter of bytes written = " + counter); } @@ -196,7 +195,6 @@ public class BMP_Writer implements PlugI fo.write (intToWord (bfReserved1)); fo.write (intToWord (bfReserved2)); fo.write (intToDWord (bfOffBits)); - // IJ.write("biClrUsed = " + biClrUsed + " bfSize = " + bfSize + " bfOffBits=" + bfOffBits); } /* diff -pruN 1.51q-1/ij/plugin/BrowserLauncher.java 1.52g-1/ij/plugin/BrowserLauncher.java --- 1.51q-1/ij/plugin/BrowserLauncher.java 2017-06-30 16:59:44.000000000 +0000 +++ 1.52g-1/ij/plugin/BrowserLauncher.java 2018-06-22 21:39:00.000000000 +0000 @@ -54,7 +54,6 @@ public class BrowserLauncher implements /** The openURL method of com.apple.mrj.MRJFileUtils */ private static Method openURL; private static boolean error; - static {loadClasses();} /** Opens the specified URL (default is the ImageJ home page). */ @@ -73,19 +72,9 @@ public class BrowserLauncher implements */ public static void openURL(String url) throws IOException { String errorMessage = ""; - if (IJ.isMacOSX()) { - if (IJ.isJava16()) - IJ.runMacro("exec('open', getArgument())",url); - else { - try { - Method aMethod = mrjFileUtilsClass.getDeclaredMethod("sharedWorkspace", new Class[] {}); - Object aTarget = aMethod.invoke( mrjFileUtilsClass, new Object[] {}); - openURL.invoke(aTarget, new Object[] { new java.net.URL( url )}); - } catch (Exception e) { - errorMessage = ""+e; - } - } - } else if (IJ.isWindows()) { + if (IJ.isMacOSX()) + IJ.runMacro("exec('open', getArgument())",url); + else if (IJ.isWindows()) { String cmd = "rundll32 url.dll,FileProtocolHandler " + url; if (System.getProperty("os.name").startsWith("Windows 2000")) cmd = "rundll32 shell32.dll,ShellExec_RunDLL " + url; @@ -121,25 +110,5 @@ public class BrowserLauncher implements } } - /** - * Called by a static initializer to load any classes, fields, and methods - * required at runtime to locate the user's web browser. - */ - private static void loadClasses() { - if (IJ.isMacOSX() && !IJ.isJava16()) { - try { - if (new File("/System/Library/Java/com/apple/cocoa/application/NSWorkspace.class").exists()) { - ClassLoader classLoader = new URLClassLoader(new URL[]{new File("/System/Library/Java").toURL()}); - mrjFileUtilsClass = Class.forName("com.apple.cocoa.application.NSWorkspace", true, classLoader); - } else - mrjFileUtilsClass = Class.forName("com.apple.cocoa.application.NSWorkspace"); - openURL = mrjFileUtilsClass.getDeclaredMethod("openURL", new Class[] { java.net.URL.class }); - } catch (Exception e) { - IJ.log("BrowserLauncher"+e); - error = true; - } - } - } - } diff -pruN 1.51q-1/ij/plugin/ClassChecker.java 1.52g-1/ij/plugin/ClassChecker.java --- 1.51q-1/ij/plugin/ClassChecker.java 2011-04-04 11:11:40.000000000 +0000 +++ 1.52g-1/ij/plugin/ClassChecker.java 2018-03-31 09:26:26.000000000 +0000 @@ -88,7 +88,6 @@ public class ClassChecker implements Plu /** Looks for class and jar files in a subfolders of the plugins folder. */ void getSubdirectoryFiles(String path, String dir, Vector v1, Vector v2) { - //IJ.write("getSubdirectoryClassFiles: "+path+dir); if (dir.endsWith(".java")) return; File f = new File(path, dir); if (!f.isDirectory()) return; diff -pruN 1.51q-1/ij/plugin/Clipboard.java 1.52g-1/ij/plugin/Clipboard.java --- 1.51q-1/ij/plugin/Clipboard.java 2016-03-08 15:02:22.000000000 +0000 +++ 1.52g-1/ij/plugin/Clipboard.java 2018-07-14 17:03:08.000000000 +0000 @@ -38,9 +38,11 @@ public class Clipboard implements PlugIn void copy(boolean cut) { ImagePlus imp = WindowManager.getCurrentImage(); - if (imp!=null) + if (imp!=null) { imp.copy(cut); - else + if (cut) + imp.changes = true; + } else IJ.noImage(); } @@ -91,12 +93,6 @@ public class Clipboard implements PlugIn Transferable transferable = clipboard.getContents(null); boolean imageSupported = transferable.isDataFlavorSupported(DataFlavor.imageFlavor); boolean textSupported = transferable.isDataFlavorSupported(DataFlavor.stringFlavor); - if (!imageSupported && IJ.isMacOSX() && !IJ.isJava16()) { - // attempt to open PICT file using QuickTime for Java - Object mc = IJ.runPlugIn("MacClipboard", ""); - if (mc!=null && (mc instanceof ImagePlus) && ((ImagePlus)mc).getImage()!=null) - return; - } if (imageSupported) { Image img = (Image)transferable.getTransferData(DataFlavor.imageFlavor); if (img==null) { diff -pruN 1.51q-1/ij/plugin/CommandFinder.java 1.52g-1/ij/plugin/CommandFinder.java --- 1.51q-1/ij/plugin/CommandFinder.java 2016-05-06 20:01:08.000000000 +0000 +++ 1.52g-1/ij/plugin/CommandFinder.java 2018-01-05 17:46:26.000000000 +0000 @@ -269,9 +269,9 @@ public class CommandFinder implements Pl //completions.ensureIndexIsVisible(index); table.setRowSelectionInterval(index, index); } - } else if (key==KeyEvent.VK_BACK_SPACE) { - /* If someone presses backspace they probably want to - remove the last letter from the search string, so + } else if (key==KeyEvent.VK_BACK_SPACE || key==KeyEvent.VK_DELETE) { + /* If someone presses backspace or delete they probably + want to remove the last letter from the search string, so switch the focus back to the prompt: */ prompt.requestFocus(); } else if (source==table) { diff -pruN 1.51q-1/ij/plugin/Commands.java 1.52g-1/ij/plugin/Commands.java --- 1.51q-1/ij/plugin/Commands.java 2016-06-06 13:08:02.000000000 +0000 +++ 1.52g-1/ij/plugin/Commands.java 2017-10-08 18:06:40.000000000 +0000 @@ -117,6 +117,7 @@ public class Commands implements PlugIn if (gd.wasCanceled()) return false; } + Prefs.closingAll = true; for (int i=0; iTARGET19) target = TARGET19; - if (targetTARGET15 && !(IJ.isJava16()||IJ.isJava17()||IJ.isJava18()||IJ.isJava19())) + if (targetTARGET16 && !(IJ.isJava17()||IJ.isJava18()||IJ.isJava19())) + if (target>TARGET16 && IJ.javaVersion()<7) target = TARGET16; - if (target>TARGET17 && !(IJ.isJava18()||IJ.isJava19())) + if (target>TARGET17 && IJ.javaVersion()<8) target = TARGET17; - if (target>TARGET18 && !IJ.isJava19()) + if (target>TARGET18 && IJ.javaVersion()<9) target = TARGET18; Prefs.set(TARGET_KEY, target); } @@ -315,7 +313,7 @@ class PlugInExecuter implements Runnable } void runCompiledPlugin(String className) { - if (IJ.debugMode) IJ.log("Compiler: running "+className); + if (IJ.debugMode) IJ.log("Compiler: running \""+className+"\""); IJ.resetClassLoader(); ClassLoader loader = IJ.getClassLoader(); Object thePlugIn = null; @@ -332,11 +330,22 @@ class PlugInExecuter implements Runnable } catch (NoClassDefFoundError e) { String err = e.getMessage(); + if (IJ.debugMode) IJ.log("NoClassDefFoundError: "+err); int index = err!=null?err.indexOf("wrong name: "):-1; if (index>-1 && !className.contains(".")) { String className2 = err.substring(index+12, err.length()-1); className2 = className2.replace("/", "."); - runCompiledPlugin(className2); + if (className2.equals(className)) { // Java 9 error format different + int spaceIndex = err.indexOf(" "); + if (spaceIndex>-1) { + className2 = err.substring(0, spaceIndex); + className2 = className2.replace("/", "."); + } + } + if (className2.equals(className)) + IJ.error("Plugin not found: "+className2); + else + runCompiledPlugin(className2); return; } if (className.indexOf('_')!=-1) @@ -412,11 +421,9 @@ abstract class CompilerTool { } public static CompilerTool getDefault() { - if (IJ.isJava16()) { - CompilerTool javax = new JavaxCompilerTool(); - if (javax.isSupported()) - return javax; - } + CompilerTool javax = new JavaxCompilerTool(); + if (javax.isSupported()) + return javax; CompilerTool legacy = new LegacyCompilerTool(); if (legacy.isSupported()) return legacy; diff -pruN 1.51q-1/ij/plugin/Concatenator.java 1.52g-1/ij/plugin/Concatenator.java --- 1.51q-1/ij/plugin/Concatenator.java 2016-06-27 12:18:16.000000000 +0000 +++ 1.52g-1/ij/plugin/Concatenator.java 2018-04-27 21:58:06.000000000 +0000 @@ -1,7 +1,7 @@ package ij.plugin; import ij.*; import ij.macro.Interpreter; -import ij.process.*; +import ij.process.*; import ij.gui.*; import java.awt.*; import ij.measure.*; @@ -14,9 +14,8 @@ import java.awt.image.ColorModel; /** This plugin, which concatenates two or more images or stacks, * implements the Image/Stacks/Tools/Concatenate command. - * Gives the option of viewing the concatenated stack as a 4D image. + * Has the option of viewing the concatenated stack as a 4D image. * @author Jon Jackson j.jackson # ucl.ac.uk - * last modified June 29 2006 */ public class Concatenator implements PlugIn, ItemListener{ public String pluginName = "Concatenator"; @@ -27,13 +26,13 @@ public class Concatenator implements Plu private boolean batch = false; private boolean macro = false; private boolean im4D = true; - private static boolean im4D_option = false; + private static boolean im4D_option = true; private String[] imageTitles; private ImagePlus[] images; private Vector choices; private Checkbox allWindows; private final String none = "-- None --"; - private String newtitle = "Concatenated Stacks"; + private String newtitle = "Untitled"; private ImagePlus newImp; private int stackSize; private double min = 0, max = Float.MAX_VALUE; @@ -43,13 +42,10 @@ public class Concatenator implements Plu /** Optional string argument sets the name dialog boxes if called from another plugin. */ public void run(String arg) { - macro = ! arg.equals(""); - if (!showDialog()) return; - ImagePlus imp0 = images!=null&&images.length>0?images[0]:null; - if (imp0.isComposite() || imp0.isHyperStack()) - newImp =concatenateHyperstacks(images, newtitle, keep); - else - newImp = createHypervol(); + macro = !arg.equals(""); + if (!showDialog()) + return; + newImp = concatenate(images, keep); if (newImp!=null) newImp.show(); } @@ -63,34 +59,42 @@ public class Concatenator implements Plu return newImp; } - /** Concatenate two images or stacks. */ + /** Concatenates two images, stacks or hyperstacks. */ public static ImagePlus run(ImagePlus img1, ImagePlus img2) { ImagePlus[] images = new ImagePlus[2]; images[0]=img1; images[1]=img2; return (new Concatenator()).concatenate(images, false); } - /** Concatenate three images or stacks. */ + /** Concatenates three images, stacks or hyperstacks. */ public static ImagePlus run(ImagePlus img1, ImagePlus img2, ImagePlus img3) { ImagePlus[] images = new ImagePlus[3]; images[0]=img1; images[1]=img2; images[2]=img3; return (new Concatenator()).concatenate(images, false); } - /** Concatenate four images or stacks. */ + /** Concatenates four images, stacks or hyperstacks. */ public static ImagePlus run(ImagePlus img1, ImagePlus img2, ImagePlus img3, ImagePlus img4) { ImagePlus[] images = new ImagePlus[4]; images[0]=img1; images[1]=img2; images[2]=img3; images[2]=img4; return (new Concatenator()).concatenate(images, false); } - /** Concatenate five images or stacks. */ + /** Concatenates five images, stacks or hyperstacks. */ public static ImagePlus run(ImagePlus img1, ImagePlus img2, ImagePlus img3, ImagePlus img4, ImagePlus img5) { ImagePlus[] images = new ImagePlus[5]; images[0]=img1; images[1]=img2; images[2]=img3; images[2]=img4; images[5]=img5; return (new Concatenator()).concatenate(images, false); } + /** Concatenates two or more images, stacks or hyperstacks. + * @param images Array of source images + * @return Returns the concatenated images as an ImagePlus + */ + public static ImagePlus run(ImagePlus[] images) { + return (new Concatenator()).concatenate(images, false); + } + /* // Why does this not work with Java 6? public static ImagePlus run(ImagePlus... args) { @@ -98,7 +102,7 @@ public class Concatenator implements Plu } */ - /** Concatenate two or more images or stacks. */ + /** Concatenates two or more images or stacks. */ public ImagePlus concatenate(ImagePlus[] ims, boolean keepIms) { images = ims; imageTitles = new String[ims.length]; @@ -112,7 +116,17 @@ public class Concatenator implements Plu } keep = keepIms; batch = true; - newImp = createHypervol(); + ImagePlus imp0 = images[0]; + if (imp0.isComposite() || imp0.getNChannels()>1) + newImp = concatenateHyperstacks(images, newtitle, keep); + else + newImp = createHypervol(); + if (Recorder.scriptMode()) { + String args = "imp1"; + for (int i=1; i1) concatSlices = false; + if (images[i].getNFrames()>1) + concatSlices = false; if (images[i].getBitDepth()!=bitDepth || images[i].getNChannels()!=channels || (!concatSlices && images[i].getNSlices()!=slices)) { @@ -305,11 +311,12 @@ public class Concatenator implements Plu return imp2; } - boolean showDialog() { + private boolean showDialog() { boolean all_windows = false; batch = Interpreter.isBatchMode(); macro = macro || (IJ.isMacro()&&Macro.getOptions()!=null); - im4D = Menus.commandInUse("Stack to Image5D") && ! batch; + if (Menus.commandInUse("Stack to Image5D") && !batch) + im4D = true; showingDialog = Macro.getOptions()==null; if (macro) { String options = Macro.getOptions(); @@ -375,19 +382,22 @@ public class Concatenator implements Plu return false; all_windows = gd.getNextBoolean(); all_option = all_windows; + gd.setSmartRecording(true); newtitle = gd.getNextString(); + gd.setSmartRecording(false); keep = gd.getNextBoolean(); keep_option = keep; im4D = gd.getNextBoolean(); im4D_option = im4D; ImagePlus[] tmpImpArr = new ImagePlus[nImages+1]; String[] tmpStrArr = new String[nImages+1]; - int index, count = 0; - for (int i=0; i<(nImages+1); i++) { // compile a list of images to concatenate from user selection + int index=0, count = 0; + for (int i=0; i<=nImages; i++) { // compile a list of images to concatenate from user selection if (all_windows) { // Useful to not have to specify images in batch mode index = i; } else { if (i == ((nImages+1)= nImages) break; // reached the 'none' string or handled all images (in case of all_windows) diff -pruN 1.51q-1/ij/plugin/ControlPanel.java 1.52g-1/ij/plugin/ControlPanel.java --- 1.51q-1/ij/plugin/ControlPanel.java 2013-08-21 11:30:48.000000000 +0000 +++ 1.52g-1/ij/plugin/ControlPanel.java 2018-03-31 09:30:54.000000000 +0000 @@ -467,11 +467,7 @@ public class ControlPanel implements Plu void unsetPanelShowingProperty(String item) { String s = pStr2Key(item); - if (visiblePanels.remove(s)) - { - //IJ.write("removed from showing "+item); - } - //propertiesChanged=true; + visiblePanels.remove(s); } boolean hasPanelShowingProperty(String item) { @@ -786,7 +782,8 @@ class TreePanel implements pTree.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { isDragging = false; - if(pcp.requiresDoubleClick() && e.getClickCount()!=2) return; + if (e.getClickCount()!=2) + return; int selRow=pTree.getRowForLocation(e.getX(),e.getY()); if (selRow!=-1) toAction(); } @@ -868,10 +865,6 @@ class TreePanel implements TreeNode[] nodePath = node.getPath(); TreePath nTreePath = new TreePath(nodePath); String npS = nTreePath.toString(); -/* if(pcp.hasPanelShowingProperty(npS)) - { - IJ.write("has panel showing: "+npS); - }*/ DefaultMutableTreeNode[] localPath = new DefaultMutableTreeNode[nodePath.length-rootPath.length+1]; for(int i=0; i1||last1) { + if (imp.isStack()) { ImageStack stack = imp.getStack(); String label = stack.getSliceLabel(imp.getCurrentSlice()); - if (label!=null && label.indexOf('\n')>0) - imp2.setProperty("Info", label); + if (label!=null) { + if (label.length()>250 && label.indexOf('\n')>0 && label.contains("0002,")) + imp2.setProperty("Info", label); // DICOM metadata + else + imp2.setProperty("Label", label); + } if (imp.isComposite()) { LUT lut = ((CompositeImage)imp).getChannelLut(); imp2.getProcessor().setColorModel(lut); } + } else { + String label = (String)imp.getProperty("Label"); + if (label!=null) + imp2.setProperty("Label", label); } Overlay overlay = imp.getOverlay(); if (overlay!=null && !imp.getHideOverlay()) { diff -pruN 1.51q-1/ij/plugin/FFT.java 1.52g-1/ij/plugin/FFT.java --- 1.51q-1/ij/plugin/FFT.java 2017-08-30 18:58:14.000000000 +0000 +++ 1.52g-1/ij/plugin/FFT.java 2018-06-18 18:04:56.000000000 +0000 @@ -6,12 +6,13 @@ import ij.measure.*; import ij.plugin.ContrastEnhancer; import ij.measure.Calibration; import ij.util.Tools; +import ij.plugin.frame.Recorder; import java.awt.*; import java.util.*; /** This class implements the FFT, Inverse FFT and Redisplay Power Spectrum commands -in the Process/FFT submenu. It is based on Arlo Reeves' +in the Process/FFT submenu. It is based on Arlo Reeves' Pascal implementation of the Fast Hartley Transform from NIH Image (http://imagej.nih.gov/ij/docs/ImageFFT/). The Fast Hartley Transform was restricted by U.S. Patent No. 4,646,256, but was placed @@ -19,388 +20,503 @@ in the public domain by Stanford Univers Version 2008-08-25 inverse transform: mask is always symmetrized */ -public class FFT implements PlugIn, Measurements { +public class FFT implements PlugIn, Measurements { - static boolean displayFFT = true; - public static boolean displayRawPS; - public static boolean displayFHT; - public static boolean displayComplex; - public static String fileName; - - private ImagePlus imp; - private boolean padded; - private int originalWidth; - private int originalHeight; - private int stackSize = 1; - private int slice = 1; - private boolean doFFT; - - public void run(String arg) { - if (arg.equals("options")) { - showDialog(); - if (doFFT) - arg="fft"; - else - return; - } - imp = IJ.getImage(); - if (arg.equals("fft") && imp.isComposite()) { - if (!GUI.showCompositeAdvisory(imp,"FFT")) - return; - } - if (arg.equals("redisplay")) - {redisplayPowerSpectrum(); return;} - if (arg.equals("swap")) - {swapQuadrants(imp.getStack()); imp.updateAndDraw(); return;} - if (arg.equals("inverse")) { - if (imp.getTitle().startsWith("FHT of")) - {doFHTInverseTransform(); return;} - if (imp.getStackSize()==2) - {doComplexInverseTransform(); return;} - } - ImageProcessor ip = imp.getProcessor(); - Object obj = imp.getProperty("FHT"); - FHT fht = (obj instanceof FHT)?(FHT)obj:null; - stackSize = imp.getStackSize(); - boolean inverse; - if (fht==null && arg.equals("inverse")) { - IJ.error("FFT", "Frequency domain image required"); - return; - } - if (fht!=null) { - inverse = true; - imp.deleteRoi(); - } else { - if (imp.getRoi()!=null) - ip = ip.crop(); - fht = newFHT(ip); - inverse = false; - } - if (inverse) - doInverseTransform(fht); - else { - fileName = imp.getTitle(); - doForwardTransform(fht); - } - IJ.showProgress(1.0); - } - - void doInverseTransform(FHT fht) { - fht = fht.getCopy(); - doMasking(fht); - showStatus("Inverse transform"); - fht.inverseTransform(); - if (fht.quadrantSwapNeeded) - fht.swapQuadrants(); - fht.resetMinAndMax(); - ImageProcessor ip2 = fht; - if (fht.originalWidth>0) { - fht.setRoi(0, 0, fht.originalWidth, fht.originalHeight); - ip2 = fht.crop(); - } - int bitDepth = fht.originalBitDepth>0?fht.originalBitDepth:imp.getBitDepth(); - switch (bitDepth) { - case 8: ip2 = ip2.convertToByte(false); break; - case 16: ip2 = ip2.convertToShort(false); break; - case 24: - showStatus("Setting brightness"); - if (fht.rgb==null || ip2==null) { - IJ.error("FFT", "Unable to set brightness"); - return; - } - ColorProcessor rgb = (ColorProcessor)fht.rgb.duplicate(); - rgb.setBrightness((FloatProcessor)ip2); - ip2 = rgb; - fht.rgb = null; - break; - case 32: break; - } - if (bitDepth!=24 && fht.originalColorModel!=null) - ip2.setColorModel(fht.originalColorModel); - String title = imp.getTitle(); - if (title.startsWith("FFT of ")) - title = title.substring(7, title.length()); - ImagePlus imp2 = new ImagePlus("Inverse FFT of "+title, ip2); - imp2.setCalibration(imp.getCalibration()); - imp2.show(); - } - - void doForwardTransform(FHT fht) { - showStatus("Forward transform"); - fht.transform(); - showStatus("Calculating power spectrum"); - long t0 = System.currentTimeMillis(); - ImageProcessor ps = fht.getPowerSpectrum(); - if (!(displayFHT||displayComplex||displayRawPS)) - displayFFT = true; - if (displayFFT) { - ImagePlus imp2 = new ImagePlus("FFT of "+imp.getTitle(), ps); - imp2.show((System.currentTimeMillis()-t0)+" ms"); - imp2.setProperty("FHT", fht); - imp2.setCalibration(imp.getCalibration()); - String properties = "Fast Hartley Transform\n"; - properties += "width: "+fht.originalWidth + "\n"; - properties += "height: "+fht.originalHeight + "\n"; - properties += "bitdepth: "+fht.originalBitDepth + "\n"; - imp2.setProperty("Info", properties); - } - } - - FHT newFHT(ImageProcessor ip) { - FHT fht; - if (ip instanceof ColorProcessor) { - showStatus("Extracting brightness"); - ImageProcessor ip2 = ((ColorProcessor)ip).getBrightness(); - fht = new FHT(pad(ip2)); - fht.rgb = (ColorProcessor)ip.duplicate(); // save so we can later update the brightness - } else - fht = new FHT(pad(ip)); - if (padded) { - fht.originalWidth = originalWidth; - fht.originalHeight = originalHeight; - } - int bitDepth = imp.getBitDepth(); - fht.originalBitDepth = bitDepth; - if (bitDepth!=24) - fht.originalColorModel = ip.getColorModel(); - return fht; - } - - ImageProcessor pad(ImageProcessor ip) { - originalWidth = ip.getWidth(); - originalHeight = ip.getHeight(); - int maxN = Math.max(originalWidth, originalHeight); - int i = 2; - while(i1) - IJ.showStatus("FFT: " + slice+"/"+stackSize); - else - IJ.showStatus(msg); - } - - void doMasking(FHT ip) { - if (stackSize>1) - return; - float[] fht = (float[])ip.getPixels(); - ImageProcessor mask = imp.getProcessor(); - mask = mask.convertToByte(false); - if (mask.getWidth()!=ip.getWidth() || mask.getHeight()!=ip.getHeight()) - return; - ImageStatistics stats = ImageStatistics.getStatistics(mask, MIN_MAX, null); - if (stats.histogram[0]==0 && stats.histogram[255]==0) - return; - boolean passMode = stats.histogram[255]!=0; - IJ.showStatus("Masking: "+(passMode?"pass":"filter")); - mask = mask.duplicate(); - if (passMode) - changeValuesAndSymmetrize(mask, (byte)255, (byte)0); //0-254 become 0 - else - changeValuesAndSymmetrize(mask, (byte)0, (byte)255); //1-255 become 255 - //long t0=System.currentTimeMillis(); - for (int i=0; i<3; i++) - smooth(mask); - //IJ.log("smoothing time:"+(System.currentTimeMillis()-t0)); - if (IJ.debugMode || IJ.altKeyDown()) - new ImagePlus("mask", mask.duplicate()).show(); - ip.swapQuadrants(mask); - byte[] maskPixels = (byte[])mask.getPixels(); - for (int i=0; i0) pixels[n*n-i] = v1; - } else if (i0) { + fht.setRoi(0, 0, fht.originalWidth, fht.originalHeight); + ip2 = fht.crop(); + } + int bitDepth = fht.originalBitDepth>0?fht.originalBitDepth:imp.getBitDepth(); + if (!showOutput && bitDepth!=24) + bitDepth = 32; + switch (bitDepth) { + case 8: ip2 = ip2.convertToByte(false); break; + case 16: ip2 = ip2.convertToShort(false); break; + case 24: + showStatus("Setting brightness"); + if (fht.rgb==null || ip2==null) { + IJ.error("FFT", "Unable to set brightness"); + return; + } + ColorProcessor rgb = (ColorProcessor)fht.rgb.duplicate(); + rgb.setBrightness((FloatProcessor)ip2); + ip2 = rgb; + fht.rgb = null; + break; + case 32: break; + } + if (bitDepth!=24 && fht.originalColorModel!=null) + ip2.setColorModel(fht.originalColorModel); + String title = imp.getTitle(); + if (title.startsWith("FFT of ")) + title = title.substring(7, title.length()); + ImagePlus imp2 = new ImagePlus("Inverse FFT of "+title, ip2); + imp2.setCalibration(imp.getCalibration()); + if (showOutput) + imp2.show(); + else + this.imp2 = imp2; + } + + void doForwardTransform(FHT fht) { + showStatus("Forward transform"); + fht.transform(); + showStatus("Calculating power spectrum"); + long t0 = System.currentTimeMillis(); + ImageProcessor ps = fht.getPowerSpectrum(); + if (!(displayFHT||displayComplex||displayRawPS)) + displayFFT = true; + if (displayFFT) { + ImagePlus imp2 = new ImagePlus("FFT of "+imp.getTitle(), ps); + if (showOutput) + imp2.show((System.currentTimeMillis()-t0)+" ms"); + imp2.setProperty("FHT", fht); + imp2.setCalibration(imp.getCalibration()); + String properties = "Fast Hartley Transform\n"; + properties += "width: "+fht.originalWidth + "\n"; + properties += "height: "+fht.originalHeight + "\n"; + properties += "bitdepth: "+fht.originalBitDepth + "\n"; + imp2.setProperty("Info", properties); + if (!showOutput) + this.imp2 = imp2; + + } + } + + FHT newFHT(ImageProcessor ip) { + FHT fht; + if (ip instanceof ColorProcessor) { + showStatus("Extracting brightness"); + ImageProcessor ip2 = ((ColorProcessor)ip).getBrightness(); + fht = new FHT(pad(ip2)); + fht.rgb = (ColorProcessor)ip.duplicate(); // save so we can later update the brightness + } else + fht = new FHT(pad(ip)); + if (padded) { + fht.originalWidth = originalWidth; + fht.originalHeight = originalHeight; + } + int bitDepth = imp.getBitDepth(); + fht.originalBitDepth = bitDepth; + if (bitDepth!=24) + fht.originalColorModel = ip.getColorModel(); + return fht; + } + + ImageProcessor pad(ImageProcessor ip) { + originalWidth = ip.getWidth(); + originalHeight = ip.getHeight(); + int maxN = Math.max(originalWidth, originalHeight); + int i = 2; + while(i=65536) { + IJ.error("FFT", "Padded image is too large ("+maxN+"x"+maxN+")"); + return null; + } + ImageStatistics stats = ImageStatistics.getStatistics(ip, MEAN, null); + ImageProcessor ip2 = ip.createProcessor(maxN, maxN); + ip2.setValue(stats.mean); + ip2.fill(); + ip2.insert(ip, 0, 0); + padded = true; + Undo.reset(); + //new ImagePlus("padded", ip2.duplicate()).show(); + return ip2; + } + + void showStatus(String msg) { + if (stackSize>1) + IJ.showStatus("FFT: " + slice+"/"+stackSize); + else + IJ.showStatus(msg); + } + + void doMasking(FHT ip) { + if (stackSize>1) + return; + float[] fht = (float[])ip.getPixels(); + ImageProcessor mask = imp.getProcessor(); + mask = mask.convertToByte(false); + if (mask.getWidth()!=ip.getWidth() || mask.getHeight()!=ip.getHeight()) + return; + ImageStatistics stats = ImageStatistics.getStatistics(mask, MIN_MAX, null); + if (stats.histogram[0]==0 && stats.histogram[255]==0) + return; + boolean passMode = stats.histogram[255]!=0; + IJ.showStatus("Masking: "+(passMode?"pass":"filter")); + mask = mask.duplicate(); + if (passMode) + changeValuesAndSymmetrize(mask, (byte)255, (byte)0); //0-254 become 0 + else + changeValuesAndSymmetrize(mask, (byte)0, (byte)255); //1-255 become 255 + //long t0=System.currentTimeMillis(); + for (int i=0; i<3; i++) + smooth(mask); + //IJ.log("smoothing time:"+(System.currentTimeMillis()-t0)); + if (IJ.debugMode || IJ.altKeyDown()) + new ImagePlus("mask", mask.duplicate()).show(); + ip.swapQuadrants(mask); + byte[] maskPixels = (byte[])mask.getPixels(); + for (int i=0; i0) pixels[n*n-i] = v1; + } else if (i1) { + n = validateNImages(fi); info = new FileInfo[n]; long size = fi.width*fi.height*fi.getBytesPerPixel(); for (int i=0; i=0; i--) { + long offset = fi.getOffset() + i*(bytesPerImage+fi.gapBetweenImages); + if (offset+bytesPerImage<=fileLength) + return i+1; + } + return fi.nImages; + } int getInt(Properties props, String key) { Double n = getNumber(props, key); diff -pruN 1.51q-1/ij/plugin/filter/Analyzer.java 1.52g-1/ij/plugin/filter/Analyzer.java --- 1.51q-1/ij/plugin/filter/Analyzer.java 2017-05-26 14:45:30.000000000 +0000 +++ 1.52g-1/ij/plugin/filter/Analyzer.java 2018-06-26 08:31:04.000000000 +0000 @@ -103,12 +103,14 @@ public class Analyzer implements PlugInF public void run(ImageProcessor ip) { measure(); - displayResults(); + Roi roi = imp.getRoi(); + if (roi==null || roi.getType()!=Roi.POINT) + displayResults(); if ((measurements&ADD_TO_OVERLAY)!=0) - addToOverlay(); + addRoiToOverlay(); } - void addToOverlay() { + private void addRoiToOverlay() { Roi roi = imp.getRoi(); if (roi==null) return; @@ -121,7 +123,7 @@ public class Analyzer implements PlugInF } if (roi.getName()==null) roi.setName(""+rt.size()); - //roi.setName(IJ.getString("Label:", "m"+rt.getCounter())); + //roi.setName(IJ.getString("Label:", "m"+rt.size())); roi.setIgnoreClipRect(true); Overlay overlay = imp.getOverlay(); if (overlay==null) @@ -235,6 +237,8 @@ public class Analyzer implements PlugInF else systemMeasurements &= ~list[i]; } + if (rt!=null && rt.size()>1 && !IJ.isResultsWindow() && IJ.getInstance()!=null) + rt.reset(); if ((oldMeasurements&(~SCIENTIFIC_NOTATION))!=(systemMeasurements&(~SCIENTIFIC_NOTATION))&&IJ.isResultsWindow()) { rt.setPrecision((systemMeasurements&SCIENTIFIC_NOTATION)!=0?-precision:precision); clearSummary(); @@ -396,7 +400,7 @@ public class Analyzer implements PlugInF ImageStatistics stats = ImageStatistics.getStatistics(ip, measurements, imp2.getCalibration()); PointRoi point = new PointRoi(p.xpoints[i], p.ypoints[i]); point.setPosition(position); - if (pointRoi!=null) { + if (pointRoi!=null && pointRoi.getNCounters()>1) { int[] counters = pointRoi.getCounters(); if (counters!=null && i=count || last>=count) diff -pruN 1.51q-1/ij/plugin/filter/FractalBoxCounter.java 1.52g-1/ij/plugin/filter/FractalBoxCounter.java --- 1.51q-1/ij/plugin/filter/FractalBoxCounter.java 2016-07-15 15:15:44.000000000 +0000 +++ 1.52g-1/ij/plugin/filter/FractalBoxCounter.java 2018-04-17 09:40:12.000000000 +0000 @@ -226,7 +226,7 @@ public class FractalBoxCounter implement return; ResultsTable rt=ResultsTable.getResultsTable(); rt.incrementCounter(); - rt.setLabel(imp.getShortTitle(), rt.getCounter()-1); + rt.setLabel(imp.getShortTitle(), rt.size()-1); for (int i=0; i= v0-(float)tolerance) { if (v2 > v0) { //maybe this point should have been treated earlier @@ -722,7 +719,7 @@ public class MaximumFinder implements Ex if (!IJ.isResultsWindow()) rt = new ResultsTable(); rt.incrementCounter(); - rt.setValue("Count", rt.getCounter()-1, npoints); + rt.setValue("Count", rt.size()-1, npoints); int measurements = Analyzer.getMeasurements(); if ((measurements&Measurements.LABELS)!=0) { String s = imp.getTitle(); @@ -739,7 +736,7 @@ public class MaximumFinder implements Ex else s += colon+currentSlice; } - rt.setLabel(s, rt.getCounter()-1); + rt.setLabel(s, rt.size()-1); } rt.show("Results"); } @@ -768,7 +765,6 @@ public class MaximumFinder implements Ex minValue = (threshold == ImageProcessor.NO_THRESHOLD)?globalMin:threshold; double offset = minValue - (globalMax-minValue)*(1./253/2-1e-6); //everything above minValue should become >(byte)0 double factor = 253/(globalMax-minValue); - //IJ.write("min,max="+minValue+","+globalMax+"; offset, 1/factor="+offset+", "+(1./factor)); if (isEDM && factor>1) factor = 1; // with EDM, no better resolution ByteProcessor outIp = new ByteProcessor(width, height); @@ -850,7 +846,6 @@ public class MaximumFinder implements Ex int level = pixels[offset0]&255; int loLevel = level+1; pList[0] = offset0; //we start the list at the current maximum - //if (xList[0]==122) IJ.write("max#"+iMax+" at x,y="+xList[0]+","+yList[0]+"; level="+level); types[offset0] |= LISTED; //mark first point as listed int listLen = 1; //number of elements in the list int lastLen = 1; @@ -870,7 +865,6 @@ public class MaximumFinder implements Ex if ((isInner || isWithin(x, y, d)) && (types[offset2]&LISTED)==0) { if ((types[offset2]&MAX_AREA)!=0 || (((types[offset2]&ELIMINATED)!=0) && (pixels[offset2]&255)>=loLevel)) { saddleFound = true; //we have reached a point touching a "true" maximum... - //if (xList[0]==122) IJ.write("saddle found at level="+loLevel+"; x,y="+xList[listI]+","+yList[listI]+", dir="+d); break; //...or a level not lower, but touching a "true" maximum } else if ((pixels[offset2]&255)>=loLevel && (types[offset2]&ELIMINATED)==0) { pList[listLen] = offset2; @@ -919,7 +913,6 @@ public class MaximumFinder implements Ex /** delete a line starting at x, y up to the next (4-connected) vertex */ void removeLineFrom (byte[] pixels, int x, int y) { //IJ.log("del line from "+x+","+y); - //if(x<50&&y<40)IJ.write("x,y start="+x+","+y); pixels[x + width*y] = (byte)255; //delete the first point boolean continues; do { @@ -940,7 +933,6 @@ public class MaximumFinder implements Ex } } } // for directions d - //if(!continues && x<50&&y<40)IJ.write("x,y end="+x+","+y); } while (continues); //IJ.log("deleted to "+x+","+y); } // void removeLineFrom diff -pruN 1.51q-1/ij/plugin/filter/ParticleAnalyzer.java 1.52g-1/ij/plugin/filter/ParticleAnalyzer.java --- 1.51q-1/ij/plugin/filter/ParticleAnalyzer.java 2017-01-11 00:07:56.000000000 +0000 +++ 1.52g-1/ij/plugin/filter/ParticleAnalyzer.java 2018-05-09 22:18:32.000000000 +0000 @@ -47,7 +47,7 @@ public class ParticleAnalyzer implements /** Display a progress bar. */ public static final int SHOW_PROGRESS = 32; - /** Clear ImageJ console before starting. */ + /** Clear "Results" window before starting. */ public static final int CLEAR_WORKSHEET = 64; /** Record starting coordinates so outline can be recreated later using doWand(x,y). */ @@ -529,7 +529,7 @@ public class ParticleAnalyzer implements rt = Analyzer.getResultsTable(); } analyzer = new Analyzer(imp, measurements, rt); - if (resetCounter && slice==1 && rt.getCounter()>0) { + if (resetCounter && slice==1 && rt.size()>0) { if (!Analyzer.resetCounter()) return false; } @@ -598,7 +598,7 @@ public class ParticleAnalyzer implements } if (showProgress) IJ.showProgress(1.0); - if (showResults && showResultsWindow && rt.getCounter()>0) + if (showResults && showResultsWindow && rt.size()>0) rt.updateResults(); imp.deleteRoi(); ip.resetRoi(); @@ -633,10 +633,8 @@ public class ParticleAnalyzer implements summaryTable = table; } } - if (summaryTable==null) { + if (summaryTable==null) summaryTable = new ResultsTable(); - summaryTable.showRowNumbers(false); - } float[] areas = rt.getColumn(ResultsTable.AREA); if (areas==null) areas = new float[0]; @@ -938,7 +936,7 @@ public class ParticleAnalyzer implements } if (lineWidth!=1) roi.setStrokeWidth(lineWidth); - roiManager.add(imp, roi, rt.getCounter()); + roiManager.add(imp, roi, rt.size()); } if (showResultsWindow && showResults) rt.addResults(); @@ -951,9 +949,9 @@ public class ParticleAnalyzer implements switch (showChoice) { case MASKS: drawFilledParticle(drawIP, roi, mask); break; case OUTLINES: case BARE_OUTLINES: case OVERLAY_OUTLINES: case OVERLAY_MASKS: - drawOutline(drawIP, roi, rt.getCounter()); break; - case ELLIPSES: drawEllipse(drawIP, stats, rt.getCounter()); break; - case ROI_MASKS: drawRoiFilledParticle(drawIP, roi, mask, rt.getCounter()); break; + drawOutline(drawIP, roi, rt.size()); break; + case ELLIPSES: drawEllipse(drawIP, stats, rt.size()); break; + case ROI_MASKS: drawRoiFilledParticle(drawIP, roi, mask, rt.size()); break; default: } } @@ -983,6 +981,8 @@ public class ParticleAnalyzer implements } else roi2.setPosition(slice); } + if (showResults) + roi2.setName(""+count); overlay.add(roi2); } else { Rectangle r = roi.getBounds(); @@ -1018,7 +1018,7 @@ public class ParticleAnalyzer implements } void showResults() { - int count = rt.getCounter(); + int count = rt.size(); // if (count==0) return; boolean lastSlice = !processStack||slice==imp.getStackSize(); if ((showChoice==OVERLAY_OUTLINES||showChoice==OVERLAY_MASKS) && count>0 && (!processStack||slice==imp.getStackSize())) @@ -1047,7 +1047,7 @@ public class ParticleAnalyzer implements outputImage.show(); } if (showResults && !processStack) { - if (showResultsWindow && rt.getCounter()>0) { + if (showResultsWindow && rt.size()>0) { TextPanel tp = IJ.getTextPanel(); if (beginningCount>0 && tp!=null && tp.getLineCount()!=count) rt.show("Results"); @@ -1056,7 +1056,7 @@ public class ParticleAnalyzer implements Analyzer.lastParticle = Analyzer.getCounter()-1; } else Analyzer.firstParticle = Analyzer.lastParticle = 0; - if (showResults && rt.getCounter()==0 && !(IJ.isMacro()||calledByPlugin) && (!processStack||slice==imp.getStackSize())) { + if (showResults && rt.size()==0 && !(IJ.isMacro()||calledByPlugin) && (!processStack||slice==imp.getStackSize())) { int digits = (int)level1==level1&&(int)level2==level2?0:2; String range = IJ.d2s(level1,digits)+"-"+IJ.d2s(level2,digits); String assummed = noThreshold?"assumed":""; diff -pruN 1.51q-1/ij/plugin/filter/RankFilters.java 1.52g-1/ij/plugin/filter/RankFilters.java --- 1.51q-1/ij/plugin/filter/RankFilters.java 2014-10-10 15:29:20.000000000 +0000 +++ 1.52g-1/ij/plugin/filter/RankFilters.java 2018-01-22 12:36:40.000000000 +0000 @@ -31,7 +31,7 @@ public class RankFilters implements Exte private static double[] lastRadius = new double[HIGHEST_FILTER+1]; //separate for each filter type private static double lastThreshold = 50.; private static int lastWhichOutliers = BRIGHT_OUTLIERS; - // + // // F u r t h e r c l a s s v a r i a b l e s int flags = DOES_ALL|SUPPORTS_MASKING|KEEP_PREVIEW; private ImagePlus imp; @@ -56,7 +56,7 @@ public class RankFilters implements Exte * @param arg Defines type of filter operation * @param imp The ImagePlus to be processed * @return Flags specifying further action of the PlugInFilterRunner - */ + */ public int setup(String arg, ImagePlus imp) { this.imp = imp; if (arg.equals("mean")) @@ -248,7 +248,7 @@ public class RankFilters implements Exte final int cacheHeight = kHeight + (numThreads>1 ? 2*numThreads : 0); // 'cache' is the input buffer. Each line y in the image is mapped onto cache line y%cacheHeight final float[] cache = new float[cacheWidth*cacheHeight]; - highestYinCache = Math.max(roi.y-kHeight/2, 0) - 1; //this line+1 will be read into the cache first + highestYinCache = Math.max(roi.y-kHeight/2, 0) - 1; //this line+1 will be read into the cache first final int[] yForThread = new int[numThreads]; //threads announce here which line they currently process Arrays.fill(yForThread, -1); @@ -315,7 +315,7 @@ public class RankFilters implements Exte int width = ip.getWidth(); int height = ip.getHeight(); Rectangle roi = ip.getRoi(); - + int kHeight = kHeight(lineRadii); int kRadius = kRadius(lineRadii); int kNPoints = kNPoints(lineRadii); @@ -329,7 +329,7 @@ public class RankFilters implements Exte int xminInside = xmin>0 ? xmin : 0; int xmaxInside = xmaxmaxValue) value = maxValue; + if (value < 0) value = 0; // numeric noise can cause values < 0 values[valuesP] = value; } } else if (filterType == MEDIAN) { - median = getMedian(cache, x, cachePointers, medianBuf1, medianBuf2, kNPoints, median); + if (isFloat) { + median = Float.isNaN(values[valuesP]) ? Float.NaN : values[valuesP]; // a first guess + median = getNaNAwareMedian(cache, x, cachePointers, medianBuf1, medianBuf2, kNPoints, median); + } else + median = getMedian(cache, x, cachePointers, medianBuf1, medianBuf2, kNPoints, median); values[valuesP] = median; } else if (filterType == OUTLIERS) { float v = cache[cacheLineP+x]; @@ -529,7 +534,7 @@ public class RankFilters implements Exte if (y < height) { readLineToCache(pixels, y*width, xminInside, widthInside, cache, lineInCache*cacheWidth, padLeft, padRight, colorChannel); - if (y==0) for (int prevY = roiY-kHeight/2; prevY<0; prevY++) { //for y<0, pad with y=0 border pixels + if (y==0) for (int prevY = roiY-kHeight/2; prevY<0; prevY++) { //for y<0, pad with y=0 border pixels int prevLineInCache = cacheHeight+prevY; System.arraycopy(cache, 0, cache, prevLineInCache*cacheWidth, cacheWidth); } @@ -617,7 +622,7 @@ public class RankFilters implements Exte double sum=0, sum2=0; for (int kk=0; kkImport>Image Sequence", "Directory not found: "+directory); return; + } String title = directory; if (title.endsWith(File.separator) || title.endsWith("/")) title = title.substring(0, title.length()-1); int index = title.lastIndexOf(File.separatorChar); - if (index!=-1) title = title.substring(index + 1); + if (index!=-1) + title = title.substring(index + 1); + else { + index = title.lastIndexOf("/"); + if (index!=-1) + title = title.substring(index + 1); + } if (title.endsWith(":")) title = title.substring(0, title.length()-1); @@ -121,34 +141,33 @@ public class FolderOpener implements Plu n = list.length; start = 1; increment = 1; + boolean dicomImages = false; try { - if (isMacro) { - if (!showDialog(null, list)) - return; - } else { - for (int i=0; i100 && info.indexOf('\n')>0) + label += "\n" + info; // multi-line metadata + else + label = info; + } } if (Math.abs(imp.getCalibration().pixelWidth-cal.pixelWidth)>0.0000000001) allSameCalibration = false; @@ -322,7 +345,8 @@ public class FolderOpener implements Plu imp2.setCalibration(cal); } if (info1!=null && info1.lastIndexOf("7FE0,0010")>0) { - stack = DicomTools.sort(stack); + if (sortByMetaData) + stack = DicomTools.sort(stack); imp2.setStack(stack); double voxelDepth = DicomTools.getVoxelDepth(stack); if (voxelDepth>0.0) { @@ -349,6 +373,18 @@ public class FolderOpener implements Plu image = imp2; } IJ.showProgress(1.0); + if (Recorder.record) { + String options = openAsVirtualStack?"virtual":""; + if (filter!=null && filter.length()>0) { + if (filter.contains(" ")) + filter = "["+filter+"]"; + options = options + " file=" + filter; + } + if (!sortByMetaData) + options = options + " noMetaSort"; + Recorder.recordCall("imp = FolderOpener.open(\""+directory+"\", \""+options+"\");"); + } + } private void openAsFileInfoStack(FileInfoVirtualStack stack, String path) { @@ -400,6 +436,8 @@ public class FolderOpener implements Plu filter = "("+legacyRegex+")"; convertToRGB = gd.getNextBoolean(); sortFileNames = gd.getNextBoolean(); + if (!sortFileNames) + sortByMetaData = false; openAsVirtualStack = gd.getNextBoolean(); if (openAsVirtualStack) scale = 100.0; @@ -514,6 +552,10 @@ public class FolderOpener implements Plu sortFileNames = b; } + public void sortByMetaData(boolean b) { + sortByMetaData = b; + } + /** Sorts file names containing numerical components. * @see ij.util.StringSorter#sortNumerically * Author: Norbert Vischer diff -pruN 1.51q-1/ij/plugin/frame/ColorThresholder.java 1.52g-1/ij/plugin/frame/ColorThresholder.java --- 1.51q-1/ij/plugin/frame/ColorThresholder.java 2015-05-09 10:47:02.000000000 +0000 +++ 1.52g-1/ij/plugin/frame/ColorThresholder.java 2017-12-20 19:37:02.000000000 +0000 @@ -1363,7 +1363,7 @@ public class ColorThresholder extends Pl public static void RGBtoYUV() { ImagePlus imp = IJ.getImage(); if (imp.getBitDepth()==24) { - RGBtoLab(imp.getProcessor()); + RGBtoYUV(imp.getProcessor()); imp.updateAndDraw(); } } diff -pruN 1.51q-1/ij/plugin/frame/ContrastAdjuster.java 1.52g-1/ij/plugin/frame/ContrastAdjuster.java --- 1.51q-1/ij/plugin/frame/ContrastAdjuster.java 2017-07-30 00:32:52.000000000 +0000 +++ 1.52g-1/ij/plugin/frame/ContrastAdjuster.java 2018-07-02 09:37:14.000000000 +0000 @@ -657,7 +657,7 @@ public class ContrastAdjuster extends Pl if (imp.getStackSize()>1 && !imp.isComposite()) { ImageStack stack = imp.getStack(); YesNoCancelDialog d = new YesNoCancelDialog(new Frame(), - "Entire Stack?", "Apply LUT to all "+stack.getSize()+" slices in the stack?"); + "Entire Stack?", "Apply LUT to all "+stack.getSize()+" stack slices?"); if (d.cancelPressed()) {imp.unlock(); return;} if (d.yesPressed()) { @@ -1026,18 +1026,18 @@ public class ContrastAdjuster extends Pl } } - void recordSetMinAndMax(double min, double max) { + public static void recordSetMinAndMax(double min, double max) { if ((int)min==min && (int)max==max) { int imin=(int)min, imax = (int)max; if (Recorder.scriptMode()) - Recorder.recordCall("IJ.setMinAndMax(imp, "+imin+", "+imax+");"); + Recorder.recordCall("imp.setDisplayRange("+imin+", "+imax+");"); else Recorder.record("setMinAndMax", imin, imax); } else { if (Recorder.scriptMode()) - Recorder.recordCall("IJ.setMinAndMax(imp, "+min+", "+max+");"); + Recorder.recordCall("imp.setDisplayRange("+IJ.d2s(min,2)+", "+IJ.d2s(max,2)+");"); else - Recorder.record("setMinAndMax", min, max); + Recorder.record("setMinAndMax", IJ.d2s(min,2), IJ.d2s(max,2)); } } @@ -1087,7 +1087,6 @@ public class ContrastAdjuster extends Pl ip = imp.getProcessor(); if (RGBImage && !imp.lock()) {imp=null; return;} - //IJ.write("setup: "+(imp==null?"null":imp.getTitle())); switch (action) { case RESET: reset(imp, ip); diff -pruN 1.51q-1/ij/plugin/frame/Editor.java 1.52g-1/ij/plugin/frame/Editor.java --- 1.51q-1/ij/plugin/frame/Editor.java 2017-09-01 13:06:42.000000000 +0000 +++ 1.52g-1/ij/plugin/frame/Editor.java 2018-09-05 14:24:04.000000000 +0000 @@ -47,6 +47,7 @@ public class Editor extends PlugInFrame static final String DEFAULT_DIR= "editor.dir"; static final String INSERT_SPACES= "editor.spaces"; static final String TAB_INC= "editor.tab-inc"; + public static Editor currentMacroEditor; private TextArea ta; private String path; protected boolean changes; @@ -80,7 +81,6 @@ public class Editor extends PlugInFrame private int previousLine; private static Editor instance; private int runToLine; - private boolean fixedLineEndings; private String downloadUrl; private boolean downloading; private FunctionFinder functionFinder; @@ -113,7 +113,7 @@ public class Editor extends PlugInFrame fontSize = sizes.length-1; setFont(); positionWindow(); - if (IJ.isJava16() && !IJ.isJava18() && !IJ.isLinux()) + if (!IJ.isJava18() && !IJ.isLinux()) insertSpaces = false; } @@ -133,24 +133,24 @@ public class Editor extends PlugInFrame mb.add(m); m = new Menu("Edit"); - MenuItem item = new MenuItem("Undo",new MenuShortcut(KeyEvent.VK_Z)); + MenuItem item = null; + if (IJ.isWindows()) + item = new MenuItem("Undo Ctrl+Z"); + else + item = new MenuItem("Undo",new MenuShortcut(KeyEvent.VK_Z)); m.add(item); - m.addSeparator(); - boolean shortcutsBroken = IJ.isWindows() - && (System.getProperty("java.version").indexOf("1.1.8")>=0 - ||System.getProperty("java.version").indexOf("1.5.")>=0); - shortcutsBroken = false; - if (shortcutsBroken) + m.addSeparator(); + if (IJ.isWindows()) item = new MenuItem("Cut Ctrl+X"); else item = new MenuItem("Cut",new MenuShortcut(KeyEvent.VK_X)); m.add(item); - if (shortcutsBroken) + if (IJ.isWindows()) item = new MenuItem("Copy Ctrl+C"); else item = new MenuItem("Copy", new MenuShortcut(KeyEvent.VK_C)); m.add(item); - if (shortcutsBroken) + if (IJ.isWindows()) item = new MenuItem("Paste Ctrl+V"); else item = new MenuItem("Paste",new MenuShortcut(KeyEvent.VK_V)); @@ -214,7 +214,6 @@ public class Editor extends PlugInFrame } public void create(String name, String text) { - if (text!=null && text.length()>0) fixedLineEndings = true; ta.append(text); if (IJ.isMacOSX()) IJ.wait(25); // needed to get setCaretPosition() on OS X ta.setCaretPosition(0); @@ -250,8 +249,6 @@ public class Editor extends PlugInFrame debugMenu.addActionListener(this); mb.add(debugMenu); } - if (macroExtension && text.indexOf("macro ")!=-1) - installMacros(text, false); } else { fileMenu.addSeparator(); fileMenu.add(new MenuItem("Compile and Run", new MenuShortcut(KeyEvent.VK_R))); @@ -285,6 +282,7 @@ public class Editor extends PlugInFrame if (installInPluginsMenu || nShortcutsOrTools>0) installer.install(null); dontShowWindow = installer.isAutoRunAndHide(); + currentMacroEditor = this; } /** Opens a file and replaces the text (if any) by the contents of the file. */ @@ -398,13 +396,7 @@ public class Editor extends PlugInFrame text = ta.getText(); else text = ta.getSelectedText(); - Interpreter instance = Interpreter.getInstance(); - if (instance!=null) { // abort any currently running macro - instance.abortMacro(); - long t0 = System.currentTimeMillis(); - while (Interpreter.getInstance()!=null && (System.currentTimeMillis()-t0)<3000L) - IJ.wait(10); - } + Interpreter.abort(); // abort any currently running macro if (checkForCurlyQuotes && text.contains("\u201D")) { // replace curly quotes with standard quotes text = text.replaceAll("\u201C", "\""); @@ -420,19 +412,20 @@ public class Editor extends PlugInFrame changes = true; checkForCurlyQuotes = false; } + currentMacroEditor = this; new MacroRunner(text, debug?this:null); } void evaluateMacro() { String title = getTitle(); - if (title.endsWith(".js")||title.endsWith("..bsh")||title.endsWith(".py")) - setTitle(title.substring(0,title.length()-3)+".ijm"); + if (title.endsWith(".js")||title.endsWith(".bsh")||title.endsWith(".py")) + setWindowTitle(title.substring(0,title.length()-3)+".ijm"); runMacro(false); } void evaluateJavaScript() { if (!getTitle().endsWith(".js")) - setTitle(SaveDialog.setExtension(getTitle(), ".js")); + setWindowTitle(SaveDialog.setExtension(getTitle(), ".js")); int start = ta.getSelectionStart(); int end = ta.getSelectionEnd(); String text; @@ -454,7 +447,7 @@ public class Editor extends PlugInFrame if (strictMode) text = "'use strict';" + text; } - if ((IJ.isJava16() && !(IJ.isMacOSX()&&!IJ.is64Bit()))) { + if (!(IJ.isMacOSX()&&!IJ.is64Bit())) { // Use JavaScript engine built into Java 6 and later. IJ.runPlugIn("ij.plugin.JavaScriptEvaluator", text); } else { @@ -475,7 +468,7 @@ public class Editor extends PlugInFrame return; } if (!getTitle().endsWith(ext)) - setTitle(SaveDialog.setExtension(getTitle(), ext)); + setWindowTitle(SaveDialog.setExtension(getTitle(), ext)); int start = ta.getSelectionStart(); int end = ta.getSelectionEnd(); String text; @@ -612,6 +605,10 @@ public class Editor extends PlugInFrame } void undo() { + if (IJ.isWindows()) { + IJ.showMessage("Editor", "Press Ctrl-Z to undo"); + return; + } if (IJ.debugMode) IJ.log("Undo1: "+undoBuffer.size()); int position = ta.getCaretPosition(); if (undoBuffer.size()>1) { @@ -620,7 +617,7 @@ public class Editor extends PlugInFrame performingUndo = true; ta.setText(text); if (position<=text.length()) - ta.setCaretPosition(position); + ta.setCaretPosition(position-offset(position)); if (IJ.debugMode) IJ.log("Undo2: "+undoBuffer.size()+" "+text); } } @@ -636,13 +633,12 @@ public class Editor extends PlugInFrame } else return false; } - void cut() { if (copy()) { int start = ta.getSelectionStart(); int end = ta.getSelectionEnd(); - ta.replaceRange("", start, end); + ta.replaceRange("", start-offset(start), end-offset(end-2>=start?end-2:start)); if (IJ.isMacOSX()) ta.setCaretPosition(start); } @@ -655,20 +651,30 @@ public class Editor extends PlugInFrame Transferable clipData = clipboard.getContents(s); try { s = (String)(clipData.getTransferData(DataFlavor.stringFlavor)); - } - catch (Exception e) { + } catch (Exception e) { s = e.toString( ); } - if (!fixedLineEndings && IJ.isWindows()) - fixLineEndings(); - fixedLineEndings = true; int start = ta.getSelectionStart( ); int end = ta.getSelectionEnd( ); - ta.replaceRange(s, start, end); + ta.replaceRange(s, start-offset(start), end-offset(end-2>=start?end-2:start)); if (IJ.isMacOSX()) ta.setCaretPosition(start+s.length()); checkForCurlyQuotes = true; } + + // workaround for TextArea.getCaretPosition() bug on Windows + private int offset(int pos) { + if (!IJ.isWindows()) + return 0; + String text = ta.getText(); + int rcount = 0; + for (int i=0; i<=pos; i++) { + if (text.charAt(i)=='\r') + rcount++; + } + if (IJ.debugMode) IJ.log("offset: "+pos+" "+rcount); + return pos-rcount>=0?rcount:0; + } void copyToInfo() { ImagePlus imp = WindowManager.getCurrentImage(); @@ -685,7 +691,7 @@ public class Editor extends PlugInFrame text = ta.getSelectedText(); imp.setProperty("Info", text); } - + public void actionPerformed(ActionEvent e) { String what = e.getActionCommand(); int flags = e.getModifiers(); @@ -739,13 +745,13 @@ public class Editor extends PlugInFrame revert(); else if ("Print...".equals(what)) print(); - else if (what.equals("Undo")) + else if (what.startsWith("Undo")) undo(); - else if (what.equals("Paste")) + else if (what.startsWith("Paste")) paste(); - else if (what.equals("Copy")) + else if (what.startsWith("Copy")) copy(); - else if (what.equals("Cut")) + else if (what.startsWith("Cut")) cut(); else if ("Save As...".equals(what)) saveAs(); @@ -776,7 +782,7 @@ public class Editor extends PlugInFrame else if (what.equals("Copy to Image Info")) copyToInfo(); else if (what.endsWith(".ijm") || what.endsWith(".java") || what.endsWith(".js") || what.endsWith(".bsh") || what.endsWith(".py")) - openExample(what, e); + openExample(what); else { if (altKeyDown) { enableDebugging(); @@ -786,16 +792,17 @@ public class Editor extends PlugInFrame } } - private void openExample(String name, ActionEvent e) { + /** Opens an example from the Help/Examples menu + and runs if "Autorun Exampes" is checked. */ + public static boolean openExample(String name) { boolean isJava = name.endsWith(".java"); boolean isJavaScript = name.endsWith(".js"); boolean isBeanShell = name.endsWith(".bsh"); boolean isPython = name.endsWith(".py"); - int flags = e.getModifiers(); - boolean shift = (flags & KeyEvent.SHIFT_MASK) != 0; - boolean control = (flags & KeyEvent.CTRL_MASK) != 0; - boolean alt = (flags & KeyEvent.ALT_MASK) != 0; - boolean run = !isJava && (Prefs.autoRunExamples||shift||control||alt); + boolean isMacro = name.endsWith(".ijm"); + if (!(isMacro||isJava||isJavaScript||isBeanShell||isPython)) + return false; + boolean run = !isJava && !name.contains("_Tool") && Prefs.autoRunExamples; int rows = 24; int columns = 70; int options = MENU_BAR; @@ -814,24 +821,12 @@ public class Editor extends PlugInFrame text = IJ.openUrlAsString(url); if (text.startsWith(" '~' && c < 0xa0) { + sb.append('\\'); + String octal = Integer.toString(c,8); + while (octal.length()<3) + octal = '0' + octal; + sb.append(octal); + } else + sb.append(c); } return new String(sb); } @@ -370,6 +396,7 @@ public class Recorder extends PlugInFram public static void recordOption(String key, String value) { if (key==null) return; + key = fixString(key); key = trimKey(key); value = addQuotes(value); checkForDuplicate(key+"=", value); @@ -436,6 +463,7 @@ public class Recorder extends PlugInFram /** Writes the current command and options to the Recorder window. */ public static void saveCommand() { String name = commandName; + //IJ.log("saveCommand: "+name+" "+scriptMode+" "+commandOptions); if (name!=null) { if (commandOptions==null && (name.equals("Fill")||name.equals("Clear")||name.equals("Draw"))) commandOptions = "slice"; @@ -526,6 +554,8 @@ public class Recorder extends PlugInFram ; else if (name.equals("Find Commands...")) ; + else if (scriptMode && name.equals("Create Mask")) + ; else if (roi!=null && (roi instanceof TextRoi) && (name.equals("Draw")||name.equals("Add Selection..."))) textArea.append(((TextRoi)roi).getMacroCode(name, imp)); else { diff -pruN 1.51q-1/ij/plugin/frame/RoiManager.java 1.52g-1/ij/plugin/frame/RoiManager.java --- 1.51q-1/ij/plugin/frame/RoiManager.java 2017-06-11 20:35:44.000000000 +0000 +++ 1.52g-1/ij/plugin/frame/RoiManager.java 2018-08-25 14:57:30.000000000 +0000 @@ -68,8 +68,10 @@ public class RoiManager extends PlugInFr private int imageID; private boolean allowRecording; private boolean recordShowAll = true; + private boolean allowDuplicates; /** Opens the "ROI Manager" window, or activates it if it is already open. + * @see #RoiManager(boolean) * @see #getRoiManager */ public RoiManager() { @@ -89,7 +91,7 @@ public class RoiManager extends PlugInFr showWindow(); } - /* Constructs an ROIManager without displaying it. */ + /** Constructs an ROIManager without displaying it. The boolean argument is ignored. */ public RoiManager(boolean b) { super("ROI Manager"); list = new JList(); @@ -325,6 +327,7 @@ public class RoiManager extends PlugInFr /** Adds the specified ROI. */ public void addRoi(Roi roi) { + allowDuplicates = true; addRoi(roi, false, null, -1); } @@ -368,7 +371,7 @@ public class RoiManager extends PlugInFr imp.setSliceWithoutUpdate(position); else position = 0; - if (n>0 && !IJ.isMacro() && imp!=null) { + if (n>0 && !IJ.isMacro() && imp!=null && !allowDuplicates) { // check for duplicate Roi roi2 = (Roi)rois.get(n-1); if (roi2!=null) { @@ -381,6 +384,7 @@ public class RoiManager extends PlugInFr } } } + allowDuplicates = false; prevID = imp!=null?imp.getID():0; String name = roi.getName(); if (isStandardName(name)) @@ -429,7 +433,11 @@ public class RoiManager extends PlugInFr } /** Adds the specified ROI to the list. The third argument ('n') will - be used to form the first part of the ROI label if it is >= 0. */ + * be used to form the first part of the ROI label if it is zero or greater. + * @param imp the image associated with the ROI, or null + * @param roi the Roi to be added + * @param n if zero or greater, will be used to form the first part of the label + */ public void add(ImagePlus imp, Roi roi, int n) { if (IJ.debugMode && n<3 && roi!=null) IJ.log("RoiManager.add: "+n+" "+roi.getName()); if (roi==null) @@ -438,8 +446,10 @@ public class RoiManager extends PlugInFr String label2 = label; if (label==null) label = getLabel(imp, roi, n); - else - label = label+"-"+n; + else { + if (n>=0) + label = n+"-"+label; + } if (label==null) return; listModel.addElement(label); @@ -449,6 +459,14 @@ public class RoiManager extends PlugInFr roi.setName(label); rois.add((Roi)roi.clone()); } + + /** Replaces the ROI at the specified index. */ + public void setRoi(Roi roi, int index) { + if (index<0 || index>=rois.size()) + throw new IllegalArgumentException("setRoi: Index out of range"); + rois.set(index, (Roi)roi.clone()); + updateShowAll(); + } boolean isStandardName(String name) { if (name==null) @@ -537,19 +555,37 @@ public class RoiManager extends PlugInFr delete = true; } if (delete) { - rois.remove(i); - listModel.remove(i); - } + if (EventQueue.isDispatchThread()) { + rois.remove(i); + listModel.remove(i); + } else + deleteOnEDT(i); + } } } ImagePlus imp = WindowManager.getCurrentImage(); if (count>1 && index.length==1 && imp!=null) imp.deleteRoi(); updateShowAll(); - if (record()) Recorder.record("roiManager", "Delete"); + if (record()) + Recorder.record("roiManager", "Delete"); return true; } + // Delete ROI on event dispatch thread + private void deleteOnEDT(final int i) { + try { + EventQueue.invokeAndWait(new Runnable() { + public void run() { + rois.remove(i); + listModel.remove(i); + } + }); + } catch ( + Exception e) { + } + } + boolean update(boolean clone) { ImagePlus imp = getImage(); if (imp==null) @@ -730,6 +766,8 @@ public class RoiManager extends PlugInFr if (name==null) name = o.getName(path); Roi roi = o.openRoi(path); if (roi!=null) { + if (roi.getName()!=null) + name = roi.getName(); if (name.endsWith(".roi")) name = name.substring(0, name.length()-4); listModel.addElement(name); @@ -787,32 +825,37 @@ public class RoiManager extends PlugInFr int[] indexes = getIndexes(); if (indexes.length>1) return saveMultiple(indexes, null); - String name = (String) listModel.getElementAt(indexes[0]); - Macro.setOptions(null); - SaveDialog sd = new SaveDialog("Save Selection...", name, ".roi"); - String name2 = sd.getFileName(); - if (name2 == null) - return false; - String dir = sd.getDirectory(); + else + return saveOne(indexes, null); + } + + boolean saveOne(int[] indexes, String path) { + if (indexes.length==0) + return error("The list is empty"); Roi roi = (Roi)rois.get(indexes[0]); - if (!name2.endsWith(".roi")) name2 = name2+".roi"; - String newName = name2.substring(0, name2.length()-4); - rois.set(indexes[0], roi); - roi.setName(newName); - listModel.setElementAt(newName, indexes[0]); - RoiEncoder re = new RoiEncoder(dir+name2); + if (path==null) { + Macro.setOptions(null); + String name = (String) listModel.getElementAt(indexes[0]); + SaveDialog sd = new SaveDialog("Save Selection...", name, ".roi"); + String name2 = sd.getFileName(); + if (name2 == null) + return false; + String dir = sd.getDirectory(); + if (!name2.endsWith(".roi")) name2 = name2+".roi"; + String newName = name2.substring(0, name2.length()-4); + rois.set(indexes[0], roi); + roi.setName(newName); + listModel.setElementAt(newName, indexes[0]); + path = dir+name2; + } + RoiEncoder re = new RoiEncoder(path); try { re.write(roi); } catch (IOException e) { IJ.error("ROI Manager", e.getMessage()); } - if (record()) { - String path = dir+name2; - if (Recorder.scriptMode()) - Recorder.recordCall("IJ.saveAs(imp, \"Selection\", \""+path+"\");"); - else - Recorder.record("saveAs", "Selection", path); - } + if (Recorder.record && !IJ.isMacro()) + Recorder.record("roiManager", "Save", path); return true; } @@ -910,7 +953,8 @@ public class RoiManager extends PlugInFr for (int i=0; i1) allSliceOne=false; + if (getSliceNumber(roi,label)>1 || roi.hasHyperStackPosition()) + allSliceOne=false; } int measurements = Analyzer.getMeasurements(); if (imp.getStackSize()>1) @@ -998,6 +1042,7 @@ public class RoiManager extends PlugInFr if (!onePerSlice) { int measurements2 = nSlices>1?measurements|Measurements.SLICE:measurements; ResultsTable rt = new ResultsTable(); + rt.showRowNumbers(true); if (appendResults && mmResults2!=null) rt = mmResults2; Analyzer analyzer = new Analyzer(imp, measurements2, rt); @@ -1056,6 +1101,7 @@ public class RoiManager extends PlugInFr Analyzer aSys = new Analyzer(imp); // System Analyzer ResultsTable rtSys = Analyzer.getResultsTable(); ResultsTable rtMulti = new ResultsTable(); + rtMulti.showRowNumbers(true); if (appendResults && mmResults!=null) rtMulti = mmResults; rtSys.reset(); @@ -1094,6 +1140,7 @@ public class RoiManager extends PlugInFr int getColumnCount(ImagePlus imp, int measurements) { ImageStatistics stats = imp.getStatistics(measurements); ResultsTable rt = new ResultsTable(); + rt.showRowNumbers(true); Analyzer analyzer = new Analyzer(imp, measurements, rt); analyzer.saveResults(stats, null); int count = 0; @@ -2016,9 +2063,15 @@ public class RoiManager extends PlugInFr macro = false; return true; } else if (cmd.equals("save")) { - save(name, false); + if (name!=null && name.endsWith(".roi")) + saveOne(getIndexes(), name); + else + save(name, false); } else if (cmd.equals("save selected")) { - save(name, true); + if (name!=null && name.endsWith(".roi")) + saveOne(getIndexes(), name); + else + save(name, true); } else if (cmd.equals("rename")) { rename(name); macro = false; @@ -2097,21 +2150,15 @@ public class RoiManager extends PlugInFr roi.setLocation(r.getX()+dx, r.getY()+dy); } ImagePlus imp = WindowManager.getCurrentImage(); - if (imp!=null) { - Roi roi = imp.getRoi(); - if (roi!=null && !(rois.length==1 && rois[0]==roi)) { - Rectangle2D r = roi.getFloatBounds(); - roi.setLocation(r.getX()+dx, r.getY()+dy); - } + if (imp!=null) imp.draw(); - } } private boolean save(String name, boolean saveSelected) { if (!name.endsWith(".zip") && !name.equals("")) return error("Name must end with '.zip'"); if (getCount()==0) - return error("The selection list is empty."); + return error("The list is empty"); int[] indexes = null; if (saveSelected) indexes = getIndexes(); @@ -2204,7 +2251,7 @@ public class RoiManager extends PlugInFr /** Deselect the specified ROI if it is the only one selected. */ public void deselect(Roi roi) { int[] indexes = getSelectedIndexes(); - if (indexes.length==1) { + if (indexes.length==1 && listModel.getSize()>0) { String label = (String)listModel.getElementAt(indexes[0]); if (label.equals(roi.getName())) { deselect(); @@ -2321,6 +2368,17 @@ public class RoiManager extends PlugInFr return list.getSelectedIndices(); } + /** This is a macro-callable version of getSelectedIndexes(). + * Example: indexes=split(call("ij.plugin.frame.RoiManager.getIndexesAsString")); + */ + public static String getIndexesAsString() { + RoiManager rm = RoiManager.getInstance(); + if (rm==null) return ""; + String str = Arrays.toString(rm.getSelectedIndexes()); + str = str.replaceAll(",",""); + return str.substring(1,str.length()-1); + } + /** Returns an array of the selected indexes or all indexes if none are selected. */ public int[] getIndexes() { int[] indexes = getSelectedIndexes(); @@ -2401,6 +2459,7 @@ public class RoiManager extends PlugInFr Roi.previousRoi = (Roi)roi.clone(); } restore(imp, selected[0], true); + ResultsTable.selectRow(imp!=null?imp.getRoi():null); imageID = imp!=null?imp.getID():0; } if (recordInEvent()) { diff -pruN 1.51q-1/ij/plugin/frame/ThresholdAdjuster.java 1.52g-1/ij/plugin/frame/ThresholdAdjuster.java --- 1.51q-1/ij/plugin/frame/ThresholdAdjuster.java 2017-05-18 19:20:30.000000000 +0000 +++ 1.52g-1/ij/plugin/frame/ThresholdAdjuster.java 2018-07-02 21:58:10.000000000 +0000 @@ -14,8 +14,8 @@ import ij.plugin.Thresholder; /** Adjusts the lower and upper threshold levels of the active image. This class is multi-threaded to provide a more responsive user interface. */ -public class ThresholdAdjuster extends PlugInDialog implements PlugIn, Measurements, - Runnable, ActionListener, AdjustmentListener, ItemListener, ImageListener { +public class ThresholdAdjuster extends PlugInDialog implements PlugIn, Measurements, Runnable, + ActionListener, AdjustmentListener, ItemListener, FocusListener, KeyListener, MouseWheelListener, ImageListener { public static final String LOC_KEY = "threshold.loc"; public static final String MODE_KEY = "threshold.mode"; @@ -53,13 +53,14 @@ public class ThresholdAdjuster extends P ImageJ ij; double minThreshold, maxThreshold; // 0-255 Scrollbar minSlider, maxSlider; - Label label1, label2; // for current threshold + TextField minLabel, maxLabel; // for current threshold Label percentiles; boolean done; int lutColor; Choice methodChoice, modeChoice; Checkbox darkBackground, stackHistogram; boolean firstActivation = true; + boolean setButtonPressed; public ThresholdAdjuster() { @@ -125,7 +126,8 @@ public class ThresholdAdjuster extends P c.insets = new Insets(1, 10, 0, 0); add(minSlider, c); minSlider.addAdjustmentListener(this); - minSlider.addKeyListener(ij); + minSlider.addMouseWheelListener(this); +// minSlider.addKeyListener(ij); minSlider.setUnitIncrement(1); minSlider.setFocusable(false); @@ -134,10 +136,14 @@ public class ThresholdAdjuster extends P c.gridwidth = 1; c.weightx = IJ.isMacintosh()?10:0; c.insets = new Insets(5, 0, 0, 10); - String text = IJ.isMacOSX()?"000000":"00000000"; - label1 = new Label(text, Label.RIGHT); - label1.setFont(font); - add(label1, c); + String text = "000000"; + int columns = 4; + minLabel = new TextField(text,columns); + minLabel.setFont(font); + add(minLabel, c); + minLabel.addFocusListener(this); + minLabel.addMouseWheelListener(this); + minLabel.addKeyListener(this); // maxThreshold slider maxSlider = new Scrollbar(Scrollbar.HORIZONTAL, sliderRange*2/3, 1, 0, sliderRange); @@ -148,7 +154,8 @@ public class ThresholdAdjuster extends P c.insets = new Insets(2, 10, 0, 0); add(maxSlider, c); maxSlider.addAdjustmentListener(this); - maxSlider.addKeyListener(ij); + maxSlider.addMouseWheelListener(this); +// maxSlider.addKeyListener(ij); maxSlider.setUnitIncrement(1); maxSlider.setFocusable(false); @@ -157,9 +164,12 @@ public class ThresholdAdjuster extends P c.gridwidth = 1; c.weightx = 0; c.insets = new Insets(2, 0, 0, 10); - label2 = new Label(text, Label.RIGHT); - label2.setFont(font); - add(label2, c); + maxLabel = new TextField(text,columns); + maxLabel.setFont(font); + add(maxLabel, c); + maxLabel.addFocusListener(this); + maxLabel.addMouseWheelListener(this); + maxLabel.addKeyListener(this); // choices panel = new Panel(); @@ -254,7 +264,7 @@ public class ThresholdAdjuster extends P notify(); } - public synchronized void actionPerformed(ActionEvent e) { + public synchronized void actionPerformed(ActionEvent e) { Button b = (Button)e.getSource(); if (b==null) return; if (b==resetB) @@ -263,10 +273,54 @@ public class ThresholdAdjuster extends P doAutoAdjust = true; else if (b==applyB) doApplyLut = true; - else if (b==setB) + else if (b==setB) { doSet = true; + setButtonPressed = true; + } + notify(); + } + + public synchronized void focusLost(FocusEvent e) { + doSet = true; + notify(); + } + + public synchronized void mouseWheelMoved(MouseWheelEvent e) { + if (e.getSource()==minSlider || e.getSource()==minLabel) { + minSlider.setValue(minSlider.getValue() + e.getWheelRotation()); + minValue = minSlider.getValue(); + } else { + maxSlider.setValue(maxSlider.getValue() + e.getWheelRotation()); + maxValue = maxSlider.getValue(); + } + notify(); + } + + public synchronized void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + doSet = true; + } else if(e.getKeyCode() == KeyEvent.VK_DOWN) { + if (e.getSource()==minLabel) { + minSlider.setValue(minSlider.getValue() - 1); + minValue = minSlider.getValue(); + } else { + maxSlider.setValue(maxSlider.getValue() - 1); + maxValue = maxSlider.getValue(); + } + } else if(e.getKeyCode() == KeyEvent.VK_UP) { + if (e.getSource()==minLabel) { + minSlider.setValue(minSlider.getValue() + 1); + minValue = minSlider.getValue(); + } else { + maxSlider.setValue(maxSlider.getValue() + 1); + maxValue = maxSlider.getValue(); + } + } notify(); } + + public void keyReleased(KeyEvent e) {} + public void keyTyped(KeyEvent e) {} public void imageUpdated(ImagePlus imp) { if (imp.getID()==previousImageID && Thread.currentThread()!=thread) @@ -342,7 +396,7 @@ public class ThresholdAdjuster extends P && ip.getCurrentColorModel() != ip.getColorModel(); //does not work??? if (not8Bits && minMaxChange) { double max1 = ip.getMax(); - ip.resetMinAndMax(); + resetMinAndMax(ip); if (maxThreshold==max1) maxThreshold = ip.getMax(); } @@ -374,6 +428,11 @@ public class ThresholdAdjuster extends P return ip; } + private void resetMinAndMax(ImageProcessor ip) { + if ((ip instanceof ByteProcessor) || (mode==OVER_UNDER)) + ip.resetMinAndMax(); + } + boolean entireStack(ImagePlus imp) { return stackHistogram!=null && stackHistogram.getState() && imp.getStackSize()>1; } @@ -407,7 +466,13 @@ public class ThresholdAdjuster extends P minThreshold = 255; if (Recorder.record) { boolean stack = stackHistogram!=null && stackHistogram.getState(); - String options = method+(darkb?" dark":"")+(stack?" stack":""); + boolean noReset = !((ip instanceof ByteProcessor) || (mode==OVER_UNDER)); + if (noReset) { + ImageStatistics stats2 = ip.getStats(); + if (ip.getMin()>stats2.min || ip.getMax()99999.0) { - label1.setText(ResultsTable.d2s(min,0)); - label2.setText(ResultsTable.d2s(max,0)); + minLabel.setText(ResultsTable.d2s(min,0)); + maxLabel.setText(ResultsTable.d2s(max,0)); } else { - label1.setText(""+(min<-3.4e38?"-3.4e38":ResultsTable.d2s(min,2))); - label2.setText(""+ResultsTable.d2s(max,max==Double.MAX_VALUE?0:2)); + minLabel.setText(""+(min<-3.4e38?"-3.4e38":ResultsTable.d2s(min,2))); + maxLabel.setText(""+ResultsTable.d2s(max,max==Double.MAX_VALUE?0:2)); } } } @@ -580,7 +645,7 @@ public class ThresholdAdjuster extends P if (entireStack(imp)) ip.setMinAndMax(stats.min, stats.max); else - ip.resetMinAndMax(); + resetMinAndMax(ip);; } updateScrollBars(); if (Recorder.record) { @@ -594,29 +659,37 @@ public class ThresholdAdjuster extends P void doSet(ImagePlus imp, ImageProcessor ip) { double level1 = ip.getMinThreshold(); double level2 = ip.getMaxThreshold(); - if (level1==ImageProcessor.NO_THRESHOLD) { - level1 = scaleUp(ip, defaultMinThreshold); - level2 = scaleUp(ip, defaultMaxThreshold); - } Calibration cal = imp.getCalibration(); int digits = (ip instanceof FloatProcessor)||cal.calibrated()?2:0; - level1 = cal.getCValue(level1); - level2 = cal.getCValue(level2); - GenericDialog gd = new GenericDialog("Set Threshold Levels"); - gd.addNumericField("Lower Threshold Level: ", level1, digits); - gd.addNumericField("Upper Threshold Level: ", level2, digits); - gd.showDialog(); - if (gd.wasCanceled()) - return; - level1 = gd.getNextNumber(); - level2 = gd.getNextNumber(); + if (setButtonPressed) { + if (level1==ImageProcessor.NO_THRESHOLD) { + level1 = scaleUp(ip, defaultMinThreshold); + level2 = scaleUp(ip, defaultMaxThreshold); + } + level1 = cal.getCValue(level1); + level2 = cal.getCValue(level2); + GenericDialog gd = new GenericDialog("Set Threshold Levels"); + gd.addNumericField("Lower threshold level: ", level1, digits); + gd.addNumericField("Upper threshold level: ", level2, digits); + gd.showDialog(); + if (gd.wasCanceled()) { + setButtonPressed = false; + return; + } + level1 = gd.getNextNumber(); + level2 = gd.getNextNumber(); + setButtonPressed = false; + } else { + level1 = Double.parseDouble(minLabel.getText()); + level2 = Double.parseDouble(maxLabel.getText()); + } level1 = cal.getRawValue(level1); level2 = cal.getRawValue(level2); if (level2 - * Extensively Modified for ImagePlus - * Extended to handle 8 bit Images with more complex Color lookup tables with transparency index - * - * Ryan Raz March 2002 - * raz@rraz.ca - * Version 1.01 - ** Extensively Modified for ImagePlus - * Extended to handle 8 bit Images with more complex Color lookup tables with transparency index - * - * Ryan Raz March 2002 - * ryan@rraz.ca - * Version 1.01 Please report any bugs - * - * Credits for the base conversion codes - * No copyright asserted on the source code of this class. May be used - * for any purpose, however, refer to the Unisys LZW patent for restrictions - * on use of the associated LZWEncoder class. Please forward any corrections - * to kweiner@fmsware.com. - *
- * - * @author Kevin Weiner, FM Software - * @version 1.0 December 2000 - * - */ - -/** Writes a stack as an animated Gif */ +/** Saves the active image in GIF format, or as an animated GIF if the image is a stack. */ public class GifWriter implements PlugIn { static int transparentIndex = Prefs.getTransparentIndex(); - + private boolean showErrors = true; + private String error; + + /** Saves the specified image in GIF format or as an animated GIF if the image is a stack. */ + public static String save(ImagePlus imp, String path) { + if (imp==null) + imp = IJ.getImage(); + if (path==null || path.length()==0) + path = SaveDialog.getPath(imp, ".gif"); + if (path==null) + return null; + GifWriter gf = new GifWriter(); + gf.showErrors = false; + gf.run(imp, path); + return gf.error; + } + public void run(String path) { ImagePlus imp = IJ.getImage(); - if (path.equals("")) { + if (path==null || path.equals("")) { SaveDialog sd = new SaveDialog("Save as Gif", imp.getTitle(), ".gif"); if (sd.getFileName()==null) return; path = sd.getDirectory()+sd.getFileName(); } - + run(imp, path); + } + + private void run(ImagePlus imp, String path) { ImageStack stack = imp.getStack(); Overlay overlay = imp.getOverlay(); - int nSlices = stack.getSize(); - + int nSlices = stack.getSize(); if (nSlices==1) { // save using ImageIO if (overlay!=null) imp = imp.flatten(); @@ -68,12 +52,17 @@ public class GifWriter implements PlugIn String msg = e.getMessage(); if (msg==null || msg.equals("")) msg = ""+e; - IJ.error("GifWriter", "An error occured writing the file.\n \n" + msg); + error = msg; + if (showErrors) { + IJ.error("GifWriter", "An error occured writing the file.\n \n" + msg); + showErrors = false; + } } return; - } - - GifEncoder ge = new GifEncoder(); + } + AnimatedGifEncoder2 ge = new AnimatedGifEncoder2(); + if (!ge.setoptions()) + return; double fps = imp.getCalibration().fps; if (fps==0.0) fps = Animator.getFrameRate(); if (fps<=0.2) fps = 0.2; @@ -84,29 +73,31 @@ public class GifWriter implements PlugIn ge.transIndex = transparentIndex; } ge.start(path); - ImagePlus tmp = new ImagePlus(); for (int i=1; i<=nSlices; i++) { IJ.showStatus("writing: "+i+"/"+nSlices); IJ.showProgress((double)i/nSlices); - tmp.setProcessor(stack.getProcessor(i)); + tmp.setProcessor(null, stack.getProcessor(i)); if (overlay!=null) { Overlay overlay2 = overlay.duplicate(); overlay2.crop(i, i); if (overlay2.size()>0) { tmp.setOverlay(overlay2); tmp = tmp.flatten(); - new ImageConverter(tmp).convertRGBtoIndexedColor(256); + if (imp.getBitDepth()==8) + new ImageConverter(tmp).convertRGBtoIndexedColor(256); } - } + } try { ge.addFrame(tmp); } catch(Exception e) { - IJ.showMessage("Save as Gif", ""+e); - return; + error = ""+e; + if (showErrors) { + IJ.error("Save as Gif: "+e); + showErrors = false; + } } - - } + } ge.finish(); IJ.showStatus(""); IJ.showProgress(1.0); @@ -144,36 +135,112 @@ public class GifWriter implements PlugIn } -class GifEncoder { - int width; // image size - int height; - boolean transparent; // transparent color if given - int transIndex; // transparent index in color table - int repeat = 0; // repeat forever - protected int delay = 50; // frame delay (hundredths) - boolean started = false; // ready to output frames - OutputStream out; - ImagePlus image; // current frame - byte[] pixels; // BGR byte array from frame - byte[] indexedPixels; // converted frame indexed to palette - int colorDepth; // number of bit planes - byte[] colorTab; // RGB palette - int lctSize = 7; // local color table size (bits-1) - int dispose = 0; // disposal code (-1 = use default) - boolean closeStream = false; // close stream when finished - boolean firstFrame = true; - boolean sizeSet = false; // if false, get size from first frame - int sample = 2; // default sample interval for quantizer distance should be small for small icons - byte[] gct = null; //Global color table - boolean GCTextracted = false; // Set if global color table extracted from rgb image - boolean GCTloadedExternal = false; // Set if global color table loaded directly from external image - int GCTred = 0; //Transparent Color - int GCTgrn = 0; // green - int GCTbl = 0; // blue - int GCTcindex = 0; //index into color table - boolean GCTsetTransparent = false; //If true then Color table transparency index is set - boolean GCToverideIndex = false; //If true Transparent index is set to index with closest colors - boolean GCToverideColor = false; //if true Color at Transparent index is set to GCTred, GCTgrn GCTbl +/** + * Class AnimatedGifEncoder2 - Encodes a GIF file consisting of one or + * more frames. + *
+ *
+ *
+ * Extensively Modified for ImagePlus
+ * Extended to handle 8 bit Images with more complex Color lookup tables with transparency index
+ *
+ * Ryan Raz March 2002
+ * raz@rraz.ca
+ * Version 1.01
+ ** Extensively Modified for ImagePlus
+ * Extended to handle 8 bit Images with more complex Color lookup tables with transparency index
+ *
+ * Ryan Raz March 2002
+ * ryan@rraz.ca
+ * Version 1.01 Please report any bugs
+ *
+ * Operation Manual
+ *
+ *
+ * 1) Load stack with 8 bit or RGB images it is possible to use the animated gif reader but because the color
+ *	 table is lost it is best to also load a separate copy of the first image in the series this will allow 
+ *	 extraction of the original image color look up table (see 1below)
+ * 2)Check the option list to bring up the option list.
+ * 3)Experiment with the option list. I usually use a global color table to save space, set to do not dispose if 
+ *		each consecutive image is overlayed on the previous image.
+ * 4)Color table can be imported from another image or extracted from 8bit stack images or loaded as the
+ *	  first 256	 RGB triplets from a RGB images, the last mode takes either a imported image or current 
+ *	  stack and creates the color table from scratch.
+ *	
+ *
+ *	  To do list 
+ *
+ *	   1) Modify existing Animated Gif reader plug in to import in 8 bit mode (currently only works in 
+ *		   RGB	mode.  Right now the best way to alter an animated gif is to save the first image separately
+ *		   and then read the single gif and use the plugin animated reader to read the animated gif to the 
+ *		   stack. Let this plugin encode the stack using the single gif's color table.
+ *		2) Add support for background colors easy but I have no use for them
+ *		3) RGB to 8 bit converter is a linear search. Needs to be replaced with sorted list and fast search. But 
+ *			this update could cause problems with some types of gifs. Easy fix get a faster computer.
+ *		4) Try updating NN color converter seems to be heavily weighted towards quantity of pixels.
+ *		  example:
+ *			 if there is 90% of the image covered in shades of one color or grey the 10% of other colors tend 
+ *			 to be poorly represented it  over fits the shades and under fits the others. Works well if the
+ *			distribution  is balanced.
+ *		 5) Add support for all sizes of Color Look Up tables.
+ *		 6) Re-code to be cleaner. This is my second Java program and I started with some code with too 
+ *			 many  global variables and I added more switches so its a bit hard to follow.
+ *
+ * Credits for the base conversion codes
+ * No copyright asserted on the source code of this class.	May be used
+ * for any purpose, however, refer to the Unisys LZW patent for restrictions
+ * on use of the associated LZWEncoder class.  Please forward any corrections
+ * to kweiner@fmsware.com.
+ *
+ * @author Kevin Weiner, FM Software
+ * @version 1.0 December 2000
+ *
+ *
+ * Example:
+ *	  AnimatedGifEncoder2 e = new AnimatedGifEncoder2();
+ *	  e.start(outputFileName);
+ *	  e.addFrame(image1);
+ *	  e.addFrame(image2);
+ *		"			"			  "
+ *	  e.finish();
+ * 
+ * + * + */ + +class AnimatedGifEncoder2 { + + protected int width; // image size + protected int height; + protected boolean transparent = false; // transparent color if given + protected int transIndex; // transparent index in color table + protected int repeat = -1; // no repeat + protected int delay = 50; // frame delay (hundredths) + protected boolean started = false; // ready to output frames + protected OutputStream out; + protected ImagePlus image; // current frame + protected byte[] pixels; // BGR byte array from frame + protected byte[] indexedPixels; // converted frame indexed to palette + protected int colorDepth; // number of bit planes + protected byte[] colorTab; // RGB palette + protected int lctSize = 7; // local color table size (bits-1) + protected int dispose = 0; // disposal code (-1 = use default) + protected boolean closeStream = false; // close stream when finished + protected boolean firstFrame = true; + protected boolean sizeSet = false; // if false, get size from first frame + protected int sample = 2; // default sample interval for quantizer distance should be small for small icons + protected byte[] gct = null; //Global color table + protected boolean gctused = false; // Set to true to use Global color table + protected boolean autotransparent = false; // Set True if transparency index coming from image 8 bit only + protected boolean GCTextracted = false; // Set if global color table extracted from rgb image + protected boolean GCTloadedExternal = false; // Set if global color table loaded directly from external image + protected int GCTred = 0; //Transparent Color + protected int GCTgrn = 0; // green + protected int GCTbl = 0; // blue + protected int GCTcindex = 0; //index into color table + protected boolean GCTsetTransparent = false; //If true then Color table transparency index is set + protected boolean GCToverideIndex = false; //If true Transparent index is set to index with closest colors + protected boolean GCToverideColor = false; //if true Color at Transparent index is set to GCTred, GCTgrn GCTbl /** * Adds next GIF frame. The frame is not written immediately, but is @@ -190,74 +257,200 @@ class GifEncoder { boolean ok = true; try { if (firstFrame) { - if (!sizeSet) // use first frame's size + if (!sizeSet) { + // use first frame's size setSize(image.getWidth(), image.getHeight()); - writeLSD(); - if (repeat>=0) writeNetscapeExt(); // use NS app extension to indicate reps + } + if(gctused) + writeLSDgct(); // logical screen descriptior + if (GCTloadedExternal){ //Using external image as color table + colorTab = gct; + TransparentIndex(colorTab); //check transparency color + writePalette(); // write global color table + if (repeat >= 0) + writeNetscapeExt(); // use NS app extension to indicate reps + } + if (!gctused) { + writeLSD(); + if (repeat >= 0) + writeNetscapeExt(); // use NS app extension to indicate reps + } firstFrame = false; } - int bitDepth = image.getBitDepth(); - // If indexed byte image then format does not need changing + + int type = image.getType(); + // If indexed byte image then format does not need changing int k; - Process8bitCLT(image); - writeGraphicCtrlExt(); // write graphic control extension - writeImageDesc(); // image descriptor - writePalette(); // local color table - writePixels(); // encode and write pixel data + if ((type == 0) ||( type == 3)) //8 bit images + Process8bitCLT(image); + else if (type==4) { //4 for RGB + packrgb(image); + OverRideQuality(image.getWidth()*image.getHeight()); + if (gctused && (gct == null)) { //quality should not depend on image size + analyzePixels(); // build global color table & map pixels + colorTab = gct; + TransparentIndex(colorTab); //check transparency color + writePalette(); // write global color table + if (repeat >= 0) + writeNetscapeExt(); // use NS app extension to indicate reps + } else + analyzePixels(); // build color table & map pixels + } + else throw new IllegalArgumentException("Image must be 8-bit or RGB"); + TransparentIndex(colorTab); //check transparency color + writeGraphicCtrlExt(); // write graphic control extension + writeImageDesc(); // image descriptor + if(!gctused) writePalette(); // local color table + writePixels(); // encode and write pixel data } catch (IOException e) { ok = false; } + return ok; - } - - /* -* Get Options because options box has been checked - - Some of the code being set - setTransparent(Color.black); - Dispose = 0; - setDelay(500); // time per frame in milliseconds - gctused = false; // Set to true to use Global color table - GCTextracted = false; // Set if global color table extracted from rgb image - GCTloadedExternal = false; // Set if global color table loaded directly from external image - GCTextracted = false; // Set if global color table extracted from rgb image - GCTred = 0; //Transparent Color - GCTgrn = 0; // green - GCTbl = 0; // blue - GCTcindex = 0; //index into color table - autotransparent = false; // Set True if transparency index coming from image 8 bit only - GCTsetTransparent = true; //If true then Color table transparency index is set - GCToverideIndex = false; //If true Transparent index is set to index with closest colors - GCToverideColor = false; //if true Color at Transparent index is set to GCTred, GCTgrn GCTbl + } -*/ + /* + Handles transparency color Index + Assumes colors and index are already checked for validity + */ + void TransparentIndex(byte[] colorTab){ + if(autotransparent|| !GCTsetTransparent) return; + if(colorTab==null)throw new IllegalArgumentException("Color Table not loaded."); + int len = colorTab.length; + setTransparent(true); //Sets color tranparency flag + if (!(GCToverideColor||GCToverideIndex)){ + transIndex = GCTcindex; //sets color index + return; + } + if(GCToverideIndex) + GCTcindex= findClosest(colorTab, GCTred, GCTgrn, GCTbl); + //finds index in color Table + transIndex = GCTcindex; + int pindex = 3*GCTcindex; + if (pindex>(len-3)) + throw new IllegalArgumentException("Index ("+transIndex+") too large for Color Lookup table."); + colorTab[pindex++] = (byte)GCTred; //Set Color Table[transparent index] with specified color + colorTab[pindex++] = (byte)GCTgrn; + colorTab[pindex] = (byte)GCTbl; + } +String name; + +public boolean setoptions() { + String[] GCTtype = {"Do not use","Load from Current Image", "Load from another Image RGB or 8 Bit", + "Use another RGB to create a new color table " }; + String[] DisposalType = { "No Disposal","Do not Dispose", "Restore to Background", "Restore to previous" }; + String[] TransparencyType ={"No Transparency", "Automatically Set if Available (8 bit only)", "Set to Index", + "Set to index with specified color", "Set to the index that is closest to specified color"}; + int setdelay=delay*10; + int gctType=0; + int setTrans; + if (GCTloadedExternal) gctType = 2; + if (GCTextracted&&GCTloadedExternal) gctType =3; + if (gctused&&!(GCTextracted||GCTloadedExternal))gctType=1; + setTrans=1; + if (!(autotransparent||GCTsetTransparent||GCToverideIndex||GCToverideColor)) setTrans=0; + if (GCTsetTransparent&& !(GCToverideIndex||GCToverideColor)) setTrans = 2; + if (GCTsetTransparent&& GCToverideIndex && !GCToverideColor) setTrans = 4; + if (GCTsetTransparent&& !GCToverideIndex && GCToverideColor) setTrans = 3; + int red = GCTred; + int grn = GCTgrn; + int bl = GCTbl; + int cindex =GCTcindex; + setRepeat(0); + autotransparent=false; //no transparent index + GCTsetTransparent=false; + GCToverideIndex=false; + GCToverideColor=false; + setTransparent(false); + switch (setTrans) { + case 0: break; + case 1: autotransparent=true; //Set if available from image byte images only + break; + case 2: if(cindex>-1) { + GCTsetTransparent=true; //set specified index as transparent color + GCTcindex=cindex; + } else + IJ.error("Incorrect color index must have value between 0 and 255"); + break; + case 3: if((cindex>-1)&&(red>-1)) { //Set transparent index with specified color + GCTsetTransparent=true; + GCToverideColor=true; + GCTcindex=cindex; + GCTred=red; + GCTgrn=grn; + GCTbl=bl; + } else + IJ.error("Incorrect colors or color index, they must have values between 0 and 255."); + break; + case 4: if(red>-1){ + GCTsetTransparent=true; //Set transparent index to + GCToverideIndex=true; //index which is closest to the specified color + GCTred=red; // and replace the color at the index with + GCTgrn=grn; + GCTbl=bl; + } else + IJ.error("Incorrect colors, they must have values between 0 and 255."); + break; + default: break; + } + + gctused = false; // Set to true to use Global color table + GCTextracted = false; // Set if global color table extracted from rgb image + GCTloadedExternal = false; // Set if global color table loaded directly from external image + return true; + } /******************************************************** -* Gets Color lookup Table from 8 bit ImagePlus +* Gets Color lookup Table from 8 bit image plus pointer to image */ -void Process8bitCLT(ImagePlus image){ - colorDepth = 8; - //setTransparent(false); - ImageProcessor ip = image.getProcessor(); - ip = ip.convertToByte(true); - ColorModel cm = ip.getColorModel(); - indexedPixels = (byte[])(ip.getPixels()); +void Process8bitCLT(ImagePlus image) { + colorDepth = 8; + setTransparent(false); + ByteProcessor pg = new ByteProcessor(image.getImage()); + ColorModel cm = pg.getColorModel(); + if (cm instanceof IndexColorModel) + indexedPixels = (byte[])(pg.getPixels()); + else + throw new IllegalArgumentException("Image must be 8-bit"); IndexColorModel m = (IndexColorModel)cm; - int mapSize = m.getMapSize(); - if (transIndex>=mapSize) { - setTransparent(false); - transIndex = 0; + if (autotransparent) { + transIndex = m.getTransparentPixel(); + if ((transIndex > -1) && (transIndex < 256)) setTransparent(true); //Sets color flag + else transIndex =0; } + int mapSize = m.getMapSize(); int k; - colorTab = new byte[mapSize*3]; - for (int i = 0; i < mapSize; i++) { - k=i*3; - colorTab[k] = (byte)m.getRed(i); - colorTab[k+1] = (byte)m.getGreen(i); - colorTab[k+2] = (byte)m.getBlue(i); + if (gctused && (gct == null)) { + gct = new byte[mapSize*3]; //Global color table needs to be intialized + for (int i = 0; i < mapSize; i++) { + k=i*3; + colorTab[k] = (byte)m.getRed(i); + colorTab[k+1] = (byte)m.getGreen(i); + colorTab[k+2] = (byte)m.getBlue(i); + } + try { + if (! GCTloadedExternal) { + colorTab = gct; + writePalette(); // write global color table + if (repeat >= 0) + writeNetscapeExt(); // use NS app extension to indicate reps + } + } catch (IOException e) { + System.err.println("Caught IOException: " + e.getMessage()); + } + } + if (gctused) + colorTab = gct; + else { + colorTab = new byte[mapSize*3]; + for (int i = 0; i < mapSize; i++) { + k=i*3; + colorTab[k] = (byte)m.getRed(i); + colorTab[k+1] = (byte)m.getGreen(i); + colorTab[k+2] = (byte)m.getBlue(i); + } } - m.finalize(); - - } + m.finalize(); + } /** * Flushes any pending data and closes output file. @@ -291,6 +484,82 @@ void Process8bitCLT(ImagePlus image){ return ok; } + +/* + * Function to load Global Color Table from 8 bit ImagePlus + * This function has to be called before addFrame + */ + public void loadGCT8bit(ImagePlus image){ + int type = image.getType(); + if (!(((type == 0) ||( type == 3))&&(image!=null))) + throw new IllegalArgumentException("Color Table Image must be 8 bit"); + gctused = true; + GCTloadedExternal = true; + gct = null; + Process8bitCLT(image); + } +/* + * Function to extract Global Color Table from RGB ImagePlus + * This function has to be called before addFrame + */ + public void extractGCTrgb(ImagePlus image){ + if((image== null)||(4!=image.getType())) + throw new IllegalArgumentException("Color Table Image must be RGB"); + packrgb(image); + gctused = true; + GCTextracted = true; + GCTloadedExternal =true; + gct = null; + OverRideQuality(image.getWidth()*image.getHeight()); + analyzePixels(); // build color table + pixels = null; + } + +void packrgb(ImagePlus image){ + int len = image.getWidth()*image.getHeight(); + ImageProcessor imp = image.getProcessor(); + int[] pix = (int[]) imp.getPixels(); + pixels = new byte[len*3]; + //pack pixels + for(int i=0; i>16); //red + pixels[k+1] = (byte)((pix[i] & 0x00ff00)>>8); //green + pixels[k] = (byte)(pix[i] & 0x0000ff); //blue + } +} + +/* + * Function to use the first up to 255 elements of a RGB ImagePlus to construct + * a global color table + * This function has to be called before addFrame + */ +public void loadGCTrgb(ImagePlus image){ + if((image == null)||(4!=image.getType())) + throw new IllegalArgumentException("Color Table Image must be RGB"); + int len = image.getWidth()*image.getHeight(); + if(len>255)len=255; + ImageProcessor imp = image.getProcessor(); + int[] pix = (int[]) imp.getPixels(); + gct = new byte[len*3]; + //pack pixels into color Table + for(int i=0; i>16); //red + gct[k+1] = (byte)((pix[i] & 0x00ff00)>>8); //green + gct[k+2] = (byte)(pix[i] & 0x0000ff); //blue + } + gctused = true; + GCTloadedExternal = true; +} + + /* + * If gct = true then a global color table is use + * + */ + public void setGCT(boolean flag){ + gctused = flag; + } /** * Sets the delay time between each frame, or changes it @@ -343,6 +612,14 @@ void Process8bitCLT(ImagePlus image){ if (quality < 1) quality = 1; sample = quality; } +/** + * Set True for Global Color Table use + * This saves space in the output file but colors may not be so goodif the stack uses + * True color images + */ + public void GlobalColorTableused(boolean gtu){ + gctused = gtu; + } /** * Sets the number of times the set of GIF frames @@ -358,7 +635,6 @@ void Process8bitCLT(ImagePlus image){ repeat = iter; } - /** * Sets the GIF frame size. The default size is the * size of the first frame added if this method is @@ -437,7 +713,105 @@ void Process8bitCLT(ImagePlus image){ if(sample < 1) sample = 1; } - + /** + * Analyzes image colors and creates color map. + */ + protected void analyzePixels() { + int len = pixels.length; + int nPix = len / 3; + indexedPixels = new byte[nPix]; + if (gctused && (gct == null)) { + NeuQuant nq = new NeuQuant(pixels, len, sample); // initialize quantizer + colorTab = nq.process(); // create reduced palette + gct = new byte[colorTab.length]; + // convert map from BGR to RGB + for (int i = 0; i < colorTab.length; i+=3) { + byte temp = colorTab[i]; + colorTab[i] = colorTab[i+2]; + colorTab[i+2] = temp; + gct[i] = colorTab[i]; + gct[i+1] = colorTab[i+1]; + gct[i+2] =colorTab[i+2]; + } + if(GCTextracted){ + indexedPixels= null; + return; + } + } + if (!gctused){ + NeuQuant nq = new NeuQuant(pixels, len, sample); // initialize quantizer + colorTab = nq.process(); // create reduced palette + // convert map from BGR to RGB + for (int i = 0; i < colorTab.length; i+=3) { + byte temp = colorTab[i]; + colorTab[i] = colorTab[i+2]; + colorTab[i+2] = temp; + } + // map image pixels to new palette + int k = 0; + for (int i = 0; i < nPix; i++) + indexedPixels[i] = + (byte) nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff); + pixels = null; + colorDepth = 8; + lctSize = 7; + } + if(gctused){ + // find closest match for all pixels This routine is not optimized real slow linear search. + colorTab = gct; + int k = 0; + int minpos; + for (int j = 0; j < nPix; j++){ + int b = pixels[k++] & 0xff; + int g = pixels[k++] & 0xff; + int r = pixels[k++] & 0xff; + minpos = 0; + int dmin = 256*256*256; + int lenct = colorTab.length; + for (int i = 0; i < lenct; ) { + int dr = r - (colorTab[i++] & 0xff); + int dg = g - (colorTab[i++] & 0xff); + int db = b - (colorTab[i] & 0xff); + int d = dr*dr + dg*dg + db*db; + if (d < dmin) { + dmin = d; + minpos = i/3; + } + i++; + }//end inside for + indexedPixels[j]=(byte)minpos; + }//end for + pixels = null; + colorDepth = 8; + lctSize = 7; + } //end if +} + + + + /** + * Returns index of palette color closest to c + * + */ + protected int findClosest(byte[] colorTab, int r, int g, int b) { + if (colorTab == null) return -1; + int minpos = 0; + int dmin = 256*256*256; + int len = colorTab.length; + for (int i = 0; i < len; ) { + int dr = r - (colorTab[i++] & 0xff); + int dg = g - (colorTab[i++] & 0xff); + int db = b - (colorTab[i] & 0xff); + int d = dr*dr + dg*dg + db*db; + if (d < dmin) { + dmin = d; + minpos = i/3; + } + i++; + } + return minpos; + } + /** * Writes Graphic Control Extension */ @@ -479,14 +853,17 @@ void Process8bitCLT(ImagePlus image){ writeShort(width); // image size writeShort(height); // packed fields - out.write(0x80 | // 1 local color table 1=yes + if(gctused) + out.write(0x00); //global color table + else + out.write(0x80 | // 1 local color table 1=yes 0 | // 2 interlace - 0=no - 0 | // 3 sorted - 0=no - 0 | // 4-5 reserved - lctSize); // size of local color table + 0 | // 3 sorted - 0=no + 0 | // 4-5 reserved + lctSize); // size of local color table + } - /** * Writes Logical Screen Descriptor with global color table */ @@ -521,7 +898,6 @@ void Process8bitCLT(ImagePlus image){ out.write(0); // pixel aspect ratio - assume 1:1 } - /** * Writes Netscape application extension to define * repeat count. @@ -537,7 +913,6 @@ void Process8bitCLT(ImagePlus image){ out.write(0); // block terminator } - /** * Writes color table */ @@ -548,16 +923,15 @@ void Process8bitCLT(ImagePlus image){ out.write(0); } - /** * Encodes and writes pixel data */ protected void writePixels() throws IOException { - LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth); + LZWEncoder2 encoder = + new LZWEncoder2(width, height, indexedPixels, colorDepth); encoder.encode(out); } - /** * Write 16-bit value to output stream, LSB first */ @@ -566,7 +940,6 @@ void Process8bitCLT(ImagePlus image){ out.write((value >> 8) & 0xff); } - /** * Writes string to output stream */ @@ -576,6 +949,787 @@ void Process8bitCLT(ImagePlus image){ } } +//============================================================================== +// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. +// K Weiner 12/00 +class LZWEncoder2 { + + private static final int EOF = -1; + + private int imgW, imgH; + private byte[] pixAry; + private int initCodeSize; + private int remaining; + private int curPixel; + + + // GIFCOMPR.C - GIF Image compression routines + // + // Lempel-Ziv compression based on 'compress'. GIF modifications by + // David Rowley (mgardi@watdcsu.waterloo.edu) + + // General DEFINEs + + static final int BITS = 12; + + static final int HSIZE = 5003; // 80% occupancy + + // GIF Image compression - modified 'compress' + // + // Based on: compress.c - File compression ala IEEE Computer, June 1984. + // + // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) + // Jim McKie (decvax!mcvax!jim) + // Steve Davies (decvax!vax135!petsd!peora!srd) + // Ken Turkowski (decvax!decwrl!turtlevax!ken) + // James A. Woods (decvax!ihnp4!ames!jaw) + // Joe Orost (decvax!vax135!petsd!joe) + + int n_bits; // number of bits/code + int maxbits = BITS; // user settable max # bits/code + int maxcode; // maximum code, given n_bits + int maxmaxcode = 1 << BITS; // should NEVER generate this code + + int[] htab = new int[HSIZE]; + int[] codetab = new int[HSIZE]; + + int hsize = HSIZE; // for dynamic table sizing + + int free_ent = 0; // first unused entry + + // block compression parameters -- after all codes are used up, + // and compression rate changes, start over. + boolean clear_flg = false; + + // Algorithm: use open addressing double hashing (no chaining) on the + // prefix code / next character combination. We do a variant of Knuth's + // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + // secondary probe. Here, the modular division first probe is gives way + // to a faster exclusive-or manipulation. Also do block compression with + // an adaptive reset, whereby the code table is cleared when the compression + // ratio decreases, but after the table fills. The variable-length output + // codes are re-sized at this point, and a special CLEAR code is generated + // for the decompressor. Late addition: construct the table according to + // file size for noticeable speed improvement on small files. Please direct + // questions about this implementation to ames!jaw. + + int g_init_bits; + + int ClearCode; + int EOFCode; + + // output + // + // Output the given code. + // Inputs: + // code: A n_bits-bit integer. If == -1, then EOF. This assumes + // that n_bits =< wordsize - 1. + // Outputs: + // Outputs code to the file. + // Assumptions: + // Chars are 8 bits long. + // Algorithm: + // Maintain a BITS character long buffer (so that 8 codes will + // fit in it exactly). Use the VAX insv instruction to insert each + // code in turn. When the buffer fills up empty it and start over. + + int cur_accum = 0; + int cur_bits = 0; + + int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, + 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, + 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + + // Number of characters so far in this 'packet' + int a_count; + + // Define the storage for the packet accumulator + byte[] accum = new byte[256]; + + + //---------------------------------------------------------------------------- + LZWEncoder2(int width, int height, byte[] pixels, int color_depth) + { + imgW = width; + imgH = height; + pixAry = pixels; + initCodeSize = Math.max(2, color_depth); + } + + + // Add a character to the end of the current packet, and if it is 254 + // characters, flush the packet to disk. + void char_out( byte c, OutputStream outs ) throws IOException + { + accum[a_count++] = c; + if ( a_count >= 254 ) + flush_char( outs ); + } + + + // Clear out the hash table + + // table clear for block compress + void cl_block( OutputStream outs ) throws IOException + { + cl_hash( hsize ); + free_ent = ClearCode + 2; + clear_flg = true; + + output( ClearCode, outs ); + } + + + // reset code table + void cl_hash( int hsize ) + { + for ( int i = 0; i < hsize; ++i ) + htab[i] = -1; + } + + + void compress( int init_bits, OutputStream outs ) throws IOException + { + int fcode; + int i /* = 0 */; + int c; + int ent; + int disp; + int hsize_reg; + int hshift; + + // Set up the globals: g_init_bits - initial number of bits + g_init_bits = init_bits; + + // Set up the necessary values + clear_flg = false; + n_bits = g_init_bits; + maxcode = MAXCODE( n_bits ); + + ClearCode = 1 << ( init_bits - 1 ); + EOFCode = ClearCode + 1; + free_ent = ClearCode + 2; + + a_count = 0; // clear packet + + ent = nextPixel(); + + hshift = 0; + for ( fcode = hsize; fcode < 65536; fcode *= 2 ) + ++hshift; + hshift = 8 - hshift; // set hash code range bound + + hsize_reg = hsize; + cl_hash( hsize_reg ); // clear hash table + + output( ClearCode, outs ); + + outer_loop: + while ( (c = nextPixel()) != EOF ) + { + fcode = ( c << maxbits ) + ent; + i = ( c << hshift ) ^ ent; // xor hashing + + if ( htab[i] == fcode ) + { + ent = codetab[i]; + continue; + } + else if ( htab[i] >= 0 ) // non-empty slot + { + disp = hsize_reg - i; // secondary hash (after G. Knott) + if ( i == 0 ) + disp = 1; + do + { + if ( (i -= disp) < 0 ) + i += hsize_reg; + + if ( htab[i] == fcode ) + { + ent = codetab[i]; + continue outer_loop; + } + } + while ( htab[i] >= 0 ); + } + output( ent, outs ); + ent = c; + if ( free_ent < maxmaxcode ) + { + codetab[i] = free_ent++; // code -> hashtable + htab[i] = fcode; + } + else + cl_block( outs ); + } + // Put out the final code. + output( ent, outs ); + output( EOFCode, outs ); + } + + + //---------------------------------------------------------------------------- + void encode(OutputStream os) throws IOException + { + os.write(initCodeSize); // write "initial code size" byte + + remaining = imgW * imgH; // reset navigation variables + curPixel = 0; + + compress(initCodeSize + 1, os); // compress and write the pixel data + + os.write(0); // write block terminator + } + + + // Flush the packet to disk, and reset the accumulator + void flush_char( OutputStream outs ) throws IOException + { + if ( a_count > 0 ) + { + outs.write( a_count ); + outs.write( accum, 0, a_count ); + a_count = 0; + } + } + + + final int MAXCODE( int n_bits ) + { + return ( 1 << n_bits ) - 1; + } + + + //---------------------------------------------------------------------------- + // Return the next pixel from the image + //---------------------------------------------------------------------------- + private int nextPixel() + { + if (remaining == 0) + return EOF; + + --remaining; + + byte pix = pixAry[curPixel++]; + + return pix & 0xff; + } + + + void output( int code, OutputStream outs ) throws IOException + { + cur_accum &= masks[cur_bits]; + + if ( cur_bits > 0 ) + cur_accum |= ( code << cur_bits ); + else + cur_accum = code; + + cur_bits += n_bits; + + while ( cur_bits >= 8 ) + { + char_out( (byte) ( cur_accum & 0xff ), outs ); + cur_accum >>= 8; + cur_bits -= 8; + } + + // If the next entry is going to be too big for the code size, + // then increase it, if possible. + if ( free_ent > maxcode || clear_flg ) + { + if ( clear_flg ) + { + maxcode = MAXCODE(n_bits = g_init_bits); + clear_flg = false; + } + else + { + ++n_bits; + if ( n_bits == maxbits ) + maxcode = maxmaxcode; + else + maxcode = MAXCODE(n_bits); + } + } + + if ( code == EOFCode ) + { + // At EOF, write the rest of the buffer. + while ( cur_bits > 0 ) + { + char_out( (byte) ( cur_accum & 0xff ), outs ); + cur_accum >>= 8; + cur_bits -= 8; + } + + flush_char( outs ); + } + } +} + + +/* NeuQuant Neural-Net Quantization Algorithm + * ------------------------------------------ + * + * Copyright (c) 1994 Anthony Dekker + * + * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. + * See "Kohonen neural networks for optimal colour quantization" + * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367. + * for a discussion of the algorithm. + * + * Any party obtaining a copy of these files from the author, directly or + * indirectly, is granted, free of charge, a full and unrestricted irrevocable, + * world-wide, paid up, royalty-free, nonexclusive right and license to deal + * in this software and documentation files (the "Software"), including without + * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons who receive + * copies from any such party to do so, with the only requirement being + * that this copyright notice remain intact. + */ + +// Ported to Java 12/00 K Weiner + +class NeuQuant { + + protected static final int netsize = 256; /* number of colours used */ + + /* four primes near 500 - assume no image has a length so large */ + /* that it is divisible by all four primes */ + protected static final int prime1 = 499; + protected static final int prime2 = 491; + protected static final int prime3 = 487; + protected static final int prime4 = 503; + + protected static final int minpicturebytes = (3 * prime4); + /* minimum size for input image */ + + /* Program Skeleton + ---------------- + [select samplefac in range 1..30] + [read image from input file] + pic = (unsigned char*) malloc(3*width*height); + initnet(pic,3*width*height,samplefac); + learn(); + unbiasnet(); + [write output image header, using writecolourmap(f)] + inxbuild(); + write output image using inxsearch(b,g,r) */ + + /* Network Definitions + ------------------- */ + + protected static final int maxnetpos = (netsize - 1); + protected static final int netbiasshift = 4; /* bias for colour values */ + protected static final int ncycles = 100; /* no. of learning cycles */ + + /* defs for freq and bias */ + protected static final int intbiasshift = 16; /* bias for fractions */ + protected static final int intbias = (((int) 1) << intbiasshift); + protected static final int gammashift = 10; /* gamma = 1024 */ + protected static final int gamma = (((int) 1) << gammashift); + protected static final int betashift = 10; + protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */ + protected static final int betagamma = (intbias << (gammashift - betashift)); + + /* defs for decreasing radius factor */ + protected static final int initrad = (netsize >> 3); /* for 256 cols, radius starts */ + protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */ + protected static final int radiusbias = (((int) 1) << radiusbiasshift); + protected static final int initradius = (initrad * radiusbias); /* and decreases by a */ + protected static final int radiusdec = 30; /* factor of 1/30 each cycle */ + + /* defs for decreasing alpha factor */ + protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */ + protected static final int initalpha = (((int) 1) << alphabiasshift); + + protected int alphadec; /* biased by 10 bits */ + + /* radbias and alpharadbias used for radpower calculation */ + protected static final int radbiasshift = 8; + protected static final int radbias = (((int) 1) << radbiasshift); + protected static final int alpharadbshift = (alphabiasshift + radbiasshift); + protected static final int alpharadbias = (((int) 1) << alpharadbshift); + + /* Types and Global Variables + -------------------------- */ + + protected byte[] thepicture; /* the input image itself */ + protected int lengthcount; /* lengthcount = H*W*3 */ + + protected int samplefac; /* sampling factor 1..30 */ + + // typedef int pixel[4]; /* BGRc */ + protected int[][] network; /* the network itself - [netsize][4] */ + + protected int[] netindex = new int[256]; /* for network lookup - really 256 */ + + protected int[] bias = new int[netsize]; /* bias and freq arrays for learning */ + protected int[] freq = new int[netsize]; + protected int[] radpower = new int[initrad]; /* radpower for precomputation */ + + + /* Initialise network in range (0,0,0) to (255,255,255) and set parameters + ----------------------------------------------------------------------- */ + + public NeuQuant(byte[] thepic, int len, int sample) { + + int i; + int[] p; + + thepicture = thepic; + lengthcount = len; + samplefac = sample; + + network = new int[netsize][]; + for (i = 0; i < netsize; i++) { + network[i] = new int[4]; + p = network[i]; + p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize; + freq[i] = intbias / netsize; /* 1/netsize */ + bias[i] = 0; + } + } + + + public byte[] colorMap() { + byte[] map = new byte[3*netsize]; + int[] index = new int[netsize]; + for (int i = 0; i < netsize; i++) + index[network[i][3]] = i; + int k = 0; + for (int i = 0; i < netsize; i++) { + int j = index[i]; + map[k++] = (byte) (network[j][0]); + map[k++] = (byte) (network[j][1]); + map[k++] = (byte) (network[j][2]); + } + return map; + } + + + /* Insertion sort of network and building of netindex[0..255] (to do after unbias) + ------------------------------------------------------------------------------- */ + + public void inxbuild() { + + int i, j, smallpos, smallval; + int[] p; + int[] q; + int previouscol, startpos; + + previouscol = 0; + startpos = 0; + for (i = 0; i < netsize; i++) { + p = network[i]; + smallpos = i; + smallval = p[1]; /* index on g */ + /* find smallest in i..netsize-1 */ + for (j = i + 1; j < netsize; j++) { + q = network[j]; + if (q[1] < smallval) { /* index on g */ + smallpos = j; + smallval = q[1]; /* index on g */ + } + } + q = network[smallpos]; + /* swap p (i) and q (smallpos) entries */ + if (i != smallpos) { + j = q[0]; q[0] = p[0]; p[0] = j; + j = q[1]; q[1] = p[1]; p[1] = j; + j = q[2]; q[2] = p[2]; p[2] = j; + j = q[3]; q[3] = p[3]; p[3] = j; + } + /* smallval entry is now in position i */ + if (smallval != previouscol) { + netindex[previouscol] = (startpos + i) >> 1; + for (j = previouscol + 1; j < smallval; j++) + netindex[j] = i; + previouscol = smallval; + startpos = i; + } + } + netindex[previouscol] = (startpos + maxnetpos) >> 1; + for (j = previouscol + 1; j < 256; j++) + netindex[j] = maxnetpos; /* really 256 */ + } + + + /* Main Learning Loop + ------------------ */ + + public void learn() { + + int i, j, b, g, r; + int radius, rad, alpha, step, delta, samplepixels; + byte[] p; + int pix, lim; + + if (lengthcount < minpicturebytes) + samplefac = 1; + alphadec = 30 + ((samplefac - 1) / 3); + p = thepicture; + pix = 0; + lim = lengthcount; + samplepixels = lengthcount / (3 * samplefac); + delta = samplepixels / ncycles; + alpha = initalpha; + radius = initradius; + + rad = radius >> radiusbiasshift; + if (rad <= 1) + rad = 0; + for (i = 0; i < rad; i++) + radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad)); + + //fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad); + + if (lengthcount < minpicturebytes) + step = 3; + else if ((lengthcount % prime1) != 0) + step = 3 * prime1; + else { + if ((lengthcount % prime2) != 0) + step = 3 * prime2; + else { + if ((lengthcount % prime3) != 0) + step = 3 * prime3; + else + step = 3 * prime4; + } + } + + i = 0; + while (i < samplepixels) { + b = (p[pix + 0] & 0xff) << netbiasshift; + g = (p[pix + 1] & 0xff) << netbiasshift; + r = (p[pix + 2] & 0xff) << netbiasshift; + j = contest(b, g, r); + + altersingle(alpha, j, b, g, r); + if (rad != 0) + alterneigh(rad, j, b, g, r); /* alter neighbours */ + + pix += step; + if (pix >= lim) + pix -= lengthcount; + + i++; + if (i % delta == 0) { + alpha -= alpha / alphadec; + radius -= radius / radiusdec; + rad = radius >> radiusbiasshift; + if (rad <= 1) + rad = 0; + for (j = 0; j < rad; j++) + radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad)); + } + } + //fprintf(stderr,"finished 1D learning: final alpha=%f !\n",((float)alpha)/initalpha); + } + + + /* Search for BGR values 0..255 (after net is unbiased) and return colour index + ---------------------------------------------------------------------------- */ + + public int map(int b, int g, int r) { + + int i, j, dist, a, bestd; + int[] p; + int best; + + bestd = 1000; /* biggest possible dist is 256*3 */ + best = -1; + i = netindex[g]; /* index on g */ + j = i - 1; /* start at netindex[g] and work outwards */ + + while ((i < netsize) || (j >= 0)) { + if (i < netsize) { + p = network[i]; + dist = p[1] - g; /* inx key */ + if (dist >= bestd) + i = netsize; /* stop iter */ + else { + i++; + if (dist < 0) + dist = -dist; + a = p[0] - b; + if (a < 0) + a = -a; + dist += a; + if (dist < bestd) { + a = p[2] - r; + if (a < 0) + a = -a; + dist += a; + if (dist < bestd) { + bestd = dist; + best = p[3]; + } + } + } + } + if (j >= 0) { + p = network[j]; + dist = g - p[1]; /* inx key - reverse dif */ + if (dist >= bestd) + j = -1; /* stop iter */ + else { + j--; + if (dist < 0) + dist = -dist; + a = p[0] - b; + if (a < 0) + a = -a; + dist += a; + if (dist < bestd) { + a = p[2] - r; + if (a < 0) + a = -a; + dist += a; + if (dist < bestd) { + bestd = dist; + best = p[3]; + } + } + } + } + } + return (best); + } + + + public byte[] process() { + learn(); + unbiasnet(); + inxbuild(); + return colorMap(); + } + + + /* Unbias network to give byte values 0..255 and record position i to prepare for sort + ----------------------------------------------------------------------------------- */ + + public void unbiasnet() { + + int i, j; + + for (i = 0; i < netsize; i++) { + network[i][0] >>= netbiasshift; + network[i][1] >>= netbiasshift; + network[i][2] >>= netbiasshift; + network[i][3] = i; /* record colour no */ + } + } + + + /* Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|] + --------------------------------------------------------------------------------- */ + + protected void alterneigh(int rad, int i, int b, int g, int r) { + + int j, k, lo, hi, a, m; + int[] p; + + lo = i - rad; + if (lo < -1) + lo = -1; + hi = i + rad; + if (hi > netsize) + hi = netsize; + + j = i + 1; + k = i - 1; + m = 1; + while ((j < hi) || (k > lo)) { + a = radpower[m++]; + if (j < hi) { + p = network[j++]; + try { + p[0] -= (a * (p[0] - b)) / alpharadbias; + p[1] -= (a * (p[1] - g)) / alpharadbias; + p[2] -= (a * (p[2] - r)) / alpharadbias; + } catch (Exception e) {} // prevents 1.3 miscompilation + } + if (k > lo) { + p = network[k--]; + try { + p[0] -= (a * (p[0] - b)) / alpharadbias; + p[1] -= (a * (p[1] - g)) / alpharadbias; + p[2] -= (a * (p[2] - r)) / alpharadbias; + } catch (Exception e) {} + } + } + } + + + /* Move neuron i towards biased (b,g,r) by factor alpha + ---------------------------------------------------- */ + + protected void altersingle(int alpha, int i, int b, int g, int r) { + + /* alter hit neuron */ + int[] n = network[i]; + n[0] -= (alpha * (n[0] - b)) / initalpha; + n[1] -= (alpha * (n[1] - g)) / initalpha; + n[2] -= (alpha * (n[2] - r)) / initalpha; + } + + + /* Search for biased BGR values + ---------------------------- */ + + protected int contest(int b, int g, int r) { + + /* finds closest neuron (min dist) and updates freq */ + /* finds best neuron (min dist-bias) and returns position */ + /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */ + /* bias[i] = gamma*((1/netsize)-freq[i]) */ + + int i, dist, a, biasdist, betafreq; + int bestpos, bestbiaspos, bestd, bestbiasd; + int[] n; + + bestd = ~(((int) 1) << 31); + bestbiasd = bestd; + bestpos = -1; + bestbiaspos = bestpos; + + for (i = 0; i < netsize; i++) { + n = network[i]; + dist = n[0] - b; + if (dist < 0) + dist = -dist; + a = n[1] - g; + if (a < 0) + a = -a; + dist += a; + a = n[2] - r; + if (a < 0) + a = -a; + dist += a; + if (dist < bestd) { + bestd = dist; + bestpos = i; + } + biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift)); + if (biasdist < bestbiasd) { + bestbiasd = biasdist; + bestbiaspos = i; + } + betafreq = (freq[i] >> betashift); + freq[i] -= betafreq; + bias[i] += (betafreq << gammashift); + } + freq[bestpos] += beta; + bias[bestpos] -= betagamma; + return (bestbiaspos); + } +} + //============================================================================== // Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. diff -pruN 1.51q-1/ij/plugin/HyperStackConverter.java 1.52g-1/ij/plugin/HyperStackConverter.java --- 1.51q-1/ij/plugin/HyperStackConverter.java 2017-02-25 19:14:10.000000000 +0000 +++ 1.52g-1/ij/plugin/HyperStackConverter.java 2018-09-13 13:05:08.000000000 +0000 @@ -34,6 +34,13 @@ public class HyperStackConverter impleme return toHyperStack(imp, c, z, t, null, null); } + /** Converts the specified stack into a hyperstack with 'c' channels, 'z' slices and + 't' frames using the default ordering ("xyczt") and the specified display + mode ("composite", "color" or "grayscale"). */ + public static ImagePlus toHyperStack(ImagePlus imp, int c, int z, int t, String mode) { + return toHyperStack(imp, c, z, t, null, mode); + } + /** Converts the specified stack into a hyperstack with 'c' channels, * 'z' slices and 't' frames. The default "xyczt" order is used if * 'order' is null. The default "composite" display mode is used @@ -159,10 +166,13 @@ public class HyperStackConverter impleme } if (Recorder.record && Recorder.scriptMode()) { String order = orders[ordering]; - if (order.equals(orders[0])) - order = "default"; - Recorder.recordCall("imp2 = HyperStackConverter.toHyperStack(imp, "+nChannels+", "+ - nSlices+", "+nFrames+", \""+order+"\", \""+modes[mode]+"\");"); + if (order.equals(orders[0])) { // default order + Recorder.recordCall("imp2 = HyperStackConverter.toHyperStack(imp, "+nChannels+", "+ + nSlices+", "+nFrames+", \""+modes[mode]+"\");"); + } else { + Recorder.recordCall("imp2 = HyperStackConverter.toHyperStack(imp, "+nChannels+", "+ + nSlices+", "+nFrames+", \""+order+"\", \""+modes[mode]+"\");"); + } } } diff -pruN 1.51q-1/ij/plugin/HyperStackReducer.java 1.52g-1/ij/plugin/HyperStackReducer.java --- 1.51q-1/ij/plugin/HyperStackReducer.java 2014-01-14 09:16:36.000000000 +0000 +++ 1.52g-1/ij/plugin/HyperStackReducer.java 2018-04-06 08:33:06.000000000 +0000 @@ -51,6 +51,7 @@ public class HyperStackReducer implement imp2.setOpenAsHyperStack(true); } else imp2 = imp.createHyperStack(title2, channels2, slices2, frames2, imp.getBitDepth()); + imp2.setProperty("Info", (String)imp.getProperty("Info")); reduce(imp2); if (channels2>1 && channels2==imp.getNChannels() && imp.isComposite()) { int mode = ((CompositeImage)imp).getMode(); diff -pruN 1.51q-1/ij/plugin/ImageInfo.java 1.52g-1/ij/plugin/ImageInfo.java --- 1.51q-1/ij/plugin/ImageInfo.java 2017-05-02 13:39:28.000000000 +0000 +++ 1.52g-1/ij/plugin/ImageInfo.java 2018-05-29 14:30:56.000000000 +0000 @@ -214,7 +214,9 @@ public class ImageInfo implements PlugIn ImageStack stack = imp.getStack(); int slice = imp.getCurrentSlice(); String number = slice + "/" + stackSize; - String label = stack.getShortSliceLabel(slice); + String label = stack.getSliceLabel(slice); + if (label!=null && label.contains("\n")) + label = stack.getShortSliceLabel(slice); if (label!=null && label.length()>0) label = " (" + label + ")"; else @@ -331,10 +333,11 @@ public class ImageInfo implements PlugIn Overlay overlay = imp.getOverlay(); if (overlay!=null) { - String hidden = imp.getHideOverlay()?" (hidden)":" "; int n = overlay.size(); String elements = n==1?" element":" elements"; - s += "Overlay: " + n + elements + (imp.getHideOverlay()?" (hidden)":"") + "\n"; + String selectable = overlay.isSelectable()?" selectable ":" non-selectable "; + String hidden = imp.getHideOverlay()?" (hidden)":""; + s += "Overlay: " + n + selectable + elements + hidden + "\n"; } else s += "No overlay\n"; diff -pruN 1.51q-1/ij/plugin/ImagesToStack.java 1.52g-1/ij/plugin/ImagesToStack.java --- 1.51q-1/ij/plugin/ImagesToStack.java 2016-01-15 19:52:48.000000000 +0000 +++ 1.52g-1/ij/plugin/ImagesToStack.java 2018-06-05 09:29:26.000000000 +0000 @@ -1,4 +1,5 @@ package ij.plugin; +import ij.plugin.frame.Recorder; import ij.*; import ij.gui.*; import ij.process.*; @@ -12,10 +13,14 @@ public class ImagesToStack implements Pl private static final int rgb = 33; private static final int COPY_CENTER=0, COPY_TOP_LEFT=1, SCALE_SMALL=2, SCALE_LARGE=3; private static final String[] methods = {"Copy (center)", "Copy (top-left)", "Scale (smallest)", "Scale (largest)"}; - private static int method = COPY_CENTER; - private static boolean bicubic; - private static boolean keep; - private static boolean titlesAsLabels = true; + private static int staticMethod = COPY_CENTER; + private static boolean staticBicubic; + private static boolean staticKeep; + private static boolean staticTitlesAsLabels = true; + private int method = COPY_CENTER; + private boolean bicubic; + private boolean keep; + private boolean titlesAsLabels = true; private String filter; private int width, height; private int maxWidth, maxHeight; @@ -24,8 +29,16 @@ public class ImagesToStack implements Pl private boolean allInvertedLuts; private Calibration cal2; private int stackType; - private ImagePlus[] image; + private ImagePlus[] images; private String name = "Stack"; + + /** Converts the images in 'images' to a stack, using the + default settings ("copy center" and "titles as labels"). */ + public static ImagePlus run(ImagePlus[] images) { + ImagesToStack itos = new ImagesToStack(); + int count = itos.findMinMaxSize(images, images.length); + return itos.convert(images, count); + } public void run(String arg) { convertImagesToStack(); @@ -41,11 +54,11 @@ public class ImagesToStack implements Pl int count = 0; int stackCount = 0; - image = new ImagePlus[wList.length]; + images = new ImagePlus[wList.length]; for (int i=0; imax) max = ip.getMax(); - String label = titlesAsLabels?image[i].getTitle():null; + String label = titlesAsLabels?images[i].getTitle():null; if (label!=null) { - String info = (String)image[i].getProperty("Info"); + String info = (String)images[i].getProperty("Info"); if (info!=null) label += "\n" + info; } if (fi!=null) { - FileInfo fi2 = image[i].getOriginalFileInfo(); + FileInfo fi2 = images[i].getOriginalFileInfo(); if (fi2!=null && !fi.directory.equals(fi2.directory)) fi = null; } @@ -172,7 +197,7 @@ public class ImagesToStack implements Pl } else { if (keep) ip = ip.duplicate(); - Overlay overlay2 = image[i].getOverlay(); + Overlay overlay2 = images[i].getOverlay(); if (overlay2!=null) { for (int j=0; j0) imp.setOverlay(overlay); - imp.show(); + return imp; } - final int findMinMaxSize(int count) { + private int findMinMaxSize(ImagePlus[] images, int count) { int index = 0; stackType = 8; width = 0; height = 0; - cal2 = image[0].getCalibration(); + cal2 = images[0].getCalibration(); maxWidth = 0; maxHeight = 0; minWidth = Integer.MAX_VALUE; @@ -219,15 +245,15 @@ public class ImagesToStack implements Pl allInvertedLuts = true; maxSize = 0; for (int i=0; istackType) stackType = type; - int w=image[i].getWidth(), h=image[i].getHeight(); + int w=images[i].getWidth(), h=images[i].getHeight(); if (w>width) width = w; if (h>height) height = h; int size = w*h; @@ -241,10 +267,10 @@ public class ImagesToStack implements Pl maxWidth = w; maxHeight = h; } - Calibration cal = image[i].getCalibration(); - if (!image[i].getCalibration().equals(cal2)) + Calibration cal = images[i].getCalibration(); + if (!images[i].getCalibration().equals(cal2)) cal2 = null; - image[index++] = image[i]; + images[index++] = images[i]; } return index; } diff -pruN 1.51q-1/ij/plugin/JavaProperties.java 1.52g-1/ij/plugin/JavaProperties.java --- 1.51q-1/ij/plugin/JavaProperties.java 2015-07-10 06:12:04.000000000 +0000 +++ 1.52g-1/ij/plugin/JavaProperties.java 2018-04-30 20:42:02.000000000 +0000 @@ -66,8 +66,7 @@ public class JavaProperties implements P String osName = System.getProperty("os.name"); list.add(" IJ.getVersion: "+IJ.getVersion()); list.add(" IJ.getFullVersion: "+IJ.getFullVersion()); - list.add(" IJ.isJava17: "+IJ.isJava17()); - list.add(" IJ.isJava18: "+IJ.isJava18()); + list.add(" IJ.javaVersion: "+IJ.javaVersion()); list.add(" IJ.isLinux: "+IJ.isLinux()); list.add(" IJ.isMacintosh: "+IJ.isMacintosh()); list.add(" IJ.isMacOSX: "+IJ.isMacOSX()); @@ -99,8 +98,7 @@ public class JavaProperties implements P list.add(" Prefs.useInvertingLut: "+Prefs.useInvertingLut); list.add(" Prefs.antialiasedTools: "+Prefs.antialiasedTools); list.add(" Prefs.useInvertingLut: "+Prefs.useInvertingLut); - list.add(" Prefs.intelByteOrder: "+Prefs.intelByteOrder); - list.add(" Prefs.doubleBuffer: "+Prefs.doubleBuffer); + list.add(" Prefs.intelByteOrder: "+Prefs.intelByteOrder); list.add(" Prefs.noPointLabels: "+Prefs.noPointLabels); list.add(" Prefs.disableUndo: "+Prefs.disableUndo); list.add(" Prefs dir: "+Prefs.getPrefsDir()); diff -pruN 1.51q-1/ij/plugin/JavaScriptEvaluator.java 1.52g-1/ij/plugin/JavaScriptEvaluator.java --- 1.51q-1/ij/plugin/JavaScriptEvaluator.java 2016-01-27 21:54:12.000000000 +0000 +++ 1.52g-1/ij/plugin/JavaScriptEvaluator.java 2018-06-22 21:51:38.000000000 +0000 @@ -15,11 +15,8 @@ public class JavaScriptEvaluator impleme // run script in separate thread public void run(String script) { - if (script.equals("")) return; - if (!IJ.isJava16()) { - IJ.error("Java 1.6 or later required"); + if (script.equals("")) return; - } this.script = script; thread = new Thread(this, "JavaScript"); thread.setPriority(Math.max(thread.getPriority()-2, Thread.MIN_PRIORITY)); @@ -36,6 +33,8 @@ public class JavaScriptEvaluator impleme public void run() { result = null; Thread.currentThread().setContextClassLoader(IJ.getClassLoader()); + if (IJ.isJava19()) + System.setProperty("nashorn.args", "--language=es6"); // Use ECMAScript 6 on Java 9 try { ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); ScriptEngine engine = scriptEngineManager.getEngineByName("ECMAScript"); diff -pruN 1.51q-1/ij/plugin/JpegWriter.java 1.52g-1/ij/plugin/JpegWriter.java --- 1.51q-1/ij/plugin/JpegWriter.java 2017-02-20 17:30:40.000000000 +0000 +++ 1.52g-1/ij/plugin/JpegWriter.java 2018-07-03 09:08:00.000000000 +0000 @@ -2,6 +2,7 @@ package ij.plugin; import ij.*; import ij.process.*; import ij.io.FileSaver; +import ij.io.SaveDialog; import java.awt.image.*; import java.awt.*; import java.io.*; @@ -24,9 +25,13 @@ public class JpegWriter implements PlugI /** Thread-safe method. */ public static String save(ImagePlus imp, String path, int quality) { - imp.startTiming(); + if (imp==null) + imp = IJ.getImage(); + if (path==null || path.length()==0) + path = SaveDialog.getPath(imp, ".jpg"); + if (path==null) + return null; String error = (new JpegWriter()).saveAsJpeg(imp, path, quality); - IJ.showTime(imp, imp.getStartTime(), "JpegWriter: "); return error; } @@ -35,7 +40,8 @@ public class JpegWriter implements PlugI int height = imp.getHeight(); int biType = BufferedImage.TYPE_INT_RGB; boolean overlay = imp.getOverlay()!=null && !imp.getHideOverlay(); - if (imp.getProcessor().isDefaultLut() && !imp.isComposite() && !overlay) + ImageProcessor ip = imp.getProcessor(); + if (ip.isDefaultLut() && !imp.isComposite() && !overlay && ip.getMinThreshold()==ImageProcessor.NO_THRESHOLD) biType = BufferedImage.TYPE_BYTE_GRAY; BufferedImage bi = new BufferedImage(width, height, biType); String error = null; diff -pruN 1.51q-1/ij/plugin/LutLoader.java 1.52g-1/ij/plugin/LutLoader.java --- 1.51q-1/ij/plugin/LutLoader.java 2017-04-13 20:18:34.000000000 +0000 +++ 1.52g-1/ij/plugin/LutLoader.java 2018-03-31 09:34:22.000000000 +0000 @@ -219,7 +219,6 @@ public class LutLoader extends ImagePlus i2 = i1+1; if (i2==nColors) i2 = nColors-1; fraction = i*scale - i1; - //IJ.write(i+" "+i1+" "+i2+" "+fraction); reds[i] = (byte)((1.0-fraction)*(r[i1]&255) + fraction*(r[i2]&255)); greens[i] = (byte)((1.0-fraction)*(g[i1]&255) + fraction*(g[i2]&255)); blues[i] = (byte)((1.0-fraction)*(b[i1]&255) + fraction*(b[i2]&255)); @@ -310,7 +309,6 @@ public class LutLoader extends ImagePlus long fill2 = f.readLong(); int filler = f.readInt(); } - //IJ.write(id+" "+version+" "+nColors); f.read(fi.reds, 0, nColors); f.read(fi.greens, 0, nColors); f.read(fi.blues, 0, nColors); diff -pruN 1.51q-1/ij/plugin/MacroInstaller.java 1.52g-1/ij/plugin/MacroInstaller.java --- 1.51q-1/ij/plugin/MacroInstaller.java 2017-06-26 10:28:30.000000000 +0000 +++ 1.52g-1/ij/plugin/MacroInstaller.java 2018-09-05 14:24:20.000000000 +0000 @@ -43,6 +43,11 @@ public class MacroInstaller implements P private Thread macroToolThread; private ArrayList
subMenus = new ArrayList(); + private static Program autoRunPgm; + private static int autoRunAddress; + private static String autoRunName; + private boolean autoRunOnCurrentThread; + public void run(String path) { if (path==null || path.equals("")) path = showDialog(); @@ -112,8 +117,13 @@ public class MacroInstaller implements P tools.add(name); toolCount++; } else if (name.startsWith("AutoRun")) { - if (autoRunCount==0 && !openingStartupMacrosInEditor) { - new MacroRunner(pgm, macroStarts[count], name, (String)null); + if (autoRunCount==0 && !openingStartupMacrosInEditor && !IJ.isMacro()) { + if (autoRunOnCurrentThread) { //autoRun() method will run later + autoRunPgm = pgm; + autoRunAddress = macroStarts[count]; + autoRunName = name; + } else + new MacroRunner(pgm, macroStarts[count], name, (String)null); // run on separate thread if (name.equals("AutoRunAndHide")) autoRunAndHideCount++; } @@ -392,10 +402,6 @@ public class MacroInstaller implements P return text; } - //void runMacro() { - // new MacroRunner(text); - //} - public boolean runMacroTool(String name) { for (int i=0; i100) { + IJ.error("Z spacing ("+(int)az+") is too large."); + return; + } win = imp.getWindow(); canvas = win.getCanvas(); addListeners(canvas); @@ -233,6 +237,7 @@ public class Orthogonal_Views implements double arat=az/ax; int width2 = fp1.getWidth(); int height2 = (int)Math.round(fp1.getHeight()*az); + if (height2<1) height2=1; if (width2!=fp1.getWidth()||height2!=fp1.getHeight()) { fp1.setInterpolate(true); ImageProcessor sfp1=fp1.resize(width2, height2); @@ -249,11 +254,13 @@ public class Orthogonal_Views implements updateZYView(p, is); width2 = (int)Math.round(fp2.getWidth()*az); + if (width2<1) width2=1; height2 = fp2.getHeight(); String title = "YZ "; if (rotateYZ) { width2 = fp2.getWidth(); height2 = (int)Math.round(fp2.getHeight()*az); + if (height2<1) height2=1; title = "ZY "; } if (width2!=fp2.getWidth()||height2!=fp2.getHeight()) { diff -pruN 1.51q-1/ij/plugin/OverlayCommands.java 1.52g-1/ij/plugin/OverlayCommands.java --- 1.51q-1/ij/plugin/OverlayCommands.java 2017-06-12 20:37:34.000000000 +0000 +++ 1.52g-1/ij/plugin/OverlayCommands.java 2018-06-22 21:53:02.000000000 +0000 @@ -7,7 +7,6 @@ import ij.plugin.frame.Recorder; import ij.macro.Interpreter; import ij.io.RoiDecoder; import ij.plugin.filter.PlugInFilter; -import ij.text.TextWindow; import ij.measure.ResultsTable; import java.awt.*; import java.util.ArrayList; @@ -257,10 +256,6 @@ public class OverlayCommands implements return; else if (flags==PlugInFilter.DOES_STACKS) { //Added by Marcel Boeglin 2014.01.24 - if (!IJ.isJava16()) { - IJ.error("Flatten Stack", "Java 1.6 required to flatten a stack"); - return; - } if (overlay==null && roiManagerOverlay==null && !imp.isComposite()) { IJ.error("Flatten", "Overlay or multi-channel image required"); return; @@ -360,7 +355,7 @@ public class OverlayCommands implements } public static void listRois(Roi[] rois) { - ArrayList list = new ArrayList(); + ResultsTable rt = new ResultsTable(); for (int i=0; i Press 'alt+y' (Edit>Selection>Properties plus
alt key) to display the counts in a results table.
" +"
  • Press 'm' (Analyze>Measure) to list the counter
    and stack position associated with each point.
    " +"
  • Use File>Save As>Tiff or File>Save As>Selection
    to save the points and counts.
    " + +"
  • Press 'F' (Image>Overlay>Flatten) to create an
    RGB image with embedded markers for export.
    " +"
  • Hold the shift key down and points will be
    constrained to a horizontal or vertical line.
    " +"" +"
    " diff -pruN 1.51q-1/ij/plugin/Projector.java 1.52g-1/ij/plugin/Projector.java --- 1.51q-1/ij/plugin/Projector.java 2017-02-25 19:22:24.000000000 +0000 +++ 1.52g-1/ij/plugin/Projector.java 2018-03-31 09:35:10.000000000 +0000 @@ -74,6 +74,10 @@ public class Projector implements PlugIn } if (!showDialog()) return; + if (sliceInterval>100) { + IJ.error("Z spacing ("+(int)sliceInterval+") is too large."); + return; + } imp.startTiming(); isRGB = imp.getType()==ImagePlus.COLOR_RGB; if (imp.isHyperStack()) { @@ -329,8 +333,7 @@ public class Projector implements PlugIn } if ((projwidth%2)==1) projwidth++; - int projsize = projwidth * projheight; - + int projsize = projwidth * projheight; if (projwidth<=0 || projheight<=0) { IJ.error("'projwidth' or 'projheight' <= 0"); return null; @@ -536,7 +539,6 @@ public class Projector implements PlugIn int lineIndex = j*imageWidth; for (int i=left; i=projsize) || (offset<0)) offset = 0; @@ -588,7 +590,6 @@ public class Projector implements PlugIn /** Projects each pixel of a volume (stack of slices) onto a plane as the volume rotates about the y-axis. */ private void doOneProjectionY (int nSlices, int xcenter, int zcenter, int projwidth, int projheight, int costheta, int sintheta) { - //IJ.write("DoOneProjectionY: "+xcenter+" "+zcenter+" "+(double)costheta/BIGPOWEROF2+ " "+(double)sintheta/BIGPOWEROF2); int thispixel; //current pixel to be projected int offset, offsetinit; //precomputed offsets into an image buffer int z; //z-coordinate of points in current slice before rotation @@ -634,7 +635,6 @@ public class Projector implements PlugIn thispixel =pixels[lineOffset+i]&0xff; xcostheta += costheta; //rotate about x-axis and find new y,z xsintheta += sintheta; //x-coordinates will not change - //if (k==1 && j==top) IJ.write(k+" "thispixel); if ((thispixel <= transparencyUpper) && (thispixel >= transparencyLower)) { xnew = (xcostheta + zsintheta)/BIGPOWEROF2 + xcenter - left; znew = (zcostheta - xsintheta)/BIGPOWEROF2 + zcenter; @@ -718,14 +718,6 @@ public class Projector implements PlugIn xsinthetainit = (left - xcenter - 1) * sintheta; ycosthetainit = (top - ycenter - 1) * costheta; ysinthetainit = (top - ycenter - 1) * sintheta; - //float[] f = new float[projsize]; - //IJ.write(""); - //IJ.write("depthCueSurf: "+depthCueSurf); - //IJ.write("zmax: "+zmax); - //IJ.write("zmin: "+zmin); - //IJ.write("zcenter: "+zcenter); - //IJ.write("zmaxminuszmintimes100: "+zmaxminuszmintimes100); - //IJ.write("c100minusDepthCueSurf: "+c100minusDepthCueSurf); offsetinit = ((projheight-bottom+top)/2) * projwidth + (projwidth - right + left)/2 - 1; for (int k=1; k<=nSlices; k++) { pixels = (byte[])stack.getPixels(k); diff -pruN 1.51q-1/ij/plugin/Raw.java 1.52g-1/ij/plugin/Raw.java --- 1.51q-1/ij/plugin/Raw.java 2011-04-04 11:11:40.000000000 +0000 +++ 1.52g-1/ij/plugin/Raw.java 2018-08-02 09:52:44.000000000 +0000 @@ -19,5 +19,48 @@ public class Raw implements PlugIn { ImportDialog d = new ImportDialog(fileName, directory); d.openImage(); } + + /** Opens the image at 'filePath' using the format specified by 'fi'. */ + public static ImagePlus open(String filePath, FileInfo fi) { + File f = new File(filePath); + fi.directory = f.getParent()+ "/"; + fi.fileName = f.getName(); + return (new FileOpener(fi)).open(false); + } + + + /** Opens all the images in the specified directory as a stack, + using the format specified by 'fi'. */ + public static ImagePlus openAll(String directory, FileInfo fi) { + ImagePlus imp = openAllVirtual(directory,fi); + if (imp!=null) + return imp.duplicate(); + else + return null; + } + + /** Opens all the images in the specified directory as a virtual stack, + using the format specified by 'fi'. */ + public static ImagePlus openAllVirtual(String directory, FileInfo fi) { + String[] list = new File(directory).list(); + if (list==null) + return null; + FolderOpener fo = new FolderOpener(); + list = fo.trimFileList(list); + list = fo.sortFileList(list); + if (list==null) + return null; + if (!(directory.endsWith(File.separator)||directory.endsWith("/"))) + directory += "/"; + FileInfo[] info = new FileInfo[list.length]; + for (int i=0; i0) imp2.setOverlay(overlay2); } + if (i==size) + lastImageID = imp2.getID(); imp2.show(); } imp.changes = false; diff -pruN 1.51q-1/ij/plugin/StackWriter.java 1.52g-1/ij/plugin/StackWriter.java --- 1.51q-1/ij/plugin/StackWriter.java 2015-03-31 17:51:18.000000000 +0000 +++ 1.52g-1/ij/plugin/StackWriter.java 2018-08-01 06:15:22.000000000 +0000 @@ -88,9 +88,7 @@ public class StackWriter implements Plug return; } String format = fileType.toLowerCase(Locale.US); - if (format.equals("gif") && !FileSaver.okForGif(imp)) - return; - else if (format.equals("fits") && !FileSaver.okForFits(imp)) + if (format.equals("fits") && !FileSaver.okForFits(imp)) return; if (format.equals("text")) @@ -118,8 +116,8 @@ public class StackWriter implements Plug } if (!f.isDirectory() && (exists||directory.lastIndexOf(".")>directory.length()-5)) directory = f.getParent(); - if (!directory.endsWith(File.separator)) - directory += File.separator; + if (!(directory.endsWith(File.separator)||directory.endsWith("/"))) + directory += "/"; } } if (directory==null) { diff -pruN 1.51q-1/ij/plugin/SubHyperstackMaker.java 1.52g-1/ij/plugin/SubHyperstackMaker.java --- 1.51q-1/ij/plugin/SubHyperstackMaker.java 2013-05-03 22:25:22.000000000 +0000 +++ 1.52g-1/ij/plugin/SubHyperstackMaker.java 2018-03-22 11:07:18.000000000 +0000 @@ -15,9 +15,9 @@ import java.awt.Color; */ public class SubHyperstackMaker implements PlugIn { - public void run(final String arg) { + public void run(String arg) { // verify input image is appropriate - final ImagePlus input = WindowManager.getCurrentImage(); + ImagePlus input = WindowManager.getCurrentImage(); if (input == null) { IJ.showMessage("No image open."); return; @@ -26,15 +26,15 @@ public class SubHyperstackMaker implemen IJ.showMessage("Image is not a stack."); return; } - final int cCount = input.getNChannels(); - final int zCount = input.getNSlices(); - final int tCount = input.getNFrames(); - final boolean hasC = cCount > 1; - final boolean hasZ = zCount > 1; - final boolean hasT = tCount > 1; + int cCount = input.getNChannels(); + int zCount = input.getNSlices(); + int tCount = input.getNFrames(); + boolean hasC = cCount > 1; + boolean hasZ = zCount > 1; + boolean hasT = tCount > 1; // prompt for C, Z and T ranges - final GenericDialog gd = new GenericDialog("Subhyperstack Maker"); + GenericDialog gd = new GenericDialog("Subhyperstack Maker"); gd.addMessage("Enter a range (e.g. 2-14), a range with increment\n" + "(e.g. 1-100-2) or a list (e.g. 7,9,25,27)", null, Color.darkGray); if (hasC) gd.addStringField("Channels:", "1-" + cCount, 40); @@ -42,160 +42,138 @@ public class SubHyperstackMaker implemen if (hasT) gd.addStringField("Frames:", "1-" + tCount, 40); gd.showDialog(); if (gd.wasCanceled()) return; - final String cString = hasC ? gd.getNextString() : "1"; - final String zString = hasZ ? gd.getNextString() : "1"; - final String tString = hasT ? gd.getNextString() : "1"; + String cString = hasC ? gd.getNextString() : "1"; + String zString = hasZ ? gd.getNextString() : "1"; + String tString = hasT ? gd.getNextString() : "1"; // compute subhyperstack - final ImagePlus output = - makeSubhyperstack(input, cString, zString, tString); + ImagePlus output = makeSubhyperstack(input, cString, zString, tString); // display result output.show(); } - public static ImagePlus makeSubhyperstack(final ImagePlus input, - final String cString, final String zString, final String tString) - { - final ArrayList cList = parseList(cString, input.getNChannels()); - final ArrayList zList = parseList(zString, input.getNSlices()); - final ArrayList tList = parseList(tString, input.getNFrames()); + public static ImagePlus makeSubhyperstack(ImagePlus input, String cString, String zString, String tString) { + ArrayList cList = parseList(cString, input.getNChannels()); + ArrayList zList = parseList(zString, input.getNSlices()); + ArrayList tList = parseList(tString, input.getNFrames()); return makeSubhyperstack(input, cList, zList, tList); } - public static ImagePlus makeSubhyperstack(final ImagePlus input, - final List cList, final List zList, - final List tList) - { + public static ImagePlus makeSubhyperstack(ImagePlus input, List cList, List zList, List tList) { // validate inputs - if (cList.size() == 0) { + if (cList.size() == 0) throw new IllegalArgumentException("Must specify at least one channel"); - } - if (zList.size() == 0) { + if (zList.size() == 0) throw new IllegalArgumentException("Must specify at least one slice"); - } - if (tList.size() == 0) { + if (tList.size() == 0) throw new IllegalArgumentException("Must specify at least one frame"); - } - final ImageStack inputStack = input.getImageStack(); + ImageStack inputStack = input.getImageStack(); - final int cCount = input.getNChannels(); - final int zCount = input.getNSlices(); - final int tCount = input.getNFrames(); + int cCount = input.getNChannels(); + int zCount = input.getNSlices(); + int tCount = input.getNFrames(); - for (final int c : cList) + for (int c : cList) check("C", c, cCount); - for (final int z : zList) + for (int z : zList) check("Z", z, zCount); - for (final int t : tList) + for (int t : tList) check("T", t, tCount); // create output image - final String title = WindowManager.getUniqueName(input.getTitle()); - ImagePlus output = - input.createHyperStack(title, cList.size(), zList.size(), tList.size(), - input.getBitDepth()); - final ImageStack outputStack = output.getImageStack(); + String title = WindowManager.getUniqueName(input.getTitle()); + ImagePlus output = input.createHyperStack(title, cList.size(), zList.size(), tList.size(), input.getBitDepth()); + ImageStack outputStack = output.getImageStack(); // add specified planes to subhyperstack int oc = 0, oz, ot; - for (final int c : cList) { + for (int c : cList) { oc++; oz = 0; - for (final int z : zList) { + for (int z : zList) { oz++; ot = 0; - for (final int t : tList) { + for (int t : tList) { ot++; - final int i = input.getStackIndex(c, z, t); - final int oi = output.getStackIndex(oc, oz, ot); - final String label = inputStack.getSliceLabel(i); - final ImageProcessor ip = inputStack.getProcessor(i); + int i = input.getStackIndex(c, z, t); + int oi = output.getStackIndex(oc, oz, ot); + String label = inputStack.getSliceLabel(i); + ImageProcessor ip = inputStack.getProcessor(i); outputStack.setSliceLabel(label, oi); outputStack.setPixels(ip.getPixels(), oi); + //IJ.log(" "+c + " "+z+" "+t+" "+i +" "+oi+" "+outputStack.getProcessor(1).getPixelValue(0,0)); } } } + output.setStack(outputStack); // propagate composite image settings, if appropriate if (input instanceof CompositeImage) { - final CompositeImage compositeInput = (CompositeImage) input; - final CompositeImage compositeOutput = + CompositeImage compositeInput = (CompositeImage) input; + CompositeImage compositeOutput = new CompositeImage(output, compositeInput.getMode()); oc = 0; - for (final int c : cList) { + for (int c : cList) { oc++; - final LUT table = compositeInput.getChannelLut(c); + LUT table = compositeInput.getChannelLut(c); compositeOutput.setChannelLut(table, oc); compositeOutput.setPositionWithoutUpdate(oc, 1, 1); compositeInput.setPositionWithoutUpdate(c, 1, 1); - final double min = compositeInput.getDisplayRangeMin(); - final double max = compositeInput.getDisplayRangeMax(); + double min = compositeInput.getDisplayRangeMin(); + double max = compositeInput.getDisplayRangeMax(); compositeOutput.setDisplayRange(min, max); } output = compositeOutput; } - - // return result return output; } - private static void - check(final String name, final int index, final int count) - { + private static void check(String name, int index, int count) { if (index < 1 || index > count) { throw new IllegalArgumentException("Invalid " + name + " index: " + index); } } - private static ArrayList parseList(final String planeString, - int count) - { - final ArrayList list = new ArrayList(); - for (final String token : planeString.split("\\s*,\\s*")) { - final int dash1 = token.indexOf("-"); - final int dash2 = token.lastIndexOf("-"); + private static ArrayList parseList(String planeString, int count) { + ArrayList list = new ArrayList(); + for (String token : planeString.split("\\s*,\\s*")) { + int dash1 = token.indexOf("-"); + int dash2 = token.lastIndexOf("-"); if (dash1 < 0) { // single number - final int index; + int index; try { index = Integer.parseInt(token); - } - catch (final NumberFormatException exc) { + } catch (NumberFormatException exc) { throw new IllegalArgumentException("Invalid number: " + token); } - if (index < 1 || index > count) { + if (index < 1 || index > count) throw new IllegalArgumentException("Invalid number: " + token); - } list.add(Integer.parseInt(token)); - } - else { + } else { // range, with or without increment - final int min, max, step; + int min, max, step; try { min = Integer.parseInt(token.substring(0, dash1)); if (dash1 == dash2) { // range (e.g. 2-14) max = Integer.parseInt(token.substring(dash1 + 1)); step = 1; - } - else { + } else { // range with increment (e.g. 1-100-2) max = Integer.parseInt(token.substring(dash1 + 1, dash2)); step = Integer.parseInt(token.substring(dash2 + 1)); } - } - catch (final NumberFormatException exc) { + } catch (NumberFormatException exc) { throw new IllegalArgumentException("Invalid range: " + token); } - if (min < 1 || min > max || max > count || step < 1) { + if (min < 1 || min > max || max > count || step < 1) throw new IllegalArgumentException("Invalid range: " + token); - } - for (int index = min; index <= max; index += step) { + for (int index = min; index <= max; index += step) list.add(index); - } } } return list; diff -pruN 1.51q-1/ij/plugin/Thresholder.java 1.52g-1/ij/plugin/Thresholder.java --- 1.51q-1/ij/plugin/Thresholder.java 2017-05-16 21:53:02.000000000 +0000 +++ 1.52g-1/ij/plugin/Thresholder.java 2018-08-12 07:27:36.000000000 +0000 @@ -381,6 +381,21 @@ public class Thresholder implements Plug imp.setCalibration(imp.getCalibration()); //update calibration } + /** Returns an 8-bit binary (0 and 255) threshold mask + * that has the same dimensions as this image. + * @see ij.process.ImageProcessor#createMask + * @see ij.ImagePlus#createThresholdMask + * @see ij.ImagePlus#createRoiMask + */ + public static ByteProcessor createMask(ImagePlus imp) { + ImageProcessor ip = imp.getProcessor(); + if (ip instanceof ColorProcessor) + throw new IllegalArgumentException("Non-RGB image requires"); + if (ip.getMinThreshold()==ImageProcessor.NO_THRESHOLD) + throw new IllegalArgumentException("Image must be thresholded"); + return ip.createMask(); + } + void autoThreshold(ImageProcessor ip) { ip.setAutoThreshold(ImageProcessor.ISODATA2, ImageProcessor.NO_LUT_UPDATE); minThreshold = ip.getMinThreshold(); diff -pruN 1.51q-1/ij/plugin/Timer.java 1.52g-1/ij/plugin/Timer.java --- 1.51q-1/ij/plugin/Timer.java 2011-04-04 11:11:40.000000000 +0000 +++ 1.52g-1/ij/plugin/Timer.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,204 +0,0 @@ -package ij.plugin; -import java.awt.*; -import ij.*; - -/**ImageJ plugin for measuring the speed of various Java operations.*/ -public class Timer implements PlugIn { - int j=0; - long startTime, nullLoopTime; - int numLoops; - - - public void run(String arg) { - int j=0, k; - int[] a = new int[10]; - long endTime; - - /* - startTime = System.currentTimeMillis(); - //for (int i=0; i<100; i++) IJ.wait(10); - for (int i=0; i<100; i++) Thread.yield(); - long elapsedTime = System.currentTimeMillis() - startTime; - IJ.write(elapsedTime + " ms"); - */ - - numLoops = 10000; - do { - numLoops = (int)(numLoops*1.33); - startTime = System.currentTimeMillis(); - for (int i=0; i1 && fi!=null && fi.description!=null && fi.description.indexOf("mode=")!=-1) { + int mode = IJ.COLOR; + if (fi.description.indexOf("mode=composite")!=-1) + mode = IJ.COMPOSITE; + else if (fi.description.indexOf("mode=gray")!=-1) + mode = IJ.GRAYSCALE; + imp = new CompositeImage(imp, mode); + } + if (fi!=null && (fi.url==null || fi.url.length()==0)) { + fi.url = url; + imp.setFileInfo(fi); + } imp.show(Opener.getLoadRate(startTime,imp)); } IJ.register(URLOpener.class); // keeps this class from being GC'd diff -pruN 1.51q-1/ij/plugin/WindowOrganizer.java 1.52g-1/ij/plugin/WindowOrganizer.java --- 1.51q-1/ij/plugin/WindowOrganizer.java 2016-03-04 14:29:48.000000000 +0000 +++ 1.52g-1/ij/plugin/WindowOrganizer.java 2018-08-04 18:59:22.000000000 +0000 @@ -8,7 +8,7 @@ import java.awt.*; /** This class implements the Window menu's "Show All", "Main Window", "Cascade" and "Tile" commands. */ public class WindowOrganizer implements PlugIn { - private static final int XSTART=4, YSTART=80, XOFFSET=8, YOFFSET=24,MAXSTEP=200,GAP=2; + private static final int XSTART=4, YSTART=94, XOFFSET=8, YOFFSET=24,MAXSTEP=200,GAP=2; private int titlebarHeight = IJ.isMacintosh()?40:20; public void run(String arg) { @@ -64,7 +64,6 @@ public class WindowOrganizer implements double averageHeight = totalHeight/nPics; int tileWidth = (int)averageWidth; int tileHeight = (int)averageHeight; - //IJ.write("tileWidth, tileHeight: "+tileWidth+" "+tileHeight); int hspace = screen.width - 2 * GAP; if (tileWidth>hspace) tileWidth = hspace; @@ -108,7 +107,6 @@ public class WindowOrganizer implements ImageWindow win = getWindow(wList[i]); if (win!=null) { win.setLocation(hloc, vloc); - //IJ.write(i+" "+w+" "+tileWidth+" "+mag+" "+IJ.d2s(zoomFactor,2)+" "+zoomCount); ImageCanvas canvas = win.getCanvas(); while (win.getSize().width*0.85>=tileWidth && canvas.getMagnification()>0.03125) canvas.zoomOut(0, 0); diff -pruN 1.51q-1/ij/plugin/ZAxisProfiler.java 1.52g-1/ij/plugin/ZAxisProfiler.java --- 1.51q-1/ij/plugin/ZAxisProfiler.java 2016-12-14 12:52:34.000000000 +0000 +++ 1.52g-1/ij/plugin/ZAxisProfiler.java 2017-12-01 18:02:02.000000000 +0000 @@ -169,7 +169,8 @@ public class ZAxisProfiler implements Pl size = slices; float[] values = new float[size]; Calibration cal = imp.getCalibration(); - Analyzer analyzer = new Analyzer(imp); + ResultsTable rt = new ResultsTable(); + Analyzer analyzer = new Analyzer(imp, rt); int measurements = Analyzer.getMeasurements(); boolean showResults = !isPlotMaker && measurements!=0 && measurements!=LIMIT; measurements |= MEAN; @@ -195,10 +196,8 @@ public class ZAxisProfiler implements Pl analyzer.saveResults(stats, roi); values[i-1] = (float)stats.mean; } - if (showResults) { - ResultsTable rt = Analyzer.getResultsTable(); + if (showResults) rt.show("Results"); - } return values; } @@ -213,7 +212,8 @@ public class ZAxisProfiler implements Pl boolean showProgress = size>400 || stack.isVirtual(); float[] values = new float[size]; Calibration cal = imp.getCalibration(); - Analyzer analyzer = new Analyzer(imp); + ResultsTable rt = new ResultsTable(); + Analyzer analyzer = new Analyzer(imp, rt); int measurements = Analyzer.getMeasurements(); boolean showResults = !isPlotMaker && measurements!=0 && measurements!=LIMIT; boolean showingLabels = firstTime && showResults && ((measurements&LABELS)!=0 || (measurements&SLICE)!=0); @@ -241,10 +241,8 @@ public class ZAxisProfiler implements Pl analyzer.saveResults(stats, roi); values[i-1] = (float)stats.mean; } - if (showResults) { - ResultsTable rt = Analyzer.getResultsTable(); + if (showResults) rt.show("Results"); - } if (showingLabels) imp.setSlice(current); return values; diff -pruN 1.51q-1/ij/Prefs.java 1.52g-1/ij/Prefs.java --- 1.51q-1/ij/Prefs.java 2017-07-02 16:53:22.000000000 +0000 +++ 1.52g-1/ij/Prefs.java 2018-08-12 14:12:48.000000000 +0000 @@ -55,7 +55,8 @@ public class Prefs { private static final int USE_SYSTEM_PROXIES=1<<0, USE_FILE_CHOOSER=1<<1, SUBPIXEL_RESOLUTION=1<<2, ENHANCED_LINE_TOOL=1<<3, SKIP_RAW_DIALOG=1<<4, REVERSE_NEXT_PREVIOUS_ORDER=1<<5, AUTO_RUN_EXAMPLES=1<<6, SHOW_ALL_POINTS=1<<7, - DO_NOT_SAVE_WINDOW_LOCS=1<<8, JFILE_CHOOSER_CHANGED=1<<9; + DO_NOT_SAVE_WINDOW_LOCS=1<<8, JFILE_CHOOSER_CHANGED=1<<9, + CANCEL_BUTTON_ON_RIGHT=1<<10; public static final String OPTIONS2 = "prefs.options2"; /** file.separator system property */ @@ -88,7 +89,7 @@ public class Prefs { public static boolean antialiasedTools = true; /** Export TIFF and Raw using little-endian byte order. */ public static boolean intelByteOrder; - /** Double buffer display of selections and overlays. */ + /** No longer used */ public static boolean doubleBuffer = true; /** Do not label multiple points created using point tool. */ public static boolean noPointLabels; @@ -142,13 +143,13 @@ public class Prefs { public static boolean useFileChooser; /** Use sub-pixel resolution with line selections */ public static boolean subPixelResolution; - /** Adjust contrast when scrolling stacks (or hold shift key down) */ + /** Adjust contrast when scrolling stacks */ public static boolean autoContrast; /** Allow lines to be created with one click at start and another at the end */ public static boolean enhancedLineTool; /** Keep arrow selection after adding to overlay */ public static boolean keepArrowSelections; - /** Aways paint using double buffering, except on OS X */ + /** Aways paint images using double buffering */ public static boolean paintDoubleBuffered; /** Do not display dialog when opening .raw files */ public static boolean skipRawDialog; @@ -173,8 +174,12 @@ public class Prefs { public static boolean convertToMicrons = true; /** Wand tool "Smooth if thresholded" option */ public static boolean smoothWand; + /** "Close All" command running */ + public static boolean closingAll; + /** Dialog "Cancel" button is on right on Linux */ + public static boolean dialogCancelButtonOnRight; - + static boolean commandLineMacro; static Properties ijPrefs = new Properties(); static Properties props = new Properties(ijPrefs); static String prefsDir; @@ -182,7 +187,6 @@ public class Prefs { static String homeDir; // ImageJ folder static int threads; static int transparentIndex = -1; - static boolean commandLineMacro; private static boolean resetPreferences; /** Finds and loads the ImageJ configuration file, "IJ_Props.txt". @@ -325,7 +329,7 @@ public class Prefs { if (s!=null) { try { return Integer.decode(s).intValue(); - } catch (NumberFormatException e) {IJ.write(""+e);} + } catch (NumberFormatException e) {IJ.log(""+e);} } return defaultValue; } @@ -403,6 +407,7 @@ public class Prefs { prefs.put(NOISE_SD, Double.toString(Filters.getSD())); if (threads>1) prefs.put(THREADS, Integer.toString(threads)); if (IJ.isMacOSX()) useJFileChooser = false; + if (!IJ.isLinux()) dialogCancelButtonOnRight = false; saveOptions(prefs); savePluginPrefs(prefs); IJ.getInstance().savePreferences(prefs); @@ -444,7 +449,7 @@ public class Prefs { static void loadOptions() { int defaultOptions = ANTIALIASING+AVOID_RESLICE_INTERPOLATION+ANTIALIASED_TOOLS+MULTI_POINT_MODE - +(!IJ.isMacOSX()?RUN_SOCKET_LISTENER:0); + +(!IJ.isMacOSX()?RUN_SOCKET_LISTENER:0)+BLACK_BACKGROUND; int options = getInt(OPTIONS, defaultOptions); usePointerCursor = (options&USE_POINTER)!=0; //antialiasedText = (options&ANTIALIASING)!=0; @@ -470,8 +475,8 @@ public class Prefs { multiPointMode = (options&MULTI_POINT_MODE)!=0; rotateYZ = (options&ROTATE_YZ)!=0; flipXZ = (options&FLIP_XZ)!=0; - dontSaveHeaders = (options&DONT_SAVE_HEADERS)!=0; - dontSaveRowNumbers = (options&DONT_SAVE_ROW_NUMBERS)!=0; + //dontSaveHeaders = (options&DONT_SAVE_HEADERS)!=0; + //dontSaveRowNumbers = (options&DONT_SAVE_ROW_NUMBERS)!=0; noClickToGC = (options&NO_CLICK_TO_GC)!=0; avoidResliceInterpolation = (options&AVOID_RESLICE_INTERPOLATION)!=0; keepUndoBuffers = (options&KEEP_UNDO_BUFFERS)!=0; @@ -488,6 +493,7 @@ public class Prefs { showAllPoints = (options2&SHOW_ALL_POINTS)!=0; doNotSaveWindowLocations = (options2&DO_NOT_SAVE_WINDOW_LOCS)!=0; jFileChooserSettingChanged = (options2&JFILE_CHOOSER_CHANGED)!=0; + dialogCancelButtonOnRight = (options2&CANCEL_BUTTON_ON_RIGHT)!=0; } static void saveOptions(Properties prefs) { @@ -515,7 +521,8 @@ public class Prefs { + (reverseNextPreviousOrder?REVERSE_NEXT_PREVIOUS_ORDER:0) + (autoRunExamples?AUTO_RUN_EXAMPLES:0) + (showAllPoints?SHOW_ALL_POINTS:0) + (doNotSaveWindowLocations?DO_NOT_SAVE_WINDOW_LOCS:0) - + (jFileChooserSettingChanged?JFILE_CHOOSER_CHANGED:0); + + (jFileChooserSettingChanged?JFILE_CHOOSER_CHANGED:0) + + (dialogCancelButtonOnRight?CANCEL_BUTTON_ON_RIGHT:0); prefs.put(OPTIONS2, Integer.toString(options2)); } diff -pruN 1.51q-1/ij/process/ByteBlitter.java 1.52g-1/ij/process/ByteBlitter.java --- 1.51q-1/ij/process/ByteBlitter.java 2013-05-17 15:05:02.000000000 +0000 +++ 1.52g-1/ij/process/ByteBlitter.java 2018-03-31 09:39:28.000000000 +0000 @@ -19,7 +19,6 @@ public class ByteBlitter implements Blit public void setTransparentColor(Color c) { transparent = ip.getBestIndex(c); - //ij.IJ.write(c+" "+transparent); } /** Copies the byte image in 'ip' to (x,y) using the specified mode. */ diff -pruN 1.51q-1/ij/process/ByteProcessor.java 1.52g-1/ij/process/ByteProcessor.java --- 1.51q-1/ij/process/ByteProcessor.java 2017-05-15 15:15:52.000000000 +0000 +++ 1.52g-1/ij/process/ByteProcessor.java 2018-08-06 18:42:32.000000000 +0000 @@ -35,10 +35,8 @@ public class ByteProcessor extends Image cm = pg.getColorModel(); if (cm instanceof IndexColorModel) pixels = (byte[])(pg.getPixels()); - else - System.err.println("ByteProcessor: not 8-bit image"); - if (((IndexColorModel)cm).getTransparentPixel()!=-1) { - IndexColorModel icm = (IndexColorModel)cm; + if ((cm instanceof IndexColorModel) && ((IndexColorModel)cm).getTransparentPixel()!=-1) { + IndexColorModel icm = (IndexColorModel)cm; int mapSize = icm.getMapSize(); byte[] reds = new byte[mapSize]; byte[] greens = new byte[mapSize]; @@ -104,19 +102,9 @@ public class ByteProcessor extends Image } public Image createImage() { - if (cm==null) cm = getDefaultColorModel(); - if (ij.IJ.isJava16()) return createBufferedImage(); - if (source==null) { - source = new MemoryImageSource(width, height, cm, pixels, 0, width); - source.setAnimated(true); - source.setFullBufferUpdates(true); - img = Toolkit.getDefaultToolkit().createImage(source); - } else if (newPixels) { - source.newPixels(pixels, cm, 0, width); - newPixels = false; - } else - source.newPixels(); - return img; + if (cm==null) + cm = getDefaultColorModel(); + return createBufferedImage(); } Image createBufferedImage() { @@ -259,14 +247,37 @@ public class ByteProcessor extends Image return 0; } - public final int get(int x, int y) {return pixels[y*width+x]&0xff;} - public final void set(int x, int y, int value) {pixels[y*width+x] = (byte)value;} - public final int get(int index) {return pixels[index]&0xff;} - public final void set(int index, int value) {pixels[index] = (byte)value;} - public final float getf(int x, int y) {return pixels[y*width+x]&0xff;} - public final void setf(int x, int y, float value) {pixels[y*width+x] = (byte)(value+0.5f);} - public final float getf(int index) {return pixels[index]&0xff;} - public final void setf(int index, float value) {pixels[index] = (byte)value;} + public final int get(int x, int y) { + return pixels[y*width+x]&0xff; + } + + public final void set(int x, int y, int value) { + pixels[y*width+x] = (byte)value; + } + + public final int get(int index) { + return pixels[index]&0xff; + } + + public final void set(int index, int value) { + pixels[index] = (byte)value; + } + + public final float getf(int x, int y) { + return pixels[y*width+x]&0xff; + } + + public final void setf(int x, int y, float value) { + pixels[y*width+x] = (byte)(value+0.5f); + } + + public final float getf(int index) { + return pixels[index]&0xff; + } + + public final void setf(int index, float value) { + pixels[index] = (byte)value; + } static double oldx, oldy; @@ -448,8 +459,6 @@ public class ByteProcessor extends Image } } cm = new IndexColorModel(8, 256, rLUT2, gLUT2, bLUT2); - newPixels = true; - if (min==0.0 && max==255.0) source = null; minThreshold = NO_THRESHOLD; } @@ -469,8 +478,15 @@ public class ByteProcessor extends Image /** Copies the image contained in 'ip' to (xloc, yloc) using one of the transfer modes defined in the Blitter interface. */ public void copyBits(ImageProcessor ip, int xloc, int yloc, int mode) { - ip = ip.convertToByte(true); - new ByteBlitter(this).copyBits(ip, xloc, yloc, mode); + boolean temporaryFloat = ip.getBitDepth()==32 && (mode==Blitter.MULTIPLY || mode==Blitter.DIVIDE); + if (temporaryFloat) { + FloatProcessor ipFloat = this.convertToFloatProcessor(); + new FloatBlitter(ipFloat).copyBits(ip, xloc, yloc, mode); + setPixels(1, ipFloat); + } else { + ip = ip.convertToByte(true); + new ByteBlitter(this).copyBits(ip, xloc, yloc, mode); + } } /* Filters start here */ @@ -832,10 +848,15 @@ public class ByteProcessor extends Image filter(MEDIAN_FILTER); } + /** Adds pseudorandom, Gaussian ("normally") distributed values, with mean 0.0 and the specified standard deviation, to this image or ROI. */ - public void noise(double standardDeviation) { - Random rnd=new Random(); + public void noise(double standardDeviation) { + if (rnd==null) + rnd = new Random(); + if (!Double.isNaN(seed)) + rnd.setSeed((int)seed); + seed = Double.NaN; int v, ran; boolean inRange; for (int y=roiY; y<(roiY+roiHeight); y++) { @@ -852,6 +873,7 @@ public class ByteProcessor extends Image } } } + /** Scales the image or selection using the specified scale factors. @see ImageProcessor#setInterpolate @@ -1240,7 +1262,22 @@ public class ByteProcessor extends Image public int getBitDepth() { return 8; } - + + /** Returns a binary mask, or null if a threshold is not set. */ + public ByteProcessor createMask() { + if (getMinThreshold()==NO_THRESHOLD) + return null; + int minThreshold = (int)getMinThreshold(); + int maxThreshold = (int)getMaxThreshold(); + ByteProcessor mask = new ByteProcessor(width, height); + byte[] mpixels = (byte[])mask.getPixels(); + for (int i=0; i=minThreshold && value<=maxThreshold) + mpixels[i] = (byte)255; + } + return mask; + } byte[] create8BitImage() { return pixels; diff -pruN 1.51q-1/ij/process/ColorProcessor.java 1.52g-1/ij/process/ColorProcessor.java --- 1.51q-1/ij/process/ColorProcessor.java 2017-05-29 16:36:56.000000000 +0000 +++ 1.52g-1/ij/process/ColorProcessor.java 2018-06-24 15:38:32.000000000 +0000 @@ -8,7 +8,7 @@ import ij.ImageStack; /** This is an 32-bit RGB image and methods that operate on that image.. Based on the ImageProcessor class from -"KickAss Java Programming" by Tonny Espeset (http://www.sn.no/~espeset). +"KickAss Java Programming" by Tonny Espeset (1996). */ public class ColorProcessor extends ImageProcessor { @@ -62,19 +62,7 @@ public class ColorProcessor extends Imag } public Image createImage() { - if (ij.IJ.isJava16()) - return createBufferedImage(); - if (source==null) { - source = new MemoryImageSource(width, height, cm, pixels, 0, width); - source.setAnimated(true); - source.setFullBufferUpdates(true); - img = Toolkit.getDefaultToolkit().createImage(source); - } else if (newPixels) { - source.newPixels(pixels, cm, 0, width); - newPixels = false; - } else - source.newPixels(); - return img; + return createBufferedImage(); } Image createBufferedImage() { @@ -101,8 +89,6 @@ public class ColorProcessor extends Imag if (cm!=null && (cm instanceof IndexColorModel)) throw new IllegalArgumentException("DirectColorModel required"); this.cm = cm; - newPixels = true; - source = null; rgbSampleModel = null; rgbRaster = null; } @@ -769,9 +755,9 @@ public class ColorProcessor extends Imag return null; } - public void noise(double range) { - filterRGB(RGB_NOISE, range); - } + public void noise(double range) { + filterRGB(RGB_NOISE, range); + } public void medianFilter() { filterRGB(RGB_MEDIAN, 0.0); diff -pruN 1.51q-1/ij/process/EllipseFitter.java 1.52g-1/ij/process/EllipseFitter.java --- 1.51q-1/ij/process/EllipseFitter.java 2011-04-12 17:01:22.000000000 +0000 +++ 1.52g-1/ij/process/EllipseFitter.java 2018-03-31 09:40:12.000000000 +0000 @@ -56,7 +56,7 @@ public class Ellipse_Fitter implements P public void run(ImageProcessor ip) { EllipseFitter ef = new EllipseFitter(); ef.fit(ip); - IJ.write(IJ.d2s(ef.major)+" "+IJ.d2s(ef.minor)+" "+IJ.d2s(ef.angle)+" "+IJ.d2s(ef.xCenter)+" "+IJ.d2s(ef.yCenter)); + IJ.log(IJ.d2s(ef.major)+" "+IJ.d2s(ef.minor)+" "+IJ.d2s(ef.angle)+" "+IJ.d2s(ef.xCenter)+" "+IJ.d2s(ef.yCenter)); ef.drawEllipse(ip); } } diff -pruN 1.51q-1/ij/process/FHT.java 1.52g-1/ij/process/FHT.java --- 1.51q-1/ij/process/FHT.java 2017-08-30 19:01:04.000000000 +0000 +++ 1.52g-1/ij/process/FHT.java 2018-03-31 09:40:32.000000000 +0000 @@ -227,7 +227,6 @@ public class FHT extends FloatProcessor /** Performs a 2D FHT (Fast Hartley Transform). */ public void rc2DFHT(float[] x, boolean inverse, int maxN) { - //IJ.write("FFT: rc2DFHT (row-column Fast Hartley Transform)"); if (S==null) initializeTables(maxN); for (int row=0; row=minThreshold && value<=maxThreshold) + pixels8[i] = (byte)255; + else + pixels8[i] = (byte)0; + } + } else { // threshold red + for (int i=0; i=minThreshold && value<=maxThreshold) + pixels8[i] = (byte)255; + } + } + } + return createBufferedImage(); } - - // scale from float to 8-bits - protected byte[] create8BitImage() { + + // creates 8-bit image by linearly scaling from float to 8-bits + private byte[] create8BitImage(boolean thresholding) { int size = width*height; if (pixels8==null) pixels8 = new byte[size]; - float value; + double value; int ivalue; - float min2=(float)getMin(), max2=(float)getMax(); - float scale = 255f/(max2-min2); + double min2 = getMin(), max2=getMax(); + int maxValue = 255; + double scale = 256.0/(max2-min2); + if (thresholding) { + maxValue = 254; + scale = 255.0/(max2-min2); + } for (int i=0; i255) ivalue = 255; + if (ivalue>maxValue) ivalue = maxValue; pixels8[i] = (byte)ivalue; } + //if (ij.IJ.debugMode) new ij.ImagePlus("pixels8",new ByteProcessor(width,height,pixels8).duplicate()).show(); return pixels8; } + + @Override + byte[] create8BitImage() { + return create8BitImage(false); + } Image createBufferedImage() { if (raster==null) { @@ -707,7 +726,11 @@ public class FloatProcessor extends Imag } public void noise(double standardDeviation) { - Random rnd=new Random(); + if (rnd==null) + rnd = new Random(); + if (!Double.isNaN(seed)) + rnd.setSeed((int) seed); + seed = Double.NaN; for (int y=roiY; y<(roiY+roiHeight); y++) { int i = y * width + roiX; for (int x=roiX; x<(roiX+roiWidth); x++) { @@ -1007,13 +1030,36 @@ public class FloatProcessor extends Imag return 0.0; } + public void setLutAnimation(boolean lutAnimation) { + this.lutAnimation = false; + } + public void setThreshold(double minThreshold, double maxThreshold, int lutUpdate) { - if (minThreshold==NO_THRESHOLD) - {resetThreshold(); return;} + if (minThreshold==NO_THRESHOLD) { + resetThreshold(); + return; + } if (getMax()>getMin()) { - double minT = Math.round(((minThreshold-getMin())/(getMax()-getMin()))*255.0); - double maxT = Math.round(((maxThreshold-getMin())/(getMax()-getMin()))*255.0); - super.setThreshold(minT, maxT, lutUpdate); // update LUT + if (lutUpdate==OVER_UNDER_LUT) { + double minT = ((minThreshold-getMin())/(getMax()-getMin())*255.0); + double maxT = ((maxThreshold-getMin())/(getMax()-getMin())*255.0); + super.setThreshold(minT, maxT, lutUpdate); // update LUT + } else { + lutUpdateMode = lutUpdate; + if (rLUT1==null) { + if (cm==null) + makeDefaultColorModel(); + baseCM = cm; + IndexColorModel m = (IndexColorModel)cm; + rLUT1 = new byte[256]; gLUT1 = new byte[256]; bLUT1 = new byte[256]; + m.getReds(rLUT1); m.getGreens(gLUT1); m.getBlues(bLUT1); + rLUT2 = new byte[256]; gLUT2 = new byte[256]; bLUT2 = new byte[256]; + } + if (lutUpdateMode==RED_LUT) + cm = getThresholdColorModel(); + else + cm = getDefaultColorModel(); + } } else super.resetThreshold(); this.minThreshold = minThreshold; @@ -1075,6 +1121,21 @@ public class FloatProcessor extends Imag public int getBitDepth() { return 32; } + + /** Returns a binary mask, or null if a threshold is not set. */ + public ByteProcessor createMask() { + if (getMinThreshold()==NO_THRESHOLD) + return null; + float minThreshold = (float)getMinThreshold(); + float maxThreshold = (float)getMaxThreshold(); + ByteProcessor mask = new ByteProcessor(width, height); + byte[] mpixels = (byte[])mask.getPixels(); + for (int i=0; i=minThreshold && pixels[i]<=maxThreshold) + mpixels[i] = (byte)255; + } + return mask; + } } diff -pruN 1.51q-1/ij/process/ImageConverter.java 1.52g-1/ij/process/ImageConverter.java --- 1.51q-1/ij/process/ImageConverter.java 2017-02-24 12:15:10.000000000 +0000 +++ 1.52g-1/ij/process/ImageConverter.java 2018-06-30 18:06:52.000000000 +0000 @@ -126,9 +126,7 @@ public class ImageConverter { /** Converts an RGB image to a HSB (hue, saturation and brightness) stack. */ public void convertToHSB() { if (type!=ImagePlus.COLOR_RGB) - throw new IllegalArgumentException("Image must be RGB"); - //convert to hue, saturation and brightness - //IJ.showProgress(0.1); + throw new IllegalArgumentException("Image must be RGB");; ColorProcessor cp; if (imp.getType()==ImagePlus.COLOR_RGB) cp = (ColorProcessor)imp.getProcessor(); @@ -138,7 +136,6 @@ public class ImageConverter { imp.trimProcessor(); imp.setStack(null, stack); imp.setDimensions(3, 1, 1); - //IJ.showProgress(1.0); } /** Converts an RGB image to a Lab stack. */ diff -pruN 1.51q-1/ij/process/ImageProcessor.java 1.52g-1/ij/process/ImageProcessor.java --- 1.51q-1/ij/process/ImageProcessor.java 2017-09-21 21:13:56.000000000 +0000 +++ 1.52g-1/ij/process/ImageProcessor.java 2018-08-12 13:34:42.000000000 +0000 @@ -68,6 +68,7 @@ public abstract class ImageProcessor imp private static boolean useBicubic; private int sliceNumber; private Overlay overlay; + private boolean noReset; ProgressBar progressBar; protected int width, snapshotWidth; @@ -87,9 +88,9 @@ public abstract class ImageProcessor imp protected double histogramMin, histogramMax; protected float[] cTable; protected boolean lutAnimation; - protected MemoryImageSource source; + protected MemoryImageSource source; //unused protected Image img; - protected boolean newPixels; + protected boolean newPixels; // unused protected Color drawingColor = Color.black; protected int clipXMin, clipXMax, clipYMin, clipYMax; // clip rect used by drawTo, drawLine, drawDot and drawPixel protected int justification = LEFT_JUSTIFY; @@ -102,6 +103,8 @@ public abstract class ImageProcessor imp protected SampleModel sampleModel; protected static IndexColorModel defaultColorModel; protected boolean minMaxSet; + protected static double seed = Double.NaN; + protected static Random rnd; protected void showProgress(double percentDone) { if (progressBar!=null) @@ -152,6 +155,16 @@ public abstract class ImageProcessor imp return cm; } + private IndexColorModel getIndexColorModel() { + ColorModel cm2 = baseCM; + if (cm2==null) + cm2 = cm; + if (cm2!=null && (cm2 instanceof IndexColorModel)) + return (IndexColorModel)cm2; + else + return null; + } + /** Returns the current color model, which may have been modified by setMinAndMax() or setThreshold(). */ public ColorModel getCurrentColorModel() { @@ -169,10 +182,8 @@ public abstract class ImageProcessor imp this.cm = cm; baseCM = null; rLUT1 = rLUT2 = null; - newPixels = true; inversionTested = false; minThreshold = NO_THRESHOLD; - source = null; } public LUT getLut() { @@ -278,12 +289,9 @@ public abstract class ImageProcessor imp public boolean isInvertedLut() { if (inversionTested) return invertedLut; - if (cm==null || !(cm instanceof IndexColorModel)) { - invertedLut = false; - inversionTested = true; - return invertedLut; - } - IndexColorModel icm = (IndexColorModel)cm; + IndexColorModel icm = getIndexColorModel(); + if (icm==null) + return false; boolean hasAscendingStep = false; int v1, v2; for (int i=1; i<255; i++) { @@ -308,9 +316,9 @@ public abstract class ImageProcessor imp /** Returns true if this image uses a color LUT. */ public boolean isColorLut() { - if (cm==null || !(cm instanceof IndexColorModel)) + IndexColorModel icm = getIndexColorModel(); + if (icm==null) return false; - IndexColorModel icm = (IndexColorModel)cm; int mapSize = icm.getMapSize(); byte[] reds = new byte[mapSize]; byte[] greens = new byte[mapSize]; @@ -331,11 +339,11 @@ public abstract class ImageProcessor imp /** Returns true if this image uses a pseudocolor or grayscale LUT, in other words, is this an image that can be filtered. */ public boolean isPseudoColorLut() { - if (cm==null || !(cm instanceof IndexColorModel)) + IndexColorModel icm = getIndexColorModel(); + if (icm==null) return false; if (getMinThreshold()!=NO_THRESHOLD) return true; - IndexColorModel icm = (IndexColorModel)cm; int mapSize = icm.getMapSize(); if (mapSize!=256) return false; @@ -371,9 +379,9 @@ public abstract class ImageProcessor imp public boolean isDefaultLut() { if (cm==null) makeDefaultColorModel(); - if (!(cm instanceof IndexColorModel)) - return false; - IndexColorModel icm = (IndexColorModel)cm; + IndexColorModel icm = getIndexColorModel(); + if (icm==null) + return false; int mapSize = icm.getMapSize(); if (mapSize!=256) return false; @@ -507,18 +515,37 @@ public abstract class ImageProcessor imp } } cm = new IndexColorModel(8, 256, rLUT2, gLUT2, bLUT2); - newPixels = true; - source = null; } - public void setAutoThreshold(String mString) { - if (mString==null) + /** Automatically sets the lower and upper threshold levels, where 'method' + * must be "Default", "Huang", "Intermodes", "IsoData", "IJ_IsoData", "Li", + * "MaxEntropy", "Mean", "MinError", "Minimum", "Moments", "Otsu", + * "Percentile", "RenyiEntropy", "Shanbhag", "Triangle" or "Yen". The + * 'method' string may also include the keywords 'dark' (dark background) + * 'red' (red LUT, the default), 'b&w' (black and white LUT), 'over/under' (over/under LUT) or + * 'no-lut' (no LUT changes), for example "Huang dark b&w". The display range + * of 16-bit and 32-bit images is not reset if the 'method' string contains 'no-reset'. + * @see ImageProcessor#resetThreshold + * @see ImageProcessor#setThreshold + * @see ImageProcessor#createMask + */ + public void setAutoThreshold(String method) { + if (method==null) throw new IllegalArgumentException("Null method"); - boolean darkBackground = mString.indexOf("dark")!=-1; - int index = mString.indexOf(" "); + boolean darkBackground = method.contains("dark"); + noReset = method.contains("no-reset"); + int lut = RED_LUT; + if (method.contains("b&w")) + lut = BLACK_AND_WHITE_LUT; + if (method.contains("over")) + lut = OVER_UNDER_LUT; + if (method.contains("no-lut")) + lut = NO_LUT_UPDATE; + int index = method.indexOf(" "); if (index!=-1) - mString = mString.substring(0, index); - setAutoThreshold(mString, darkBackground, RED_LUT); + method = method.substring(0, index); + setAutoThreshold(method, darkBackground, lut); + noReset = false; } public void setAutoThreshold(String mString, boolean darkBackground, int lutUpdate) { @@ -546,9 +573,11 @@ public abstract class ImageProcessor imp if (notByteData) { ImageProcessor mask = ip2.getMask(); Rectangle rect = ip2.getRoi(); - resetMinAndMax(); - min = getMin(); max = getMax(); - ip2 = convertToByte(true); + if (!noReset || lutUpdate==OVER_UNDER_LUT) + ip2.resetMinAndMax(); + noReset = false; + min = ip2.getMin(); max = ip2.getMax(); + ip2 = ip2.convertToByte(true); ip2.setMask(mask); ip2.setRoi(rect); } @@ -649,14 +678,12 @@ public abstract class ImageProcessor imp if (max>min) { if (bitDepth==16 && lower==0.0) lower = 0.0; - else if (bitDepth==32 && lower==0.0) - lower = -Float.MAX_VALUE; else lower = min + (lower/255.0)*(max-min); if (bitDepth==16 && upper==255.0) upper = 65535; else if (bitDepth==32 && upper==255.0) - upper = Float.MAX_VALUE; + upper = getStats().max; else upper = min + (upper/255.0)*(max-min); } else @@ -674,8 +701,6 @@ public abstract class ImageProcessor imp } rLUT1 = rLUT2 = null; inversionTested = false; - newPixels = true; - source = null; } /** Returns the lower threshold level. Returns NO_THRESHOLD @@ -1408,6 +1433,8 @@ public abstract class ImageProcessor imp /** Sets the justification used by drawString(), where justification is CENTER_JUSTIFY, RIGHT_JUSTIFY or LEFT_JUSTIFY. The default is LEFT_JUSTIFY. */ public void setJustification(int justification) { + if (justificationRIGHT_JUSTIFY) + justification = LEFT_JUSTIFY; this.justification = justification; } @@ -1575,6 +1602,13 @@ public abstract class ImageProcessor imp * @see #fill(Roi) */ public void fill(Roi roi) { + if (roi!=null && roi.isLine()) { + if ((roi instanceof Line) && roi.getStrokeWidth()>1 && !(roi instanceof Arrow)) + fillPolygon(roi.getPolygon()); + else + roi.drawPixels(this); + return; + } ImageProcessor m = getMask(); Rectangle r = getRoi(); setRoi(roi); @@ -1612,7 +1646,6 @@ public abstract class ImageProcessor imp width, stroke color and fill color defined by roi.setStrokeWidth, roi.setStrokeColor() and roi.setFillColor(). Works with RGB images. Does not work with 16-bit and float images. - Requires Java 1.6. @see ImageProcessor#draw @see ImageProcessor#drawOverlay */ @@ -1720,8 +1753,8 @@ public abstract class ImageProcessor imp public abstract void set(int index, int value); - /** Returns the value of the pixel at (x,y) as a float. Faster than - getPixel() because no bounds checking is done. */ + /** Returns the value of the pixel at (x,y) as a float. Faster + than getPixelValue() because no bounds checking is done. */ public abstract float getf(int x, int y); public abstract float getf(int index); @@ -1928,7 +1961,7 @@ public abstract class ImageProcessor imp /** Returns the value of the pixel at (x,y). For byte and short images, returns a calibrated value if a calibration table - has been set using setCalibraionTable(). For RGB images, + has been set using setCalibraionTable(). For RGB images, returns the luminance value. */ public abstract float getPixelValue(int x, int y); @@ -2250,8 +2283,6 @@ public abstract class ImageProcessor imp of the image. */ public void setLutAnimation(boolean lutAnimation) { this.lutAnimation = lutAnimation; - newPixels = true; - source = null; } void resetPixels(Object pixels) { @@ -2260,10 +2291,7 @@ public abstract class ImageProcessor imp img.flush(); img = null; } - source = null; } - newPixels = true; - source = null; } /** Returns an 8-bit version of this image as a ByteProcessor. */ @@ -2697,4 +2725,31 @@ public abstract class ImageProcessor imp return inc; } + public static void setRandomSeed(double randomSeed) { + seed = randomSeed; + } + + /** Returns a binary mask, or null if a threshold is not set or this is an RGB image. + * @see ij.ImagePlus#createThresholdMask + * @see ij.ImagePlus#createRoiMask + */ + public ByteProcessor createMask() { + return null; + } + + protected IndexColorModel getThresholdColorModel() { + byte[] r = new byte[256]; + byte[] g = new byte[256]; + byte[] b = new byte[256]; + for(int i=0; i<255; i++) { + r[i]=(byte)i; + g[i]=(byte)i; + b[i]=(byte)i; + } + r[255] = (byte)255; + g[255] = (byte)0; + b[255] = (byte)0; + return new IndexColorModel(8, 256, r, g, b); + } + } diff -pruN 1.51q-1/ij/process/ImageStatistics.java 1.52g-1/ij/process/ImageStatistics.java --- 1.51q-1/ij/process/ImageStatistics.java 2016-11-12 16:23:52.000000000 +0000 +++ 1.52g-1/ij/process/ImageStatistics.java 2018-06-07 08:42:24.000000000 +0000 @@ -7,14 +7,17 @@ public class ImageStatistics implements /** Use getHIstogram() to get histogram as long array. */ public int[] histogram; + /** Int pixel count (limited to 2^31-1) */ public int pixelCount; /** Long pixel count */ - public long longPixelCount; - /** Int mode (limited to 2^31-1) */ - public int mode; - /** Double mode*/ + public long longPixelCount; + + /** Mode */ public double dmode; + /** Mode of 256 bin histogram (counts limited to 2^31-1) */ + public int mode; + public double area; public double min; public double max; diff -pruN 1.51q-1/ij/process/ShortProcessor.java 1.52g-1/ij/process/ShortProcessor.java --- 1.51q-1/ij/process/ShortProcessor.java 2017-04-13 18:12:00.000000000 +0000 +++ 1.52g-1/ij/process/ShortProcessor.java 2018-08-06 18:43:36.000000000 +0000 @@ -86,44 +86,64 @@ public class ShortProcessor extends Imag /** Create an 8-bit AWT image by scaling pixels in the range min-max to 0-255. */ public Image createImage() { boolean firstTime = pixels8==null; + boolean thresholding = minThreshold!=NO_THRESHOLD && lutUpdateMode=t1 && value<=t2) + pixels8[i] = (byte)255; + else + pixels8[i] = (byte)0; + } + } else { // threshold red + for (int i=0; i=t1 && value<=t2) + pixels8[i] = (byte)255; + } + } + } + return createBufferedImage(); } // create 8-bit image by linearly scaling from 16-bits to 8-bits - byte[] create8BitImage() { + private byte[] create8BitImage(boolean thresholding) { int size = width*height; if (pixels8==null) pixels8 = new byte[size]; int value; - int min2=(int)getMin(), max2=(int)getMax(); + int min2=(int)getMin(), max2=(int)getMax(); + int maxValue = 255; double scale = 256.0/(max2-min2+1); + if (thresholding) { + maxValue = 254; + scale = 255.0/(max2-min2+1); + } for (int i=0; i255) value = 255; + if (value>maxValue) value = maxValue; pixels8[i] = (byte)value; } return pixels8; } + @Override + byte[] create8BitImage() { + return create8BitImage(false); + } + Image createBufferedImage() { if (raster==null) { SampleModel sm = getIndexSampleModel(); @@ -283,7 +303,7 @@ public class ShortProcessor extends Imag pixels[index] = (short)value; } - public final float getf(int x, int y) { + public final float getf(int x, int y) { return pixels[y*width+x]&0xffff; } @@ -424,10 +444,17 @@ public class ShortProcessor extends Imag /** Copies the image contained in 'ip' to (xloc, yloc) using one of the transfer modes defined in the Blitter interface. */ public void copyBits(ImageProcessor ip, int xloc, int yloc, int mode) { - ip = ip.convertToShort(false); - new ShortBlitter(this).copyBits(ip, xloc, yloc, mode); + boolean temporaryFloat = ip.getBitDepth()==32 && (mode==Blitter.MULTIPLY || mode==Blitter.DIVIDE); + if (temporaryFloat) { + FloatProcessor ipFloat = this.convertToFloatProcessor(); + new FloatBlitter(ipFloat).copyBits(ip, xloc, yloc, mode); + setPixels(1, ipFloat); + } else { + ip = ip.convertToShort(false); + new ShortBlitter(this).copyBits(ip, xloc, yloc, mode); + } } - + /** Transforms the pixel data using a 65536 entry lookup table. */ public void applyTable(int[] lut) { if (lut.length!=65536) @@ -957,11 +984,13 @@ public class ShortProcessor extends Imag return 0.0; } - /** Returns 65536 bin histogram of the current ROI, which + /** Returns 65,536 bin histogram of the current ROI, which can be non-rectangular. */ public int[] getHistogram() { if (mask!=null) return getHistogram(mask); + int roiX=this.roiX, roiY=this.roiY; + int roiWidth=this.roiWidth, roiHeight=this.roiHeight; int[] histogram = new int[65536]; for (int y=roiY; y<(roiY+roiHeight); y++) { int i = y*width + roiX; @@ -974,6 +1003,8 @@ public class ShortProcessor extends Imag int[] getHistogram(ImageProcessor mask) { if (mask.getWidth()!=roiWidth||mask.getHeight()!=roiHeight) throw new IllegalArgumentException(maskSizeError(mask)); + int roiX=this.roiX, roiY=this.roiY; + int roiWidth=this.roiWidth, roiHeight=this.roiHeight; byte[] mpixels = (byte[])mask.getPixels(); int[] histogram = new int[65536]; for (int y=roiY, my=0; y<(roiY+roiHeight); y++, my++) { @@ -988,10 +1019,27 @@ public class ShortProcessor extends Imag return histogram; } + /** Creates a histogram of length maxof(max+1,256). For small + images or selections, computations using these histograms + are faster compared to 65536 element histograms. */ int[] getHistogram2() { if (mask!=null) return getHistogram2(mask); - int[] histogram = makeHistogramArray(); + int roiX=this.roiX, roiY=this.roiY; + int roiWidth=this.roiWidth, roiHeight=this.roiHeight; + int max = 0; + int value; + for (int y=roiY; y<(roiY+roiHeight); y++) { + int index = y*width + roiX; + for (int i=0; imax) + max = value; + } + } + int size = max + 1; + if (size<256) size = 256; + int[] histogram = new int[size]; for (int y=roiY; y<(roiY+roiHeight); y++) { int index = y*width + roiX; for (int i=0; i65535.0) maxThreshold = 65535.0; int min2=(int)getMin(), max2=(int)getMax(); if (max2>min2) { - // scale to 0-255 using same method as create8BitImage() - double scale = 256.0/(max2-min2+1); - double minT = minThreshold-min2; - if (minT<0) minT = 0; - minT = (int)(minT*scale+0.5); - if (minT>255) minT = 255; - //ij.IJ.log("setThreshold: "+minT+" "+Math.round(((minThreshold-min2)/(max2-min2))*255.0)); - double maxT = maxThreshold-min2; - if (maxT<0) maxT = 0; - maxT = (int)(maxT*scale+0.5); - if (maxT>255) maxT = 255; - super.setThreshold(minT, maxT, lutUpdate); // update LUT + if (lutUpdate==OVER_UNDER_LUT) { + double minT = ((minThreshold-getMin())/(getMax()-getMin())*255.0); + double maxT = ((maxThreshold-getMin())/(getMax()-getMin())*255.0); + super.setThreshold(minT, maxT, lutUpdate); // update LUT + } else { + lutUpdateMode = lutUpdate; + if (rLUT1==null) { + if (cm==null) + makeDefaultColorModel(); + baseCM = cm; + IndexColorModel m = (IndexColorModel)cm; + rLUT1 = new byte[256]; gLUT1 = new byte[256]; bLUT1 = new byte[256]; + m.getReds(rLUT1); m.getGreens(gLUT1); m.getBlues(bLUT1); + rLUT2 = new byte[256]; gLUT2 = new byte[256]; bLUT2 = new byte[256]; + } + if (lutUpdateMode==RED_LUT) + cm = getThresholdColorModel(); + else + cm = getDefaultColorModel(); + } } else super.resetThreshold(); this.minThreshold = Math.round(minThreshold); this.maxThreshold = Math.round(maxThreshold); + //ij.IJ.log("setThreshold: "+lutUpdateMode+" "+this.minThreshold+" "+this.maxThreshold); } /** Performs a convolution operation using the specified kernel. */ @@ -1072,7 +1132,11 @@ public class ShortProcessor extends Imag /** Adds pseudorandom, Gaussian ("normally") distributed values, with mean 0.0 and the specified standard deviation, to this image or ROI. */ public void noise(double standardDeviation) { - Random rnd=new Random(); + if (rnd==null) + rnd = new Random(); + if (!Double.isNaN(seed)) + rnd.setSeed((int) seed); + seed = Double.NaN; int v, ran; boolean inRange; for (int y=roiY; y<(roiY+roiHeight); y++) { @@ -1156,6 +1220,22 @@ public class ShortProcessor extends Imag public boolean isSigned16Bit() { return cTable!=null && cTable[0]==-32768f && cTable[1]==-32767f; } + + /** Returns a binary mask, or null if a threshold is not set. */ + public ByteProcessor createMask() { + if (getMinThreshold()==NO_THRESHOLD) + return null; + int minThreshold = (int)getMinThreshold(); + int maxThreshold = (int)getMaxThreshold(); + ByteProcessor mask = new ByteProcessor(width, height); + byte[] mpixels = (byte[])mask.getPixels(); + for (int i=0; i=minThreshold && value<=maxThreshold) + mpixels[i] = (byte)255; + } + return mask; + } /** Not implemented. */ public void medianFilter() {} diff -pruN 1.51q-1/ij/process/ShortStatistics.java 1.52g-1/ij/process/ShortStatistics.java --- 1.51q-1/ij/process/ShortStatistics.java 2015-06-10 22:12:08.000000000 +0000 +++ 1.52g-1/ij/process/ShortStatistics.java 2018-06-07 17:14:02.000000000 +0000 @@ -1,5 +1,6 @@ package ij.process; import ij.measure.Calibration; +import java.awt.Rectangle; /** 16-bit image statistics, including histogram. */ public class ShortStatistics extends ImageStatistics { @@ -31,7 +32,9 @@ public class ShortStatistics extends Ima } if (limitToThreshold) saveThreshold(minThreshold, maxThreshold, cal); - int[] hist = (ip instanceof ShortProcessor)?((ShortProcessor)ip).getHistogram2():ip.getHistogram(); + Rectangle r = ip.getRoi(); + boolean smallRoi = r.width*r.height<250000; + int[] hist = smallRoi&&(ip instanceof ShortProcessor)?((ShortProcessor)ip).getHistogram2():ip.getHistogram(); if (maxThreshold>hist.length-1) maxThreshold = hist.length-1; histogram16 = hist; @@ -128,10 +131,8 @@ public class ShortStatistics extends Ima mode = i; } } - //ij.IJ.write("mode2: "+mode+" "+dmode+" "+maxCount); } - void getCentroid(ImageProcessor ip, int minThreshold, int maxThreshold) { short[] pixels = (short[])ip.getPixels(); byte[] mask = ip.getMaskArray(); diff -pruN 1.51q-1/ij/process/StackConverter.java 1.52g-1/ij/process/StackConverter.java --- 1.51q-1/ij/process/StackConverter.java 2017-04-28 17:59:44.000000000 +0000 +++ 1.52g-1/ij/process/StackConverter.java 2018-01-25 19:03:18.000000000 +0000 @@ -81,6 +81,11 @@ public class StackConverter { /** Converts an RGB or 8-bit color stack to 8-bit grayscale. */ void convertRGBToGray8() { ImageStack stack1 = imp.getStack(); + if (stack1 instanceof PlotVirtualStack) { + ((PlotVirtualStack)stack1).setBitDepth(8); + imp.setStack(stack1); + return; + } ImageStack stack2 = new ImageStack(width, height); ImageProcessor ip; Image img; @@ -173,6 +178,11 @@ public class StackConverter { return; } ImageStack stack1 = imp.getStack(); + if (stack1 instanceof PlotVirtualStack) { + ((PlotVirtualStack)stack1).setBitDepth(24); + imp.setStack(stack1); + return; + } ImageStack stack2 = new ImageStack(width, height); String label; int inc = nSlices/20; diff -pruN 1.51q-1/ij/process/TypeConverter.java 1.52g-1/ij/process/TypeConverter.java --- 1.51q-1/ij/process/TypeConverter.java 2015-03-26 19:18:44.000000000 +0000 +++ 1.52g-1/ij/process/TypeConverter.java 2018-07-27 13:41:40.000000000 +0000 @@ -75,8 +75,10 @@ public class TypeConverter { /** Converts a FloatProcessor to a ByteProcessor. */ ByteProcessor convertFloatToByte() { if (doScaling) { - Image img = ip.createImage(); - return new ByteProcessor(img); + byte[] pixels8 = ip.create8BitImage(); + ByteProcessor bp = new ByteProcessor(ip.getWidth(), ip.getHeight(), pixels8); + bp.setColorModel(ip.getColorModel()); + return bp; } else { ByteProcessor bp = new ByteProcessor(width, height); bp.setPixels(0, (FloatProcessor)ip); diff -pruN 1.51q-1/ij/text/TextCanvas.java 1.52g-1/ij/text/TextCanvas.java --- 1.51q-1/ij/text/TextCanvas.java 2016-05-31 19:30:38.000000000 +0000 +++ 1.52g-1/ij/text/TextCanvas.java 2018-08-14 16:19:28.000000000 +0000 @@ -41,9 +41,9 @@ class TextCanvas extends Canvas { g.setColor(Color.lightGray); if (iImage==null) makeImage(iWidth,iHeight); - if (tp.iRowHeight==0 || (tp.iColWidth[0]==0&&tp.iRowCount>0)) { + if (tp.iRowHeight==0 || (tp.iColWidth.length>0 && tp.iColWidth[0]==0&&tp.iRowCount>0)) { tp.iRowHeight=fMetrics.getHeight()+2; - for(int i=0;i=tp.selStart && j<=tp.selEnd) { + if (j>=tp.selStart && j<=tp.selEnd) { int w2 = w; if (tp.iColCount==1) w2 = iWidth; @@ -100,14 +100,14 @@ class TextCanvas extends Canvas { gImage.setColor(Color.darkGray); gImage.drawLine(0,tp.iRowHeight,iWidth,tp.iRowHeight); int x=-tp.iX; - for(int i=0;i1) { + if (tp.iColCount>0) { gImage.setColor(Color.darkGray); gImage.drawLine(x+w-1,0,x+w-1,tp.iRowHeight-1); gImage.setColor(Color.white); @@ -128,8 +128,8 @@ class TextCanvas extends Canvas { return null; if (row>=tp.vData.size()) return null; - char[] chars = (char[])(tp.vData.elementAt(row)); - if (chars.length==0) + char[] chars = row=tp.iColWidth.length || gImage==null) return; - if(fMetrics==null) + if (fMetrics==null) fMetrics=gImage.getFontMetrics(); int w=15; - int maxRows; - if (tp.iColCount==1) - maxRows = 100; + int maxRows = 20; + if (column==0 && tp.sColHead[0].equals(" ")) + w += 5; else { - maxRows = 20; - if (column==0 && tp.sColHead[0].equals(" ")) - w += 5; - else { - char[] chars = tp.sColHead[column].toCharArray(); - w = Math.max(w,fMetrics.charsWidth(chars,0,chars.length)); - } + char[] chars = tp.sColHead[column].toCharArray(); + w = Math.max(w,fMetrics.charsWidth(chars,0,chars.length)); } int rowCount = Math.min(tp.iRowCount, maxRows); - for(int row=0; row0?getChars(column, tp.iRowCount-1):null; if (chars!=null) w = Math.max(w,fMetrics.charsWidth(chars,0,chars.length)); - tp.iColWidth[column] = w+15; + if (column0 && tw.mb.getMenu(mbSize-1).getLabel().equals("Results")) tw.mb.remove(mbSize-1); title = title2; + rt.show(title); } Menus.updateWindowMenuItem(title1, title2); - if (Recorder.record) - Recorder.recordString("IJ.renameResults(\""+title2+"\");\n"); + if (Recorder.record) { + if (Recorder.scriptMode()) + Recorder.recordString("IJ.renameResults(\""+title1+"\", \""+title2+"\");\n"); + else + Recorder.record("Table.rename", title1, title2); + } } void duplicate() { @@ -699,8 +704,16 @@ public class TextPanel extends Panel imp IJ.error("Selection required"); return; } - if (Recorder.record) - Recorder.recordString("IJ.deleteRows("+selStart+", "+selEnd+");\n"); + if (Recorder.record) { + if (Recorder.scriptMode()) + Recorder.recordString("IJ.deleteRows("+selStart+", "+selEnd+");\n"); + else { + if ("Results".equals(title)) + Recorder.record("Table.deleteRows", selStart, selEnd); + else + Recorder.record("Table.deleteRows", selStart, selEnd, title); + } + } int first=selStart, last=selEnd, rows=iRowCount; if (selStart==0 && selEnd==(iRowCount-1)) { vData.removeAllElements(); @@ -720,7 +733,7 @@ public class TextPanel extends Panel imp vData.removeElementAt(selStart); iRowCount--; } - if (rt!=null && rowCount==rt.getCounter()) { + if (rt!=null && rowCount==rt.size()) { for (int i=0; i 36 ? 1 : //the polynomials go crazy for some large x2; erf(6) is 1 - 2e-17, less than ulp from 1 + Math.sqrt(1 - Math.exp(-sqr(x*((2/Math.sqrt(Math.PI) - A) + A * + (1 + x2*x2*(B + x2*(C + x2*(H + x2*I)))) / + (1 + x2*(D + x2*(E + x2*(F + x2*G)))) + )))); + return x>0 ? erf : -erf; //could be Math.copySign in Java 1.6 & up + } + + static double sqr(double x) { + return x*x; + } + +} diff -pruN 1.51q-1/ij/util/Tools.java 1.52g-1/ij/util/Tools.java --- 1.51q-1/ij/util/Tools.java 2015-05-26 07:18:46.000000000 +0000 +++ 1.52g-1/ij/util/Tools.java 2018-05-28 19:42:46.000000000 +0000 @@ -1,8 +1,10 @@ package ij.util; +import ij.process.*; import java.awt.Color; import java.util.*; import java.io.*; import java.util.Comparator; +import java.nio.channels.FileChannel; /** This class contains static utility methods. */ public class Tools { @@ -44,6 +46,11 @@ import java.util.Comparator; return new String(buf); } + public static ImageStatistics getStatistics(double[] a) { + ImageProcessor ip = new FloatProcessor(a.length, 1, a); + return ip.getStats(); + } + public static double[] getMinMax(double[] a) { double min = Double.MAX_VALUE; double max = -Double.MAX_VALUE; @@ -252,11 +259,70 @@ import java.util.Comparator; return indexes2; } + /** Returns an array linearly resampled to a different length. */ + public static double[] resampleArray(double[] y1, int len2) { + int len1 = y1.length; + double factor = (double)(len2-1)/(len1-1); + double[] y2 = new double[len2]; + if(len1 == 0){ + return y2; + } + if(len1 == 1){ + for (int jj=0; jjnSlices) throw new IllegalArgumentException("Argument out of range: "+n); - if (nSlices<1) - return; - for (int i=n; i100 && label.indexOf('\n')>0) return names[n-1]+"\n"+label; + else + return label; } /** Returns null. */ diff -pruN 1.51q-1/ij/WindowManager.java 1.52g-1/ij/WindowManager.java --- 1.51q-1/ij/WindowManager.java 2015-12-07 21:51:30.000000000 +0000 +++ 1.52g-1/ij/WindowManager.java 2018-08-12 14:00:14.000000000 +0000 @@ -15,6 +15,7 @@ public class WindowManager { public static boolean checkForDuplicateName; private static Vector imageList = new Vector(); // list of image windows + private static Vector activations = new Vector(); // list of image windows, ordered by activation time private static Vector nonImageList = new Vector(); // list of non-image windows (Frames and Dialogs) private static ImageWindow currentWindow; // active image window private static Window frontWindow; @@ -44,6 +45,8 @@ public class WindowManager { } Undo.reset(); currentWindow = win; + activations.remove(win); + activations.add(win); Menus.updateMenus(); if (Recorder.record && !IJ.isMacro()) Recorder.record("selectWindow", win.getImagePlus().getTitle()); @@ -51,7 +54,6 @@ public class WindowManager { /** Returns the active ImageWindow. */ public static ImageWindow getCurrentWindow() { - //if (IJ.debugMode) IJ.write("ImageWindow.getCurrentWindow"); return currentWindow; } @@ -75,7 +77,7 @@ public class WindowManager { image for this thread. Call again with a null argument to revert to the previous active image. */ public static void setTempCurrentImage(ImagePlus img) { - //IJ.log("setTempImage: "+(img!=null?img.getTitle():"null")+" "+Thread.currentThread().hashCode()); + //IJ.log("setTempImage: "+(img!=null?""+img:"null")+" "+Thread.currentThread().hashCode()); if (img==null) tempImageTable.remove(Thread.currentThread()); else @@ -102,7 +104,7 @@ public class WindowManager { ImagePlus imp = getFocusManagerActiveImage(); if (imp!=null) return imp; - ImageWindow win = (ImageWindow)imageList.elementAt(imageList.size()-1); + ImageWindow win = (ImageWindow)imageList.get(imageList.size()-1); return win.getImagePlus(); } else return Interpreter.getLastBatchModeImage(); @@ -157,7 +159,7 @@ public class WindowManager { list[i] = batchModeImages[i]; int index = 0; for (int i=nBatchImages; i0) imageID = getNthImageID(imageID); if (imageID==0 || getImageCount()==0) @@ -230,7 +231,7 @@ public class WindowManager { return imp2; ImagePlus imp = null; for (int i=0; iimageList.size()) return 0; - ImageWindow win = (ImageWindow)imageList.elementAt(n-1); + ImageWindow win = (ImageWindow)imageList.get(n-1); if (win!=null) return win.getImagePlus().getID(); else @@ -278,14 +279,13 @@ public class WindowManager { /** Adds the specified window to the Window menu. */ public synchronized static void addWindow(Window win) { - //IJ.write("addWindow: "+win.getTitle()); if (win==null) return; else if (win instanceof ImageWindow) addImageWindow((ImageWindow)win); else { Menus.insertWindowMenuItem(win); - nonImageList.addElement(win); + nonImageList.add(win); } } @@ -298,11 +298,11 @@ public class WindowManager { ImagePlus imp = win.getImagePlus(); if (imp==null) return; checkForDuplicateName(imp); - imageList.addElement(win); + imageList.add(win); Menus.addWindowMenuItem(imp); setCurrentWindow(win); } - + static void checkForDuplicateName(ImagePlus imp) { if (checkForDuplicateName) { String name = imp.getTitle(); @@ -315,7 +315,7 @@ public class WindowManager { static boolean isDuplicateName(String name) { int n = imageList.size(); for (int i=0; i=0) { - //if (ij!=null && !ij.quitting()) Menus.removeWindowMenuItem(index); nonImageList.removeElement(win); } @@ -377,14 +375,13 @@ public class WindowManager { int index = imageList.indexOf(win); if (index==-1) return; // not on the window list - if (imageList.size()>1 && IJ.isMacro()) { - int newIndex = index-1; - if (newIndex<0) - newIndex = imageList.size()-1; - setCurrentWindow((ImageWindow)imageList.elementAt(newIndex)); + imageList.removeElementAt(index); + activations.remove(win); + if (imageList.size()>1 && !Prefs.closingAll) { + ImageWindow win2 = activations.size()>0?(ImageWindow)activations.get(activations.size()-1):null; + setCurrentWindow(win2); } else currentWindow = null; - imageList.removeElementAt(index); setTempCurrentImage(null); //??? int nonImageCount = nonImageList.size(); if (nonImageCount>0) @@ -396,6 +393,7 @@ public class WindowManager { /** The specified Window becomes the front window. */ public static void setWindow(Window win) { + //System.out.println("setWindow: "+win); frontWindow = win; if (win instanceof Frame) frontFrame = (Frame)win; @@ -405,17 +403,21 @@ public class WindowManager { public static void setWindow(Frame win) { frontWindow = win; frontFrame = win; - //IJ.log("Set window: "+(win!=null?win.getTitle():"null")); + //System.out.println("Set window: "+(win!=null?win.getTitle():"null")); } /** Closes all windows. Stops and returns false if an image or Editor "save changes" dialog is canceled. */ public synchronized static boolean closeAllWindows() { + Prefs.closingAll = true; while (imageList.size()>0) { - if (!((ImageWindow)imageList.elementAt(0)).close()) + if (!((ImageWindow)imageList.get(0)).close()) { + Prefs.closingAll = false; return false; + } if (!quittingViaMacro()) IJ.wait(100); } + Prefs.closingAll = false; Frame[] nonImages = getNonImageWindows(); for (int i=0; i 0) - baos.write(buf, 0, retval); - baos.close(); - size = baos.size(); - //IJ.log("size: "+size); IJ.wait(2000); - if (size<=0) - return null; - byte[] imgBytes= baos.toByteArray(); - // Again with the uglyness. Here we need to use the Quicktime - // for Java code in order to create an Image object from - // the PICT data we received on the clipboard. However, in - // order to get this to compile on other platforms, we use - // the Java reflection API. - // - // For reference, here is the equivalent code without - // reflection: - // - // - // if (QTSession.isInitialized() == false) { - // QTSession.open(); - // } - // QTHandle handle= new QTHandle(imgBytes); - // GraphicsImporter gi= - // new GraphicsImporter(QTUtils.toOSType("PICT")); - // gi.setDataHandle(handle); - // QDRect qdRect= gi.getNaturalBounds(); - // GraphicsImporterDrawer gid= new GraphicsImporterDrawer(gi); - // QTImageProducer qip= new QTImageProducer(gid, - // new Dimension(qdRect.getWidth(), - // qdRect.getHeight())); - // return(Toolkit.getDefaultToolkit().createImage(qip)); - // - // --GP - //IJ.log("quicktime.QTSession"); - Class c = Class.forName("quicktime.QTSession"); - Method m = c.getMethod("isInitialized", null); - Boolean b= (Boolean)m.invoke(null, null); - if (b.booleanValue() == false) { - m= c.getMethod("open", null); - m.invoke(null, null); - } - c= Class.forName("quicktime.util.QTHandle"); - Constructor con = c.getConstructor(new Class[] {imgBytes.getClass() }); - Object handle= con.newInstance(new Object[] { imgBytes }); - String s= new String("PICT"); - c = Class.forName("quicktime.util.QTUtils"); - m = c.getMethod("toOSType", new Class[] { s.getClass() }); - Integer type= (Integer)m.invoke(null, new Object[] { s }); - c = Class.forName("quicktime.std.image.GraphicsImporter"); - con = c.getConstructor(new Class[] { type.TYPE }); - Object importer= con.newInstance(new Object[] { type }); - m = c.getMethod("setDataHandle", - new Class[] { Class.forName("quicktime.util." + "QTHandleRef") }); - m.invoke(importer, new Object[] { handle }); - m = c.getMethod("getNaturalBounds", null); - Object rect= m.invoke(importer, null); - c = Class.forName("quicktime.app.view.GraphicsImporterDrawer"); - con = c.getConstructor(new Class[] { importer.getClass() }); - Object iDrawer = con.newInstance(new Object[] { importer }); - m = rect.getClass().getMethod("getWidth", null); - Integer width= (Integer)m.invoke(rect, null); - m = rect.getClass().getMethod("getHeight", null); - Integer height= (Integer)m.invoke(rect, null); - Dimension d= new Dimension(width.intValue(), height.intValue()); - c = Class.forName("quicktime.app.view.QTImageProducer"); - con = c.getConstructor(new Class[] { iDrawer.getClass(), d.getClass() }); - Object producer= con.newInstance(new Object[] { iDrawer, d }); - if (producer instanceof ImageProducer) - return(Toolkit.getDefaultToolkit().createImage((ImageProducer)producer)); - } catch (Exception e) { - IJ.showStatus(""+e); - } - return null; - } - - // Retuns true if QuickTime for Java is installed. - // This code was contributed by Gord Peters. - boolean isQTJavaInstalled() { - boolean isInstalled = false; - try { - Class c= Class.forName("quicktime.QTSession"); - isInstalled = true; - } catch (Exception e) {} - return isInstalled; - } - -} - - - diff -pruN 1.51q-1/release-notes.html 1.52g-1/release-notes.html --- 1.51q-1/release-notes.html 2017-09-23 22:09:12.000000000 +0000 +++ 1.52g-1/release-notes.html 2018-09-15 08:14:54.000000000 +0000 @@ -5,10 +5,335 @@ -
  • 1.51r 23 September 2017 +
  • 1.52g 15 September 2018
      -
    • Fixed a 1.51q reqression that caused the Plot class to -sometimes throw an exception. +
    • A text selection in an overlay can be deleted by alt-clicking on +it and pressing backspace+delete (on Windows) or command+delete (on Macs). +
    • Fixed a bug that caused polygon and polyline selections to lose +properties like color, line width and name when a point is added by +shift-clicking on an existing point. +
    • Thanks to Dave Mason, fixed a bug that caused the +File>Import>URL command to not correctly +open Hyperstacks. +
    • Thanks to Volko Straub, fixed a 1.52f regression that caused +getDirectory("") on Windows to return a string ending in "/" instead +of the expected "\". +
    + +
  • 1.52f 7 September 2018 +
      +
    • Thanks to Norbert Vischer, added the Plot.addHistogram() +macro function, which plots a histogram from an array +(example). +
    • Thanks to Norbert Vischer, any currently running macro is aborted +when running an installed macro from the Plugins>Macros +menu or the editor's Macros menu. +
    • Thanks to Michael Schmid, added the "Error Function" [y=a+b*erf((x-c)/d)] +curve fit option and the ij.util.IJ.Math.erf(x) method +(example). +
    • The Edit>Selection>Create Mask command works +with line selections and overlays. +
    • The "Black background" setting defaults to 'true'. +
    • Thanks to Anna Povolna, the "Open all files in folder" and "Use virtual stack" +options in the File>Import>raw dialog can be used at +the same time. +
    • Thanks to 'Sethur', ImageJ no longer sorts imported DICOM +stacks by series number (tag 0020,0011) if "Sort images numerically" +is not checked in the File>Import>ImageSequence +dialog. Also added a recordable 'noMetaSort' option to the options +string of the FolderOpener.open(dir,options) method. +
    • Thanks to 'Alan', added the createThresholdMask() +and createRoiMask() methods to the ImagePlus class. These methods +are recorded when using the Edit>Selection>Create Mask +command +(JavaScript example). +
    • Added the recordable Raw.open(), Raw.openAll() and Raw.openAllVirtual() methods +(JavaScript example). +
    • Thanks to Stein Rorvik, added the DicomTools.getTagName(tag) method. +
    • Thanks to Christian Tischer, fixed bugs in the RoiManager.add(ImagePlus,Roi,int) +method than caused it to not behave as expected if the ROI name was not null. +
    • Thanks to Norbert Vischer, fixed bugs that caused the +Edit>Cut and Analyze>Set Scale commands +to not set the 'changes' flag. +
    • Thanks to Stein Rorvik, fixed a bug that caused the Overlay.drawString +macro function to ignore the justification set by the setJustification() function. +
    • Thanks to Stein Rorvik, fixed a bug that caused output of the +Overlay.drawString() function to be lost when text is added using +Overlay.addSelection(). +
    • Thanks to Lorenzo Cangiono, fixed a bug that caused selections +pasted into images to not be saved. +
    • Thanks to Mahmoudi Sidi Ahmed, fixed a bug that caused the +Analyze>Calibrate command to not work with signed 16-bit images. +
    • Thanks to Menelaos Symeonides, fixed an ROI Manager bug that +caused the stack position to be ignored when measuring +renamed ROIs. +
    • Thanks to Ron DeSpain, fixed a bug that caused attempts to add to (by holding +shift key down) and subract from (by holding alt key down) elliptical +selections to fail. +
    • Thanks to Ron DeSpain, fixed a bug that caused the +Window>Tile command to overlap the "ImageJ" window +with image titles. +
    • Thanks to Pete Bankhead, fixed a bug the caused the +Process>Batch>Convert command to not work +correctly on Macs. +
    • Thanks to 'Hamish', fixed a bug that caused the roiManager("delete") macro +function to throw an exception when used in a loop. +
    • Thanks to Stein Rorvik, fixed a bug that caused one +column results tables to not be displayed correctly. +
    • Thanks to Stein Rorvik, fixed a bug that caused +File>Import>Raw to silently fail when importing a +stack and the file size was smaller than the size of a single image. +
    • Thanks to Stein Rorvik, fixed a bug that caused +the Overlay.drawString() macro function to not correctly +display right-justified text. +
    • Thanks to Stein Rorvik, fixed bugs that caused the +run("Raw...",options) and run("Image Sequence...",options) +macro functions to fail silently if the file or folder did not exist. +
    • Thanks to Stein Rorvik, fixed a bug that caused +the stack title to be incorrect after importing an image sequence +on Windows using a file path with forward slashes. +
    • Thanks to Stein Rorvik, fixed a bug that caused +File>Open Next to not correctly open a non-composite +image after having opened a composite image. +
    • Thanks to Ved Sharma, fixed an ROI Manager bug that +caused a "Rename ROI" dialog do be unexpectadly displayed +when running macro using setKeyDown("alt") and makeOval() + to create a composite selection. +
    • Thanks to Ved Sharma, fixed an ROI Manager bug that caused + an exception when saving an ROI set containing a point selection + followed by a composite selection. +
    • Thanks to Salim Kanoun, fixed a bug that caused + RoiManager.addRoi(roi) calls to be ignored if the ROI was + a duplicate. +
    • Thanks to 'TMC', fixed a bug that caused the +ROI Manager's "Interpolate ROIs" command to +throw an exception. +
    • Thanks to 'Agnieszka', fixed a bug that caused the ImagePlus.getFileInfo() +method to throw a NullPointerException. +
    • Thanks to Stein Rorvik, fixed a bug that caused the +Macros>Evaluate commands in the text editor +to not update the Window menu. +
    • Thanks to Norbert Vischer, fixed a bug that caused the macro editor +to not always abort any existing macro before running another one. +
    • Thanks to Manel Bosch, fixed a 1.52e regression that caused +image conversions to RGB to not preserve the color coding. +
    + +
  • 1.52e 11 July 2018 +
      +
    • The "Threshold" tool no longer resets the display range of 16-bit and 32-bit images. +
    • Restored the "Set" button (removed in 1.51r) to the "Threshold" dialog, +
    • Thanks to Kai Schleicher, the options string of the FolderOpener.open(dir,options) +method can now include a file name filter +(example). +
    • Added a no-argument constructor to the PointRoi class +(example at Help>Examples>JavaScript>Points). +
    • Added the Roi.size() method, equivalent to Roi.getPolygon().npoints. +
    • Added the FFT.forward(), FFT.multiply(), FFT.inverse() and FFT.filter() methods +(JavaScript example). +
    • The open("/path/to/file.avi") macro function and the IJ.openImage("/path/to/file.avi") +method now open AVI files by default as virtual stacks. +
    • Thanks to Volko Straub and Michael Schmid, fixed bugs that caused the +ImageProcessor.isInvertedLut() and ImageProcessor.isColorLut() methods +to sometimes incorrectly return 'true' with thresholded images. +
    • Thanks to Volko Straub, fixed a bug that could cause non-thresholded pixels in +16-bit and 32-bit images to be highlighted in red +(example). +
    • Thanks to Thomas Boudier and Jan Eglinger, fixed a bug that caused macro file name +parameters containing a "]" character to not be read correctly. +
    • Thanks to Michael Nonet, fixed a bug caused the median to always be set to zero +when measuring line selections. +
    • Thanks to Norbert Vischer, fixed a bug that caused errors after running the +run("RGB Stack") or run("HSB Stack") macro functions on large images when +running Java 8 on macOS. +
    • Thanks to Chris Wood and Curtis Rueden, fixed a bug that caused the +Image>Stacks>Delete Slice command to deadlock when deleting the +last channel of a hyperstack. +
    • Thanks to Stein Rorvik, fixed a bug that caused the Process>Math>NaN Background +command to throw an exception when attempting to process a non-32-bit stack. +
    • Thanks to Stein Rorvik, fixed a bug on Windows that caused text +to be pasted into text editor windows at the wrong position. +
    • Thanks to Stein Rorvik, fixed a bug that caused print("\n\n") to do +nothing instead of outputting two blank lines as expected. +
    • Thanks to 'mryellow', fixed a bug that caused polygonal ROIs with +more than 65,535 points to not be saved correctly. +
    • Thanks to Stein Rorvik, fixed a bug that caused right justified TextRois. +to not be displayed correctly if the fill color was not set. +
    • Thanks to Lorenzo Cangiano and Jan Eglinger, fixed a 1.52c regression +that caused a "Save changes?" dialog to be unexpectedly displayed when +closing an image with a multi-point selection. +
    • Thanks to Stein Rorvik, fixed a 1.52d regression that caused density +calibration to be lost when duplicating 8-bit and 16-bit images. +
    + +
  • 1.52d 11 June 2018 +
      +
    • The Image>Show Info command no longer truncates +one line slice labels to 60 charactera. +
    • Thanks to Norbert Vischer, the changeValues() macro function +can now be used to replace NaNs. +(example). +
    • Thanks to Mark Histed, when the shift key is down, the display + range is no longer changed when stepping forward/back in a stack. +
    • Thanks to Hugo, the Process>FFT command displays an error + message if the padded image is going to be 65536x65536 or larger. +
    • Thanks to Roger Leigh, the IJ.javaVersion() method works with Java 11. +
    • Added the IJ.Roi(x,y,w,h) and IJ.OvalRoi(x,y,w,h) methods. +
    • Thanks to Gregor and Michael Schmid, added the ImageProcessor.createMask() method +(example). +
    • Thanks to David Kysela, added the PointRoi.promptBeforeDeleting(boolean) method +(example). +
    • Thanks to Laurent Thomas, added the ImagesToStack.run(images) method +(example). +
    • Added the Tools.getStatistics(double[]) method. +
    • Thanks to Ximo Soriano and Michael Schmid, fixed a bug that could cause the +newImage("Name","RGB noise",width,height,1) macro function to throw an exception. +
    • Thanks to Ruben Gres, fixed a bug that caused the Overlay.duplicate() method +to not keep the 'isCalibrationBar' and 'selectable' settings. +
    • Thanks to David Schreiner, fixed bugs that caused the +File>Import>Image Sequence command to not correctly +handle image metadata longer then 60 characters. +
    • Thanks to 'yahbai', fixed bugs that caused the saveAs("jpg",path), +saveAs("gif",path) and Overlay.measure macro functions to not work +in headless mode. +
    • Thanks to 'yahbai', fixed a bug that caused the ImagePlus.deleteRoi() +method to throw an exception in headless mode. +
    • Thanks to Michael Kaul, fixed a 1.52a regression that caused +an EOFException when opening Analyze format RGB images. +
    • Thanks to Bill Christens-Barry, fixed a 1.52b regression that caused the +label set with the setMetadata("Label", label) macro function to not be displayed +in the image subtitle of single images. +
    • Thanks to Michael Cammer, fixed a 1.52c regression that caused +the Image>Crop command to not work as expected +with 16-bit images. +
    • Thanks to Peter Meijer, fixed a 1.52c regression that could cause +ClassCastExceptions when converting RGB images to stacks. +
    + +
  • 1.52c 20 May 2018 +
      +
    • Activating an ROI in an overlay created by Measure (with ‚ÄúAdd to overlay" enabled), +or by the particle analyzer, selects the corresponding row in the "Results" table. +
    • ImageJ displays a "Delete Points?" dialog before deleting a multi-point selection +with counters. +
    • Thanks to "zzyzyman", fixed a bug that caused ImageJ to fail to open odd-width RGB AVI files. +
    • Thanks to Merijn van Erp, fixed a bug that caused the CurveFitter.getSortedFitList() +method to throw an exception. +
    • Thanks to Kees Straatman and Bill Christens-Barry, fixed a bug in the macro interpreter +that caused it to not detect a missing '[' after an array variable. +
    • Thanks to Mike, fixed a 1.52b regression that caused the AVI Reader to +fail with a "Required item ‚Äò00ix‚Äô not found" error. +
    • Thanks to Curtis Rueden, fixed a 1.51t regression that caused errors in +headless mode macros to throw a HeadlessException. +
    • Thanks to 'cortig' and Curtis Rueden, fixed a 1.52b regression that caused +the "Apply" option of the Image>Adjust>Threshold command to not +behave as expected with 16 and 32 bit images. +
    + +
  • 1.52b 6 May 2018 +
      +
    • Changed "Random" to "Noise" in the "Fill with:" choice of +the File>New>Image dialog box. +
    • Thanks to Christian Evenhuis, fixed a bug with the +Edit>Selection>Line to Area command that caused +the resulting area selection to translated if the source was a polyline +or freeline selection less than 5 pixels wide. +
    • Added the IJ.javaVersion() method. +
    • Thanks to Fred Damen, fixed bugs that caused the +Image>Stacks>Tools>Concatenate command +to sometimes not work as expected. +
    • Thanks to Aryeh Weiss and Michael Schmid, worked around a +Linux problem with large images not showing at low magnification. +
    • Thanks to Fred Damen, fixed bugs that sometimes caused +slices labels of single slice stacks to be lost. +
    • Thanks to BrainPatcher and Stefan Helfrich, fixed a bug +that caused the ROI Manager's "Multi Measure" command to +not work correctly with point selections. +
    • Thanks to "zzyzyman", fixed a bug that caused ImageJ to +fail to open AVI files that used the 'indx'/'ix00' indexing schema. +
    • Thanks to Moses, fixed a 1.52a regression that caused row +labels to be omitted from tables created by the ROI Manager's +"Multi Measure" command. +
    • Thanks to Jerome Mutterer, fixed a 1.52a regression that caused +imported Results tables to be displayed without row numbers. +
    + +
  • 1.52a 23 April 2018 +
      +
    • Thanks to Michael Schmid, added Apply Macro +commands to the Edit and contextual (right click pop-up) +menus of table windows. +Also added the ResultsTable.applyMacro() method +(JavaScript example). +
    • Thanks to Ron DeSpain, tables created by the Image>Overlay>List Elements +command can be renamed "Results" and the values accessed using the getResults() +and getStringResult() macro functions. +
    • Removed the obsolete Plugins>New>Table command. Macros +that use this command will continue to work. +
    • Thanks to Michael Kaul, added an optional 5th argument +('stepSize') to the Dialog.addSlider() macro function +(example). +
    • Thanks to Gabriel Landini, on Linux, added the +‚ÄúCancel button on right‚Äù option to the Edit>Options>Appearance +dialog box. +
    • The "showRowNumbers" property of the ResultsTable class now defaults to 'false'. +
    • Thanks to Adrian Daerr, when importing ".raw" files with dimensions encoded + in the name (e.g. "myfile-640x480.raw"), the byte order is set to +big-endian if the name ends in "be.raw" and to little-endian if the name +ends in "le.raw". +
    • Thanks to Arttu Miettinen, imported ".raw" files with dimensions encoded + in the name can have a width and/or height less than 10. +
    • Thanks to Stein Rorvik, added toScaled(x,y,z) and toUnscaled(x,y,z) macro functions. +
    • Added 20 macro functions that work with tables. They operate on the +current (frontmost) table or an optional title argument can be provided (e.g., Table.size("My Table")). +Examples: Sine/Cosine Tables, +Rearrange Table and +Access Tables. +
        +
      • Table.create() - opens a new table +
      • Table.reset() - resets (clears) the table +
      • Table.size() - number of rows in the table +
      • Table.title() - title of the current table +
      • Table.headings() - column headings as a tab-delimited string +
      • Table.get(columnName, rowIndex) - returns a numeric value +
      • Table.getColumn(columnName) - returns a column as an array +
      • Table.getString(columnName, rowIndex) - returns a string value +
      • Table.set(columnName, rowIndex, value) - sets numeric or string value +
      • Table.setColumn(columnName, array) - sets an array as a column +
      • Table.update() - updates table window +
      • Table.applyMacro(macro) - applies macro code to table +
      • Table.rename(title1, title2) - renames a table +
      • Table.save(filePath) - saves the table +
      • Table.open(filePath) - opens a table +
      • Table.deleteRows(index1, index2) - deletes specified rows +
      • Table.renameColumn(oldName, newName) - renames a column +
      • Table.deleteColumn(columnName) - deletes specified column +
      • Table.showRowNumbers(boolean) - enable/disable row numbers +
      • Table.showArrays(titleAndOptions, array1, array2, ...) - displays arrays in a table (like Array.show) +
      +
    • Thanks to Trevor Joyce and A Schain, added the macro-callable RoiManager.getIndexesAsString() method. +
    • Thanks to Michael Schmid, added the Tools.copyFile(path1,path2) method. +
    • Thanks to Salim Kanoun, added the RoiManager.setRoi(roi,index) and Overlay.set(roi,index) methods. +
    • Thanks to Fred Damen, fixed a bug that caused the SubHyperstackMaker.makeSubhyperstack() +method to not work when extracting a single frame. +
    • Thanks to Fred Damen and Michael Schmid, fixed a curve fitter +bug that could cause a NullPointerException. +
    • Thanks to Norbert Vischer, fixed a bug that caused the File.copy() macro +function to not preserve the modification date. +
    • Thanks to Philippe Carl, fixed a bug that caused the Image>Hyperstacks>Reduce Dimensionality +command to not preserve the "Info" image property. +
    • Thanks to Jan Eglinger, fixed bugs that caused the roiManager("open",path) +macro function to ignore the roi name and roiManager("save",path) not +to be recorded when 'path' ended in ".roi". +
    • Thanks to Norbert Vischer, fixed a bug that caused the Image Calculator to not correctly +calculate the result when the operation is MULTIPLY or DIVIDE, the 1st image is 8 or 16 bit, +the 2nd image is 32 bit and "32-bit result" is unchecked. +
    • Thanks to Mark Rivers, fixed a bug that caused ImageJ to not +correctly open some uncompressed planar RGB TIFFs. +
    • Fixed a 1.51s regression that caused statistical calculations on +small 16-bit images or selections to be much slower. +
    • Thanks to Michael Schmid, fixed a 1.51o regression that caused the particle +analyzer to hang when processong 32-bit images.
    Home