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 |
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 |
Free forum by Nabble | Edit this page |