Re: NonBlockingGenericDialog for Process>Filters

classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

Re: NonBlockingGenericDialog for Process>Filters

Curtis Rueden
Hi Philippe,

> Is there a reason why the Process>Filters are written with a
> GenericDialog and not a NonBlockingGenericDialog?

One reason is that ImageJ2's headless support currently only supports
GenericDialog, not NonBlockingGenericDialog.

Consider the following macro:

  run("Blobs (25K)");
  run("Variance...", "radius=2");
  run("Save", "save=/Users/curtis/Desktop/blobs.tif");

With ImageJ2, you can execute this headless from the command line as
follows:

  Contents/MacOS/ImageJ-macosx --headless -macro ~/Desktop/variance.ijm

Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your
platform, and "~/Desktop/variance.ijm" is the path to that macro file on
disk.

The result will be a file "/Users/curtis/Desktop/blobs.tif" with the
variance filter applied to the Blobs sample image.

If you make the change from GenericDialog to NonBlockingGenericDialog,
there will instead be an error as follows:

  java.lang.VerifyError: Bad type on operand stack
  Exception Details:
    Location:
      ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic
    Reason:
      Type 'ij/gui/NonBlockingGenericDialog' (current frame, stack[0]) is
not assignable to 'java/awt/Window'

Followed by some details to assist with debugging.

If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, work
will need to be done to improve the ImageJ Legacy component to support
NonBlockingGenericDialog in headless mode.

Regards,
Curtis

--
Curtis Rueden
LOCI software architect - https://loci.wisc.edu/software
ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden
Have you tried the Image.sc Forum? https://forum.image.sc/



On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL <[hidden email]>
wrote:

> Dear all (probably Wayne),
> Is there a reason why the Process>Filters are written with a GenericDialog
> and not a NonBlockingGenericDialog?
> Using a NonBlockingGenericDialog has the huge advantage of being able to
> make a preview on a picture with a given filter while still being able to
> zoom and drag it in order to check the effect of the filter on small
> details
> within the picture.
> And in order to do this, all that is needed to be changed is to:
>         - Add on line 5: import ij.gui.NonBlockingGenericDialog;
>         - Replace the line 112  from "GenericDialog gd = new
> GenericDialog(command+"...");"
>                                         into " NonBlockingGenericDialog gd
> =
> new NonBlockingGenericDialog (command+"...");"
> Within the "ij.plugin.filter.RankFilters.java" file.
> I thank you very much in advance for your answers.
> My best regards,
> Philippe
>
> Philippe CARL
> Laboratoire de Bioimagerie et Pathologies
> UMR 7021 CNRS - Université de Strasbourg
> Faculté de Pharmacie
> 74 route du Rhin
> 67401 ILLKIRCH
> Tel : +33(0)3 68 85 41 84
>
> --
> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html
Reply | Threaded
Open this post in threaded view
|

Re: NonBlockingGenericDialog for Process>Filters

CARL Philippe (LBP)
Dear Curtis,
Given that I never use ImageJ in headless mode, I wasn't aware of this potential issue.
Thus what would you think about adding an additional option within "Edit>Options>Misc..." which would give the possibility to choose between GenericDialog or NonBlockingGenericDialog dialogs within all the filter tools?
This could give an agreement between both functionalities.
My best regards,
Philippe

-----Message d'origine-----
De : ImageJ Interest Group [mailto:[hidden email]] De la part de Curtis Rueden
Envoyé : vendredi 22 février 2019 19:01
À : [hidden email]
Objet : Re: NonBlockingGenericDialog for Process>Filters

Hi Philippe,

> Is there a reason why the Process>Filters are written with a
> GenericDialog and not a NonBlockingGenericDialog?

One reason is that ImageJ2's headless support currently only supports
GenericDialog, not NonBlockingGenericDialog.

Consider the following macro:

  run("Blobs (25K)");
  run("Variance...", "radius=2");
  run("Save", "save=/Users/curtis/Desktop/blobs.tif");

With ImageJ2, you can execute this headless from the command line as
follows:

  Contents/MacOS/ImageJ-macosx --headless -macro ~/Desktop/variance.ijm

Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your
platform, and "~/Desktop/variance.ijm" is the path to that macro file on
disk.

The result will be a file "/Users/curtis/Desktop/blobs.tif" with the
variance filter applied to the Blobs sample image.

If you make the change from GenericDialog to NonBlockingGenericDialog,
there will instead be an error as follows:

  java.lang.VerifyError: Bad type on operand stack
  Exception Details:
    Location:
      ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic
    Reason:
      Type 'ij/gui/NonBlockingGenericDialog' (current frame, stack[0]) is
not assignable to 'java/awt/Window'

Followed by some details to assist with debugging.

If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, work
will need to be done to improve the ImageJ Legacy component to support
NonBlockingGenericDialog in headless mode.

Regards,
Curtis

--
Curtis Rueden
LOCI software architect - https://loci.wisc.edu/software
ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden
Have you tried the Image.sc Forum? https://forum.image.sc/



On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL <[hidden email]>
wrote:

> Dear all (probably Wayne),
> Is there a reason why the Process>Filters are written with a GenericDialog
> and not a NonBlockingGenericDialog?
> Using a NonBlockingGenericDialog has the huge advantage of being able to
> make a preview on a picture with a given filter while still being able to
> zoom and drag it in order to check the effect of the filter on small
> details
> within the picture.
> And in order to do this, all that is needed to be changed is to:
>         - Add on line 5: import ij.gui.NonBlockingGenericDialog;
>         - Replace the line 112  from "GenericDialog gd = new
> GenericDialog(command+"...");"
>                                         into " NonBlockingGenericDialog gd
> =
> new NonBlockingGenericDialog (command+"...");"
> Within the "ij.plugin.filter.RankFilters.java" file.
> I thank you very much in advance for your answers.
> My best regards,
> Philippe
>
> Philippe CARL
> Laboratoire de Bioimagerie et Pathologies
> UMR 7021 CNRS - Université de Strasbourg
> Faculté de Pharmacie
> 74 route du Rhin
> 67401 ILLKIRCH
> Tel : +33(0)3 68 85 41 84
>
> --
> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html
Reply | Threaded
Open this post in threaded view
|

Re: NonBlockingGenericDialog for Process>Filters

Michael Schmid-3
Hi Philippe,

well, I fear that making standard PlugInFilter dialogs non-modal could
cause more problems than just headless execution.

Unfortunately, most filters are not multithreading-safe. With a
non-modal dialog there would be nothing that prevents the user to use
the same filter on two different images at the same time, which would
result in unpredictable behavior.
I think that the RankFilters (Mean, Variance, Median, Min, Max) are fine.
Gaussian Blur, Convolve, and the 3D filters use static variables for the
filter parameters, so they must remain modal.

What I had thought about a while ago was creating a GenericDialog field
that would allow the user to pan and zoom in/out with a line of small
buttons (arrows and +/-) or scrollbars during preview. Maybe also
dragging in a small square representing the image.
Same for brightness&contrast.
I had an experimental version for B&C with scrollbars, but these were
too clumsy and then I did not find time to continue on that project. In
case you want to make something nice out of it (or someone else wants to
do this), feel free to use my experimental code pasted at the very bottom!


Michael
________________________________________________________________
On 25.02.19 11:53, Philippe CARL wrote:

> Dear Curtis,
> Given that I never use ImageJ in headless mode, I wasn't aware of this potential issue.
> Thus what would you think about adding an additional option within "Edit>Options>Misc..." which would give the possibility to choose between GenericDialog or NonBlockingGenericDialog dialogs within all the filter tools?
> This could give an agreement between both functionalities.
> My best regards,
> Philippe
>
> -----Message d'origine-----
> De : ImageJ Interest Group [mailto:[hidden email]] De la part de Curtis Rueden
> Envoyé : vendredi 22 février 2019 19:01
> À : [hidden email]
> Objet : Re: NonBlockingGenericDialog for Process>Filters
>
> Hi Philippe,
>
>> Is there a reason why the Process>Filters are written with a
>> GenericDialog and not a NonBlockingGenericDialog?
>
> One reason is that ImageJ2's headless support currently only supports
> GenericDialog, not NonBlockingGenericDialog.
>
> Consider the following macro:
>
>    run("Blobs (25K)");
>    run("Variance...", "radius=2");
>    run("Save", "save=/Users/curtis/Desktop/blobs.tif");
>
> With ImageJ2, you can execute this headless from the command line as
> follows:
>
>    Contents/MacOS/ImageJ-macosx --headless -macro ~/Desktop/variance.ijm
>
> Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your
> platform, and "~/Desktop/variance.ijm" is the path to that macro file on
> disk.
>
> The result will be a file "/Users/curtis/Desktop/blobs.tif" with the
> variance filter applied to the Blobs sample image.
>
> If you make the change from GenericDialog to NonBlockingGenericDialog,
> there will instead be an error as follows:
>
>    java.lang.VerifyError: Bad type on operand stack
>    Exception Details:
>      Location:
>        ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic
>      Reason:
>        Type 'ij/gui/NonBlockingGenericDialog' (current frame, stack[0]) is
> not assignable to 'java/awt/Window'
>
> Followed by some details to assist with debugging.
>
> If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, work
> will need to be done to improve the ImageJ Legacy component to support
> NonBlockingGenericDialog in headless mode.
>
> Regards,
> Curtis
>
> --
> Curtis Rueden
> LOCI software architect - https://loci.wisc.edu/software
> ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden
> Have you tried the Image.sc Forum? https://forum.image.sc/
>
>
>
> On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL <[hidden email]>
> wrote:
>
>> Dear all (probably Wayne),
>> Is there a reason why the Process>Filters are written with a GenericDialog
>> and not a NonBlockingGenericDialog?
>> Using a NonBlockingGenericDialog has the huge advantage of being able to
>> make a preview on a picture with a given filter while still being able to
>> zoom and drag it in order to check the effect of the filter on small
>> details
>> within the picture.
>> And in order to do this, all that is needed to be changed is to:
>>          - Add on line 5: import ij.gui.NonBlockingGenericDialog;
>>          - Replace the line 112  from "GenericDialog gd = new
>> GenericDialog(command+"...");"
>>                                          into " NonBlockingGenericDialog gd
>> =
>> new NonBlockingGenericDialog (command+"...");"
>> Within the "ij.plugin.filter.RankFilters.java" file.
>> I thank you very much in advance for your answers.
>> My best regards,
>> Philippe
>>
>> Philippe CARL
>> Laboratoire de Bioimagerie et Pathologies
>> UMR 7021 CNRS - Université de Strasbourg
____________________________________________________________________________________________________________________
import ij.*;
import ij.measure.Measurements;
import ij.process.*;
import java.awt.*;
import java.awt.event.*;


public class PreviewHelper extends Panel implements MouseListener,
AdjustmentListener {
        final static int WIDTH = 250;
        final static Dimension SB_SIZE = new Dimension(120,8);
        final static int BC_HEIGHT = 100;   // brightness/contrast area
        final static int ZS_HEIGHT = 100;   // zoom/scroll area
        final static int SB_FULL = 200;     // scrollbar steps
        final static double MIN_CONTRAST = 0.25;
        final static double MAX_CONTRAST = 128;
     final ImagePlus imp;
     final boolean hasBC;        //whether we have a Brightness&Contrast
subpanel
     private final int height;
     private Scrollbar bScrollbar, cScrollbar, sScrollbar;
     private double brightness, contrast;// 0 - SB_FULL range each
     private static double saturated;   // 0 - SB_FULL range, is remembered
     private boolean autoMode;           // saturated overrides min&max
if true
     Label rangeText;                    // text displaying min&max
     Label sLabel;                       // 'auto contrast' (saturated)
label

        public PreviewHelper(ImagePlus imp) {
         this.imp = imp;
         hasBC = imp.getType() != ImagePlus.COLOR_RGB;
         height = hasBC ? ZS_HEIGHT : BC_HEIGHT + ZS_HEIGHT;
         prepareGui();
     }

     private void prepareGui() {
                setLayout(new GridBagLayout());
                GridBagConstraints c = new GridBagConstraints();
                c.gridx = 0; c.gridy = 0;
                c.insets = new Insets(1, 1, 1, 1);
                c.fill = GridBagConstraints.HORIZONTAL;
         if (hasBC) {        // setup B&C area
                    add(new Label("Bright"),c);
                    c.gridx++;
             bScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL,
1, 0, SB_FULL+1);
             bScrollbar.setFocusable(false); // prevents blinking on Windows
             bScrollbar.setPreferredSize(SB_SIZE);
             bScrollbar.addAdjustmentListener(this);
             add(bScrollbar, c);
             c.gridx = 0; c.gridy++;
                    add(new Label("Contr"),c);
                    c.gridx++;
             cScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL,
1, 0, SB_FULL+1);
             cScrollbar.setFocusable(false); // prevents blinking on Windows
             cScrollbar.setPreferredSize(SB_SIZE);
             cScrollbar.addAdjustmentListener(this);
             add(cScrollbar, c);
             c.gridx = 0; c.gridy++;
             sLabel = new Label("Auto");
             sLabel.addMouseListener(this);
                    add(sLabel, c);
                    c.gridx++;
             sScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL,
1, 0, SB_FULL+1);
             sScrollbar.setFocusable(false); // prevents blinking on Windows
             sScrollbar.setPreferredSize(SB_SIZE);
             sScrollbar.addAdjustmentListener(this);
             add(sScrollbar, c);

             calculateBC('m', imp.getProcessor()); //set initial
scrollbar values
         }

     }

     /** Overrides Component getPreferredSize(). Added to work
      around a bug in Java 1.4.1 on Mac OS X.*/
     public Dimension getPreferredSize() {
         return new Dimension(WIDTH+1, height+1);
     }


        public void update(Graphics g) {
                paint(g);
        }

        public void paint(Graphics g) {
            super.paint(g);
            drawRect(g, sLabel.getBounds());
     }


        public void mousePressed(MouseEvent e) {}
        public void mouseReleased(MouseEvent e) {}
        public void mouseExited(MouseEvent e) {}
        public void mouseClicked(MouseEvent e) {
            Object source = e.getSource();
            if (source==sLabel) {
                autoMode = !autoMode;
                sLabel.setFont(sLabel.getFont().deriveFont(autoMode ? Font.BOLD
: Font.PLAIN));
                calculateBC('s', imp.getProcessor());
            }
        }
        public void mouseEntered(MouseEvent e) {}

        public void adjustmentValueChanged(AdjustmentEvent e) {
                Object source = e.getSource();
                if (source==bScrollbar)
                        calculateBC('b', imp.getProcessor());
                else if (source==cScrollbar)
                        calculateBC('c', imp.getProcessor());
                else if (source==sScrollbar)
                        calculateBC('s', imp.getProcessor());
                }

     /** If in 'Auto' mode, adjust brightness&contrast according to
'Auto' (saturated) scrollbar */
     public void autoAdjust(ImageProcessor ip) {
         if (autoMode) calculateBC('s', ip);
     }

     /** Calculate brightness, contrast, min, and max; set
imageProcessor and scrollbars
       * accordingly. 'changed' can be 'b'(rightness), 'c'(ontrast),
's'(aturation), 'm'(in&max)
       * If changed='m', the min and max of the imageProcessor sets B&C
scrollbars
       * Otherwise, the scrollbar values set the imageProcessor's min&max
       */
     private void calculateBC(char changed, ImageProcessor ip) {
         double fullMin = 0, fullMax = 255;
         double min = ip.getMin(), max = ip.getMax();
         if (!(ip instanceof ByteProcessor)) {
             ip.resetMinAndMax();        // calculate range of pixels
             fullMin = ip.getMin();
             fullMax = ip.getMax();
             if (changed=='m') ip.setMinAndMax(min, max); //revert to
previous min&max
         }
         double fullRange = fullMax - fullMin;
         if (fullRange <= 0) fullRange = 1e-100; //avoid division by 0
         double fullMid = 0.5*(fullMax + fullMin);
         if (changed=='m') {
             sScrollbar.setValue((int)(saturated+0.5));
         } else if (changed=='s') {
             saturated = sScrollbar.getValue();
             min = fullMin; max=fullMax;
             if (saturated != 0) {
                 ImageStatistics stats =
ImageStatistics.getStatistics(ip, Measurements.MIN_MAX, null);
                 int[] histogram = ip.getHistogram();
                 int total = 0;
                 int first = -1, last = -1;
                 for (int i=0; i<histogram.length; i++) {
                     int n = histogram[i];
                     if (n > 0) {
                         total += n;
                         if (first < 0) first = i;
                         last = i;
                     }
                 }
                 int nSaturated = (int)(total*0.2*saturated/SB_FULL);
//max 20% saturated
                 int count = 0;
                 int iMin = first;
                 for (; iMin<last; iMin++) {
                     count += histogram[iMin];
                     if (count>nSaturated) break;
                 }
                 count = 0;
                 int iMax = last;
                 for (; iMax>first; iMax--) {
                     count += histogram[iMax];
                     if (count>nSaturated) break;
                 }
                 if (ip instanceof FloatProcessor) {
                 } else {
                     min = iMin; max = iMax;
                 }
             }
IJ.log("reset. full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange));
             ip.setMinAndMax(min,max);
             imp.updateAndDraw();
         }
         if (changed=='m' || changed=='s') { //calculate and set B&C
scrollbars
             double currentMid = 0.5*(max + min);
             double currentRange = max-min;
             if (currentRange <= 0) currentRange = 1e-100;
IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange));
IJ.log("full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange));
             brightness = SB_FULL * (0.5 -
(currentMid-fullMid)/(fullRange));
             contrast = SB_FULL *
Math.log(fullRange/currentRange/MIN_CONTRAST)/Math.log(MAX_CONTRAST/MIN_CONTRAST);
             bScrollbar.setValue((int)(brightness+0.5));
             cScrollbar.setValue((int)(contrast+0.5));
IJ.log("=> b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast));
         } else if (changed=='b' || changed=='c') {
             brightness = bScrollbar.getValue();
             contrast = cScrollbar.getValue();
//IJ.log("b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast));
             double currentMid = fullMid + fullRange*(0.5 -
brightness/SB_FULL);
             double currentRange =
fullRange/(MIN_CONTRAST*Math.exp(contrast/SB_FULL*Math.log(MAX_CONTRAST/MIN_CONTRAST)));
             min = currentMid - 0.5*currentRange;
             max = currentMid + 0.5*currentRange;
//IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange));
//IJ.log("=> min.max="+IJ.d2s(min)+", "+IJ.d2s(max));
             ip.setMinAndMax(min, max);
             imp.updateAndDraw();
         }
     }

     //draw rectangle around a component
     private void drawRect(Graphics g, Rectangle r) {
         g.drawRect(r.x-1, r.y-1, r.width+1, r.height+1);
     }
} // ContrastPlot class

____________________________________________________________________________________________________________________

import ij.*;
import ij.plugin.filter.ExtendedPlugInFilter;
import ij.plugin.filter.PlugInFilterRunner;
import ij.gui.GenericDialog;
import ij.gui.DialogListener;
import ij.process.*;
import java.awt.*;
import java.awt.event.*;

//test plugin for Preview helper

/*
  * An ImageJ ExtendedPlugInFilter that changes pixels that deviate from the
  * two neighbors above&below by more than a certain value (the threshold).
  * These pixels are set to the mean of these two neighbors. Pixels at the
  * top and bottom edges are not processed.
  * The filter is useful for eliminating hot pixels of CCDs and single
  * bad lines of scanning probe images.
  *
  * It demonstrates how to write an plugin-filter with preview.
  * Michael Schmid, 2007-05-21
  */

public class  Outliers_Simple implements ExtendedPlugInFilter,
DialogListener {
     //  ExtendedPlugInFilter:   An ExtendedPlugInFilter will be invoked
by ImageJ the
        //                  following way:
     //              (1) Call to setup with the current foreground
ImagePlus (image window)
     //                  and the argument string 'arg' specified in the
plugins.config file
     //                  or a.jar file that contains it.
     //                  The PlugInFilter returns its flags.
     //              (2) If the currently active image is compatible
with the flags (and DONE
        //                  was not specified)  showDialog is called. If the
reference to the
        //                  PlugInFilterRunner passed with showDialog is
specified as an argument
        //                  to the addPreviewCheckbox method of a
GenericDialog, preview will be
        //                  possible and the run method of this
ExtendedPlugInFilter will be called
        //                  in the background for preview.
     //              (3) Unless otherwise specified in the flags (DONE
or keeping the preview
     //                  as an output for a non-stack ImagePlus), the
run method is called.
     //                  For stacks, it is called repeatedly for each slice.
     //              (4) If the FINAL_PROCESSING flag has been set (but
not DONE), the
        //                  setup method is invoked with "final" as parameter
string.
     //               ** If this PlugInFilter contains a showAbout()
method, its setup method
     //                  may be also called with argument string
"about". Then, the filter will
     //                  display a short help message.
     //
     //  DialogListener: This PlugInFilter provides the
dialogItemChanged method, whoch will
        //                  be called if required by the addDialogListener
method of the
        // GenericDialog.
        //                  For preview-enabled ExtendedPlugInFilters, the
filter parameters/options
        //                  should be set in the dialogItemChanged method.


     // F i l t e r   p a r a m e t e r s   - don't declare the
paramters actually used as static,
     //   otherwise parallel execution (in different threads with
different parameters) won't work.
     /** The threshold. Only pixels deviating by more than this value
will be replaced */
     private double threshold;
     /** For saving the threshold; here we also put the initial value */
     private static double sThreshold = 10;
        /** Whether to filter dark (instead of bright) pixels */
        private static boolean filterDark = false;
     // F u r t h e r   c l a s s   v a r i a b l e s
        /** Flags specifying the capabilities and needs of this filter. See
interfaces PlugInFilter and
      ExtendedPlugInFIlter for details */
     int flags =
DOES_ALL|SUPPORTS_MASKING|CONVERT_TO_FLOAT|SNAPSHOT|KEEP_PREVIEW|
             PARALLELIZE_STACKS;
     PreviewHelper previewHelper;

     /** Setup of the PlugInFilter. Returns the flags specifying the
capabilities and needs
      * of the filter.
      *
      * @param arg   An argument string that may be specified, e.g., in
the plugins.config file of a
      *              jar archive containing th plugin. Unused here.
      * @param imp   The ImagePlus to be processed (image with
associated window, calibration, etc.)
      * @return      Flags specifying further action of the
PlugInFilterRunner
      */
     public int setup(String arg, ImagePlus imp) {
         if (arg.equals("about")) {              //this is a special
case - we only display the "about..." info
             showAbout();
             return DONE;
                } else {
                    previewHelper = new PreviewHelper(imp);
                        return flags;
                }
     }

     /** Show the dialog asking for the parameters and get them.
      * @return Flags specifying further action of the PlugInFilterRunner
      */
     public int showDialog(ImagePlus imp, String command,
PlugInFilterRunner pfr) {
         GenericDialog gd = new GenericDialog(command+"...");
         gd.addNumericField("Threshold", sThreshold, 0); //use saved
value as default
                gd.addCheckbox("Dark Outliers", filterDark);
         gd.addPreviewCheckbox(pfr);    //passing pfr makes the filter
ready for preview
         gd.addDialogListener(this);    //the DialogItemChanged method
will be called on user input
         gd.addPanel(previewHelper);
         gd.showDialog();             //display the dialog; preview runs
in the background
         if (gd.wasCanceled())
                        return DONE;
         dialogItemChanged(gd, null);    //read the parameters finally set
         IJ.register(this.getClass());   //protect static class
variables (filter parameters) from garbage collection
         return IJ.setupDialog(imp, flags);  //ask whether to process
all slices of stack (if a stack)
     }

     /** If this PlugInFilter has been added as DialogListener, the
method dialogItemChanged
      * is invoked by GenericDialog every time the user changes
something in the dialog.
      * Time-consuming code should not be placed in this method and any
methods called.
         * @param gd A reference to the GenericDialog, needed to read the input
      * @param e An Event characterizing what has been changed in the
dialog.
      * @return Whether the dialog input is valid. If true, preview
may be invoked.
      *                  (There is no need to check the state of the
preview Checkbox here,
      *                  this is done by the PlugInFilterRunner.)
         * If false is returned (invalid input), the "OK" button of the dialog
         *                  and preview are disabled.
      */
     public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
         threshold = gd.getNextNumber();
                filterDark = gd.getNextBoolean();
         if (gd.invalidNumber() || threshold <= 0)
             return false;
         sThreshold = threshold; // save valid value for the next time
         return true;
     }

     /** This method is invoked by ImageJ for processing. For stacks, it
can be called once for
      * each slice (depending on IJ.setupDialog, where the user is asked
whether to do so, see
      * end of method setup, above).
      * If preview is enabled and the filter parameters have changed,
processing should
      * be stopped when this thread is interrupted. Thherefore, during
long calculations
         * (say, longer than a tenth of a second) the following code should be
executed repeatedly:
      * <code> if (Thread.currentThread().isInterrupted()) return; </code>
      * Since this PlugInFilter has specified the CONVERT_TO_FLOAT and
SNAPSHOT flags
      * it will be always called with a FloatProcessor that has a valid
snapshot.
      * @param ip The ImageProcessor containing the image.
      */
     public void run(ImageProcessor ip) {
         // For filters with preview, the filter parameter(s) should be
copied from the
                // class variables set in DialogItemChanged to local variables to
avoid changing
                // parameters during filtering. This can be done by calling a method with
                // the filter parameter(s) as arguments.
                // If the filter may crash with invalid or inconsistent parameters,
parameter checking
                // can be done in dialogItemChanged. In that case, both the
dialogItemChanged
                // method and copying should be done <code>synchronized</code> to
avoid using
                // invalid parameters. The filtering operation itself should not be
synchronized,
                // otherwise it would block the dialog during processing.
         doFiltering((FloatProcessor)ip, (float)threshold, filterDark);
     }

     /** Here the filtering is really done.
      * This is also the method that provides the API for use in other
plugins, it should
      * not use any class variables.
      * A static method has the advantage that unintended reading of the
filter parameters
      * from the class variables (that may change asynchronously during
preview) will be
      * flagged by the compiler as error.
      * (it cannot be static if it needs to access any class variable
such as the ImagePlus imp
      * or if we want to display a progress bar).
      *
      * This method only affects the pixels within the getRoi(ip)
rectangle. Pixels within the
      * rectangle, but outside the actual roi will be reset by ImageJ if
SUPPORTS_MASKING
      * has been added to the flags in the setup method.
      *
      * @param ip A FloatProcessor containing the image data. It must
have a valid snapshot.
      * @param threshold  Determines how much a pixel value may deviate
from the mean of the
      * neighboring pixels to remain unaltered. Threshold is in
uncalibrated units.
      * @param filterDark Whether dark outlies should be eliminated
(otherwise bright outliers are
      * eliminated)
      */
     public static void doFiltering(FloatProcessor ip, float threshold,
boolean filterDark) {
                boolean filterMax = // whether to eliminate maxima (not minima)
                                (ip.isInvertedLut()&&filterDark) || (!ip.isInvertedLut()&&!filterDark);
         Thread thread = Thread.currentThread();     // needed to check
for interrupted state
         float[] pixels = (float[])ip.getPixels();   // array of the
pixel values of the input image
         float[] snapshotPixels =
                        (float[])ip.getSnapshotPixels();    // array with an
(unaltered) copy of the  pixel values
         int width = ip.getWidth();                  // width & height
of the image
         int height = ip.getHeight();
         Rectangle roi = ip.getRoi();                // the rectangle
containing the roi (full image size if no roi)
                int xEnd = roi.x + roi.width; // loops run only over pixels inside
the roi
                int yStart = roi.y==0 ? 1 : roi.y; // do not process the topmost
line (line y=0)
                int yEnd = roi.y + roi.height;
                if (yEnd == height) yEnd--; // do not process the bottommost line
(line y=height-1)
                for (int y=yStart; y<yEnd; y++) {
                        if (y%100==0 && thread.isInterrupted()) return;   // from time to
time, check whether interrupted
                    for (int x=roi.x, p=y*width+x; x<xEnd; x++,p++) { // p points to
pixel (x,y) in the arrays
                            float neighbor1 = snapshotPixels[p+width];    // the neighbor at
(x, y+1)
                                float neighbor2 = snapshotPixels[p-width];    // the neighbor at (x,
y-1)
                                if ((filterMax && snapshotPixels[p]>neighbor1+threshold &&
snapshotPixels[p]>neighbor2+threshold) ||
                                        (!filterMax && snapshotPixels[p]<neighbor1-threshold &&
snapshotPixels[p]<neighbor2-threshold))
                                        pixels[p] = (neighbor1+neighbor2)/2;
                        }
                }
     }

        /** This method required by the ExtendedPlugInFilter interface is not
used here.
         *  It specifies the number of calls to the run(ip) method.
         *  For filters displaying a progress bar, nPasses is needed to display a
         *  smooth progress bar when processing stacks. */
        public void setNPasses (int nPasses) {}

     /** Show the "About..." text.
      *  This method must be named "showAbout", then the plugin is added
to the
         *  Help>About Plugins menu
      */
     private void showAbout() {
                IJ.showMessage("About Outliers...",
                "This plug-in filter alters pixels that deviate from their \n"+
                "neighbors above and below by more than a given threshold. \n" +
                "These outliers are set to the mean of these neighbors. \n" +
                "Edge pixels at the top and bottom of the image are not \n" +
                "processed."
                );
     }
}

____________________________________________________________________________________________________________________

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html
Reply | Threaded
Open this post in threaded view
|

Re: NonBlockingGenericDialog for Process>Filters

CARL Philippe (LBP)
Gruss Gott Michael,
I made as well a GenericDialog to NonBlockingGenericDialog conversion onto the GaussianBlur.java file, replaced the compiled GaussianBlur.class within the IJ.jar and then made all crazy things I could think of which could be problematic having static variables.
More precisely, I opened two pictures, launched a Gaussian blurr on one of it, activate the preview button and then selected the other one, applied a Contras&Brightness update and then modified the Gaussian Blurr filter values and the modifications were only applied on the picture on which the filter had been launched.
Alternatively, I launched a Gaussian blur on one picture, activated the preview button, than opened another one, played with it, and finally modified the filter values and the updates were on the picture on which the filter had at first be applied.
So which issues could up to you be found on some filters due to static variables?
I compiled as well the codes you had as attachment, but wasn't able to see any special zoom and drag buttons (unless I missed something) when I launched them.
Thanks a lot on advance for your very lightened answer.
Kindest regards,
Philippe

-----Message d'origine-----
De : ImageJ Interest Group [mailto:[hidden email]] De la part de Michael Schmid
Envoyé : lundi 25 février 2019 13:17
À : [hidden email]
Objet : Re: NonBlockingGenericDialog for Process>Filters

Hi Philippe,

well, I fear that making standard PlugInFilter dialogs non-modal could
cause more problems than just headless execution.

Unfortunately, most filters are not multithreading-safe. With a
non-modal dialog there would be nothing that prevents the user to use
the same filter on two different images at the same time, which would
result in unpredictable behavior.
I think that the RankFilters (Mean, Variance, Median, Min, Max) are fine.
Gaussian Blur, Convolve, and the 3D filters use static variables for the
filter parameters, so they must remain modal.

What I had thought about a while ago was creating a GenericDialog field
that would allow the user to pan and zoom in/out with a line of small
buttons (arrows and +/-) or scrollbars during preview. Maybe also
dragging in a small square representing the image.
Same for brightness&contrast.
I had an experimental version for B&C with scrollbars, but these were
too clumsy and then I did not find time to continue on that project. In
case you want to make something nice out of it (or someone else wants to
do this), feel free to use my experimental code pasted at the very bottom!


Michael
________________________________________________________________
On 25.02.19 11:53, Philippe CARL wrote:

> Dear Curtis,
> Given that I never use ImageJ in headless mode, I wasn't aware of this potential issue.
> Thus what would you think about adding an additional option within "Edit>Options>Misc..." which would give the possibility to choose between GenericDialog or NonBlockingGenericDialog dialogs within all the filter tools?
> This could give an agreement between both functionalities.
> My best regards,
> Philippe
>
> -----Message d'origine-----
> De : ImageJ Interest Group [mailto:[hidden email]] De la part de Curtis Rueden
> Envoyé : vendredi 22 février 2019 19:01
> À : [hidden email]
> Objet : Re: NonBlockingGenericDialog for Process>Filters
>
> Hi Philippe,
>
>> Is there a reason why the Process>Filters are written with a
>> GenericDialog and not a NonBlockingGenericDialog?
>
> One reason is that ImageJ2's headless support currently only supports
> GenericDialog, not NonBlockingGenericDialog.
>
> Consider the following macro:
>
>    run("Blobs (25K)");
>    run("Variance...", "radius=2");
>    run("Save", "save=/Users/curtis/Desktop/blobs.tif");
>
> With ImageJ2, you can execute this headless from the command line as
> follows:
>
>    Contents/MacOS/ImageJ-macosx --headless -macro ~/Desktop/variance.ijm
>
> Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your
> platform, and "~/Desktop/variance.ijm" is the path to that macro file on
> disk.
>
> The result will be a file "/Users/curtis/Desktop/blobs.tif" with the
> variance filter applied to the Blobs sample image.
>
> If you make the change from GenericDialog to NonBlockingGenericDialog,
> there will instead be an error as follows:
>
>    java.lang.VerifyError: Bad type on operand stack
>    Exception Details:
>      Location:
>        ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic
>      Reason:
>        Type 'ij/gui/NonBlockingGenericDialog' (current frame, stack[0]) is
> not assignable to 'java/awt/Window'
>
> Followed by some details to assist with debugging.
>
> If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, work
> will need to be done to improve the ImageJ Legacy component to support
> NonBlockingGenericDialog in headless mode.
>
> Regards,
> Curtis
>
> --
> Curtis Rueden
> LOCI software architect - https://loci.wisc.edu/software
> ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden
> Have you tried the Image.sc Forum? https://forum.image.sc/
>
>
>
> On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL <[hidden email]>
> wrote:
>
>> Dear all (probably Wayne),
>> Is there a reason why the Process>Filters are written with a GenericDialog
>> and not a NonBlockingGenericDialog?
>> Using a NonBlockingGenericDialog has the huge advantage of being able to
>> make a preview on a picture with a given filter while still being able to
>> zoom and drag it in order to check the effect of the filter on small
>> details
>> within the picture.
>> And in order to do this, all that is needed to be changed is to:
>>          - Add on line 5: import ij.gui.NonBlockingGenericDialog;
>>          - Replace the line 112  from "GenericDialog gd = new
>> GenericDialog(command+"...");"
>>                                          into " NonBlockingGenericDialog gd
>> =
>> new NonBlockingGenericDialog (command+"...");"
>> Within the "ij.plugin.filter.RankFilters.java" file.
>> I thank you very much in advance for your answers.
>> My best regards,
>> Philippe
>>
>> Philippe CARL
>> Laboratoire de Bioimagerie et Pathologies
>> UMR 7021 CNRS - Université de Strasbourg
____________________________________________________________________________________________________________________
import ij.*;
import ij.measure.Measurements;
import ij.process.*;
import java.awt.*;
import java.awt.event.*;


public class PreviewHelper extends Panel implements MouseListener,
AdjustmentListener {
        final static int WIDTH = 250;
        final static Dimension SB_SIZE = new Dimension(120,8);
        final static int BC_HEIGHT = 100;   // brightness/contrast area
        final static int ZS_HEIGHT = 100;   // zoom/scroll area
        final static int SB_FULL = 200;     // scrollbar steps
        final static double MIN_CONTRAST = 0.25;
        final static double MAX_CONTRAST = 128;
     final ImagePlus imp;
     final boolean hasBC;        //whether we have a Brightness&Contrast
subpanel
     private final int height;
     private Scrollbar bScrollbar, cScrollbar, sScrollbar;
     private double brightness, contrast;// 0 - SB_FULL range each
     private static double saturated;   // 0 - SB_FULL range, is remembered
     private boolean autoMode;           // saturated overrides min&max
if true
     Label rangeText;                    // text displaying min&max
     Label sLabel;                       // 'auto contrast' (saturated)
label

        public PreviewHelper(ImagePlus imp) {
         this.imp = imp;
         hasBC = imp.getType() != ImagePlus.COLOR_RGB;
         height = hasBC ? ZS_HEIGHT : BC_HEIGHT + ZS_HEIGHT;
         prepareGui();
     }

     private void prepareGui() {
                setLayout(new GridBagLayout());
                GridBagConstraints c = new GridBagConstraints();
                c.gridx = 0; c.gridy = 0;
                c.insets = new Insets(1, 1, 1, 1);
                c.fill = GridBagConstraints.HORIZONTAL;
         if (hasBC) {        // setup B&C area
                    add(new Label("Bright"),c);
                    c.gridx++;
             bScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL,
1, 0, SB_FULL+1);
             bScrollbar.setFocusable(false); // prevents blinking on Windows
             bScrollbar.setPreferredSize(SB_SIZE);
             bScrollbar.addAdjustmentListener(this);
             add(bScrollbar, c);
             c.gridx = 0; c.gridy++;
                    add(new Label("Contr"),c);
                    c.gridx++;
             cScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL,
1, 0, SB_FULL+1);
             cScrollbar.setFocusable(false); // prevents blinking on Windows
             cScrollbar.setPreferredSize(SB_SIZE);
             cScrollbar.addAdjustmentListener(this);
             add(cScrollbar, c);
             c.gridx = 0; c.gridy++;
             sLabel = new Label("Auto");
             sLabel.addMouseListener(this);
                    add(sLabel, c);
                    c.gridx++;
             sScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL,
1, 0, SB_FULL+1);
             sScrollbar.setFocusable(false); // prevents blinking on Windows
             sScrollbar.setPreferredSize(SB_SIZE);
             sScrollbar.addAdjustmentListener(this);
             add(sScrollbar, c);

             calculateBC('m', imp.getProcessor()); //set initial
scrollbar values
         }

     }

     /** Overrides Component getPreferredSize(). Added to work
      around a bug in Java 1.4.1 on Mac OS X.*/
     public Dimension getPreferredSize() {
         return new Dimension(WIDTH+1, height+1);
     }


        public void update(Graphics g) {
                paint(g);
        }

        public void paint(Graphics g) {
            super.paint(g);
            drawRect(g, sLabel.getBounds());
     }


        public void mousePressed(MouseEvent e) {}
        public void mouseReleased(MouseEvent e) {}
        public void mouseExited(MouseEvent e) {}
        public void mouseClicked(MouseEvent e) {
            Object source = e.getSource();
            if (source==sLabel) {
                autoMode = !autoMode;
                sLabel.setFont(sLabel.getFont().deriveFont(autoMode ? Font.BOLD
: Font.PLAIN));
                calculateBC('s', imp.getProcessor());
            }
        }
        public void mouseEntered(MouseEvent e) {}

        public void adjustmentValueChanged(AdjustmentEvent e) {
                Object source = e.getSource();
                if (source==bScrollbar)
                        calculateBC('b', imp.getProcessor());
                else if (source==cScrollbar)
                        calculateBC('c', imp.getProcessor());
                else if (source==sScrollbar)
                        calculateBC('s', imp.getProcessor());
                }

     /** If in 'Auto' mode, adjust brightness&contrast according to
'Auto' (saturated) scrollbar */
     public void autoAdjust(ImageProcessor ip) {
         if (autoMode) calculateBC('s', ip);
     }

     /** Calculate brightness, contrast, min, and max; set
imageProcessor and scrollbars
       * accordingly. 'changed' can be 'b'(rightness), 'c'(ontrast),
's'(aturation), 'm'(in&max)
       * If changed='m', the min and max of the imageProcessor sets B&C
scrollbars
       * Otherwise, the scrollbar values set the imageProcessor's min&max
       */
     private void calculateBC(char changed, ImageProcessor ip) {
         double fullMin = 0, fullMax = 255;
         double min = ip.getMin(), max = ip.getMax();
         if (!(ip instanceof ByteProcessor)) {
             ip.resetMinAndMax();        // calculate range of pixels
             fullMin = ip.getMin();
             fullMax = ip.getMax();
             if (changed=='m') ip.setMinAndMax(min, max); //revert to
previous min&max
         }
         double fullRange = fullMax - fullMin;
         if (fullRange <= 0) fullRange = 1e-100; //avoid division by 0
         double fullMid = 0.5*(fullMax + fullMin);
         if (changed=='m') {
             sScrollbar.setValue((int)(saturated+0.5));
         } else if (changed=='s') {
             saturated = sScrollbar.getValue();
             min = fullMin; max=fullMax;
             if (saturated != 0) {
                 ImageStatistics stats =
ImageStatistics.getStatistics(ip, Measurements.MIN_MAX, null);
                 int[] histogram = ip.getHistogram();
                 int total = 0;
                 int first = -1, last = -1;
                 for (int i=0; i<histogram.length; i++) {
                     int n = histogram[i];
                     if (n > 0) {
                         total += n;
                         if (first < 0) first = i;
                         last = i;
                     }
                 }
                 int nSaturated = (int)(total*0.2*saturated/SB_FULL);
//max 20% saturated
                 int count = 0;
                 int iMin = first;
                 for (; iMin<last; iMin++) {
                     count += histogram[iMin];
                     if (count>nSaturated) break;
                 }
                 count = 0;
                 int iMax = last;
                 for (; iMax>first; iMax--) {
                     count += histogram[iMax];
                     if (count>nSaturated) break;
                 }
                 if (ip instanceof FloatProcessor) {
                 } else {
                     min = iMin; max = iMax;
                 }
             }
IJ.log("reset. full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange));
             ip.setMinAndMax(min,max);
             imp.updateAndDraw();
         }
         if (changed=='m' || changed=='s') { //calculate and set B&C
scrollbars
             double currentMid = 0.5*(max + min);
             double currentRange = max-min;
             if (currentRange <= 0) currentRange = 1e-100;
IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange));
IJ.log("full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange));
             brightness = SB_FULL * (0.5 -
(currentMid-fullMid)/(fullRange));
             contrast = SB_FULL *
Math.log(fullRange/currentRange/MIN_CONTRAST)/Math.log(MAX_CONTRAST/MIN_CONTRAST);
             bScrollbar.setValue((int)(brightness+0.5));
             cScrollbar.setValue((int)(contrast+0.5));
IJ.log("=> b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast));
         } else if (changed=='b' || changed=='c') {
             brightness = bScrollbar.getValue();
             contrast = cScrollbar.getValue();
//IJ.log("b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast));
             double currentMid = fullMid + fullRange*(0.5 -
brightness/SB_FULL);
             double currentRange =
fullRange/(MIN_CONTRAST*Math.exp(contrast/SB_FULL*Math.log(MAX_CONTRAST/MIN_CONTRAST)));
             min = currentMid - 0.5*currentRange;
             max = currentMid + 0.5*currentRange;
//IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange));
//IJ.log("=> min.max="+IJ.d2s(min)+", "+IJ.d2s(max));
             ip.setMinAndMax(min, max);
             imp.updateAndDraw();
         }
     }

     //draw rectangle around a component
     private void drawRect(Graphics g, Rectangle r) {
         g.drawRect(r.x-1, r.y-1, r.width+1, r.height+1);
     }
} // ContrastPlot class

____________________________________________________________________________________________________________________

import ij.*;
import ij.plugin.filter.ExtendedPlugInFilter;
import ij.plugin.filter.PlugInFilterRunner;
import ij.gui.GenericDialog;
import ij.gui.DialogListener;
import ij.process.*;
import java.awt.*;
import java.awt.event.*;

//test plugin for Preview helper

/*
  * An ImageJ ExtendedPlugInFilter that changes pixels that deviate from the
  * two neighbors above&below by more than a certain value (the threshold).
  * These pixels are set to the mean of these two neighbors. Pixels at the
  * top and bottom edges are not processed.
  * The filter is useful for eliminating hot pixels of CCDs and single
  * bad lines of scanning probe images.
  *
  * It demonstrates how to write an plugin-filter with preview.
  * Michael Schmid, 2007-05-21
  */

public class  Outliers_Simple implements ExtendedPlugInFilter,
DialogListener {
     //  ExtendedPlugInFilter:   An ExtendedPlugInFilter will be invoked
by ImageJ the
        //                  following way:
     //              (1) Call to setup with the current foreground
ImagePlus (image window)
     //                  and the argument string 'arg' specified in the
plugins.config file
     //                  or a.jar file that contains it.
     //                  The PlugInFilter returns its flags.
     //              (2) If the currently active image is compatible
with the flags (and DONE
        //                  was not specified)  showDialog is called. If the
reference to the
        //                  PlugInFilterRunner passed with showDialog is
specified as an argument
        //                  to the addPreviewCheckbox method of a
GenericDialog, preview will be
        //                  possible and the run method of this
ExtendedPlugInFilter will be called
        //                  in the background for preview.
     //              (3) Unless otherwise specified in the flags (DONE
or keeping the preview
     //                  as an output for a non-stack ImagePlus), the
run method is called.
     //                  For stacks, it is called repeatedly for each slice.
     //              (4) If the FINAL_PROCESSING flag has been set (but
not DONE), the
        //                  setup method is invoked with "final" as parameter
string.
     //               ** If this PlugInFilter contains a showAbout()
method, its setup method
     //                  may be also called with argument string
"about". Then, the filter will
     //                  display a short help message.
     //
     //  DialogListener: This PlugInFilter provides the
dialogItemChanged method, whoch will
        //                  be called if required by the addDialogListener
method of the
        // GenericDialog.
        //                  For preview-enabled ExtendedPlugInFilters, the
filter parameters/options
        //                  should be set in the dialogItemChanged method.


     // F i l t e r   p a r a m e t e r s   - don't declare the
paramters actually used as static,
     //   otherwise parallel execution (in different threads with
different parameters) won't work.
     /** The threshold. Only pixels deviating by more than this value
will be replaced */
     private double threshold;
     /** For saving the threshold; here we also put the initial value */
     private static double sThreshold = 10;
        /** Whether to filter dark (instead of bright) pixels */
        private static boolean filterDark = false;
     // F u r t h e r   c l a s s   v a r i a b l e s
        /** Flags specifying the capabilities and needs of this filter. See
interfaces PlugInFilter and
      ExtendedPlugInFIlter for details */
     int flags =
DOES_ALL|SUPPORTS_MASKING|CONVERT_TO_FLOAT|SNAPSHOT|KEEP_PREVIEW|
             PARALLELIZE_STACKS;
     PreviewHelper previewHelper;

     /** Setup of the PlugInFilter. Returns the flags specifying the
capabilities and needs
      * of the filter.
      *
      * @param arg   An argument string that may be specified, e.g., in
the plugins.config file of a
      *              jar archive containing th plugin. Unused here.
      * @param imp   The ImagePlus to be processed (image with
associated window, calibration, etc.)
      * @return      Flags specifying further action of the
PlugInFilterRunner
      */
     public int setup(String arg, ImagePlus imp) {
         if (arg.equals("about")) {              //this is a special
case - we only display the "about..." info
             showAbout();
             return DONE;
                } else {
                    previewHelper = new PreviewHelper(imp);
                        return flags;
                }
     }

     /** Show the dialog asking for the parameters and get them.
      * @return Flags specifying further action of the PlugInFilterRunner
      */
     public int showDialog(ImagePlus imp, String command,
PlugInFilterRunner pfr) {
         GenericDialog gd = new GenericDialog(command+"...");
         gd.addNumericField("Threshold", sThreshold, 0); //use saved
value as default
                gd.addCheckbox("Dark Outliers", filterDark);
         gd.addPreviewCheckbox(pfr);    //passing pfr makes the filter
ready for preview
         gd.addDialogListener(this);    //the DialogItemChanged method
will be called on user input
         gd.addPanel(previewHelper);
         gd.showDialog();             //display the dialog; preview runs
in the background
         if (gd.wasCanceled())
                        return DONE;
         dialogItemChanged(gd, null);    //read the parameters finally set
         IJ.register(this.getClass());   //protect static class
variables (filter parameters) from garbage collection
         return IJ.setupDialog(imp, flags);  //ask whether to process
all slices of stack (if a stack)
     }

     /** If this PlugInFilter has been added as DialogListener, the
method dialogItemChanged
      * is invoked by GenericDialog every time the user changes
something in the dialog.
      * Time-consuming code should not be placed in this method and any
methods called.
         * @param gd A reference to the GenericDialog, needed to read the input
      * @param e An Event characterizing what has been changed in the
dialog.
      * @return Whether the dialog input is valid. If true, preview
may be invoked.
      *                  (There is no need to check the state of the
preview Checkbox here,
      *                  this is done by the PlugInFilterRunner.)
         * If false is returned (invalid input), the "OK" button of the dialog
         *                  and preview are disabled.
      */
     public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
         threshold = gd.getNextNumber();
                filterDark = gd.getNextBoolean();
         if (gd.invalidNumber() || threshold <= 0)
             return false;
         sThreshold = threshold; // save valid value for the next time
         return true;
     }

     /** This method is invoked by ImageJ for processing. For stacks, it
can be called once for
      * each slice (depending on IJ.setupDialog, where the user is asked
whether to do so, see
      * end of method setup, above).
      * If preview is enabled and the filter parameters have changed,
processing should
      * be stopped when this thread is interrupted. Thherefore, during
long calculations
         * (say, longer than a tenth of a second) the following code should be
executed repeatedly:
      * <code> if (Thread.currentThread().isInterrupted()) return; </code>
      * Since this PlugInFilter has specified the CONVERT_TO_FLOAT and
SNAPSHOT flags
      * it will be always called with a FloatProcessor that has a valid
snapshot.
      * @param ip The ImageProcessor containing the image.
      */
     public void run(ImageProcessor ip) {
         // For filters with preview, the filter parameter(s) should be
copied from the
                // class variables set in DialogItemChanged to local variables to
avoid changing
                // parameters during filtering. This can be done by calling a method with
                // the filter parameter(s) as arguments.
                // If the filter may crash with invalid or inconsistent parameters,
parameter checking
                // can be done in dialogItemChanged. In that case, both the
dialogItemChanged
                // method and copying should be done <code>synchronized</code> to
avoid using
                // invalid parameters. The filtering operation itself should not be
synchronized,
                // otherwise it would block the dialog during processing.
         doFiltering((FloatProcessor)ip, (float)threshold, filterDark);
     }

     /** Here the filtering is really done.
      * This is also the method that provides the API for use in other
plugins, it should
      * not use any class variables.
      * A static method has the advantage that unintended reading of the
filter parameters
      * from the class variables (that may change asynchronously during
preview) will be
      * flagged by the compiler as error.
      * (it cannot be static if it needs to access any class variable
such as the ImagePlus imp
      * or if we want to display a progress bar).
      *
      * This method only affects the pixels within the getRoi(ip)
rectangle. Pixels within the
      * rectangle, but outside the actual roi will be reset by ImageJ if
SUPPORTS_MASKING
      * has been added to the flags in the setup method.
      *
      * @param ip A FloatProcessor containing the image data. It must
have a valid snapshot.
      * @param threshold  Determines how much a pixel value may deviate
from the mean of the
      * neighboring pixels to remain unaltered. Threshold is in
uncalibrated units.
      * @param filterDark Whether dark outlies should be eliminated
(otherwise bright outliers are
      * eliminated)
      */
     public static void doFiltering(FloatProcessor ip, float threshold,
boolean filterDark) {
                boolean filterMax = // whether to eliminate maxima (not minima)
                                (ip.isInvertedLut()&&filterDark) || (!ip.isInvertedLut()&&!filterDark);
         Thread thread = Thread.currentThread();     // needed to check
for interrupted state
         float[] pixels = (float[])ip.getPixels();   // array of the
pixel values of the input image
         float[] snapshotPixels =
                        (float[])ip.getSnapshotPixels();    // array with an
(unaltered) copy of the  pixel values
         int width = ip.getWidth();                  // width & height
of the image
         int height = ip.getHeight();
         Rectangle roi = ip.getRoi();                // the rectangle
containing the roi (full image size if no roi)
                int xEnd = roi.x + roi.width; // loops run only over pixels inside
the roi
                int yStart = roi.y==0 ? 1 : roi.y; // do not process the topmost
line (line y=0)
                int yEnd = roi.y + roi.height;
                if (yEnd == height) yEnd--; // do not process the bottommost line
(line y=height-1)
                for (int y=yStart; y<yEnd; y++) {
                        if (y%100==0 && thread.isInterrupted()) return;   // from time to
time, check whether interrupted
                    for (int x=roi.x, p=y*width+x; x<xEnd; x++,p++) { // p points to
pixel (x,y) in the arrays
                            float neighbor1 = snapshotPixels[p+width];    // the neighbor at
(x, y+1)
                                float neighbor2 = snapshotPixels[p-width];    // the neighbor at (x,
y-1)
                                if ((filterMax && snapshotPixels[p]>neighbor1+threshold &&
snapshotPixels[p]>neighbor2+threshold) ||
                                        (!filterMax && snapshotPixels[p]<neighbor1-threshold &&
snapshotPixels[p]<neighbor2-threshold))
                                        pixels[p] = (neighbor1+neighbor2)/2;
                        }
                }
     }

        /** This method required by the ExtendedPlugInFilter interface is not
used here.
         *  It specifies the number of calls to the run(ip) method.
         *  For filters displaying a progress bar, nPasses is needed to display a
         *  smooth progress bar when processing stacks. */
        public void setNPasses (int nPasses) {}

     /** Show the "About..." text.
      *  This method must be named "showAbout", then the plugin is added
to the
         *  Help>About Plugins menu
      */
     private void showAbout() {
                IJ.showMessage("About Outliers...",
                "This plug-in filter alters pixels that deviate from their \n"+
                "neighbors above and below by more than a given threshold. \n" +
                "These outliers are set to the mean of these neighbors. \n" +
                "Edge pixels at the top and bottom of the image are not \n" +
                "processed."
                );
     }
}

____________________________________________________________________________________________________________________

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html
Reply | Threaded
Open this post in threaded view
|

Re: NonBlockingGenericDialog for Process>Filters

Michael Schmid-3
Hi Philippe,

a typical case of two Gaussian Blurs interfering would be the following:

Have one Gaussian Blur of a stack running (a large stack so it take some
time) and at the same time do a preview of another GaussianBlur with a
different radius (sigma). Then, as soon as the preview starts, the
remaining slices of the stack will be processed with the sigma of the
preview, not the one that should be used for the stack.

In principle, you can do the same thing already now (I just tried with a
1024x1024x1024 stack that needs about 15-20 seconds for sigma=7, and
quickly started another GaussianBlur on another image with different
sigma before the stack was fully processed - the last slices stack got
blurred with sigma intended for the other image). Fortunately, it is
rather unlikely that this happens in practice.

It is much more likely that this kind of problem occurs if one can have
several GaussianBlur dialogs open at one time, e.g., to compare how it
affects different images. Then it would be much more likely that
GaussianBlur runs on two images/stacks at the same moment.

Of course, the clean solution would be making the class variables for
the parameters non-static, and adding the static ones for saving the
parameters, in GaussianBlur, UnsharpMask, Convolver, and ImageMath (the
BackgroundSubtracter & RankFilters seem to be fine). In principle, it
would be easy, but it has to be done.

Then, we still have to care about the headless mode...

--
Experimental test plugin: There should be three additional sliders
(rather narrow ones) for adjusting B&C, but only for grayscale images
(not for RGB).


Michael
________________________________________________________________
On 25.02.19 15:41, Philippe CARL wrote:

> Gruss Gott Michael,
> I made as well a GenericDialog to NonBlockingGenericDialog conversion onto the GaussianBlur.java file, replaced the compiled GaussianBlur.class within the IJ.jar and then made all crazy things I could think of which could be problematic having static variables.
> More precisely, I opened two pictures, launched a Gaussian blurr on one of it, activate the preview button and then selected the other one, applied a Contras&Brightness update and then modified the Gaussian Blurr filter values and the modifications were only applied on the picture on which the filter had been launched.
> Alternatively, I launched a Gaussian blur on one picture, activated the preview button, than opened another one, played with it, and finally modified the filter values and the updates were on the picture on which the filter had at first be applied.
> So which issues could up to you be found on some filters due to static variables?
> I compiled as well the codes you had as attachment, but wasn't able to see any special zoom and drag buttons (unless I missed something) when I launched them.
> Thanks a lot on advance for your very lightened answer.
> Kindest regards,
> Philippe
>
> -----Message d'origine-----
> De : ImageJ Interest Group [mailto:[hidden email]] De la part de Michael Schmid
> Envoyé : lundi 25 février 2019 13:17
> À : [hidden email]
> Objet : Re: NonBlockingGenericDialog for Process>Filters
>
> Hi Philippe,
>
> well, I fear that making standard PlugInFilter dialogs non-modal could
> cause more problems than just headless execution.
>
> Unfortunately, most filters are not multithreading-safe. With a
> non-modal dialog there would be nothing that prevents the user to use
> the same filter on two different images at the same time, which would
> result in unpredictable behavior.
> I think that the RankFilters (Mean, Variance, Median, Min, Max) are fine.
> Gaussian Blur, Convolve, and the 3D filters use static variables for the
> filter parameters, so they must remain modal.
>
> What I had thought about a while ago was creating a GenericDialog field
> that would allow the user to pan and zoom in/out with a line of small
> buttons (arrows and +/-) or scrollbars during preview. Maybe also
> dragging in a small square representing the image.
> Same for brightness&contrast.
> I had an experimental version for B&C with scrollbars, but these were
> too clumsy and then I did not find time to continue on that project. In
> case you want to make something nice out of it (or someone else wants to
> do this), feel free to use my experimental code pasted at the very bottom!
>
>
> Michael
> ________________________________________________________________
> On 25.02.19 11:53, Philippe CARL wrote:
>> Dear Curtis,
>> Given that I never use ImageJ in headless mode, I wasn't aware of this potential issue.
>> Thus what would you think about adding an additional option within "Edit>Options>Misc..." which would give the possibility to choose between GenericDialog or NonBlockingGenericDialog dialogs within all the filter tools?
>> This could give an agreement between both functionalities.
>> My best regards,
>> Philippe
>>
>> -----Message d'origine-----
>> De : ImageJ Interest Group [mailto:[hidden email]] De la part de Curtis Rueden
>> Envoyé : vendredi 22 février 2019 19:01
>> À : [hidden email]
>> Objet : Re: NonBlockingGenericDialog for Process>Filters
>>
>> Hi Philippe,
>>
>>> Is there a reason why the Process>Filters are written with a
>>> GenericDialog and not a NonBlockingGenericDialog?
>>
>> One reason is that ImageJ2's headless support currently only supports
>> GenericDialog, not NonBlockingGenericDialog.
>>
>> Consider the following macro:
>>
>>     run("Blobs (25K)");
>>     run("Variance...", "radius=2");
>>     run("Save", "save=/Users/curtis/Desktop/blobs.tif");
>>
>> With ImageJ2, you can execute this headless from the command line as
>> follows:
>>
>>     Contents/MacOS/ImageJ-macosx --headless -macro ~/Desktop/variance.ijm
>>
>> Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your
>> platform, and "~/Desktop/variance.ijm" is the path to that macro file on
>> disk.
>>
>> The result will be a file "/Users/curtis/Desktop/blobs.tif" with the
>> variance filter applied to the Blobs sample image.
>>
>> If you make the change from GenericDialog to NonBlockingGenericDialog,
>> there will instead be an error as follows:
>>
>>     java.lang.VerifyError: Bad type on operand stack
>>     Exception Details:
>>       Location:
>>         ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic
>>       Reason:
>>         Type 'ij/gui/NonBlockingGenericDialog' (current frame, stack[0]) is
>> not assignable to 'java/awt/Window'
>>
>> Followed by some details to assist with debugging.
>>
>> If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, work
>> will need to be done to improve the ImageJ Legacy component to support
>> NonBlockingGenericDialog in headless mode.
>>
>> Regards,
>> Curtis
>>
>> --
>> Curtis Rueden
>> LOCI software architect - https://loci.wisc.edu/software
>> ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden
>> Have you tried the Image.sc Forum? https://forum.image.sc/
>>
>>
>>
>> On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL <[hidden email]>
>> wrote:
>>
>>> Dear all (probably Wayne),
>>> Is there a reason why the Process>Filters are written with a GenericDialog
>>> and not a NonBlockingGenericDialog?
>>> Using a NonBlockingGenericDialog has the huge advantage of being able to
>>> make a preview on a picture with a given filter while still being able to
>>> zoom and drag it in order to check the effect of the filter on small
>>> details
>>> within the picture.
>>> And in order to do this, all that is needed to be changed is to:
>>>           - Add on line 5: import ij.gui.NonBlockingGenericDialog;
>>>           - Replace the line 112  from "GenericDialog gd = new
>>> GenericDialog(command+"...");"
>>>                                           into " NonBlockingGenericDialog gd
>>> =
>>> new NonBlockingGenericDialog (command+"...");"
>>> Within the "ij.plugin.filter.RankFilters.java" file.
>>> I thank you very much in advance for your answers.
>>> My best regards,
>>> Philippe
>>>
>>> Philippe CARL
>>> Laboratoire de Bioimagerie et Pathologies
>>> UMR 7021 CNRS - Université de Strasbourg
> ____________________________________________________________________________________________________________________
> import ij.*;
> import ij.measure.Measurements;
> import ij.process.*;
> import java.awt.*;
> import java.awt.event.*;
>
>
> public class PreviewHelper extends Panel implements MouseListener,
> AdjustmentListener {
> final static int WIDTH = 250;
> final static Dimension SB_SIZE = new Dimension(120,8);
> final static int BC_HEIGHT = 100;   // brightness/contrast area
> final static int ZS_HEIGHT = 100;   // zoom/scroll area
> final static int SB_FULL = 200;     // scrollbar steps
> final static double MIN_CONTRAST = 0.25;
> final static double MAX_CONTRAST = 128;
>       final ImagePlus imp;
>       final boolean hasBC;        //whether we have a Brightness&Contrast
> subpanel
>       private final int height;
>       private Scrollbar bScrollbar, cScrollbar, sScrollbar;
>       private double brightness, contrast;// 0 - SB_FULL range each
>       private static double saturated;   // 0 - SB_FULL range, is remembered
>       private boolean autoMode;           // saturated overrides min&max
> if true
>       Label rangeText;                    // text displaying min&max
>       Label sLabel;                       // 'auto contrast' (saturated)
> label
>
> public PreviewHelper(ImagePlus imp) {
>           this.imp = imp;
>           hasBC = imp.getType() != ImagePlus.COLOR_RGB;
>           height = hasBC ? ZS_HEIGHT : BC_HEIGHT + ZS_HEIGHT;
>           prepareGui();
>       }
>
>       private void prepareGui() {
> setLayout(new GridBagLayout());
> GridBagConstraints c = new GridBagConstraints();
> c.gridx = 0; c.gridy = 0;
> c.insets = new Insets(1, 1, 1, 1);
> c.fill = GridBagConstraints.HORIZONTAL;
>           if (hasBC) {        // setup B&C area
>    add(new Label("Bright"),c);
>    c.gridx++;
>               bScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL,
> 1, 0, SB_FULL+1);
>               bScrollbar.setFocusable(false); // prevents blinking on Windows
>               bScrollbar.setPreferredSize(SB_SIZE);
>               bScrollbar.addAdjustmentListener(this);
>               add(bScrollbar, c);
>               c.gridx = 0; c.gridy++;
>    add(new Label("Contr"),c);
>    c.gridx++;
>               cScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL,
> 1, 0, SB_FULL+1);
>               cScrollbar.setFocusable(false); // prevents blinking on Windows
>               cScrollbar.setPreferredSize(SB_SIZE);
>               cScrollbar.addAdjustmentListener(this);
>               add(cScrollbar, c);
>               c.gridx = 0; c.gridy++;
>               sLabel = new Label("Auto");
>               sLabel.addMouseListener(this);
>    add(sLabel, c);
>    c.gridx++;
>               sScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL,
> 1, 0, SB_FULL+1);
>               sScrollbar.setFocusable(false); // prevents blinking on Windows
>               sScrollbar.setPreferredSize(SB_SIZE);
>               sScrollbar.addAdjustmentListener(this);
>               add(sScrollbar, c);
>
>               calculateBC('m', imp.getProcessor()); //set initial
> scrollbar values
>           }
>
>       }
>
>       /** Overrides Component getPreferredSize(). Added to work
>       around a bug in Java 1.4.1 on Mac OS X.*/
>       public Dimension getPreferredSize() {
>           return new Dimension(WIDTH+1, height+1);
>       }
>
>
> public void update(Graphics g) {
> paint(g);
> }
>
> public void paint(Graphics g) {
>    super.paint(g);
>    drawRect(g, sLabel.getBounds());
>       }
>
>
> public void mousePressed(MouseEvent e) {}
> public void mouseReleased(MouseEvent e) {}
> public void mouseExited(MouseEvent e) {}
> public void mouseClicked(MouseEvent e) {
>    Object source = e.getSource();
>    if (source==sLabel) {
>        autoMode = !autoMode;
>        sLabel.setFont(sLabel.getFont().deriveFont(autoMode ? Font.BOLD
> : Font.PLAIN));
>        calculateBC('s', imp.getProcessor());
>    }
> }
> public void mouseEntered(MouseEvent e) {}
>
> public void adjustmentValueChanged(AdjustmentEvent e) {
> Object source = e.getSource();
> if (source==bScrollbar)
> calculateBC('b', imp.getProcessor());
> else if (source==cScrollbar)
> calculateBC('c', imp.getProcessor());
> else if (source==sScrollbar)
> calculateBC('s', imp.getProcessor());
> }
>
>       /** If in 'Auto' mode, adjust brightness&contrast according to
> 'Auto' (saturated) scrollbar */
>       public void autoAdjust(ImageProcessor ip) {
>           if (autoMode) calculateBC('s', ip);
>       }
>
>       /** Calculate brightness, contrast, min, and max; set
> imageProcessor and scrollbars
>         * accordingly. 'changed' can be 'b'(rightness), 'c'(ontrast),
> 's'(aturation), 'm'(in&max)
>         * If changed='m', the min and max of the imageProcessor sets B&C
> scrollbars
>         * Otherwise, the scrollbar values set the imageProcessor's min&max
>         */
>       private void calculateBC(char changed, ImageProcessor ip) {
>           double fullMin = 0, fullMax = 255;
>           double min = ip.getMin(), max = ip.getMax();
>           if (!(ip instanceof ByteProcessor)) {
>               ip.resetMinAndMax();        // calculate range of pixels
>               fullMin = ip.getMin();
>               fullMax = ip.getMax();
>               if (changed=='m') ip.setMinAndMax(min, max); //revert to
> previous min&max
>           }
>           double fullRange = fullMax - fullMin;
>           if (fullRange <= 0) fullRange = 1e-100; //avoid division by 0
>           double fullMid = 0.5*(fullMax + fullMin);
>           if (changed=='m') {
>               sScrollbar.setValue((int)(saturated+0.5));
>           } else if (changed=='s') {
>               saturated = sScrollbar.getValue();
>               min = fullMin; max=fullMax;
>               if (saturated != 0) {
>                   ImageStatistics stats =
> ImageStatistics.getStatistics(ip, Measurements.MIN_MAX, null);
>                   int[] histogram = ip.getHistogram();
>                   int total = 0;
>                   int first = -1, last = -1;
>                   for (int i=0; i<histogram.length; i++) {
>                       int n = histogram[i];
>                       if (n > 0) {
>                           total += n;
>                           if (first < 0) first = i;
>                           last = i;
>                       }
>                   }
>                   int nSaturated = (int)(total*0.2*saturated/SB_FULL);
> //max 20% saturated
>                   int count = 0;
>                   int iMin = first;
>                   for (; iMin<last; iMin++) {
>                       count += histogram[iMin];
>                       if (count>nSaturated) break;
>                   }
>                   count = 0;
>                   int iMax = last;
>                   for (; iMax>first; iMax--) {
>                       count += histogram[iMax];
>                       if (count>nSaturated) break;
>                   }
>                   if (ip instanceof FloatProcessor) {
>                   } else {
>                       min = iMin; max = iMax;
>                   }
>               }
> IJ.log("reset. full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange));
>               ip.setMinAndMax(min,max);
>               imp.updateAndDraw();
>           }
>           if (changed=='m' || changed=='s') { //calculate and set B&C
> scrollbars
>               double currentMid = 0.5*(max + min);
>               double currentRange = max-min;
>               if (currentRange <= 0) currentRange = 1e-100;
> IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange));
> IJ.log("full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange));
>               brightness = SB_FULL * (0.5 -
> (currentMid-fullMid)/(fullRange));
>               contrast = SB_FULL *
> Math.log(fullRange/currentRange/MIN_CONTRAST)/Math.log(MAX_CONTRAST/MIN_CONTRAST);
>               bScrollbar.setValue((int)(brightness+0.5));
>               cScrollbar.setValue((int)(contrast+0.5));
> IJ.log("=> b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast));
>           } else if (changed=='b' || changed=='c') {
>               brightness = bScrollbar.getValue();
>               contrast = cScrollbar.getValue();
> //IJ.log("b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast));
>               double currentMid = fullMid + fullRange*(0.5 -
> brightness/SB_FULL);
>               double currentRange =
> fullRange/(MIN_CONTRAST*Math.exp(contrast/SB_FULL*Math.log(MAX_CONTRAST/MIN_CONTRAST)));
>               min = currentMid - 0.5*currentRange;
>               max = currentMid + 0.5*currentRange;
> //IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange));
> //IJ.log("=> min.max="+IJ.d2s(min)+", "+IJ.d2s(max));
>               ip.setMinAndMax(min, max);
>               imp.updateAndDraw();
>           }
>       }
>
>       //draw rectangle around a component
>       private void drawRect(Graphics g, Rectangle r) {
>           g.drawRect(r.x-1, r.y-1, r.width+1, r.height+1);
>       }
> } // ContrastPlot class
>
> ____________________________________________________________________________________________________________________
>
> import ij.*;
> import ij.plugin.filter.ExtendedPlugInFilter;
> import ij.plugin.filter.PlugInFilterRunner;
> import ij.gui.GenericDialog;
> import ij.gui.DialogListener;
> import ij.process.*;
> import java.awt.*;
> import java.awt.event.*;
>
> //test plugin for Preview helper
>
> /*
>    * An ImageJ ExtendedPlugInFilter that changes pixels that deviate from the
>    * two neighbors above&below by more than a certain value (the threshold).
>    * These pixels are set to the mean of these two neighbors. Pixels at the
>    * top and bottom edges are not processed.
>    * The filter is useful for eliminating hot pixels of CCDs and single
>    * bad lines of scanning probe images.
>    *
>    * It demonstrates how to write an plugin-filter with preview.
>    * Michael Schmid, 2007-05-21
>    */
>
> public class  Outliers_Simple implements ExtendedPlugInFilter,
> DialogListener {
>       //  ExtendedPlugInFilter:   An ExtendedPlugInFilter will be invoked
> by ImageJ the
> //                  following way:
>       //              (1) Call to setup with the current foreground
> ImagePlus (image window)
>       //                  and the argument string 'arg' specified in the
> plugins.config file
>       //                  or a.jar file that contains it.
>       //                  The PlugInFilter returns its flags.
>       //              (2) If the currently active image is compatible
> with the flags (and DONE
> //                  was not specified)  showDialog is called. If the
> reference to the
> //                  PlugInFilterRunner passed with showDialog is
> specified as an argument
> //                  to the addPreviewCheckbox method of a
> GenericDialog, preview will be
> //                  possible and the run method of this
> ExtendedPlugInFilter will be called
> //                  in the background for preview.
>       //              (3) Unless otherwise specified in the flags (DONE
> or keeping the preview
>       //                  as an output for a non-stack ImagePlus), the
> run method is called.
>       //                  For stacks, it is called repeatedly for each slice.
>       //              (4) If the FINAL_PROCESSING flag has been set (but
> not DONE), the
> //                  setup method is invoked with "final" as parameter
> string.
>       //               ** If this PlugInFilter contains a showAbout()
> method, its setup method
>       //                  may be also called with argument string
> "about". Then, the filter will
>       //                  display a short help message.
>       //
>       //  DialogListener: This PlugInFilter provides the
> dialogItemChanged method, whoch will
> //                  be called if required by the addDialogListener
> method of the
> // GenericDialog.
> //                  For preview-enabled ExtendedPlugInFilters, the
> filter parameters/options
> //                  should be set in the dialogItemChanged method.
>
>
>       // F i l t e r   p a r a m e t e r s   - don't declare the
> paramters actually used as static,
>       //   otherwise parallel execution (in different threads with
> different parameters) won't work.
>       /** The threshold. Only pixels deviating by more than this value
> will be replaced */
>       private double threshold;
>       /** For saving the threshold; here we also put the initial value */
>       private static double sThreshold = 10;
> /** Whether to filter dark (instead of bright) pixels */
> private static boolean filterDark = false;
>       // F u r t h e r   c l a s s   v a r i a b l e s
> /** Flags specifying the capabilities and needs of this filter. See
> interfaces PlugInFilter and
>        ExtendedPlugInFIlter for details */
>       int flags =
> DOES_ALL|SUPPORTS_MASKING|CONVERT_TO_FLOAT|SNAPSHOT|KEEP_PREVIEW|
>               PARALLELIZE_STACKS;
>       PreviewHelper previewHelper;
>
>       /** Setup of the PlugInFilter. Returns the flags specifying the
> capabilities and needs
>        * of the filter.
>        *
>        * @param arg   An argument string that may be specified, e.g., in
> the plugins.config file of a
>        *              jar archive containing th plugin. Unused here.
>        * @param imp   The ImagePlus to be processed (image with
> associated window, calibration, etc.)
>        * @return      Flags specifying further action of the
> PlugInFilterRunner
>        */
>       public int setup(String arg, ImagePlus imp) {
>           if (arg.equals("about")) {              //this is a special
> case - we only display the "about..." info
>               showAbout();
>               return DONE;
> } else {
>    previewHelper = new PreviewHelper(imp);
> return flags;
> }
>       }
>
>       /** Show the dialog asking for the parameters and get them.
>        * @return Flags specifying further action of the PlugInFilterRunner
>        */
>       public int showDialog(ImagePlus imp, String command,
> PlugInFilterRunner pfr) {
>           GenericDialog gd = new GenericDialog(command+"...");
>           gd.addNumericField("Threshold", sThreshold, 0); //use saved
> value as default
> gd.addCheckbox("Dark Outliers", filterDark);
>           gd.addPreviewCheckbox(pfr);    //passing pfr makes the filter
> ready for preview
>           gd.addDialogListener(this);    //the DialogItemChanged method
> will be called on user input
>           gd.addPanel(previewHelper);
>           gd.showDialog();             //display the dialog; preview runs
> in the background
>           if (gd.wasCanceled())
> return DONE;
>           dialogItemChanged(gd, null);    //read the parameters finally set
>           IJ.register(this.getClass());   //protect static class
> variables (filter parameters) from garbage collection
>           return IJ.setupDialog(imp, flags);  //ask whether to process
> all slices of stack (if a stack)
>       }
>
>       /** If this PlugInFilter has been added as DialogListener, the
> method dialogItemChanged
>        * is invoked by GenericDialog every time the user changes
> something in the dialog.
>        * Time-consuming code should not be placed in this method and any
> methods called.
> * @param gd A reference to the GenericDialog, needed to read the input
>        * @param e An Event characterizing what has been changed in the
> dialog.
>        * @return Whether the dialog input is valid. If true, preview
> may be invoked.
>        *                  (There is no need to check the state of the
> preview Checkbox here,
>        *                  this is done by the PlugInFilterRunner.)
> * If false is returned (invalid input), the "OK" button of the dialog
> *                  and preview are disabled.
>        */
>       public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
>           threshold = gd.getNextNumber();
> filterDark = gd.getNextBoolean();
>           if (gd.invalidNumber() || threshold <= 0)
>               return false;
>           sThreshold = threshold; // save valid value for the next time
>           return true;
>       }
>
>       /** This method is invoked by ImageJ for processing. For stacks, it
> can be called once for
>        * each slice (depending on IJ.setupDialog, where the user is asked
> whether to do so, see
>        * end of method setup, above).
>        * If preview is enabled and the filter parameters have changed,
> processing should
>        * be stopped when this thread is interrupted. Thherefore, during
> long calculations
> * (say, longer than a tenth of a second) the following code should be
> executed repeatedly:
>        * <code> if (Thread.currentThread().isInterrupted()) return; </code>
>        * Since this PlugInFilter has specified the CONVERT_TO_FLOAT and
> SNAPSHOT flags
>        * it will be always called with a FloatProcessor that has a valid
> snapshot.
>        * @param ip The ImageProcessor containing the image.
>        */
>       public void run(ImageProcessor ip) {
>           // For filters with preview, the filter parameter(s) should be
> copied from the
> // class variables set in DialogItemChanged to local variables to
> avoid changing
> // parameters during filtering. This can be done by calling a method with
> // the filter parameter(s) as arguments.
> // If the filter may crash with invalid or inconsistent parameters,
> parameter checking
> // can be done in dialogItemChanged. In that case, both the
> dialogItemChanged
> // method and copying should be done <code>synchronized</code> to
> avoid using
> // invalid parameters. The filtering operation itself should not be
> synchronized,
> // otherwise it would block the dialog during processing.
>           doFiltering((FloatProcessor)ip, (float)threshold, filterDark);
>       }
>
>       /** Here the filtering is really done.
>        * This is also the method that provides the API for use in other
> plugins, it should
>        * not use any class variables.
>        * A static method has the advantage that unintended reading of the
> filter parameters
>        * from the class variables (that may change asynchronously during
> preview) will be
>        * flagged by the compiler as error.
>        * (it cannot be static if it needs to access any class variable
> such as the ImagePlus imp
>        * or if we want to display a progress bar).
>        *
>        * This method only affects the pixels within the getRoi(ip)
> rectangle. Pixels within the
>        * rectangle, but outside the actual roi will be reset by ImageJ if
> SUPPORTS_MASKING
>        * has been added to the flags in the setup method.
>        *
>        * @param ip A FloatProcessor containing the image data. It must
> have a valid snapshot.
>        * @param threshold  Determines how much a pixel value may deviate
> from the mean of the
>        * neighboring pixels to remain unaltered. Threshold is in
> uncalibrated units.
>        * @param filterDark Whether dark outlies should be eliminated
> (otherwise bright outliers are
>        * eliminated)
>        */
>       public static void doFiltering(FloatProcessor ip, float threshold,
> boolean filterDark) {
> boolean filterMax = // whether to eliminate maxima (not minima)
> (ip.isInvertedLut()&&filterDark) || (!ip.isInvertedLut()&&!filterDark);
>           Thread thread = Thread.currentThread();     // needed to check
> for interrupted state
>           float[] pixels = (float[])ip.getPixels();   // array of the
> pixel values of the input image
>           float[] snapshotPixels =
>        (float[])ip.getSnapshotPixels();    // array with an
> (unaltered) copy of the  pixel values
>           int width = ip.getWidth();                  // width & height
> of the image
>           int height = ip.getHeight();
>           Rectangle roi = ip.getRoi();                // the rectangle
> containing the roi (full image size if no roi)
> int xEnd = roi.x + roi.width; // loops run only over pixels inside
> the roi
> int yStart = roi.y==0 ? 1 : roi.y; // do not process the topmost
> line (line y=0)
> int yEnd = roi.y + roi.height;
> if (yEnd == height) yEnd--; // do not process the bottommost line
> (line y=height-1)
> for (int y=yStart; y<yEnd; y++) {
> if (y%100==0 && thread.isInterrupted()) return;   // from time to
> time, check whether interrupted
>    for (int x=roi.x, p=y*width+x; x<xEnd; x++,p++) { // p points to
> pixel (x,y) in the arrays
>    float neighbor1 = snapshotPixels[p+width];    // the neighbor at
> (x, y+1)
> float neighbor2 = snapshotPixels[p-width];    // the neighbor at (x,
> y-1)
> if ((filterMax && snapshotPixels[p]>neighbor1+threshold &&
> snapshotPixels[p]>neighbor2+threshold) ||
>        (!filterMax && snapshotPixels[p]<neighbor1-threshold &&
> snapshotPixels[p]<neighbor2-threshold))
> pixels[p] = (neighbor1+neighbor2)/2;
> }
> }
>       }
>
> /** This method required by the ExtendedPlugInFilter interface is not
> used here.
> *  It specifies the number of calls to the run(ip) method.
> *  For filters displaying a progress bar, nPasses is needed to display a
> *  smooth progress bar when processing stacks. */
> public void setNPasses (int nPasses) {}
>
>       /** Show the "About..." text.
>        *  This method must be named "showAbout", then the plugin is added
> to the
> *  Help>About Plugins menu
>        */
>       private void showAbout() {
> IJ.showMessage("About Outliers...",
> "This plug-in filter alters pixels that deviate from their \n"+
> "neighbors above and below by more than a given threshold. \n" +
> "These outliers are set to the mean of these neighbors. \n" +
> "Edge pixels at the top and bottom of the image are not \n" +
> "processed."
> );
>       }
> }
>
> ____________________________________________________________________________________________________________________
>
> --
> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>
> --
> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html
Reply | Threaded
Open this post in threaded view
|

Re: NonBlockingGenericDialog for Process>Filters

Michael Schmid-3
In reply to this post by Curtis Rueden
Hi Curtis, (Wayne, Philippe, and everyone):

can we get some boolean to determine whether ImageJ/Fiji is headless?
Then, maybe we could have a static method, e.g., in
NonBlockingGenericDialog:

/** Returns a new NonBlockingGenericDialog with given title, unless
  *  java is running in headless mode; then a GenericDialog will be
  *  returned*/
public static GenericDialog newDialog(String title) {
   if (IJ.isHeadless())
     return new GenericDialog(title);
   else
     return new NonBlockingGenericDialog(title);
}

Since NonBlockingGenericDialog is a subclass of GenericDialog, this
should give a modeless user interface and nevertheless work as
GenericDialog in headless mode.


Michael
________________________________________________________________
On 22.02.19 19:01, Curtis Rueden wrote:

     Hi Philippe,

         Is there a reason why the Process>Filters are written with a
         GenericDialog and not a NonBlockingGenericDialog?


     One reason is that ImageJ2's headless support currently only
supports
     GenericDialog, not NonBlockingGenericDialog.

     Consider the following macro:

        run("Blobs (25K)");
        run("Variance...", "radius=2");
        run("Save", "save=/Users/curtis/Desktop/blobs.tif");

     With ImageJ2, you can execute this headless from the command line as
     follows:

        Contents/MacOS/ImageJ-macosx --headless -macro
~/Desktop/variance.ijm

     Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your
     platform, and "~/Desktop/variance.ijm" is the path to that macro
file on
     disk.

     The result will be a file "/Users/curtis/Desktop/blobs.tif" with the
     variance filter applied to the Blobs sample image.

     If you make the change from GenericDialog to
NonBlockingGenericDialog,
     there will instead be an error as follows:

        java.lang.VerifyError: Bad type on operand stack
        Exception Details:
          Location:
            ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic
          Reason:
            Type 'ij/gui/NonBlockingGenericDialog' (current frame,
stack[0]) is
     not assignable to 'java/awt/Window'

     Followed by some details to assist with debugging.

     If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog,
work
     will need to be done to improve the ImageJ Legacy component to
support
     NonBlockingGenericDialog in headless mode.

     Regards,
     Curtis

     --
     Curtis Rueden
     LOCI software architect - https://loci.wisc.edu/software
     ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden
     Have you tried the Image.sc Forum? https://forum.image.sc/



     On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL
<[hidden email]>
     wrote:

         Dear all (probably Wayne),
         Is there a reason why the Process>Filters are written with a
GenericDialog
         and not a NonBlockingGenericDialog?
         Using a NonBlockingGenericDialog has the huge advantage of being
able to
         make a preview on a picture with a given filter while still
being able to
         zoom and drag it in order to check the effect of the filter on
small
         details
         within the picture.
         And in order to do this, all that is needed to be changed is to:
                  - Add on line 5: import
ij.gui.NonBlockingGenericDialog;
                  - Replace the line 112  from "GenericDialog gd = new
         GenericDialog(command+"...");"
                                                  into "
NonBlockingGenericDialog gd
         =
         new NonBlockingGenericDialog (command+"...");"
         Within the "ij.plugin.filter.RankFilters.java" file.
         I thank you very much in advance for your answers.
         My best regards,
         Philippe

         Philippe CARL

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html
Reply | Threaded
Open this post in threaded view
|

Re: NonBlockingGenericDialog for Process>Filters

CARL Philippe (LBP)
In reply to this post by Michael Schmid-3
Servus Michael,
To play with several Gaussian Blur windows in parallel, I indeed didn't think about it.
And considering static parameters definitions, I now agree completely with you that this will indeed be an issue.
My idea (and actually need) behind NonBlockingGenericDialog filters was to be nice and gentle with them, by only using "regular" tools in parallel with these filters like zoom, drag, threshold, ..., i.e. tools where there would be no static parameter definition issues.
So in order to fix this potential static parameter issue, what would you think about restricting the creation of only one instance of these filters (potentially throwing an error message in the case a user wants to create a second one)?
This solution would enlarge the analysis versatility, providing in the same the "needed security" for the static parameters.
As the issue with the headless mode, it could be solved with the addition of a new option within "Edit>Options>Misc..." window.
Experimental test plugin: Ok I saw these 3 sliders. But thinking more about it and considering what I described higher, i.e. the combined use of the tools zoom, drag and threshold with a filter, I think that your idea becomes already too complicated.
I also saw that the Analyze>Analyze-particles... tool now excludes the holes. Thanks a lot!!!
Kindest regards,
Philippe

-----Message d'origine-----
De : ImageJ Interest Group [mailto:[hidden email]] De la part de Michael Schmid
Envoyé : lundi 25 février 2019 17:02
À : [hidden email]
Objet : Re: NonBlockingGenericDialog for Process>Filters

Hi Philippe,

a typical case of two Gaussian Blurs interfering would be the following:

Have one Gaussian Blur of a stack running (a large stack so it take some
time) and at the same time do a preview of another GaussianBlur with a
different radius (sigma). Then, as soon as the preview starts, the
remaining slices of the stack will be processed with the sigma of the
preview, not the one that should be used for the stack.

In principle, you can do the same thing already now (I just tried with a
1024x1024x1024 stack that needs about 15-20 seconds for sigma=7, and
quickly started another GaussianBlur on another image with different
sigma before the stack was fully processed - the last slices stack got
blurred with sigma intended for the other image). Fortunately, it is
rather unlikely that this happens in practice.

It is much more likely that this kind of problem occurs if one can have
several GaussianBlur dialogs open at one time, e.g., to compare how it
affects different images. Then it would be much more likely that
GaussianBlur runs on two images/stacks at the same moment.

Of course, the clean solution would be making the class variables for
the parameters non-static, and adding the static ones for saving the
parameters, in GaussianBlur, UnsharpMask, Convolver, and ImageMath (the
BackgroundSubtracter & RankFilters seem to be fine). In principle, it
would be easy, but it has to be done.

Then, we still have to care about the headless mode...

--
Experimental test plugin: There should be three additional sliders
(rather narrow ones) for adjusting B&C, but only for grayscale images
(not for RGB).


Michael
________________________________________________________________
On 25.02.19 15:41, Philippe CARL wrote:

> Gruss Gott Michael,
> I made as well a GenericDialog to NonBlockingGenericDialog conversion onto the GaussianBlur.java file, replaced the compiled GaussianBlur.class within the IJ.jar and then made all crazy things I could think of which could be problematic having static variables.
> More precisely, I opened two pictures, launched a Gaussian blurr on one of it, activate the preview button and then selected the other one, applied a Contras&Brightness update and then modified the Gaussian Blurr filter values and the modifications were only applied on the picture on which the filter had been launched.
> Alternatively, I launched a Gaussian blur on one picture, activated the preview button, than opened another one, played with it, and finally modified the filter values and the updates were on the picture on which the filter had at first be applied.
> So which issues could up to you be found on some filters due to static variables?
> I compiled as well the codes you had as attachment, but wasn't able to see any special zoom and drag buttons (unless I missed something) when I launched them.
> Thanks a lot on advance for your very lightened answer.
> Kindest regards,
> Philippe
>
> -----Message d'origine-----
> De : ImageJ Interest Group [mailto:[hidden email]] De la part de Michael Schmid
> Envoyé : lundi 25 février 2019 13:17
> À : [hidden email]
> Objet : Re: NonBlockingGenericDialog for Process>Filters
>
> Hi Philippe,
>
> well, I fear that making standard PlugInFilter dialogs non-modal could
> cause more problems than just headless execution.
>
> Unfortunately, most filters are not multithreading-safe. With a
> non-modal dialog there would be nothing that prevents the user to use
> the same filter on two different images at the same time, which would
> result in unpredictable behavior.
> I think that the RankFilters (Mean, Variance, Median, Min, Max) are fine.
> Gaussian Blur, Convolve, and the 3D filters use static variables for the
> filter parameters, so they must remain modal.
>
> What I had thought about a while ago was creating a GenericDialog field
> that would allow the user to pan and zoom in/out with a line of small
> buttons (arrows and +/-) or scrollbars during preview. Maybe also
> dragging in a small square representing the image.
> Same for brightness&contrast.
> I had an experimental version for B&C with scrollbars, but these were
> too clumsy and then I did not find time to continue on that project. In
> case you want to make something nice out of it (or someone else wants to
> do this), feel free to use my experimental code pasted at the very bottom!
>
>
> Michael
> ________________________________________________________________
> On 25.02.19 11:53, Philippe CARL wrote:
>> Dear Curtis,
>> Given that I never use ImageJ in headless mode, I wasn't aware of this potential issue.
>> Thus what would you think about adding an additional option within "Edit>Options>Misc..." which would give the possibility to choose between GenericDialog or NonBlockingGenericDialog dialogs within all the filter tools?
>> This could give an agreement between both functionalities.
>> My best regards,
>> Philippe
>>
>> -----Message d'origine-----
>> De : ImageJ Interest Group [mailto:[hidden email]] De la part de Curtis Rueden
>> Envoyé : vendredi 22 février 2019 19:01
>> À : [hidden email]
>> Objet : Re: NonBlockingGenericDialog for Process>Filters
>>
>> Hi Philippe,
>>
>>> Is there a reason why the Process>Filters are written with a
>>> GenericDialog and not a NonBlockingGenericDialog?
>>
>> One reason is that ImageJ2's headless support currently only supports
>> GenericDialog, not NonBlockingGenericDialog.
>>
>> Consider the following macro:
>>
>>     run("Blobs (25K)");
>>     run("Variance...", "radius=2");
>>     run("Save", "save=/Users/curtis/Desktop/blobs.tif");
>>
>> With ImageJ2, you can execute this headless from the command line as
>> follows:
>>
>>     Contents/MacOS/ImageJ-macosx --headless -macro ~/Desktop/variance.ijm
>>
>> Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your
>> platform, and "~/Desktop/variance.ijm" is the path to that macro file on
>> disk.
>>
>> The result will be a file "/Users/curtis/Desktop/blobs.tif" with the
>> variance filter applied to the Blobs sample image.
>>
>> If you make the change from GenericDialog to NonBlockingGenericDialog,
>> there will instead be an error as follows:
>>
>>     java.lang.VerifyError: Bad type on operand stack
>>     Exception Details:
>>       Location:
>>         ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic
>>       Reason:
>>         Type 'ij/gui/NonBlockingGenericDialog' (current frame, stack[0]) is
>> not assignable to 'java/awt/Window'
>>
>> Followed by some details to assist with debugging.
>>
>> If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog, work
>> will need to be done to improve the ImageJ Legacy component to support
>> NonBlockingGenericDialog in headless mode.
>>
>> Regards,
>> Curtis
>>
>> --
>> Curtis Rueden
>> LOCI software architect - https://loci.wisc.edu/software
>> ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden
>> Have you tried the Image.sc Forum? https://forum.image.sc/
>>
>>
>>
>> On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL <[hidden email]>
>> wrote:
>>
>>> Dear all (probably Wayne),
>>> Is there a reason why the Process>Filters are written with a GenericDialog
>>> and not a NonBlockingGenericDialog?
>>> Using a NonBlockingGenericDialog has the huge advantage of being able to
>>> make a preview on a picture with a given filter while still being able to
>>> zoom and drag it in order to check the effect of the filter on small
>>> details
>>> within the picture.
>>> And in order to do this, all that is needed to be changed is to:
>>>           - Add on line 5: import ij.gui.NonBlockingGenericDialog;
>>>           - Replace the line 112  from "GenericDialog gd = new
>>> GenericDialog(command+"...");"
>>>                                           into " NonBlockingGenericDialog gd
>>> =
>>> new NonBlockingGenericDialog (command+"...");"
>>> Within the "ij.plugin.filter.RankFilters.java" file.
>>> I thank you very much in advance for your answers.
>>> My best regards,
>>> Philippe
>>>
>>> Philippe CARL
>>> Laboratoire de Bioimagerie et Pathologies
>>> UMR 7021 CNRS - Université de Strasbourg
> ____________________________________________________________________________________________________________________
> import ij.*;
> import ij.measure.Measurements;
> import ij.process.*;
> import java.awt.*;
> import java.awt.event.*;
>
>
> public class PreviewHelper extends Panel implements MouseListener,
> AdjustmentListener {
> final static int WIDTH = 250;
> final static Dimension SB_SIZE = new Dimension(120,8);
> final static int BC_HEIGHT = 100;   // brightness/contrast area
> final static int ZS_HEIGHT = 100;   // zoom/scroll area
> final static int SB_FULL = 200;     // scrollbar steps
> final static double MIN_CONTRAST = 0.25;
> final static double MAX_CONTRAST = 128;
>       final ImagePlus imp;
>       final boolean hasBC;        //whether we have a Brightness&Contrast
> subpanel
>       private final int height;
>       private Scrollbar bScrollbar, cScrollbar, sScrollbar;
>       private double brightness, contrast;// 0 - SB_FULL range each
>       private static double saturated;   // 0 - SB_FULL range, is remembered
>       private boolean autoMode;           // saturated overrides min&max
> if true
>       Label rangeText;                    // text displaying min&max
>       Label sLabel;                       // 'auto contrast' (saturated)
> label
>
> public PreviewHelper(ImagePlus imp) {
>           this.imp = imp;
>           hasBC = imp.getType() != ImagePlus.COLOR_RGB;
>           height = hasBC ? ZS_HEIGHT : BC_HEIGHT + ZS_HEIGHT;
>           prepareGui();
>       }
>
>       private void prepareGui() {
> setLayout(new GridBagLayout());
> GridBagConstraints c = new GridBagConstraints();
> c.gridx = 0; c.gridy = 0;
> c.insets = new Insets(1, 1, 1, 1);
> c.fill = GridBagConstraints.HORIZONTAL;
>           if (hasBC) {        // setup B&C area
>    add(new Label("Bright"),c);
>    c.gridx++;
>               bScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL,
> 1, 0, SB_FULL+1);
>               bScrollbar.setFocusable(false); // prevents blinking on Windows
>               bScrollbar.setPreferredSize(SB_SIZE);
>               bScrollbar.addAdjustmentListener(this);
>               add(bScrollbar, c);
>               c.gridx = 0; c.gridy++;
>    add(new Label("Contr"),c);
>    c.gridx++;
>               cScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL,
> 1, 0, SB_FULL+1);
>               cScrollbar.setFocusable(false); // prevents blinking on Windows
>               cScrollbar.setPreferredSize(SB_SIZE);
>               cScrollbar.addAdjustmentListener(this);
>               add(cScrollbar, c);
>               c.gridx = 0; c.gridy++;
>               sLabel = new Label("Auto");
>               sLabel.addMouseListener(this);
>    add(sLabel, c);
>    c.gridx++;
>               sScrollbar = new Scrollbar(Scrollbar.HORIZONTAL, SB_FULL,
> 1, 0, SB_FULL+1);
>               sScrollbar.setFocusable(false); // prevents blinking on Windows
>               sScrollbar.setPreferredSize(SB_SIZE);
>               sScrollbar.addAdjustmentListener(this);
>               add(sScrollbar, c);
>
>               calculateBC('m', imp.getProcessor()); //set initial
> scrollbar values
>           }
>
>       }
>
>       /** Overrides Component getPreferredSize(). Added to work
>       around a bug in Java 1.4.1 on Mac OS X.*/
>       public Dimension getPreferredSize() {
>           return new Dimension(WIDTH+1, height+1);
>       }
>
>
> public void update(Graphics g) {
> paint(g);
> }
>
> public void paint(Graphics g) {
>    super.paint(g);
>    drawRect(g, sLabel.getBounds());
>       }
>
>
> public void mousePressed(MouseEvent e) {}
> public void mouseReleased(MouseEvent e) {}
> public void mouseExited(MouseEvent e) {}
> public void mouseClicked(MouseEvent e) {
>    Object source = e.getSource();
>    if (source==sLabel) {
>        autoMode = !autoMode;
>        sLabel.setFont(sLabel.getFont().deriveFont(autoMode ? Font.BOLD
> : Font.PLAIN));
>        calculateBC('s', imp.getProcessor());
>    }
> }
> public void mouseEntered(MouseEvent e) {}
>
> public void adjustmentValueChanged(AdjustmentEvent e) {
> Object source = e.getSource();
> if (source==bScrollbar)
> calculateBC('b', imp.getProcessor());
> else if (source==cScrollbar)
> calculateBC('c', imp.getProcessor());
> else if (source==sScrollbar)
> calculateBC('s', imp.getProcessor());
> }
>
>       /** If in 'Auto' mode, adjust brightness&contrast according to
> 'Auto' (saturated) scrollbar */
>       public void autoAdjust(ImageProcessor ip) {
>           if (autoMode) calculateBC('s', ip);
>       }
>
>       /** Calculate brightness, contrast, min, and max; set
> imageProcessor and scrollbars
>         * accordingly. 'changed' can be 'b'(rightness), 'c'(ontrast),
> 's'(aturation), 'm'(in&max)
>         * If changed='m', the min and max of the imageProcessor sets B&C
> scrollbars
>         * Otherwise, the scrollbar values set the imageProcessor's min&max
>         */
>       private void calculateBC(char changed, ImageProcessor ip) {
>           double fullMin = 0, fullMax = 255;
>           double min = ip.getMin(), max = ip.getMax();
>           if (!(ip instanceof ByteProcessor)) {
>               ip.resetMinAndMax();        // calculate range of pixels
>               fullMin = ip.getMin();
>               fullMax = ip.getMax();
>               if (changed=='m') ip.setMinAndMax(min, max); //revert to
> previous min&max
>           }
>           double fullRange = fullMax - fullMin;
>           if (fullRange <= 0) fullRange = 1e-100; //avoid division by 0
>           double fullMid = 0.5*(fullMax + fullMin);
>           if (changed=='m') {
>               sScrollbar.setValue((int)(saturated+0.5));
>           } else if (changed=='s') {
>               saturated = sScrollbar.getValue();
>               min = fullMin; max=fullMax;
>               if (saturated != 0) {
>                   ImageStatistics stats =
> ImageStatistics.getStatistics(ip, Measurements.MIN_MAX, null);
>                   int[] histogram = ip.getHistogram();
>                   int total = 0;
>                   int first = -1, last = -1;
>                   for (int i=0; i<histogram.length; i++) {
>                       int n = histogram[i];
>                       if (n > 0) {
>                           total += n;
>                           if (first < 0) first = i;
>                           last = i;
>                       }
>                   }
>                   int nSaturated = (int)(total*0.2*saturated/SB_FULL);
> //max 20% saturated
>                   int count = 0;
>                   int iMin = first;
>                   for (; iMin<last; iMin++) {
>                       count += histogram[iMin];
>                       if (count>nSaturated) break;
>                   }
>                   count = 0;
>                   int iMax = last;
>                   for (; iMax>first; iMax--) {
>                       count += histogram[iMax];
>                       if (count>nSaturated) break;
>                   }
>                   if (ip instanceof FloatProcessor) {
>                   } else {
>                       min = iMin; max = iMax;
>                   }
>               }
> IJ.log("reset. full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange));
>               ip.setMinAndMax(min,max);
>               imp.updateAndDraw();
>           }
>           if (changed=='m' || changed=='s') { //calculate and set B&C
> scrollbars
>               double currentMid = 0.5*(max + min);
>               double currentRange = max-min;
>               if (currentRange <= 0) currentRange = 1e-100;
> IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange));
> IJ.log("full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange));
>               brightness = SB_FULL * (0.5 -
> (currentMid-fullMid)/(fullRange));
>               contrast = SB_FULL *
> Math.log(fullRange/currentRange/MIN_CONTRAST)/Math.log(MAX_CONTRAST/MIN_CONTRAST);
>               bScrollbar.setValue((int)(brightness+0.5));
>               cScrollbar.setValue((int)(contrast+0.5));
> IJ.log("=> b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast));
>           } else if (changed=='b' || changed=='c') {
>               brightness = bScrollbar.getValue();
>               contrast = cScrollbar.getValue();
> //IJ.log("b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast));
>               double currentMid = fullMid + fullRange*(0.5 -
> brightness/SB_FULL);
>               double currentRange =
> fullRange/(MIN_CONTRAST*Math.exp(contrast/SB_FULL*Math.log(MAX_CONTRAST/MIN_CONTRAST)));
>               min = currentMid - 0.5*currentRange;
>               max = currentMid + 0.5*currentRange;
> //IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange));
> //IJ.log("=> min.max="+IJ.d2s(min)+", "+IJ.d2s(max));
>               ip.setMinAndMax(min, max);
>               imp.updateAndDraw();
>           }
>       }
>
>       //draw rectangle around a component
>       private void drawRect(Graphics g, Rectangle r) {
>           g.drawRect(r.x-1, r.y-1, r.width+1, r.height+1);
>       }
> } // ContrastPlot class
>
> ____________________________________________________________________________________________________________________
>
> import ij.*;
> import ij.plugin.filter.ExtendedPlugInFilter;
> import ij.plugin.filter.PlugInFilterRunner;
> import ij.gui.GenericDialog;
> import ij.gui.DialogListener;
> import ij.process.*;
> import java.awt.*;
> import java.awt.event.*;
>
> //test plugin for Preview helper
>
> /*
>    * An ImageJ ExtendedPlugInFilter that changes pixels that deviate from the
>    * two neighbors above&below by more than a certain value (the threshold).
>    * These pixels are set to the mean of these two neighbors. Pixels at the
>    * top and bottom edges are not processed.
>    * The filter is useful for eliminating hot pixels of CCDs and single
>    * bad lines of scanning probe images.
>    *
>    * It demonstrates how to write an plugin-filter with preview.
>    * Michael Schmid, 2007-05-21
>    */
>
> public class  Outliers_Simple implements ExtendedPlugInFilter,
> DialogListener {
>       //  ExtendedPlugInFilter:   An ExtendedPlugInFilter will be invoked
> by ImageJ the
> //                  following way:
>       //              (1) Call to setup with the current foreground
> ImagePlus (image window)
>       //                  and the argument string 'arg' specified in the
> plugins.config file
>       //                  or a.jar file that contains it.
>       //                  The PlugInFilter returns its flags.
>       //              (2) If the currently active image is compatible
> with the flags (and DONE
> //                  was not specified)  showDialog is called. If the
> reference to the
> //                  PlugInFilterRunner passed with showDialog is
> specified as an argument
> //                  to the addPreviewCheckbox method of a
> GenericDialog, preview will be
> //                  possible and the run method of this
> ExtendedPlugInFilter will be called
> //                  in the background for preview.
>       //              (3) Unless otherwise specified in the flags (DONE
> or keeping the preview
>       //                  as an output for a non-stack ImagePlus), the
> run method is called.
>       //                  For stacks, it is called repeatedly for each slice.
>       //              (4) If the FINAL_PROCESSING flag has been set (but
> not DONE), the
> //                  setup method is invoked with "final" as parameter
> string.
>       //               ** If this PlugInFilter contains a showAbout()
> method, its setup method
>       //                  may be also called with argument string
> "about". Then, the filter will
>       //                  display a short help message.
>       //
>       //  DialogListener: This PlugInFilter provides the
> dialogItemChanged method, whoch will
> //                  be called if required by the addDialogListener
> method of the
> // GenericDialog.
> //                  For preview-enabled ExtendedPlugInFilters, the
> filter parameters/options
> //                  should be set in the dialogItemChanged method.
>
>
>       // F i l t e r   p a r a m e t e r s   - don't declare the
> paramters actually used as static,
>       //   otherwise parallel execution (in different threads with
> different parameters) won't work.
>       /** The threshold. Only pixels deviating by more than this value
> will be replaced */
>       private double threshold;
>       /** For saving the threshold; here we also put the initial value */
>       private static double sThreshold = 10;
> /** Whether to filter dark (instead of bright) pixels */
> private static boolean filterDark = false;
>       // F u r t h e r   c l a s s   v a r i a b l e s
> /** Flags specifying the capabilities and needs of this filter. See
> interfaces PlugInFilter and
>        ExtendedPlugInFIlter for details */
>       int flags =
> DOES_ALL|SUPPORTS_MASKING|CONVERT_TO_FLOAT|SNAPSHOT|KEEP_PREVIEW|
>               PARALLELIZE_STACKS;
>       PreviewHelper previewHelper;
>
>       /** Setup of the PlugInFilter. Returns the flags specifying the
> capabilities and needs
>        * of the filter.
>        *
>        * @param arg   An argument string that may be specified, e.g., in
> the plugins.config file of a
>        *              jar archive containing th plugin. Unused here.
>        * @param imp   The ImagePlus to be processed (image with
> associated window, calibration, etc.)
>        * @return      Flags specifying further action of the
> PlugInFilterRunner
>        */
>       public int setup(String arg, ImagePlus imp) {
>           if (arg.equals("about")) {              //this is a special
> case - we only display the "about..." info
>               showAbout();
>               return DONE;
> } else {
>    previewHelper = new PreviewHelper(imp);
> return flags;
> }
>       }
>
>       /** Show the dialog asking for the parameters and get them.
>        * @return Flags specifying further action of the PlugInFilterRunner
>        */
>       public int showDialog(ImagePlus imp, String command,
> PlugInFilterRunner pfr) {
>           GenericDialog gd = new GenericDialog(command+"...");
>           gd.addNumericField("Threshold", sThreshold, 0); //use saved
> value as default
> gd.addCheckbox("Dark Outliers", filterDark);
>           gd.addPreviewCheckbox(pfr);    //passing pfr makes the filter
> ready for preview
>           gd.addDialogListener(this);    //the DialogItemChanged method
> will be called on user input
>           gd.addPanel(previewHelper);
>           gd.showDialog();             //display the dialog; preview runs
> in the background
>           if (gd.wasCanceled())
> return DONE;
>           dialogItemChanged(gd, null);    //read the parameters finally set
>           IJ.register(this.getClass());   //protect static class
> variables (filter parameters) from garbage collection
>           return IJ.setupDialog(imp, flags);  //ask whether to process
> all slices of stack (if a stack)
>       }
>
>       /** If this PlugInFilter has been added as DialogListener, the
> method dialogItemChanged
>        * is invoked by GenericDialog every time the user changes
> something in the dialog.
>        * Time-consuming code should not be placed in this method and any
> methods called.
> * @param gd A reference to the GenericDialog, needed to read the input
>        * @param e An Event characterizing what has been changed in the
> dialog.
>        * @return Whether the dialog input is valid. If true, preview
> may be invoked.
>        *                  (There is no need to check the state of the
> preview Checkbox here,
>        *                  this is done by the PlugInFilterRunner.)
> * If false is returned (invalid input), the "OK" button of the dialog
> *                  and preview are disabled.
>        */
>       public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
>           threshold = gd.getNextNumber();
> filterDark = gd.getNextBoolean();
>           if (gd.invalidNumber() || threshold <= 0)
>               return false;
>           sThreshold = threshold; // save valid value for the next time
>           return true;
>       }
>
>       /** This method is invoked by ImageJ for processing. For stacks, it
> can be called once for
>        * each slice (depending on IJ.setupDialog, where the user is asked
> whether to do so, see
>        * end of method setup, above).
>        * If preview is enabled and the filter parameters have changed,
> processing should
>        * be stopped when this thread is interrupted. Thherefore, during
> long calculations
> * (say, longer than a tenth of a second) the following code should be
> executed repeatedly:
>        * <code> if (Thread.currentThread().isInterrupted()) return; </code>
>        * Since this PlugInFilter has specified the CONVERT_TO_FLOAT and
> SNAPSHOT flags
>        * it will be always called with a FloatProcessor that has a valid
> snapshot.
>        * @param ip The ImageProcessor containing the image.
>        */
>       public void run(ImageProcessor ip) {
>           // For filters with preview, the filter parameter(s) should be
> copied from the
> // class variables set in DialogItemChanged to local variables to
> avoid changing
> // parameters during filtering. This can be done by calling a method with
> // the filter parameter(s) as arguments.
> // If the filter may crash with invalid or inconsistent parameters,
> parameter checking
> // can be done in dialogItemChanged. In that case, both the
> dialogItemChanged
> // method and copying should be done <code>synchronized</code> to
> avoid using
> // invalid parameters. The filtering operation itself should not be
> synchronized,
> // otherwise it would block the dialog during processing.
>           doFiltering((FloatProcessor)ip, (float)threshold, filterDark);
>       }
>
>       /** Here the filtering is really done.
>        * This is also the method that provides the API for use in other
> plugins, it should
>        * not use any class variables.
>        * A static method has the advantage that unintended reading of the
> filter parameters
>        * from the class variables (that may change asynchronously during
> preview) will be
>        * flagged by the compiler as error.
>        * (it cannot be static if it needs to access any class variable
> such as the ImagePlus imp
>        * or if we want to display a progress bar).
>        *
>        * This method only affects the pixels within the getRoi(ip)
> rectangle. Pixels within the
>        * rectangle, but outside the actual roi will be reset by ImageJ if
> SUPPORTS_MASKING
>        * has been added to the flags in the setup method.
>        *
>        * @param ip A FloatProcessor containing the image data. It must
> have a valid snapshot.
>        * @param threshold  Determines how much a pixel value may deviate
> from the mean of the
>        * neighboring pixels to remain unaltered. Threshold is in
> uncalibrated units.
>        * @param filterDark Whether dark outlies should be eliminated
> (otherwise bright outliers are
>        * eliminated)
>        */
>       public static void doFiltering(FloatProcessor ip, float threshold,
> boolean filterDark) {
> boolean filterMax = // whether to eliminate maxima (not minima)
> (ip.isInvertedLut()&&filterDark) || (!ip.isInvertedLut()&&!filterDark);
>           Thread thread = Thread.currentThread();     // needed to check
> for interrupted state
>           float[] pixels = (float[])ip.getPixels();   // array of the
> pixel values of the input image
>           float[] snapshotPixels =
>        (float[])ip.getSnapshotPixels();    // array with an
> (unaltered) copy of the  pixel values
>           int width = ip.getWidth();                  // width & height
> of the image
>           int height = ip.getHeight();
>           Rectangle roi = ip.getRoi();                // the rectangle
> containing the roi (full image size if no roi)
> int xEnd = roi.x + roi.width; // loops run only over pixels inside
> the roi
> int yStart = roi.y==0 ? 1 : roi.y; // do not process the topmost
> line (line y=0)
> int yEnd = roi.y + roi.height;
> if (yEnd == height) yEnd--; // do not process the bottommost line
> (line y=height-1)
> for (int y=yStart; y<yEnd; y++) {
> if (y%100==0 && thread.isInterrupted()) return;   // from time to
> time, check whether interrupted
>    for (int x=roi.x, p=y*width+x; x<xEnd; x++,p++) { // p points to
> pixel (x,y) in the arrays
>    float neighbor1 = snapshotPixels[p+width];    // the neighbor at
> (x, y+1)
> float neighbor2 = snapshotPixels[p-width];    // the neighbor at (x,
> y-1)
> if ((filterMax && snapshotPixels[p]>neighbor1+threshold &&
> snapshotPixels[p]>neighbor2+threshold) ||
>        (!filterMax && snapshotPixels[p]<neighbor1-threshold &&
> snapshotPixels[p]<neighbor2-threshold))
> pixels[p] = (neighbor1+neighbor2)/2;
> }
> }
>       }
>
> /** This method required by the ExtendedPlugInFilter interface is not
> used here.
> *  It specifies the number of calls to the run(ip) method.
> *  For filters displaying a progress bar, nPasses is needed to display a
> *  smooth progress bar when processing stacks. */
> public void setNPasses (int nPasses) {}
>
>       /** Show the "About..." text.
>        *  This method must be named "showAbout", then the plugin is added
> to the
> *  Help>About Plugins menu
>        */
>       private void showAbout() {
> IJ.showMessage("About Outliers...",
> "This plug-in filter alters pixels that deviate from their \n"+
> "neighbors above and below by more than a given threshold. \n" +
> "These outliers are set to the mean of these neighbors. \n" +
> "Edge pixels at the top and bottom of the image are not \n" +
> "processed."
> );
>       }
> }
>
> ____________________________________________________________________________________________________________________
>
> --
> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>
> --
> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html
Reply | Threaded
Open this post in threaded view
|

Re: NonBlockingGenericDialog for Process>Filters

Curtis Rueden
In reply to this post by Curtis Rueden
Hi Michael,

> can we get some boolean to determine whether ImageJ/Fiji is headless?

It will probably be OK to use java.awt.GraphicsEnvironment.isHeadless().

> Since NonBlockingGenericDialog is a subclass of GenericDialog, this
> should give a modeless user interface and nevertheless work as
> GenericDialog in headless mode.

Sounds like a sensible solution.

Regards,
Curtis

--
Curtis Rueden
LOCI software architect - https://loci.wisc.edu/software
ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden
Have you tried the Image.sc Forum? https://forum.image.sc/



On Tue, Feb 26, 2019 at 4:44 AM Michael Schmid <[hidden email]>
wrote:

> Hi Curtis, (Wayne, Philippe, and everyone):
>
> can we get some boolean to determine whether ImageJ/Fiji is headless?
> Then, maybe we could have a static method, e.g., in
> NonBlockingGenericDialog:
>
> /** Returns a new NonBlockingGenericDialog with given title, unless
>   *  java is running in headless mode; then a GenericDialog will be
>   *  returned*/
> public static GenericDialog newDialog(String title) {
>    if (IJ.isHeadless())
>      return new GenericDialog(title);
>    else
>      return new NonBlockingGenericDialog(title);
> }
>
> Since NonBlockingGenericDialog is a subclass of GenericDialog, this
> should give a modeless user interface and nevertheless work as
> GenericDialog in headless mode.
>
>
> Michael
> ________________________________________________________________
> On 22.02.19 19:01, Curtis Rueden wrote:
>
>      Hi Philippe,
>
>          Is there a reason why the Process>Filters are written with a
>          GenericDialog and not a NonBlockingGenericDialog?
>
>
>      One reason is that ImageJ2's headless support currently only
> supports
>      GenericDialog, not NonBlockingGenericDialog.
>
>      Consider the following macro:
>
>         run("Blobs (25K)");
>         run("Variance...", "radius=2");
>         run("Save", "save=/Users/curtis/Desktop/blobs.tif");
>
>      With ImageJ2, you can execute this headless from the command line as
>      follows:
>
>         Contents/MacOS/ImageJ-macosx --headless -macro
> ~/Desktop/variance.ijm
>
>      Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your
>      platform, and "~/Desktop/variance.ijm" is the path to that macro
> file on
>      disk.
>
>      The result will be a file "/Users/curtis/Desktop/blobs.tif" with the
>      variance filter applied to the Blobs sample image.
>
>      If you make the change from GenericDialog to
> NonBlockingGenericDialog,
>      there will instead be an error as follows:
>
>         java.lang.VerifyError: Bad type on operand stack
>         Exception Details:
>           Location:
>             ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic
>           Reason:
>             Type 'ij/gui/NonBlockingGenericDialog' (current frame,
> stack[0]) is
>      not assignable to 'java/awt/Window'
>
>      Followed by some details to assist with debugging.
>
>      If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog,
> work
>      will need to be done to improve the ImageJ Legacy component to
> support
>      NonBlockingGenericDialog in headless mode.
>
>      Regards,
>      Curtis
>
>      --
>      Curtis Rueden
>      LOCI software architect - https://loci.wisc.edu/software
>      ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden
>      Have you tried the Image.sc Forum? https://forum.image.sc/
>
>
>
>      On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL
> <[hidden email]>
>      wrote:
>
>          Dear all (probably Wayne),
>          Is there a reason why the Process>Filters are written with a
> GenericDialog
>          and not a NonBlockingGenericDialog?
>          Using a NonBlockingGenericDialog has the huge advantage of being
> able to
>          make a preview on a picture with a given filter while still
> being able to
>          zoom and drag it in order to check the effect of the filter on
> small
>          details
>          within the picture.
>          And in order to do this, all that is needed to be changed is to:
>                   - Add on line 5: import
> ij.gui.NonBlockingGenericDialog;
>                   - Replace the line 112  from "GenericDialog gd = new
>          GenericDialog(command+"...");"
>                                                   into "
> NonBlockingGenericDialog gd
>          =
>          new NonBlockingGenericDialog (command+"...");"
>          Within the "ij.plugin.filter.RankFilters.java" file.
>          I thank you very much in advance for your answers.
>          My best regards,
>          Philippe
>
>          Philippe CARL
>
> --
> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html
Reply | Threaded
Open this post in threaded view
|

Re: NonBlockingGenericDialog for Process>Filters

Michael Schmid-3
In reply to this post by CARL Philippe (LBP)
Bonjour Philippe, and Hi everyone in the non-francophone regions,

there is more to care about when using NonBlockingGenericDialog for the
filters:
Any action that overwrites the undo buffer ("snapshot") will interfere
with preview (which uses the snapshot).

Plugins that lock the image (all PlugInFilters) should be no problem;
running any of these on a locked image should cause an error message.

I did a quick search for "snapshot" and found the following potentially
problematic ones (currently none of them locks the image they work on):
ContrastEnhancer
GelAnalyzer
ImageCalculator *
ScaleBar
Scaler *
ContrastAdjuster
RoiManager
  * I think these should lock the image anyhow

Out of these, the ContrastAdjuster (Brightness&Contrast) and
ContrastEnhancer are most relevant; if the user tries to adjust the
contrast during preview to better see the result, and if there is either
a ROI or the image is RGB, it will make it impossible to revert preview.

A possible way out might be keeping the snapshot during preview, and
using setSnapshotPixels before reverting from preview. But this will
need more thought.

What one should also think of is whether creating or modifying ROIs
should be blocked while an image is locked.

To me, the snapshot issue is currently the most severe problem (Wayne,
are you aware of any others? Or someone else?).

Concerning the static variables, I think that the cleaner and easier
solution would be fixing it in the relevant plugins; then we need no
mechanism to block concurrent running of GaussianBlur etc.


Best,

Michael
________________________________________________________________


On 2019-02-26 12:05, Philippe CARL wrote:

> Servus Michael,
> To play with several Gaussian Blur windows in parallel, I indeed
> didn't think about it.
> And considering static parameters definitions, I now agree completely
> with you that this will indeed be an issue.
> My idea (and actually need) behind NonBlockingGenericDialog filters
> was to be nice and gentle with them, by only using "regular" tools in
> parallel with these filters like zoom, drag, threshold, ..., i.e.
> tools where there would be no static parameter definition issues.
> So in order to fix this potential static parameter issue, what would
> you think about restricting the creation of only one instance of these
> filters (potentially throwing an error message in the case a user
> wants to create a second one)?
> This solution would enlarge the analysis versatility, providing in the
> same the "needed security" for the static parameters.
> As the issue with the headless mode, it could be solved with the
> addition of a new option within "Edit>Options>Misc..." window.
> Experimental test plugin: Ok I saw these 3 sliders. But thinking more
> about it and considering what I described higher, i.e. the combined
> use of the tools zoom, drag and threshold with a filter, I think that
> your idea becomes already too complicated.
> I also saw that the Analyze>Analyze-particles... tool now excludes the
> holes. Thanks a lot!!!
> Kindest regards,
> Philippe
>
> -----Message d'origine-----
> De : ImageJ Interest Group [mailto:[hidden email]] De la part de
> Michael Schmid
> Envoyé : lundi 25 février 2019 17:02
> À : [hidden email]
> Objet : Re: NonBlockingGenericDialog for Process>Filters
>
> Hi Philippe,
>
> a typical case of two Gaussian Blurs interfering would be the
> following:
>
> Have one Gaussian Blur of a stack running (a large stack so it take
> some
> time) and at the same time do a preview of another GaussianBlur with a
> different radius (sigma). Then, as soon as the preview starts, the
> remaining slices of the stack will be processed with the sigma of the
> preview, not the one that should be used for the stack.
>
> In principle, you can do the same thing already now (I just tried with
> a
> 1024x1024x1024 stack that needs about 15-20 seconds for sigma=7, and
> quickly started another GaussianBlur on another image with different
> sigma before the stack was fully processed - the last slices stack got
> blurred with sigma intended for the other image). Fortunately, it is
> rather unlikely that this happens in practice.
>
> It is much more likely that this kind of problem occurs if one can have
> several GaussianBlur dialogs open at one time, e.g., to compare how it
> affects different images. Then it would be much more likely that
> GaussianBlur runs on two images/stacks at the same moment.
>
> Of course, the clean solution would be making the class variables for
> the parameters non-static, and adding the static ones for saving the
> parameters, in GaussianBlur, UnsharpMask, Convolver, and ImageMath (the
> BackgroundSubtracter & RankFilters seem to be fine). In principle, it
> would be easy, but it has to be done.
>
> Then, we still have to care about the headless mode...
>
> --
> Experimental test plugin: There should be three additional sliders
> (rather narrow ones) for adjusting B&C, but only for grayscale images
> (not for RGB).
>
>
> Michael
> ________________________________________________________________
> On 25.02.19 15:41, Philippe CARL wrote:
>> Gruss Gott Michael,
>> I made as well a GenericDialog to NonBlockingGenericDialog conversion
>> onto the GaussianBlur.java file, replaced the compiled
>> GaussianBlur.class within the IJ.jar and then made all crazy things I
>> could think of which could be problematic having static variables.
>> More precisely, I opened two pictures, launched a Gaussian blurr on
>> one of it, activate the preview button and then selected the other
>> one, applied a Contras&Brightness update and then modified the
>> Gaussian Blurr filter values and the modifications were only applied
>> on the picture on which the filter had been launched.
>> Alternatively, I launched a Gaussian blur on one picture, activated
>> the preview button, than opened another one, played with it, and
>> finally modified the filter values and the updates were on the picture
>> on which the filter had at first be applied.
>> So which issues could up to you be found on some filters due to static
>> variables?
>> I compiled as well the codes you had as attachment, but wasn't able to
>> see any special zoom and drag buttons (unless I missed something) when
>> I launched them.
>> Thanks a lot on advance for your very lightened answer.
>> Kindest regards,
>> Philippe
>>
>> -----Message d'origine-----
>> De : ImageJ Interest Group [mailto:[hidden email]] De la part de
>> Michael Schmid
>> Envoyé : lundi 25 février 2019 13:17
>> À : [hidden email]
>> Objet : Re: NonBlockingGenericDialog for Process>Filters
>>
>> Hi Philippe,
>>
>> well, I fear that making standard PlugInFilter dialogs non-modal could
>> cause more problems than just headless execution.
>>
>> Unfortunately, most filters are not multithreading-safe. With a
>> non-modal dialog there would be nothing that prevents the user to use
>> the same filter on two different images at the same time, which would
>> result in unpredictable behavior.
>> I think that the RankFilters (Mean, Variance, Median, Min, Max) are
>> fine.
>> Gaussian Blur, Convolve, and the 3D filters use static variables for
>> the
>> filter parameters, so they must remain modal.
>>
>> What I had thought about a while ago was creating a GenericDialog
>> field
>> that would allow the user to pan and zoom in/out with a line of small
>> buttons (arrows and +/-) or scrollbars during preview. Maybe also
>> dragging in a small square representing the image.
>> Same for brightness&contrast.
>> I had an experimental version for B&C with scrollbars, but these were
>> too clumsy and then I did not find time to continue on that project.
>> In
>> case you want to make something nice out of it (or someone else wants
>> to
>> do this), feel free to use my experimental code pasted at the very
>> bottom!
>>
>>
>> Michael
>> ________________________________________________________________
>> On 25.02.19 11:53, Philippe CARL wrote:
>>> Dear Curtis,
>>> Given that I never use ImageJ in headless mode, I wasn't aware of
>>> this potential issue.
>>> Thus what would you think about adding an additional option within
>>> "Edit>Options>Misc..." which would give the possibility to choose
>>> between GenericDialog or NonBlockingGenericDialog dialogs within all
>>> the filter tools?
>>> This could give an agreement between both functionalities.
>>> My best regards,
>>> Philippe
>>>
>>> -----Message d'origine-----
>>> De : ImageJ Interest Group [mailto:[hidden email]] De la part de
>>> Curtis Rueden
>>> Envoyé : vendredi 22 février 2019 19:01
>>> À : [hidden email]
>>> Objet : Re: NonBlockingGenericDialog for Process>Filters
>>>
>>> Hi Philippe,
>>>
>>>> Is there a reason why the Process>Filters are written with a
>>>> GenericDialog and not a NonBlockingGenericDialog?
>>>
>>> One reason is that ImageJ2's headless support currently only supports
>>> GenericDialog, not NonBlockingGenericDialog.
>>>
>>> Consider the following macro:
>>>
>>>     run("Blobs (25K)");
>>>     run("Variance...", "radius=2");
>>>     run("Save", "save=/Users/curtis/Desktop/blobs.tif");
>>>
>>> With ImageJ2, you can execute this headless from the command line as
>>> follows:
>>>
>>>     Contents/MacOS/ImageJ-macosx --headless -macro
>>> ~/Desktop/variance.ijm
>>>
>>> Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your
>>> platform, and "~/Desktop/variance.ijm" is the path to that macro file
>>> on
>>> disk.
>>>
>>> The result will be a file "/Users/curtis/Desktop/blobs.tif" with the
>>> variance filter applied to the Blobs sample image.
>>>
>>> If you make the change from GenericDialog to
>>> NonBlockingGenericDialog,
>>> there will instead be an error as follows:
>>>
>>>     java.lang.VerifyError: Bad type on operand stack
>>>     Exception Details:
>>>       Location:
>>>         ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic
>>>       Reason:
>>>         Type 'ij/gui/NonBlockingGenericDialog' (current frame,
>>> stack[0]) is
>>> not assignable to 'java/awt/Window'
>>>
>>> Followed by some details to assist with debugging.
>>>
>>> If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog,
>>> work
>>> will need to be done to improve the ImageJ Legacy component to
>>> support
>>> NonBlockingGenericDialog in headless mode.
>>>
>>> Regards,
>>> Curtis
>>>
>>> --
>>> Curtis Rueden
>>> LOCI software architect - https://loci.wisc.edu/software
>>> ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden
>>> Have you tried the Image.sc Forum? https://forum.image.sc/
>>>
>>>
>>>
>>> On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL
>>> <[hidden email]>
>>> wrote:
>>>
>>>> Dear all (probably Wayne),
>>>> Is there a reason why the Process>Filters are written with a
>>>> GenericDialog
>>>> and not a NonBlockingGenericDialog?
>>>> Using a NonBlockingGenericDialog has the huge advantage of being
>>>> able to
>>>> make a preview on a picture with a given filter while still being
>>>> able to
>>>> zoom and drag it in order to check the effect of the filter on small
>>>> details
>>>> within the picture.
>>>> And in order to do this, all that is needed to be changed is to:
>>>>           - Add on line 5: import ij.gui.NonBlockingGenericDialog;
>>>>           - Replace the line 112  from "GenericDialog gd = new
>>>> GenericDialog(command+"...");"
>>>>                                           into "
>>>> NonBlockingGenericDialog gd
>>>> =
>>>> new NonBlockingGenericDialog (command+"...");"
>>>> Within the "ij.plugin.filter.RankFilters.java" file.
>>>> I thank you very much in advance for your answers.
>>>> My best regards,
>>>> Philippe
>>>>
>>>> Philippe CARL
>>>> Laboratoire de Bioimagerie et Pathologies
>>>> UMR 7021 CNRS - Université de Strasbourg
>> ____________________________________________________________________________________________________________________
>> import ij.*;
>> import ij.measure.Measurements;
>> import ij.process.*;
>> import java.awt.*;
>> import java.awt.event.*;
>>
>>
>> public class PreviewHelper extends Panel implements MouseListener,
>> AdjustmentListener {
>> final static int WIDTH = 250;
>> final static Dimension SB_SIZE = new Dimension(120,8);
>> final static int BC_HEIGHT = 100;   // brightness/contrast area
>> final static int ZS_HEIGHT = 100;   // zoom/scroll area
>> final static int SB_FULL = 200;     // scrollbar steps
>> final static double MIN_CONTRAST = 0.25;
>> final static double MAX_CONTRAST = 128;
>>       final ImagePlus imp;
>>       final boolean hasBC;        //whether we have a
>> Brightness&Contrast
>> subpanel
>>       private final int height;
>>       private Scrollbar bScrollbar, cScrollbar, sScrollbar;
>>       private double brightness, contrast;// 0 - SB_FULL range each
>>       private static double saturated;   // 0 - SB_FULL range, is
>> remembered
>>       private boolean autoMode;           // saturated overrides
>> min&max
>> if true
>>       Label rangeText;                    // text displaying min&max
>>       Label sLabel;                       // 'auto contrast'
>> (saturated)
>> label
>>
>> public PreviewHelper(ImagePlus imp) {
>>           this.imp = imp;
>>           hasBC = imp.getType() != ImagePlus.COLOR_RGB;
>>           height = hasBC ? ZS_HEIGHT : BC_HEIGHT + ZS_HEIGHT;
>>           prepareGui();
>>       }
>>
>>       private void prepareGui() {
>> setLayout(new GridBagLayout());
>> GridBagConstraints c = new GridBagConstraints();
>> c.gridx = 0; c.gridy = 0;
>> c.insets = new Insets(1, 1, 1, 1);
>> c.fill = GridBagConstraints.HORIZONTAL;
>>           if (hasBC) {        // setup B&C area
>>    add(new Label("Bright"),c);
>>    c.gridx++;
>>               bScrollbar = new Scrollbar(Scrollbar.HORIZONTAL,
>> SB_FULL,
>> 1, 0, SB_FULL+1);
>>               bScrollbar.setFocusable(false); // prevents blinking on
>> Windows
>>               bScrollbar.setPreferredSize(SB_SIZE);
>>               bScrollbar.addAdjustmentListener(this);
>>               add(bScrollbar, c);
>>               c.gridx = 0; c.gridy++;
>>    add(new Label("Contr"),c);
>>    c.gridx++;
>>               cScrollbar = new Scrollbar(Scrollbar.HORIZONTAL,
>> SB_FULL,
>> 1, 0, SB_FULL+1);
>>               cScrollbar.setFocusable(false); // prevents blinking on
>> Windows
>>               cScrollbar.setPreferredSize(SB_SIZE);
>>               cScrollbar.addAdjustmentListener(this);
>>               add(cScrollbar, c);
>>               c.gridx = 0; c.gridy++;
>>               sLabel = new Label("Auto");
>>               sLabel.addMouseListener(this);
>>    add(sLabel, c);
>>    c.gridx++;
>>               sScrollbar = new Scrollbar(Scrollbar.HORIZONTAL,
>> SB_FULL,
>> 1, 0, SB_FULL+1);
>>               sScrollbar.setFocusable(false); // prevents blinking on
>> Windows
>>               sScrollbar.setPreferredSize(SB_SIZE);
>>               sScrollbar.addAdjustmentListener(this);
>>               add(sScrollbar, c);
>>
>>               calculateBC('m', imp.getProcessor()); //set initial
>> scrollbar values
>>           }
>>
>>       }
>>
>>       /** Overrides Component getPreferredSize(). Added to work
>>       around a bug in Java 1.4.1 on Mac OS X.*/
>>       public Dimension getPreferredSize() {
>>           return new Dimension(WIDTH+1, height+1);
>>       }
>>
>>
>> public void update(Graphics g) {
>> paint(g);
>> }
>>
>> public void paint(Graphics g) {
>>    super.paint(g);
>>    drawRect(g, sLabel.getBounds());
>>       }
>>
>>
>> public void mousePressed(MouseEvent e) {}
>> public void mouseReleased(MouseEvent e) {}
>> public void mouseExited(MouseEvent e) {}
>> public void mouseClicked(MouseEvent e) {
>>    Object source = e.getSource();
>>    if (source==sLabel) {
>>        autoMode = !autoMode;
>>        sLabel.setFont(sLabel.getFont().deriveFont(autoMode ?
>> Font.BOLD
>> : Font.PLAIN));
>>        calculateBC('s', imp.getProcessor());
>>    }
>> }
>> public void mouseEntered(MouseEvent e) {}
>>
>> public void adjustmentValueChanged(AdjustmentEvent e) {
>> Object source = e.getSource();
>> if (source==bScrollbar)
>> calculateBC('b', imp.getProcessor());
>> else if (source==cScrollbar)
>> calculateBC('c', imp.getProcessor());
>> else if (source==sScrollbar)
>> calculateBC('s', imp.getProcessor());
>> }
>>
>>       /** If in 'Auto' mode, adjust brightness&contrast according to
>> 'Auto' (saturated) scrollbar */
>>       public void autoAdjust(ImageProcessor ip) {
>>           if (autoMode) calculateBC('s', ip);
>>       }
>>
>>       /** Calculate brightness, contrast, min, and max; set
>> imageProcessor and scrollbars
>>         * accordingly. 'changed' can be 'b'(rightness), 'c'(ontrast),
>> 's'(aturation), 'm'(in&max)
>>         * If changed='m', the min and max of the imageProcessor sets
>> B&C
>> scrollbars
>>         * Otherwise, the scrollbar values set the imageProcessor's
>> min&max
>>         */
>>       private void calculateBC(char changed, ImageProcessor ip) {
>>           double fullMin = 0, fullMax = 255;
>>           double min = ip.getMin(), max = ip.getMax();
>>           if (!(ip instanceof ByteProcessor)) {
>>               ip.resetMinAndMax();        // calculate range of pixels
>>               fullMin = ip.getMin();
>>               fullMax = ip.getMax();
>>               if (changed=='m') ip.setMinAndMax(min, max); //revert to
>> previous min&max
>>           }
>>           double fullRange = fullMax - fullMin;
>>           if (fullRange <= 0) fullRange = 1e-100; //avoid division by
>> 0
>>           double fullMid = 0.5*(fullMax + fullMin);
>>           if (changed=='m') {
>>               sScrollbar.setValue((int)(saturated+0.5));
>>           } else if (changed=='s') {
>>               saturated = sScrollbar.getValue();
>>               min = fullMin; max=fullMax;
>>               if (saturated != 0) {
>>                   ImageStatistics stats =
>> ImageStatistics.getStatistics(ip, Measurements.MIN_MAX, null);
>>                   int[] histogram = ip.getHistogram();
>>                   int total = 0;
>>                   int first = -1, last = -1;
>>                   for (int i=0; i<histogram.length; i++) {
>>                       int n = histogram[i];
>>                       if (n > 0) {
>>                           total += n;
>>                           if (first < 0) first = i;
>>                           last = i;
>>                       }
>>                   }
>>                   int nSaturated = (int)(total*0.2*saturated/SB_FULL);
>> //max 20% saturated
>>                   int count = 0;
>>                   int iMin = first;
>>                   for (; iMin<last; iMin++) {
>>                       count += histogram[iMin];
>>                       if (count>nSaturated) break;
>>                   }
>>                   count = 0;
>>                   int iMax = last;
>>                   for (; iMax>first; iMax--) {
>>                       count += histogram[iMax];
>>                       if (count>nSaturated) break;
>>                   }
>>                   if (ip instanceof FloatProcessor) {
>>                   } else {
>>                       min = iMin; max = iMax;
>>                   }
>>               }
>> IJ.log("reset. full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange));
>>               ip.setMinAndMax(min,max);
>>               imp.updateAndDraw();
>>           }
>>           if (changed=='m' || changed=='s') { //calculate and set B&C
>> scrollbars
>>               double currentMid = 0.5*(max + min);
>>               double currentRange = max-min;
>>               if (currentRange <= 0) currentRange = 1e-100;
>> IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange));
>> IJ.log("full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange));
>>               brightness = SB_FULL * (0.5 -
>> (currentMid-fullMid)/(fullRange));
>>               contrast = SB_FULL *
>> Math.log(fullRange/currentRange/MIN_CONTRAST)/Math.log(MAX_CONTRAST/MIN_CONTRAST);
>>               bScrollbar.setValue((int)(brightness+0.5));
>>               cScrollbar.setValue((int)(contrast+0.5));
>> IJ.log("=> b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast));
>>           } else if (changed=='b' || changed=='c') {
>>               brightness = bScrollbar.getValue();
>>               contrast = cScrollbar.getValue();
>> //IJ.log("b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast));
>>               double currentMid = fullMid + fullRange*(0.5 -
>> brightness/SB_FULL);
>>               double currentRange =
>> fullRange/(MIN_CONTRAST*Math.exp(contrast/SB_FULL*Math.log(MAX_CONTRAST/MIN_CONTRAST)));
>>               min = currentMid - 0.5*currentRange;
>>               max = currentMid + 0.5*currentRange;
>> //IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange));
>> //IJ.log("=> min.max="+IJ.d2s(min)+", "+IJ.d2s(max));
>>               ip.setMinAndMax(min, max);
>>               imp.updateAndDraw();
>>           }
>>       }
>>
>>       //draw rectangle around a component
>>       private void drawRect(Graphics g, Rectangle r) {
>>           g.drawRect(r.x-1, r.y-1, r.width+1, r.height+1);
>>       }
>> } // ContrastPlot class
>>
>> ____________________________________________________________________________________________________________________
>>
>> import ij.*;
>> import ij.plugin.filter.ExtendedPlugInFilter;
>> import ij.plugin.filter.PlugInFilterRunner;
>> import ij.gui.GenericDialog;
>> import ij.gui.DialogListener;
>> import ij.process.*;
>> import java.awt.*;
>> import java.awt.event.*;
>>
>> //test plugin for Preview helper
>>
>> /*
>>    * An ImageJ ExtendedPlugInFilter that changes pixels that deviate
>> from the
>>    * two neighbors above&below by more than a certain value (the
>> threshold).
>>    * These pixels are set to the mean of these two neighbors. Pixels
>> at the
>>    * top and bottom edges are not processed.
>>    * The filter is useful for eliminating hot pixels of CCDs and
>> single
>>    * bad lines of scanning probe images.
>>    *
>>    * It demonstrates how to write an plugin-filter with preview.
>>    * Michael Schmid, 2007-05-21
>>    */
>>
>> public class  Outliers_Simple implements ExtendedPlugInFilter,
>> DialogListener {
>>       //  ExtendedPlugInFilter:   An ExtendedPlugInFilter will be
>> invoked
>> by ImageJ the
>> //                  following way:
>>       //              (1) Call to setup with the current foreground
>> ImagePlus (image window)
>>       //                  and the argument string 'arg' specified in
>> the
>> plugins.config file
>>       //                  or a.jar file that contains it.
>>       //                  The PlugInFilter returns its flags.
>>       //              (2) If the currently active image is compatible
>> with the flags (and DONE
>> //                  was not specified)  showDialog is called. If the
>> reference to the
>> //                  PlugInFilterRunner passed with showDialog is
>> specified as an argument
>> //                  to the addPreviewCheckbox method of a
>> GenericDialog, preview will be
>> //                  possible and the run method of this
>> ExtendedPlugInFilter will be called
>> //                  in the background for preview.
>>       //              (3) Unless otherwise specified in the flags
>> (DONE
>> or keeping the preview
>>       //                  as an output for a non-stack ImagePlus), the
>> run method is called.
>>       //                  For stacks, it is called repeatedly for each
>> slice.
>>       //              (4) If the FINAL_PROCESSING flag has been set
>> (but
>> not DONE), the
>> //                  setup method is invoked with "final" as parameter
>> string.
>>       //               ** If this PlugInFilter contains a showAbout()
>> method, its setup method
>>       //                  may be also called with argument string
>> "about". Then, the filter will
>>       //                  display a short help message.
>>       //
>>       //  DialogListener: This PlugInFilter provides the
>> dialogItemChanged method, whoch will
>> //                  be called if required by the addDialogListener
>> method of the
>> // GenericDialog.
>> //                  For preview-enabled ExtendedPlugInFilters, the
>> filter parameters/options
>> //                  should be set in the dialogItemChanged method.
>>
>>
>>       // F i l t e r   p a r a m e t e r s   - don't declare the
>> paramters actually used as static,
>>       //   otherwise parallel execution (in different threads with
>> different parameters) won't work.
>>       /** The threshold. Only pixels deviating by more than this value
>> will be replaced */
>>       private double threshold;
>>       /** For saving the threshold; here we also put the initial value
>> */
>>       private static double sThreshold = 10;
>> /** Whether to filter dark (instead of bright) pixels */
>> private static boolean filterDark = false;
>>       // F u r t h e r   c l a s s   v a r i a b l e s
>> /** Flags specifying the capabilities and needs of this filter. See
>> interfaces PlugInFilter and
>>        ExtendedPlugInFIlter for details */
>>       int flags =
>> DOES_ALL|SUPPORTS_MASKING|CONVERT_TO_FLOAT|SNAPSHOT|KEEP_PREVIEW|
>>               PARALLELIZE_STACKS;
>>       PreviewHelper previewHelper;
>>
>>       /** Setup of the PlugInFilter. Returns the flags specifying the
>> capabilities and needs
>>        * of the filter.
>>        *
>>        * @param arg   An argument string that may be specified, e.g.,
>> in
>> the plugins.config file of a
>>        *              jar archive containing th plugin. Unused here.
>>        * @param imp   The ImagePlus to be processed (image with
>> associated window, calibration, etc.)
>>        * @return      Flags specifying further action of the
>> PlugInFilterRunner
>>        */
>>       public int setup(String arg, ImagePlus imp) {
>>           if (arg.equals("about")) {              //this is a special
>> case - we only display the "about..." info
>>               showAbout();
>>               return DONE;
>> } else {
>>    previewHelper = new PreviewHelper(imp);
>> return flags;
>> }
>>       }
>>
>>       /** Show the dialog asking for the parameters and get them.
>>        * @return Flags specifying further action of the
>> PlugInFilterRunner
>>        */
>>       public int showDialog(ImagePlus imp, String command,
>> PlugInFilterRunner pfr) {
>>           GenericDialog gd = new GenericDialog(command+"...");
>>           gd.addNumericField("Threshold", sThreshold, 0); //use saved
>> value as default
>> gd.addCheckbox("Dark Outliers", filterDark);
>>           gd.addPreviewCheckbox(pfr);    //passing pfr makes the
>> filter
>> ready for preview
>>           gd.addDialogListener(this);    //the DialogItemChanged
>> method
>> will be called on user input
>>           gd.addPanel(previewHelper);
>>           gd.showDialog();             //display the dialog; preview
>> runs
>> in the background
>>           if (gd.wasCanceled())
>> return DONE;
>>           dialogItemChanged(gd, null);    //read the parameters
>> finally set
>>           IJ.register(this.getClass());   //protect static class
>> variables (filter parameters) from garbage collection
>>           return IJ.setupDialog(imp, flags);  //ask whether to process
>> all slices of stack (if a stack)
>>       }
>>
>>       /** If this PlugInFilter has been added as DialogListener, the
>> method dialogItemChanged
>>        * is invoked by GenericDialog every time the user changes
>> something in the dialog.
>>        * Time-consuming code should not be placed in this method and
>> any
>> methods called.
>> * @param gd A reference to the GenericDialog, needed to read the
>> input
>>        * @param e An Event characterizing what has been changed in
>> the
>> dialog.
>>        * @return Whether the dialog input is valid. If true,
>> preview
>> may be invoked.
>>        *                  (There is no need to check the state of the
>> preview Checkbox here,
>>        *                  this is done by the PlugInFilterRunner.)
>> * If false is returned (invalid input), the "OK" button of the
>> dialog
>> *                  and preview are disabled.
>>        */
>>       public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
>>           threshold = gd.getNextNumber();
>> filterDark = gd.getNextBoolean();
>>           if (gd.invalidNumber() || threshold <= 0)
>>               return false;
>>           sThreshold = threshold; // save valid value for the next
>> time
>>           return true;
>>       }
>>
>>       /** This method is invoked by ImageJ for processing. For stacks,
>> it
>> can be called once for
>>        * each slice (depending on IJ.setupDialog, where the user is
>> asked
>> whether to do so, see
>>        * end of method setup, above).
>>        * If preview is enabled and the filter parameters have changed,
>> processing should
>>        * be stopped when this thread is interrupted. Thherefore,
>> during
>> long calculations
>> * (say, longer than a tenth of a second) the following code should
>> be
>> executed repeatedly:
>>        * <code> if (Thread.currentThread().isInterrupted()) return;
>> </code>
>>        * Since this PlugInFilter has specified the CONVERT_TO_FLOAT
>> and
>> SNAPSHOT flags
>>        * it will be always called with a FloatProcessor that has a
>> valid
>> snapshot.
>>        * @param ip The ImageProcessor containing the image.
>>        */
>>       public void run(ImageProcessor ip) {
>>           // For filters with preview, the filter parameter(s) should
>> be
>> copied from the
>> // class variables set in DialogItemChanged to local variables to
>> avoid changing
>> // parameters during filtering. This can be done by calling a method
>> with
>> // the filter parameter(s) as arguments.
>> // If the filter may crash with invalid or inconsistent parameters,
>> parameter checking
>> // can be done in dialogItemChanged. In that case, both the
>> dialogItemChanged
>> // method and copying should be done <code>synchronized</code> to
>> avoid using
>> // invalid parameters. The filtering operation itself should not be
>> synchronized,
>> // otherwise it would block the dialog during processing.
>>           doFiltering((FloatProcessor)ip, (float)threshold,
>> filterDark);
>>       }
>>
>>       /** Here the filtering is really done.
>>        * This is also the method that provides the API for use in
>> other
>> plugins, it should
>>        * not use any class variables.
>>        * A static method has the advantage that unintended reading of
>> the
>> filter parameters
>>        * from the class variables (that may change asynchronously
>> during
>> preview) will be
>>        * flagged by the compiler as error.
>>        * (it cannot be static if it needs to access any class variable
>> such as the ImagePlus imp
>>        * or if we want to display a progress bar).
>>        *
>>        * This method only affects the pixels within the getRoi(ip)
>> rectangle. Pixels within the
>>        * rectangle, but outside the actual roi will be reset by ImageJ
>> if
>> SUPPORTS_MASKING
>>        * has been added to the flags in the setup method.
>>        *
>>        * @param ip A FloatProcessor containing the image data. It must
>> have a valid snapshot.
>>        * @param threshold  Determines how much a pixel value may
>> deviate
>> from the mean of the
>>        * neighboring pixels to remain unaltered. Threshold is in
>> uncalibrated units.
>>        * @param filterDark Whether dark outlies should be eliminated
>> (otherwise bright outliers are
>>        * eliminated)
>>        */
>>       public static void doFiltering(FloatProcessor ip, float
>> threshold,
>> boolean filterDark) {
>> boolean filterMax = // whether to eliminate maxima (not
>> minima)
>> (ip.isInvertedLut()&&filterDark) ||
>> (!ip.isInvertedLut()&&!filterDark);
>>           Thread thread = Thread.currentThread();     // needed to
>> check
>> for interrupted state
>>           float[] pixels = (float[])ip.getPixels();   // array of the
>> pixel values of the input image
>>           float[] snapshotPixels =
>>        (float[])ip.getSnapshotPixels();    // array with an
>> (unaltered) copy of the  pixel values
>>           int width = ip.getWidth();                  // width &
>> height
>> of the image
>>           int height = ip.getHeight();
>>           Rectangle roi = ip.getRoi();                // the rectangle
>> containing the roi (full image size if no roi)
>> int xEnd = roi.x + roi.width; // loops run only over pixels
>> inside
>> the roi
>> int yStart = roi.y==0 ? 1 : roi.y; // do not process the topmost
>> line (line y=0)
>> int yEnd = roi.y + roi.height;
>> if (yEnd == height) yEnd--; // do not process the bottommost
>> line
>> (line y=height-1)
>> for (int y=yStart; y<yEnd; y++) {
>> if (y%100==0 && thread.isInterrupted()) return;   // from time to
>> time, check whether interrupted
>>    for (int x=roi.x, p=y*width+x; x<xEnd; x++,p++) { // p points to
>> pixel (x,y) in the arrays
>>    float neighbor1 = snapshotPixels[p+width];    // the neighbor
>> at
>> (x, y+1)
>> float neighbor2 = snapshotPixels[p-width];    // the neighbor at
>> (x,
>> y-1)
>> if ((filterMax && snapshotPixels[p]>neighbor1+threshold &&
>> snapshotPixels[p]>neighbor2+threshold) ||
>>        (!filterMax && snapshotPixels[p]<neighbor1-threshold &&
>> snapshotPixels[p]<neighbor2-threshold))
>> pixels[p] = (neighbor1+neighbor2)/2;
>> }
>> }
>>       }
>>
>> /** This method required by the ExtendedPlugInFilter interface is not
>> used here.
>> *  It specifies the number of calls to the run(ip) method.
>> *  For filters displaying a progress bar, nPasses is needed to
>> display a
>> *  smooth progress bar when processing stacks. */
>> public void setNPasses (int nPasses) {}
>>
>>       /** Show the "About..." text.
>>        *  This method must be named "showAbout", then the plugin is
>> added
>> to the
>> *  Help>About Plugins menu
>>        */
>>       private void showAbout() {
>> IJ.showMessage("About Outliers...",
>> "This plug-in filter alters pixels that deviate from their \n"+
>> "neighbors above and below by more than a given threshold. \n" +
>> "These outliers are set to the mean of these neighbors. \n" +
>> "Edge pixels at the top and bottom of the image are not \n" +
>> "processed."
>> );
>>       }
>> }
>>
>> ____________________________________________________________________________________________________________________
>>
>> --
>> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>>
>> --
>> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>>
>
> --
> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>
> --
> ImageJ mailing list: http://imagej.nih.gov/ij/list.html

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html
Reply | Threaded
Open this post in threaded view
|

Re: NonBlockingGenericDialog for Process>Filters

CARL Philippe (LBP)
Dear Michael,
You will find modified versions (i.e. using NonBlockingGenericDialog and getting rid of the static parameters) of the filters plugins under the following links:
        http://punias.free.fr/ImageJ/NonBlocking/RankFilters.java
        http://punias.free.fr/ImageJ/NonBlocking/GaussianBlur.java
        http://punias.free.fr/ImageJ/NonBlocking/UnsharpMask.java
        http://punias.free.fr/ImageJ/NonBlocking/Convolver.java
I didn't try to generate NonBlockingGenericDialog versions for the 3D filters plugins given that they didn't have a preview button.
Also I have to admit that I don't understand your recommendations about what should be done with the use of the ij.process.ImageProcessor.snapshot() method.
My best regards,
Philippe

Philippe CARL
Laboratoire de Bioimagerie et Pathologies
UMR 7021 CNRS - Université de Strasbourg
Faculté de Pharmacie
74 route du Rhin
67401 ILLKIRCH
Tel : +33(0)3 68 85 41 84

-----Message d'origine-----
De : ImageJ Interest Group [mailto:[hidden email]] De la part de Michael Schmid
Envoyé : mercredi 27 février 2019 18:50
À : [hidden email]
Objet : Re: NonBlockingGenericDialog for Process>Filters

Bonjour Philippe, and Hi everyone in the non-francophone regions,

there is more to care about when using NonBlockingGenericDialog for the
filters:
Any action that overwrites the undo buffer ("snapshot") will interfere
with preview (which uses the snapshot).

Plugins that lock the image (all PlugInFilters) should be no problem;
running any of these on a locked image should cause an error message.

I did a quick search for "snapshot" and found the following potentially
problematic ones (currently none of them locks the image they work on):
ContrastEnhancer
GelAnalyzer
ImageCalculator *
ScaleBar
Scaler *
ContrastAdjuster
RoiManager
  * I think these should lock the image anyhow

Out of these, the ContrastAdjuster (Brightness&Contrast) and
ContrastEnhancer are most relevant; if the user tries to adjust the
contrast during preview to better see the result, and if there is either
a ROI or the image is RGB, it will make it impossible to revert preview.

A possible way out might be keeping the snapshot during preview, and
using setSnapshotPixels before reverting from preview. But this will
need more thought.

What one should also think of is whether creating or modifying ROIs
should be blocked while an image is locked.

To me, the snapshot issue is currently the most severe problem (Wayne,
are you aware of any others? Or someone else?).

Concerning the static variables, I think that the cleaner and easier
solution would be fixing it in the relevant plugins; then we need no
mechanism to block concurrent running of GaussianBlur etc.


Best,

Michael
________________________________________________________________


On 2019-02-26 12:05, Philippe CARL wrote:

> Servus Michael,
> To play with several Gaussian Blur windows in parallel, I indeed
> didn't think about it.
> And considering static parameters definitions, I now agree completely
> with you that this will indeed be an issue.
> My idea (and actually need) behind NonBlockingGenericDialog filters
> was to be nice and gentle with them, by only using "regular" tools in
> parallel with these filters like zoom, drag, threshold, ..., i.e.
> tools where there would be no static parameter definition issues.
> So in order to fix this potential static parameter issue, what would
> you think about restricting the creation of only one instance of these
> filters (potentially throwing an error message in the case a user
> wants to create a second one)?
> This solution would enlarge the analysis versatility, providing in the
> same the "needed security" for the static parameters.
> As the issue with the headless mode, it could be solved with the
> addition of a new option within "Edit>Options>Misc..." window.
> Experimental test plugin: Ok I saw these 3 sliders. But thinking more
> about it and considering what I described higher, i.e. the combined
> use of the tools zoom, drag and threshold with a filter, I think that
> your idea becomes already too complicated.
> I also saw that the Analyze>Analyze-particles... tool now excludes the
> holes. Thanks a lot!!!
> Kindest regards,
> Philippe
>
> -----Message d'origine-----
> De : ImageJ Interest Group [mailto:[hidden email]] De la part de
> Michael Schmid
> Envoyé : lundi 25 février 2019 17:02
> À : [hidden email]
> Objet : Re: NonBlockingGenericDialog for Process>Filters
>
> Hi Philippe,
>
> a typical case of two Gaussian Blurs interfering would be the
> following:
>
> Have one Gaussian Blur of a stack running (a large stack so it take
> some
> time) and at the same time do a preview of another GaussianBlur with a
> different radius (sigma). Then, as soon as the preview starts, the
> remaining slices of the stack will be processed with the sigma of the
> preview, not the one that should be used for the stack.
>
> In principle, you can do the same thing already now (I just tried with
> a
> 1024x1024x1024 stack that needs about 15-20 seconds for sigma=7, and
> quickly started another GaussianBlur on another image with different
> sigma before the stack was fully processed - the last slices stack got
> blurred with sigma intended for the other image). Fortunately, it is
> rather unlikely that this happens in practice.
>
> It is much more likely that this kind of problem occurs if one can have
> several GaussianBlur dialogs open at one time, e.g., to compare how it
> affects different images. Then it would be much more likely that
> GaussianBlur runs on two images/stacks at the same moment.
>
> Of course, the clean solution would be making the class variables for
> the parameters non-static, and adding the static ones for saving the
> parameters, in GaussianBlur, UnsharpMask, Convolver, and ImageMath (the
> BackgroundSubtracter & RankFilters seem to be fine). In principle, it
> would be easy, but it has to be done.
>
> Then, we still have to care about the headless mode...
>
> --
> Experimental test plugin: There should be three additional sliders
> (rather narrow ones) for adjusting B&C, but only for grayscale images
> (not for RGB).
>
>
> Michael
> ________________________________________________________________
> On 25.02.19 15:41, Philippe CARL wrote:
>> Gruss Gott Michael,
>> I made as well a GenericDialog to NonBlockingGenericDialog conversion
>> onto the GaussianBlur.java file, replaced the compiled
>> GaussianBlur.class within the IJ.jar and then made all crazy things I
>> could think of which could be problematic having static variables.
>> More precisely, I opened two pictures, launched a Gaussian blurr on
>> one of it, activate the preview button and then selected the other
>> one, applied a Contras&Brightness update and then modified the
>> Gaussian Blurr filter values and the modifications were only applied
>> on the picture on which the filter had been launched.
>> Alternatively, I launched a Gaussian blur on one picture, activated
>> the preview button, than opened another one, played with it, and
>> finally modified the filter values and the updates were on the picture
>> on which the filter had at first be applied.
>> So which issues could up to you be found on some filters due to static
>> variables?
>> I compiled as well the codes you had as attachment, but wasn't able to
>> see any special zoom and drag buttons (unless I missed something) when
>> I launched them.
>> Thanks a lot on advance for your very lightened answer.
>> Kindest regards,
>> Philippe
>>
>> -----Message d'origine-----
>> De : ImageJ Interest Group [mailto:[hidden email]] De la part de
>> Michael Schmid
>> Envoyé : lundi 25 février 2019 13:17
>> À : [hidden email]
>> Objet : Re: NonBlockingGenericDialog for Process>Filters
>>
>> Hi Philippe,
>>
>> well, I fear that making standard PlugInFilter dialogs non-modal could
>> cause more problems than just headless execution.
>>
>> Unfortunately, most filters are not multithreading-safe. With a
>> non-modal dialog there would be nothing that prevents the user to use
>> the same filter on two different images at the same time, which would
>> result in unpredictable behavior.
>> I think that the RankFilters (Mean, Variance, Median, Min, Max) are
>> fine.
>> Gaussian Blur, Convolve, and the 3D filters use static variables for
>> the
>> filter parameters, so they must remain modal.
>>
>> What I had thought about a while ago was creating a GenericDialog
>> field
>> that would allow the user to pan and zoom in/out with a line of small
>> buttons (arrows and +/-) or scrollbars during preview. Maybe also
>> dragging in a small square representing the image.
>> Same for brightness&contrast.
>> I had an experimental version for B&C with scrollbars, but these were
>> too clumsy and then I did not find time to continue on that project.
>> In
>> case you want to make something nice out of it (or someone else wants
>> to
>> do this), feel free to use my experimental code pasted at the very
>> bottom!
>>
>>
>> Michael
>> ________________________________________________________________
>> On 25.02.19 11:53, Philippe CARL wrote:
>>> Dear Curtis,
>>> Given that I never use ImageJ in headless mode, I wasn't aware of
>>> this potential issue.
>>> Thus what would you think about adding an additional option within
>>> "Edit>Options>Misc..." which would give the possibility to choose
>>> between GenericDialog or NonBlockingGenericDialog dialogs within all
>>> the filter tools?
>>> This could give an agreement between both functionalities.
>>> My best regards,
>>> Philippe
>>>
>>> -----Message d'origine-----
>>> De : ImageJ Interest Group [mailto:[hidden email]] De la part de
>>> Curtis Rueden
>>> Envoyé : vendredi 22 février 2019 19:01
>>> À : [hidden email]
>>> Objet : Re: NonBlockingGenericDialog for Process>Filters
>>>
>>> Hi Philippe,
>>>
>>>> Is there a reason why the Process>Filters are written with a
>>>> GenericDialog and not a NonBlockingGenericDialog?
>>>
>>> One reason is that ImageJ2's headless support currently only supports
>>> GenericDialog, not NonBlockingGenericDialog.
>>>
>>> Consider the following macro:
>>>
>>>     run("Blobs (25K)");
>>>     run("Variance...", "radius=2");
>>>     run("Save", "save=/Users/curtis/Desktop/blobs.tif");
>>>
>>> With ImageJ2, you can execute this headless from the command line as
>>> follows:
>>>
>>>     Contents/MacOS/ImageJ-macosx --headless -macro
>>> ~/Desktop/variance.ijm
>>>
>>> Where "Contents/MacOS/ImageJ-macosx" is the ImageJ launcher for your
>>> platform, and "~/Desktop/variance.ijm" is the path to that macro file
>>> on
>>> disk.
>>>
>>> The result will be a file "/Users/curtis/Desktop/blobs.tif" with the
>>> variance filter applied to the Blobs sample image.
>>>
>>> If you make the change from GenericDialog to
>>> NonBlockingGenericDialog,
>>> there will instead be an error as follows:
>>>
>>>     java.lang.VerifyError: Bad type on operand stack
>>>     Exception Details:
>>>       Location:
>>>         ij/gui/NonBlockingGenericDialog.dispose()V @5: invokestatic
>>>       Reason:
>>>         Type 'ij/gui/NonBlockingGenericDialog' (current frame,
>>> stack[0]) is
>>> not assignable to 'java/awt/Window'
>>>
>>> Followed by some details to assist with debugging.
>>>
>>> If ImageJ1 switches from GenericDialog to NonBlockingGenericDialog,
>>> work
>>> will need to be done to improve the ImageJ Legacy component to
>>> support
>>> NonBlockingGenericDialog in headless mode.
>>>
>>> Regards,
>>> Curtis
>>>
>>> --
>>> Curtis Rueden
>>> LOCI software architect - https://loci.wisc.edu/software
>>> ImageJ2 lead, Fiji maintainer - https://imagej.net/User:Rueden
>>> Have you tried the Image.sc Forum? https://forum.image.sc/
>>>
>>>
>>>
>>> On Fri, Feb 22, 2019 at 11:09 AM Philippe CARL
>>> <[hidden email]>
>>> wrote:
>>>
>>>> Dear all (probably Wayne),
>>>> Is there a reason why the Process>Filters are written with a
>>>> GenericDialog
>>>> and not a NonBlockingGenericDialog?
>>>> Using a NonBlockingGenericDialog has the huge advantage of being
>>>> able to
>>>> make a preview on a picture with a given filter while still being
>>>> able to
>>>> zoom and drag it in order to check the effect of the filter on small
>>>> details
>>>> within the picture.
>>>> And in order to do this, all that is needed to be changed is to:
>>>>           - Add on line 5: import ij.gui.NonBlockingGenericDialog;
>>>>           - Replace the line 112  from "GenericDialog gd = new
>>>> GenericDialog(command+"...");"
>>>>                                           into "
>>>> NonBlockingGenericDialog gd
>>>> =
>>>> new NonBlockingGenericDialog (command+"...");"
>>>> Within the "ij.plugin.filter.RankFilters.java" file.
>>>> I thank you very much in advance for your answers.
>>>> My best regards,
>>>> Philippe
>>>>
>>>> Philippe CARL
>>>> Laboratoire de Bioimagerie et Pathologies
>>>> UMR 7021 CNRS - Université de Strasbourg
>> ____________________________________________________________________________________________________________________
>> import ij.*;
>> import ij.measure.Measurements;
>> import ij.process.*;
>> import java.awt.*;
>> import java.awt.event.*;
>>
>>
>> public class PreviewHelper extends Panel implements MouseListener,
>> AdjustmentListener {
>> final static int WIDTH = 250;
>> final static Dimension SB_SIZE = new Dimension(120,8);
>> final static int BC_HEIGHT = 100;   // brightness/contrast area
>> final static int ZS_HEIGHT = 100;   // zoom/scroll area
>> final static int SB_FULL = 200;     // scrollbar steps
>> final static double MIN_CONTRAST = 0.25;
>> final static double MAX_CONTRAST = 128;
>>       final ImagePlus imp;
>>       final boolean hasBC;        //whether we have a
>> Brightness&Contrast
>> subpanel
>>       private final int height;
>>       private Scrollbar bScrollbar, cScrollbar, sScrollbar;
>>       private double brightness, contrast;// 0 - SB_FULL range each
>>       private static double saturated;   // 0 - SB_FULL range, is
>> remembered
>>       private boolean autoMode;           // saturated overrides
>> min&max
>> if true
>>       Label rangeText;                    // text displaying min&max
>>       Label sLabel;                       // 'auto contrast'
>> (saturated)
>> label
>>
>> public PreviewHelper(ImagePlus imp) {
>>           this.imp = imp;
>>           hasBC = imp.getType() != ImagePlus.COLOR_RGB;
>>           height = hasBC ? ZS_HEIGHT : BC_HEIGHT + ZS_HEIGHT;
>>           prepareGui();
>>       }
>>
>>       private void prepareGui() {
>> setLayout(new GridBagLayout());
>> GridBagConstraints c = new GridBagConstraints();
>> c.gridx = 0; c.gridy = 0;
>> c.insets = new Insets(1, 1, 1, 1);
>> c.fill = GridBagConstraints.HORIZONTAL;
>>           if (hasBC) {        // setup B&C area
>>    add(new Label("Bright"),c);
>>    c.gridx++;
>>               bScrollbar = new Scrollbar(Scrollbar.HORIZONTAL,
>> SB_FULL,
>> 1, 0, SB_FULL+1);
>>               bScrollbar.setFocusable(false); // prevents blinking on
>> Windows
>>               bScrollbar.setPreferredSize(SB_SIZE);
>>               bScrollbar.addAdjustmentListener(this);
>>               add(bScrollbar, c);
>>               c.gridx = 0; c.gridy++;
>>    add(new Label("Contr"),c);
>>    c.gridx++;
>>               cScrollbar = new Scrollbar(Scrollbar.HORIZONTAL,
>> SB_FULL,
>> 1, 0, SB_FULL+1);
>>               cScrollbar.setFocusable(false); // prevents blinking on
>> Windows
>>               cScrollbar.setPreferredSize(SB_SIZE);
>>               cScrollbar.addAdjustmentListener(this);
>>               add(cScrollbar, c);
>>               c.gridx = 0; c.gridy++;
>>               sLabel = new Label("Auto");
>>               sLabel.addMouseListener(this);
>>    add(sLabel, c);
>>    c.gridx++;
>>               sScrollbar = new Scrollbar(Scrollbar.HORIZONTAL,
>> SB_FULL,
>> 1, 0, SB_FULL+1);
>>               sScrollbar.setFocusable(false); // prevents blinking on
>> Windows
>>               sScrollbar.setPreferredSize(SB_SIZE);
>>               sScrollbar.addAdjustmentListener(this);
>>               add(sScrollbar, c);
>>
>>               calculateBC('m', imp.getProcessor()); //set initial
>> scrollbar values
>>           }
>>
>>       }
>>
>>       /** Overrides Component getPreferredSize(). Added to work
>>       around a bug in Java 1.4.1 on Mac OS X.*/
>>       public Dimension getPreferredSize() {
>>           return new Dimension(WIDTH+1, height+1);
>>       }
>>
>>
>> public void update(Graphics g) {
>> paint(g);
>> }
>>
>> public void paint(Graphics g) {
>>    super.paint(g);
>>    drawRect(g, sLabel.getBounds());
>>       }
>>
>>
>> public void mousePressed(MouseEvent e) {}
>> public void mouseReleased(MouseEvent e) {}
>> public void mouseExited(MouseEvent e) {}
>> public void mouseClicked(MouseEvent e) {
>>    Object source = e.getSource();
>>    if (source==sLabel) {
>>        autoMode = !autoMode;
>>        sLabel.setFont(sLabel.getFont().deriveFont(autoMode ?
>> Font.BOLD
>> : Font.PLAIN));
>>        calculateBC('s', imp.getProcessor());
>>    }
>> }
>> public void mouseEntered(MouseEvent e) {}
>>
>> public void adjustmentValueChanged(AdjustmentEvent e) {
>> Object source = e.getSource();
>> if (source==bScrollbar)
>> calculateBC('b', imp.getProcessor());
>> else if (source==cScrollbar)
>> calculateBC('c', imp.getProcessor());
>> else if (source==sScrollbar)
>> calculateBC('s', imp.getProcessor());
>> }
>>
>>       /** If in 'Auto' mode, adjust brightness&contrast according to
>> 'Auto' (saturated) scrollbar */
>>       public void autoAdjust(ImageProcessor ip) {
>>           if (autoMode) calculateBC('s', ip);
>>       }
>>
>>       /** Calculate brightness, contrast, min, and max; set
>> imageProcessor and scrollbars
>>         * accordingly. 'changed' can be 'b'(rightness), 'c'(ontrast),
>> 's'(aturation), 'm'(in&max)
>>         * If changed='m', the min and max of the imageProcessor sets
>> B&C
>> scrollbars
>>         * Otherwise, the scrollbar values set the imageProcessor's
>> min&max
>>         */
>>       private void calculateBC(char changed, ImageProcessor ip) {
>>           double fullMin = 0, fullMax = 255;
>>           double min = ip.getMin(), max = ip.getMax();
>>           if (!(ip instanceof ByteProcessor)) {
>>               ip.resetMinAndMax();        // calculate range of pixels
>>               fullMin = ip.getMin();
>>               fullMax = ip.getMax();
>>               if (changed=='m') ip.setMinAndMax(min, max); //revert to
>> previous min&max
>>           }
>>           double fullRange = fullMax - fullMin;
>>           if (fullRange <= 0) fullRange = 1e-100; //avoid division by
>> 0
>>           double fullMid = 0.5*(fullMax + fullMin);
>>           if (changed=='m') {
>>               sScrollbar.setValue((int)(saturated+0.5));
>>           } else if (changed=='s') {
>>               saturated = sScrollbar.getValue();
>>               min = fullMin; max=fullMax;
>>               if (saturated != 0) {
>>                   ImageStatistics stats =
>> ImageStatistics.getStatistics(ip, Measurements.MIN_MAX, null);
>>                   int[] histogram = ip.getHistogram();
>>                   int total = 0;
>>                   int first = -1, last = -1;
>>                   for (int i=0; i<histogram.length; i++) {
>>                       int n = histogram[i];
>>                       if (n > 0) {
>>                           total += n;
>>                           if (first < 0) first = i;
>>                           last = i;
>>                       }
>>                   }
>>                   int nSaturated = (int)(total*0.2*saturated/SB_FULL);
>> //max 20% saturated
>>                   int count = 0;
>>                   int iMin = first;
>>                   for (; iMin<last; iMin++) {
>>                       count += histogram[iMin];
>>                       if (count>nSaturated) break;
>>                   }
>>                   count = 0;
>>                   int iMax = last;
>>                   for (; iMax>first; iMax--) {
>>                       count += histogram[iMax];
>>                       if (count>nSaturated) break;
>>                   }
>>                   if (ip instanceof FloatProcessor) {
>>                   } else {
>>                       min = iMin; max = iMax;
>>                   }
>>               }
>> IJ.log("reset. full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange));
>>               ip.setMinAndMax(min,max);
>>               imp.updateAndDraw();
>>           }
>>           if (changed=='m' || changed=='s') { //calculate and set B&C
>> scrollbars
>>               double currentMid = 0.5*(max + min);
>>               double currentRange = max-min;
>>               if (currentRange <= 0) currentRange = 1e-100;
>> IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange));
>> IJ.log("full:"+IJ.d2s(fullMid)+"+-"+IJ.d2s(0.5*fullRange));
>>               brightness = SB_FULL * (0.5 -
>> (currentMid-fullMid)/(fullRange));
>>               contrast = SB_FULL *
>> Math.log(fullRange/currentRange/MIN_CONTRAST)/Math.log(MAX_CONTRAST/MIN_CONTRAST);
>>               bScrollbar.setValue((int)(brightness+0.5));
>>               cScrollbar.setValue((int)(contrast+0.5));
>> IJ.log("=> b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast));
>>           } else if (changed=='b' || changed=='c') {
>>               brightness = bScrollbar.getValue();
>>               contrast = cScrollbar.getValue();
>> //IJ.log("b="+IJ.d2s(brightness)+" c="+IJ.d2s(contrast));
>>               double currentMid = fullMid + fullRange*(0.5 -
>> brightness/SB_FULL);
>>               double currentRange =
>> fullRange/(MIN_CONTRAST*Math.exp(contrast/SB_FULL*Math.log(MAX_CONTRAST/MIN_CONTRAST)));
>>               min = currentMid - 0.5*currentRange;
>>               max = currentMid + 0.5*currentRange;
>> //IJ.log("current:"+IJ.d2s(currentMid)+"+-"+IJ.d2s(0.5*currentRange));
>> //IJ.log("=> min.max="+IJ.d2s(min)+", "+IJ.d2s(max));
>>               ip.setMinAndMax(min, max);
>>               imp.updateAndDraw();
>>           }
>>       }
>>
>>       //draw rectangle around a component
>>       private void drawRect(Graphics g, Rectangle r) {
>>           g.drawRect(r.x-1, r.y-1, r.width+1, r.height+1);
>>       }
>> } // ContrastPlot class
>>
>> ____________________________________________________________________________________________________________________
>>
>> import ij.*;
>> import ij.plugin.filter.ExtendedPlugInFilter;
>> import ij.plugin.filter.PlugInFilterRunner;
>> import ij.gui.GenericDialog;
>> import ij.gui.DialogListener;
>> import ij.process.*;
>> import java.awt.*;
>> import java.awt.event.*;
>>
>> //test plugin for Preview helper
>>
>> /*
>>    * An ImageJ ExtendedPlugInFilter that changes pixels that deviate
>> from the
>>    * two neighbors above&below by more than a certain value (the
>> threshold).
>>    * These pixels are set to the mean of these two neighbors. Pixels
>> at the
>>    * top and bottom edges are not processed.
>>    * The filter is useful for eliminating hot pixels of CCDs and
>> single
>>    * bad lines of scanning probe images.
>>    *
>>    * It demonstrates how to write an plugin-filter with preview.
>>    * Michael Schmid, 2007-05-21
>>    */
>>
>> public class  Outliers_Simple implements ExtendedPlugInFilter,
>> DialogListener {
>>       //  ExtendedPlugInFilter:   An ExtendedPlugInFilter will be
>> invoked
>> by ImageJ the
>> //                  following way:
>>       //              (1) Call to setup with the current foreground
>> ImagePlus (image window)
>>       //                  and the argument string 'arg' specified in
>> the
>> plugins.config file
>>       //                  or a.jar file that contains it.
>>       //                  The PlugInFilter returns its flags.
>>       //              (2) If the currently active image is compatible
>> with the flags (and DONE
>> //                  was not specified)  showDialog is called. If the
>> reference to the
>> //                  PlugInFilterRunner passed with showDialog is
>> specified as an argument
>> //                  to the addPreviewCheckbox method of a
>> GenericDialog, preview will be
>> //                  possible and the run method of this
>> ExtendedPlugInFilter will be called
>> //                  in the background for preview.
>>       //              (3) Unless otherwise specified in the flags
>> (DONE
>> or keeping the preview
>>       //                  as an output for a non-stack ImagePlus), the
>> run method is called.
>>       //                  For stacks, it is called repeatedly for each
>> slice.
>>       //              (4) If the FINAL_PROCESSING flag has been set
>> (but
>> not DONE), the
>> //                  setup method is invoked with "final" as parameter
>> string.
>>       //               ** If this PlugInFilter contains a showAbout()
>> method, its setup method
>>       //                  may be also called with argument string
>> "about". Then, the filter will
>>       //                  display a short help message.
>>       //
>>       //  DialogListener: This PlugInFilter provides the
>> dialogItemChanged method, whoch will
>> //                  be called if required by the addDialogListener
>> method of the
>> // GenericDialog.
>> //                  For preview-enabled ExtendedPlugInFilters, the
>> filter parameters/options
>> //                  should be set in the dialogItemChanged method.
>>
>>
>>       // F i l t e r   p a r a m e t e r s   - don't declare the
>> paramters actually used as static,
>>       //   otherwise parallel execution (in different threads with
>> different parameters) won't work.
>>       /** The threshold. Only pixels deviating by more than this value
>> will be replaced */
>>       private double threshold;
>>       /** For saving the threshold; here we also put the initial value
>> */
>>       private static double sThreshold = 10;
>> /** Whether to filter dark (instead of bright) pixels */
>> private static boolean filterDark = false;
>>       // F u r t h e r   c l a s s   v a r i a b l e s
>> /** Flags specifying the capabilities and needs of this filter. See
>> interfaces PlugInFilter and
>>        ExtendedPlugInFIlter for details */
>>       int flags =
>> DOES_ALL|SUPPORTS_MASKING|CONVERT_TO_FLOAT|SNAPSHOT|KEEP_PREVIEW|
>>               PARALLELIZE_STACKS;
>>       PreviewHelper previewHelper;
>>
>>       /** Setup of the PlugInFilter. Returns the flags specifying the
>> capabilities and needs
>>        * of the filter.
>>        *
>>        * @param arg   An argument string that may be specified, e.g.,
>> in
>> the plugins.config file of a
>>        *              jar archive containing th plugin. Unused here.
>>        * @param imp   The ImagePlus to be processed (image with
>> associated window, calibration, etc.)
>>        * @return      Flags specifying further action of the
>> PlugInFilterRunner
>>        */
>>       public int setup(String arg, ImagePlus imp) {
>>           if (arg.equals("about")) {              //this is a special
>> case - we only display the "about..." info
>>               showAbout();
>>               return DONE;
>> } else {
>>    previewHelper = new PreviewHelper(imp);
>> return flags;
>> }
>>       }
>>
>>       /** Show the dialog asking for the parameters and get them.
>>        * @return Flags specifying further action of the
>> PlugInFilterRunner
>>        */
>>       public int showDialog(ImagePlus imp, String command,
>> PlugInFilterRunner pfr) {
>>           GenericDialog gd = new GenericDialog(command+"...");
>>           gd.addNumericField("Threshold", sThreshold, 0); //use saved
>> value as default
>> gd.addCheckbox("Dark Outliers", filterDark);
>>           gd.addPreviewCheckbox(pfr);    //passing pfr makes the
>> filter
>> ready for preview
>>           gd.addDialogListener(this);    //the DialogItemChanged
>> method
>> will be called on user input
>>           gd.addPanel(previewHelper);
>>           gd.showDialog();             //display the dialog; preview
>> runs
>> in the background
>>           if (gd.wasCanceled())
>> return DONE;
>>           dialogItemChanged(gd, null);    //read the parameters
>> finally set
>>           IJ.register(this.getClass());   //protect static class
>> variables (filter parameters) from garbage collection
>>           return IJ.setupDialog(imp, flags);  //ask whether to process
>> all slices of stack (if a stack)
>>       }
>>
>>       /** If this PlugInFilter has been added as DialogListener, the
>> method dialogItemChanged
>>        * is invoked by GenericDialog every time the user changes
>> something in the dialog.
>>        * Time-consuming code should not be placed in this method and
>> any
>> methods called.
>> * @param gd A reference to the GenericDialog, needed to read the
>> input
>>        * @param e An Event characterizing what has been changed in
>> the
>> dialog.
>>        * @return Whether the dialog input is valid. If true,
>> preview
>> may be invoked.
>>        *                  (There is no need to check the state of the
>> preview Checkbox here,
>>        *                  this is done by the PlugInFilterRunner.)
>> * If false is returned (invalid input), the "OK" button of the
>> dialog
>> *                  and preview are disabled.
>>        */
>>       public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
>>           threshold = gd.getNextNumber();
>> filterDark = gd.getNextBoolean();
>>           if (gd.invalidNumber() || threshold <= 0)
>>               return false;
>>           sThreshold = threshold; // save valid value for the next
>> time
>>           return true;
>>       }
>>
>>       /** This method is invoked by ImageJ for processing. For stacks,
>> it
>> can be called once for
>>        * each slice (depending on IJ.setupDialog, where the user is
>> asked
>> whether to do so, see
>>        * end of method setup, above).
>>        * If preview is enabled and the filter parameters have changed,
>> processing should
>>        * be stopped when this thread is interrupted. Thherefore,
>> during
>> long calculations
>> * (say, longer than a tenth of a second) the following code should
>> be
>> executed repeatedly:
>>        * <code> if (Thread.currentThread().isInterrupted()) return;
>> </code>
>>        * Since this PlugInFilter has specified the CONVERT_TO_FLOAT
>> and
>> SNAPSHOT flags
>>        * it will be always called with a FloatProcessor that has a
>> valid
>> snapshot.
>>        * @param ip The ImageProcessor containing the image.
>>        */
>>       public void run(ImageProcessor ip) {
>>           // For filters with preview, the filter parameter(s) should
>> be
>> copied from the
>> // class variables set in DialogItemChanged to local variables to
>> avoid changing
>> // parameters during filtering. This can be done by calling a method
>> with
>> // the filter parameter(s) as arguments.
>> // If the filter may crash with invalid or inconsistent parameters,
>> parameter checking
>> // can be done in dialogItemChanged. In that case, both the
>> dialogItemChanged
>> // method and copying should be done <code>synchronized</code> to
>> avoid using
>> // invalid parameters. The filtering operation itself should not be
>> synchronized,
>> // otherwise it would block the dialog during processing.
>>           doFiltering((FloatProcessor)ip, (float)threshold,
>> filterDark);
>>       }
>>
>>       /** Here the filtering is really done.
>>        * This is also the method that provides the API for use in
>> other
>> plugins, it should
>>        * not use any class variables.
>>        * A static method has the advantage that unintended reading of
>> the
>> filter parameters
>>        * from the class variables (that may change asynchronously
>> during
>> preview) will be
>>        * flagged by the compiler as error.
>>        * (it cannot be static if it needs to access any class variable
>> such as the ImagePlus imp
>>        * or if we want to display a progress bar).
>>        *
>>        * This method only affects the pixels within the getRoi(ip)
>> rectangle. Pixels within the
>>        * rectangle, but outside the actual roi will be reset by ImageJ
>> if
>> SUPPORTS_MASKING
>>        * has been added to the flags in the setup method.
>>        *
>>        * @param ip A FloatProcessor containing the image data. It must
>> have a valid snapshot.
>>        * @param threshold  Determines how much a pixel value may
>> deviate
>> from the mean of the
>>        * neighboring pixels to remain unaltered. Threshold is in
>> uncalibrated units.
>>        * @param filterDark Whether dark outlies should be eliminated
>> (otherwise bright outliers are
>>        * eliminated)
>>        */
>>       public static void doFiltering(FloatProcessor ip, float
>> threshold,
>> boolean filterDark) {
>> boolean filterMax = // whether to eliminate maxima (not
>> minima)
>> (ip.isInvertedLut()&&filterDark) ||
>> (!ip.isInvertedLut()&&!filterDark);
>>           Thread thread = Thread.currentThread();     // needed to
>> check
>> for interrupted state
>>           float[] pixels = (float[])ip.getPixels();   // array of the
>> pixel values of the input image
>>           float[] snapshotPixels =
>>        (float[])ip.getSnapshotPixels();    // array with an
>> (unaltered) copy of the  pixel values
>>           int width = ip.getWidth();                  // width &
>> height
>> of the image
>>           int height = ip.getHeight();
>>           Rectangle roi = ip.getRoi();                // the rectangle
>> containing the roi (full image size if no roi)
>> int xEnd = roi.x + roi.width; // loops run only over pixels
>> inside
>> the roi
>> int yStart = roi.y==0 ? 1 : roi.y; // do not process the topmost
>> line (line y=0)
>> int yEnd = roi.y + roi.height;
>> if (yEnd == height) yEnd--; // do not process the bottommost
>> line
>> (line y=height-1)
>> for (int y=yStart; y<yEnd; y++) {
>> if (y%100==0 && thread.isInterrupted()) return;   // from time to
>> time, check whether interrupted
>>    for (int x=roi.x, p=y*width+x; x<xEnd; x++,p++) { // p points to
>> pixel (x,y) in the arrays
>>    float neighbor1 = snapshotPixels[p+width];    // the neighbor
>> at
>> (x, y+1)
>> float neighbor2 = snapshotPixels[p-width];    // the neighbor at
>> (x,
>> y-1)
>> if ((filterMax && snapshotPixels[p]>neighbor1+threshold &&
>> snapshotPixels[p]>neighbor2+threshold) ||
>>        (!filterMax && snapshotPixels[p]<neighbor1-threshold &&
>> snapshotPixels[p]<neighbor2-threshold))
>> pixels[p] = (neighbor1+neighbor2)/2;
>> }
>> }
>>       }
>>
>> /** This method required by the ExtendedPlugInFilter interface is not
>> used here.
>> *  It specifies the number of calls to the run(ip) method.
>> *  For filters displaying a progress bar, nPasses is needed to
>> display a
>> *  smooth progress bar when processing stacks. */
>> public void setNPasses (int nPasses) {}
>>
>>       /** Show the "About..." text.
>>        *  This method must be named "showAbout", then the plugin is
>> added
>> to the
>> *  Help>About Plugins menu
>>        */
>>       private void showAbout() {
>> IJ.showMessage("About Outliers...",
>> "This plug-in filter alters pixels that deviate from their \n"+
>> "neighbors above and below by more than a given threshold. \n" +
>> "These outliers are set to the mean of these neighbors. \n" +
>> "Edge pixels at the top and bottom of the image are not \n" +
>> "processed."
>> );
>>       }
>> }
>>
>> ____________________________________________________________________________________________________________________
>>
>> --
>> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>>
>> --
>> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>>
>
> --
> ImageJ mailing list: http://imagej.nih.gov/ij/list.html
>
> --
> ImageJ mailing list: http://imagej.nih.gov/ij/list.html

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html

--
ImageJ mailing list: http://imagej.nih.gov/ij/list.html