How do I create an RGB to Float filter (with preview and undo)?

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

How do I create an RGB to Float filter (with preview and undo)?

Chris Pudney
G'day,

I'm working on an ExtendedPlugInFilter that takes an RGB image as input and produces a floating-point image as output. The processing is done "in place", i.e. the result image replaces the input image.

I'd like the filter dialog to provide a preview checkbox, so the user can interactively adjust filter parameters.

I also want the user to be able to undo (^Z) the filter if necessary.

I've come up with a solution (the code is included below) but it has a kludge "smell".

To convert the input RGB image to an output float image I create a FloatProcessor, write the processing results to it, and assign it to the input image's processor.

In order to preview the processing results I add a listener to the filter dialog's preview checkbox, and switch the input image's processor between the RGB and float processors:

        final Checkbox checkbox = dialog.getPreviewCheckbox();
        checkbox.addItemListener(new ItemListener()
        {
            @Override
            public void itemStateChanged(final ItemEvent itemEvent)
            {
                imp.setProcessor(checkbox.getState() ? floatProcessor : rgbProcessor);
            }
        });

In order to support undoing the filter I need to explicitly call Undo.setup(Undo.TYPE_CONVERSION, imp) because by default PluginFilter uses             Undo.setup(Undo.FILTER, imp) which isn't effective when the image's processor has been replaced. Additionally, this must be called *after* the PluginFilterRunner has called Undo.set(). To achieve this I use the FINAL_PROCESSING flag and then in setup() I add:

        if ("final".equalsIgnoreCase(arg))
        {
            final ImageProcessor processor = imp.getProcessor();
            imp.setProcessor(rgbProcessor);
            Undo.setup(Undo.TYPE_CONVERSION, imp);
            imp.setProcessor(processor);
        }

As I said, it works but feels fragile. I'd appreciate any suggestions on alternative approaches.

Thanks,
Chris.
-----
import ij.IJ;
import ij.ImagePlus;
import ij.Undo;
import ij.gui.DialogListener;
import ij.gui.GenericDialog;
import ij.plugin.filter.ExtendedPlugInFilter;
import ij.plugin.filter.PlugInFilterRunner;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;

import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

public class DemoFilter
        implements ExtendedPlugInFilter, DialogListener
{
    // Processing flags for this filter.
    private static final int FLAGS = DOES_RGB | PARALLELIZE_IMAGES | FINAL_PROCESSING;

    // Progress counter.
    private int progress;

    // Processors.
    private FloatProcessor floatProcessor;
    private ImageProcessor rgbProcessor;

    // Pixel value.
    private float param;

    public DemoFilter()
    {
        //Initialize.
        progress = 0;
        floatProcessor = null;
        rgbProcessor = null;
        param = 0.0f;
    }

    @Override
    public int setup(final String arg,
                     final ImagePlus imp)
    {
        // See {@code FINAL_PROCESSING}.
        if ("final".equalsIgnoreCase(arg))
        {

            // A "kludge" to allow the filter to be undone.
            final ImageProcessor processor = imp.getProcessor();
            imp.setProcessor(rgbProcessor);
            Undo.setup(Undo.TYPE_CONVERSION, imp);
            imp.setProcessor(processor);
        }

        // The original and results processors.
        rgbProcessor = imp.getProcessor();
        floatProcessor = new FloatProcessor(imp.getWidth(), imp.getHeight());
        floatProcessor.setMinAndMax(-1.0, 1.0);

        return FLAGS;
    }

    @Override
    public boolean dialogItemChanged(final GenericDialog gd,
                                     final AWTEvent e)
    {
        progress = 0;
        param = (float) gd.getNextNumber()/ 100.0f;

        // Return indicating parameter validity.
        return true;
    }

    @Override
    public int showDialog(final ImagePlus imp,
                          final String command,
                          final PlugInFilterRunner pfr)
    {
        // Create parameters dialog.
        final GenericDialog dialog = new GenericDialog("Demo", IJ.getInstance());
        dialog.addSlider("Value", -100.0, 100.0, param);
        dialog.addDialogListener(this);
        dialog.addPreviewCheckbox(pfr);

        final Checkbox checkbox = dialog.getPreviewCheckbox();
        checkbox.addItemListener(new ItemListener()
        {
            @Override
            public void itemStateChanged(final ItemEvent itemEvent)
            {
                imp.setProcessor(checkbox.getState() ? floatProcessor : rgbProcessor);
            }
        });
        dialog.showDialog();

        // Dialog exit status.
        if (dialog.wasCanceled())
        {
            imp.setProcessor(rgbProcessor);
            return DONE;
        }
        else
        {
            imp.setProcessor(floatProcessor);
            return IJ.setupDialog(imp, FLAGS);
        }
    }

    @Override
    public void setNPasses(final int nPasses)
    {
        // ignored - this is not a multi-pass filter.
    }

    @Override
    public void run(final ImageProcessor ip)
    {
        IJ.showStatus(" ... ");

        // Dimensions.
        final int width = ip.getWidth();
        final int height = ip.getHeight();

        // Get pixels.
        final float[] pixels = (float[]) floatProcessor.getPixels();
        final int[] origPixels = (int[]) rgbProcessor.getPixels();

        // Loop through ROI pixels.
        final Rectangle roi = ip.getRoi();
        final int r1 = (int) roi.getY();
        final int r2 = r1 + (int) roi.getHeight();
        final int c1 = (int) roi.getX();
        final int c2 = c1 + (int) roi.getWidth();
        for (int row = r1;
             row < r2;
             row++)
        {
            int offset = row * width + c1;
            for (int col = c1;
                 col < c2;
                 col++)
            {
                pixels[offset++] = param;
            }

            // Interrupted? E.g. during preview mode when the user has entered new values.
            if (Thread.currentThread().isInterrupted())
            {
                return;
            }

            // Update progress - should be synchronized due to {@code PARALLELIZE_IMAGES} but not really dangerous to do
            // un-synchronized so avoid the performance hit.
            IJ.showProgress(progress++, height - 1);
        }
    }
}

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

Re: How do I create an RGB to Float filter (with preview and undo)?

Stephan Saalfeld
Hi Chris,

you could convert the float result back to RGB and show that during
preview.  Your monitor isn't capable of displaying anything else than
24bit RGB anyways so the `visual' result would be just fine.  That also
gives you room for automatically adjusting the display range (min and
max) that ends up in preview.

Just a thought...

Best,
Stephan




On Tue, 2013-03-26 at 05:03 -0400, Chris Pudney wrote:

> G'day,
>
> I'm working on an ExtendedPlugInFilter that takes an RGB image as input and produces a floating-point image as output. The processing is done "in place", i.e. the result image replaces the input image.
>
> I'd like the filter dialog to provide a preview checkbox, so the user can interactively adjust filter parameters.
>
> I also want the user to be able to undo (^Z) the filter if necessary.
>
> I've come up with a solution (the code is included below) but it has a kludge "smell".
>
> To convert the input RGB image to an output float image I create a FloatProcessor, write the processing results to it, and assign it to the input image's processor.
>
> In order to preview the processing results I add a listener to the filter dialog's preview checkbox, and switch the input image's processor between the RGB and float processors:
>
>         final Checkbox checkbox = dialog.getPreviewCheckbox();
>         checkbox.addItemListener(new ItemListener()
>         {
>             @Override
>             public void itemStateChanged(final ItemEvent itemEvent)
>             {
>                 imp.setProcessor(checkbox.getState() ? floatProcessor : rgbProcessor);
>             }
>         });
>
> In order to support undoing the filter I need to explicitly call Undo.setup(Undo.TYPE_CONVERSION, imp) because by default PluginFilter uses             Undo.setup(Undo.FILTER, imp) which isn't effective when the image's processor has been replaced. Additionally, this must be called *after* the PluginFilterRunner has called Undo.set(). To achieve this I use the FINAL_PROCESSING flag and then in setup() I add:
>
>         if ("final".equalsIgnoreCase(arg))
>         {
>             final ImageProcessor processor = imp.getProcessor();
>             imp.setProcessor(rgbProcessor);
>             Undo.setup(Undo.TYPE_CONVERSION, imp);
>             imp.setProcessor(processor);
>         }
>
> As I said, it works but feels fragile. I'd appreciate any suggestions on alternative approaches.
>
> Thanks,
> Chris.
> -----
> import ij.IJ;
> import ij.ImagePlus;
> import ij.Undo;
> import ij.gui.DialogListener;
> import ij.gui.GenericDialog;
> import ij.plugin.filter.ExtendedPlugInFilter;
> import ij.plugin.filter.PlugInFilterRunner;
> import ij.process.FloatProcessor;
> import ij.process.ImageProcessor;
>
> import java.awt.*;
> import java.awt.event.ItemEvent;
> import java.awt.event.ItemListener;
>
> public class DemoFilter
>         implements ExtendedPlugInFilter, DialogListener
> {
>     // Processing flags for this filter.
>     private static final int FLAGS = DOES_RGB | PARALLELIZE_IMAGES | FINAL_PROCESSING;
>
>     // Progress counter.
>     private int progress;
>
>     // Processors.
>     private FloatProcessor floatProcessor;
>     private ImageProcessor rgbProcessor;
>
>     // Pixel value.
>     private float param;
>
>     public DemoFilter()
>     {
>         //Initialize.
>         progress = 0;
>         floatProcessor = null;
>         rgbProcessor = null;
>         param = 0.0f;
>     }
>
>     @Override
>     public int setup(final String arg,
>                      final ImagePlus imp)
>     {
>         // See {@code FINAL_PROCESSING}.
>         if ("final".equalsIgnoreCase(arg))
>         {
>
>             // A "kludge" to allow the filter to be undone.
>             final ImageProcessor processor = imp.getProcessor();
>             imp.setProcessor(rgbProcessor);
>             Undo.setup(Undo.TYPE_CONVERSION, imp);
>             imp.setProcessor(processor);
>         }
>
>         // The original and results processors.
>         rgbProcessor = imp.getProcessor();
>         floatProcessor = new FloatProcessor(imp.getWidth(), imp.getHeight());
>         floatProcessor.setMinAndMax(-1.0, 1.0);
>
>         return FLAGS;
>     }
>
>     @Override
>     public boolean dialogItemChanged(final GenericDialog gd,
>                                      final AWTEvent e)
>     {
>         progress = 0;
>         param = (float) gd.getNextNumber()/ 100.0f;
>
>         // Return indicating parameter validity.
>         return true;
>     }
>
>     @Override
>     public int showDialog(final ImagePlus imp,
>                           final String command,
>                           final PlugInFilterRunner pfr)
>     {
>         // Create parameters dialog.
>         final GenericDialog dialog = new GenericDialog("Demo", IJ.getInstance());
>         dialog.addSlider("Value", -100.0, 100.0, param);
>         dialog.addDialogListener(this);
>         dialog.addPreviewCheckbox(pfr);
>
>         final Checkbox checkbox = dialog.getPreviewCheckbox();
>         checkbox.addItemListener(new ItemListener()
>         {
>             @Override
>             public void itemStateChanged(final ItemEvent itemEvent)
>             {
>                 imp.setProcessor(checkbox.getState() ? floatProcessor : rgbProcessor);
>             }
>         });
>         dialog.showDialog();
>
>         // Dialog exit status.
>         if (dialog.wasCanceled())
>         {
>             imp.setProcessor(rgbProcessor);
>             return DONE;
>         }
>         else
>         {
>             imp.setProcessor(floatProcessor);
>             return IJ.setupDialog(imp, FLAGS);
>         }
>     }
>
>     @Override
>     public void setNPasses(final int nPasses)
>     {
>         // ignored - this is not a multi-pass filter.
>     }
>
>     @Override
>     public void run(final ImageProcessor ip)
>     {
>         IJ.showStatus(" ... ");
>
>         // Dimensions.
>         final int width = ip.getWidth();
>         final int height = ip.getHeight();
>
>         // Get pixels.
>         final float[] pixels = (float[]) floatProcessor.getPixels();
>         final int[] origPixels = (int[]) rgbProcessor.getPixels();
>
>         // Loop through ROI pixels.
>         final Rectangle roi = ip.getRoi();
>         final int r1 = (int) roi.getY();
>         final int r2 = r1 + (int) roi.getHeight();
>         final int c1 = (int) roi.getX();
>         final int c2 = c1 + (int) roi.getWidth();
>         for (int row = r1;
>              row < r2;
>              row++)
>         {
>             int offset = row * width + c1;
>             for (int col = c1;
>                  col < c2;
>                  col++)
>             {
>                 pixels[offset++] = param;
>             }
>
>             // Interrupted? E.g. during preview mode when the user has entered new values.
>             if (Thread.currentThread().isInterrupted())
>             {
>                 return;
>             }
>
>             // Update progress - should be synchronized due to {@code PARALLELIZE_IMAGES} but not really dangerous to do
>             // un-synchronized so avoid the performance hit.
>             IJ.showProgress(progress++, height - 1);
>         }
>     }
> }
>
> --
> ImageJ mailing list: http://imagej.nih.gov/ij/list.html

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