Perform Log convolution and edge artifact

I tried to perform a Laplacian of Gaussian convolution but wanted a result which will not have edge artifacts.
When I ran the following simple example in groovy, I got some edge artifact (tested on the sample image found with File>Open Samples>Dot Blot in Fiji.

//@ OpService ops
//@ Double sigma
//@ ImgPlus inputData
//@ OUTPUT ImgPlus logFiltered

//Tesedt on File>Open Samples>Dot Blot
logKernel=ops.create().kernelLog(inputData.numDimensions(), sigma)
logFiltered=ops.filter().convolve(inputData, logKernel)

Capture
I looked a bit on other example and saw that for example the 3D Gaussian there is a way to avoid that by using an OutOfBoundsStrategy (see https://imagej.net/ImgLib2_Examples).

But I couldn’t figure out how to do it using ops.filter().convolve(.

Is there a simple way to do it or could someone provide me with an example to avoid this artifact?

Benjamin

2 Likes

I wanted to suggest using the optional OutOfBoundsFactory argument of the Op signature:

RandomAccessibleInterval out  <=  Convolve(RandomAccessibleInterval in1, RandomAccessibleInterval in2, OutOfBoundsFactory obf?, Type outType?)

… as illustrated in this script:

#@ OpService ops
#@ Double sigma
#@ ImgPlus inputData
#@output logFiltered

import net.imglib2.outofbounds.OutOfBoundsBorderFactory

//Tested on File>Open Samples>Dot Blot
logKernel=ops.create().kernelLog(inputData.numDimensions(), sigma)
logFiltered=ops.filter().convolve(inputData, logKernel, new OutOfBoundsBorderFactory())

… but I get an IllegalArgumentException:

java.lang.IllegalArgumentException
	at java.lang.reflect.Array.set(Native Method)
	at org.scijava.convert.DefaultConverter.convertToArray(DefaultConverter.java:244)
	at org.scijava.convert.DefaultConverter.convert(DefaultConverter.java:84)
	at org.scijava.convert.AbstractConverter.convert(AbstractConverter.java:126)
	at org.scijava.convert.AbstractConvertService.convert(AbstractConvertService.java:134)
	at org.scijava.convert.AbstractConvertService.convert(AbstractConvertService.java:120)
	at net.imagej.ops.DefaultOpMatchingService.convert(DefaultOpMatchingService.java:623)
	at net.imagej.ops.DefaultOpMatchingService.assign(DefaultOpMatchingService.java:611)
	at net.imagej.ops.DefaultOpMatchingService.assignInputs(DefaultOpMatchingService.java:161)
	at net.imagej.ops.DefaultOpMatchingService.createModule(DefaultOpMatchingService.java:571)
	at net.imagej.ops.DefaultOpMatchingService.moduleConforms(DefaultOpMatchingService.java:483)
	at net.imagej.ops.DefaultOpMatchingService.filterMatches(DefaultOpMatchingService.java:293)
	at net.imagej.ops.DefaultOpMatchingService.filterMatches(DefaultOpMatchingService.java:138)
	at net.imagej.ops.DefaultOpMatchingService.findMatch(DefaultOpMatchingService.java:95)
	at net.imagej.ops.DefaultOpMatchingService.findMatch(DefaultOpMatchingService.java:83)
	at net.imagej.ops.OpEnvironment.module(OpEnvironment.java:269)
	at net.imagej.ops.OpEnvironment.run(OpEnvironment.java:157)
	at net.imagej.ops.filter.FilterNamespace.convolve(FilterNamespace.java:184)
	at net.imagej.ops.filter.FilterNamespace$convolve$0.call(Unknown Source)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:141)
	at Script63.run(Script63.groovy:10)
	at org.scijava.plugins.scripting.groovy.GroovyScriptEngine.eval(GroovyScriptEngine.java:303)
	at org.scijava.plugins.scripting.groovy.GroovyScriptEngine.eval(GroovyScriptEngine.java:122)
	at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264)
	at org.scijava.script.ScriptModule.run(ScriptModule.java:160)
	at org.scijava.module.ModuleRunner.run(ModuleRunner.java:168)
	at org.scijava.module.ModuleRunner.call(ModuleRunner.java:127)
	at org.scijava.module.ModuleRunner.call(ModuleRunner.java:66)
	at org.scijava.thread.DefaultThreadService$3.call(DefaultThreadService.java:238)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

Maybe some Ops experts can shed some light on this?

1 Like

Would a short term fix be padding the image first and then unpadding it?
https://javadoc.scijava.org/ImageJ/net/imagej/ops/filter/pad/package-summary.html

Hi @imagejan and @bpavie

The short answer is that it will work if you add a ‘null’ parameter for parameter 3, and move the OutOfboundsFactory to parameter 4. See the java code at the bottom of this reply.

The long answer is that this is an imperfection on my part, due to the way I set up the Parameters in the FFT version of convolve. And also (perhaps) a small issue with the ops matcher, where it will throw an exception in certain cases (instead of just reporting the match issue).

The naive (spatial) version of convolve is meant for small kernels, so there is no need for a border size (there is no reason not to extend using the filter size). The FFT version of convolve is meant for large (potentially very large) filters. In your case the kernel was large enough that the spatial version of convolve was rejected.

In the FFT version there is also an option to adjust the border size (the user may not want the image extended too much, as it may become too large). I made borderSize the third parameter, when it should of been the fourth (after BorderFactory) so that the “Naive” and the “FFT” version of convolve would have the same signature.

Ideally the Op Matcher would of given a more informative message. However it looks like an uncaught exception was raised when it tried to convert BorderFactory to long[].

		// create an instance of imagej
		final ImageJ ij = new ImageJ();
		double sigma=3.0f;

		// launch it
		ij.launch(args);

		@SuppressWarnings("unchecked")
		final Img<T> image = (Img<T>) ij.io().open(
			"http://imagej.net/images/bridge.gif"); // convenient example stack

		ij.ui().show("bridge", image);
		
		RandomAccessibleInterval<FloatType> logKernel=ij.op().create().kernelLog(sigma, image.numDimensions(), new FloatType());
		RandomAccessibleInterval<FloatType> logFiltered=ij.op().filter().convolve(image, logKernel, null, new OutOfBoundsBorderFactory());
		
		ij.ui().show("log filtered", logFiltered);
2 Likes

Thanks @bnorthan for your answer!
the following groovy script is now working:

#@ OpService ops
#@ Double sigma
#@ ImgPlus inputData
#@output logFiltered

import net.imglib2.outofbounds.OutOfBoundsBorderFactory

//Tested on File>Open Samples>Dot Blot
logKernel=ops.create().kernelLog(inputData.numDimensions(), sigma)
logFiltered=ops.filter().convolve(inputData, logKernel, null, new OutOfBoundsBorderFactory())
3 Likes