Procedurally generated image

Code
%mavenRepo scijava.public https://maven.scijava.org/content/groups/public
%maven org.scijava:scijava-common:2.99.0
%maven net.imglib2:imglib2-algorithm:0.17.1-SNAPSHOT
%maven net.imglib2:imglib2:7.1.2
%maven net.imglib2:imglib2-ij:2.0.3
%maven sc.fiji:bigdataviewer-core:10.6.2
%maven org.janelia.saalfeldlab:n5:3.3.0

We define the Juliaset as a function in 2D real space using a BiConsumer lambda function. The BiConsumer receives two parameters, the first one (x) is the 2D coordinate, the second one (fx) is the target of the function whose value will be set in place, here we use an UnsignedByteType. We also have to provide a Supplier for instances of the target such that multiple threads can each create their own.

The result is a function over continuous coordinates that is unbounded.

Code
import bdv.util.*;
import bdv.util.volatiles.*;
import bdv.viewer.*;
import net.imglib2.*;
import net.imglib2.algorithm.gauss3.*;
import net.imglib2.cache.img.*;
import net.imglib2.loops.*;
import net.imglib2.parallel.*;
import net.imglib2.position.*;
import net.imglib2.realtransform.*;
import net.imglib2.type.numeric.*;
import net.imglib2.type.numeric.integer.*;
import net.imglib2.util.*;
import static net.imglib2.view.fluent.RandomAccessibleIntervalView.Extension.*;

var juliaset = new FunctionRealRandomAccessible<UnsignedByteType>(
    2,
    (x, fx) -> {
      int i = 0;
      double v = 0;
      double c = x.getDoublePosition(0);
      double d = x.getDoublePosition(1);
      for (; i < 255 && v < 4096; ++i) {
        final double e = c * c - d * d;
        d = 2 * c * d;
        c = e + 0.3;
        d += 0.6;
        v = Math.sqrt(c * c + d * d);
        ++i;
      }
      fx.set(i);
    },
    UnsignedByteType::new);

BdvSource bdv = BdvFunctions.show(
    juliaset,
    Intervals.createMinMax(-1, -1, 1, 1),
    "juliaset",
    Bdv.options().is2D());
bdv.setDisplayRange(0, 127);

Caching results of expensive operations

Trying to show benefits of caching with very contrived example…

Use the juliaset from above. To have something that can be put in a cache, we rasterize (virtually)

Code
AffineTransform2D transform = new AffineTransform2D();
transform.set(
    4000, 0, 8000,
    0, 4000, 8000);
RandomAccessible<UnsignedByteType> affine = RealViews.affine(juliaset, transform);

final RandomAccessibleInterval<UnsignedByteType> transformed = affine
    .view()
    .interval(Intervals.createMinSize(0, 0, 16000, 16000))
    .convert(UnsignedByteType::new, (i, o) -> o.set(Math.min(i.get() * 3, 255)));

BdvSource bdv = BdvFunctions.show(
    transformed,
    "transformed and rasterized",
    Bdv.options().is2D());
bdv.getBdvHandle().getViewerPanel().setDisplayMode(DisplayMode.SINGLE);

It is relatively expensive (not really, but use your imagination) to compute the value of a pixel in transformed. And the value is re-computed everytime it is accessed.

To avoid that, we can wrap that into a CachedCellImg. Pixel values are computed once and cached for subsequent accesses.
(The CachedCellImg pre-computes whole blocks of data when a single pixel from the block is accessed. This is often what you want, but you should be aware of it…)

Code
final RandomAccessibleInterval<UnsignedByteType> cached = new ReadOnlyCachedCellImgFactory().create(
    transformed.dimensionsAsLongArray(),
    new UnsignedByteType(),
    cell -> LoopBuilder.setImages(transformed.view().interval(cell), cell).forEachPixel((i, o) -> o.set(i)),
    ReadOnlyCachedCellImgOptions.options().cellDimensions(512));

BdvFunctions.show(
    VolatileViews.wrapAsVolatile(cached),
    "cached",
    Bdv.options().addTo(bdv));
bdv.util.BdvStackSource@1bb6ec18

We define two additional CachedCellImgs: one that generates data by smoothing the procedural image, one that generates data by smoothing the cached image.

Code
final RandomAccessibleInterval<UnsignedByteType> convolved = new ReadOnlyCachedCellImgFactory().create(
    transformed.dimensionsAsLongArray(),
    new UnsignedByteType(),
    cell -> Parallelization.runSingleThreaded(() -> Gauss3.gauss(new double[]{8, 8}, transformed, cell)),
    ReadOnlyCachedCellImgOptions.options().cellDimensions(512));

BdvSource source = BdvFunctions.show(
    VolatileViews.wrapAsVolatile(convolved),
    "convolved",
    Bdv.options().addTo(bdv));
source.setColor(new ARGBType(0xff00ff));

final RandomAccessibleInterval<UnsignedByteType> cachedConvolved = new ReadOnlyCachedCellImgFactory().create(
    transformed.dimensionsAsLongArray(),
    new UnsignedByteType(),
    cell -> Parallelization.runSingleThreaded(() -> Gauss3.gauss(new double[]{8, 8}, cached.view().extend(mirrorDouble()), cell)),
    ReadOnlyCachedCellImgOptions.options().cellDimensions(512));

BdvSource source = BdvFunctions.show(
    VolatileViews.wrapAsVolatile(cachedConvolved),
    "cached convolved",
    Bdv.options().addTo(bdv));
source.setColor(new ARGBType(0x00ff00));