Skip to content

Commit

Permalink
Merge pull request #195 from igorpisarev/fix-orthoslice-interpolation…
Browse files Browse the repository at this point in the history
…-revised

Fix interpolation artifacts between render tiles [revised]
  • Loading branch information
hanslovsky authored Jan 31, 2019
2 parents 223b7d6 + 872a8ca commit b3d464e
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 66 deletions.
44 changes: 23 additions & 21 deletions src/main/java/bdv/fx/viewer/ViewerPanelFX.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import net.imglib2.Point;
import net.imglib2.Positionable;
import net.imglib2.RealLocalizable;
Expand All @@ -65,10 +65,10 @@
import net.imglib2.ui.TransformListener;
import net.imglib2.util.Intervals;
import net.imglib2.view.Views;

import org.janelia.saalfeldlab.paintera.data.axisorder.AxisOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -102,7 +102,6 @@ public class ViewerPanelFX
private final OverlayPane overlayPane = new OverlayPane();

private final ViewerState state;

private final AffineTransform3D viewerTransform;

private ThreadGroup threadGroup;
Expand Down Expand Up @@ -228,7 +227,7 @@ public ViewerPanelFX(
{
this.imageDisplayGrid.set(renderUnit.getImagePropertyGrid());
});
this.imageDisplayGrid.addListener((obs, oldv, newv) -> {synchronized(renderUnit){this.display.setChild(makeGrid(newv));}});
this.imageDisplayGrid.addListener((obs, oldv, newv) -> {synchronized(renderUnit) {this.display.setChild(makeCanvas(newv));}});
this.widthProperty().addListener((obs, oldv, newv) -> this.renderUnit.setDimensions((long)getWidth(), (long)getHeight()));
this.heightProperty().addListener((obs, oldv, newv) -> this.renderUnit.setDimensions((long)getWidth(), (long)getHeight()));
setWidth(options.getWidth());
Expand Down Expand Up @@ -570,32 +569,35 @@ private static int[] range(int size)
return range;
}

private static GridPane makeGrid(final RenderUnit.ImagePropertyGrid grid)
private CanvasPane makeCanvas(final RenderUnit.ImagePropertyGrid grid)
{
final GridPane pane = new GridPane();

final CanvasPane canvasPane = new CanvasPane(1, 1);
if (grid == null)
return pane;
return canvasPane;

final CellGrid cellGrid = grid.getGrid();
pane.setMinWidth(1);
pane.setMinHeight(1);
pane.setPrefWidth(cellGrid.getImgDimensions()[0]);
pane.setPrefHeight(cellGrid.getImgDimensions()[0]);
final long[] gridPos = new long[2];
final long[] cellMin = new long[2];
final int[] cellDims = new int[2];
final int numTiles = grid.numTiles();
for (int i = 0; i < numTiles; ++i) {
for (int i = 0; i < grid.numTiles(); ++i) {
final long[] cellMin = new long[2];
final int[] cellDims = new int[2];
cellGrid.getCellGridPositionFlat(i, gridPos);
cellGrid.getCellDimensions(gridPos, cellMin, cellDims);

final ReadOnlyObjectProperty<Image> image = grid.imagePropertyAt(i);
final ImagePane imagePane = new ImagePane(cellDims[0], cellDims[1]);
imagePane.imageProperty().bind(image);
LOG.debug("Putting render block {} into cell at position {}", i, gridPos);
pane.add(imagePane, (int) gridPos[0], (int) gridPos[1]);
image.addListener((obs, oldv, newv) -> {
if (newv != null) {
final int[] padding = grid.getPadding();
canvasPane.getCanvas().getGraphicsContext2D().drawImage(
newv, // src
padding[0], padding[1], // src X, Y
newv.getWidth() - 2 * padding[0], newv.getHeight() - 2 * padding[1], // src width, height
cellMin[0], cellMin[1], // dst X, Y
cellDims[0], cellDims[1] // dst width, height
);
}
});
}
return pane;
return canvasPane;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ public BufferExposingWritableImage create(final int width, final int height, fin

}

@SuppressWarnings("unchecked")
public MultiResolutionRendererFX(
final TransformAwareRenderTargetGeneric<BufferExposingWritableImage> display,
final PainterThread painterThread,
Expand All @@ -123,7 +122,8 @@ public MultiResolutionRendererFX(
final ExecutorService renderingExecutorService,
final boolean useVolatileIfAvailable,
final AccumulateProjectorFactory<ARGBType> accumulateProjectorFactory,
final CacheControl cacheControl)
final CacheControl cacheControl,
final int[] padding)
{
super(
display,
Expand All @@ -136,6 +136,7 @@ public MultiResolutionRendererFX(
useVolatileIfAvailable,
accumulateProjectorFactory,
cacheControl,
padding,
BufferExposingWritableImage::asArrayImg,
new MakeWritableImage(),
img -> (int) img.getWidth(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ public interface ImageGenerator<T>

private final ImageGenerator<T> makeImage;

private final int[] padding;

/**
* @param display
* The canvas that will display the images we render.
Expand All @@ -273,8 +275,9 @@ public interface ImageGenerator<T>
* can be used to customize how sources are combined.
* @param cacheControl
* the cache controls IO budgeting and fetcher queue.
* @param padding
* specifies by how many pixels the rendered images are extended on each side to enable seamless interpolation
*/
@SuppressWarnings("unchecked")
MultiResolutionRendererGeneric(
final TransformAwareRenderTargetGeneric<T> display,
final PainterThread painterThread,
Expand All @@ -286,6 +289,7 @@ public interface ImageGenerator<T>
final boolean useVolatileIfAvailable,
final AccumulateProjectorFactory<ARGBType> accumulateProjectorFactory,
final CacheControl cacheControl,
final int[] padding,
final Function<T, ArrayImg<ARGBType, ? extends IntAccess>> wrapAsArrayImg,
final ImageGenerator<T> makeImage,
final ToIntFunction<T> width,
Expand Down Expand Up @@ -319,6 +323,8 @@ public interface ImageGenerator<T>
this.cacheControl = cacheControl;
newFrameRequest = false;
previousTimepoint = -1;

this.padding = padding;
}

/**
Expand All @@ -332,8 +338,8 @@ private synchronized boolean checkResize()
final int componentW = display.getWidth();
final int componentH = display.getHeight();
if (screenImages.get(0).get(0) == null
|| width.applyAsInt(screenImages.get(0).get(0)) != (int)(componentW * screenScales[0])
|| height.applyAsInt(screenImages.get(0).get(0)) != (int)(componentH * screenScales[0]))
|| width .applyAsInt(screenImages.get(0).get(0)) != Math.max((int) (componentW * screenScales[0]), 1) + 2 * padding[0]
|| height.applyAsInt(screenImages.get(0).get(0)) != Math.max((int) (componentH * screenScales[0]), 1) + 2 * padding[1])
{
renderIdQueue.clear();
renderIdQueue.addAll(Arrays.asList(0, 1, 2));
Expand All @@ -343,21 +349,25 @@ private synchronized boolean checkResize()
final double screenToViewerScale = screenScales[i];
final int w = Math.max((int) (screenToViewerScale * componentW), 1);
final int h = Math.max((int) (screenToViewerScale * componentH), 1);
final int paddedW = w + 2 * padding[0];
final int paddedH = h + 2 * padding[1];
if (doubleBuffered)
{
for (int b = 0; b < 3; ++b)
{
// reuse storage arrays of level 0 (highest resolution)
screenImages.get(i).set(b, i == 0
? makeImage.create(w, h)
: makeImage.create(w, h, screenImages.get(0).get(b)));
? makeImage.create(paddedW, paddedH)
: makeImage.create(paddedW, paddedH, screenImages.get(0).get(b)));
final T bi = screenImages.get(i).get(b);
// getBufferedImage.apply( screenImages[ i ][ b ] );
bufferedImages.get(i).set(b, bi);
bufferedImageToRenderId.put(bi, b);
}
}
else
{
screenImages.get(i).set(0, makeImage.create(w, h));
screenImages.get(i).set(0, makeImage.create(paddedW, paddedH));
bufferedImages.get(i).set(0, screenImages.get(i).get(0));
// getBufferedImage.apply( screenImages[ i ][ 0 ] );
}
Expand All @@ -366,8 +376,8 @@ private synchronized boolean checkResize()
final double yScale = (double) h / componentH;
scale.set(xScale, 0, 0);
scale.set(yScale, 1, 1);
scale.set(0.5 * xScale - 0.5, 0, 3);
scale.set(0.5 * yScale - 0.5, 1, 3);
scale.set(0.5 * xScale - 0.5 + padding[0], 0, 3);
scale.set(0.5 * yScale - 0.5 + padding[1], 1, 3);
screenScaleTransforms[i] = scale;
}

Expand Down
44 changes: 37 additions & 7 deletions src/main/java/bdv/fx/viewer/render/RenderUnit.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import java.util.stream.LongStream;

/**
* Render sreen as tiles instead of single image.
* Render screen as tiles instead of single image.
*/
public class RenderUnit {

Expand All @@ -40,6 +40,8 @@ public class RenderUnit {

private final long[] dimensions = {1, 1};

private final int[] padding = {1, 1};

private double[] screenScales = ScreenScalesConfig.defaultScreenScalesCopy();

private CellGrid grid;
Expand Down Expand Up @@ -243,7 +245,12 @@ private synchronized void update()
p.interrupt();
}

this.grid = new CellGrid(dimensions, blockSize);
// Adjust the dimensions to be a greater or equal multiple of the block size
// to make sure that the border images have the same scaling coefficients
final long[] adjustedDimensions = new long[dimensions.length];
Arrays.setAll(adjustedDimensions, d -> dimensions[d] > blockSize[d] ? (int) Math.ceil((double) dimensions[d] / blockSize[d]) * blockSize[d] : dimensions[d]);

this.grid = new CellGrid(adjustedDimensions, blockSize);

int numBlocks = (int) LongStream.of(this.grid.getGridDimensions()).reduce(1, (l1, l2) -> l1 * l2);
renderers = new MultiResolutionRendererFX[numBlocks];
Expand All @@ -258,8 +265,6 @@ private synchronized void update()
final int[] cellDims = new int[2];
for (int index = 0; index < renderers.length; ++index) {
this.grid.getCellGridPositionFlat(index, cellPos);
min[0] = this.grid.getCellMin(0, cellPos[0]);
min[1] = this.grid.getCellMin(1, cellPos[1]);
this.grid.getCellDimensions(cellPos, min, cellDims);
Arrays.setAll(max, d -> min[d] + cellDims[d] - 1);
final TransformAwareBufferedImageOverlayRendererFX renderTarget = new TransformAwareBufferedImageOverlayRendererFX();
Expand All @@ -275,7 +280,8 @@ private synchronized void update()
renderingExecutorService,
true,
accumulateProjectorFactory,
cacheControl
cacheControl,
padding
);
LOG.trace("Creating new renderer for block ({}) ({})", min, max);
renderTarget.setCanvasSize(cellDims[0], cellDims[1]);
Expand All @@ -292,7 +298,7 @@ private synchronized void update()

public synchronized ImagePropertyGrid getImagePropertyGrid()
{
return new ImagePropertyGrid(images, grid);
return new ImagePropertyGrid(images, grid, dimensions, padding);
}

private class Paintable implements PainterThread.Paintable
Expand Down Expand Up @@ -369,9 +375,15 @@ public static class ImagePropertyGrid

private final CellGrid grid;

private ImagePropertyGrid(final ObjectProperty<Image>[] images, final CellGrid grid) {
private long[] dimensions;

private final int[] padding;

private ImagePropertyGrid(final ObjectProperty<Image>[] images, final CellGrid grid, final long[] dimensions, final int[] padding) {
this.images = images;
this.grid = grid;
this.dimensions = dimensions;
this.padding = padding;
}

/**
Expand Down Expand Up @@ -401,6 +413,24 @@ public int numTiles()
{
return images.length;
}

/**
*
* @return dimensions of the displayed image (may be smaller than the grid size)
*/
public long[] getDimensions()
{
return dimensions;
}

/**
*
* @return padding value by which the rendered images are extended on each side
*/
public int[] getPadding()
{
return padding;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

import javafx.scene.image.Image;
import net.imglib2.realtransform.AffineTransform3D;
import net.imglib2.type.numeric.ARGBType;
import net.imglib2.ui.TransformListener;
import org.janelia.saalfeldlab.fx.util.InvokeOnJavaFXApplicationThread;
import org.slf4j.Logger;
Expand All @@ -47,6 +48,8 @@ public class TransformAwareBufferedImageOverlayRendererFX

private static Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

private static final int FULL_OPACITY = 0xff << 24;

protected AffineTransform3D pendingTransform;

protected AffineTransform3D paintedTransform;
Expand Down Expand Up @@ -99,6 +102,23 @@ public void drawOverlays(final Consumer<Image> g)

LOG.trace("Setting image to {}", sourceImage);
g.accept(null);

/*
* NOTE: need to make the final image fully opaque to ensure that it is properly drawn on the screen later on.
* It is only necessary because existing BlendMode options in JavaFX always perform some kind of blending with old contents of the displayed component.
*
* Ideally, we would use BlendMode.SRC if it was available (that would overwrite the destination with the source regardless of alpha).
* See discussion about possibility of adding this mode in the future versions and more information here:
*
* https://bugs.openjdk.java.net/browse/JDK-8092156
*
* https://books.google.com/books?id=UxM2iXiFqbMC&pg=PA97&lpg=PA97&dq=Porter-Duff+%22source+over+destination%22&source=bl&ots=hYj5U2X5Lk&sig=ACfU3U095X67T4FghqCpgYrAhEWbNtd_xQ&hl=en&sa=X&ved=2ahUKEwjyg4rRufXfAhXvuFkKHfK8BysQ6AEwAnoECAcQAQ#v=snippet&q=%22overwrites%20the%20destination%20with%20the%20source%2C%20regardless%20of%20alpha%22&f=false
*
* https://docs.oracle.com/javase/8/javafx/api/javafx/scene/effect/BlendMode.html
*/
for (final ARGBType px : sourceImage.asArrayImg())
px.set(px.get() | FULL_OPACITY);

sourceImage.setPixelsDirty();
g.accept(sourceImage);
// TODO add countdown latch to wait for setImage to return
Expand Down
Loading

0 comments on commit b3d464e

Please sign in to comment.