G'day,
I'm working on a multi-threaded ExtendedPluginFilter. I've taken the standard approach used elsewhere <http://repo.or.cz/w/trakem2.git/blob/HEAD:/TrakEM2_/src/main/java/mpi/fruitfly/general/MultiThreading.java>and it works really well - I get an almost linear speed up on multi-core CPUs. Briefly, the main thread (the one that call's the filter's run() method) divides the image into a series of contiguous sets of rows (rows 0 ... n; rows n+1 ... 2n, etc.) and creates a thread that processes each set of rows. The processing threads are started and the main thread waits for them to finish. The trouble is that I use GenericDialog.addPreviewCheckbox() to provide a preview-mode to the user. Unfortunately, I can't get processing to work properly during preview-mode. I've tried a couple of approaches: 1. Each processing thread periodically calls Thread.currentThread().isInterrupted(). If it's true then the thread exits without completing its processing. When all the processing threads have exited the main thread returns without producing a result. If no processing thread is interrupted then the main thread displays the processing results. 2. Each processing thread periodically calls mainThread.isInterrupted(). If it's true then the thread exists without completing its processing. When all the processing threads have exited the main thread returns without producing a result. If the main thread is not interrupted then it displays the processing results. Neither approach works very well in preview-mode. There seems to be a disparity between interrupts to the main thread and processing threads, particularly when the user drags a slider control, i.e. sometimes the processing threads get interrupted and the main thread doesn't, and vice versa. I've included an abbreviated version of the source-code (approach 2.) below. You can see the issue I'm talking about if you enable Preview, use a large value of Radius (>50) and then drag the Factor slider. The preview image will update but will have artefacts indicating a partially processed image (even at the end of a drag action). I'd appreciate any pointers. Thanks, Chris. ----- import ij.IJ; import ij.ImagePlus; import ij.gui.DialogListener; import ij.gui.GenericDialog; import ij.gui.NewImage; import ij.plugin.filter.ExtendedPlugInFilter; import ij.plugin.filter.PlugInFilterRunner; import ij.process.ImageProcessor; import java.awt.*; /** * Example multi-threaded ExtendedPlugInFilter. * * @author Chris Pudney * @version $Revision$ */ public class My_MultiThreaded_Preview_Filter implements ExtendedPlugInFilter, DialogListener { // Processing flags for this filter: handles RGB; doesn't change the image (creates a result image). private static final int FLAGS = DOES_RGB | NO_CHANGES | KEEP_PREVIEW; // The image to operate on. private ImagePlus image; // The result image. private ImagePlus result; // Adjustable factor. private double factor; // Radius of filter. private int radius; // Tracks progress. private Integer progress; // The current thread. private Thread runThread; /** * Construct the filter. */ public My_MultiThreaded_Preview_Filter() { // Initialize. image = null; result = null; factor = 25.0; radius = 50; progress = 0; runThread = null; } /** * This method is called once when the filter is loaded. * <p/> * For Plugin-filters specifying the FINAL_PROCESSING flag, the setup method will be called again, this time with arg = "final" after all other * processing is done. * * @param arg may be blank, is the argument specified for this plugin in IJ_Props.txt or in the plugins.config file of a jar archive containing * the plugin. * @param imp is the currently active image. * @return Flags that specifies the filters capabilities (e.g. acceptable image types) */ @Override public int setup(final String arg, final ImagePlus imp) { // Initialize. image = imp; return FLAGS; } /** * This method is invoked by a Generic Dialog if any of the inputs have changed (CANCEL does not trigger it; OK and running the dialog * from a macro only trigger the first DialogListener added to a GenericDialog). * * @param e The event that has been generated by the user action in the dialog. Note that {@code e} is {@code null} * if the dialogItemChanged method is called after the user has pressed the OK button or if the GenericDialog has read its * parameters from a macro. * @param dialog A reference to the GenericDialog. * @return Should be true if the dialog input is valid. False disables the OK button and preview (if any). */ @Override public boolean dialogItemChanged(final GenericDialog dialog, final AWTEvent e) { factor = dialog.getNextNumber(); radius = (int) dialog.getNextNumber(); return !Double.isNaN(factor) && radius > 0; } /** * This method is called after {@code setup(arg, imp)} unless the {@code DONE} flag has been set. * * @param imp The active image already passed in the {@code setup(arg, imp)} call. It will be null, however, if * the {@code NO_IMAGE_REQUIRED} flag has been set. * @param command The command that has led to the invocation of the plugin-filter. Useful as a title for the dialog. * @param pfr The PlugInFilterRunner calling this plugin-filter. It can be passed to a GenericDialog by * {@code addPreviewCheckbox} to enable preview by calling the {@code run(ip)} method of this plugin-filter. * {@code pfr} can be also used later for calling back the PlugInFilterRunner, e.g., to obtain the slice number currently * processed by {@code run(ip)}. * @return The method should return a combination (bitwise OR) of the flags specified in interfaces {@code PlugInFilter} and * {@code ExtendedPlugInFilter}. */ @Override public int showDialog(final ImagePlus imp, final String command, final PlugInFilterRunner pfr) { // Create parameters dialog. final GenericDialog dialog = new GenericDialog("Multi-Theaded Filter", IJ.getInstance()); dialog.addSlider("Factor", 0.0, 1000.0, factor); dialog.addNumericField("Radius", radius, 0, 3, "pixels"); dialog.addPreviewCheckbox(pfr); dialog.addDialogListener(this); dialog.showDialog(); // Dialog exit status. return dialog.wasCanceled() ? DONE : IJ.setupDialog(imp, FLAGS); } /** * This method is called by ImageJ to inform the plugin-filter about the passes to its run method. During preview, the number of passes is * one (or 3 for RGB images, if {@code CONVERT_TO_FLOAT} has been specified). When processing a stack, it is the number of * slices to be processed (minus one, if one slice has been processed for preview before), and again, 3 times that number for RGB images * processed with {@code CONVERT_TO_FLOAT}. * * @param nPasses the number of passes. */ @Override public void setNPasses(final int nPasses) { // ignored - this is not a multi-pass filter. } /** * Filters use this method to process the image. If the SUPPORTS_STACKS flag was set, it is called for each slice in a stack. With * CONVERT_TO_FLOAT, the filter is called with the image data converted to a FloatProcessor (3 times per image for RGB images). * ImageJ will lock the image before calling this method and unlock it when the filter is finished. For PlugInFilters specifying the * NO_IMAGE_REQUIRED flag and not the DONE flag, run(ip) is called once with the argument {@code null}. * * @param ip is the current slice (ignored - use image set above). */ @Override public void run(final ImageProcessor ip) { // Initialize. runThread = Thread.currentThread(); progress = 0; IJ.showStatus("Processing image for factor=" + factor + " radius=" + radius); // Dimensions. final int width = image.getWidth(); final int height = image.getHeight(); // Create result image if necessary (preview mode might have created it already). if (result == null) { result = NewImage.createRGBImage("", width, height, 1, NewImage.FILL_BLACK); } // Helps us see preview-mode artifacts. result.getProcessor().fill(); // Process image using multiple threads. multiThreadedAD(); // Display the results if we weren't interrupted, e.g. dialog changed during preview. if (!runThread.isInterrupted()) { // Display the final result - draw is needed to refresh during preview mode. result.setTitle( image.getTitle() + " [Result factor=" + factor + "; radius=" + radius + ']'); result.show(); result.updateAndDraw(); } } /** * Calculate result image using multiple threads. */ private void multiThreadedAD() { // Create threads: one per available processor. // Each thread handles a series of image rows. final int numThreads = Runtime.getRuntime().availableProcessors(); final Thread[] threads = new Thread[numThreads]; final int height = image.getHeight(); final int rowsPerThread = (int) Math.ceil((double) height / (double) numThreads); int startRow = 0; for (int t = 0; t < numThreads; t++) { final int numRows = Math.min(height - startRow, rowsPerThread); threads[t] = createThread(startRow, numRows); startRow += numRows; } // Start threads. for (final Thread thread : threads) { thread.setPriority(Thread.NORM_PRIORITY); thread.start(); } // Wait for threads to finish. for (final Thread thread : threads) { try { thread.join(); } catch (InterruptedException e) { // Ignored - do nothing. } } } /** * Create a thread the processes a set of row. * * @param startRow index of the row to start processing. * @param numRows number of rows to process. * @return A thread that processes a series of rows. */ private Thread createThread(final int startRow, final int numRows) { return new Thread(new Runnable() { @Override public void run() { processRows(startRow, numRows); } }); } /** * Process a series of rows. * * @param startRow index of the row to start processing. * @param numRows number of rows to process. */ private void processRows(final int startRow, final int numRows) { // Get pixels. final int[] pixels = (int[]) image.getProcessor().getPixels(); final int[] results = (int[]) result.getProcessor().getPixels(); // Dimensions. final int width = image.getWidth(); final int height = image.getHeight(); // Loop through pixels. int offset = startRow * width; final int endRow = startRow + numRows; for (int i = startRow; !runThread.isInterrupted() // exit if the run thread is interrupted, e.g. the user has adjusted parameters during preview. && i < endRow; i++) { for (int j = 0; j < width; j++) { // Process pixel.. results[offset++] = processPixel(width, height, pixels, i, j); } // Multiple threads are writing to this value - update must be synchronized. synchronized (progress) { progress++; } IJ.showProgress(progress, height - 1); } } /** * Process a given pixel. * * @param width image width * @param height image height. * @param pixels image pixel array. * @param row current pixel row index. * @param col current pixel column index. * @return the result value for the input pixel. */ private int processPixel(final int width, final int height, final int[] pixels, final int row, final int col) { // Kernel extents. final int rMin = Math.max(0, row - radius); final int rMax = Math.min(height - 1, row + radius); final int cMin = Math.max(0, col - radius); final int cMax = Math.min(width - 1, col + radius); double result = 0.0; // Loop through kernel. for (int r = rMin; r <= rMax; r++) { for (int c = cMin; c <= cMax; c++) { // do some calculation. result += 1.0 / factor * (double) pixels[c + width * r]; } } // Return the pixel RGB value. return (int) result; } } -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html |
Hi Chris,
for most cases, you can simply specify the PARALLELIZE_IMAGES flag in the (Extended)PlugInFilter. Then the filter is called in parallel threads, with the Roi rectangle of the ImageProcessor set according to the area that should be processed. ImageJ will then care about all the multithreading. If you have a reason not to use PARALLELIZE_IMAGES, you have to care about the InterruptedException when you join the threads. You can do it the same way as in ij.plugin.filter.GaussianBlur: try { for (final Thread thread : threads) thread.join(); } catch (InterruptedException e) { //happens if main thread is interrupted for (final Thread thread : threads) thread.interrupt(); //tell all other threads to abort... try { for (final Thread thread : threads) thread.join(); //...and wait till all aborted } catch ( InterruptedException f ) {} Thread.currentThread().interrupt(); //recover to 'interrupted' condition } The last statement is needed only if your plugin might do further tasks where it checks for the 'interrupted' condition. --- By the way, don't bother about synchronizing the 'progress++' statement; in the rare case that it will not be executed correctly, nothing bad will happen. Depending on the operating system, this might be a significant waste of processing time, caused by the 'synchronized' overhead. Michael ________________________________________________________________ On Dec 13, 2012, at 11:05, Chris Pudney wrote: > G'day, > > I'm working on a multi-threaded ExtendedPluginFilter. I've taken the standard approach used elsewhere <http://repo.or.cz/w/trakem2.git/blob/HEAD:/TrakEM2_/src/main/java/mpi/fruitfly/general/MultiThreading.java>and it works really well - I get an almost linear speed up on multi-core CPUs. > > Briefly, the main thread (the one that call's the filter's run() method) divides the image into a series of contiguous sets of rows (rows 0 ... n; rows n+1 ... 2n, etc.) and creates a thread that processes each set of rows. The processing threads are started and the main thread waits for them to finish. > > The trouble is that I use GenericDialog.addPreviewCheckbox() to provide a preview-mode to the user. Unfortunately, I can't get processing to work properly during preview-mode. I've tried a couple of approaches: > > 1. Each processing thread periodically calls Thread.currentThread().isInterrupted(). If it's true then the thread exits without completing its processing. When all the processing threads have exited the main thread returns without producing a result. If no processing thread is interrupted then the main thread displays the processing results. > > 2. Each processing thread periodically calls mainThread.isInterrupted(). If it's true then the thread exists without completing its processing. When all the processing threads have exited the main thread returns without producing a result. If the main thread is not interrupted then it displays the processing results. > > Neither approach works very well in preview-mode. There seems to be a disparity between interrupts to the main thread and processing threads, particularly when the user drags a slider control, i.e. sometimes the processing threads get interrupted and the main thread doesn't, and vice versa. > > I've included an abbreviated version of the source-code (approach 2.) below. You can see the issue I'm talking about if you enable Preview, use a large value of Radius (>50) and then drag the Factor slider. The preview image will update but will have artefacts indicating a partially processed image (even at the end of a drag action). > > I'd appreciate any pointers. > > Thanks, > Chris. > ----- > import ij.IJ; > import ij.ImagePlus; > import ij.gui.DialogListener; > import ij.gui.GenericDialog; > import ij.gui.NewImage; > import ij.plugin.filter.ExtendedPlugInFilter; > import ij.plugin.filter.PlugInFilterRunner; > import ij.process.ImageProcessor; > > import java.awt.*; > > /** > * Example multi-threaded ExtendedPlugInFilter. > * > * @author Chris Pudney > * @version $Revision$ > */ > public class My_MultiThreaded_Preview_Filter > implements ExtendedPlugInFilter, DialogListener > { > // Processing flags for this filter: handles RGB; doesn't change the image (creates a result image). > private static final int FLAGS = DOES_RGB | NO_CHANGES | KEEP_PREVIEW; > > // The image to operate on. > private ImagePlus image; > > // The result image. > private ImagePlus result; > > // Adjustable factor. > private double factor; > > // Radius of filter. > private int radius; > > // Tracks progress. > private Integer progress; > > // The current thread. > private Thread runThread; > > /** > * Construct the filter. > */ > public My_MultiThreaded_Preview_Filter() > { > // Initialize. > image = null; > result = null; > factor = 25.0; > radius = 50; > progress = 0; > runThread = null; > } > > /** > * This method is called once when the filter is loaded. > * <p/> > * For Plugin-filters specifying the FINAL_PROCESSING flag, the setup method will be called again, this time with arg = "final" after all other > * processing is done. > * > * @param arg may be blank, is the argument specified for this plugin in IJ_Props.txt or in the plugins.config file of a jar archive containing > * the plugin. > * @param imp is the currently active image. > * @return Flags that specifies the filters capabilities (e.g. acceptable image types) > */ > @Override > public int setup(final String arg, > final ImagePlus imp) > { > // Initialize. > image = imp; > > return FLAGS; > } > > /** > * This method is invoked by a Generic Dialog if any of the inputs have changed (CANCEL does not trigger it; OK and running the dialog > * from a macro only trigger the first DialogListener added to a GenericDialog). > * > * @param e The event that has been generated by the user action in the dialog. Note that {@code e} is {@code null} > * if the dialogItemChanged method is called after the user has pressed the OK button or if the GenericDialog has read its > * parameters from a macro. > * @param dialog A reference to the GenericDialog. > * @return Should be true if the dialog input is valid. False disables the OK button and preview (if any). > */ > @Override > public boolean dialogItemChanged(final GenericDialog dialog, > final AWTEvent e) > { > factor = dialog.getNextNumber(); > radius = (int) dialog.getNextNumber(); > return !Double.isNaN(factor) && radius > 0; > } > > /** > * This method is called after {@code setup(arg, imp)} unless the {@code DONE} flag has been set. > * > * @param imp The active image already passed in the {@code setup(arg, imp)} call. It will be null, however, if > * the {@code NO_IMAGE_REQUIRED} flag has been set. > * @param command The command that has led to the invocation of the plugin-filter. Useful as a title for the dialog. > * @param pfr The PlugInFilterRunner calling this plugin-filter. It can be passed to a GenericDialog by > * {@code addPreviewCheckbox} to enable preview by calling the {@code run(ip)} method of this plugin-filter. > * {@code pfr} can be also used later for calling back the PlugInFilterRunner, e.g., to obtain the slice number currently > * processed by {@code run(ip)}. > * @return The method should return a combination (bitwise OR) of the flags specified in interfaces {@code PlugInFilter} and > * {@code ExtendedPlugInFilter}. > */ > @Override > public int showDialog(final ImagePlus imp, > final String command, > final PlugInFilterRunner pfr) > { > // Create parameters dialog. > final GenericDialog dialog = new GenericDialog("Multi-Theaded Filter", IJ.getInstance()); > dialog.addSlider("Factor", 0.0, 1000.0, factor); > dialog.addNumericField("Radius", radius, 0, 3, "pixels"); > dialog.addPreviewCheckbox(pfr); > dialog.addDialogListener(this); > dialog.showDialog(); > > // Dialog exit status. > return dialog.wasCanceled() ? DONE : IJ.setupDialog(imp, FLAGS); > } > > /** > * This method is called by ImageJ to inform the plugin-filter about the passes to its run method. During preview, the number of passes is > * one (or 3 for RGB images, if {@code CONVERT_TO_FLOAT} has been specified). When processing a stack, it is the number of > * slices to be processed (minus one, if one slice has been processed for preview before), and again, 3 times that number for RGB images > * processed with {@code CONVERT_TO_FLOAT}. > * > * @param nPasses the number of passes. > */ > @Override > public void setNPasses(final int nPasses) > { > // ignored - this is not a multi-pass filter. > } > > > /** > * Filters use this method to process the image. If the SUPPORTS_STACKS flag was set, it is called for each slice in a stack. With > * CONVERT_TO_FLOAT, the filter is called with the image data converted to a FloatProcessor (3 times per image for RGB images). > * ImageJ will lock the image before calling this method and unlock it when the filter is finished. For PlugInFilters specifying the > * NO_IMAGE_REQUIRED flag and not the DONE flag, run(ip) is called once with the argument {@code null}. > * > * @param ip is the current slice (ignored - use image set above). > */ > @Override > public void run(final ImageProcessor ip) > { > // Initialize. > runThread = Thread.currentThread(); > progress = 0; > > IJ.showStatus("Processing image for factor=" + factor + " radius=" + radius); > > // Dimensions. > final int width = image.getWidth(); > final int height = image.getHeight(); > > // Create result image if necessary (preview mode might have created it already). > if (result == null) > { > result = NewImage.createRGBImage("", width, height, 1, NewImage.FILL_BLACK); > } > > // Helps us see preview-mode artifacts. > result.getProcessor().fill(); > > // Process image using multiple threads. > multiThreadedAD(); > > // Display the results if we weren't interrupted, e.g. dialog changed during preview. > if (!runThread.isInterrupted()) > { > // Display the final result - draw is needed to refresh during preview mode. > result.setTitle( > image.getTitle() + " [Result factor=" + factor + "; radius=" + radius + ']'); > result.show(); > result.updateAndDraw(); > } > } > > /** > * Calculate result image using multiple threads. > */ > private void multiThreadedAD() > { > // Create threads: one per available processor. > // Each thread handles a series of image rows. > final int numThreads = Runtime.getRuntime().availableProcessors(); > final Thread[] threads = new Thread[numThreads]; > final int height = image.getHeight(); > final int rowsPerThread = (int) Math.ceil((double) height / (double) numThreads); > int startRow = 0; > for (int t = 0; > t < numThreads; > t++) > { > final int numRows = Math.min(height - startRow, rowsPerThread); > threads[t] = createThread(startRow, numRows); > startRow += numRows; > } > > // Start threads. > for (final Thread thread : threads) > { > thread.setPriority(Thread.NORM_PRIORITY); > thread.start(); > } > > // Wait for threads to finish. > for (final Thread thread : threads) > { > try > { > thread.join(); > } > catch (InterruptedException e) > { > // Ignored - do nothing. > } > } > } > > /** > * Create a thread the processes a set of row. > * > * @param startRow index of the row to start processing. > * @param numRows number of rows to process. > * @return A thread that processes a series of rows. > */ > private Thread createThread(final int startRow, > final int numRows) > { > return new Thread(new Runnable() > { > @Override > public void run() > { > processRows(startRow, numRows); > } > }); > } > > /** > * Process a series of rows. > * > * @param startRow index of the row to start processing. > * @param numRows number of rows to process. > */ > private void processRows(final int startRow, > final int numRows) > { > // Get pixels. > final int[] pixels = (int[]) image.getProcessor().getPixels(); > final int[] results = (int[]) result.getProcessor().getPixels(); > > // Dimensions. > final int width = image.getWidth(); > final int height = image.getHeight(); > > // Loop through pixels. > int offset = startRow * width; > final int endRow = startRow + numRows; > for (int i = startRow; > !runThread.isInterrupted() > // exit if the run thread is interrupted, e.g. the user has adjusted parameters during preview. > && i < endRow; > i++) > { > for (int j = 0; > j < width; > j++) > { > // Process pixel.. > results[offset++] = processPixel(width, height, pixels, i, j); > } > > // Multiple threads are writing to this value - update must be synchronized. > synchronized (progress) > { > progress++; > } > IJ.showProgress(progress, height - 1); > } > } > > /** > * Process a given pixel. > * > * @param width image width > * @param height image height. > * @param pixels image pixel array. > * @param row current pixel row index. > * @param col current pixel column index. > * @return the result value for the input pixel. > */ > private int processPixel(final int width, > final int height, > final int[] pixels, > final int row, > final int col) > { > // Kernel extents. > final int rMin = Math.max(0, row - radius); > final int rMax = Math.min(height - 1, row + radius); > final int cMin = Math.max(0, col - radius); > final int cMax = Math.min(width - 1, col + radius); > > double result = 0.0; > > // Loop through kernel. > for (int r = rMin; > r <= rMax; > r++) > { > for (int c = cMin; > c <= cMax; > c++) > { > // do some calculation. > result += 1.0 / factor * (double) pixels[c + width * r]; > } > } > > // Return the pixel RGB value. > return (int) result; > } > } -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html |
In reply to this post by Chris Pudney
G'day,
Thanks Michael (and Wayne Rasband) for bringing PARALLELIZE_IMAGES to my attention. I've re-implemented my filter using PARALLELIZE_IMAGES and it greatly simplifies the code. I'm new to ImageJ development and all my searching for multi-threading had lead me to the old method. For the benefit of others like me who haven't seen PARALLELIZE_IMAGES in use I've included below a reworking of my original example using PARALLELIZE_IMAGES. One caveat is that there are still issues in preview mode, especially when dragging the slider control and using large values of radius. I see similar issues when using other filters that use PARALLELIZE_IMAGES, e.g. Mexican Hat Filter http://imagej.nih.gov/ij/plugins/mexican-hat/ Regards, Chris. ----- import ij.IJ; import ij.ImagePlus; import ij.gui.DialogListener; import ij.gui.GenericDialog; import ij.gui.NewImage; import ij.plugin.filter.ExtendedPlugInFilter; import ij.plugin.filter.PlugInFilterRunner; import ij.process.ImageProcessor; import java.awt.*; /** * Example multi-threaded ExtendedPlugInFilter. * * @author Chris Pudney * @version $Revision$ */ public class My_MultiThreaded_Preview_Filter implements ExtendedPlugInFilter, DialogListener { // Processing flags for this filter. private static final int FLAGS = DOES_RGB | PARALLELIZE_IMAGES | SNAPSHOT; // Adjustable factor. private double factor; // Radius of filter. private int radius; // Tracks progress. private int progress; /** * Construct the filter. */ public My_MultiThreaded_Preview_Filter() { // Initialize. factor = 25.0; radius = 50; progress = 0; } /** * This method is called once when the filter is loaded. * <p/> * For Plugin-filters specifying the FINAL_PROCESSING flag, the setup method will be called again, this time with arg = "final" after all other * processing is done. * * @param arg may be blank, is the argument specified for this plugin in IJ_Props.txt or in the plugins.config file of a jar archive containing * the plugin. * @param imp is the currently active image. * @return Flags that specifies the filters capabilities (e.g. acceptable image types) */ @Override public int setup(final String arg, final ImagePlus imp) { return FLAGS; } /** * This method is invoked by a Generic Dialog if any of the inputs have changed (CANCEL does not trigger it; OK and running the dialog * from a macro only trigger the first DialogListener added to a GenericDialog). * * @param e The event that has been generated by the user action in the dialog. Note that {@code e} is {@code null} * if the dialogItemChanged method is called after the user has pressed the OK button or if the GenericDialog has read its * parameters from a macro. * @param dialog A reference to the GenericDialog. * @return Should be true if the dialog input is valid. False disables the OK button and preview (if any). */ @Override public boolean dialogItemChanged(final GenericDialog dialog, final AWTEvent e) { factor = dialog.getNextNumber(); radius = (int) dialog.getNextNumber(); progress = 0; return !Double.isNaN(factor) && radius > 0; } /** * This method is called after {@code setup(arg, imp)} unless the {@code DONE} flag has been set. * * @param imp The active image already passed in the {@code setup(arg, imp)} call. It will be null, however, if * the {@code NO_IMAGE_REQUIRED} flag has been set. * @param command The command that has led to the invocation of the plugin-filter. Useful as a title for the dialog. * @param pfr The PlugInFilterRunner calling this plugin-filter. It can be passed to a GenericDialog by * {@code addPreviewCheckbox} to enable preview by calling the {@code run(ip)} method of this plugin-filter. * {@code pfr} can be also used later for calling back the PlugInFilterRunner, e.g., to obtain the slice number currently * processed by {@code run(ip)}. * @return The method should return a combination (bitwise OR) of the flags specified in interfaces {@code PlugInFilter} and * {@code ExtendedPlugInFilter}. */ @Override public int showDialog(final ImagePlus imp, final String command, final PlugInFilterRunner pfr) { // Create parameters dialog. final GenericDialog dialog = new GenericDialog("Multi-Theaded Filter", IJ.getInstance()); dialog.addSlider("Factor", 0.0, 1000.0, factor); dialog.addNumericField("Radius", radius, 0, 3, "pixels"); dialog.addPreviewCheckbox(pfr); dialog.addDialogListener(this); dialog.showDialog(); // Dialog exit status. return dialog.wasCanceled() ? DONE : IJ.setupDialog(imp, FLAGS); } /** * This method is called by ImageJ to inform the plugin-filter about the passes to its run method. During preview, the number of passes is * one (or 3 for RGB images, if {@code CONVERT_TO_FLOAT} has been specified). When processing a stack, it is the number of * slices to be processed (minus one, if one slice has been processed for preview before), and again, 3 times that number for RGB images * processed with {@code CONVERT_TO_FLOAT}. * * @param nPasses the number of passes. */ @Override public void setNPasses(final int nPasses) { // ignored - this is not a multi-pass filter. } /** * Filters use this method to process the image. If the SUPPORTS_STACKS flag was set, it is called for each slice in a stack. With * CONVERT_TO_FLOAT, the filter is called with the image data converted to a FloatProcessor (3 times per image for RGB images). * ImageJ will lock the image before calling this method and unlock it when the filter is finished. For PlugInFilters specifying the * NO_IMAGE_REQUIRED flag and not the DONE flag, run(ip) is called once with the argument {@code null}. * * @param ip is the current slice (ignored - use image set above). */ @Override public void run(final ImageProcessor ip) { IJ.showStatus("Processing image for factor=" + factor + " radius=" + radius); // Dimensions. final int width = ip.getWidth(); final int height = ip.getHeight(); // Get pixels. final int[] pixels = (int[]) ip.getPixels(); final int[] origPixels = (int[]) ip.getSnapshotPixels(); // see {@code SNAPSHOT}. // 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++) { // Calculate flux. pixels[offset++] = processPixel(width, height, origPixels, row, col); } // 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); } } /** * Process a given pixel. * * @param width image width * @param height image height. * @param pixels image pixel array. * @param row current pixel row index. * @param col current pixel column index. * @return the result value for the input pixel. */ private int processPixel(final int width, final int height, final int[] pixels, final int row, final int col) { // Kernel extents. final int rMin = Math.max(0, row - radius); final int rMax = Math.min(height - 1, row + radius); final int cMin = Math.max(0, col - radius); final int cMax = Math.min(width - 1, col + radius); double result = 0.0; // Loop through kernel. for (int r = rMin; r <= rMax; r++) { for (int c = cMin; c <= cMax; c++) { // do some calculation. result += 1.0 / factor * (double) pixels[c + width * r]; } } // Return the pixel RGB value. return (int) result; } } -- ImageJ mailing list: http://imagej.nih.gov/ij/list.html |
Hi Chris,
if you update to the daily build of ImageJ (Help>Update ImageJ), the preview bug with PARALLELIZE_IMAGES should be fixed. If you still find some problems, let me know, please. Michael _______________________________________________________________________ On Wed, December 19, 2012 03:30, Chris Pudney wrote: > G'day, > > Thanks Michael (and Wayne Rasband) for bringing PARALLELIZE_IMAGES to my > attention. I've re-implemented my filter using PARALLELIZE_IMAGES and it > greatly simplifies the code. > > I'm new to ImageJ development and all my searching for multi-threading had > lead me to the old method. For the benefit of others like me who haven't > seen PARALLELIZE_IMAGES in use I've included below a reworking of my > original example using PARALLELIZE_IMAGES. > > One caveat is that there are still issues in preview mode, especially when > dragging the slider control and using large values of radius. I see > similar issues when using other filters that use PARALLELIZE_IMAGES, e.g. > Mexican Hat Filter http://imagej.nih.gov/ij/plugins/mexican-hat/ > > Regards, > Chris. > ----- > import ij.IJ; > import ij.ImagePlus; > import ij.gui.DialogListener; > import ij.gui.GenericDialog; > import ij.gui.NewImage; > import ij.plugin.filter.ExtendedPlugInFilter; > import ij.plugin.filter.PlugInFilterRunner; > import ij.process.ImageProcessor; > > import java.awt.*; > > /** > * Example multi-threaded ExtendedPlugInFilter. > * > * @author Chris Pudney > * @version $Revision$ > */ > public class My_MultiThreaded_Preview_Filter > implements ExtendedPlugInFilter, DialogListener > { > // Processing flags for this filter. > private static final int FLAGS = DOES_RGB | PARALLELIZE_IMAGES | > SNAPSHOT; > > // Adjustable factor. > private double factor; > > // Radius of filter. > private int radius; > > // Tracks progress. > private int progress; > > /** > * Construct the filter. > */ > public My_MultiThreaded_Preview_Filter() > { > // Initialize. > factor = 25.0; > radius = 50; > progress = 0; > } > > /** > * This method is called once when the filter is loaded. > * <p/> > * For Plugin-filters specifying the FINAL_PROCESSING flag, the setup > method will be called again, this time with arg = "final" after all > other > * processing is done. > * > * @param arg may be blank, is the argument specified for this plugin > in IJ_Props.txt or in the plugins.config file of a jar archive > containing > * the plugin. > * @param imp is the currently active image. > * @return Flags that specifies the filters capabilities (e.g. > acceptable image types) > */ > @Override > public int setup(final String arg, > final ImagePlus imp) > { > return FLAGS; > } > > /** > * This method is invoked by a Generic Dialog if any of the inputs > have changed (CANCEL does not trigger it; OK and running the dialog > * from a macro only trigger the first DialogListener added to a > GenericDialog). > * > * @param e The event that has been generated by the user action > in the dialog. Note that {@code e} is {@code null} > * if the dialogItemChanged method is called after the > user has pressed the OK button or if the GenericDialog has read its > * parameters from a macro. > * @param dialog A reference to the GenericDialog. > * @return Should be true if the dialog input is valid. False disables > the OK button and preview (if any). > */ > @Override > public boolean dialogItemChanged(final GenericDialog dialog, > final AWTEvent e) > { > factor = dialog.getNextNumber(); > radius = (int) dialog.getNextNumber(); > progress = 0; > return !Double.isNaN(factor) && radius > 0; > } > > /** > * This method is called after {@code setup(arg, imp)} unless the > {@code DONE} flag has been set. > * > * @param imp The active image already passed in the {@code > setup(arg, imp)} call. It will be null, however, if > * the {@code NO_IMAGE_REQUIRED} flag has been set. > * @param command The command that has led to the invocation of the > plugin-filter. Useful as a title for the dialog. > * @param pfr The PlugInFilterRunner calling this plugin-filter. > It can be passed to a GenericDialog by > * {@code addPreviewCheckbox} to enable preview by > calling the {@code run(ip)} method of this plugin-filter. > * {@code pfr} can be also used later for calling back > the PlugInFilterRunner, e.g., to obtain the slice number currently > * processed by {@code run(ip)}. > * @return The method should return a combination (bitwise OR) of the > flags specified in interfaces {@code PlugInFilter} and > * {@code ExtendedPlugInFilter}. > */ > @Override > public int showDialog(final ImagePlus imp, > final String command, > final PlugInFilterRunner pfr) > { > // Create parameters dialog. > final GenericDialog dialog = new GenericDialog("Multi-Theaded > Filter", IJ.getInstance()); > dialog.addSlider("Factor", 0.0, 1000.0, factor); > dialog.addNumericField("Radius", radius, 0, 3, "pixels"); > dialog.addPreviewCheckbox(pfr); > dialog.addDialogListener(this); > dialog.showDialog(); > > // Dialog exit status. > return dialog.wasCanceled() ? DONE : IJ.setupDialog(imp, FLAGS); > } > > /** > * This method is called by ImageJ to inform the plugin-filter about > the passes to its run method. During preview, the number of passes is > * one (or 3 for RGB images, if {@code CONVERT_TO_FLOAT} has been > specified). When processing a stack, it is the number of > * slices to be processed (minus one, if one slice has been processed > for preview before), and again, 3 times that number for RGB images > * processed with {@code CONVERT_TO_FLOAT}. > * > * @param nPasses the number of passes. > */ > @Override > public void setNPasses(final int nPasses) > { > // ignored - this is not a multi-pass filter. > } > > > /** > * Filters use this method to process the image. If the > SUPPORTS_STACKS flag was set, it is called for each slice in a stack. > With > * CONVERT_TO_FLOAT, the filter is called with the image data > converted to a FloatProcessor (3 times per image for RGB images). > * ImageJ will lock the image before calling this method and unlock it > when the filter is finished. For PlugInFilters specifying the > * NO_IMAGE_REQUIRED flag and not the DONE flag, run(ip) is called > once with the argument {@code null}. > * > * @param ip is the current slice (ignored - use image set above). > */ > @Override > public void run(final ImageProcessor ip) > { > IJ.showStatus("Processing image for factor=" + factor + " radius=" > + radius); > > // Dimensions. > final int width = ip.getWidth(); > final int height = ip.getHeight(); > > // Get pixels. > final int[] pixels = (int[]) ip.getPixels(); > final int[] origPixels = (int[]) ip.getSnapshotPixels(); // see > {@code SNAPSHOT}. > > // 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++) > { > // Calculate flux. > pixels[offset++] = processPixel(width, height, origPixels, > row, col); > } > > // 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); > } > } > > /** > * Process a given pixel. > * > * @param width image width > * @param height image height. > * @param pixels image pixel array. > * @param row current pixel row index. > * @param col current pixel column index. > * @return the result value for the input pixel. > */ > private int processPixel(final int width, > final int height, > final int[] pixels, > final int row, > final int col) > { > // Kernel extents. > final int rMin = Math.max(0, row - radius); > final int rMax = Math.min(height - 1, row + radius); > final int cMin = Math.max(0, col - radius); > final int cMax = Math.min(width - 1, col + radius); > > double result = 0.0; > > // Loop through kernel. > for (int r = rMin; > r <= rMax; > r++) > { > for (int c = cMin; > c <= cMax; > c++) > { > // do some calculation. > result += 1.0 / factor * (double) pixels[c + width * r]; > } > } > > // Return the pixel RGB value. > return (int) result; > } > } > > -- > 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 |