Arvie Carpio, Rucha Pandav and Sina Masoud-Ansari
Department of Software Engineering
The University of Auckland
ImageJ is widely used open source image processing toolkit developed by the National Institutes of Health (NIH). It has a plugin architecture which allows communities to develop workflows suitable for a wide range of research applications and leverage tools developed by others.
Image processing tasks can be computationally intensive, especially on large data sets. ImageJ provides high-level parallelisation for image stacks, which are images can be thought of as layers or frames. When image processing tasks such as filters are applied to an image stack, ImageJ will allocate slices in the stack to a group of threads so that the overall processing time is much less.
In some cases, ImageJ also provides image-level parallelisation where the pixels in an image are divided amongst a group of threads for more efficient processing. Our project aimed to show how parallelisation frameworks such as the Java Executor Service, Fork/Join and Parallel Task can be used to improve the performance of standard ImageJ filters such as Gaussian Noise, Shadows and Salt and Pepper. The results showed that those filters can be modified to make use of a common parallelisation interface called an ImageDivision, with improvements in the performance time.
Using ImageDivision is relatively straight forward. In the ImageProcessor subclasses, implement a method that creates an ImageDivision and a method that returns a Runnable that can process a Division. A Division is a block of rows in an image.
@Override
public void filter_P_EXECUTOR(){
ImageDivision div = new ImageDivision(roiX, roiY, roiWidth, roiHeight);
Collection<Future<?>> futures = new LinkedList<Future<?>>();
for (Division d : div.getDivisions()){
futures.add(executor.submit(getFilterRunnable(d)));
}
// start tasks and wait for for them to finish
div.processFutures(futures);
}
public Runnable getFilterRunnable(final Division div){
return new Runnable(){
@Override
public void run() {
// for each row
for (int y = div.yStart; y < div.yLimit; y++){
// process pixels in ROI
for (int x = div.xStart; x < div.xEnd; x++){
// pixels is a 1D array so need to map to it
p = y * roiWidth + div.xStart + x;
pixels[p] = 1; // replace with your filter code
} // end x loop
if (y%20==0) {
showProgress((double)(y-roiY)/roiHeight);
}
} // end y loop
}
};
}
Once you have a Runnable you can call other methods such as Parallel Task:
@Override
public void filter_P_PARATASK(){
ImageDivision div = new ImageDivision(roiX, roiY, roiWidth, roiHeight);
ConcurrentLinkedQueue<Runnable> tasks = new ConcurrentLinkedQueue<Runnable>();
for (Division d : div.getDivisions()){
tasks.add(getNoiseRunnable(d));
}
div.processTasks(tasks);
}
For more information on these classes, investigate the package ij.parallel in our source repository.
For more details on our project, see our presentation slides.