CLIJ: Difference of Gaussians2D

I’m adapting my regular ImageJ macro to CLIJ2 and noticed something that I don’t understand.
Note, this is not important, but I’m curious.

I was doing:
Gaussian Blur, then subtract that from original, as a form of background subtraction.
This was easy to implement in CLIJ:

//Set Guassian blur sigma values
//CLIJ uses pixel values! 
//Calculate sigma in pixels as equivalent to 12 micron
sigma1x = 0;
sigma1y = 0;
sigma2x = 12*scale;
sigma2y = 12*scale;

// Perform a Gaussian blur
Ext.CLIJ2_gaussianBlur2D(orig, blurred, sigma2x, sigma2y);
//Background subtraction by subtracting the blurred image from original
Ext.CLIJ2_subtractImages(orig, blurred, DoG);
Ext.CLIJ2_release(blurred);
//
Ext.CLIJ2_release(orig);

Works perfectly.
Then I saw the DoG command.
Using:

Ext.CLIJ2_differenceOfGaussian2D(orig, DoG, sigma1x, sigma1y, sigma2x, sigma2y);

This gives mathematically the same output as above, but is slower. Time tracing:

 > DifferenceOfGaussian2D
  > GaussianBlur2D
   > Copy
   < Copy                       14.093416 ms
   > Copy
   < Copy                       7.487375 ms
  < GaussianBlur2D              21.730375 ms
  > GaussianBlur2D
  < GaussianBlur2D              1153.302042 ms
  > SubtractImages
  < SubtractImages              14.045417 ms
 < DifferenceOfGaussian2D       1189.77225 ms

 > Copy
 < Copy                         6.547083 ms
 > GaussianBlur2D
 < GaussianBlur2D               902.202417 ms
 > SubtractImages
 < SubtractImages               9.496958 ms

(Tested in reverse order too.)
Setting aside that a sigma=0 blur still takes time, I was surprised doing it piecewise was also quicker for the sigma!=0 blur.
Are the blurs implemented differently between the commands?

Note: TopHatBox is 2X faster but I haven’t gotten as good results—can’t get it as sharp I guess. Still playing with the size.

1 Like

No, DifferenceOfGaussian uses GaussianBlur. The code lives here.

For proper benchmarking, can you execute the code multiple times and compare then? It’s possible that you are tricked by the warmup effect demonstrated in this tutorial. The first execution of an operation may take longer because OpenCL code needs to be compiled in the background.

Thanks! From the code it certainly should be identical, but yet:

I re-ran the code 4 times, fresh Fiji session, with the two step (blur, subtract) version first, then the DoG. The effect seems persistent, which is odd.

Comparing just the 2nd blur, which should be same sigma:
Two-step: 829 ms
DoG: 1156 ms

Two step:

// Perform a Gaussian blur
Ext.CLIJ2_gaussianBlur2D(orig, blurred, sigma2x, sigma2y);
//Background subtraction by subtracting the blurred image from original
Ext.CLIJ2_subtractImages(orig, blurred, DoG);
 > GaussianBlur2D
 < GaussianBlur2D               838.8485 ms
 > SubtractImages
 < SubtractImages               9.339792 ms
 > GaussianBlur2D
 < GaussianBlur2D               822.413584 ms
 > SubtractImages
 < SubtractImages               8.987208 ms
  > GaussianBlur2D
 < GaussianBlur2D               832.805917 ms
 > SubtractImages
 < SubtractImages               8.58325 ms
  > GaussianBlur2D
 < GaussianBlur2D               821.116958 ms
 > SubtractImages
 < SubtractImages               8.163416 ms

One line using DoG (with sigma = zero for first blur).

//Background subtraction by subtracting the blurred image from original
sigma1x = 0;
sigma1y = 0;
sigma2x = 12*scale;
sigma2y = 12*scale;
	Ext.CLIJ2_differenceOfGaussian2D(orig, DoG, sigma1x, sigma1y, sigma2x, sigma2y);

  > GaussianBlur2D
   > Copy
   < Copy                       17.228458 ms
   > Copy
   < Copy                       9.050209 ms
  < GaussianBlur2D              26.422625 ms
  > GaussianBlur2D
  < GaussianBlur2D              1161.934 ms
  > SubtractImages
  < SubtractImages              12.4765 ms
 < DifferenceOfGaussian2D       1201.460167 ms
  > GaussianBlur2D
   > Copy
   < Copy                       13.165125 ms
   > Copy
   < Copy                       6.995792 ms
  < GaussianBlur2D              20.27775 ms
  > GaussianBlur2D
  < GaussianBlur2D              1156.1055 ms
  > SubtractImages
  < SubtractImages              11.716625 ms
 < DifferenceOfGaussian2D       1188.777708 ms
  > GaussianBlur2D
   > Copy
   < Copy                       13.905875 ms
   > Copy
   < Copy                       7.434167 ms
  < GaussianBlur2D              21.516542 ms
  > GaussianBlur2D
  < GaussianBlur2D              1153.609291 ms
  > SubtractImages
  < SubtractImages              12.057542 ms
 < DifferenceOfGaussian2D       1187.822167 ms
  > GaussianBlur2D
   > Copy
   < Copy                       13.39875 ms
   > Copy
   < Copy                       7.059959 ms
  < GaussianBlur2D              20.558375 ms
  > GaussianBlur2D
  < GaussianBlur2D              1153.0965 ms
  > SubtractImages
  < SubtractImages              12.862291 ms
 < DifferenceOfGaussian2D       1187.060625 ms

Edit: The above was with a large image (7k x 4x px), so I tried with a smaller one (1920 x 1440) and the effect persists.
For the 2 step, the single blur takes 32-34 ms, while for the DoG, the 2nd blur takes >50 ms.
Two-step:

  > GaussianBlur2D
 < GaussianBlur2D               33.303375 ms
 > SubtractImages
 < SubtractImages               1.114875 ms
 > GaussianBlur2D
 < GaussianBlur2D               32.651541 ms
 > SubtractImages
 < SubtractImages               1.15325 ms
 > GaussianBlur2D
 < GaussianBlur2D               34.34125 ms
 > SubtractImages
 < SubtractImages               1.272334 ms

DoG

 > GaussianBlur2D
   > Copy
   < Copy                       7.882417 ms
   > Copy
   < Copy                       1.30975 ms
  < GaussianBlur2D              9.272375 ms
  > GaussianBlur2D
  < GaussianBlur2D              63.434875 ms
  > SubtractImages
  < SubtractImages              1.106 ms
 < DifferenceOfGaussian2D       73.89825 ms
 > DifferenceOfGaussian2D
  > GaussianBlur2D
   > Copy
   < Copy                       2.592584 ms
   > Copy
   < Copy                       1.276375 ms
  < GaussianBlur2D              3.953292 ms
  > GaussianBlur2D
  < GaussianBlur2D              55.220375 ms
  > SubtractImages
  < SubtractImages              0.969042 ms
 < DifferenceOfGaussian2D       60.219292 ms
 > DifferenceOfGaussian2D
  > GaussianBlur2D
   > Copy
   < Copy                       2.446 ms
   > Copy
   < Copy                       1.219125 ms
  < GaussianBlur2D              3.743375 ms
  > GaussianBlur2D
  < GaussianBlur2D              53.214958 ms
  > SubtractImages
  < SubtractImages              1.039083 ms
 < DifferenceOfGaussian2D       58.076916 ms

I just checked your code again and found the reason: Your “two-step” approach is not a DoG. You are subtracting a Gaussian blurred image from the original, which corresponds to subtractGaussianBackground.

OK, so cool subtractGaussianBackground
does what I’m doing in one command—a plus.
It says deprecated, presumably because mathematically it’s the same as a DoG with sigma1=0.
Right?
Or am I wrong here. I mean that was my logic of trying Ext.CLIJ2_differenceOfGaussian2D.

I’ve checked, manually doing:
-GaussianBlur(sigma1=0)
-GaussianBlur(sigma2=12)
-take difference
gives the same pixels as just the last two steps.

So if internally DoG is using clij2.gaussianBlur
and pressumably so is Ext.CLIJ2_gaussianBlur2D, so a with the same sigma it should take the same amount of time? And I’m seeing the blur by gaussianBlur2D somehow faster than the one “inside” Ext.CLIJ2_differenceOfGaussian2D.

Using Ext.CLIJx_subtractGaussianBackground(orig, DoG, sigma2x, sigma2y, 0);
Seems to behave the same as the blur in DoG—somehow slower than the blur in Ext.CLIJ2_gaussianBlur2D.

GaussianBlur3D
< GaussianBlur3D 1155.895625 ms
SubtractImages
< SubtractImages 16.2705 ms
GaussianBlur3D
< GaussianBlur3D 1159.818458 ms
SubtractImages
< SubtractImages 14.535375 ms
GaussianBlur3D
< GaussianBlur3D 1156.988209 ms
SubtractImages
< SubtractImages 12.876667 ms

Edit:
OK, Ext.CLIJx_subtractGaussianBackground uses clij2.gaussianBlur inside, just like Ext.CLIJ2_differenceOfGaussian2D.

But, if I’m reading correctly, Ext.CLIJ2_gaussianBlur2D does not. It uses
clij2.gaussianBlur2D:

I can’t quite figure out where those functions are defined though…

The code is not identical and that’s why it takes longer in the one situation than in the other. If you want to optimize your code towards speed, I recommend using the function which is faster.

If you want to trace down implementation, go to the code on github.com and hover with your mouse over the function call of interest. You will then be sent to where this is implemented:

I’m not really trying to optimize for speed, just trying to familiarize. Was playing with different background subtraction options and was just curious why they two were different.

Thanks for the function tip btw—really nice feature, is that a GitHub thing or something you’ve facilitated?

So going down the rabbit hole, what I can’t figure out is when/which .cl files are called—that’s what’s doing the work right?
And there’s a 2d and 3d gaussian blur .cl…

1 Like

Correct! You can for example see in the GaussianBlur3D class how the filename of the .cl file is assembled:

For understanding the clij internals, I recommend reading the introduction for java developers and the plugin development documentation.

Let me know if there is anything unclear. I didn’t receive so much feedback yet specifically for this part of the documentation. I’d be happy to work on it.